mirror of
https://github.com/VibedByKaKi/t3-code-android-nightly.git
synced 2026-10-10 04:11:15 +02:00
Co-authored-by: Dara Adedeji <daraadedeji07@gmail.com> Co-authored-by: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
108 lines
6.1 KiB
Markdown
108 lines
6.1 KiB
Markdown
# Linux window capture
|
|
|
|
Wayland only. Each capture picks one backend in `LinuxSnapShot.ts` (Hyprland helper, Niri IPC, KDE
|
|
helper, Screenshot portal with active-window target, GNOME extension, then the Electron picker) and
|
|
never falls back to another on failure, cancellation, or denial. A stale `NIRI_SOCKET` or a reachable
|
|
GNOME extension must not select another desktop's backend; session detection comes from
|
|
`XDG_CURRENT_DESKTOP`, in `linuxCaptureSession.ts`.
|
|
|
|
## Module boundaries
|
|
|
|
`dbus-next` must only be reached through dynamic imports. `linuxCaptureSession.ts` holds the pure
|
|
helpers (session detection, Niri binding text, portal key mapping, PNG reading) so the main process
|
|
can answer "which desktop is this" without loading a D-Bus client on macOS or Windows.
|
|
|
|
`patches/dbus-next@0.10.2.patch` removes `usocket` (its optional native Unix-FD transport) and
|
|
connects to both `unix:path=` and `unix:abstract=` buses through Node's `net`. We never pass Unix
|
|
descriptors, and upstream had no `net` fallback for abstract sockets, which `dbus-launch` produces
|
|
outside systemd. Keep the patch when bumping `dbus-next`.
|
|
|
|
## Accessibility identity
|
|
|
|
The Screenshot portal returns a PNG URI and no window identity, so portal and picker captures carry
|
|
no accessibility data. Do not infer it from the subsequently focused window or a guessed title.
|
|
|
|
Native backends supply PID, title, and frame. AT-SPI matching is by PID plus a unique title and size
|
|
match. AT-SPI reports `(0, 0)` for a window's screen position on Wayland even when the compositor
|
|
knows the real one, so only width and height are compared, and descendant coordinates are trusted
|
|
only when the AT-SPI root agrees with the compositor frame and at least one descendant reports a
|
|
distinct position. Otherwise bounds are `null` rather than zero-origin rectangles.
|
|
|
|
Title matching ignores a single leading Braille CLI spinner frame, since terminals change it between
|
|
capture and lookup. KDE supplies both `frameGeometry` and `clientGeometry`; accept either verified
|
|
size, never a decoration-height tolerance.
|
|
|
|
On GNOME, browser accessibility bridges need `org.gnome.desktop.interface toolkit-accessibility`
|
|
before the browser starts. Do not toggle it during capture.
|
|
|
|
## KDE
|
|
|
|
KWin authorizes `org.kde.KWin.ScreenShot2` by resolving the calling PID's executable against its
|
|
application registry, so the helper is installed at a stable path under the XDG data home with a
|
|
hidden desktop entry, not run from the AppImage mount. Two traps:
|
|
|
|
- `X-KDE-DBUS-Restricted-Interfaces` uses KConfig's comma-separated list syntax. A trailing
|
|
semicolon becomes part of the interface name and KWin rejects it.
|
|
- KService's cache key includes search paths and locale. Refresh with `kbuildsycoca6` both directly
|
|
and through `systemd-run --user`; refreshing only inside the AppImage environment can leave KWin
|
|
reading stale permissions.
|
|
|
|
Readiness is checked by calling `CaptureWindow` with an invalid ID and expecting `InvalidWindow`,
|
|
which runs after KWin's authorization check. Files on disk alone never mean ready.
|
|
|
|
Plasma consumes Shift for shifted digits and produces a punctuation keysym, so a successfully bound
|
|
`Ctrl+Shift+2` may never fire on some layouts. Letter chords work around it. A general fix needs
|
|
layout-aware encoding.
|
|
|
|
## Hyprland
|
|
|
|
The helper maps a foreign-toplevel handle to the full 64-bit window address with
|
|
`hyprland-toplevel-mapping-v1` before exporting through `hyprland-toplevel-export-v1`. Never
|
|
truncate the address or pick a window by title.
|
|
|
|
Hyprland 0.56.2 renders an access-denied texture instead of failing the export frame when consent
|
|
is rejected or a `no_screen_share` rule applies. `ready` is not a permission grant, and pixels must
|
|
not be inspected to guess one. See
|
|
[ScreenshareFrame](https://github.com/hyprwm/Hyprland/blob/v0.56.2/src/managers/screenshare/ScreenshareFrame.cpp).
|
|
|
|
Hyprland's GlobalShortcuts portal registers actions, not key chords. `shortcutActionRegistered` is
|
|
distinct from `shortcutRegistered`; nothing in the UI may claim the keys are reserved.
|
|
|
|
The bundled protocol XML ships with the helper because its BSD license requires the notice.
|
|
|
|
## Niri
|
|
|
|
Niri does not implement the global-shortcut portal. While capture is enabled the app owns
|
|
`<app-id>.SnapShot` on the session bus and exports `com.t3tools.SnapShot.Capture`; the config
|
|
binding spawns `gdbus` to call it. Development and packaged app IDs use separate names so a dev
|
|
build does not steal the user's binding.
|
|
|
|
Niri supplies no global screenshot origin, so AT-SPI matching uses logical size and title only.
|
|
|
|
## Config edits
|
|
|
|
`CaptureShortcutConfig` edits user dotfiles. The invariants: opening setup never reads the file;
|
|
**Review changes** is the read consent; the renderer only ever sees before/after text over trusted
|
|
desktop IPC; **Save** sends a proposal ID and the desktop re-verifies bytes, inode, mode, and
|
|
includes against the snapshot it previewed before writing. Writes go through a staged temp file and
|
|
atomic rename with a backup beside the original. Niri validates the staged file first.
|
|
|
|
Modifier serialization writes Linux `Ctrl` explicitly, never the cross-platform `mod` alias.
|
|
|
|
## GNOME extension
|
|
|
|
Source in `apps/desktop/gnome-extension`, UUID `snap-shot@t3.codes`. GNOME only discovers a newly
|
|
installed extension at login, so setup distinguishes "installed, needs logout" from "discovered but
|
|
disabled" and compares loaded and installed versions.
|
|
|
|
The extension trusts callers that own `com.t3tools.T3Code.SnapShot` (or the `.Development`
|
|
variant) on the same connection. This is GNOME's trusted-session-client pattern, not authentication
|
|
against a hostile process on the user's bus.
|
|
|
|
Electron does not position overlay windows on Wayland, so the flash and flight run as Shell actors
|
|
inside the extension with coordinates relative to T3's content area. Electron 44's restored-session
|
|
path can skip rebinding and leave callbacks behind on unregister, which is why
|
|
`PortalCaptureShortcut` owns its own portal session instead of using Electron's global-shortcut API.
|
|
|
|
GNOME 50 removed `Meta.is_wayland_compositor`. Shell internals change across majors; verify each
|
|
version before adding it to `metadata.json`.
|