diff --git a/gui/src/bus.rs b/gui/src/bus.rs new file mode 100644 index 0000000..92dfb66 --- /dev/null +++ b/gui/src/bus.rs @@ -0,0 +1,56 @@ +//! D-Bus client for the GUI. zbus runs on the workspace tokio runtime; the +//! runtime guard installed in `main` keeps the runtime context alive for the +//! lifetime of `app.run()`. Closures dispatch async work via +//! `glib::MainContext::spawn_local`, which polls the future on the GTK main +//! thread; tokio's reactor wakes the future when zbus I/O is ready. + +use authforge_common::types::Credential; + +const BUS_NAME: &str = "io.dangerousthings.AuthForge"; +const OBJECT_PATH: &str = "/io/dangerousthings/AuthForge"; +const IFACE: &str = "io.dangerousthings.AuthForge1"; + +#[derive(Clone)] +#[allow(dead_code)] // wired through keys_page.rs / enroll_dialog.rs in Tasks 4-6. +pub(crate) struct Daemon { + proxy: zbus::Proxy<'static>, +} + +#[allow(dead_code)] // wired through keys_page.rs / enroll_dialog.rs in Tasks 4-6. +impl Daemon { + pub async fn connect() -> zbus::Result { + let conn = zbus::Connection::system().await?; + // Mirror the CLI's known-working pattern; `Proxy::new_owned` returns a + // Proxy<'static> owning its connection so `Daemon` can be passed + // around freely. + let proxy = zbus::Proxy::new_owned(conn, BUS_NAME, OBJECT_PATH, IFACE).await?; + Ok(Self { proxy }) + } + + pub async fn list_credentials(&self, user: &str) -> zbus::Result> { + self.proxy.call("ListCredentials", &(user,)).await + } + + pub async fn enroll_own(&self, user: &str, nickname: &str) -> zbus::Result { + self.proxy.call("EnrollOwn", &(user, nickname)).await + } + + pub async fn remove_own(&self, user: &str, cred_id: &str) -> zbus::Result<()> { + self.proxy.call("RemoveOwn", &(user, cred_id)).await + } + + /// Subscribes to `EnrollmentFailed`. The returned stream is alive as long + /// as the caller holds it; the touch dialog drops it when it closes. We + /// don't subscribe to `EnrollmentSucceeded` because the call's return + /// value carries the new `Credential` — the source of truth for success. + pub async fn subscribe_enrollment_failed( + &self, + ) -> zbus::Result> { + self.proxy.receive_signal("EnrollmentFailed").await + } +} + +#[allow(dead_code)] // wired through keys_page.rs in Task 4. +pub(crate) fn current_user() -> String { + std::env::var("USER").unwrap_or_else(|_| "unknown".into()) +} diff --git a/gui/src/main.rs b/gui/src/main.rs index 6e684d1..2f1b8a8 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -1,6 +1,7 @@ use adw::prelude::*; use gtk::glib; +mod bus; mod error; const APP_ID: &str = "io.dangerousthings.AuthForge";