From edde940e393ab7f623cc6baeaeb87f71d62f34b9 Mon Sep 17 00:00:00 2001 From: "Klare, Heiko" Date: Fri, 9 Jan 2026 18:37:34 +0100 Subject: [PATCH] [Win32] Process DPI change events via OS instead of Display#asyncExec Asynchronous processing of DPI change events is currently done by processing the event for each control via Display#asyncExec. This has several drawbacks: - the events are processed rather late (even after earlier scheduled, lower-priority business events - the events may not be processed while dragging a shell to another monitor in case a browser currently has focus - the events are not processed at all if a Synchronizer is used that does not process async events (such as for an Eclipse workspace during startup) This change replaces the usage of Display#asyncExec for asynchronous processing of DPI events with the a timer message beings sent with a callback to the processed operation via the OS. WM_TIMER events are used as they are processed with low priority by the OS but still with higher priority by SWT than any business-logic-side tasks scheduled at a Display instance. --- .../org/eclipse/swt/widgets/Control.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java index 4b8c7ad85d..33e02b03f2 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java @@ -15,6 +15,7 @@ package org.eclipse.swt.widgets; +import java.lang.reflect.*; import java.util.*; import java.util.concurrent.atomic.*; import java.util.stream.*; @@ -5992,7 +5993,7 @@ private void process(Control control, Runnable operation) { asyncExec &= (comp.layout != null); } if (asyncExec) { - control.getDisplay().asyncExec(operation::run); + DPIChangeProcessingCallback.schedule(control, operation); } else { operation.run(); } @@ -6010,6 +6011,49 @@ private boolean decrement() { } } +private static class DPIChangeProcessingCallback { + private final Runnable operation; + private final long address; + + private DPIChangeProcessingCallback(Control control, Runnable dpiChangeProcessing) { + // Has to have TIMERPROC signature, see https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-timerproc + Callback callback = new Callback(this, "run", void.class, new Type[] { int.class, int.class, int.class, int.class} ); + this.operation = () -> { + if (!control.isDisposed()) { + dpiChangeProcessing.run(); + } + callback.dispose(); + }; + this.address = callback.getAddress(); + } + + @SuppressWarnings("unused") // Executed as callback method referenced by signature description + public void run(int hwnd, int msg , int idEvent, int dwTime) { + OS.KillTimer(hwnd, idEvent); + operation.run(); + } + + private long getAddress() { + return address; + } + + static void schedule(Control control, Runnable runnable) { + try { + DPIChangeProcessingCallback callback = new DPIChangeProcessingCallback(control, runnable); + OS.SetTimer(0, 0, 0, callback.getAddress()); + } catch (SWTError error) { + // In case of too many callbacks, fall back to running the operation synchronously + if (error.code == SWT.ERROR_NO_MORE_CALLBACKS) { + runnable.run(); + } else { + throw error; + } + } + + } + +} + void sendZoomChangedEvent(Event event, Shell shell) { if (event.data instanceof DPIChangeExecution dpiExecData) { dpiExecData.increment();