From e52db3496c38c948a9dd27b9781e93c5b6ee2bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=8A?= =?UTF-8?q?=D1=80=20=D0=9A=D1=83=D1=80=D1=82=D0=B0=D0=BA=D0=BE=D0=B2?= Date: Tue, 2 Jun 2026 23:16:30 +0300 Subject: [PATCH] [Gtk4] Overhaul Shell sizing Non-resizable shells (e.g. DIALOG_TRIM) appear too tall when setSize(computeSize()) is called before open() because window decoration heights were not correctly accounted for with GTK4 CSD. Maximized shells had their content cut off at the bottom because the shell content was not resized to fill the full maximized area. Use the layout signal, which fires after the the new dimensions are set, to correctly resize the shell content to fill the maximized window. --- .../gtk/org/eclipse/swt/internal/gtk/OS.java | 1 + .../gtk/org/eclipse/swt/widgets/Display.java | 14 +- .../gtk/org/eclipse/swt/widgets/Shell.java | 130 ++++++++++++------ .../gtk/org/eclipse/swt/widgets/Widget.java | 3 + 4 files changed, 102 insertions(+), 46 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java index 3b231190be8..bc668ae3105 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java @@ -468,6 +468,7 @@ public static String getEnvironmentalVariable (String envVarName) { public static final byte[] notify_theme_change = ascii("notify::gtk-application-prefer-dark-theme"); public static final byte[] response = ascii("response"); public static final byte[] compute_size = ascii("compute-size"); + public static final byte[] layout = ascii("layout"); /** Properties */ public static final byte[] active = ascii("active"); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java index 2b123dceda7..bb64c561eb2 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java @@ -134,13 +134,13 @@ public class Display extends Device implements Executor { long eventProc, windowProc2, windowProc3, windowProc4, windowProc5, windowProc6; long changeValueProc; long snapshotDrawProc, keyPressReleaseProc, focusProc, windowActiveProc, enterMotionProc, leaveProc, - scrollProc, resizeProc, activateProc, gesturePressReleaseProc; + scrollProc, resizeProc, layoutProc, activateProc, gesturePressReleaseProc; long notifyProc; long computeSizeProc; Callback windowCallback2, windowCallback3, windowCallback4, windowCallback5, windowCallback6; Callback changeValue; Callback snapshotDraw, keyPressReleaseCallback, focusCallback, windowActiveCallback, enterMotionCallback, computeSizeCallback, - scrollCallback, leaveCallback, resizeCallback, activateCallback, gesturePressReleaseCallback; + scrollCallback, leaveCallback, resizeCallback, layoutCallback, activateCallback, gesturePressReleaseCallback; Callback notifyCallback; EventTable eventTable, filterTable; static String APP_NAME = "SWT"; //$NON-NLS-1$ @@ -3631,6 +3631,8 @@ void initializeCallbacks () { resizeCallback = new Callback(this, "resizeProc", void.class, new Type[] {long.class, int.class, int.class}); //$NON-NLS-1$ resizeProc = resizeCallback.getAddress(); + layoutCallback = new Callback(this, "layoutProc", void.class, new Type[] {long.class, int.class, int.class, long.class}); //$NON-NLS-1$ + layoutProc = layoutCallback.getAddress(); activateCallback = new Callback(this, "activateProc", void.class, new Type[] {long.class, long.class, long.class}); //$NON-NLS-1$ activateProc = activateCallback.getAddress(); @@ -4671,6 +4673,9 @@ void releaseDisplay () { resizeCallback.dispose(); resizeCallback = null; resizeProc = 0; + layoutCallback.dispose(); + layoutCallback = null; + layoutProc = 0; activateCallback.dispose(); activateCallback = null; @@ -6155,6 +6160,11 @@ void resizeProc(long handle, int width, int height) { if (widget != null) widget.gtk_size_allocate(handle, 0); } +void layoutProc(long surface, int width, int height, long user_data) { + Widget widget = getWidget(user_data); + if (widget != null) widget.gtk_layout(surface, width, height); +} + long notifyProc (long object, long param_spec, long user_data) { Widget widget = getWidget (object); if (widget == null) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java index 23729c5ad9a..5f053ff7bd3 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java @@ -723,14 +723,6 @@ Rectangle computeTrimInPixels (int x, int y, int width, int height) { trim.y -= menuBarHeight; trim.height += menuBarHeight; } - if (GTK.GTK4 && OS.isWayland()) { - long titlebar = GTK4.gtk_window_get_titlebar(shellHandle); - if (titlebar != 0) { - int titleBarHeight = GTK4.gtk_widget_get_height (titlebar); - trim.y -= titleBarHeight; - trim.height += titleBarHeight; - } - } return trim; } @@ -971,6 +963,7 @@ void hookEvents () { long gdkSurface = gtk_widget_get_surface (shellHandle); OS.g_signal_connect (gdkSurface, OS.notify_state, display.notifyProc, shellHandle); OS.g_signal_connect (gdkSurface, OS.compute_size, display.computeSizeProc, shellHandle); + OS.g_signal_connect (gdkSurface, OS.layout, display.layoutProc, shellHandle); OS.g_signal_connect(shellHandle, OS.notify_default_height, display.notifyProc, Widget.NOTIFY_DEFAULT_HEIGHT); OS.g_signal_connect(shellHandle, OS.notify_default_width, display.notifyProc, Widget.NOTIFY_DEFAULT_WIDTH); OS.g_signal_connect(shellHandle, OS.notify_maximized, display.notifyProc, Widget.NOTIFY_MAXIMIZED); @@ -1812,7 +1805,6 @@ long gtk3_key_press_event (long widget, long event) { @Override long gtk_size_allocate (long widget, long allocation) { int width, height; - GdkRectangle monitorSize = new GdkRectangle(); int[] widthA = new int [1]; int[] heightA = new int [1]; @@ -1840,11 +1832,14 @@ long gtk_size_allocate (long widget, long allocation) { heightA[0] = Math.max(1, heightA[0] - headerNaturalHeight[0]); } } else { - long display = GDK.gdk_display_get_default(); - long monitor = GDK.gdk_display_get_monitor_at_surface(display, paintSurface()); - GDK.gdk_monitor_get_geometry(monitor, monitorSize); - widthA[0] = monitorSize.width; - heightA[0] = monitorSize.height - headerNaturalHeight[0]; + /* + * For maximized windows, neither gtk_window_get_default_size() nor + * gdk_surface_get_width/height() have the new dimensions yet when + * notify::maximized fires. The GdkSurface::layout signal fires + * synchronously after the compositor commits the new allocation; + * gtk_layout() handles the resize from there. + */ + return 0; } } else { GTK3.gtk_window_get_size(shellHandle, widthA, heightA); @@ -1864,6 +1859,37 @@ long gtk_size_allocate (long widget, long allocation) { return 0; } +@Override +void gtk_layout (long surface, int surfaceWidth, int surfaceHeight) { + /* + * Used exclusively for the maximized case: + * notify::maximized fires before the compositor commits the new size, so + * gtk_size_allocate returns 0 early for maximized windows and defers here. + * + * For non-maximized resizes, sizing is already handled correctly via + * gtk_window_get_default_size, so those are skipped here to avoid overriding + * with different surface dimensions. + * + * Surface width and height - total GTK window size including the header bar. + */ + if (!GTK4.gtk_window_is_maximized(shellHandle)) return; + + long header = GTK4.gtk_window_get_titlebar(shellHandle); + int headerH = 0; + if (header != 0) { + int[] headerNaturalHeight = new int[1]; + GTK4.gtk_widget_measure(header, GTK.GTK_ORIENTATION_VERTICAL, -1, null, headerNaturalHeight, null, null); + headerH = headerNaturalHeight[0]; + } + int newWidth = surfaceWidth; + int newHeight = Math.max(1, surfaceHeight - headerH); + if ((!resized || oldWidth != newWidth || oldHeight != newHeight) && newWidth > 0) { + oldWidth = newWidth; + oldHeight = newHeight; + resizeBounds(newWidth, newHeight, true); + } +} + private void updateDecorations(long gdkResource) { if (OS.isX11()) { /* @@ -2345,25 +2371,21 @@ int setBounds (int x, int y, int width, int height, boolean move, boolean resize if (geometry.getMaxHeight() > 0) { height = Math.min(height, geometry.getMaxHeight()); } - /* - * If the shell is created without a RESIZE style bit, and the - * minWidth/minHeight/maxWidth/maxHeight have been set, allow the resize. - */ - if ((style & SWT.RESIZE) != 0 || (geometry.getMinHeight() != 0 || geometry.getMinWidth() != 0 || geometry.getMaxHeight() != 0 || geometry.getMaxWidth() != 0)) { - if (GTK.GTK4) { - /* - * On GTK4, GtkWindow size includes the header bar. In order to keep window size allocation of the client area - * consistent with previous versions of SWT, we need to include the header bar height in addition to the given height value. - */ - long header = GTK4.gtk_window_get_titlebar(shellHandle); - int[] headerNaturalHeight = new int[1]; - if (header != 0) { - GTK4.gtk_widget_measure(header, GTK.GTK_ORIENTATION_VERTICAL, -1, null, headerNaturalHeight, null, null); - } - GTK.gtk_window_set_default_size(shellHandle, width, height + headerNaturalHeight[0]); - } else { - GTK3.gtk_window_resize (shellHandle, width, height); + if (GTK.GTK4) { + /* + * GtkWindow size includes the header bar. To stay consistent with previous + * versions of SWT, header bar height has to be added to the given height value. + * This applies to all shell styles that have a title bar (e.g. SHELL_TRIM, + * DIALOG_TRIM). + */ + long header = GTK4.gtk_window_get_titlebar(shellHandle); + int[] headerNaturalHeight = new int[1]; + if (header != 0) { + GTK4.gtk_widget_measure(header, GTK.GTK_ORIENTATION_VERTICAL, -1, null, headerNaturalHeight, null, null); } + GTK.gtk_window_set_default_size(shellHandle, width, height + headerNaturalHeight[0]); + } else if ((style & SWT.RESIZE) != 0 || (geometry.getMinHeight() != 0 || geometry.getMinWidth() != 0 || geometry.getMaxHeight() != 0 || geometry.getMaxWidth() != 0)) { + GTK3.gtk_window_resize (shellHandle, width, height); } boolean changed = width != oldWidth || height != oldHeight; if (changed) { @@ -2520,19 +2542,17 @@ void setInitialBounds() { height = (int) (dest.height * SHELL_TO_MONITOR_RATIO); } - if ((style & SWT.RESIZE) != 0) { - /* - * On GTK4, GtkWindow size includes the header bar. In order to keep window size allocation of the client area - * consistent with previous versions of SWT, we need to include the header bar height in addition to the given height value. - */ - long header = GTK4.gtk_window_get_titlebar(shellHandle); - int[] headerNaturalHeight = new int[1]; - if (header != 0) { - GTK4.gtk_widget_measure(header, GTK.GTK_ORIENTATION_VERTICAL, -1, null, headerNaturalHeight, null, null); - } - - GTK.gtk_window_set_default_size(shellHandle, width, height + headerNaturalHeight[0]); + /* + * GtkWindow size includes the header bar. To stay + * consistent with previous versions of SWT the header bar height is added the given height value. + * This applies to all shell styles. + */ + long header = GTK4.gtk_window_get_titlebar(shellHandle); + int[] headerNaturalHeight = new int[1]; + if (header != 0) { + GTK4.gtk_widget_measure(header, GTK.GTK_ORIENTATION_VERTICAL, -1, null, headerNaturalHeight, null, null); } + GTK.gtk_window_set_default_size(shellHandle, width, height + headerNaturalHeight[0]); } else { long display = GDK.gdk_display_get_default(); if (display != 0) { @@ -3158,6 +3178,24 @@ int trimHeight () { // Shells with both ON_TOP and RESIZE set only use border, not trim. // See bug 319612. if (isCustomResize()) return 0; + if (GTK.GTK4 && OS.isWayland()) { + /* + * On GTK4 Wayland, window decorations are implemented as GTK CSD widgets. The + * title bar (GtkHeaderBar) is part of the GtkWindow and its height must be + * queried dynamically. Use gtk_widget_get_height() if already allocated, + * otherwise gtk_widget_measure() which works even before the window is + * realized/shown. + */ + long titlebar = GTK4.gtk_window_get_titlebar(shellHandle); + if (titlebar != 0) { + int height = GTK4.gtk_widget_get_height(titlebar); + if (height > 0) return height; + int[] naturalHeight = new int[1]; + GTK4.gtk_widget_measure(titlebar, GTK.GTK_ORIENTATION_VERTICAL, -1, null, naturalHeight, null, null); + return naturalHeight[0]; + } + return 0; + } boolean hasTitle = false, hasResize = false, hasBorder = false; hasTitle = (style & (SWT.MIN | SWT.MAX | SWT.TITLE | SWT.MENU)) != 0; hasResize = (style & SWT.RESIZE) != 0; @@ -3178,6 +3216,10 @@ int trimWidth () { // Shells with both ON_TOP and RESIZE set only use border, not trim. // See bug 319612. if (isCustomResize()) return 0; + if (GTK.GTK4 && OS.isWayland()) { + // On GTK4 Wayland CSD, the title bar adds height only. + return 0; + } boolean hasTitle = false, hasResize = false, hasBorder = false; hasTitle = (style & (SWT.MIN | SWT.MAX | SWT.TITLE | SWT.MENU)) != 0; hasResize = (style & SWT.RESIZE) != 0; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java index 13f79b40be8..3471c7e010d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java @@ -1112,6 +1112,9 @@ long gtk_size_allocate (long widget, long allocation) { return 0; } +void gtk_layout (long surface, int width, int height) { +} + long gtk_status_icon_popup_menu (long handle, long button, long activate_time) { return 0; }