D-Bus Phase 3/4: upgrade sessiond, services, add StatusNotifierWatcher, consolidate configs
- redbear-sessiond: add Manager.Inhibit (pipe FD), CanPowerOff/CanReboot/ CanSuspend/CanHibernate/CanHybridSleep/CanSleep (return na), PowerOff/ Reboot/Suspend stubs, GetSessionByPID, ListUsers, ListSeats, ListInhibitors, ActivateSession/LockSession/UnlockSession/TerminateSession - redbear-sessiond: add Session SetIdleHint, SetLockedHint, SetType, Terminate methods; wire PauseDevice/ResumeDevice/Lock/Unlock signal emission via SignalEmitter injection; add dynamic device enumeration scanning /scheme/drm/card* and /dev/input/event* at startup - redbear-sessiond: replace infinite pending() with stoppable shutdown via tokio watch channel + control socket shutdown command - redbear-upower: add Changed signal emission with 30s periodic polling and power state snapshot comparison - redbear-notifications: add ActionInvoked signal, expand capabilities to body + body-markup + actions - redbear-polkit, redbear-udisks: replace pending() with stoppable shutdown via signal handling + watch channel - Add redbear-statusnotifierwatcher: new session bus service implementing org.freedesktop.StatusNotifierWatcher for KDE system tray - Add D-Bus activation file for StatusNotifierWatcher - KWin session.cpp: try LogindSession before NoopSession fallback - Consolidate config profiles: remove obsolete redbear-desktop, redbear-kde, redbear-live-*, redbear-minimal-*, redbear-wayland configs; simplify to three supported targets (redbear-full, redbear-mini, redbear-grub) - Update DBUS-INTEGRATION-PLAN.md and DESKTOP-STACK-CURRENT-STATUS.md with Phase 3/4 fragility assessment, KWin readiness matrix, and completeness gap analysis
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "redbear-statusnotifierwatcher"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "redbear-statusnotifierwatcher"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
zbus = { version = "5", default-features = false, features = ["tokio"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
@@ -0,0 +1,168 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use zbus::{
|
||||
fdo::{self, ObjectManager},
|
||||
interface,
|
||||
object_server::SignalEmitter,
|
||||
connection::Builder as ConnectionBuilder,
|
||||
zvariant::ObjectPath,
|
||||
};
|
||||
|
||||
const BUS_NAME: &str = "org.freedesktop.StatusNotifierWatcher";
|
||||
const OBJECT_PATH: &str = "/StatusNotifierWatcher";
|
||||
|
||||
/// org.freedesktop.StatusNotifierWatcher D-Bus interface
|
||||
/// Tracks registered system tray items and hosts for KDE Plasma.
|
||||
struct StatusNotifierWatcher {
|
||||
items: Arc<Mutex<HashSet<String>>>,
|
||||
hosts: Arc<Mutex<HashSet<String>>>,
|
||||
}
|
||||
|
||||
#[interface(name = "org.freedesktop.StatusNotifierWatcher")]
|
||||
impl StatusNotifierWatcher {
|
||||
// --- Methods ---
|
||||
|
||||
/// Register a status notifier item.
|
||||
/// The item parameter is either a full object path (e.g., "/org/example/Item")
|
||||
/// sent by the item itself, or a bus name (e.g., ":1.42" or "org.example.App")
|
||||
/// sent via the KDE protocol extension.
|
||||
async fn register_status_notifier_item(
|
||||
&self,
|
||||
#[zbus(signal_emitter)] signal_emitter: SignalEmitter<'_>,
|
||||
item: &str,
|
||||
) -> fdo::Result<()> {
|
||||
let is_new = {
|
||||
let mut items = self.items.lock().map_err(|e| {
|
||||
fdo::Error::Failed(format!("items lock poisoned: {e}"))
|
||||
})?;
|
||||
items.insert(item.to_owned())
|
||||
};
|
||||
if is_new {
|
||||
eprintln!("statusnotifierwatcher: item registered: {item}");
|
||||
let _ = Self::status_notifier_item_registered(&signal_emitter, item).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a status notifier host (typically the system tray panel).
|
||||
async fn register_status_notifier_host(
|
||||
&self,
|
||||
#[zbus(signal_emitter)] signal_emitter: SignalEmitter<'_>,
|
||||
host: &str,
|
||||
) -> fdo::Result<()> {
|
||||
let is_new = {
|
||||
let mut hosts = self.hosts.lock().map_err(|e| {
|
||||
fdo::Error::Failed(format!("hosts lock poisoned: {e}"))
|
||||
})?;
|
||||
hosts.insert(host.to_owned())
|
||||
};
|
||||
if is_new {
|
||||
eprintln!("statusnotifierwatcher: host registered: {host}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Properties ---
|
||||
|
||||
/// List of registered status notifier item bus names / paths.
|
||||
#[zbus(property)]
|
||||
fn registered_status_notifier_items(&self) -> fdo::Result<Vec<String>> {
|
||||
let items = self.items.lock().map_err(|e| {
|
||||
fdo::Error::Failed(format!("items lock poisoned: {e}"))
|
||||
})?;
|
||||
Ok(items.iter().cloned().collect())
|
||||
}
|
||||
|
||||
/// Whether at least one status notifier host is registered.
|
||||
#[zbus(property)]
|
||||
fn is_status_notifier_host_registered(&self) -> fdo::Result<bool> {
|
||||
let hosts = self.hosts.lock().map_err(|e| {
|
||||
fdo::Error::Failed(format!("hosts lock poisoned: {e}"))
|
||||
})?;
|
||||
Ok(!hosts.is_empty())
|
||||
}
|
||||
|
||||
/// Protocol version (always 0 per spec).
|
||||
#[zbus(property)]
|
||||
fn protocol_version(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
// --- Signals ---
|
||||
|
||||
/// Emitted when a new status notifier item is registered.
|
||||
#[zbus(signal, name = "StatusNotifierItemRegistered")]
|
||||
async fn status_notifier_item_registered(
|
||||
signal_emitter: &SignalEmitter<'_>,
|
||||
service: &str,
|
||||
) -> zbus::Result<()>;
|
||||
|
||||
/// Emitted when a status notifier item is unregistered.
|
||||
#[zbus(signal, name = "StatusNotifierItemUnregistered")]
|
||||
async fn status_notifier_item_unregistered(
|
||||
signal_emitter: &SignalEmitter<'_>,
|
||||
service: &str,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
async fn wait_for_session_bus() {
|
||||
for _ in 0..30 {
|
||||
if std::env::var("DBUS_SESSION_BUS_ADDRESS").is_ok() {
|
||||
return;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
wait_for_session_bus().await;
|
||||
|
||||
let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false);
|
||||
|
||||
// Signal handler task for clean shutdown
|
||||
let signal_tx = shutdown_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
if let Ok(mut sigterm) = signal(SignalKind::terminate()) {
|
||||
tokio::select! {
|
||||
_ = sigterm.recv() => {},
|
||||
_ = tokio::signal::ctrl_c() => {},
|
||||
}
|
||||
} else {
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
}
|
||||
let _ = signal_tx.send(true);
|
||||
});
|
||||
// Keep original sender alive so receiver doesn't see all-senders-dropped
|
||||
let _shutdown_guard = shutdown_tx;
|
||||
|
||||
let watcher = StatusNotifierWatcher {
|
||||
items: Arc::new(Mutex::new(HashSet::new())),
|
||||
hosts: Arc::new(Mutex::new(HashSet::new())),
|
||||
};
|
||||
|
||||
let path: ObjectPath<'_> = OBJECT_PATH.try_into()?;
|
||||
let connection = ConnectionBuilder::session()?
|
||||
.name(BUS_NAME)?
|
||||
.serve_at(path, watcher)?
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
eprintln!("statusnotifierwatcher: {BUS_NAME} registered on session bus");
|
||||
|
||||
// Wait for shutdown signal
|
||||
let _ = shutdown_rx.changed().await;
|
||||
eprintln!("statusnotifierwatcher: shutdown signal received, exiting cleanly");
|
||||
drop(connection);
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user