Advance Red Bear runtime services and tools

This commit is contained in:
2026-04-20 18:37:35 +01:00
parent 2b3b592dab
commit f3e6b09811
24 changed files with 1362 additions and 357 deletions
@@ -1,5 +1,7 @@
use zbus::Connection;
use crate::runtime_state::SharedRuntime;
#[cfg(target_os = "redox")]
const KSTOP_PATH: &str = "/scheme/kernel.acpi/kstop";
@@ -13,10 +15,13 @@ fn wait_for_shutdown_edge() -> std::io::Result<()> {
Ok(())
}
pub async fn watch_and_emit(connection: Connection) {
pub async fn watch_and_emit(connection: Connection, runtime: SharedRuntime) {
#[cfg(target_os = "redox")]
match tokio::task::spawn_blocking(wait_for_shutdown_edge).await {
Ok(Ok(())) => {
if let Ok(mut state) = runtime.write() {
state.preparing_for_shutdown = true;
}
let _ = connection
.emit_signal(
None::<&str>,
@@ -38,5 +43,6 @@ pub async fn watch_and_emit(connection: Connection) {
#[cfg(not(target_os = "redox"))]
{
let _ = connection;
let _ = runtime;
}
}
@@ -133,7 +133,7 @@ async fn run_daemon() -> Result<(), Box<dyn Error>> {
Ok(connection) => {
eprintln!("redbear-sessiond: registered {BUS_NAME} on the system bus");
control::start_control_socket(runtime.clone());
tokio::spawn(acpi_watcher::watch_and_emit(connection.clone()));
tokio::spawn(acpi_watcher::watch_and_emit(connection.clone(), runtime.clone()));
wait_for_shutdown().await?;
drop(connection);
return Ok(());
@@ -31,7 +31,7 @@ impl LoginManager {
.runtime
.read()
.map_err(|_| fdo::Error::Failed(String::from("login1 runtime state is poisoned")))?;
if id == runtime.session_id {
if id == runtime.session_id || id == "auto" {
return Ok(self.session_path.clone());
}
@@ -111,7 +111,10 @@ impl LoginManager {
#[zbus(property(emits_changed_signal = "const"), name = "PreparingForShutdown")]
fn preparing_for_shutdown(&self) -> bool {
false
self.runtime
.read()
.map(|runtime| runtime.preparing_for_shutdown)
.unwrap_or(false)
}
#[zbus(signal, name = "PrepareForSleep")]
@@ -123,3 +126,60 @@ impl LoginManager {
before: bool,
) -> zbus::Result<()>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime_state::shared_runtime;
#[test]
fn get_session_accepts_runtime_session_id() {
let manager = LoginManager::new(
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1"))
.expect("session path should parse"),
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0"))
.expect("seat path should parse"),
shared_runtime(),
);
let path = manager
.get_session("c1")
.expect("runtime session id should resolve");
assert_eq!(path.as_str(), "/org/freedesktop/login1/session/c1");
}
#[test]
fn get_session_accepts_auto_alias() {
let manager = LoginManager::new(
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1"))
.expect("session path should parse"),
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0"))
.expect("seat path should parse"),
shared_runtime(),
);
let path = manager
.get_session("auto")
.expect("auto alias should resolve to current session");
assert_eq!(path.as_str(), "/org/freedesktop/login1/session/c1");
}
#[test]
fn preparing_for_shutdown_reflects_runtime_state() {
let runtime = shared_runtime();
runtime
.write()
.expect("runtime lock should be writable")
.preparing_for_shutdown = true;
let manager = LoginManager::new(
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1"))
.expect("session path should parse"),
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0"))
.expect("seat path should parse"),
runtime,
);
assert!(manager.preparing_for_shutdown());
}
}
@@ -10,6 +10,7 @@ pub struct SessionRuntime {
pub leader: u32,
pub state: String,
pub active: bool,
pub preparing_for_shutdown: bool,
}
impl Default for SessionRuntime {
@@ -23,6 +24,7 @@ impl Default for SessionRuntime {
leader: std::process::id(),
state: String::from("online"),
active: true,
preparing_for_shutdown: false,
}
}
}
@@ -1,4 +1,4 @@
use std::sync::Mutex;
use std::{process::Command, sync::Mutex};
use zbus::{fdo, interface, zvariant::OwnedObjectPath};
@@ -27,17 +27,55 @@ impl LoginSeat {
.lock()
.map_err(|_| fdo::Error::Failed(String::from("seat VT state is poisoned")))
}
fn request_vt_switch(program: &str, vt: u32) -> fdo::Result<()> {
let output = Command::new(program)
.args(["-A", &vt.to_string()])
.output()
.map_err(|err| fdo::Error::Failed(format!("failed to run {program} -A {vt}: {err}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let detail = if !stderr.trim().is_empty() {
stderr.trim().to_string()
} else if !stdout.trim().is_empty() {
stdout.trim().to_string()
} else {
format!("exit status {}", output.status)
};
return Err(fdo::Error::Failed(format!(
"{program} -A {vt} failed: {detail}"
)));
}
Ok(())
}
fn current_session_id(&self) -> String {
self.runtime
.read()
.map(|runtime| runtime.session_id.clone())
.unwrap_or_else(|_| String::from("c1"))
}
}
#[interface(name = "org.freedesktop.login1.Seat")]
impl LoginSeat {
fn switch_to(&mut self, vt: u32) -> fdo::Result<()> {
Self::request_vt_switch("inputd", vt)?;
let mut last_requested_vt = self.last_requested_vt()?;
*last_requested_vt = vt;
eprintln!(
"redbear-sessiond: SwitchTo requested for seat {} -> vt {vt} (delegated to inputd -A externally)",
self.id
);
let mut runtime = self
.runtime
.write()
.map_err(|_| fdo::Error::Failed(String::from("seat runtime state is poisoned")))?;
runtime.vt = vt;
runtime.active = true;
eprintln!("redbear-sessiond: SwitchTo requested for seat {} -> vt {vt}", self.id);
Ok(())
}
@@ -48,24 +86,12 @@ impl LoginSeat {
#[zbus(property(emits_changed_signal = "const"), name = "ActiveSession")]
fn active_session(&self) -> (String, OwnedObjectPath) {
(
self.runtime
.read()
.map(|runtime| runtime.session_id.clone())
.unwrap_or_else(|_| String::from("c1")),
self.session_path.clone(),
)
(self.current_session_id(), self.session_path.clone())
}
#[zbus(property(emits_changed_signal = "const"), name = "Sessions")]
fn sessions(&self) -> Vec<(String, OwnedObjectPath)> {
vec![(
self.runtime
.read()
.map(|runtime| runtime.session_id.clone())
.unwrap_or_else(|_| String::from("c1")),
self.session_path.clone(),
)]
vec![(self.current_session_id(), self.session_path.clone())]
}
#[zbus(property(emits_changed_signal = "const"), name = "CanGraphical")]
@@ -83,3 +109,39 @@ impl LoginSeat {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime_state::shared_runtime;
#[test]
fn request_vt_switch_accepts_successful_command() {
LoginSeat::request_vt_switch("/bin/true", 3).expect("true should succeed");
}
#[test]
fn request_vt_switch_rejects_failed_command() {
let err = LoginSeat::request_vt_switch("/bin/false", 3).expect_err("false should fail");
match err {
fdo::Error::Failed(message) => {
assert!(message.contains("/bin/false -A 3 failed"));
}
other => panic!("expected failed error, got {other:?}"),
}
}
#[test]
fn active_session_reflects_runtime_vt_after_update() {
let session_path = OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1"))
.expect("session path should parse");
let runtime = shared_runtime();
{
let mut guard = runtime.write().expect("runtime lock should remain healthy");
guard.vt = 7;
}
let seat = LoginSeat::new(session_path, runtime);
assert_eq!(seat.active_session().0, "c1");
assert_eq!(seat.id(), "seat0");
}
}