M2: KWin and X11 focus backends, with the KWin script verified live #4
@@ -47,11 +47,24 @@
|
|||||||
//! .isScriptLoaded method s b
|
//! .isScriptLoaded method s b
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The KWin *JavaScript* API — `workspace.windowActivated`, `client.resourceClass`
|
//! The KWin *JavaScript* API has been **executed against the live session**. A
|
||||||
//! — is written against the documented Plasma 6 scripting surface and has **not**
|
//! probe script was loaded into the running Plasma 6.6.6, which printed:
|
||||||
//! been executed against a live KWin yet, because doing so would load a script
|
//!
|
||||||
//! into the user's running compositor. That is the honest status, and
|
//! ```text
|
||||||
//! `LIMITATIONS.md` records it.
|
//! 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
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! That confirmed `workspace.windowActivated` exists and fires, and that
|
||||||
|
//! `resourceClass`, `resourceName`, `caption`, `internalId` and — contrary to an
|
||||||
|
//! earlier assumption in this file — **`pid`** are all readable from a script.
|
||||||
|
//! The probe was unloaded immediately afterwards and verified gone.
|
||||||
|
//!
|
||||||
|
//! What remains unverified is the D-Bus leg: whether `callDBus` from inside the
|
||||||
|
//! script reaches the daemon's exported interface. That half is ordinary Rust
|
||||||
|
//! and is exercised by the integration test in M2.
|
||||||
|
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
@@ -92,12 +105,19 @@ function reportFocus(client) {
|
|||||||
"Focused", "", function(err) {}, 0);
|
"Focused", "", function(err) {}, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// resourceClass is the most stable identifier KWin offers; caption changes
|
// resourceClass is the stable identifier; caption changes constantly and
|
||||||
// constantly and internalId is not stable across restarts.
|
// internalId is not stable across restarts.
|
||||||
|
//
|
||||||
|
// pid is included because it is EXPOSED — verified on Plasma 6.6.6, where a
|
||||||
|
// probe script printed `pid=4147` for Konsole and `pid=74378` for Spotify.
|
||||||
|
// An earlier version of this file asserted that KWin hid the pid from
|
||||||
|
// scripts, and that was wrong; with a real pid the daemon can resolve
|
||||||
|
// /proc/<pid>/exe instead of inferring an app from its resource class.
|
||||||
var payload = (client.resourceClass || "") + "\t" +
|
var payload = (client.resourceClass || "") + "\t" +
|
||||||
(client.resourceName || "") + "\t" +
|
(client.resourceName || "") + "\t" +
|
||||||
(client.caption || "") + "\t" +
|
(client.caption || "") + "\t" +
|
||||||
String(client.internalId || "");
|
String(client.internalId || "") + "\t" +
|
||||||
|
String(client.pid || "");
|
||||||
callDBus("vu.valiq.blocker", "/Watcher", "vu.valiq.blocker.Watcher",
|
callDBus("vu.valiq.blocker", "/Watcher", "vu.valiq.blocker.Watcher",
|
||||||
"Focused", payload, function(err) {}, 0);
|
"Focused", payload, function(err) {}, 0);
|
||||||
}
|
}
|
||||||
@@ -261,12 +281,12 @@ pub fn parse_payload(payload: &str) -> Option<ForegroundWindow> {
|
|||||||
let resource_name = parts.next().unwrap_or_default();
|
let resource_name = parts.next().unwrap_or_default();
|
||||||
let caption = parts.next().unwrap_or_default();
|
let caption = parts.next().unwrap_or_default();
|
||||||
let internal_id = parts.next().unwrap_or_default();
|
let internal_id = parts.next().unwrap_or_default();
|
||||||
|
let pid = parts.next().and_then(|p| p.trim().parse::<u32>().ok());
|
||||||
|
|
||||||
Some(ForegroundWindow {
|
Some(ForegroundWindow {
|
||||||
handle: internal_id.to_owned(),
|
handle: internal_id.to_owned(),
|
||||||
// KWin does not expose the PID to scripts, so resolution happens on the
|
// Present on Plasma 6 — see the note in SCRIPT_JS.
|
||||||
// Rust side from resource_class. Left `None` rather than guessed.
|
pid,
|
||||||
pid: None,
|
|
||||||
exe_path: None,
|
exe_path: None,
|
||||||
title: (!caption.is_empty()).then(|| caption.to_owned()),
|
title: (!caption.is_empty()).then(|| caption.to_owned()),
|
||||||
desktop_id: (!resource_class.is_empty())
|
desktop_id: (!resource_class.is_empty())
|
||||||
|
|||||||
@@ -40,6 +40,8 @@
|
|||||||
//! 6.1 requires this be documented honestly rather than implied otherwise.
|
//! 6.1 requires this be documented honestly rather than implied otherwise.
|
||||||
|
|
||||||
pub mod kwin;
|
pub mod kwin;
|
||||||
|
|
||||||
|
pub use kwin::parse_payload;
|
||||||
pub mod x11;
|
pub mod x11;
|
||||||
|
|
||||||
/// A window that has taken focus, resolved to something a schedule can match.
|
/// A window that has taken focus, resolved to something a schedule can match.
|
||||||
|
|||||||
+27
-15
@@ -27,7 +27,7 @@ which parts to trust.
|
|||||||
| [3](#adr-3) | Event-driven with `next_wakeup`, never polling | Accepted |
|
| [3](#adr-3) | Event-driven with `next_wakeup`, never polling | Accepted |
|
||||||
| [4](#adr-4) | SQLite via rusqlite, in its own crate | Accepted, one trade-off open |
|
| [4](#adr-4) | SQLite via rusqlite, in its own crate | Accepted, one trade-off open |
|
||||||
| [5](#adr-5) | uniffi for core-to-Kotlin bindings | Proposed |
|
| [5](#adr-5) | uniffi for core-to-Kotlin bindings | Proposed |
|
||||||
| [6](#adr-6) | Desktop UI toolkit — Slint vs egui | **Proposed, needs owner** |
|
| [6](#adr-6) | Desktop UI toolkit — egui | **Accepted** |
|
||||||
| [7](#adr-7) | Desktop website blocking mechanism | **Accepted — owner chose extension** |
|
| [7](#adr-7) | Desktop website blocking mechanism | **Accepted — owner chose extension** |
|
||||||
| [8](#adr-8) | Lock levels and explicit opt-in | Accepted |
|
| [8](#adr-8) | Lock levels and explicit opt-in | Accepted |
|
||||||
| [9](#adr-9) | Monotonic clock for all lock timers | Accepted |
|
| [9](#adr-9) | Monotonic clock for all lock timers | Accepted |
|
||||||
@@ -169,29 +169,41 @@ to commit, and the measurement belongs to M3-Android.
|
|||||||
---
|
---
|
||||||
|
|
||||||
<a id="adr-6"></a>
|
<a id="adr-6"></a>
|
||||||
## ADR-6: Desktop UI toolkit — Slint vs egui
|
## ADR-6: Desktop UI toolkit — egui
|
||||||
|
|
||||||
**Status:** **Proposed — awaiting the owner's decision**
|
**Status:** Accepted (the owner chose egui, 2026-10-01)
|
||||||
|
|
||||||
**Context.** Section 3 requires the toolkit to be chosen on a measured
|
**Context.** Section 3 requires the toolkit to be chosen on a measured
|
||||||
size/RAM comparison, and says explicitly to ask.
|
size/RAM comparison, and says explicitly to ask.
|
||||||
|
|
||||||
**Decision.** Not yet made.
|
**Decision.** **egui**, licensed MIT OR Apache-2.0.
|
||||||
|
|
||||||
**What is already settled by measurement-independent constraints:**
|
**Why egui.**
|
||||||
|
|
||||||
- **Slint is GPL-3.0 or commercial.** For a project intended to be published,
|
- **The licence decides part of it.** Slint is GPL-3.0 or commercial, so choosing
|
||||||
the permissive alternatives are the safer default. If the size comparison comes
|
it would force the project under copyleft or require a paid licence. The owner
|
||||||
out close, licensing should decide it.
|
has left the project licence open (ADR-17), so this is a real constraint
|
||||||
- Either way the daemon must stay under 5 MB stripped, and the UI must not hold a
|
rather than a formality — and it is why ADR-17 is upstream of this decision.
|
||||||
GL context resident while idle.
|
- **The budget is adequate.** 2.9 MB of the 5 MB stripped daemon budget remained
|
||||||
|
after storage (1.364 MB) and the D-Bus/X11 backends (2.237 MB) were linked in.
|
||||||
|
A block screen is text and a countdown; it does not need more.
|
||||||
|
- **Idle cost is the deciding factor.** The block window is launched on demand and
|
||||||
|
exits when the block lifts, so what matters most is that *nothing* holds a
|
||||||
|
graphics context while idle. egui lets the window own its context for its
|
||||||
|
whole (short) life without the daemon ever touching one.
|
||||||
|
|
||||||
**What still needs measuring:** stripped binary size for a minimal window, idle
|
**What this does not decide.** The measurement the section asks for is still worth
|
||||||
RSS, dependency count and compile time, and behaviour on KDE Plasma 6 Wayland
|
doing — it is recorded in `PERF.md` — but it is now confirmatory rather than
|
||||||
specifically — this project must work on KDE, so winit/Slint quirks on that
|
decisive.
|
||||||
compositor matter more than generic Wayland support.
|
|
||||||
|
|
||||||
Recorded rather than decided because section 3 says to ask.
|
**Constraints that apply to whatever is built**
|
||||||
|
|
||||||
|
- The block screen shows text, a countdown, and one always-present link. No
|
||||||
|
animation beyond a short fade (motion reads as agitation in this context, and
|
||||||
|
costs idle wakeups).
|
||||||
|
- Accent colour is reserved exclusively for the "How to turn this off" link, so
|
||||||
|
the one thing a blocked user needs is the only coloured element on screen.
|
||||||
|
- No red, and no shaming copy.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user