GNOME/Mutter Wayland support (XWayland hybrid backend); popup anchors above selection#4
Open
GaimsDevSoftware wants to merge 10 commits into
Open
GNOME/Mutter Wayland support (XWayland hybrid backend); popup anchors above selection#4GaimsDevSoftware wants to merge 10 commits into
GaimsDevSoftware wants to merge 10 commits into
Conversation
Mutter implements neither wlr-layer-shell (no absolute surface placement)
nor an external cursor-position API (no KWin workspace.cursorPos equivalent)
the two things the KDE backend relies on for popup positioning. So GNOME
gets a hybrid backend that reuses the proven X11 path for the hard parts and
swaps only the I/O that X11 tools can't do on Wayland.
New: platform_backend/xwayland_gnome.py (XWaylandGnomeBackend)
- inherits X11Backend for pointer (XQueryPointer) and popup placement
(Gtk.Window.move) - both work for an XWayland toplevel and return global
coordinates, no layer-shell needed
- overrides selection/clipboard (wl-clipboard) and key injection (ydotool)
by delegating to the KDE backend's compositor-agnostic helpers, because
xclip/XFixes/xdotool only see XWayland apps, not native Wayland ones
- selection watch via wl-paste --primary --watch (sees native Wayland apps)
- double-click via the existing kernel-level evdev watcher
- active window via AT-SPI, merged with XWayland WM_CLASS
New: platform_backend/evdev_hotkey.py (EvdevHotkey)
- global hotkey read from /dev/input at kernel level, so it fires whatever
app has focus (an X11 grab via XWayland misses native-Wayland-focused
windows). Modifier state via libxkbcommon, so ctrl:swap_lalt_lctl and
other xkb options are honoured. Needs the 'input' group (same as ydotool).
detect(): GNOME Wayland -> xwayland_gnome; KDE and X11 paths unchanged.
main.py: force GDK_BACKEND=x11 before GTK import, but only for the gnome
backend (KDE keeps native Wayland for gtk-layer-shell). LINUXPOP_GDK_BACKEND
opts out.
editable_detect.py: add active_window_atspi_haystacks() for blocklist matching
on Wayland where WM_CLASS is unavailable for native apps.
Verified on KDE-under-XWayland: detection, pointer, selection, paste,
positioning (test_popup), and evdev hotkey arming (honouring the user's
ctrl:swap_lalt_lctl). KDE backend confirmed unchanged. Visual verification
on a real GNOME session (placement accuracy, fractional scaling) still
pending; Flatpak packaging for GNOME deferred until then.
Self-contained state-of-play so the GNOME work can be continued on a fresh Fedora GNOME machine (or by a fresh Claude session) without the original chat transcript: design rationale, file map, what's verified vs pending, how to run and test on GNOME, and the deferred work (Flatpak packaging, fractional-scaling check, docs).
Replace the branch handoff note with a standard architecture doc covering the hybrid XWayland approach, the capability split vs the KDE backend, the fractional-scaling coordinate handling, and setup on Fedora GNOME.
…on rect) The popup is meant to open directly above the selected text, falling back to the mouse pointer only when that geometry isn't available. The rect path already existed (focused_selection_rect -> popup.show_for rect anchoring) but was unreachable on GNOME for two reasons, both fixed here: - editable_detect._atspi_environment_safe() only accepted AT-SPI when a socket existed at $XDG_RUNTIME_DIR/at-spi/bus_0. GNOME drops no socket there and exposes the bus via org.a11y.Bus on the session bus instead, so the probe returned False and _HAS_ATSPI was False. The probe now runs the foreign-uid at-spi-bus-launcher crash guard first (unchanged), then accepts either the XDG socket (KDE / X11) or a reachable org.a11y.Bus (GNOME). - focused_selection_rect() was gated behind editable_atspi_listener_enabled (the heavy always-on focus listener, off by default). It does a one-shot bounded walk and never needs that listener, so it's re-gated on the user-facing popup_anchor_to_selection toggle (default on). The focus listener and is_focus_editable's synchronous walk stay gated on editable_atspi_listener_enabled, so this does not turn those on. Requires desktop accessibility (toolkit-accessibility) so apps expose AT-SPI. When the rect is unavailable the popup falls back to the mouse pointer exactly as before, so this is safe when AT-SPI is off or an app exposes no geometry.
The selection-rect anchoring enabled in the previous commit runs a one-shot AT-SPI walk by default (gated on popup_anchor_to_selection). On Cinnamon (Linux Mint's default) activating AT-SPI was correlated with a desktop-panel segfault - the exact reason the focus listener defaults off. Guard focused_selection_rect with a Cinnamon check so Mint keeps its proven mouse-pointer placement; power users who set editable_atspi_listener_enabled still opt in. GNOME/KDE are unaffected.
…ice) Native (non-Flatpak) installs had no working keystroke injection on Wayland: the distro ydotool package ships only a *system* ydotoold service that runs as root on a root-only socket the app can't reach, and /dev/uinput is root-only. So Cut/Paste/Backspace silently no-op'd on GNOME/KDE Wayland. Add packaging/wayland/ with the standard fix: a udev rule giving the 'input' group access to /dev/uinput, a per-user ydotoold.service on the socket path the Wayland backend expects ($XDG_RUNTIME_DIR/.ydotool_socket), and a README with the one-time setup. The Flatpak path is unchanged (it bundles ydotool itself). Fix the stale gnome-wayland.md instruction that referenced a non-existent user unit. Verified on Fedora 44 GNOME/Wayland: with the udev rule + user ydotoold, ydotool injection lands keystrokes in the focused window and the backend reports can_paste=True.
The selection-triggered popup - the primary way to invoke LinuxPop - never
fired on GNOME Wayland. The watcher used wl-paste --primary --watch, which
needs the wlr-data-control protocol; Mutter does not implement it, so wl-paste
exits immediately ("Watch mode requires a compositor that supports the
data-control protocol") and no selection ever reached the popup.
Add _PollingSelectionWatcher and use it from the xwayland_gnome backend: it
polls the primary selection with one-shot wl-paste reads (which DO work on
Mutter) on a 250 ms interval, debounces so a drag settles on its final value,
skips the selection already present at startup, and fires on change. Sees both
native Wayland and XWayland apps. KDE/wlroots keep the event-driven watcher
(they have data-control).
Verified on Fedora 44 GNOME/Wayland: changing the primary selection now fires
the popup callback; the pre-existing selection does not.
Swap tray_qt.py (QSystemTrayIcon, needs PySide6) for tray_dbus.py, a hand-rolled StatusNotifierItem over dbus-python + GdkPixbuf - same length-prefixed JSON socket protocol, no Qt dependency. PySide6 wasn't installed, so the Qt tray subprocess failed to connect (the popup still worked, but Settings/Plugins/Quit were unreachable from the tray). dbus-python and GdkPixbuf are already in use, so the tray now works with no extra dep. On GNOME the icon still needs the AppIndicator shell extension to be displayed (GNOME shows no StatusNotifierItem host otherwise).
… Mutter) Screen OCR never worked on GNOME/Wayland: the fullscreen grab tried spectacle/grim/maim, but Mutter has no wlr-screencopy (grim fails) and X11 grabbers see only XWayland (maim captures black). _capture_fullscreen now skips maim under native Wayland and falls back to the xdg-desktop-portal Screenshot interface (interactive=false), which captures the real screen on GNOME - and on KDE, and inside the Flatpak sandbox. The portal writes a PNG and returns its URI; we copy it out and delete the portal's ~/Pictures copy. Verified on Fedora 44 GNOME/Wayland: capture returns the real screen and tesseract recognises the on-screen text end to end.
…Fixes watch The selection popup could not be placed correctly on GNOME Wayland and the selection watch disrupted typing. Root causes were all GNOME/Mutter Wayland limitations with no app-level workaround, confirmed against upstream: - Pointer: XQueryPointer freezes over native-Wayland windows and GNOME ships no wlr virtual-pointer / layer-shell protocol, so there is no way for an app to read the global cursor. Ship a tiny GNOME Shell extension (packaging/gnome-shell-extension/) that exposes global.get_pointer() and an ActivateApp() over D-Bus. The xwayland_gnome backend reads the cursor through it (logical coords -> pointer_is_logical=True, no scale division) and re-focuses the app before injecting keystrokes (clicking the popup steals focus on GNOME). Degrades gracefully (a short settle delay) without it. - Selection watch: wl-paste --watch needs wlr-data-control (absent on Mutter) and polling wl-paste hammered the source app into re-serving the selection, making the highlight and caret blink. Use the X11 XFixes watcher instead - event-driven, reads once per change; Mutter bridges the Wayland primary to the X11 PRIMARY so it sees native-Wayland and XWayland apps. watcher.py gains a pointer_fn hook so the GNOME backend feeds it the extension-based cursor. - AT-SPI selection rect is window-relative on Wayland (compositor security, RH bug 1517301), so it can't anchor a screen popup: skip it on Wayland, anchor to the pointer. - popup.py: raise the window without gtk_window_present() (which requests activation and stole keyboard focus on Mutter, eating the user's typing), and trim the Wayland line-clearance so the popup sits closer to the cursor. Verified on Fedora 44 GNOME/Wayland: popup tracks the cursor, no blink, typing unaffected. Keystroke actions need the ActivateApp half of the extension (loads after a re-login).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Fedora GNOME (Mutter, Wayland) support so one build auto-detects and runs
correctly on KDE Plasma (Wayland), GNOME (Wayland), and Mint/Cinnamon (X11).
How it works
platform_backend.detect()now routes a GNOME Wayland session to a newxwayland_gnomebackend (KDE stayswayland_kde, non-Wayland staysx11).Mutter has no
wlr-layer-shelland no external cursor API, so the popup runsunder XWayland and reuses the proven X11 positioning/pointer path, swapping only
the I/O that X11 tools can't do for native Wayland apps:
Gtk.Window.move,XQueryPointer) under XWaylandwl-paste --primary --watch(sees native Wayland apps)wl-clipboard, ydotool)EvdevHotkey(/dev/input) so it fires even when a native Wayland window is focusedmain.pyforcesGDK_BACKEND=x11before importing GTK, but only for thexwayland_gnomebackend. Seedocs/gnome-wayland.mdfor the full design.Popup anchors above the selected text
Enables the selection-rect anchoring on GNOME:
_atspi_environment_safe()nowaccepts GNOME's
org.a11y.Bus(not just the XDG socket KDE/X11 use), andfocused_selection_rect()is re-gated on the user-facingpopup_anchor_to_selection(default on) instead of the heavy focus listener.Falls back to the mouse pointer when no selection geometry is available.
Status / verification
Verified on real Fedora 44 GNOME/Wayland: detection →
xwayland_gnome, selectionread, pointer, popup render, plugin load, no crashes; the fractional-scaling
pointer math is correct.
Needs verification on real GNOME hardware before relying on it:
inputgroup