// Copyright 2025 System76 // SPDX-License-Identifier: GPL-3.0-only use calloop::channel::*; use cctk::{ sctk::{ self, reexports::{ calloop::{self, channel}, calloop_wayland_source::WaylandSource, }, registry::RegistryState, }, wayland_client::{self, globals::GlobalListContents, protocol::wl_registry, Dispatch, Proxy}, }; use cosmic::iced::futures::{self, SinkExt}; use cosmic_protocols::a11y::v1::client::cosmic_a11y_manager_v1::{self, Filter}; use futures::{channel::mpsc, executor::block_on}; use wayland_client::{globals::registry_queue_init, Connection}; use super::{AccessibilityEvent, AccessibilityRequest}; pub fn spawn_a11y( mut tx: mpsc::Sender, ) -> anyhow::Result> { let (a11y_tx, a11y_rx) = calloop::channel::sync_channel(100); let conn = Connection::connect_to_env()?; std::thread::spawn(move || { struct State { loop_signal: calloop::LoopSignal, tx: mpsc::Sender, global: cosmic_a11y_manager_v1::CosmicA11yManagerV1, magnifier: bool, screen_inverted: bool, screen_filter: Filter, } impl Dispatch for State { fn event( state: &mut Self, _proxy: &cosmic_a11y_manager_v1::CosmicA11yManagerV1, event: ::Event, _data: &(), _conn: &Connection, _qhandle: &sctk::reexports::client::QueueHandle, ) { match event { cosmic_a11y_manager_v1::Event::Magnifier { active } => { let magnifier = active .into_result() .unwrap_or(cosmic_a11y_manager_v1::ActiveState::Disabled) == cosmic_a11y_manager_v1::ActiveState::Enabled; if magnifier != state.magnifier { if block_on(state.tx.send(AccessibilityEvent::Magnifier(magnifier))) .is_err() { state.loop_signal.stop(); state.loop_signal.wakeup(); }; state.magnifier = magnifier; } } cosmic_a11y_manager_v1::Event::ScreenFilter { inverted, filter } => { let inverted = inverted .into_result() .unwrap_or(cosmic_a11y_manager_v1::ActiveState::Disabled) == cosmic_a11y_manager_v1::ActiveState::Enabled; let filter = filter.into_result().unwrap_or(Filter::Unknown); if inverted != state.screen_inverted || filter != state.screen_filter { if block_on( state .tx .send(AccessibilityEvent::ScreenFilter { inverted, filter }), ) .is_err() { state.loop_signal.stop(); state.loop_signal.wakeup(); }; state.screen_inverted = inverted; state.screen_filter = filter; } } _ => unreachable!(), } } } impl Dispatch for State { fn event( _state: &mut Self, _proxy: &wl_registry::WlRegistry, _event: ::Event, _data: &GlobalListContents, _conn: &Connection, _qhandle: &sctk::reexports::client::QueueHandle, ) { // We don't care about any dynamic globals } } let mut event_loop = calloop::EventLoop::::try_new().unwrap(); let loop_handle = event_loop.handle(); let (globals, event_queue) = registry_queue_init(&conn).unwrap(); let qhandle = event_queue.handle(); WaylandSource::new(conn, event_queue) .insert(loop_handle.clone()) .unwrap(); let registry_state = RegistryState::new(&globals); let global = registry_state .bind_one::(&qhandle, 1..=2, ()) .unwrap(); let _ = block_on(tx.send(AccessibilityEvent::Bound(global.version()))); loop_handle .insert_source(a11y_rx, |request, _, state| match request { channel::Event::Msg(AccessibilityRequest::Magnifier(val)) => { state.global.set_magnifier(if val { cosmic_a11y_manager_v1::ActiveState::Enabled } else { cosmic_a11y_manager_v1::ActiveState::Disabled }); } channel::Event::Msg(AccessibilityRequest::ScreenFilter { inverted, filter }) => { state.global.set_screen_filter( if inverted { cosmic_a11y_manager_v1::ActiveState::Enabled } else { cosmic_a11y_manager_v1::ActiveState::Disabled }, filter, ); } channel::Event::Closed => { state.loop_signal.stop(); state.loop_signal.wakeup(); } }) .unwrap(); let mut state = State { loop_signal: event_loop.get_signal(), tx, global, magnifier: false, screen_inverted: false, screen_filter: Filter::Unknown, }; event_loop.run(None, &mut state, |_| {}).unwrap(); }); Ok(a11y_tx) }