From d07b1a4f2ff167c574f0198ba3ce05faf41c322a Mon Sep 17 00:00:00 2001 From: r0chd Date: Sun, 11 Oct 2026 18:57:00 +0200 Subject: [PATCH] sessiond: introduce tty field --- .../protocols/session_core_v1.xml | 4 ++ .../protocols/session_management_v1.xml | 2 + .../protocols/session_management_v2.xml | 7 +++ libsession/libsession-rs/src/lib.rs | 44 +++++++++++++++++-- libsession/libsession-sys/src/lib.rs | 16 ++++++- .../libsession/src/backend/consolekit.rs | 16 +++++++ libsession/libsession/src/backend/logind.rs | 9 ++++ libsession/libsession/src/backend/mod.rs | 2 + libsession/libsession/src/backend/sessiond.rs | 27 ++++++++++-- libsession/libsession/src/lib.rs | 32 +++++++++++++- .../libsession/src/sessiond_state/mod.rs | 4 +- pam/src/lib.rs | 20 ++++++--- sessionctl/src/main.rs | 25 ++++++++--- sessiond/src/session.rs | 29 ++++++++++++ sessiond/src/state.rs | 4 ++ 15 files changed, 217 insertions(+), 24 deletions(-) diff --git a/hyprwire-protocols-sessiond/protocols/session_core_v1.xml b/hyprwire-protocols-sessiond/protocols/session_core_v1.xml index df689d8..718b04f 100644 --- a/hyprwire-protocols-sessiond/protocols/session_core_v1.xml +++ b/hyprwire-protocols-sessiond/protocols/session_core_v1.xml @@ -117,6 +117,9 @@ + + + @@ -301,6 +304,7 @@ + diff --git a/hyprwire-protocols-sessiond/protocols/session_management_v1.xml b/hyprwire-protocols-sessiond/protocols/session_management_v1.xml index d0d1035..b447862 100644 --- a/hyprwire-protocols-sessiond/protocols/session_management_v1.xml +++ b/hyprwire-protocols-sessiond/protocols/session_management_v1.xml @@ -39,6 +39,7 @@ + @@ -86,5 +87,6 @@ + diff --git a/hyprwire-protocols-sessiond/protocols/session_management_v2.xml b/hyprwire-protocols-sessiond/protocols/session_management_v2.xml index b91ede3..ea104ce 100644 --- a/hyprwire-protocols-sessiond/protocols/session_management_v2.xml +++ b/hyprwire-protocols-sessiond/protocols/session_management_v2.xml @@ -132,6 +132,12 @@ + + + Optional. Must be sent while staged + + + @@ -168,5 +174,6 @@ + diff --git a/libsession/libsession-rs/src/lib.rs b/libsession/libsession-rs/src/lib.rs index 95437d5..caba9db 100644 --- a/libsession/libsession-rs/src/lib.rs +++ b/libsession/libsession-rs/src/lib.rs @@ -88,7 +88,7 @@ impl Context { Ok(session) => session, Err(error) => { unsafe { - libsession_sys::libsession_session_list_free(sessions.as_ptr(), count) + libsession_sys::libsession_session_list_free(sessions.as_ptr(), count); }; return Err(error); } @@ -169,6 +169,43 @@ impl Context { Ok(state) } + pub fn get_session_vt(&self, session: &Session) -> libsession_sys::Result { + let mut vt = 0u32; + + libsession_sys::Result::<()>::from(unsafe { + libsession_sys::libsession_session_get_vt( + self.0.as_ptr(), + session.inner.as_ptr(), + &raw mut vt, + ) + })?; + + Ok(vt) + } + + pub fn get_session_tty(&self, session: &Session) -> libsession_sys::Result { + let mut tty_ptr = ptr::null_mut(); + + libsession_sys::Result::<()>::from(unsafe { + libsession_sys::libsession_session_get_tty( + self.0.as_ptr(), + session.inner.as_ptr(), + &raw mut tty_ptr, + ) + })?; + + let tty_ptr = ptr::NonNull::new(tty_ptr).ok_or(libsession_sys::Error::InternalError)?; + + let tty_result = unsafe { ffi::CStr::from_ptr(tty_ptr.as_ptr()) } + .to_str() + .map(str::to_owned) + .map_err(|_| libsession_sys::Error::InvalidData)?; + + unsafe { libsession_sys::libsession_string_free(tty_ptr.as_ptr()) }; + + Ok(tty_result) + } + pub fn is_session_active(&self, session: &Session) -> libsession_sys::Result { let mut is_active = false; @@ -257,7 +294,7 @@ impl<'a> Session { .map(str::to_owned) .map_err(|_| libsession_sys::Error::InvalidData)?; - unsafe { libsession_sys::libsession_session_id_free(id_ptr.as_ptr()) }; + unsafe { libsession_sys::libsession_string_free(id_ptr.as_ptr()) }; Ok(Self { inner: session, @@ -265,10 +302,11 @@ impl<'a> Session { }) } - fn as_ptr(&self) -> *mut libsession_sys::LibsessionSession { + const fn as_ptr(&self) -> *mut libsession_sys::LibsessionSession { self.inner.as_ptr() } + #[must_use] pub fn id(&'a self) -> &'a str { &self.id } diff --git a/libsession/libsession-sys/src/lib.rs b/libsession/libsession-sys/src/lib.rs index 795b408..b960e03 100644 --- a/libsession/libsession-sys/src/lib.rs +++ b/libsession/libsession-sys/src/lib.rs @@ -211,6 +211,18 @@ unsafe extern "C" { ret_active: *mut bool, ) -> LibsessionStatus; + pub fn libsession_session_get_vt( + ctx: *mut LibsessionContext, + session: *const LibsessionSession, + ret_vt: *mut u32, + ) -> LibsessionStatus; + + pub fn libsession_session_get_tty( + ctx: *mut LibsessionContext, + session: *const LibsessionSession, + ret_tty: *mut *mut ffi::c_char, + ) -> LibsessionStatus; + /// Reports whether a session is local. /// /// # Safety @@ -227,7 +239,7 @@ unsafe extern "C" { /// Copies a session ID into an allocated, NUL-terminated C string. /// - /// Free the returned string with `libsession_session_id_free`. + /// Free the returned string with `libsession_string_free`. /// /// # Safety /// @@ -245,7 +257,7 @@ unsafe extern "C" { /// /// `string` must be null or a pointer returned by this library that has not /// already been freed. The pointer must not be used after this call. - pub fn libsession_session_id_free(string: *mut ffi::c_char); + pub fn libsession_string_free(string: *mut ffi::c_char); pub fn libsession_monitor_new(ctx: *const LibsessionContext) -> *mut libsession_monitor; diff --git a/libsession/libsession/src/backend/consolekit.rs b/libsession/libsession/src/backend/consolekit.rs index be2df56..9266e3e 100644 --- a/libsession/libsession/src/backend/consolekit.rs +++ b/libsession/libsession/src/backend/consolekit.rs @@ -33,6 +33,9 @@ trait ConsoleKitSession { #[zbus(name = "IsLocal")] fn is_local(&self) -> zbus::Result; + + #[zbus(name = "GetDisplayDevice")] + fn get_display_device(&self) -> zbus::Result; } pub struct ConsoleKit { @@ -60,6 +63,19 @@ impl super::Backend for ConsoleKit { crate::LibsessionBackend::ConsoleKit } + fn get_tty(&mut self, session_id: &str) -> super::Result> { + for path in self.manager.get_sessions()? { + let session = ConsoleKitSessionProxyBlocking::builder(&self.connection) + .path(path)? + .build()?; + if session.get_id()? == session_id { + return Ok(Some(session.get_display_device()?)); + } + } + + Err(io::Error::new(io::ErrorKind::NotFound, "session not found").into()) + } + fn get_vt(&mut self, session_id: &str) -> super::Result> { for path in self.manager.get_sessions()? { let session = ConsoleKitSessionProxyBlocking::builder(&self.connection) diff --git a/libsession/libsession/src/backend/logind.rs b/libsession/libsession/src/backend/logind.rs index d64c8a0..7911afc 100644 --- a/libsession/libsession/src/backend/logind.rs +++ b/libsession/libsession/src/backend/logind.rs @@ -40,6 +40,15 @@ impl super::Backend for Logind { crate::LibsessionBackend::Logind } + fn get_tty(&mut self, session_id: &str) -> super::Result> { + let session_id = ffi::CString::new(session_id).unwrap(); + let mut tty = ptr::null_mut(); + + let result = unsafe { login::sd_session_get_tty(session_id.as_ptr(), &raw mut tty) }; + + Ok((result >= 0).then(|| sd_string(tty)).flatten()) + } + fn get_vt(&mut self, session_id: &str) -> super::Result> { let session_id = ffi::CString::new(session_id) .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; diff --git a/libsession/libsession/src/backend/mod.rs b/libsession/libsession/src/backend/mod.rs index 6e12ff5..7dfd905 100644 --- a/libsession/libsession/src/backend/mod.rs +++ b/libsession/libsession/src/backend/mod.rs @@ -48,6 +48,8 @@ pub trait Backend { fn list_sessions(&mut self) -> Result>; fn get_vt(&mut self, session_id: &str) -> Result>; + + fn get_tty(&mut self, session_id: &str) -> Result>; } pub trait BackendMonitor { diff --git a/libsession/libsession/src/backend/sessiond.rs b/libsession/libsession/src/backend/sessiond.rs index 40c1bc2..6149205 100644 --- a/libsession/libsession/src/backend/sessiond.rs +++ b/libsession/libsession/src/backend/sessiond.rs @@ -1,5 +1,6 @@ use crate::logging; -use crate::sessiond_state::{self, monitor}; +use crate::sessiond_state; +use crate::sessiond_state::monitor; use std::os::fd::AsRawFd; use std::{fs, io, path, rc}; @@ -16,7 +17,7 @@ impl Sessiond { Some(Self { logger, - monitor: state.monitor().ok()?, + monitor: sessiond_state::State::monitor().ok()?, state, }) } @@ -27,9 +28,27 @@ impl super::Backend for Sessiond { crate::LibsessionBackend::Sessiond } + fn get_tty(&mut self, session_id: &str) -> super::Result> { + self.logger.log(crate::LibsessionLogLevel::Debug, || { + format!("sessiond: looking up vt for session {session_id}") + }); + + if self.monitor.has_changed()? { + self.state.try_read()?; + } + + let session = self + .state + .sessions + .get(session_id) + .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "session not found"))?; + + Ok(session.tty.as_ref().map(|tty| tty.to_string())) + } + fn get_vt(&mut self, session_id: &str) -> super::Result> { self.logger.log(crate::LibsessionLogLevel::Debug, || { - format!("sessiond: looking up vt for session ") + format!("sessiond: looking up vt for session {session_id}") }); if self.monitor.has_changed()? { @@ -190,7 +209,7 @@ impl super::Backend for Sessiond { } fn get_monitor(&self) -> Option> { - Some(Box::new(self.state.monitor().ok()?)) + Some(Box::new(sessiond_state::State::monitor().ok()?)) } } diff --git a/libsession/libsession/src/lib.rs b/libsession/libsession/src/lib.rs index 15fb2fd..d139211 100644 --- a/libsession/libsession/src/lib.rs +++ b/libsession/libsession/src/lib.rs @@ -464,6 +464,34 @@ pub unsafe extern "C" fn libsession_session_is_local( LibsessionStatus::Ok } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn libsession_session_get_tty( + ctx: *mut LibsessionContext, + session: *const LibsessionSession, + ret_tty: *mut *mut ffi::c_char, +) -> LibsessionStatus { + if ctx.is_null() || session.is_null() || ret_tty.is_null() { + return LibsessionStatus::InvalidArgument; + } + + let libsession = unsafe { &mut *ctx.cast::() }; + let session = unsafe { &*session.cast::() }; + + match libsession.backend.get_tty(&session.0) { + Ok(Some(tty)) => { + let Ok(tty) = ffi::CString::new(tty) else { + return LibsessionStatus::InvalidArgument; + }; + + unsafe { ret_tty.write(tty.into_raw()) }; + } + Ok(None) => {} + Err(error) => return LibsessionStatus::from(&error), + } + + LibsessionStatus::Ok +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn libsession_session_get_vt( ctx: *mut LibsessionContext, @@ -488,7 +516,7 @@ pub unsafe extern "C" fn libsession_session_get_vt( /// Copies a session ID into an allocated, NUL-terminated C string. /// -/// Free the returned string with `libsession_session_id_free`. +/// Free the returned string with `libsession_string_free`. /// /// # Safety /// @@ -522,7 +550,7 @@ pub unsafe extern "C" fn libsession_session_get_id( /// `string` must be null or a pointer returned by this library that has not /// already been freed. The pointer must not be used after this call. #[unsafe(no_mangle)] -pub unsafe extern "C" fn libsession_session_id_free(string: *mut ffi::c_char) { +pub unsafe extern "C" fn libsession_string_free(string: *mut ffi::c_char) { if !string.is_null() { unsafe { drop(ffi::CString::from_raw(string)) }; } diff --git a/libsession/libsession/src/sessiond_state/mod.rs b/libsession/libsession/src/sessiond_state/mod.rs index 4d6a8ee..2a11b3a 100644 --- a/libsession/libsession/src/sessiond_state/mod.rs +++ b/libsession/libsession/src/sessiond_state/mod.rs @@ -68,6 +68,7 @@ pub struct Session { pub uid: u32, pub class: SessionClass, pub vt: Option, + pub tty: Option>, pub leader: Option, pub remote_host: Option>, pub timestamp: u128, @@ -94,6 +95,7 @@ pub enum Error { } impl State { + #[must_use] pub fn path() -> path::PathBuf { path::PathBuf::from("/run/sessiond/state.toml") } @@ -106,7 +108,7 @@ impl State { /// # Errors /// Returns an error if the inotify instance or directory watch cannot be created. #[cfg(feature = "monitor")] - pub fn monitor(&self) -> io::Result { + pub fn monitor() -> io::Result { monitor::Monitor::new(Self::path()) } diff --git a/pam/src/lib.rs b/pam/src/lib.rs index 320e9bc..87cac85 100644 --- a/pam/src/lib.rs +++ b/pam/src/lib.rs @@ -75,8 +75,8 @@ impl PamServiceModule for Pam { .uid(); let envlist = pamh.getenvlist()?; - let vt = pamh - .get_tty()? + let tty = pamh.get_tty()?; + let vt: Option = tty .and_then(|tty| { tty.to_string_lossy() .rsplit('/') @@ -90,8 +90,7 @@ impl PamServiceModule for Pam { let (name, value) = entry.split_once('=')?; (name == "XDG_VTNR").then(|| value.parse().ok()).flatten() }) - }) - .unwrap_or(0); + }); let remote_host = pamh .get_rhost()? @@ -112,18 +111,27 @@ impl PamServiceModule for Pam { if name == "XDG_SESSION_CLASS" { class = match value { + "user" => session_management_v1::SessionClass::User, "greeter" => session_management_v1::SessionClass::Greeter, "lock-screen" => session_management_v1::SessionClass::LockScreen, "background" => session_management_v1::SessionClass::Background, "background-light" => session_management_v1::SessionClass::BackgroundLight, "manager" => session_management_v1::SessionClass::Manager, "manager-early" => session_management_v1::SessionClass::ManagerEarly, - _ => session_management_v1::SessionClass::User, + _ => session_management_v1::SessionClass::Unkown, }; } } - manager.send_create_session(uid, vt, is_local, remote_host, class, &env); + manager.send_create_session( + uid, + vt.unwrap_or_default(), + tty.unwrap_or_default().to_string_lossy(), + is_local, + remote_host, + class, + &env, + ); while !session.done { event_queue diff --git a/sessionctl/src/main.rs b/sessionctl/src/main.rs index 45afe55..b1d9aab 100644 --- a/sessionctl/src/main.rs +++ b/sessionctl/src/main.rs @@ -23,6 +23,7 @@ struct Session { username: String, class: session_core_v1::SessionClass, vt: u32, + tty: String, leader: u32, timestamp: u32, state: session_core_v1::SessionState, @@ -107,6 +108,7 @@ impl State { username: String::new(), class: session_core_v1::SessionClass::User, vt: 0, + tty: String::new(), leader: 0, timestamp: 0, state: session_core_v1::SessionState::Online, @@ -207,6 +209,7 @@ impl hyprwire::Dispatch for State { } session_core_session::Event::Class { class } => s.class = class, session_core_session::Event::Vt { vt } => s.vt = vt, + session_core_session::Event::Tty { tty } => s.tty = tty, session_core_session::Event::Leader { leader } => s.leader = leader, session_core_session::Event::Timestamp { timestamp } => s.timestamp = timestamp, session_core_session::Event::State { state } => s.state = state, @@ -754,7 +757,13 @@ fn main() -> anyhow::Result<()> { "username": session.username, "class": class_name(session.class), "vt": session.vt, - "tty": session.vt.checked_sub(1).map(|_| format!("tty{}", session.vt)), + "tty": if !session.tty.is_empty() { + Some(session.tty.clone()) + } else if session.vt > 0 { + Some(format!("tty{}", session.vt)) + } else { + None + }, "leader": session.leader, "timestamp": session.timestamp, "state": session_state_name(session.state), @@ -775,16 +784,20 @@ fn main() -> anyhow::Result<()> { let mut table = new_table(); table.set_header(["ID", "UID", "USER", "CLASS", "TTY", "STATE", "SINCE"]); for s in state.sessions { + let tty = if !s.tty.is_empty() { + s.tty.clone() + } else if s.vt > 0 { + format!("tty{}", s.vt) + } else { + "-".to_owned() + }; + let mut row = vec![ s.id.to_string(), s.uid.to_string(), s.username, class_name(s.class).to_owned(), - if s.vt == 0 { - "-".to_owned() - } else { - format!("tty{}", s.vt) - }, + tty, ]; row.extend([ session_state_name(s.state).to_owned(), diff --git a/sessiond/src/session.rs b/sessiond/src/session.rs index 73537ed..9239960 100644 --- a/sessiond/src/session.rs +++ b/sessiond/src/session.rs @@ -96,6 +96,7 @@ pub struct PendingSession { pub uid: u32, pub class: Option, pub vt: Option, + pub tty: Option>, pub remote_host: Option>, pub env: BTreeMap, sync::Arc>, pub cgroup: Option, @@ -179,6 +180,9 @@ pub struct Uid(pub u32); #[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] pub struct Vt(pub num::NonZeroU32); +#[derive(Component, Debug, Clone, PartialEq, Eq)] +pub struct Tty(pub sync::Arc); + #[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] pub struct Timestamp(pub time::SystemTime); @@ -268,6 +272,7 @@ pub fn spawn( class: SessionClass, uid: u32, vt: Option, + tty: Option>, leader: Option, remote_host: Option>, session_state: session_core_v1::SessionState, @@ -293,6 +298,9 @@ pub fn spawn( if let Some(vt) = vt { cmd.insert(Vt(vt)); } + if let Some(tty) = tty { + cmd.insert(Tty(tty)); + } if let Some(leader) = leader { cmd.insert(Leader(leader)); } @@ -706,6 +714,9 @@ pub enum SessionManagementAction { Vt { vt: u32, }, + Tty { + tty: sync::Arc, + }, RemoteHost { remote_host: sync::Arc, }, @@ -845,6 +856,7 @@ pub fn handle_session_management_action( uid: *uid, class: None, vt: None, + tty: None, remote_host: None, env, cgroup: None, @@ -868,6 +880,13 @@ pub fn handle_session_management_action( continue; } } + SessionManagementAction::Tty { tty } => { + if let Some(pending_session) = pending_sessions.by_management_object.get_mut(object) + { + pending_session.tty = Some(sync::Arc::clone(tty)); + continue; + } + } SessionManagementAction::RemoteHost { remote_host } => { if let Some(pending_session) = pending_sessions.by_management_object.get_mut(object) { @@ -1058,6 +1077,7 @@ pub fn handle_session_management_action( pending_session.class.unwrap_or_default(), pending_session.uid, pending_session.vt, + pending_session.tty, Some(leader), pending_session.remote_host, session_state, @@ -1099,6 +1119,7 @@ pub fn handle_session_management_action( pending_session.class.unwrap_or_default(), pending_session.uid, pending_session.vt, + pending_session.tty, None, pending_session.remote_host, session_state, @@ -1153,6 +1174,7 @@ impl hyprwire::Dispatch for crate::Sessiond session_manager_v1::Event::CreateSession { uid, vt, + tty, is_local, remote_host, class, @@ -1171,6 +1193,10 @@ impl hyprwire::Dispatch for crate::Sessiond action: SessionManagementAction::Vt { vt }, object: Some(target.clone()), }); + self.world.write_message(SessionManagementMessage { + action: SessionManagementAction::Tty { tty: tty.into() }, + object: Some(target.clone()), + }); if is_local == 0 { self.world.write_message(SessionManagementMessage { action: SessionManagementAction::RemoteHost { @@ -1248,6 +1274,9 @@ impl hyprwire::Dispatch fo ) { let action = match event { session_management_session::Event::Vt { vt } => SessionManagementAction::Vt { vt }, + session_management_session::Event::Tty { tty } => { + SessionManagementAction::Tty { tty: tty.into() } + } session_management_session::Event::RemoteHost { remote_host } => { SessionManagementAction::RemoteHost { remote_host: remote_host.into(), diff --git a/sessiond/src/state.rs b/sessiond/src/state.rs index e28fe8b..3957767 100644 --- a/sessiond/src/state.rs +++ b/sessiond/src/state.rs @@ -50,6 +50,7 @@ pub fn write_state( &session::Uid, &session::SessionClass, Option<&session::Vt>, + Option<&session::Tty>, Option<&session::Leader>, Option<&session::RemoteHost>, &session::Timestamp, @@ -73,6 +74,7 @@ pub fn write_state( uid, class, vt, + tty, leader, remote_host, timestamp, @@ -109,6 +111,7 @@ pub fn write_state( uid: uid.0, class, vt: vt.map(|vt| vt.0), + tty: tty.map(|tty| sync::Arc::clone(&tty.0)), leader: leader.map(|leader| leader.0), remote_host: remote_host.map(|host| sync::Arc::clone(&host.0)), timestamp, @@ -234,6 +237,7 @@ pub fn load_state( saved_session.class(), saved_session.uid, saved_session.vt, + saved_session.tty.as_ref().map(sync::Arc::clone), saved_session.leader, saved_session.remote_host.as_ref().map(sync::Arc::clone), session_state, -- 2.54.0