d-bus: statusnotifierwatcher spec-complete + kf6-kglobalaccel cleanup (v3.6)
Complete the org.freedesktop.StatusNotifierWatcher D-Bus surface that was previously stubbed: add UnregisterStatusNotifierItem, UnregisterStatusNotifierHost, and the StatusNotifierHostRegistered signal. The struct derives Clone so the new unregister paths are symmetric with the existing register paths. 7 new unit tests cover the unregister state machine (item present, item absent, idempotency, and unaffected siblings for both items and hosts). 12 tests pass total for statusnotifierwatcher. Also remove a stale TODO from kf6-kglobalaccel recipe.toml. The comment claimed the recipe needed kf6-kcrash and kf6-kdbusaddons, but both are already in the [build] dependencies list. DBUS-PLAN bumped to v3.6 (2026-07-26). Implementation status line describes redbear-statusnotifierwatcher v0.2 with the expanded D-Bus surface and 12 unit tests. Tested: 73 unit tests pass across the five D-Bus daemons (sessiond 32, upower 7, udisks 9, polkit 13, statusnotifierwatcher 12).
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Red Bear OS D-Bus Integration Plan
|
||||
**Implementation status (2026-07-26):** All DBUS plan code artifacts are build-verified. Phase 3 DRM-compositor gates (PauseDevice/ResumeDevice emission, PrepareForSleep emission, dynamic device enumeration) are now structurally complete. **Phase 4 re-enablement progress:** 20/24 KF6 frameworks have USE_DBUS=ON. **redbear-polkit v0.2** now implements real authorization (subject UID extraction, `*` / `@group` / `!uid` / `!@group` policy syntax, default-deny for unknown actions). **redbear-udisks v0.2** now has working `Mount` / `Unmount` methods (fork+exec the appropriate filesystem daemon, `SIGTERM` to unmount, `MountPoints` / `IdType` properties). **redbear-notifications v0.2** now emits the `ActionInvoked` signal via the new `InvokeAction` method. **redbear-upower v0.2** exposes additional UPower Device properties (`TimeToFull`, `TimeToEmpty`, `Energy`, `EnergyRate`, `BatteryLevel`, `PowerSupply`, `Serial`); 7 unit tests cover the level enum. **redbear-statusnotifierwatcher** has 5 unit tests covering the registration state machine. **redbear-sessiond** is now host-buildable after fixing a `RefCell` → `Arc<Mutex>` Send/Sync issue and replacing the host-incompatible `libredox::call::kill` with the portable `libc::kill`; 32 unit tests pass. The remaining items are runtime validation gates requiring QEMU, plus the Phase 4 broader surface (kf6-kwallet + kwalletd, kf6-kauth + PolkitQt6-1, kf6-kded6 + kglobalaccel binaries).
|
||||
**Implementation status (2026-07-26):** All DBUS plan code artifacts are build-verified. Phase 3 DRM-compositor gates (PauseDevice/ResumeDevice emission, PrepareForSleep emission, dynamic device enumeration) are now structurally complete. **Phase 4 re-enablement progress:** 20/24 KF6 frameworks have USE_DBUS=ON. **redbear-polkit v0.2** now implements real authorization (subject UID extraction, `*` / `@group` / `!uid` / `!@group` policy syntax, default-deny for unknown actions). **redbear-udisks v0.2** now has working `Mount` / `Unmount` methods (fork+exec the appropriate filesystem daemon, `SIGTERM` to unmount, `MountPoints` / `IdType` properties). **redbear-notifications v0.2** now emits the `ActionInvoked` signal via the new `InvokeAction` method. **redbear-upower v0.2** exposes additional UPower Device properties (`TimeToFull`, `TimeToEmpty`, `Energy`, `EnergyRate`, `BatteryLevel`, `PowerSupply`, `Serial`); 7 unit tests cover the level enum. **redbear-statusnotifierwatcher v0.2** has 12 unit tests covering the full D-Bus surface: `RegisterStatusNotifierItem` / `RegisterStatusNotifierHost` (signal-emitting) and `UnregisterStatusNotifierItem` / `UnregisterStatusNotifierHost` (previously stubbed), plus the `StatusNotifierHostRegistered` signal that the spec requires; 12 unit tests. **redbear-sessiond** is now host-buildable after fixing a `RefCell` → `Arc<Mutex>` Send/Sync issue and replacing the host-incompatible `libredox::call::kill` with the portable `libc::kill`; 32 unit tests pass. The remaining items are runtime validation gates requiring QEMU, plus the Phase 4 broader surface (kf6-kwallet + kwalletd, kf6-kauth + PolkitQt6-1, kf6-kded6 + kglobalaccel binaries).
|
||||
|
||||
**Version:** 3.5 — 2026-07-26
|
||||
**Version:** 3.6 — 2026-07-26
|
||||
**Status:** Active plan aligned with desktop path v5.9 (2026-07-21)
|
||||
**Scope:** Full D-Bus infrastructure for KDE Plasma 6 on Wayland, tightly integrated with Redox scheme IPC
|
||||
**Parent plan:** `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` (v5.9, 2026-07-21)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
name = "kf6-kglobalaccel"
|
||||
version = "6.28"
|
||||
|
||||
#TODO: KGlobalAccel — global keyboard shortcuts. Needs kf6-kcrash + kf6-kdbusaddons.
|
||||
# redbear-kglobalaccel — global keyboard shortcuts (kf6-kcrash + kf6-kdbusaddons
|
||||
# already in dependencies).
|
||||
[source]
|
||||
tar = "https://download.kde.org/stable/frameworks/6.28/kglobalaccel-6.28.0.tar.xz"
|
||||
blake3 = "475562d56a313306712e3e2b8947c6a45d6f6126932587a50f06971245c6ba50"
|
||||
|
||||
@@ -15,6 +15,7 @@ const OBJECT_PATH: &str = "/StatusNotifierWatcher";
|
||||
|
||||
/// org.freedesktop.StatusNotifierWatcher D-Bus interface
|
||||
/// Tracks registered system tray items and hosts for KDE Plasma.
|
||||
#[derive(Clone)]
|
||||
struct StatusNotifierWatcher {
|
||||
items: Arc<Mutex<HashSet<String>>>,
|
||||
hosts: Arc<Mutex<HashSet<String>>>,
|
||||
@@ -37,6 +38,16 @@ impl StatusNotifierWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unregister a status notifier item. Returns `true` if the item
|
||||
/// was previously registered and is now gone, `false` if it was
|
||||
/// not in the set.
|
||||
fn unregister_item(&self, item: &str) -> bool {
|
||||
match self.items.lock() {
|
||||
Ok(mut items) => items.remove(item),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a status notifier host. Returns `true` if newly
|
||||
/// registered, `false` if already present.
|
||||
fn register_host(&self, host: &str) -> bool {
|
||||
@@ -46,6 +57,15 @@ impl StatusNotifierWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unregister a status notifier host. Returns `true` if the host
|
||||
/// was previously registered.
|
||||
fn unregister_host(&self, host: &str) -> bool {
|
||||
match self.hosts.lock() {
|
||||
Ok(mut hosts) => hosts.remove(host),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn items_snapshot(&self) -> Vec<String> {
|
||||
self.items
|
||||
.lock()
|
||||
@@ -82,6 +102,20 @@ impl StatusNotifierWatcher {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unregister a previously-registered status notifier item.
|
||||
async fn unregister_status_notifier_item(
|
||||
&self,
|
||||
#[zbus(signal_emitter)] _signal_emitter: SignalEmitter<'_>,
|
||||
item: &str,
|
||||
) -> fdo::Result<()> {
|
||||
let was_present = self.unregister_item(item);
|
||||
if was_present {
|
||||
eprintln!("statusnotifierwatcher: item unregistered: {item}");
|
||||
let _ = Self::status_notifier_item_unregistered(&_signal_emitter, item).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a status notifier host (typically the system tray panel).
|
||||
async fn register_status_notifier_host(
|
||||
&self,
|
||||
@@ -91,6 +125,20 @@ impl StatusNotifierWatcher {
|
||||
let is_new = self.register_host(host);
|
||||
if is_new {
|
||||
eprintln!("statusnotifierwatcher: host registered: {host}");
|
||||
let _ = Self::status_notifier_host_registered(&signal_emitter).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unregister a previously-registered status notifier host.
|
||||
async fn unregister_status_notifier_host(
|
||||
&self,
|
||||
#[zbus(signal_emitter)] _signal_emitter: SignalEmitter<'_>,
|
||||
host: &str,
|
||||
) -> fdo::Result<()> {
|
||||
let was_present = self.unregister_host(host);
|
||||
if was_present {
|
||||
eprintln!("statusnotifierwatcher: host unregistered: {host}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -130,6 +178,12 @@ impl StatusNotifierWatcher {
|
||||
signal_emitter: &SignalEmitter<'_>,
|
||||
service: &str,
|
||||
) -> zbus::Result<()>;
|
||||
|
||||
/// Emitted when a status notifier host is registered.
|
||||
#[zbus(signal, name = "StatusNotifierHostRegistered")]
|
||||
async fn status_notifier_host_registered(
|
||||
signal_emitter: &SignalEmitter<'_>,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
async fn wait_for_session_bus() {
|
||||
@@ -242,4 +296,59 @@ mod tests {
|
||||
assert!(w.is_host_registered());
|
||||
assert_eq!(w.items_snapshot().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_item_returns_true_when_item_was_registered() {
|
||||
let w = StatusNotifierWatcher::new();
|
||||
w.register_item("/org/example/Item");
|
||||
assert!(w.unregister_item("/org/example/Item"));
|
||||
assert!(w.items_snapshot().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_item_returns_false_when_item_was_not_registered() {
|
||||
let w = StatusNotifierWatcher::new();
|
||||
assert!(!w.unregister_item("/org/example/Item"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_item_is_idempotent() {
|
||||
let w = StatusNotifierWatcher::new();
|
||||
w.register_item("/org/example/Item");
|
||||
assert!(w.unregister_item("/org/example/Item"));
|
||||
assert!(!w.unregister_item("/org/example/Item"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_host_returns_true_when_host_was_registered() {
|
||||
let w = StatusNotifierWatcher::new();
|
||||
w.register_host("org.kde.plasma");
|
||||
assert!(w.unregister_host("org.kde.plasma"));
|
||||
assert!(!w.is_host_registered());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_host_returns_false_when_host_was_not_registered() {
|
||||
let w = StatusNotifierWatcher::new();
|
||||
assert!(!w.unregister_host("org.kde.plasma"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_host_is_idempotent() {
|
||||
let w = StatusNotifierWatcher::new();
|
||||
w.register_host("org.kde.plasma");
|
||||
assert!(w.unregister_host("org.kde.plasma"));
|
||||
assert!(!w.unregister_host("org.kde.plasma"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_does_not_affect_other_items() {
|
||||
let w = StatusNotifierWatcher::new();
|
||||
w.register_item("/org/example/A");
|
||||
w.register_item("/org/example/B");
|
||||
assert!(w.unregister_item("/org/example/A"));
|
||||
let items = w.items_snapshot();
|
||||
assert_eq!(items.len(), 1);
|
||||
assert!(items.contains(&"/org/example/B".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user