diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java
index 2bfa4ee4980..0d782a21718 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java
@@ -1691,8 +1691,9 @@ public Point toControl(int x, int y) {
origin_x[0] = origin.x;
origin_y[0] = origin.y;
} else {
- long window = eventWindow();
- GDK.gdk_window_get_origin(window, origin_x, origin_y);
+ Point origin = getWindowOrigin();
+ origin_x[0] = origin.x;
+ origin_y[0] = origin.y;
}
x -= origin_x[0];
@@ -1756,8 +1757,9 @@ public Point toDisplay(int x, int y) {
origin_x[0] = origin.x;
origin_y[0] = origin.y;
} else {
- long window = eventWindow();
- GDK.gdk_window_get_origin(window, origin_x, origin_y);
+ Point origin = getWindowOrigin();
+ origin_x[0] = origin.x;
+ origin_y[0] = origin.y;
}
if ((style & SWT.MIRRORED) != 0) x = getClientWidth() - x;
@@ -6919,9 +6921,31 @@ Point getWindowOrigin () {
long window = eventWindow ();
GDK.gdk_window_get_origin (window, x, y);
+ Point monitorOrigin = monitorOrigin ();
+ if (monitorOrigin != null) {
+ x [0] += monitorOrigin.x;
+ y [0] += monitorOrigin.y;
+ }
+
return new Point (x [0], y [0]);
}
+/**
+ * Offset that maps window relative GDK coordinates into the space of the monitor showing the
+ * receiver, or null when none is needed. Wayland reports no global position, so
+ * without it {@link Monitor} geometry and control coordinates cannot be compared.
+ */
+Point monitorOrigin () {
+ if (GTK.GTK4 || !OS.isWayland ()) return null;
+ // An unmapped Shell has no GdkWindow; the nearest ancestor that has one is on the same monitor.
+ long window = 0;
+ for (Control control = this; control != null; control = control.parent) {
+ window = gtk_widget_get_window (control.getShell ().topHandle ());
+ if (window != 0) break;
+ }
+ return display.monitorOrigin (window);
+}
+
/**
* Gets the position of the top left corner of the control in root window (display) coordinates.
* GTK4 only, do not call on GTK3.
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 4a1bc89cce3..3204b5affe2 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
@@ -2002,6 +2002,22 @@ boolean filters (int eventType) {
return filterTable.hooks (eventType);
}
+/**
+ * Origin of the monitor showing the given GdkWindow, or null when no offset is
+ * needed. See {@link Control#monitorOrigin()}.
+ */
+Point monitorOrigin (long window) {
+ if (GTK.GTK4 || !OS.isWayland () || window == 0) return null;
+ long displayHandle = GDK.gdk_display_get_default ();
+ if (displayHandle == 0) return null;
+ long monitor = GDK.gdk_display_get_monitor_at_window (displayHandle, window);
+ if (monitor == 0) return null;
+ GdkRectangle geometry = new GdkRectangle ();
+ GDK.gdk_monitor_get_geometry (monitor, geometry);
+ if (geometry.x == 0 && geometry.y == 0) return null;
+ return new Point (geometry.x, geometry.y);
+}
+
/**
* Returns the location of the on-screen pointer relative
* to the top left corner of the screen.
@@ -2024,7 +2040,7 @@ public Point getCursorLocation() {
x[0] = (int)xDouble[0];
y[0] = (int)yDouble[0];
} else {
- getWindowPointerPosition(0, x, y, null);
+ long pointerWindow = getWindowPointerPosition(0, x, y, null);
/*
* Wayland feature: There is no global x/y coordinates in Wayland for security measures, so they
@@ -2045,6 +2061,17 @@ public Point getCursorLocation() {
y[0]+= offsetY[0];
tempShell = tempShell.getParent().getShell();
}
+ /*
+ * Callers compare this against Control.toDisplay(), so use the same space.
+ * Prefer the monitor of the window under the pointer, which is the one the
+ * coordinates are relative to; the active shell is only a fallback.
+ */
+ Point origin = monitorOrigin (pointerWindow);
+ if (origin == null) origin = tempShell.monitorOrigin ();
+ if (origin != null) {
+ x[0] += origin.x;
+ y[0] += origin.y;
+ }
}
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java
index 19467e4b2d3..56d5636e86b 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java
@@ -447,8 +447,12 @@ void _setVisible (boolean visible) {
GTK3.memmove (eventPtr, event, GdkEventButton.sizeof);
// Bug in GTK?: testing with SWT_MENU_LOCATION_DEBUGGING=1 shows final_rect.x and
// final_rect.y popup menu position is off by 1 compared to this.x and this.y
- rect.x = this.x + 1;
- rect.y = this.y + 1;
+ // The rectangle is relative to the shell, so the monitor origin has to go.
+ Point monitorOrigin = getShell ().monitorOrigin ();
+ int originX = monitorOrigin != null ? monitorOrigin.x : 0;
+ int originY = monitorOrigin != null ? monitorOrigin.y : 0;
+ rect.x = this.x + 1 - originX;
+ rect.y = this.y + 1 - originY;
}
// Popup the menu and pin it at the top left corner of the GdkRectangle relative to the GdkWindow
GTK3.gtk_menu_popup_at_rect(handle, event.window, rect, GDK.GDK_GRAVITY_NORTH_WEST,
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 c55f99be603..97c4cc6512f 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
@@ -1348,10 +1348,26 @@ public Point getLocation() {
// TODO: GTK4 GtkWindow no longer has the ability to get position
} else {
GTK3.gtk_window_get_position (shellHandle, x, y);
+ applyMonitorOrigin (x, y);
}
return new Point (x [0], y [0]);
}
+@Override
+Point monitorOrigin () {
+ // A child Shell is positioned while still hidden, before its own monitor is known.
+ if (parent != null) return parent.monitorOrigin ();
+ return super.monitorOrigin ();
+}
+
+/** Shifts a window relative GTK position into display coordinates. */
+void applyMonitorOrigin (int [] x, int [] y) {
+ Point origin = monitorOrigin ();
+ if (origin == null) return;
+ x [0] += origin.x;
+ y [0] += origin.y;
+}
+
@Override
public boolean getMaximized () {
checkWidget();
@@ -1601,6 +1617,7 @@ long gtk3_button_press_event (long widget, long event) {
long gtk_configure_event (long widget, long event) {
int [] x = new int [1], y = new int [1];
GTK3.gtk_window_get_position (shellHandle, x, y);
+ applyMonitorOrigin (x, y);
if (!isVisible ()) {
return 0; //We shouldn't handle move/resize events if shell is hidden.
@@ -2361,8 +2378,10 @@ void resizeBounds (int width, int height, boolean notify) {
GDK.gdk_window_resize (enableWindow, width, height);
}
}
- int boxWidth = width - 2*border;
- int boxHeight = height - 2*border;
+ // GTK rejects negative allocations; a shell smaller than its own border must not
+ // leak a negative size into gtk_widget_size_allocate().
+ int boxWidth = Math.max (0, width - 2*border);
+ int boxHeight = Math.max (0, height - 2*border);
if ((style & SWT.RESIZE) == 0) {
GTK.gtk_widget_set_size_request (vboxHandle, boxWidth, boxHeight);
}
@@ -2415,9 +2434,13 @@ int setBounds (int x, int y, int width, int height, boolean move, boolean resize
}
if (mapped) positionPopover();
} else if (!GTK.GTK4) {
+ // GTK positions windows in its own space; x and y arrive in display coordinates.
+ Point origin = monitorOrigin ();
+ int gtkX = origin != null ? x - origin.x : x;
+ int gtkY = origin != null ? y - origin.y : y;
int [] x_pos = new int [1], y_pos = new int [1];
GTK3.gtk_window_get_position(shellHandle, x_pos, y_pos);
- GTK3.gtk_window_move(shellHandle, x, y);
+ GTK3.gtk_window_move(shellHandle, gtkX, gtkY);
/*
* Bug in GTK: gtk_window_get_position () is not always up-to-date right after
* gtk_window_move (). The random delays cause problems like bug 445900.
@@ -2429,11 +2452,11 @@ int setBounds (int x, int y, int width, int height, boolean move, boolean resize
for (int i = 0; i < 1000; i++) {
int [] x2_pos = new int [1], y2_pos = new int [1];
GTK3.gtk_window_get_position(shellHandle, x2_pos, y2_pos);
- if (x2_pos[0] == x && y2_pos[0] == y) {
+ if (x2_pos[0] == gtkX && y2_pos[0] == gtkY) {
break;
}
}
- if (x_pos [0] != x || y_pos [0] != y) {
+ if (x_pos [0] != gtkX || y_pos [0] != gtkY) {
moved = true;
oldX = x;
oldY = y;
@@ -3611,6 +3634,7 @@ Rectangle getBoundsInPixels () {
GDK.gdk_window_get_root_origin(GTK3.gtk_widget_get_window(shellHandle), x, y);
}
}
+ if (!GTK.GTK4) applyMonitorOrigin (x, y);
GtkAllocation allocation = new GtkAllocation ();
GTK.gtk_widget_get_allocation (vboxHandle, allocation);
int width = allocation.width;
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java
index d16f1e47ae1..18a114a39c2 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java
@@ -1054,4 +1054,63 @@ public void test_setLocationII() {
}
super.test_setLocationII();
}
+
+@Test
+public void test_degenerateSizeDoesNotExpandShell() {
+ // A bordered popup asked for a height of 0 used to be allocated -1, which GTK rejects,
+ // so it fell back to its natural size with its content never laid out. See issue 3539.
+ // GTK4 backs these shells with a GtkPopover, which never went through that box maths
+ // and enforces a minimum height of its own.
+ assumeTrue(SwtTestUtil.isGTK && !SwtTestUtil.isGTK4());
+ Shell popup = new Shell(shell, SWT.TOOL | SWT.ON_TOP);
+ popup.setLayout(new FillLayout());
+ Button child = new Button(popup, SWT.PUSH);
+ child.setText("some content that is clearly taller than two pixels");
+ popup.setSize(228, 0);
+ popup.setVisible(true);
+ SwtTestUtil.processEvents();
+ Rectangle clientArea = popup.getClientArea();
+ popup.dispose();
+ assertTrue(clientArea.height >= 0, "negative client area: " + clientArea);
+ assertTrue(clientArea.height <= 2, "shell expanded to its natural size: " + clientArea);
+}
+
+@Test
+public void test_toDisplayIsOnTheShellsMonitor() {
+ // Display coordinates and Monitor geometry have to share one coordinate space, otherwise
+ // callers that clip a location against a monitor discard it. See issue 3539.
+ // GTK4 has no root coordinates yet, so toDisplay() is still shell relative there.
+ assumeTrue(SwtTestUtil.isGTK && !SwtTestUtil.isGTK4());
+ shell.setSize(300, 200);
+ shell.open();
+ SwtTestUtil.processEvents();
+ Rectangle monitor = shell.getMonitor().getBounds();
+ Point origin = shell.toDisplay(0, 0);
+ assertTrue(monitor.contains(origin), "toDisplay " + origin + " is outside monitor " + monitor);
+}
+
+@Test
+public void test_popupLocationRoundTrip() {
+ // JFace reuses one popup: position, show, hide, reposition. Every pass has to round-trip,
+ // including the ones where the shell is still mapped. See issue 3539.
+ assumeTrue(SwtTestUtil.isGTK && !SwtTestUtil.isGTK4());
+ shell.setSize(400, 300);
+ shell.open();
+ SwtTestUtil.processEvents();
+ Shell popup = new Shell(shell, SWT.TOOL | SWT.ON_TOP);
+ popup.setSize(120, 80);
+ try {
+ for (int pass = 0; pass < 3; pass++) {
+ Point location = shell.toDisplay(40 + pass * 10, 40 + pass * 10);
+ popup.setLocation(location);
+ popup.setVisible(true);
+ SwtTestUtil.processEvents();
+ assertEquals(location, popup.getLocation(), "pass " + pass);
+ popup.setVisible(false);
+ SwtTestUtil.processEvents();
+ }
+ } finally {
+ popup.dispose();
+ }
+}
}