From c45a58c16cbb9da8b6d12a3320339e72e3dc1635 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 17 Feb 2025 20:30:26 +0100 Subject: [PATCH] wayland: Implement cosmic-a11y-v1 --- Cargo.lock | 14 ++-- Cargo.toml | 8 +- src/shell/mod.rs | 8 ++ src/state.rs | 33 ++++---- src/wayland/handlers/a11y.rs | 38 +++++++++ src/wayland/handlers/mod.rs | 1 + src/wayland/protocols/a11y.rs | 142 ++++++++++++++++++++++++++++++++++ src/wayland/protocols/mod.rs | 1 + 8 files changed, 223 insertions(+), 22 deletions(-) create mode 100644 src/wayland/handlers/a11y.rs create mode 100644 src/wayland/protocols/a11y.rs diff --git a/Cargo.lock b/Cargo.lock index 870ba4f3..bc54c29b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -786,7 +786,7 @@ dependencies = [ [[package]] name = "cosmic-client-toolkit" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=178eb0b#178eb0b14a0e5c192f64f6dee6c40341a8e5ee51" +source = "git+https://github.com/pop-os//cosmic-protocols?rev=ed2a481#ed2a48143cd6dc63274aa04461de3d341e50b16b" dependencies = [ "cosmic-protocols", "libc", @@ -906,7 +906,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=178eb0b#178eb0b14a0e5c192f64f6dee6c40341a8e5ee51" +source = "git+https://github.com/pop-os//cosmic-protocols?rev=ed2a481#ed2a48143cd6dc63274aa04461de3d341e50b16b" dependencies = [ "bitflags 2.7.0", "wayland-backend", @@ -1466,7 +1466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2911,7 +2911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -4385,7 +4385,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4985,7 +4985,7 @@ dependencies = [ "getrandom", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5278,7 +5278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f" dependencies = [ "cc", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index af3093e6..73a70879 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ bytemuck = "1.12" calloop = {version = "0.14.1", features = ["executor"]} cosmic-comp-config = {path = "cosmic-comp-config"} cosmic-config = {git = "https://github.com/pop-os/libcosmic/", features = ["calloop", "macro"]} -cosmic-protocols = {git = "https://github.com/pop-os/cosmic-protocols", rev = "178eb0b", default-features = false, features = ["server"]} +cosmic-protocols = {git = "https://github.com/pop-os/cosmic-protocols", rev = "ed2a481", default-features = false, features = ["server"]} cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" } libdisplay-info = "0.2.0" egui = {version = "0.30.0", optional = true} @@ -115,4 +115,8 @@ inherits = "release" lto = "fat" [patch.crates-io] -smithay = { git = "https://github.com/smithay/smithay.git", rev = "f93476c" } \ No newline at end of file +smithay = { git = "https://github.com/smithay/smithay.git", rev = "f93476c" } + +[patch."https://github.com/pop-os/cosmic-protocols"] +cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", rev = "ed2a481" } +cosmic-client-toolkit = { git = "https://github.com/pop-os//cosmic-protocols", rev = "ed2a481" } \ No newline at end of file diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 96c2bee4..b4f6562b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -2034,6 +2034,14 @@ impl Shell { 1. }; + let toggled = previous_level != level && (previous_level == 1.0 || level == 1.0); + if toggled { + let value = previous_level == 1.0; + let _ = loop_handle.insert_idle(move |state| { + state.common.a11y_state.set_screen_magnifier(value); + }); + } + self.zoom_state = Some(ZoomState { seat: seat.clone(), level, diff --git a/src/state.rs b/src/state.rs index 5cb519de..92de99b9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,18 +11,21 @@ use crate::{ input::{gestures::GestureState, PointerFocusState}, shell::{grabs::SeatMoveGrabState, CosmicSurface, SeatExt, Shell}, utils::prelude::OutputExt, - wayland::handlers::screencopy::SessionHolder, - wayland::protocols::{ - atspi::AtspiState, - drm::WlDrmState, - image_source::ImageSourceState, - output_configuration::OutputConfigurationState, - output_power::OutputPowerState, - overlap_notify::OverlapNotifyState, - screencopy::ScreencopyState, - toplevel_info::ToplevelInfoState, - toplevel_management::{ManagementCapabilities, ToplevelManagementState}, - workspace::{WorkspaceClientState, WorkspaceState, WorkspaceUpdateGuard}, + wayland::{ + handlers::screencopy::SessionHolder, + protocols::{ + a11y::A11yState, + atspi::AtspiState, + drm::WlDrmState, + image_source::ImageSourceState, + output_configuration::OutputConfigurationState, + output_power::OutputPowerState, + overlap_notify::OverlapNotifyState, + screencopy::ScreencopyState, + toplevel_info::ToplevelInfoState, + toplevel_management::{ManagementCapabilities, ToplevelManagementState}, + workspace::{WorkspaceClientState, WorkspaceState, WorkspaceUpdateGuard}, + }, }, xwayland::XWaylandState, }; @@ -223,6 +226,7 @@ pub struct Common { pub kde_decoration_state: KdeDecorationState, pub xdg_decoration_state: XdgDecorationState, pub overlap_notify_state: OverlapNotifyState, + pub a11y_state: A11yState, // shell-related wayland state pub xdg_shell_state: XdgShellState, @@ -581,8 +585,10 @@ impl State { tracing::warn!(?err, "Failed to initialize dbus handlers"); } + let a11y_state = A11yState::new::(dh, client_is_privileged); + // TODO: Restrict to only specific client? - let atspi_state = AtspiState::new::(dh, client_is_privileged); + let atspi_state = AtspiState::new::(dh, |_| true); State { common: Common { @@ -638,6 +644,7 @@ impl State { xdg_activation_state, xdg_foreign_state, workspace_state, + a11y_state, xwayland_scale: None, xwayland_state: None, xwayland_shell_state, diff --git a/src/wayland/handlers/a11y.rs b/src/wayland/handlers/a11y.rs new file mode 100644 index 00000000..bb97d015 --- /dev/null +++ b/src/wayland/handlers/a11y.rs @@ -0,0 +1,38 @@ +use crate::{ + state::State, + wayland::protocols::a11y::{delegate_a11y, A11yHandler, A11yState}, +}; + +impl A11yHandler for State { + fn a11y_state(&mut self) -> &mut A11yState { + &mut self.common.a11y_state + } + + fn request_screen_magnifier(&mut self, enabled: bool) { + let mut shell = self.common.shell.write().unwrap(); + + if shell + .zoom_state() + .is_some_and(|state| state.current_level() != 1.0) + != enabled + { + let seat = shell.seats.last_active().clone(); + let level = if enabled { + self.common.config.dynamic_conf.zoom_state().last_level + } else { + 1.0 + }; + let zoom_config = &self.common.config.cosmic_conf.accessibility_zoom; + + shell.trigger_zoom( + &seat, + level, + zoom_config, + true, + &self.common.event_loop_handle, + ); + } + } +} + +delegate_a11y!(State); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index f0233b61..62ec7eed 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only +pub mod a11y; pub mod alpha_modifier; pub mod atspi; pub mod buffer; diff --git a/src/wayland/protocols/a11y.rs b/src/wayland/protocols/a11y.rs new file mode 100644 index 00000000..cce1394a --- /dev/null +++ b/src/wayland/protocols/a11y.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_protocols::a11y::v1::server::cosmic_a11y_manager_v1; +use smithay::reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, +}; +use wayland_backend::server::GlobalId; + +pub trait A11yHandler { + fn a11y_state(&mut self) -> &mut A11yState; + + fn request_screen_magnifier(&mut self, enabled: bool); +} + +#[derive(Debug)] +pub struct A11yState { + global: GlobalId, + instances: Vec, + + magnifier_state: bool, +} + +impl A11yState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> A11yState + where + D: GlobalDispatch + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + A11yGlobalData { + filter: Box::new(client_filter), + }, + ); + A11yState { + global, + instances: Vec::new(), + + magnifier_state: false, + } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } + + pub fn set_screen_magnifier(&mut self, enabled: bool) { + self.magnifier_state = enabled; + + for instance in &self.instances { + instance.magnifier(if enabled { + cosmic_a11y_manager_v1::ActiveState::Enabled + } else { + cosmic_a11y_manager_v1::ActiveState::Disabled + }); + } + } +} + +pub struct A11yGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +impl GlobalDispatch for A11yState +where + D: GlobalDispatch + + Dispatch + + A11yHandler + + 'static, +{ + fn bind( + state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &A11yGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let instance = data_init.init(resource, ()); + let state = state.a11y_state(); + + instance.magnifier(if state.magnifier_state { + cosmic_a11y_manager_v1::ActiveState::Enabled + } else { + cosmic_a11y_manager_v1::ActiveState::Disabled + }); + + state.instances.push(instance); + } + + fn can_view(client: Client, global_data: &A11yGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for A11yState +where + D: Dispatch + A11yHandler + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _resource: &cosmic_a11y_manager_v1::CosmicA11yManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + cosmic_a11y_manager_v1::Request::SetMagnifier { active } => { + state.request_screen_magnifier( + active + .into_result() + .unwrap_or(cosmic_a11y_manager_v1::ActiveState::Disabled) + == cosmic_a11y_manager_v1::ActiveState::Enabled, + ); + } + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: &cosmic_a11y_manager_v1::CosmicA11yManagerV1, + _data: &(), + ) { + state.a11y_state().instances.retain(|i| i != resource); + } +} + +macro_rules! delegate_a11y { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::a11y::v1::server::cosmic_a11y_manager_v1::CosmicA11yManagerV1: $crate::wayland::protocols::a11y::A11yGlobalData + ] => $crate::wayland::protocols::a11y::A11yState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::a11y::v1::server::cosmic_a11y_manager_v1::CosmicA11yManagerV1: () + ] => $crate::wayland::protocols::a11y::A11yState); + }; +} +pub(crate) use delegate_a11y; diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 3fe5285d..a04c8d64 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only +pub mod a11y; pub mod atspi; pub mod drm; pub mod image_source;