diff --git a/cosmic-applet-a11y/i18n/en/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/en/cosmic_applet_a11y.ftl index c386db3e..f9db0dd6 100644 --- a/cosmic-applet-a11y/i18n/en/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/en/cosmic_applet_a11y.ftl @@ -1,3 +1,4 @@ screen-reader = Screen reader magnifier = Magnifier +invert-colors = Invert colors settings = Accessibility settings... \ No newline at end of file diff --git a/cosmic-applet-a11y/src/app.rs b/cosmic-applet-a11y/src/app.rs index 6a86afde..826faa5e 100644 --- a/cosmic-applet-a11y/src/app.rs +++ b/cosmic-applet-a11y/src/app.rs @@ -21,17 +21,17 @@ use cosmic::{ platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}, window, Length, Subscription, }, - iced_runtime::core::layout::Limits, - iced_widget::column, surface, theme, - widget::{divider, text}, + widget::{divider, text, Column}, Element, Task, }; +use cosmic_protocols::a11y::v1::client::cosmic_a11y_manager_v1::Filter; use cosmic_time::{anim, chain, id, once_cell::sync::Lazy, Instant, Timeline}; use tokio::sync::mpsc::UnboundedSender; static READER_TOGGLE: Lazy = Lazy::new(id::Toggler::unique); static MAGNIFIER_TOGGLE: Lazy = Lazy::new(id::Toggler::unique); +static INVERT_COLORS_TOGGLE: Lazy = Lazy::new(id::Toggler::unique); pub fn run() -> cosmic::iced::Result { cosmic::applet::run::(()) @@ -42,9 +42,11 @@ struct CosmicA11yApplet { core: cosmic::app::Core, reader_enabled: bool, magnifier_enabled: bool, + inverted_colors_enabled: bool, popup: Option, dbus_sender: Option>, wayland_sender: Option>, + wayland_protocol_version: Option, timeline: Timeline, token_tx: Option>, } @@ -55,6 +57,7 @@ enum Message { CloseRequested(window::Id), ScreenReaderEnabled(chain::Toggler, bool), MagnifierEnabled(chain::Toggler, bool), + InvertedColorsEnabled(chain::Toggler, bool), Frame(Instant), Token(TokenUpdate), OpenSettings, @@ -110,6 +113,18 @@ impl cosmic::Application for CosmicA11yApplet { self.magnifier_enabled = false; } } + Message::InvertedColorsEnabled(chain, enabled) => { + if let Some(tx) = &self.wayland_sender { + self.timeline.set_chain(chain).start(); + self.inverted_colors_enabled = enabled; + let _ = tx.send(AccessibilityRequest::ScreenFilter { + inverted: enabled, + filter: Filter::Unknown, + }); + } else { + self.inverted_colors_enabled = false; + } + } Message::TogglePopup => { if let Some(p) = self.popup.take() { return destroy_popup(p); @@ -119,7 +134,7 @@ impl cosmic::Application for CosmicA11yApplet { let new_id = window::Id::unique(); self.popup.replace(new_id); - let mut popup_settings = self.core.applet.get_popup_settings( + let popup_settings = self.core.applet.get_popup_settings( self.core.main_window_id().unwrap(), new_id, Some((1, 1)), @@ -181,11 +196,19 @@ impl cosmic::Application for CosmicA11yApplet { WaylandUpdate::Errored => { tracing::error!("Wayland error"); let _ = self.wayland_sender.take(); + self.wayland_protocol_version = None; self.magnifier_enabled = false; + self.inverted_colors_enabled = false; + } + WaylandUpdate::State(AccessibilityEvent::Bound(ver)) => { + self.wayland_protocol_version = Some(ver); } WaylandUpdate::State(AccessibilityEvent::Magnifier(enabled)) => { self.magnifier_enabled = enabled; } + WaylandUpdate::State(AccessibilityEvent::ScreenFilter { inverted, .. }) => { + self.inverted_colors_enabled = inverted; + } WaylandUpdate::Started(tx) => { self.wayland_sender = Some(tx); } @@ -234,14 +257,34 @@ impl cosmic::Application for CosmicA11yApplet { .text_size(14) .width(Length::Fill), ); + let invert_colors_toggle = padded_control( + anim!( + INVERT_COLORS_TOGGLE, + &self.timeline, + fl!("invert-colors"), + self.inverted_colors_enabled, + Message::InvertedColorsEnabled, + ) + .text_size(14) + .width(Length::Fill), + ); + + let content_list = Column::with_capacity(5) + .push(reader_toggle) + .push_maybe( + self.wayland_protocol_version + .is_some() + .then_some(magnifier_toggle), + ) + .push_maybe( + self.wayland_protocol_version + .is_some_and(|ver| ver >= 2) + .then_some(invert_colors_toggle), + ) + .push(padded_control(divider::horizontal::default()).padding([space_xxs, space_s])) + .push(menu_button(text::body(fl!("settings"))).on_press(Message::OpenSettings)) + .padding([8, 0]); - let content_list = column![ - reader_toggle, - magnifier_toggle, - padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), - menu_button(text::body(fl!("settings"))).on_press(Message::OpenSettings) - ] - .padding([8, 0]); self.core.applet.popup_container(content_list).into() } diff --git a/cosmic-applet-a11y/src/backend/wayland/mod.rs b/cosmic-applet-a11y/src/backend/wayland/mod.rs index 0981b112..c27afae0 100644 --- a/cosmic-applet-a11y/src/backend/wayland/mod.rs +++ b/cosmic-applet-a11y/src/backend/wayland/mod.rs @@ -8,6 +8,7 @@ use cosmic::iced::{ futures::{self, channel::mpsc, SinkExt, StreamExt}, stream, Subscription, }; +use cosmic_protocols::a11y::v1::client::cosmic_a11y_manager_v1::Filter; use once_cell::sync::Lazy; use tokio::sync::Mutex; @@ -25,12 +26,15 @@ pub enum WaylandUpdate { #[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 { diff --git a/cosmic-applet-a11y/src/backend/wayland/thread.rs b/cosmic-applet-a11y/src/backend/wayland/thread.rs index 85d1e4a1..9427c4ee 100644 --- a/cosmic-applet-a11y/src/backend/wayland/thread.rs +++ b/cosmic-applet-a11y/src/backend/wayland/thread.rs @@ -14,14 +14,14 @@ use cctk::{ 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; +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( - tx: mpsc::Sender, + mut tx: mpsc::Sender, ) -> anyhow::Result> { let (a11y_tx, a11y_rx) = calloop::channel::sync_channel(100); let conn = Connection::connect_to_env()?; @@ -33,6 +33,8 @@ pub fn spawn_a11y( global: cosmic_a11y_manager_v1::CosmicA11yManagerV1, magnifier: bool, + screen_inverted: bool, + screen_filter: Filter, } impl Dispatch for State { @@ -60,6 +62,28 @@ pub fn spawn_a11y( 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!(), } } @@ -89,9 +113,11 @@ pub fn spawn_a11y( let registry_state = RegistryState::new(&globals); let global = registry_state - .bind_one::(&qhandle, 1..=1, ()) + .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)) => { @@ -101,6 +127,16 @@ pub fn spawn_a11y( 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(); @@ -114,6 +150,8 @@ pub fn spawn_a11y( global, magnifier: false, + screen_inverted: false, + screen_filter: Filter::Unknown, }; event_loop.run(None, &mut state, |_| {}).unwrap();