Advance Red Bear runtime services and tools
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user