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 965b701ff3e..a80e7ef063e 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
@@ -1698,8 +1698,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];
@@ -1763,8 +1764,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;
@@ -6339,6 +6341,12 @@ boolean showMenu (int x, int y) {
}
boolean showMenu (int x, int y, int detail) {
+ // GTK3 callers pass root coordinates, which on Wayland lack the monitor origin of display coordinates.
+ Point monitorOrigin = monitorOrigin ();
+ if (monitorOrigin != null) {
+ x += monitorOrigin.x;
+ y += monitorOrigin.y;
+ }
Event event = new Event ();
Rectangle eventRect = new Rectangle (x, y, 0, 0);
event.setBounds (eventRect);
@@ -6924,9 +6932,27 @@ 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;
+ // One anchor per shell tree: Shell walks up to the root toplevel, whose monitor is the
+ // space that popup positions are relative to.
+ return getShell ().monitorOrigin ();
+}
+
/**
* 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 7d54eb15ffe..3bd578575b0 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
@@ -2009,6 +2009,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.
@@ -2031,7 +2047,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
@@ -2052,6 +2068,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 dba5080844d..90c35b011fb 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
@@ -449,8 +449,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 87f7d78f3a0..3d45185dbb6 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
@@ -1362,10 +1362,28 @@ 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 () {
+ if (GTK.GTK4 || !OS.isWayland ()) return null;
+ // A child Shell is positioned relative to its parent, and while still hidden its own
+ // monitor is not even known, so the root toplevel is the anchor for the whole tree.
+ if (parent != null) return parent.monitorOrigin ();
+ return display.monitorOrigin (gtk_widget_get_window (topHandle ()));
+}
+
+/** 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();
@@ -1624,6 +1642,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.
@@ -2468,9 +2487,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.
@@ -2482,11 +2505,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;
@@ -3663,6 +3686,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 b660734626e..0d025d20a86 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
@@ -1101,4 +1101,43 @@ public void test_degenerateSizeDoesNotExpandShell() {
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();
+ }
+}
}