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: