use futures_util::StreamExt; #[zbus::proxy( interface = "org.freedesktop.login1.Manager", default_service = "org.freedesktop.login1", default_path = "/org/freedesktop/login1" )] trait LoginManager { fn inhibit(&self, what: &str, who: &str, why: &str, mode: &str) -> zbus::Result; } #[zbus::proxy( interface = "org.freedesktop.login1.Session", default_service = "org.freedesktop.login1", default_path = "/org/freedesktop/login1/session/auto" )] trait LoginSession { #[zbus(property)] fn locked_hint(&self) -> zbus::Result; fn set_locked_hint(&self, value: bool) -> zbus::Result<()>; } #[derive(zvariant::DeserializeDict, zvariant::SerializeDict, zvariant::Type, PartialEq, Eq)] #[zvariant(signature = "dict")] struct InhibitOptions { reason: zvariant::Optional } #[derive(zvariant::Type, serde::Serialize, serde::Deserialize)] #[zvariant(signature = "u")] enum SessionState { Running, QueryEnd, Ending } #[derive(zvariant::DeserializeDict, zvariant::SerializeDict, zvariant::Type)] #[zvariant(signature = "dict")] struct State { screensaver_active: bool, session_state: SessionState, } struct Request { app_id: String, path: zvariant::OwnedObjectPath, inhibitor: Option } #[zbus::interface(name = "org.freedesktop.impl.portal.Request")] impl Request { async fn close(&mut self, #[zbus(object_server)] object_server: &zbus::ObjectServer) -> zbus::fdo::Result<()> { // Drop the file descriptor that represents the inhibitions. self.inhibitor.take(); // This might not actually deadlock if I'm lucky. object_server.remove::>(self.path.as_ref()).await?; Ok(()) } } struct Session { app_id: String, path: zvariant::OwnedObjectPath, } #[zbus::interface(name = "org.freedesktop.impl.portal.Session")] impl Session { #[zbus(property)] fn version(&self) -> u32 { 1 } async fn close( &mut self, #[zbus(object_server)] object_server: &zbus::ObjectServer, #[zbus(signal_emitter)] signal_emitter: zbus::object_server::SignalEmitter<'_> ) -> zbus::fdo::Result<()> { Self::closed(&signal_emitter).await?; // This might not actually deadlock if I'm lucky. object_server.remove::>(self.path.as_ref()).await?; Ok(()) } #[zbus(signal)] async fn closed(signal_emitter: &zbus::object_server::SignalEmitter<'_>) -> zbus::Result<()>; } struct InhibitService { logind: LoginManagerProxy<'static>, sessions: Vec> } #[zbus::interface(name = "org.freedesktop.impl.portal.Inhibit")] impl InhibitService { /// Inhibits session status changes. As a side-effect of this /// call, a `Request` object is exported on the object path /// handle. To end the inhibition, call /// `org.freedesktop.impl.portal.Request.Close` on that object. /// /// The flags determine what changes are inhibited: /// /// - `1`: Logout /// - `2`: User Switch /// - `4`: Suspend /// - `8`: Idle #[allow(clippy::too_many_arguments)] async fn inhibit( &self, handle: zvariant::ObjectPath<'_>, app_id: &str, #[allow(unused_variables)] window: &str, flags: u32, options: InhibitOptions, #[zbus(object_server)] object_server: &zbus::ObjectServer, ) -> zbus::fdo::Result<()> { let mut what: Vec<&'static str> = vec![]; if (flags & 1) == 1 || (flags & 2) == 2 { what.push("shutdown"); } if (flags & 4) == 4 { what.push("sleep"); } if (flags & 8) == 8 { what.push("idle"); } object_server.at(handle.clone(), Request { app_id: app_id.to_owned(), inhibitor: Some(self.logind.inhibit( &what.join(":"), app_id, options.reason.as_deref().unwrap_or_default(), "block" ).await?), path: handle.to_owned().into() }).await?; Ok(()) } /// Creates a monitoring session. While this session is active, /// the caller will receive StateChanged signals with updates on /// the session state. async fn create_monitor( &mut self, #[allow(unused_variables)] handle: zvariant::ObjectPath<'_>, session_handle: zvariant::ObjectPath<'_>, app_id: &str, #[allow(unused_variables)] window: &str, #[zbus(object_server)] object_server: &zbus::ObjectServer, ) -> zbus::fdo::Result { let session = Session { app_id: app_id.to_owned(), path: session_handle.clone().into_owned().into() }; object_server.at(&session_handle, session).await?; self.sessions.push(session_handle.clone().into_owned()); Ok(0) } #[zbus(signal)] async fn state_changed( signal_emitter: &zbus::object_server::SignalEmitter<'_>, session_handle: zvariant::ObjectPath<'_>, state: State, ) -> zbus::Result<()>; /// Acknowledges that the caller received the /// `Inhibit::state_changed` signal. This method should be called /// within one second of receiving a /// [`state_changed`][Inhibit::state_changed] signal with /// [`State::QueryEnd`]. async fn query_end_response(&self, session_handle: zvariant::ObjectPath<'_>) { todo!() } } #[tokio::main] async fn main() -> Result<(), zbus::Error> { let system_bus = zbus::connection::Builder::system()?.build().await?; let service = InhibitService { logind: LoginManagerProxy::builder(&system_bus).build().await?, sessions: Default::default(), }; let _conn = zbus::connection::Builder::session()? .name("xyz.vikanezrimaya.fdo.portal.Systemd")? .serve_at("/", service)? .build() .await?; // This seems broken. let session = LoginSessionProxy::builder(&system_bus) .cache_properties(zbus::proxy::CacheProperties::Lazily) .build() .await?; dbg!(&session); let mut lock_changes: zbus::proxy::PropertyStream<'_, bool> = session.receive_locked_hint_changed().await; loop { let change = lock_changes.next().await.expect("property stream broke"); let service = _conn.object_server().interface::<_, InhibitService>("/").await?; let locked = dbg!(change.get().await?); let sessions = dbg!(service.get().await.sessions.clone()); for session in sessions { InhibitService::state_changed(service.signal_emitter(), session, State { screensaver_active: locked, session_state: SessionState::Running, }).await?; } eprintln!("finished processing lock/unlock event"); } }