diff --git a/Cargo.lock b/Cargo.lock index 758dc4f..87d960f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,6 +308,14 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "cosmic-dbus-a11y" +version = "0.1.0" +source = "git+https://github.com/pop-os/dbus-settings-bindings#62100129240d164e39fff16bda34faad520936de" +dependencies = [ + "zbus", +] + [[package]] name = "cosmic-notifications-util" version = "0.1.0" @@ -323,6 +331,7 @@ version = "0.1.0" dependencies = [ "async-signals", "color-eyre", + "cosmic-dbus-a11y", "cosmic-notifications-util", "futures-util", "itertools", diff --git a/Cargo.toml b/Cargo.toml index c0c7a68..250dafd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,8 @@ publish = false async-signals = "0.4" color-eyre = "0.6" futures-util = "0.3" +cosmic-dbus-a11y = { git = "https://github.com/pop-os/dbus-settings-bindings" } + launch-pad = { git = "https://github.com/pop-os/launch-pad" } itertools = "0.12" #launch-pad = { git = "https://github.com/pop-os/launch-pad", branch = "remove-sync-bounds" } diff --git a/Justfile b/Justfile index 051f889..3f3d5be 100644 --- a/Justfile +++ b/Justfile @@ -10,6 +10,7 @@ vendor_args := if vendor == '1' { '--frozen --offline' } else { '' } debug_args := if debug == '1' { '' } else { '--release' } cargo_args := vendor_args + ' ' + debug_args xdp_cosmic := '/usr/libexec/xdg-desktop-portal-cosmic' +orca := '/usr/bin/orca' bindir := prefix + '/bin' systemddir := prefix + '/lib/systemd/user' @@ -19,7 +20,7 @@ applicationdir := prefix + '/share/applications' all: _extract_vendor build build: - XDP_COSMIC={{xdp_cosmic}} cargo build {{cargo_args}} + XDP_COSMIC={{xdp_cosmic}} ORCA={{orca}} cargo build {{cargo_args}} # Installs files into the system install: diff --git a/src/a11y.rs b/src/a11y.rs new file mode 100644 index 0000000..6c30191 --- /dev/null +++ b/src/a11y.rs @@ -0,0 +1,80 @@ +use futures_util::StreamExt; +use launch_pad::ProcessManager; +use tokio::sync::mpsc; +use tracing::Instrument; + +const ORCA: Option<&'static str> = option_env!("ORCA"); + +pub async fn start_a11y( + env_vars: Vec<(String, String)>, + pman: ProcessManager, +) -> color_eyre::Result<()> { + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut process_key = None; + let conn = zbus::Connection::session().await?; + let proxy = cosmic_dbus_a11y::StatusProxy::new(&conn).await?; + + tokio::spawn(async move { + let mut watch_changes = proxy.receive_screen_reader_enabled_changed().await; + let mut enabled = false; + if let Ok(status) = proxy.screen_reader_enabled().await { + _ = tx.send(status); + + enabled = status; + } + while let Some(change) = watch_changes.next().await { + let Ok(new_enabled) = change.get().await else { + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; + continue; + }; + if enabled != new_enabled { + _ = tx.send(new_enabled); + enabled = new_enabled; + } + } + }); + + while let Some(enabled) = rx.recv().await { + let stdout_span = info_span!(parent: None, "screen-reader"); + let stderr_span = stdout_span.clone(); + if enabled && process_key.is_none() { + // spawn orca + match pman + .start( + launch_pad::process::Process::new() + .with_executable(ORCA.unwrap_or("/usr/bin/orca")) + .with_env(env_vars.clone()) + .with_on_stdout(move |_, _, line| { + let stdout_span = stdout_span.clone(); + async move { + info!("{}", line); + } + .instrument(stdout_span) + }) + .with_on_stderr(move |_, _, line| { + let stderr_span = stderr_span.clone(); + async move { + warn!("{}", line); + } + .instrument(stderr_span) + }), + ) + .await + { + Ok(key) => { + process_key = Some(key); + } + Err(err) => { + tracing::error!("Failed to start screen reader {err:?}"); + } + } + } else if !enabled && process_key.is_some() { + // kill orca + info!("Stopping screen reader"); + if let Err(err) = pman.stop_process(process_key.take().unwrap()).await { + tracing::error!("Failed to stop screen reader. {err:?}") + } + } + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 19e0877..8da576e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ #[macro_use] extern crate tracing; +mod a11y; mod comp; mod notifications; mod process; @@ -194,6 +195,9 @@ async fn start( systemd::stop_systemd_target(); } + // start a11y if configured + tokio::spawn(a11y::start_a11y(env_vars.clone(), process_manager.clone())); + let (panel_notifications_fd, daemon_notifications_fd) = notifications::create_socket().expect("Failed to create notification socket");