//! Session management for the display manager. //! //! This module defines the `SessionManager` struct, which is responsible for //! managing user sessions. It includes functionality //! for loading configuration, starting and stopping sessions, and //! handling session-related events. use crate::configuration::Configuration; use crate::{APPLICATION_NAME, VERSION}; use gio::{self, DBusConnection, OwnerId, RegistrationId}; use glib::{g_critical, g_debug, g_info, g_warning}; use lazy_static::*; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::Duration; static G_DBUS_INTROSPECTION: &str = include_str!("../resources/introspection.xml"); const G_DBUS_SERVER_NAME: &str = "dev.iohub.diya.SessionManager"; const G_DBUS_SERVER_PATH: &str = "/dev/iohub/diya/SessionManager"; const G_DBUS_SERVER_ERROR_NAME: &str = "dev.iohub.diya.SessionManager.Error"; const G_DBUS_METHOD_LOGIN: &str = "login"; const G_PROCESSES_MON_TO: u64 = 500; // Interval for monitoring child processes in milliseconds lazy_static! { /// A global instance of the `SessionManager` wrapped in a `Mutex` for thread safety. /// This allows the session manager to be accessed and modified from different parts of the application /// while ensuring that only one instance is active at a time. static ref G_SESSION_MANAGER: Arc> = Arc::new(Mutex::::new(SessionManagerState::End)); } /// SessionManagerState /// /// An enum representing the state of the session manager. It can be in a running state with an active `SessionManager`, /// or it can be in an end or reload state. pub enum SessionManagerState { /// The session manager is currently running and managing user sessions. Running(SessionManager), /// The session manager has been stopped and is no longer managing sessions. End, /// The session manager is in the process of reloading its configuration or restarting. Reload, } /// SessionManager /// /// The `SessionManager` struct is responsible for managing user sessions in the display manager. pub struct SessionManager { /// the configuration for the session manager config: Configuration, /// The main loop for handling session events. main_loop: glib::MainLoop, /// The D-Bus owner ID for the session manager's D-Bus name. bus_owner_id: Option, /// A list of signal handler IDs for any signals the session manager has connected to. signals: Vec, /// The D-Bus connection and registration ID for the session manager's D-Bus object. bus_registration: Option<(DBusConnection, RegistrationId)>, } /// Public interface for SessionManager /// pub trait SessionManagerInterface { /// Run the session manager with an input config file /// /// # Arguments /// /// - `&self` - Object that implements the Trait /// - `config` (`&PathBuf`) - Config file. /// - `is_dbus_session` (`bool`) - run the session manager using session DBUS or system DBUS /// /// # Returns /// /// - `bool` - if True service shall be restarted after finished /// fn run(&self, config: &PathBuf, is_dbus_session: bool) -> bool; /// Quit the current session manager service /// /// # Arguments /// /// - `&self` - Object that implements the Trait /// - `reload` (`bool`) - if true the service shall be restarted after finished /// fn quit(&self, reload: bool); } /// Trait that defines callbacks for SessionManager /// trait SessionManagerCallback { /// Handler for DBUS name acquired event /// /// # Arguments /// /// - `&self` - Object that implements the Trait. /// - `connection` (`DBusConnection`) - DBUS connection. /// - `name` (`&str`) - DBUS name. /// fn on_name_acquired(&self, connection: DBusConnection, name: &str); /// Handler for DBUS bus acquired event /// /// # Arguments /// /// - `&self` - Object that implements this Trait /// - `connection` (`DBusConnection`) - DBUS connection /// - `name` (`&str`) - DBUS name /// fn on_bus_acquired(&self, connection: DBusConnection, name: &str); /// Hander for DBUS name lost event /// /// # Arguments /// /// - `&self` - Object that implements this Trait /// - `connection` (`Option`) - DBUS connection. /// - `name` (`&str`) - DBUS name. /// fn on_name_lost(&self, connection: Option, name: &str); /// Handler for DBUS method call event /// /// # Arguments /// /// - `&self` (`undefined`) - Object that implements this Trait. /// - `connection` (`DBusConnection`) - DBUS connection. /// - `sender` (`Option<&str>`) - Sender name. /// - `object_path` (`&str`) - DBUS Object path. /// - `interface_name` (`Option<&str>`) - DBUS interface name. /// - `method_name` (`&str`) - DBUS method name. /// - `parameters` (`gio::glib::Variant`) - DBUS method parameters. /// - `invocation` (`gio::DBusMethodInvocation`) - DBUS method invocation object. /// fn on_method_call( &self, connection: DBusConnection, sender: Option<&str>, object_path: &str, interface_name: Option<&str>, method_name: &str, parameters: gio::glib::Variant, invocation: gio::DBusMethodInvocation, ); /// Hander for service processes monitor /// /// # Arguments /// /// - `&self` - Object that implements this Trait /// fn on_processes_monitor(&self); } impl SessionManagerInterface for Arc> { fn run(&self, config: &PathBuf, is_dbus_session: bool) -> bool { self.quit(false); *self.lock().as_deref_mut().unwrap() = SessionManagerState::Running(SessionManager { config: Configuration::from(&config).expect("Failed to load configuration"), main_loop: glib::MainLoop::new(None, false), bus_owner_id: None, signals: Vec::new(), bus_registration: None, }); let evtloop = match self.lock().as_deref_mut().unwrap() { SessionManagerState::Running(session) => { session.setup(is_dbus_session); session.main_loop.clone() } _ => panic!("No SessionManager created"), }; evtloop.run(); match self.lock().as_deref().unwrap() { SessionManagerState::Reload => true, _ => false, } } fn quit(&self, reload: bool) { if let SessionManagerState::Running(session) = self.lock().as_deref().unwrap() { session.main_loop.quit(); } *self.lock().as_deref_mut().unwrap() = if reload { SessionManagerState::Reload } else { SessionManagerState::End }; } } impl SessionManagerCallback for Arc> { fn on_name_acquired(&self, _connection: DBusConnection, name: &str) { g_debug!(APPLICATION_NAME, "D-Bus name acquired: {}", name); } fn on_bus_acquired(&self, connection: DBusConnection, name: &str) { g_debug!(APPLICATION_NAME, "D-BUS acquired for name {}", name); let introspection = match gio::DBusNodeInfo::for_xml(G_DBUS_INTROSPECTION) { Ok(info) => info, Err(err) => { g_critical!( APPLICATION_NAME, "Failed to parse D-Bus introspection data: {}", err ); self.quit(false); return; } }; let interface = &introspection.interfaces()[0]; let registration_id = match connection .register_object(G_DBUS_SERVER_PATH, interface) .method_call( |connection: DBusConnection, sender: Option<&str>, object_path: &str, interface_name: Option<&str>, method_name: &str, parameters: gio::glib::Variant, invocation: gio::DBusMethodInvocation| { SessionManager::instance().on_method_call( connection, sender, object_path, interface_name, method_name, parameters, invocation, ); }, ) .build() { Err(error) => { g_critical!( APPLICATION_NAME, "Failed to register D-Bus object at path {}: {}", G_DBUS_SERVER_PATH, error ); self.quit(false); return; } Ok(resource) => resource, }; if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() { session.bus_registration = Some((connection, registration_id)); } } fn on_name_lost(&self, _connection: Option, name: &str) { g_critical!(APPLICATION_NAME, "D-Bus name lost: {}", name); if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() { session.bus_owner_id = None; } self.quit(false); } fn on_method_call( &self, _connection: DBusConnection, _sender: Option<&str>, _object_path: &str, interface_name: Option<&str>, method_name: &str, parameters: gio::glib::Variant, invocation: gio::DBusMethodInvocation, ) { if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() { if !matches!(interface_name, Some(G_DBUS_SERVER_NAME)) { let msg = format!( "Interface name mismatch: got {}, but expected {}", interface_name.unwrap(), G_DBUS_SERVER_NAME ); g_warning!(APPLICATION_NAME, "{}", msg); invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg); return; } g_debug!( APPLICATION_NAME, "Received method call: {}, interface: {}", method_name, interface_name.unwrap() ); match method_name { G_DBUS_METHOD_LOGIN => session.login(¶meters, invocation), _ => { let msg = format!("Unsupported method {}", method_name); g_warning!(APPLICATION_NAME, "{}", msg); invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg); } } } } fn on_processes_monitor(&self) { if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() { session.monitor(); } } } impl SessionManager { /// Get a threadsafe singleton instance of the SessionManager /// /// # Returns /// /// - `Arc>` - Smart pointer to SessionManager /// /// # Examples /// /// ``` /// use dysm_rs::SessionManager; /// /// let instance = SessionManager::instance(); /// ``` pub fn instance() -> Arc> { G_SESSION_MANAGER.clone() } /// Setup the session manager before running /// /// # Arguments /// /// - `&mut self` (`&mut SessionManager`) - Mutable reference to SessionManager. /// - `is_dbus_session` (`bool`) - if true setup service using DBUS session, otherwise SBUS system /// fn setup(&mut self, is_dbus_session: bool) { g_info!( APPLICATION_NAME, "Starting SessionManager version {}", VERSION ); g_debug!(APPLICATION_NAME, "{}", self.config); g_debug!(APPLICATION_NAME, "Use session DBUS: {}", is_dbus_session); self.bus_owner_id = Some(gio::bus_own_name( if is_dbus_session { gio::BusType::Session } else { gio::BusType::System }, G_DBUS_SERVER_NAME, gio::BusNameOwnerFlags::NONE, move |connection, name| { SessionManager::instance().on_bus_acquired(connection, name); }, |connection, name| { SessionManager::instance().on_name_acquired(connection, name); }, move |connection, name| { SessionManager::instance().on_name_lost(connection, name); }, )); for signal in vec![libc::SIGINT, libc::SIGTERM, libc::SIGHUP] { let signal_id = match signal { libc::SIGHUP => glib::unix_signal_add_local(signal, move || { g_warning!(APPLICATION_NAME, "Received SIGHUP, reloading service..."); SessionManager::instance().quit(true); glib::ControlFlow::Continue }), _ => glib::unix_signal_add_local(signal, move || { g_info!( APPLICATION_NAME, "Received signal {}, shutting down...", signal ); SessionManager::instance().quit(false); glib::ControlFlow::Continue }), }; self.signals.push(signal_id); } let processes_monitor_id = glib::timeout_add_local(Duration::from_millis(G_PROCESSES_MON_TO), move || { // Monitor child processes here // For example, you could check if any child processes have exited and handle that accordingly // g_debug!(APPLICATION_NAME, "Monitoring child processes..."); SessionManager::instance().on_processes_monitor(); glib::ControlFlow::Continue }); self.signals.push(processes_monitor_id); // Implementation for running the session manager } /// Process DBUS login method cal /// /// # Arguments /// /// - `&self` (`&mut SessionManager`) - Mutable reference to current SessionManager. /// - `parameters` (`&gio::glib::Variant`) - DBUS method parameters. /// - `invocation` (`gio::DBusMethodInvocation`) - DBUS method invocation object. /// fn login(&self, parameters: &gio::glib::Variant, invocation: gio::DBusMethodInvocation) { g_debug!(APPLICATION_NAME, "Logging called"); invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, "Unimplemented"); } /// Monitor session processes /// /// # Arguments /// /// - `&self` (`&mut SessionManager`) - Mutable reference to current SessionManager. /// fn monitor(&self) { // Here you would implement the logic to monitor child processes. // For example, you could check if any child processes have exited and handle that accordingly. g_debug!(APPLICATION_NAME, "Monitoring child processes..."); } } impl Drop for SessionManager { fn drop(&mut self) { g_info!(APPLICATION_NAME, "Shutting down SessionManager"); if let Some(bus_owner_id) = self.bus_owner_id.take() { g_debug!(APPLICATION_NAME, "Releasing acquired D-Bus name"); gio::bus_unown_name(bus_owner_id); } if let Some((connection, registration_id)) = self.bus_registration.take() { g_debug!(APPLICATION_NAME, "Unregistering D-Bus object"); connection .unregister_object(registration_id) .unwrap_or_else(|e| { g_critical!( APPLICATION_NAME, "Unable to unregister object from D-Bus connection: {}", e ) }); } g_debug!(APPLICATION_NAME, "Removing signal handlers"); for signal_id in self.signals.drain(..) { signal_id.remove(); } } }