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:
2026-04-25 12:01:25 +01:00
parent 0bd58c912f
commit dc69317ddf
55 changed files with 1535 additions and 1932 deletions
@@ -14,6 +14,7 @@ pub struct DeviceMap {
}
impl DeviceMap {
#[cfg(test)]
pub fn new() -> Self {
let static_paths = HashMap::from([
((226, 0), String::from("/scheme/drm/card0")),
@@ -31,6 +32,31 @@ impl DeviceMap {
Self { static_paths }
}
/// Build a device map that merges static entries with dynamically discovered
/// devices by scanning `/scheme/drm/card*` and `/dev/input/event*` at startup.
/// For each discovered path, stat is used to read the rdev (device number).
/// Entries with a nonzero rdev are inserted into the map; static entries are
/// kept as fallback when rdev is unavailable or zero.
pub fn discover() -> Self {
let mut paths = HashMap::from([
((226, 0), String::from("/scheme/drm/card0")),
((226, 1), String::from("/scheme/drm/card1")),
((13, 64), String::from("/dev/input/event0")),
((13, 65), String::from("/dev/input/event1")),
((13, 66), String::from("/dev/input/event2")),
((13, 67), String::from("/dev/input/event3")),
((29, 0), String::from("/dev/fb0")),
((1, 1), String::from("/scheme/null")),
((1, 5), String::from("/scheme/zero")),
((1, 8), String::from("/scheme/rand")),
]);
discover_scheme_drm(&mut paths);
discover_dev_input(&mut paths);
Self { static_paths: paths }
}
pub fn resolve(&self, major: u32, minor: u32) -> Option<String> {
if let Some(path) = self.static_paths.get(&(major, minor)) {
return Some(path.clone());
@@ -83,6 +109,74 @@ impl DeviceMap {
}
}
/// Scan `/scheme/drm/` for `card*` entries and merge any with a nonzero rdev
/// into the provided map. Static entries are not overwritten.
fn discover_scheme_drm(paths: &mut HashMap<(u32, u32), String>) {
let entries = match fs::read_dir("/scheme/drm") {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let path = entry.path();
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
continue;
};
if !name.starts_with("card") {
continue;
}
#[cfg(unix)]
if let Ok(metadata) = fs::metadata(&path) {
let rdev = metadata.rdev();
if rdev != 0 {
let major = dev_major(rdev);
let minor = dev_minor(rdev);
paths
.entry((major, minor))
.or_insert_with(|| path.to_string_lossy().into_owned());
}
}
#[cfg(not(unix))]
let _ = &path;
}
}
/// Scan `/dev/input/` for `event*` entries and merge any with a nonzero rdev
/// into the provided map. Static entries are not overwritten.
fn discover_dev_input(paths: &mut HashMap<(u32, u32), String>) {
let entries = match fs::read_dir("/dev/input") {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let path = entry.path();
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
continue;
};
if !name.starts_with("event") {
continue;
}
#[cfg(unix)]
if let Ok(metadata) = fs::metadata(&path) {
let rdev = metadata.rdev();
if rdev != 0 {
let major = dev_major(rdev);
let minor = dev_minor(rdev);
paths
.entry((major, minor))
.or_insert_with(|| path.to_string_lossy().into_owned());
}
}
#[cfg(not(unix))]
let _ = &path;
}
}
fn candidate_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
@@ -162,4 +256,12 @@ mod tests {
assert_eq!(dev_major(event), 13);
assert_eq!(dev_minor(event), 67);
}
#[test]
fn discover_returns_static_entries_when_no_dirs() {
let map = super::DeviceMap::discover();
assert!(map.resolve(226, 0).is_some());
assert!(map.resolve(13, 64).is_some());
assert!(map.resolve(29, 0).is_some());
}
}