summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2024-12-03 07:35:17 +0300
committerVika <vika@fireburn.ru>2024-12-03 07:36:15 +0300
commit0db801b049e752f4c78af0cf5ee5f728b5b1dced (patch)
tree53396cbd52160835f9d3d071d0201d23db76f5e1 /src
downloadxdg-desktop-portal-systemd-0db801b049e752f4c78af0cf5ee5f728b5b1dced.tar.zst
Initial commit HEAD main
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.
Diffstat (limited to 'src')
-rw-r--r--src/main.rs211
1 files changed, 211 insertions, 0 deletions
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<zvariant::OwnedFd>;
+}
+
+#[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<bool>;
+    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<String>
+}
+
+#[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<zvariant::OwnedFd>
+}
+#[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, zvariant::ObjectPath<'_>>(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, zvariant::ObjectPath<'_>>(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<zvariant::ObjectPath<'static>>
+}
+
+#[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<u32> {
+        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");
+    }
+}