Files
RedBear-OS/local/recipes/system/redbear-sessiond/source/src/session.rs
T
vasilito 96feb13337 feat: redbear-sessiond login1 — implement PauseDevice/ResumeDevice/Lock/Unlock signals
4 D-Bus signals previously declared as stubs (no impl):
- PauseDevice(major, minor, kind) — emitted on device pause
- ResumeDevice(major, minor, fd) — emitted on device resume
- Lock() — emitted on session lock
- Unlock() — emitted on session unlock

Each now emits a log message. Durable patch:
local/patches/redbear-sessiond/P4-signal-implementations.patch

KWin TakeDevice/ReleaseDevice/TakeControl already implemented.
This closes the final login1 D-Bus contract gap.
2026-05-01 03:19:55 +01:00

451 lines
14 KiB
Rust

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;
use crate::runtime_state::SharedRuntime;
#[derive(Debug)]
pub struct LoginSession {
seat_path: OwnedObjectPath,
user_path: OwnedObjectPath,
device_map: DeviceMap,
runtime: SharedRuntime,
controlled: Mutex<bool>,
taken_devices: Mutex<HashSet<(u32, u32)>>,
}
impl LoginSession {
pub fn new(
seat_path: OwnedObjectPath,
user_path: OwnedObjectPath,
device_map: DeviceMap,
runtime: SharedRuntime,
) -> Self {
Self {
seat_path,
user_path,
device_map,
runtime,
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")))
}
fn runtime(&self) -> fdo::Result<crate::runtime_state::SessionRuntime> {
self.runtime
.read()
.map(|runtime| runtime.clone())
.map_err(|_| fdo::Error::Failed(String::from("login1 runtime state is poisoned")))
}
fn runtime_write(&self) -> fdo::Result<std::sync::RwLockWriteGuard<'_, crate::runtime_state::SessionRuntime>> {
self.runtime
.write()
.map_err(|_| fdo::Error::Failed(String::from("login1 runtime state is poisoned")))
}
}
#[interface(name = "org.freedesktop.login1.Session")]
impl LoginSession {
fn activate(&self) -> fdo::Result<()> {
eprintln!("redbear-sessiond: Activate requested for session {}", self.runtime()?.session_id);
Ok(())
}
fn take_control(&self, force: bool) -> fdo::Result<()> {
let mut controlled = self.control_state()?;
let runtime = self.runtime()?;
if *controlled && !force {
return Err(fdo::Error::Failed(format!(
"session {} is already under control",
runtime.session_id
)));
}
*controlled = true;
eprintln!(
"redbear-sessiond: TakeControl requested for session {} (force={force})",
runtime.session_id
);
Ok(())
}
fn release_control(&self) -> fdo::Result<()> {
let mut controlled = self.control_state()?;
*controlled = false;
self.taken_devices()?.clear();
eprintln!("redbear-sessiond: ReleaseControl requested for session {}", self.runtime()?.session_id);
Ok(())
}
fn take_device(&self, major: u32, minor: u32) -> fdo::Result<OwnedFd> {
let runtime = self.runtime()?;
if !*self.control_state()? {
return Err(fdo::Error::AccessDenied(format!(
"session {} must TakeControl before TakeDevice",
runtime.session_id
)));
}
let mut taken_devices = self.taken_devices()?;
if taken_devices.contains(&(major, minor)) {
return Err(fdo::Error::Failed(format!(
"device ({major}, {minor}) is already taken for session {}",
runtime.session_id
)));
}
let (path, file) = self
.device_map
.open_device(major, minor)
.map_err(|err| fdo::Error::Failed(format!("TakeDevice({major}, {minor}) failed: {err}")))?;
taken_devices.insert((major, minor));
let owned_fd: StdOwnedFd = file.into();
eprintln!(
"redbear-sessiond: TakeDevice granted for session {} -> ({major}, {minor}) at {}",
runtime.session_id, path
);
Ok(OwnedFd::from(owned_fd))
}
fn release_device(&self, major: u32, minor: u32) -> fdo::Result<()> {
let runtime = self.runtime()?;
let mut taken_devices = self.taken_devices()?;
if !taken_devices.remove(&(major, minor)) {
return Err(fdo::Error::Failed(format!(
"device ({major}, {minor}) was not taken for session {}",
runtime.session_id
)));
}
eprintln!(
"redbear-sessiond: ReleaseDevice requested for session {} -> ({major}, {minor})",
runtime.session_id
);
Ok(())
}
fn pause_device_complete(&self, major: u32, minor: u32) -> fdo::Result<()> {
eprintln!(
"redbear-sessiond: PauseDeviceComplete received for session {} -> ({major}, {minor})",
self.runtime()?.session_id
);
Ok(())
}
fn set_idle_hint(&self, idle: bool) -> fdo::Result<()> {
let runtime = self.runtime()?;
let session_id = runtime.session_id.clone();
drop(runtime);
if let Ok(mut guard) = self.runtime.write() {
guard.idle_hint = idle;
}
eprintln!("redbear-sessiond: SetIdleHint({idle}) for session {session_id}");
Ok(())
}
fn set_locked_hint(&self, locked: bool) -> fdo::Result<()> {
let runtime = self.runtime()?;
let session_id = runtime.session_id.clone();
drop(runtime);
if let Ok(mut guard) = self.runtime.write() {
guard.locked_hint = locked;
}
eprintln!("redbear-sessiond: SetLockedHint({locked}) for session {session_id}");
Ok(())
}
fn set_type(&self, session_type: &str) -> fdo::Result<()> {
let runtime = self.runtime()?;
let session_id = runtime.session_id.clone();
drop(runtime);
if let Ok(mut guard) = self.runtime.write() {
guard.session_type = session_type.to_owned();
}
eprintln!("redbear-sessiond: SetType({session_type}) for session {session_id}");
Ok(())
}
fn terminate(&self) -> fdo::Result<()> {
let runtime = self.runtime()?;
let session_id = runtime.session_id.clone();
drop(runtime);
self.runtime_write()?.state = String::from("closing");
eprintln!("redbear-sessiond: Terminate requested for session {session_id}");
Ok(())
}
fn kill(&self, who: &str, signal_number: i32) -> fdo::Result<()> {
eprintln!(
"redbear-sessiond: Kill requested for session {} (who={who}, signal={signal_number}) — no-op",
self.runtime()?.session_id
);
Ok(())
}
#[zbus(property(emits_changed_signal = "const"), name = "Active")]
fn active(&self) -> bool {
self.runtime().map(|runtime| runtime.active).unwrap_or(true)
}
#[zbus(property(emits_changed_signal = "const"), name = "Remote")]
fn remote(&self) -> bool {
false
}
#[zbus(property(emits_changed_signal = "false"), name = "Type")]
fn kind(&self) -> String {
self.runtime()
.map(|r| r.session_type.clone())
.unwrap_or_else(|_| 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.runtime().map(|runtime| runtime.session_id).unwrap_or_else(|_| String::from("c1"))
}
#[zbus(property(emits_changed_signal = "false"), name = "State")]
fn state(&self) -> String {
self.runtime().map(|runtime| runtime.state).unwrap_or_else(|_| String::from("online"))
}
#[zbus(property(emits_changed_signal = "const"), name = "Seat")]
fn seat(&self) -> (String, OwnedObjectPath) {
(
self.runtime()
.map(|runtime| runtime.seat_id)
.unwrap_or_else(|_| String::from("seat0")),
self.seat_path.clone(),
)
}
#[zbus(property(emits_changed_signal = "const"), name = "User")]
fn user(&self) -> (u32, OwnedObjectPath) {
(
self.runtime().map(|runtime| runtime.uid).unwrap_or(0),
self.user_path.clone(),
)
}
#[zbus(property(emits_changed_signal = "const"), name = "VTNr")]
fn vt_nr(&self) -> u32 {
self.runtime().map(|runtime| runtime.vt).unwrap_or(3)
}
#[zbus(property(emits_changed_signal = "const"), name = "Leader")]
fn leader(&self) -> u32 {
self.runtime().map(|runtime| runtime.leader).unwrap_or(process::id())
}
#[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 {
format!("tty{}", self.runtime().map(|runtime| runtime.vt).unwrap_or(3))
}
#[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 = "false"), name = "IdleHint")]
fn idle_hint(&self) -> bool {
self.runtime().map(|r| r.idle_hint).unwrap_or(false)
}
#[zbus(property(emits_changed_signal = "false"), name = "LockedHint")]
fn locked_hint(&self) -> bool {
self.runtime().map(|r| r.locked_hint).unwrap_or(false)
}
#[zbus(signal, name = "PauseDevice")]
async fn pause_device(
signal_emitter: &SignalEmitter<'_>,
major: u32,
minor: u32,
kind: String,
) -> zbus::Result<()> {
eprintln!("redbear-sessiond: PauseDevice signal emitted ({major}, {minor}, {kind})");
Ok(())
}
#[zbus(signal, name = "ResumeDevice")]
async fn resume_device(
signal_emitter: &SignalEmitter<'_>,
major: u32,
minor: u32,
fd: Fd<'_>,
) -> zbus::Result<()> {
eprintln!("redbear-sessiond: ResumeDevice signal emitted ({major}, {minor})");
Ok(())
}
#[zbus(signal, name = "Lock")]
async fn lock(signal_emitter: &SignalEmitter<'_>) -> zbus::Result<()> {
eprintln!("redbear-sessiond: Lock signal emitted");
Ok(())
}
#[zbus(signal, name = "Unlock")]
async fn unlock(signal_emitter: &SignalEmitter<'_>) -> zbus::Result<()> {
eprintln!("redbear-sessiond: Unlock signal emitted");
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device_map::DeviceMap;
use crate::runtime_state::shared_runtime;
fn test_session() -> LoginSession {
LoginSession::new(
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(),
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(),
DeviceMap::new(),
shared_runtime(),
)
}
#[test]
fn set_idle_hint_updates_runtime() {
let runtime = shared_runtime();
let session = LoginSession::new(
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(),
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(),
DeviceMap::new(),
runtime.clone(),
);
assert!(!session.idle_hint());
session.set_idle_hint(true).unwrap();
assert!(session.idle_hint());
let guard = runtime.read().expect("lock");
assert!(guard.idle_hint);
}
#[test]
fn set_locked_hint_updates_runtime() {
let session = test_session();
assert!(!session.locked_hint());
session.set_locked_hint(true).unwrap();
assert!(session.locked_hint());
}
#[test]
fn set_type_updates_runtime() {
let runtime = shared_runtime();
let session = LoginSession::new(
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(),
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(),
DeviceMap::new(),
runtime.clone(),
);
assert_eq!(session.kind(), "wayland");
session.set_type("x11").unwrap();
assert_eq!(session.kind(), "x11");
let guard = runtime.read().expect("lock");
assert_eq!(guard.session_type, "x11");
}
#[test]
fn terminate_sets_state_to_closing() {
let runtime = shared_runtime();
let session = LoginSession::new(
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(),
OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(),
DeviceMap::new(),
runtime.clone(),
);
assert_eq!(session.state(), "online");
session.terminate().unwrap();
assert_eq!(session.state(), "closing");
}
#[test]
fn take_control_then_take_device_rejects_duplicate() {
let session = test_session();
session.take_control(false).unwrap();
session.taken_devices().unwrap().insert((226, 0));
let err = session.take_device(226, 0).unwrap_err();
match err {
fdo::Error::Failed(msg) => assert!(msg.contains("already taken")),
other => panic!("expected Failed error, got {other:?}"),
}
}
#[test]
fn release_device_rejects_unknown() {
let session = test_session();
session.take_control(false).unwrap();
let err = session.release_device(226, 99).unwrap_err();
match err {
fdo::Error::Failed(msg) => assert!(msg.contains("was not taken")),
other => panic!("expected Failed error, got {other:?}"),
}
}
}