Files
RedBear-OS/local/recipes/system/redbear-sessiond/source/src/control.rs
T
vasilito dc69317ddf 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
2026-04-25 12:01:25 +01:00

222 lines
6.6 KiB
Rust

use std::{
fs,
io::{BufRead, BufReader},
os::unix::{fs::PermissionsExt, net::UnixListener},
path::Path,
sync::Arc,
};
use serde::Deserialize;
use crate::runtime_state::SharedRuntime;
pub const CONTROL_SOCKET_PATH: &str = "/run/redbear-sessiond-control.sock";
#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum ControlMessage {
SetSession {
username: String,
uid: u32,
vt: u32,
leader: u32,
state: String,
},
ResetSession {
vt: u32,
},
Shutdown,
}
fn apply_message(
runtime: &SharedRuntime,
shutdown_tx: &tokio::sync::watch::Sender<bool>,
message: ControlMessage,
) {
match message {
ControlMessage::SetSession {
username,
uid,
vt,
leader,
state,
} => {
let Ok(mut runtime) = runtime.write() else {
eprintln!("redbear-sessiond: runtime state is poisoned");
return;
};
runtime.username = username;
runtime.uid = uid;
runtime.vt = vt;
runtime.leader = leader;
runtime.state = state;
runtime.active = true;
}
ControlMessage::ResetSession { vt } => {
let Ok(mut runtime) = runtime.write() else {
eprintln!("redbear-sessiond: runtime state is poisoned");
return;
};
runtime.username = String::from("root");
runtime.uid = 0;
runtime.vt = vt;
runtime.leader = std::process::id();
runtime.state = String::from("closing");
runtime.active = true;
}
ControlMessage::Shutdown => {
eprintln!("redbear-sessiond: shutdown requested via control socket");
let _ = shutdown_tx.send(true);
}
}
}
pub fn start_control_socket(
runtime: SharedRuntime,
shutdown_tx: tokio::sync::watch::Sender<bool>,
) {
let shutdown_tx = Arc::new(shutdown_tx);
std::thread::spawn(move || {
if Path::new(CONTROL_SOCKET_PATH).exists() {
if let Err(err) = fs::remove_file(CONTROL_SOCKET_PATH) {
eprintln!("redbear-sessiond: failed to remove stale control socket: {err}");
return;
}
}
let listener = match UnixListener::bind(CONTROL_SOCKET_PATH) {
Ok(listener) => listener,
Err(err) => {
eprintln!("redbear-sessiond: failed to bind control socket: {err}");
return;
}
};
if let Err(err) = fs::set_permissions(CONTROL_SOCKET_PATH, fs::Permissions::from_mode(0o600)) {
eprintln!("redbear-sessiond: failed to chmod control socket: {err}");
}
let shutdown_ref = Arc::clone(&shutdown_tx);
for stream in listener.incoming() {
let Ok(stream) = stream else {
continue;
};
let mut reader = BufReader::new(stream);
let mut line = String::new();
if reader.read_line(&mut line).is_err() {
continue;
}
match serde_json::from_str::<ControlMessage>(line.trim()) {
Ok(message) => apply_message(&runtime, &shutdown_ref, message),
Err(err) => eprintln!("redbear-sessiond: invalid control message: {err}"),
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime_state::shared_runtime;
fn test_shutdown_channel() -> (tokio::sync::watch::Sender<bool>, tokio::sync::watch::Receiver<bool>) {
tokio::sync::watch::channel(false)
}
#[test]
fn set_session_message_updates_runtime_state() {
let runtime = shared_runtime();
let (tx, _rx) = test_shutdown_channel();
apply_message(
&runtime,
&tx,
ControlMessage::SetSession {
username: String::from("user"),
uid: 1000,
vt: 7,
leader: 4242,
state: String::from("active"),
},
);
let runtime = runtime.read().expect("runtime lock should remain healthy");
assert_eq!(runtime.username, "user");
assert_eq!(runtime.uid, 1000);
assert_eq!(runtime.vt, 7);
assert_eq!(runtime.leader, 4242);
assert_eq!(runtime.state, "active");
assert!(runtime.active);
}
#[test]
fn reset_session_message_restores_root_scaffold() {
let runtime = shared_runtime();
let (tx, _rx) = test_shutdown_channel();
apply_message(
&runtime,
&tx,
ControlMessage::SetSession {
username: String::from("user"),
uid: 1000,
vt: 7,
leader: 4242,
state: String::from("active"),
},
);
apply_message(&runtime, &tx, ControlMessage::ResetSession { vt: 3 });
let runtime = runtime.read().expect("runtime lock should remain healthy");
assert_eq!(runtime.username, "root");
assert_eq!(runtime.uid, 0);
assert_eq!(runtime.vt, 3);
assert_eq!(runtime.state, "closing");
assert!(runtime.active);
}
#[test]
fn control_message_json_matches_expected_shape() {
let message = serde_json::from_str::<ControlMessage>(
r#"{"type":"set_session","username":"user","uid":1000,"vt":3,"leader":99,"state":"online"}"#,
)
.expect("control message json should parse");
match message {
ControlMessage::SetSession {
username,
uid,
vt,
leader,
state,
} => {
assert_eq!(username, "user");
assert_eq!(uid, 1000);
assert_eq!(vt, 3);
assert_eq!(leader, 99);
assert_eq!(state, "online");
}
ControlMessage::ResetSession { .. } | ControlMessage::Shutdown => {
panic!("expected set_session message")
}
}
}
#[test]
fn shutdown_message_sends_true_on_channel() {
let runtime = shared_runtime();
let (tx, mut rx) = test_shutdown_channel();
apply_message(&runtime, &tx, ControlMessage::Shutdown);
assert!(*rx.borrow_and_update());
}
#[test]
fn shutdown_message_parses_from_json() {
let message = serde_json::from_str::<ControlMessage>(r#"{"type":"shutdown"}"#)
.expect("shutdown message should parse");
assert!(matches!(message, ControlMessage::Shutdown));
}
}