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(); + } +} }