chore(accessibility): use settings subscription for color filter
This commit is contained in:
parent
cc2e4f77a0
commit
be6b799b51
5 changed files with 13 additions and 250 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -1792,19 +1792,23 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-settings-subscriptions"
|
name = "cosmic-settings-subscriptions"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"bluez-zbus",
|
"bluez-zbus",
|
||||||
"cosmic-dbus-a11y",
|
"cosmic-dbus-a11y",
|
||||||
"cosmic-dbus-networkmanager",
|
"cosmic-dbus-networkmanager",
|
||||||
|
"cosmic-protocols",
|
||||||
"futures",
|
"futures",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"libpulse-binding",
|
"libpulse-binding",
|
||||||
"log",
|
"log",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
"pipewire",
|
"pipewire",
|
||||||
"rustix 1.0.5",
|
"rustix 1.0.5",
|
||||||
"secure-string",
|
"secure-string",
|
||||||
|
"smithay-client-toolkit",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ pwhash = "1"
|
||||||
[dependencies.cosmic-settings-subscriptions]
|
[dependencies.cosmic-settings-subscriptions]
|
||||||
git = "https://github.com/pop-os/cosmic-settings-subscriptions"
|
git = "https://github.com/pop-os/cosmic-settings-subscriptions"
|
||||||
#TODO: only select features as needed
|
#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
|
optional = true
|
||||||
|
|
||||||
[dependencies.icu]
|
[dependencies.icu]
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use cosmic_settings_page::{
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use super::{AccessibilityEvent, AccessibilityRequest, wayland};
|
use super::{AccessibilityEvent, AccessibilityRequest, cosmic_a11y_manager as wayland};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ use cosmic_settings_page::{
|
||||||
section::{self, Section},
|
section::{self, Section},
|
||||||
};
|
};
|
||||||
use cosmic_settings_subscriptions::accessibility::{self, DBusRequest, DBusUpdate};
|
use cosmic_settings_subscriptions::accessibility::{self, DBusRequest, DBusUpdate};
|
||||||
|
use cosmic_settings_subscriptions::cosmic_a11y_manager;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
|
|
||||||
pub mod magnifier;
|
pub mod magnifier;
|
||||||
mod wayland;
|
pub use cosmic_a11y_manager::{AccessibilityEvent, AccessibilityRequest, ColorFilter};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
pub use wayland::{AccessibilityEvent, AccessibilityRequest, ColorFilter};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
|
|
@ -32,7 +32,7 @@ pub struct Page {
|
||||||
screen_filter_selections: Vec<String>,
|
screen_filter_selections: Vec<String>,
|
||||||
|
|
||||||
wayland_available: Option<u32>,
|
wayland_available: Option<u32>,
|
||||||
wayland_thread: Option<wayland::Sender>,
|
wayland_thread: Option<cosmic_a11y_manager::Sender>,
|
||||||
theme: Box<cosmic::cosmic_theme::Theme>,
|
theme: Box<cosmic::cosmic_theme::Theme>,
|
||||||
high_contrast: Option<bool>,
|
high_contrast: Option<bool>,
|
||||||
daemon_config: CosmicSettingsDaemonConfig,
|
daemon_config: CosmicSettingsDaemonConfig,
|
||||||
|
|
@ -74,7 +74,7 @@ impl Default for Page {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Event(wayland::AccessibilityEvent),
|
Event(cosmic_a11y_manager::AccessibilityEvent),
|
||||||
ProtocolUnavailable,
|
ProtocolUnavailable,
|
||||||
Return,
|
Return,
|
||||||
HighContrast(bool),
|
HighContrast(bool),
|
||||||
|
|
@ -110,7 +110,7 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
|
|
||||||
fn on_enter(&mut self) -> cosmic::Task<crate::pages::Message> {
|
fn on_enter(&mut self) -> cosmic::Task<crate::pages::Message> {
|
||||||
if self.wayland_thread.is_none() {
|
if self.wayland_thread.is_none() {
|
||||||
match wayland::spawn_wayland_connection() {
|
match cosmic_a11y_manager::spawn_wayland_connection() {
|
||||||
Ok((tx, mut rx)) => {
|
Ok((tx, mut rx)) => {
|
||||||
self.wayland_thread = Some(tx);
|
self.wayland_thread = Some(tx);
|
||||||
|
|
||||||
|
|
@ -385,7 +385,7 @@ impl Page {
|
||||||
if let Some(sender) = self.wayland_thread.as_ref() {
|
if let Some(sender) = self.wayland_thread.as_ref() {
|
||||||
let _ = sender.send(AccessibilityRequest::ScreenFilter {
|
let _ = sender.send(AccessibilityRequest::ScreenFilter {
|
||||||
inverted,
|
inverted,
|
||||||
filter: Some(wayland::ColorFilter::Unknown),
|
filter: Some(cosmic_a11y_manager::ColorFilter::Unknown),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue