Add D-Bus session and system services

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-17 13:33:17 +01:00
parent 5beb161cd9
commit 60170933b0
58 changed files with 2869 additions and 0 deletions
@@ -0,0 +1,67 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use zbus::Connection;
static SLEEP_ACTIVE: AtomicBool = AtomicBool::new(false);
static SHUTDOWN_FIRED: AtomicBool = AtomicBool::new(false);
const ACPI_SLEEP_PATH: &str = "/scheme/acpi/sleep";
const ACPI_SHUTDOWN_PATH: &str = "/scheme/acpi/shutdown";
const POLL_INTERVAL: Duration = Duration::from_secs(5);
fn read_acpi_flag(path: &str) -> bool {
match std::fs::read_to_string(path) {
Ok(content) => {
let trimmed = content.trim().to_lowercase();
!trimmed.is_empty() && trimmed != "0"
}
Err(_) => false,
}
}
pub async fn watch_and_emit(connection: Connection) {
loop {
tokio::time::sleep(POLL_INTERVAL).await;
let sleep_now = tokio::task::spawn_blocking(|| read_acpi_flag(ACPI_SLEEP_PATH))
.await
.unwrap_or(false);
let was_sleeping = SLEEP_ACTIVE.load(Ordering::Relaxed);
if sleep_now && !was_sleeping {
SLEEP_ACTIVE.store(true, Ordering::Relaxed);
let _ = connection.emit_signal(
None::<&str>,
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"PrepareForSleep",
&true,
).await;
} else if !sleep_now && was_sleeping {
SLEEP_ACTIVE.store(false, Ordering::Relaxed);
let _ = connection.emit_signal(
None::<&str>,
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"PrepareForSleep",
&false,
).await;
}
let shutdown_now = tokio::task::spawn_blocking(|| read_acpi_flag(ACPI_SHUTDOWN_PATH))
.await
.unwrap_or(false);
if shutdown_now && !SHUTDOWN_FIRED.load(Ordering::Relaxed) {
SHUTDOWN_FIRED.store(true, Ordering::Relaxed);
let _ = connection.emit_signal(
None::<&str>,
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"PrepareForShutdown",
&true,
).await;
}
}
}
@@ -0,0 +1,47 @@
use std::{collections::HashMap, fs::File, io};
#[derive(Clone, Debug)]
pub struct DeviceMap {
static_paths: HashMap<(u32, u32), String>,
}
impl DeviceMap {
pub fn new() -> Self {
let static_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")),
]);
Self { static_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());
}
match (major, minor) {
(13, minor) if minor >= 68 => Some(format!("/dev/input/event{}", minor - 64)),
_ => None,
}
}
pub fn open_device(&self, major: u32, minor: u32) -> io::Result<File> {
let Some(path) = self.resolve(major, minor) else {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("no Red Bear device mapping for major={major}, minor={minor}"),
));
};
File::open(path)
}
}
@@ -0,0 +1,148 @@
mod acpi_watcher;
mod device_map;
mod manager;
mod seat;
mod session;
use std::{
env,
error::Error,
process,
};
use device_map::DeviceMap;
use manager::LoginManager;
use seat::LoginSeat;
use session::LoginSession;
use tokio::runtime::Builder as RuntimeBuilder;
use zbus::{
Address,
connection::Builder as ConnectionBuilder,
zvariant::OwnedObjectPath,
};
const BUS_NAME: &str = "org.freedesktop.login1";
const MANAGER_PATH: &str = "/org/freedesktop/login1";
const SESSION_PATH: &str = "/org/freedesktop/login1/session/c1";
const SEAT_PATH: &str = "/org/freedesktop/login1/seat/seat0";
const USER_PATH: &str = "/org/freedesktop/login1/user/0";
enum Command {
Run,
Help,
}
fn usage() -> &'static str {
"Usage: redbear-sessiond [--help]"
}
fn parse_args() -> Result<Command, String> {
let mut args = env::args().skip(1);
match args.next() {
None => Ok(Command::Run),
Some(arg) if arg == "--help" || arg == "-h" => {
if args.next().is_some() {
return Err(String::from("unexpected extra arguments after --help"));
}
Ok(Command::Help)
}
Some(arg) => Err(format!("unrecognized argument '{arg}'")),
}
}
fn parse_object_path(path: &str) -> Result<OwnedObjectPath, Box<dyn Error>> {
Ok(OwnedObjectPath::try_from(path.to_owned())?)
}
fn system_connection_builder() -> Result<ConnectionBuilder<'static>, Box<dyn Error>> {
if let Ok(address) = env::var("DBUS_STARTER_ADDRESS") {
Ok(ConnectionBuilder::address(Address::try_from(address.as_str())?)?)
} else {
Ok(ConnectionBuilder::system()?)
}
}
#[cfg(all(unix, not(target_os = "redox")))]
async fn wait_for_shutdown() -> Result<(), Box<dyn Error>> {
use tokio::signal::unix::{SignalKind, signal};
let mut terminate = signal(SignalKind::terminate())?;
tokio::select! {
_ = terminate.recv() => Ok(()),
_ = tokio::signal::ctrl_c() => Ok(()),
}
}
#[cfg(target_os = "redox")]
async fn wait_for_shutdown() -> Result<(), Box<dyn Error>> {
std::future::pending::<()>().await;
#[allow(unreachable_code)]
Ok(())
}
#[cfg(all(not(unix), not(target_os = "redox")))]
async fn wait_for_shutdown() -> Result<(), Box<dyn Error>> {
tokio::signal::ctrl_c().await?;
Ok(())
}
async fn run_daemon() -> Result<(), Box<dyn Error>> {
eprintln!("redbear-sessiond: startup begin");
let session_path = parse_object_path(SESSION_PATH)?;
let seat_path = parse_object_path(SEAT_PATH)?;
let user_path = parse_object_path(USER_PATH)?;
eprintln!("redbear-sessiond: object paths parsed");
let session = LoginSession::new(seat_path.clone(), user_path, DeviceMap::new());
let seat = LoginSeat::new(session_path.clone());
let manager = LoginManager::new(session_path, seat_path);
eprintln!("redbear-sessiond: starter address={:?}", env::var("DBUS_STARTER_ADDRESS").ok());
eprintln!("redbear-sessiond: building D-Bus connection");
let connection = system_connection_builder()?
.name(BUS_NAME)?
.serve_at(MANAGER_PATH, manager)?
.serve_at(SESSION_PATH, session)?
.serve_at(SEAT_PATH, seat)?
.build()
.await?;
eprintln!("redbear-sessiond: registered {BUS_NAME} on the system bus");
tokio::spawn(acpi_watcher::watch_and_emit(connection.clone()));
wait_for_shutdown().await?;
eprintln!("redbear-sessiond: received shutdown signal, exiting cleanly");
Ok(())
}
fn main() {
match parse_args() {
Ok(Command::Help) => {
println!("{}", usage());
}
Ok(Command::Run) => {
let runtime = match RuntimeBuilder::new_multi_thread().enable_all().build() {
Ok(runtime) => runtime,
Err(err) => {
eprintln!("redbear-sessiond: failed to create tokio runtime: {err}");
process::exit(1);
}
};
if let Err(err) = runtime.block_on(run_daemon()) {
eprintln!("redbear-sessiond: fatal error: {err}");
process::exit(1);
}
}
Err(err) => {
eprintln!("redbear-sessiond: {err}");
eprintln!("{}", usage());
process::exit(1);
}
}
}
@@ -0,0 +1,113 @@
use zbus::{
fdo,
interface,
object_server::SignalEmitter,
zvariant::OwnedObjectPath,
};
#[derive(Clone, Debug)]
pub struct LoginManager {
session_id: String,
session_path: OwnedObjectPath,
seat_id: String,
seat_path: OwnedObjectPath,
}
impl LoginManager {
pub fn new(session_path: OwnedObjectPath, seat_path: OwnedObjectPath) -> Self {
Self {
session_id: String::from("c1"),
session_path,
seat_id: String::from("seat0"),
seat_path,
}
}
}
#[interface(name = "org.freedesktop.login1.Manager")]
impl LoginManager {
fn get_session(&self, id: &str) -> fdo::Result<OwnedObjectPath> {
if id == self.session_id {
return Ok(self.session_path.clone());
}
Err(fdo::Error::Failed(format!("unknown login1 session '{id}'")))
}
fn list_sessions(&self) -> fdo::Result<Vec<(String, u32, String, String, OwnedObjectPath)>> {
Ok(vec![(
self.session_id.clone(),
0,
String::from("root"),
self.seat_id.clone(),
self.session_path.clone(),
)])
}
fn get_seat(&self, id: &str) -> fdo::Result<OwnedObjectPath> {
if id == self.seat_id {
return Ok(self.seat_path.clone());
}
Err(fdo::Error::Failed(format!("unknown login1 seat '{id}'")))
}
#[zbus(property(emits_changed_signal = "const"), name = "IdleHint")]
fn idle_hint(&self) -> bool {
false
}
#[zbus(property(emits_changed_signal = "const"), name = "IdleSinceHint")]
fn idle_since_hint(&self) -> u64 {
0
}
#[zbus(property(emits_changed_signal = "const"), name = "IdleSinceHintMonotonic")]
fn idle_since_hint_monotonic(&self) -> u64 {
0
}
#[zbus(property(emits_changed_signal = "const"), name = "BlockInhibited")]
fn block_inhibited(&self) -> String {
String::new()
}
#[zbus(property(emits_changed_signal = "const"), name = "DelayInhibited")]
fn delay_inhibited(&self) -> String {
String::new()
}
#[zbus(property(emits_changed_signal = "const"), name = "InhibitDelayMaxUSec")]
fn inhibit_delay_max_usec(&self) -> u64 {
0
}
#[zbus(property(emits_changed_signal = "const"), name = "HandleLidSwitch")]
fn handle_lid_switch(&self) -> String {
String::from("ignore")
}
#[zbus(property(emits_changed_signal = "const"), name = "HandlePowerKey")]
fn handle_power_key(&self) -> String {
String::from("poweroff")
}
#[zbus(property(emits_changed_signal = "const"), name = "PreparingForSleep")]
fn preparing_for_sleep(&self) -> bool {
false
}
#[zbus(property(emits_changed_signal = "const"), name = "PreparingForShutdown")]
fn preparing_for_shutdown(&self) -> bool {
false
}
#[zbus(signal, name = "PrepareForSleep")]
async fn prepare_for_sleep(signal_emitter: &SignalEmitter<'_>, before: bool) -> zbus::Result<()>;
#[zbus(signal, name = "PrepareForShutdown")]
async fn prepare_for_shutdown(
signal_emitter: &SignalEmitter<'_>,
before: bool,
) -> zbus::Result<()>;
}
@@ -0,0 +1,71 @@
use std::sync::Mutex;
use zbus::{fdo, interface, zvariant::OwnedObjectPath};
#[derive(Debug)]
pub struct LoginSeat {
id: String,
session_id: String,
session_path: OwnedObjectPath,
last_requested_vt: Mutex<u32>,
}
impl LoginSeat {
pub fn new(session_path: OwnedObjectPath) -> Self {
Self {
id: String::from("seat0"),
session_id: String::from("c1"),
session_path,
last_requested_vt: Mutex::new(1),
}
}
fn last_requested_vt(&self) -> fdo::Result<std::sync::MutexGuard<'_, u32>> {
self.last_requested_vt
.lock()
.map_err(|_| fdo::Error::Failed(String::from("seat VT state is poisoned")))
}
}
#[interface(name = "org.freedesktop.login1.Seat")]
impl LoginSeat {
fn switch_to(&mut self, vt: u32) -> fdo::Result<()> {
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
);
Ok(())
}
#[zbus(property(emits_changed_signal = "const"), name = "Id")]
fn id(&self) -> String {
self.id.clone()
}
#[zbus(property(emits_changed_signal = "const"), name = "ActiveSession")]
fn active_session(&self) -> (String, OwnedObjectPath) {
(self.session_id.clone(), self.session_path.clone())
}
#[zbus(property(emits_changed_signal = "const"), name = "Sessions")]
fn sessions(&self) -> Vec<(String, OwnedObjectPath)> {
vec![(self.session_id.clone(), self.session_path.clone())]
}
#[zbus(property(emits_changed_signal = "const"), name = "CanGraphical")]
fn can_graphical(&self) -> bool {
true
}
#[zbus(property(emits_changed_signal = "const"), name = "CanTTY")]
fn can_tty(&self) -> bool {
true
}
#[zbus(property(emits_changed_signal = "const"), name = "IdleHint")]
fn idle_hint(&self) -> bool {
false
}
}
@@ -0,0 +1,232 @@
use std::{
collections::HashSet,
os::fd::OwnedFd as StdOwnedFd,
process,
sync::Mutex,
};
use zbus::{
fdo,
interface,
object_server::SignalEmitter,
zvariant::{Fd, OwnedFd, OwnedObjectPath},
};
use crate::device_map::DeviceMap;
#[derive(Debug)]
pub struct LoginSession {
id: String,
seat_id: String,
seat_path: OwnedObjectPath,
user_uid: u32,
user_path: OwnedObjectPath,
leader: u32,
device_map: DeviceMap,
controlled: Mutex<bool>,
taken_devices: Mutex<HashSet<(u32, u32)>>,
}
impl LoginSession {
pub fn new(
seat_path: OwnedObjectPath,
user_path: OwnedObjectPath,
device_map: DeviceMap,
) -> Self {
Self {
id: String::from("c1"),
seat_id: String::from("seat0"),
seat_path,
user_uid: 0,
user_path,
leader: process::id(),
device_map,
controlled: Mutex::new(false),
taken_devices: Mutex::new(HashSet::new()),
}
}
fn control_state(&self) -> fdo::Result<std::sync::MutexGuard<'_, bool>> {
self.controlled
.lock()
.map_err(|_| fdo::Error::Failed(String::from("login1 control state is poisoned")))
}
fn taken_devices(&self) -> fdo::Result<std::sync::MutexGuard<'_, HashSet<(u32, u32)>>> {
self.taken_devices
.lock()
.map_err(|_| fdo::Error::Failed(String::from("login1 device state is poisoned")))
}
}
#[interface(name = "org.freedesktop.login1.Session")]
impl LoginSession {
fn activate(&self) -> fdo::Result<()> {
eprintln!("redbear-sessiond: Activate requested for session {}", self.id);
Ok(())
}
fn take_control(&self, force: bool) -> fdo::Result<()> {
let mut controlled = self.control_state()?;
*controlled = true;
eprintln!(
"redbear-sessiond: TakeControl requested for session {} (force={force})",
self.id
);
Ok(())
}
fn release_control(&self) -> fdo::Result<()> {
let mut controlled = self.control_state()?;
*controlled = false;
eprintln!("redbear-sessiond: ReleaseControl requested for session {}", self.id);
Ok(())
}
fn take_device(&self, major: u32, minor: u32) -> fdo::Result<OwnedFd> {
let file = self
.device_map
.open_device(major, minor)
.map_err(|err| fdo::Error::Failed(format!("TakeDevice({major}, {minor}) failed: {err}")))?;
let mut taken_devices = self.taken_devices()?;
taken_devices.insert((major, minor));
let owned_fd: StdOwnedFd = file.into();
eprintln!(
"redbear-sessiond: TakeDevice granted for session {} -> ({major}, {minor})",
self.id
);
Ok(OwnedFd::from(owned_fd))
}
fn release_device(&self, major: u32, minor: u32) -> fdo::Result<()> {
let mut taken_devices = self.taken_devices()?;
taken_devices.remove(&(major, minor));
eprintln!(
"redbear-sessiond: ReleaseDevice requested for session {} -> ({major}, {minor})",
self.id
);
Ok(())
}
fn pause_device_complete(&self, major: u32, minor: u32) -> fdo::Result<()> {
eprintln!(
"redbear-sessiond: PauseDeviceComplete received for session {} -> ({major}, {minor})",
self.id
);
Ok(())
}
#[zbus(property(emits_changed_signal = "const"), name = "Active")]
fn active(&self) -> bool {
true
}
#[zbus(property(emits_changed_signal = "const"), name = "Remote")]
fn remote(&self) -> bool {
false
}
#[zbus(property(emits_changed_signal = "const"), name = "Type")]
fn kind(&self) -> String {
String::from("wayland")
}
#[zbus(property(emits_changed_signal = "const"), name = "Class")]
fn class(&self) -> String {
String::from("user")
}
#[zbus(property(emits_changed_signal = "const"), name = "Service")]
fn service(&self) -> String {
String::from("redbear")
}
#[zbus(property(emits_changed_signal = "const"), name = "Desktop")]
fn desktop(&self) -> String {
String::from("KDE")
}
#[zbus(property(emits_changed_signal = "const"), name = "Display")]
fn display(&self) -> String {
String::new()
}
#[zbus(property(emits_changed_signal = "const"), name = "Id")]
fn id(&self) -> String {
self.id.clone()
}
#[zbus(property(emits_changed_signal = "const"), name = "State")]
fn state(&self) -> String {
String::from("online")
}
#[zbus(property(emits_changed_signal = "const"), name = "Seat")]
fn seat(&self) -> (String, OwnedObjectPath) {
(self.seat_id.clone(), self.seat_path.clone())
}
#[zbus(property(emits_changed_signal = "const"), name = "User")]
fn user(&self) -> (u32, OwnedObjectPath) {
(self.user_uid, self.user_path.clone())
}
#[zbus(property(emits_changed_signal = "const"), name = "VTNr")]
fn vt_nr(&self) -> u32 {
1
}
#[zbus(property(emits_changed_signal = "const"), name = "Leader")]
fn leader(&self) -> u32 {
self.leader
}
#[zbus(property(emits_changed_signal = "const"), name = "Audit")]
fn audit(&self) -> String {
String::new()
}
#[zbus(property(emits_changed_signal = "const"), name = "TTY")]
fn tty(&self) -> String {
String::new()
}
#[zbus(property(emits_changed_signal = "const"), name = "RemoteUser")]
fn remote_user(&self) -> String {
String::new()
}
#[zbus(property(emits_changed_signal = "const"), name = "RemoteHost")]
fn remote_host(&self) -> String {
String::new()
}
#[zbus(property(emits_changed_signal = "const"), name = "IdleHint")]
fn idle_hint(&self) -> bool {
false
}
#[zbus(property(emits_changed_signal = "const"), name = "LockedHint")]
fn locked_hint(&self) -> bool {
false
}
#[zbus(signal, name = "PauseDevice")]
async fn pause_device(
signal_emitter: &SignalEmitter<'_>,
major: u32,
minor: u32,
kind: String,
) -> zbus::Result<()>;
#[zbus(signal, name = "ResumeDevice")]
async fn resume_device(
signal_emitter: &SignalEmitter<'_>,
major: u32,
minor: u32,
fd: Fd<'_>,
) -> zbus::Result<()>;
}