feat(accessiblity): high contrast and color filter toggles

This commit is contained in:
Ashley Wulber 2025-05-02 16:37:24 -04:00 committed by Michael Murphy
parent e6722d20e2
commit 9eb4053d31
7 changed files with 317 additions and 395 deletions

View file

@ -2,41 +2,29 @@
// SPDX-License-Identifier: GPL-3.0-only
use anyhow;
use cctk::sctk::reexports::calloop::channel::SyncSender;
use cctk::sctk::reexports::calloop::{self, channel::SyncSender};
use cosmic::iced::{
self,
futures::{self, channel::mpsc, SinkExt, StreamExt},
stream, Subscription,
};
use cosmic_protocols::a11y::v1::client::cosmic_a11y_manager_v1::Filter;
use cosmic_settings_subscriptions::cosmic_a11y_manager::{
self as thread, AccessibilityEvent, AccessibilityRequest,
};
use once_cell::sync::Lazy;
use tokio::sync::Mutex;
mod thread;
pub static WAYLAND_RX: Lazy<Mutex<Option<mpsc::Receiver<AccessibilityEvent>>>> =
pub static WAYLAND_RX: Lazy<Mutex<Option<tokio::sync::mpsc::Receiver<AccessibilityEvent>>>> =
Lazy::new(|| Mutex::new(None));
#[derive(Debug, Clone)]
pub enum WaylandUpdate {
State(AccessibilityEvent),
Started(SyncSender<AccessibilityRequest>),
Started(calloop::channel::Sender<AccessibilityRequest>),
Errored,
}
#[derive(Debug, Clone, Copy)]
pub enum AccessibilityEvent {
Bound(u32),
Magnifier(bool),
ScreenFilter { inverted: bool, filter: Filter },
}
#[derive(Debug, Clone, Copy)]
pub enum AccessibilityRequest {
Magnifier(bool),
ScreenFilter { inverted: bool, filter: Filter },
}
pub fn a11y_subscription() -> iced::Subscription<WaylandUpdate> {
Subscription::run_with_id(
std::any::TypeId::of::<WaylandUpdate>(),
@ -69,7 +57,7 @@ async fn start_listening(
}
guard.as_mut().unwrap()
};
if let Some(w) = rx.next().await {
if let Some(w) = rx.recv().await {
_ = output.send(WaylandUpdate::State(w)).await;
State::Waiting
} else {
@ -87,14 +75,13 @@ pub enum State {
}
pub struct WaylandWatcher {
rx: mpsc::Receiver<AccessibilityEvent>,
tx: SyncSender<AccessibilityRequest>,
rx: tokio::sync::mpsc::Receiver<AccessibilityEvent>,
tx: calloop::channel::Sender<AccessibilityRequest>,
}
impl WaylandWatcher {
pub fn new() -> anyhow::Result<Self> {
let (tx, rx) = mpsc::channel(20);
let tx = thread::spawn_a11y(tx)?;
let (tx, rx) = thread::spawn_wayland_connection(1)?;
Ok(Self { tx, rx })
}
}

View file

@ -1,161 +0,0 @@
// Copyright 2025 System76 <info@system76.com>
// 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<AccessibilityEvent>,
) -> anyhow::Result<SyncSender<AccessibilityRequest>> {
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<AccessibilityEvent>,
global: cosmic_a11y_manager_v1::CosmicA11yManagerV1,
magnifier: bool,
screen_inverted: bool,
screen_filter: Filter,
}
impl Dispatch<cosmic_a11y_manager_v1::CosmicA11yManagerV1, ()> for State {
fn event(
state: &mut Self,
_proxy: &cosmic_a11y_manager_v1::CosmicA11yManagerV1,
event: <cosmic_a11y_manager_v1::CosmicA11yManagerV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
) {
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<wl_registry::WlRegistry, GlobalListContents> for State {
fn event(
_state: &mut Self,
_proxy: &wl_registry::WlRegistry,
_event: <wl_registry::WlRegistry as Proxy>::Event,
_data: &GlobalListContents,
_conn: &Connection,
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
) {
// We don't care about any dynamic globals
}
}
let mut event_loop = calloop::EventLoop::<State>::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::<cosmic_a11y_manager_v1::CosmicA11yManagerV1, _, _>(&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)
}