Skip to content
Source
Sign inSign up
Open History to see the last commit

linux-snap-shot.md

108 lines6.1 KB

1# Linux window capture
2​
3Wayland only. Each capture picks one backend in `LinuxSnapShot.ts` (Hyprland helper, Niri IPC, KDE
4helper, Screenshot portal with active-window target, GNOME extension, then the Electron picker) and
5never falls back to another on failure, cancellation, or denial. A stale `NIRI_SOCKET` or a reachable
6GNOME extension must not select another desktop's backend; session detection comes from
7`XDG_CURRENT_DESKTOP`, in `linuxCaptureSession.ts`.
8​
9## Module boundaries
10​
11`dbus-next` must only be reached through dynamic imports. `linuxCaptureSession.ts` holds the pure
12helpers (session detection, Niri binding text, portal key mapping, PNG reading) so the main process
13can answer "which desktop is this" without loading a D-Bus client on macOS or Windows.
14​
15`patches/dbus-next@0.10.2.patch` removes `usocket` (its optional native Unix-FD transport) and
16connects to both `unix:path=` and `unix:abstract=` buses through Node's `net`. We never pass Unix
17descriptors, and upstream had no `net` fallback for abstract sockets, which `dbus-launch` produces
18outside systemd. Keep the patch when bumping `dbus-next`.
19​
20## Accessibility identity
21​
22The Screenshot portal returns a PNG URI and no window identity, so portal and picker captures carry
23no accessibility data. Do not infer it from the subsequently focused window or a guessed title.
24​
25Native backends supply PID, title, and frame. AT-SPI matching is by PID plus a unique title and size
26match. AT-SPI reports `(0, 0)` for a window's screen position on Wayland even when the compositor
27knows the real one, so only width and height are compared, and descendant coordinates are trusted
28only when the AT-SPI root agrees with the compositor frame and at least one descendant reports a
29distinct position. Otherwise bounds are `null` rather than zero-origin rectangles.
30​
31Title matching ignores a single leading Braille CLI spinner frame, since terminals change it between
32capture and lookup. KDE supplies both `frameGeometry` and `clientGeometry`; accept either verified
33size, never a decoration-height tolerance.
34​
35On GNOME, browser accessibility bridges need `org.gnome.desktop.interface toolkit-accessibility`
36before the browser starts. Do not toggle it during capture.
37​
38## KDE
39​
40KWin authorizes `org.kde.KWin.ScreenShot2` by resolving the calling PID's executable against its
41application registry, so the helper is installed at a stable path under the XDG data home with a
42hidden desktop entry, not run from the AppImage mount. Two traps:
43​
44- `X-KDE-DBUS-Restricted-Interfaces` uses KConfig's comma-separated list syntax. A trailing
45 semicolon becomes part of the interface name and KWin rejects it.
46- KService's cache key includes search paths and locale. Refresh with `kbuildsycoca6` both directly
47 and through `systemd-run --user`; refreshing only inside the AppImage environment can leave KWin
48 reading stale permissions.
49​
50Readiness is checked by calling `CaptureWindow` with an invalid ID and expecting `InvalidWindow`,
51which runs after KWin's authorization check. Files on disk alone never mean ready.
52​
53Plasma consumes Shift for shifted digits and produces a punctuation keysym, so a successfully bound
54`Ctrl+Shift+2` may never fire on some layouts. Letter chords work around it. A general fix needs
55layout-aware encoding.
56​
57## Hyprland
58​
59The helper maps a foreign-toplevel handle to the full 64-bit window address with
60`hyprland-toplevel-mapping-v1` before exporting through `hyprland-toplevel-export-v1`. Never
61truncate the address or pick a window by title.
62​
63Hyprland 0.56.2 renders an access-denied texture instead of failing the export frame when consent
64is rejected or a `no_screen_share` rule applies. `ready` is not a permission grant, and pixels must
65not be inspected to guess one. See
66[ScreenshareFrame](https://github.com/hyprwm/Hyprland/blob/v0.56.2/src/managers/screenshare/ScreenshareFrame.cpp).
67​
68Hyprland's GlobalShortcuts portal registers actions, not key chords. `shortcutActionRegistered` is
69distinct from `shortcutRegistered`; nothing in the UI may claim the keys are reserved.
70​
71The bundled protocol XML ships with the helper because its BSD license requires the notice.
72​
73## Niri
74​
75Niri does not implement the global-shortcut portal. While capture is enabled the app owns
76`<app-id>.SnapShot` on the session bus and exports `com.t3tools.SnapShot.Capture`; the config
77binding spawns `gdbus` to call it. Development and packaged app IDs use separate names so a dev
78build does not steal the user's binding.
79​
80Niri supplies no global screenshot origin, so AT-SPI matching uses logical size and title only.
81​
82## Config edits
83​
84`CaptureShortcutConfig` edits user dotfiles. The invariants: opening setup never reads the file;
85**Review changes** is the read consent; the renderer only ever sees before/after text over trusted
86desktop IPC; **Save** sends a proposal ID and the desktop re-verifies bytes, inode, mode, and
87includes against the snapshot it previewed before writing. Writes go through a staged temp file and
88atomic rename with a backup beside the original. Niri validates the staged file first.
89​
90Modifier serialization writes Linux `Ctrl` explicitly, never the cross-platform `mod` alias.
91​
92## GNOME extension
93​
94Source in `apps/desktop/gnome-extension`, UUID `snap-shot@t3.codes`. GNOME only discovers a newly
95installed extension at login, so setup distinguishes "installed, needs logout" from "discovered but
96disabled" and compares loaded and installed versions.
97​
98The extension trusts callers that own `com.t3tools.T3Code.SnapShot` (or the `.Development`
99variant) on the same connection. This is GNOME's trusted-session-client pattern, not authentication
100against a hostile process on the user's bus.
101​
102Electron does not position overlay windows on Wayland, so the flash and flight run as Shell actors
103inside the extension with coordinates relative to T3's content area. Electron 44's restored-session
104path can skip rebinding and leave callbacks behind on unregister, which is why
105`PortalCaptureShortcut` owns its own portal session instead of using Electron's global-shortcut API.
106​
107GNOME 50 removed `Meta.is_wayland_compositor`. Shell internals change across majors; verify each
108version before adding it to `metadata.json`.