chore(accessibility): use settings subscription for color filter

This commit is contained in:
Ashley Wulber 2025-05-01 13:52:04 -04:00 committed by Michael Murphy
parent cc2e4f77a0
commit be6b799b51
5 changed files with 13 additions and 250 deletions

6
Cargo.lock generated
View file

@ -1792,19 +1792,23 @@ dependencies = [
[[package]]
name = "cosmic-settings-subscriptions"
version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-settings-subscriptions#650f0bc1dbfdce2e541c104674257d1621b2de4c"
source = "git+https://github.com/pop-os/cosmic-settings-subscriptions#6c36b82d5f7264f3b06ca83e1b2d01c9f979d104"
dependencies = [
"bluez-zbus",
"cosmic-dbus-a11y",
"cosmic-dbus-networkmanager",
"cosmic-protocols",
"futures",
"iced_futures",
"itertools 0.14.0",
"libpulse-binding",
"log",
"num-derive",
"num-traits",
"pipewire",
"rustix 1.0.5",
"secure-string",
"smithay-client-toolkit",
"thiserror 2.0.12",
"tokio",
"tokio-stream",

View file

@ -93,7 +93,7 @@ pwhash = "1"
[dependencies.cosmic-settings-subscriptions]
git = "https://github.com/pop-os/cosmic-settings-subscriptions"
#TODO: only select features as needed
features = ["accessibility", "network_manager", "pipewire", "pulse", "bluetooth"]
features = ["cosmic_a11y_manager", "accessibility", "network_manager", "pipewire", "pulse", "bluetooth"]
optional = true
[dependencies.icu]

View file

@ -18,7 +18,7 @@ use cosmic_settings_page::{
use slotmap::SlotMap;
use tracing::error;
use super::{AccessibilityEvent, AccessibilityRequest, wayland};
use super::{AccessibilityEvent, AccessibilityRequest, cosmic_a11y_manager as wayland};
#[derive(Debug)]
pub struct Page {

View file

@ -14,13 +14,13 @@ use cosmic_settings_page::{
section::{self, Section},
};
use cosmic_settings_subscriptions::accessibility::{self, DBusRequest, DBusUpdate};
use cosmic_settings_subscriptions::cosmic_a11y_manager;
use num_traits::FromPrimitive;
use slotmap::SlotMap;
pub mod magnifier;
mod wayland;
pub use cosmic_a11y_manager::{AccessibilityEvent, AccessibilityRequest, ColorFilter};
use tokio::sync::mpsc::UnboundedSender;
pub use wayland::{AccessibilityEvent, AccessibilityRequest, ColorFilter};
#[derive(Debug)]
pub struct Page {
@ -32,7 +32,7 @@ pub struct Page {
screen_filter_selections: Vec<String>,
wayland_available: Option<u32>,
wayland_thread: Option<wayland::Sender>,
wayland_thread: Option<cosmic_a11y_manager::Sender>,
theme: Box<cosmic::cosmic_theme::Theme>,
high_contrast: Option<bool>,
daemon_config: CosmicSettingsDaemonConfig,
@ -74,7 +74,7 @@ impl Default for Page {
#[derive(Debug, Clone)]
pub enum Message {
Event(wayland::AccessibilityEvent),
Event(cosmic_a11y_manager::AccessibilityEvent),
ProtocolUnavailable,
Return,
HighContrast(bool),
@ -110,7 +110,7 @@ impl page::Page<crate::pages::Message> for Page {
fn on_enter(&mut self) -> cosmic::Task<crate::pages::Message> {
if self.wayland_thread.is_none() {
match wayland::spawn_wayland_connection() {
match cosmic_a11y_manager::spawn_wayland_connection() {
Ok((tx, mut rx)) => {
self.wayland_thread = Some(tx);
@ -385,7 +385,7 @@ impl Page {
if let Some(sender) = self.wayland_thread.as_ref() {
let _ = sender.send(AccessibilityRequest::ScreenFilter {
inverted,
filter: Some(wayland::ColorFilter::Unknown),
filter: Some(cosmic_a11y_manager::ColorFilter::Unknown),
});
}
}

View file

@ -1,241 +0,0 @@
use cosmic_protocols::a11y::v1::client::cosmic_a11y_manager_v1;
use num_derive::{FromPrimitive, ToPrimitive};
use sctk::{
reexports::{
calloop::{self, LoopSignal, channel},
calloop_wayland_source::WaylandSource,
client::{
ConnectError, Connection, Dispatch, Proxy, WEnum,
globals::{GlobalListContents, registry_queue_init},
protocol::wl_registry,
},
},
registry::RegistryState,
};
use tokio::sync::mpsc;
#[derive(Debug, Clone, Copy)]
pub enum AccessibilityEvent {
Bound(u32),
Magnifier(bool),
ScreenFilter {
inverted: bool,
filter: Option<ColorFilter>,
},
Closed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum ColorFilter {
Greyscale,
Deuteranopia,
Protanopia,
Tritanopia,
Unknown,
}
impl Default for ColorFilter {
fn default() -> Self {
ColorFilter::Unknown
}
}
#[derive(Debug, Clone, Copy)]
pub enum AccessibilityRequest {
Magnifier(bool),
ScreenFilter {
inverted: bool,
filter: Option<ColorFilter>,
},
}
pub type Sender = calloop::channel::Sender<AccessibilityRequest>;
pub fn spawn_wayland_connection() -> Result<
(
channel::Sender<AccessibilityRequest>,
mpsc::Receiver<AccessibilityEvent>,
),
ConnectError,
> {
let (event_tx, event_rx) = mpsc::channel(10);
let (request_tx, request_rx) = channel::channel();
let conn = Connection::connect_to_env()?;
std::thread::spawn(move || {
if let Err(err) = wayland_thread(conn, event_tx.clone(), request_rx) {
tracing::warn!("Accessibility protocol wayland thread crashed: {}", err);
let _ = event_tx.blocking_send(AccessibilityEvent::Closed);
}
});
Ok((request_tx, event_rx))
}
fn wayland_thread(
conn: Connection,
tx: mpsc::Sender<AccessibilityEvent>,
rx: channel::Channel<AccessibilityRequest>,
) -> anyhow::Result<()> {
struct State {
loop_signal: LoopSignal,
tx: mpsc::Sender<AccessibilityEvent>,
global: cosmic_a11y_manager_v1::CosmicA11yManagerV1,
magnifier: bool,
screen_inverted: bool,
screen_filter: Option<ColorFilter>,
}
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 state
.tx
.blocking_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 = match filter {
WEnum::Value(cosmic_a11y_manager_v1::Filter::Disabled) => None,
WEnum::Value(cosmic_a11y_manager_v1::Filter::Greyscale) => {
Some(ColorFilter::Greyscale)
}
WEnum::Value(cosmic_a11y_manager_v1::Filter::DaltonizeProtanopia) => {
Some(ColorFilter::Protanopia)
}
WEnum::Value(cosmic_a11y_manager_v1::Filter::DaltonizeDeuteranopia) => {
Some(ColorFilter::Deuteranopia)
}
WEnum::Value(cosmic_a11y_manager_v1::Filter::DaltonizeTritanopia) => {
Some(ColorFilter::Tritanopia)
}
WEnum::Value(_) | WEnum::Unknown(_) => Some(ColorFilter::Unknown),
};
if inverted != state.screen_inverted || filter != state.screen_filter {
if state
.tx
.blocking_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())
.map_err(|err| err.error)?;
let registry_state = RegistryState::new(&globals);
let Ok(global) = registry_state.bind_one::<cosmic_a11y_manager_v1::CosmicA11yManagerV1, _, _>(
&qhandle,
1..=2,
(),
) else {
return Ok(());
};
let _ = tx.blocking_send(AccessibilityEvent::Bound(global.version()));
loop_handle
.insert_source(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
},
match filter {
None => cosmic_a11y_manager_v1::Filter::Disabled,
Some(ColorFilter::Greyscale) => cosmic_a11y_manager_v1::Filter::Greyscale,
Some(ColorFilter::Protanopia) => {
cosmic_a11y_manager_v1::Filter::DaltonizeProtanopia
}
Some(ColorFilter::Deuteranopia) => {
cosmic_a11y_manager_v1::Filter::DaltonizeDeuteranopia
}
Some(ColorFilter::Tritanopia) => {
cosmic_a11y_manager_v1::Filter::DaltonizeTritanopia
}
Some(ColorFilter::Unknown) => cosmic_a11y_manager_v1::Filter::Unknown,
},
);
}
channel::Event::Closed => {
state.loop_signal.stop();
state.loop_signal.wakeup();
}
})
.map_err(|err| err.error)?;
let mut state = State {
loop_signal: event_loop.get_signal(),
tx,
global,
magnifier: false,
screen_inverted: false,
screen_filter: None,
};
event_loop.run(None, &mut state, |_| {})?;
Ok(())
}