From fef53cc77899b7075a199694da72058b3ca82ed7 Mon Sep 17 00:00:00 2001 From: Brandon Sawyers Date: Tue, 8 Sep 2026 15:26:12 -0400 Subject: [PATCH] fix: accurate mouse monitor detection on Wayland via GNOME Shell extension On Wayland sessions, Guake runs as an XWayland client (GDK_BACKEND=x11). GDK's pointer position only updates when the cursor hovers over another XWayland window, causing 'appear on mouse display' to select the wrong monitor when the cursor is over native Wayland apps or the desktop. This adds a small GNOME Shell extension (guake-pointer-helper) that queries Mutter's CursorTracker for the real Wayland cursor coordinates and exposes them over a dedicated D-Bus interface. The existing get_final_window_monitor() method now queries this interface on Wayland sessions before falling back to the GDK pointer (which remains correct on X11 sessions). The extension is installed automatically by 'make install' to the system-wide GNOME Shell extensions directory but must be enabled by the user with: gnome-extensions enable guake-pointer-helper@guake.org A log warning is emitted on the first toggle if the extension is not available on a Wayland session. When the extension is not installed or not enabled, behavior is unchanged from the current release (GDK fallback). Fixes: https://github.com/Guake/guake/issues/2121 Fixes: https://github.com/Guake/guake/issues/2115 Refs: https://github.com/Guake/guake/issues/1689 Co-authored-by: Cursor --- Makefile | 20 +++++- .../extension.js | 46 +++++++++++++ .../metadata.json | 7 ++ guake/utils.py | 66 +++++++++++++++++-- 4 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 guake/data/gnome-shell-extension/guake-pointer-helper@guake.org/extension.js create mode 100644 guake/data/gnome-shell-extension/guake-pointer-helper@guake.org/metadata.json diff --git a/Makefile b/Makefile index 38c2538b8..0b98741b0 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ LOGIN_DESTOP_PATH = $(SHARE_DIR) IMAGE_DIR:=$(SHARE_DIR)/pixmaps GLADE_DIR:=$(SHARE_DIR) SCHEMA_DIR:=$(gsettingsschemadir) +GNOME_SHELL_EXTDIR:=$(datadir)/gnome-shell/extensions SLUG:=fragment_name @@ -68,7 +69,7 @@ ln-venv: clean-ln-venv: @rm -f .venv -install-system: install-schemas install-locale install-guake +install-system: install-schemas install-locale install-guake install-gnome-extension install-guake: # you probably want to execute this target with sudo: @@ -144,7 +145,22 @@ install-schemas: install -Dm644 "$(DEV_DATA_DIR)/org.guake.gschema.xml" "$(DESTDIR)$(SCHEMA_DIR)/" if [ $(COMPILE_SCHEMA) = 1 ]; then glib-compile-schemas $(DESTDIR)$(SCHEMA_DIR); fi -uninstall-system: uninstall-schemas uninstall-locale +install-gnome-extension: + install -dm755 "$(DESTDIR)$(GNOME_SHELL_EXTDIR)/guake-pointer-helper@guake.org" + install -Dm644 "$(DEV_DATA_DIR)/gnome-shell-extension/guake-pointer-helper@guake.org/metadata.json" \ + "$(DESTDIR)$(GNOME_SHELL_EXTDIR)/guake-pointer-helper@guake.org/" + install -Dm644 "$(DEV_DATA_DIR)/gnome-shell-extension/guake-pointer-helper@guake.org/extension.js" \ + "$(DESTDIR)$(GNOME_SHELL_EXTDIR)/guake-pointer-helper@guake.org/" + @echo + @echo "Installed GNOME Shell extension: guake-pointer-helper@guake.org" + @echo "Enable it with: gnome-extensions enable guake-pointer-helper@guake.org" + @echo "A session restart (log out/in) may be required for GNOME to detect the extension." + @echo + +uninstall-gnome-extension: + rm -rf "$(DESTDIR)$(GNOME_SHELL_EXTDIR)/guake-pointer-helper@guake.org" + +uninstall-system: uninstall-schemas uninstall-locale uninstall-gnome-extension $(SHELL) -c $(PYTHON_SITEDIRS_FOR_PREFIX) \ | while read sitedir; do \ echo "rm -rf $(DESTDIR)$$sitedir/{guake,guake-*.egg-info}"; \ diff --git a/guake/data/gnome-shell-extension/guake-pointer-helper@guake.org/extension.js b/guake/data/gnome-shell-extension/guake-pointer-helper@guake.org/extension.js new file mode 100644 index 000000000..fa29ba21a --- /dev/null +++ b/guake/data/gnome-shell-extension/guake-pointer-helper@guake.org/extension.js @@ -0,0 +1,46 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; + +const DBUS_IFACE = ` + + + + + + + +`; + +export default class GuakePointerExtension { + _dbusId = null; + + enable() { + const nodeInfo = Gio.DBusNodeInfo.new_for_xml(DBUS_IFACE); + + this._dbusId = Gio.DBus.session.register_object( + '/org/guake/JsonPointer', + nodeInfo.interfaces[0], + (connection, sender, path, ifaceName, methodName, params, invocation) => { + if (methodName === 'GetPointer') { + const tracker = global.backend.get_cursor_tracker(); + const [coords] = tracker.get_pointer(); + invocation.return_value( + new GLib.Variant('(ii)', [ + Math.round(coords.x), + Math.round(coords.y), + ]) + ); + } + }, + null, + null, + ); + } + + disable() { + if (this._dbusId) { + Gio.DBus.session.unregister_object(this._dbusId); + this._dbusId = null; + } + } +} diff --git a/guake/data/gnome-shell-extension/guake-pointer-helper@guake.org/metadata.json b/guake/data/gnome-shell-extension/guake-pointer-helper@guake.org/metadata.json new file mode 100644 index 000000000..8c1401e1a --- /dev/null +++ b/guake/data/gnome-shell-extension/guake-pointer-helper@guake.org/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Guake Pointer Helper", + "description": "Exposes Wayland cursor position over D-Bus for Guake multi-monitor support", + "uuid": "guake-pointer-helper@guake.org", + "shell-version": ["45", "46", "47", "48", "49", "50", "51"], + "url": "https://github.com/Guake/guake" +} diff --git a/guake/utils.py b/guake/utils.py index dc07c80e6..7814fb528 100644 --- a/guake/utils.py +++ b/guake/utils.py @@ -323,6 +323,41 @@ def set_final_window_rect(cls, settings, window): return window_rect + @classmethod + def _get_gnome_shell_pointer(cls): + """Get cursor position via guake-pointer-helper GNOME Shell extension. + + Under XWayland, GDK only sees cursor updates when hovering over + X11-compat windows. The guake-pointer-helper extension queries + Mutter's CursorTracker for the real Wayland pointer coordinates + and exposes them over a dedicated D-Bus interface. + + Returns (x, y) or None on failure. + """ + try: + result = subprocess.run( + [ + "gdbus", "call", "--session", + "--dest", "org.gnome.Shell", + "--object-path", "/org/guake/JsonPointer", + "--method", "org.guake.JsonPointer.GetPointer", + ], + capture_output=True, + text=True, + timeout=1, + ) + if result.returncode != 0: + return None + # Output: (1234, 567) + match = re.search(r"\((\d+),\s*(\d+)\)", result.stdout) + if match: + return int(match.group(1)), int(match.group(2)) + except Exception as e: + log.debug("GNOME Shell pointer query failed: %s", e) + return None + + _wayland_extension_warned = False + @classmethod def get_final_window_monitor(cls, settings, window): """Gets the final monitor for the main window of guake.""" @@ -334,12 +369,31 @@ def get_final_window_monitor(cls, settings, window): num_monitor = settings.general.get_int("display-n") if use_mouse: - pointer = display.get_default_seat().get_pointer() - if pointer is None: - monitor = display.get_primary_monitor() - else: - _, x, y = pointer.get_position() - monitor = display.get_monitor_at_point(x, y) + monitor = None + + if os.environ.get("XDG_SESSION_TYPE") == "wayland": + pos = cls._get_gnome_shell_pointer() + if pos is not None: + x, y = pos + monitor = display.get_monitor_at_point(x, y) + log.debug("Wayland pointer via GNOME Shell: (%s, %s)", x, y) + elif not cls._wayland_extension_warned: + cls._wayland_extension_warned = True + log.warning( + "Wayland session detected but guake-pointer-helper GNOME Shell " + "extension is not available. Multi-monitor 'appear on mouse " + "display' may not work correctly. Install and enable the " + "extension with: gnome-extensions enable " + "guake-pointer-helper@guake.org" + ) + + if monitor is None: + pointer = display.get_default_seat().get_pointer() + if pointer is None: + monitor = display.get_primary_monitor() + else: + _, x, y = pointer.get_position() + monitor = display.get_monitor_at_point(x, y) else: monitor = display.get_monitor(num_monitor) if monitor is None: