Commit Graph

2 Commits

Author SHA1 Message Date
SellPL 9f1acb1421 test(linux): verify the KWin script against the live Plasma session
CI / ci (pull_request) Has been cancelled
CI / android (pull_request) Has been cancelled
The KWin JavaScript had never been executed. It is now.

A probe script was loaded into the running Plasma 6.6.6 compositor, which
reported:

    js: PROBE: activated resourceClass=org.kde.konsole resourceName=konsole
             caption=... internalId={6866aecf-...} pid=4147
    js: PROBE: activated resourceClass=Spotify resourceName=spotify
             caption=... internalId={c99b88b4-...} pid=74378

The probe was unloaded immediately afterwards and `isScriptLoaded` confirmed
`false`.

**This proved one thing I had wrong.** The file asserted that "KWin does not
expose the PID to scripts, so resolution happens on the Rust side from
resource_class." It does expose it — `pid=4147` for Konsole, `pid=74378` for
Spotify. The payload now carries it, and the daemon can resolve
`/proc/<pid>/exe` directly instead of inferring an application from its
resource class. Inferring from `resourceClass` would have been a guess, and a
wrong guess blocks the wrong application.

Also confirmed: `workspace.windowActivated` exists and fires on real window
changes, and `resourceClass`, `resourceName`, `caption` and `internalId` are all
readable from a script.

**Still unverified, and stated as such in the module:** the D-Bus leg — whether
`callDBus` from inside the script reaches an exported daemon interface. That half
is ordinary Rust and needs no compositor, so it is exercised by an integration
test rather than by touching a live session again.

Also records ADR-6 as accepted: the owner chose **egui** for the block screen,
so the UI toolkit is no longer a blocker. Reasoning is in the ADR, including the
licence entanglement — Slint is GPL-3.0, which is why ADR-17 sits upstream of
this decision.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-01 21:06:07 +03:00
SellPL a79d262986 feat(linux): add KWin and X11 focus-detection backends
The first enforcement-path code. CLAUDE.md section 3 names `x11rb`, `zbus` and
`nix` for Linux explicitly, so all three are approved and this needed no new
dependency decision.

**KWin backend (primary).** Wayland has no portable focus API (section 6.3), and
this machine runs Plasma 6.6.6 on Wayland — an X11-only design would be blind to
most of the user's own desktop. KWin will not push focus events to an
arbitrary subscriber, so this loads a JavaScript snippet via
`org.kde.KWin /Scripting`, which hooks `workspace.windowActivated` and calls
back over D-Bus with KWin's `callDBus` helper. The daemon owns the bus name, so
KWin always has somewhere to call; if the daemon dies, `callDBus` fails silently
in the script and the compositor is left alone — the fail-open behaviour
section 1.5.4 requires.

The D-Bus surface was introspected on the live session, not read from docs:

    $ busctl --user introspect org.kde.KWin /Scripting org.kde.kwin.Scripting
    .loadScript  method s->i    .start  method -    .unloadScript  method s->b

**X11 backend (fallback).** `_NET_ACTIVE_WINDOW` property subscription, resolved
through `_NET_WM_PID` to `/proc/<pid>/exe`. Blind to native Wayland apps under a
Wayland session, which is why it is the fallback and not the primary. It is also
the path that can be tested headless with Xvfb, so it is what CI would exercise.

**No polling anywhere.** Both backends park in a subscription: `callDBus` from
the compositor, `poll_for_event` on the X connection. Nothing in either module
has a timer, which is what keeps the section 5 idle budget at zero.

**zbus is blocking on async-io, deliberately.** `tokio` was available and was
rejected: a reactor thread is measurable idle cost against a zero-wakeup budget,
and several hundred KB against a 5 MB binary. Noted in the manifest so the
decision is visible next to the version pin.

**Size, measured rather than assumed:** the stripped daemon goes 1.364 MB ->
**2.237 MB**, so D-Bus, X11 and the signal handling cost ~870 KB. 2.9 MB of the
5 MB budget remains.

**What is verified, and what is not.** The D-Bus interface is introspected from
the live session. The KWin *JavaScript* API — `workspace.windowActivated`,
`client.resourceClass` — is written against the documented Plasma 6 scripting
surface and has **not** been executed, because doing so would load a script into
the user's running compositor. That is the honest status and LIMITATIONS.md
records it. Nothing here is wired into the event loop yet, so no window is
actually blocked by this PR.

Several API details cost real time and are worth recording: X11 atoms are
interned with a round trip at connect time rather than passed as an unrelated
constant, which silently queries the wrong property; the root event filter
matches on the `_NET_ACTIVE_WINDOW` *atom* rather than the property change type;
and `rusqlite`/`zbus`/`x11rb` all differ from their documented shapes in ways
that only compile once found.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-01 20:21:42 +03:00