From 0db801b049e752f4c78af0cf5ee5f728b5b1dced Mon Sep 17 00:00:00 2001 From: Vika Date: Tue, 3 Dec 2024 07:35:17 +0300 Subject: Initial commit Supposed to work, I guess. At least the idle inhibition part, which is the part I care about. State tracking is broken due to zbus weirdness. I have no desire to debug this any further. --- src/main.rs | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8c9a72e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,211 @@ +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"); + } +} -- cgit 1.4.1