modular session management daemon
Something went wrong. Try again.
Rust
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084use crate::session_core_v1::{session_core_manager, session_core_session};use crate::session_management_v1::session_manager as session_manager_v1;use crate::session_management_v2::{session_management_session, session_manager};use crate::{ config, ecs, seat, session_core_v1, session_management_v1, session_management_v2, user,};use bevy_ecs::prelude::*;use bevy_ecs::{entity, world};use hyprwire::server;use std::collections::BTreeMap;use std::{num, process, time};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub enum PowerAction { Reboot, Poweroff, Suspend, Hibernate,}
impl PowerAction { const fn name(self) -> &'static str { match self { Self::Reboot => "reboot", Self::Poweroff => "poweroff", Self::Suspend => "suspend", Self::Hibernate => "hibernate", } }
const fn display_name(self) -> &'static str { match self { Self::Reboot => "reboot", Self::Poweroff => "power off", Self::Suspend => "suspend", Self::Hibernate => "hibernate", } }}
#[derive(Debug, PartialEq, Eq)]pub enum PowerError { ActiveSessions(String), Io(String),}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]pub struct SessionId(u32);
impl SessionId { pub const fn as_raw(self) -> u32 { self.0 }
pub const fn from_raw(id: u32) -> Self { Self(id) }
pub const fn next(self) -> Self { Self(self.0.wrapping_add(1)) }}
#[allow(clippy::too_many_arguments)]pub fn spawn( world: &mut world::World, session_id: SessionId, class: ecs::SessionClass, uid: u32, username: String, vt: Option<u32>, seat: Option<String>, session_seat: Option<entity::Entity>, leader_id: i32, remote_host: Option<String>, active_state: session_core_v1::ActiveState, session_object: Option<session_management_session::SessionManagementSession>,) -> entity::Entity { let mut cmd = world.spawn(ecs::SessionBundle { marker: ecs::Session, id: ecs::SessionId(session_id), class, uid: ecs::Uid(uid), username: ecs::Username(username), timestamp: ecs::Timestamp(time::SystemTime::now()), leader_id: ecs::LeaderId(leader_id), env: ecs::SessionEnv(BTreeMap::new()), active_state: ecs::ActiveState(active_state), }); if let Some(vt) = vt.and_then(num::NonZeroU32::new) { cmd.insert(ecs::Vt(vt)); } if let Some(remote_host) = remote_host { cmd.insert(ecs::RemoteHost(remote_host)); } else { cmd.insert(ecs::IsLocal); } if let Some(name) = seat { cmd.insert(ecs::SeatName(name)); } if let Some(entity) = session_seat { cmd.insert(ecs::SessionSeat(entity)); } if let Some(session_object) = session_object { cmd.insert(ecs::SessionObject(session_object)); } cmd.id()}
pub fn entities(world: &world::World) -> Vec<entity::Entity> { let Some(mut sessions) = world.try_query_filtered::<Entity, With<ecs::Session>>() else { return Vec::new(); }; sessions.iter(world).collect()}
pub fn id(world: &world::World, entity: entity::Entity) -> Option<SessionId> { world.get::<ecs::SessionId>(entity).map(|session| session.0)}
pub fn entity(world: &world::World, session_id: SessionId) -> Option<entity::Entity> { let mut sessions = world.try_query_filtered::<(Entity, &ecs::SessionId), With<ecs::Session>>()?; sessions .iter(world) .find_map(|(entity, id)| (id.0 == session_id).then_some(entity))}
pub fn entity_by_uid(world: &world::World, uid: u32) -> Option<entity::Entity> { let mut sessions = world.try_query_filtered::<(Entity, &ecs::Uid), With<ecs::Session>>()?; sessions .iter(world) .find_map(|(entity, session_uid)| (session_uid.0 == uid).then_some(entity))}
pub fn entities_by_uid(world: &world::World, uid: u32) -> Vec<entity::Entity> { let Some(mut sessions) = world.try_query_filtered::<(Entity, &ecs::Uid), With<ecs::Session>>() else { return Vec::new(); }; sessions .iter(world) .filter_map(|(entity, session_uid)| (session_uid.0 == uid).then_some(entity)) .collect()}
pub fn entities_by_seat_name(world: &world::World, name: &str) -> Vec<entity::Entity> { let Some(mut sessions) = world.try_query_filtered::<(Entity, &ecs::SeatName), With<ecs::Session>>() else { return Vec::new(); }; sessions .iter(world) .filter_map(|(entity, session_seat)| (session_seat.0 == name).then_some(entity)) .collect()}
pub fn entity_by_object( world: &world::World, object: &session_core_session::SessionCoreSession,) -> Option<entity::Entity> { world .resource::<ecs::ProtocolObjects>() .session_entity_by_object(object)}
pub fn entity_by_management_object( world: &world::World, object: &session_management_session::SessionManagementSession,) -> Option<entity::Entity> { let mut sessions = world.try_query_filtered::<(Entity, &ecs::SessionObject), With<ecs::Session>>()?; sessions .iter(world) .find_map(|(entity, session_object)| (session_object.0 == *object).then_some(entity))}
pub fn can_modify(world: &world::World, entity: entity::Entity, creds: &hyprwire::Creds) -> bool { let session_uid = world.get::<ecs::Uid>(entity).map_or(0, |uid| uid.0); creds.uid.is_root() || creds.uid.as_raw() == session_uid}
pub fn session_envs_by_uid( world: &world::World, uid: u32, exclude: Option<entity::Entity>,) -> BTreeMap<String, String> { let mut env = BTreeMap::new(); for entity in entities_by_uid(world, uid) { if Some(entity) == exclude { continue; } if let Some(session_env) = world.get::<ecs::SessionEnv>(entity) { env.extend(session_env.0.clone()); } } env}
impl hyprwire::Dispatch<session_core_session::SessionCoreSession> for crate::Sessiond { fn event( &mut self, object: &session_core_session::SessionCoreSession, event: <session_core_session::SessionCoreSession as hyprwire::Object>::Event<'_>, ) { let Some(entity) = entity_by_object(&self.world, object) else { return; }; let Some(session_id) = id(&self.world, entity) else { return; };
if matches!(event, session_core_session::Event::Destroy) { self.world .resource_mut::<ecs::ProtocolObjects>() .remove_session_object(entity, object); return; }
let client = object.client(); if !can_modify(&self.world, entity, client.creds()) { object.send_error( session_core_v1::ErrorCode::PermissionDenied, "permission denied", ); return; }
match event { session_core_session::Event::Terminate => { self.terminate_session(entity); user::sync_all(&mut self.world); } session_core_session::Event::Kill => { self.kill_session(entity); user::sync_all(&mut self.world); } session_core_session::Event::Lock => { log::info!(session_id = session_id.as_raw(); "lock requested"); if let Some(objects) = self .world .resource::<ecs::ProtocolObjects>() .session_objects(entity) { for object in objects { object.send_locked(); object.send_ok(); } } } session_core_session::Event::Unlock => { log::info!(session_id = session_id.as_raw(); "unlock requested"); if let Some(objects) = self .world .resource::<ecs::ProtocolObjects>() .session_objects(entity) { for object in objects { object.send_unlocked(); object.send_ok(); } } } _ => unreachable!(), } }}
impl crate::Sessiond { pub fn kill_session(&mut self, entity: entity::Entity) { let Some(session_id) = id(&self.world, entity) else { return; };
log::info!(session_id = session_id.as_raw(); "kill requested");
set_active_state( &mut self.world, entity, session_core_v1::ActiveState::Closing, ); if let Err(err) = self.cgroup_manager.kill_session(session_id) { log::error!(session_id = session_id.as_raw(), err = err.to_string().as_str(); "kill failed"); } #[cfg(feature = "consolekit")] self.sync_consolekit_session(session_id, entity);
self.finish_session_destroy(session_id); }
pub fn terminate_session(&mut self, entity: entity::Entity) { let Some(session_id) = id(&self.world, entity) else { return; };
log::info!(session_id = session_id.as_raw(); "terminate requested");
set_active_state( &mut self.world, entity, session_core_v1::ActiveState::Closing, ); if let Err(err) = self.cgroup_manager.terminate_session(session_id) { log::error!(session_id = session_id.as_raw(), err = err.to_string().as_str(); "terminate failed"); } #[cfg(feature = "consolekit")] self.sync_consolekit_session(session_id, entity);
self.finish_session_destroy(session_id); }
fn finish_session_destroy(&mut self, session_id: SessionId) { let Some(entity) = entity(&self.world, session_id) else { log::warn!(session_id = session_id.as_raw(); "destroy requested for unknown session"); return; };
let uid = self.world.get::<ecs::Uid>(entity).map_or(0, |uid| uid.0); let username = self .world .get::<ecs::Username>(entity) .map_or("", |username| username.0.as_str()); log::info!(session_id = session_id.as_raw(), uid, username; "session destroyed");
if let Some(objects) = self .world .resource::<ecs::ProtocolObjects>() .session_objects(entity) { for object in objects { object.send_destroyed(); } } let _ = self.remove_session(session_id); user::sync_all(&mut self.world);
self.cleanup_user_runtime_dir(uid); }
pub(crate) fn cleanup_user_runtime_dir(&self, uid: u32) { let Some(user) = uzers::get_user_by_uid(uid) else { return; };
if !self.cgroup_manager.has_uid_sessions(uid) && !user::is_lingering(&self.world, uid) && entities_by_uid(&self.world, uid).is_empty() && let Err(e) = user::remove_runtime_dir( &self .user_config(&user.name().to_string_lossy()) .runtime_dir(uid), ) { log::error!(uid, error = e.to_string().as_str(); "failed to remove runtime dir"); } }}
pub fn add_object( world: &mut world::World, session_id: SessionId, entity: entity::Entity, object: session_core_session::SessionCoreSession,) { log::debug!(session_id = session_id.as_raw(), uid = object.client().creds().uid.as_raw(); "session info requested");
object.send_session_id(session_id.as_raw()); if let Some(uid) = world.get::<ecs::Uid>(entity) { object.send_uid(uid.0); } if let Some(username) = world.get::<ecs::Username>(entity) { object.send_username(&username.0); } if let Some(class) = world.get::<ecs::SessionClass>(entity) { object.send_class((*class).into()); } if let Some(vt) = world.get::<ecs::Vt>(entity).map(|vt| vt.0.get()) { object.send_vt(vt); } if let Some(active_state) = world.get::<ecs::ActiveState>(entity) { object.send_active(active_state.0); } object.send_can_modify(u32::from(can_modify( world, entity, object.client().creds(), ))); if let Some(seat) = world.get::<ecs::SeatName>(entity) { object.send_seat(&seat.0); } if let Some(leader_id) = world.get::<ecs::LeaderId>(entity) { object.send_leader(leader_id.0.cast_unsigned()); } if let Some(timestamp) = world.get::<ecs::Timestamp>(entity) { let timestamp_secs = timestamp .0 .duration_since(time::UNIX_EPOCH) .unwrap_or_default() .as_secs(); let timestamp = u32::try_from(timestamp_secs).unwrap_or(u32::MAX); object.send_timestamp(timestamp); } if world.get::<ecs::IsLocal>(entity).is_some() { object.send_is_local(1); } else { object.send_is_local(0); } if let Some(remote_host) = world.get::<ecs::RemoteHost>(entity) { object.send_remote_host(&remote_host.0); } object.send_done();
world .resource_mut::<ecs::ProtocolObjects>() .add_session_object(entity, object);}
pub fn sync_vt_session_states(world: &mut world::World) { seat::refresh_current_vts(world); seat::sync_states(world); user::sync_all(world);}
pub fn set_active_state( world: &mut world::World, entity: entity::Entity, state: session_core_v1::ActiveState,) { let session_id = world .get::<ecs::SessionId>(entity) .map(|session_id| session_id.0.as_raw()) .unwrap_or_default(); let vt = world .get::<ecs::Vt>(entity) .map(|vt| vt.0.get()) .unwrap_or_default(); log::info!(session_id, vt; "session state changed"); if let Some(mut active_state) = world.get_mut::<ecs::ActiveState>(entity) { active_state.0 = state; }}
impl hyprwire::Handler<session_management_v1::SessionManagementV1> for crate::Sessiond { fn protocol_filter(&mut self, client: server::ServerClient) -> bool { self.world .resource::<ecs::AccessRules>() .management .allows(client.creds()) }}
impl hyprwire::Handler<session_management_v2::SessionManagementV2> for crate::Sessiond { fn protocol_filter(&mut self, client: server::ServerClient) -> bool { self.world .resource::<ecs::AccessRules>() .management .allows(client.creds()) }}
impl hyprwire::Handler<session_core_v1::SessionCoreV1> for crate::Sessiond { fn bind(&mut self, object: session_core_manager::SessionCoreManager) { sync_vt_session_states(&mut self.world);
entities(&self.world) .into_iter() .filter_map(|entity| id(&self.world, entity)) .for_each(|session_id| { object.send_session_added(session_id.as_raw()); }); object.send_can_control_power(u32::from(self.can_control_power(object.client().creds()))); object.send_done(); self.world .resource_mut::<ecs::SessionInfoManagers>() .0 .push(object); }}
impl crate::Sessiond { // Allows to manage power commands (power off, reboot) only // if no other sessions are active or user has root permissions pub fn can_control_power(&self, creds: &hyprwire::Creds) -> bool { self.world .resource::<ecs::AccessRules>() .management .allows(creds) || entities(&self.world) .into_iter() .filter_map(|entity| { Some(( self.world.get::<ecs::Uid>(entity)?, self.world.get::<ecs::SessionClass>(entity)?, )) }) .all(|(uid, session_class)| { uid.0 == creds.uid.as_raw() || *session_class != ecs::SessionClass::User }) }
fn run_power_command(command: &[String], action: &str) -> Result<(), String> { let Some((program, args)) = command.split_first() else { log::warn!("{action} command is not configured"); return Err(format!("no {action} command configured")); };
log::info!( program = program.as_str(), args:? = args; "running {action} command" );
match process::Command::new(program).args(args).status() { Ok(status) if status.success() => Ok(()), Ok(status) => { log::error!(program = program.as_str(), status = status.to_string().as_str(); "{action} command failed"); Err(format!("{action} command {program:?} exited with {status}")) } Err(err) => { log::error!(program = program.as_str(), error = err.to_string().as_str(); "failed to start {action} command"); Err(format!( "failed to start {action} command {program:?}: {err}" )) } } }
pub fn handle_power_action( &self, action: PowerAction, creds: &hyprwire::Creds, ) -> Result<(), PowerError> { let foreign_session_count = entities(&self.world) .into_iter() .filter_map(|entity| { Some(( self.world.get::<ecs::Uid>(entity)?, self.world.get::<ecs::SessionClass>(entity)?, )) }) .filter(|(uid, session_class)| { uid.0 != creds.uid.as_raw() && **session_class == ecs::SessionClass::User }) .count();
if !self.can_control_power(creds) { log::warn!( uid = creds.uid.as_raw(), pid = creds.pid.as_raw_pid(), foreign_session_count, action = action.name(); "power action denied: multiple sessions active" ); return Err(PowerError::ActiveSessions(format!( "cannot {} while multiple sessions are active (root privileges required)", action.display_name() ))); }
log::info!( uid = creds.uid.as_raw(), pid = creds.pid.as_raw_pid(), foreign_session_count, action = action.name(); "power action accepted" );
rustix::fs::sync();
let config = &self.world.resource::<ecs::DaemonConfig>().0.power; let command = match action { PowerAction::Reboot => &config.reboot, PowerAction::Poweroff => &config.poweroff, PowerAction::Suspend => &config.suspend, PowerAction::Hibernate => &config.hibernate, };
if let Err(err) = Self::run_power_command(command, action.name()) { return Err(PowerError::Io(err)); }
Ok(()) }
pub fn user_config(&self, username: &str) -> config::UserConfig { self.world .resource::<ecs::DaemonConfig>() .0 .users .get(username) .cloned() .unwrap_or_default() }}
fn send_power_result( object: &session_core_manager::SessionCoreManager, result: Result<(), PowerError>,) { match result { Ok(()) => object.send_done(), Err(PowerError::ActiveSessions(message)) => { object.send_error(session_core_v1::ErrorCode::ActiveSessions, message); } Err(PowerError::Io(message)) => { object.send_error(session_core_v1::ErrorCode::Io, message); } }}
impl hyprwire::Dispatch<session_manager::SessionManager> for crate::Sessiond { fn event( &mut self, object: &session_manager::SessionManager, event: <session_manager::SessionManager as hyprwire::Object>::Event<'_>, ) { let client = object.client();
match event { session_manager::Event::Destroy => {} session_manager::Event::CreateSession { session_management_session, uid, } => { let session_id = self.world.resource_mut::<ecs::NextSessionId>().take(); let username = uzers::get_user_by_uid(uid) .map(|user| user.name().to_string_lossy().into_owned()) .unwrap_or_default(); log::info!(session_id = session_id.as_raw(), uid, username = username.as_str(); "session created");
let leader_id = client.creds().pid.as_raw_pid();
if let Err(e) = self .cgroup_manager .create_session_cgroup(uid, session_id, leader_id) { log::error!(session_id = session_id.as_raw(), error = e.to_string().as_str(); "failed to create session cgroup"); }
seat::refresh_current_vts(&mut self.world);
user::create_runtime_dir(uid, &self.user_config(&username).runtime_dir(uid));
let session_entity = spawn( &mut self.world, session_id, ecs::SessionClass::User, uid, username, None, None, None, leader_id, None, session_core_v1::ActiveState::Opening, Some(session_management_session), ); #[cfg(not(feature = "consolekit"))] let _ = session_entity;
#[cfg(feature = "consolekit")] self.sync_consolekit_session(session_id, session_entity); user::sync_all(&mut self.world); } } }}
impl hyprwire::Dispatch<session_manager_v1::SessionManager> for crate::Sessiond { fn event( &mut self, object: &session_manager_v1::SessionManager, event: <session_manager_v1::SessionManager as hyprwire::Object>::Event<'_>, ) { let client = object.client();
match event { session_manager_v1::Event::CreateSession { uid, vt, is_local, remote_host, class, } => { let session_id = self.world.resource_mut::<ecs::NextSessionId>().take(); let username = uzers::get_user_by_uid(uid) .map(|user| user.name().to_string_lossy().into_owned()) .unwrap_or_default(); log::info!(session_id = session_id.as_raw(), uid, username = username.as_str(), vt, is_local, remote_host = remote_host.as_str(); "session created");
if let Err(e) = self.cgroup_manager.create_session_cgroup( uid, session_id, client.creds().pid.as_raw_pid(), ) { log::error!(session_id = session_id.as_raw(), error = e.to_string().as_str(); "failed to create session cgroup"); }
seat::refresh_current_vts(&mut self.world);
let is_local = is_local != 0; let session_vt = num::NonZeroU32::new(vt); let mut session_seat_entity = if session_vt.is_some() { seat::first(&self.world) } else { None }; let mut session_seat = session_seat_entity.and_then(|seat_entity| { self.world .get::<ecs::SeatName>(seat_entity) .map(|seat| seat.0.clone()) .filter(|seat| !seat.is_empty()) }); let mut active_state = session_core_v1::ActiveState::Online;
user::create_runtime_dir(uid, &self.user_config(&username).runtime_dir(uid));
if let Some((seat_entity, seat)) = seat::entities(&self.world) .into_iter() .find_map(|seat_entity| { let mode = self.world.get::<ecs::SeatMode>(seat_entity)?; if mode.0 != ecs::SeatKind::VtBound { return None; } let current_vt = self.world.get::<ecs::CurrentVt>(seat_entity)?.0; (Some(current_vt) == session_vt && is_local).then(|| { self.world .get::<ecs::SeatName>(seat_entity) .map(|seat| (seat_entity, seat.0.clone())) })? }) { session_seat_entity = Some(seat_entity); session_seat = Some(seat); active_state = session_core_v1::ActiveState::Active; }
let pending_env = self .world .resource_mut::<ecs::PendingSessionEnvs>() .0 .remove(object);
let mut env = self.user_config(&username).environment( uid, session_id.as_raw(), session_seat.as_deref(), pending_env .as_ref() .and_then(|env| env.get("TERM")) .map(String::as_str), ); env.extend(session_envs_by_uid(&self.world, uid, None));
let session_entity = spawn( &mut self.world, session_id, class.into(), uid, username, session_vt.map(num::NonZeroU32::get), session_seat.clone(), session_seat_entity, client.creds().pid.as_raw_pid(), if is_local { None } else { Some(remote_host) }, active_state, None, );
if let Some(pending_env) = pending_env && let Some(mut session_env) = self.world.get_mut::<ecs::SessionEnv>(session_entity) { session_env.0 = pending_env; }
#[cfg(feature = "consolekit")] self.sync_consolekit_session(session_id, session_entity); user::sync_all(&mut self.world);
object.send_session_id(session_id.as_raw()); object.send_seat(session_seat.as_deref().unwrap_or_default()); for (name, value) in env { object.send_env(name.as_str(), value.as_str()); } object.send_done();
for manager in &self.world.resource::<ecs::SessionInfoManagers>().0 { manager.send_session_added(session_id.as_raw()); } } session_manager_v1::Event::Env { name, value } => { self.world .resource_mut::<ecs::PendingSessionEnvs>() .0 .entry(object.clone()) .or_default() .insert(name, value); } session_manager_v1::Event::DestroySession { session_id } => { if let Some(entity) = entity(&self.world, SessionId::from_raw(session_id)) { self.terminate_session(entity); } } session_manager_v1::Event::Destroy => { self.world .resource_mut::<ecs::PendingSessionEnvs>() .0 .remove(object); } } }}
impl hyprwire::Dispatch<session_management_session::SessionManagementSession> for crate::Sessiond { fn event( &mut self, object: &session_management_session::SessionManagementSession, event: <session_management_session::SessionManagementSession as hyprwire::Object>::Event< '_, >, ) { let Some(entity) = entity_by_management_object(&self.world, object) else { return; }; let Some(session_id) = id(&self.world, entity) else { return; };
match event { session_management_session::Event::Vt { vt } => { if let Some(vt) = num::NonZeroU32::new(vt) { self.world.entity_mut(entity).insert(ecs::Vt(vt)); } } session_management_session::Event::RemoteHost { remote_host } => { self.world .entity_mut(entity) .insert(ecs::RemoteHost(remote_host)); self.world.entity_mut(entity).remove::<ecs::IsLocal>(); } session_management_session::Event::Done => { let uid = self.world.get::<ecs::Uid>(entity).map(|uid| uid.0); let username = self .world .get::<ecs::Username>(entity) .map(|username| username.0.clone()); let session_vt = self.world.get::<ecs::Vt>(entity).map(|vt| vt.0); let is_local = self.world.get::<ecs::IsLocal>(entity).is_some(); seat::refresh_current_vts(&mut self.world);
let mut session_seat_entity = if session_vt.is_some() { seat::first(&self.world) } else { None }; let mut session_seat = session_seat_entity.and_then(|seat_entity| { self.world .get::<ecs::SeatName>(seat_entity) .map(|seat| seat.0.clone()) .filter(|seat| !seat.is_empty()) });
let active_state = if let Some((seat_entity, seat)) = seat::entities(&self.world) .into_iter() .find_map(|seat_entity| { let mode = self.world.get::<ecs::SeatMode>(seat_entity)?; if mode.0 != ecs::SeatKind::VtBound { return None; } let current_vt = self.world.get::<ecs::CurrentVt>(seat_entity)?.0; (Some(current_vt) == session_vt && is_local).then(|| { self.world .get::<ecs::SeatName>(seat_entity) .map(|seat| (seat_entity, seat.0.clone())) })? }) { session_seat_entity = Some(seat_entity); session_seat = Some(seat); session_core_v1::ActiveState::Active } else { session_core_v1::ActiveState::Online };
{ let mut session = self.world.entity_mut(entity); if let Some(seat) = session_seat.as_ref() { session.insert(ecs::SeatName(seat.clone())); } else { session.remove::<ecs::SeatName>(); } if let Some(seat_entity) = session_seat_entity { session.insert(ecs::SessionSeat(seat_entity)); } else { session.remove::<ecs::SessionSeat>(); } }
set_active_state(&mut self.world, entity, active_state); #[cfg(feature = "consolekit")] self.sync_consolekit_session(session_id, entity); user::sync_all(&mut self.world);
object.send_session_id(session_id.as_raw()); object.send_seat(session_seat.as_deref().unwrap_or_default()); if let (Some(uid), Some(username)) = (uid, username) { let term = self .world .get::<ecs::SessionEnv>(entity) .and_then(|env| env.0.get("TERM")) .map(String::as_str); let mut env = self.user_config(&username).environment( uid, session_id.as_raw(), session_seat.as_deref(), term, ); env.extend(session_envs_by_uid(&self.world, uid, Some(entity)));
for (name, value) in env { object.send_env(name.as_str(), value.as_str()); } } object.send_done();
for manager in &self.world.resource::<ecs::SessionInfoManagers>().0 { manager.send_session_added(session_id.as_raw()); } } session_management_session::Event::Env { name, value } => { if let Some(mut env) = self.world.get_mut::<ecs::SessionEnv>(entity) { env.0.insert(name, value); } else { self.world .entity_mut(entity) .insert(ecs::SessionEnv(BTreeMap::from([(name, value)]))); } } session_management_session::Event::Class { class } => { self.world .entity_mut(entity) .insert(ecs::SessionClass::from(class)); } session_management_session::Event::Destroy => { self.terminate_session(entity); } } }}
impl hyprwire::Dispatch<session_core_manager::SessionCoreManager> for crate::Sessiond { fn event( &mut self, object: &session_core_manager::SessionCoreManager, event: <session_core_manager::SessionCoreManager as hyprwire::Object>::Event<'_>, ) { match event { session_core_manager::Event::GetSession { session_core_session, session_id, } => { sync_vt_session_states(&mut self.world);
let session_id = SessionId::from_raw(session_id); if let Some(entity) = entity(&self.world, session_id) { add_object(&mut self.world, session_id, entity, session_core_session); return; }
log::warn!(session_id = session_id.as_raw(); "get_session requested for unknown session"); session_core_session.send_destroyed(); } session_core_manager::Event::GetSessionByPid { session_core_session, pid, } => { sync_vt_session_states(&mut self.world);
let Ok(Some(session_id)) = self.cgroup_manager.find_session_by_pid(pid) else { log::warn!(pid; "get_session_by_pid: no session found for pid"); session_core_session.send_destroyed(); return; };
if let Some(entity) = entity(&self.world, session_id) { add_object(&mut self.world, session_id, entity, session_core_session); return; }
log::warn!(session_id = session_id.as_raw(); "get_session_by_pid requested for unknown session"); session_core_session.send_destroyed(); } session_core_manager::Event::GetSessionByUid { session_core_session, uid, } => { sync_vt_session_states(&mut self.world);
if let Some(entity) = entity_by_uid(&self.world, uid) && let Some(session_id) = id(&self.world, entity) { add_object(&mut self.world, session_id, entity, session_core_session); return; }
log::warn!(uid; "get_session_by_uid requested for unknown session"); session_core_session.send_destroyed(); } session_core_manager::Event::GetUser { session_core_user, uid, } => { let entity = user::entity_by_uid(&self.world, uid) .or_else(|| user::create(&mut self.world, uid, false)); let Some(entity) = entity else { log::warn!(uid; "get_user requested for unknown user"); session_core_user.send_not_found(); return; };
let management_access = self.world.resource::<ecs::AccessRules>().management; user::add_object( &mut self.world, entity, session_core_user, management_access, ); } session_core_manager::Event::Reboot => { let client = object.client(); send_power_result( object, self.handle_power_action(PowerAction::Reboot, client.creds()), ); } session_core_manager::Event::Poweroff => { let client = object.client(); send_power_result( object, self.handle_power_action(PowerAction::Poweroff, client.creds()), ); } session_core_manager::Event::Suspend => { let client = object.client(); send_power_result( object, self.handle_power_action(PowerAction::Suspend, client.creds()), ); } session_core_manager::Event::Hibernate => { let client = object.client(); send_power_result( object, self.handle_power_action(PowerAction::Hibernate, client.creds()), ); } session_core_manager::Event::GetSeat { session_core_seat, seat, } => { let Some(seat_entity) = seat::entity_by_name(&self.world, &seat) else { session_core_seat.send_destroyed(); return; }; let management_access = self.world.resource::<ecs::AccessRules>().management; seat::add_object( &mut self.world, seat_entity, session_core_seat, management_access, ); } session_core_manager::Event::Destroy => { self.world .resource_mut::<ecs::SessionInfoManagers>() .0 .retain(|m| m != object); } } }}