Skip to content

Commit f368378

Browse files
committed
WIP org.freedesktop.a11y.Manager dbus protocol
unused code url unichar store set grabs KeyGrab key grabs virtual_mods Fix deadlock fmt
1 parent c590ed8 commit f368378

File tree

4 files changed

+299
-2
lines changed

4 files changed

+299
-2
lines changed

src/dbus/a11y_keyboard_monitor.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
// https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.freedesktop.a11y.xml
2+
//
3+
// TODO: Restrict protocol acccess?
4+
// TODO remove client when not connected to server
5+
6+
use futures_executor::ThreadPool;
7+
use smithay::backend::input::KeyState;
8+
use std::collections::HashMap;
9+
use std::collections::HashSet;
10+
use std::sync::OnceLock;
11+
use std::sync::{Arc, Mutex};
12+
use xkbcommon::xkb::{self, Keysym};
13+
use zbus::message::Header;
14+
use zbus::names::UniqueName;
15+
use zbus::object_server::SignalEmitter;
16+
17+
// As defined in at-spi2-core
18+
const ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START: u32 = 15;
19+
20+
#[derive(PartialEq, Eq, Debug)]
21+
struct KeyGrab {
22+
pub mods: u32,
23+
pub virtual_mods: HashSet<Keysym>,
24+
pub key: Keysym,
25+
}
26+
27+
impl KeyGrab {
28+
fn new(virtual_mods: &[Keysym], key: Keysym, raw_mods: u32) -> Self {
29+
let mods = raw_mods & ((1 << ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START) - 1);
30+
let virtual_mods = virtual_mods
31+
.iter()
32+
.copied()
33+
.enumerate()
34+
.filter(|(i, _)| {
35+
raw_mods & (1 << (ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START + *i as u32)) != 0
36+
})
37+
.map(|(_, x)| x)
38+
.collect();
39+
Self {
40+
mods,
41+
virtual_mods,
42+
key,
43+
}
44+
}
45+
}
46+
47+
#[derive(Debug, Default)]
48+
struct Client {
49+
grabbed: bool,
50+
watched: bool,
51+
virtual_mods: HashSet<Keysym>,
52+
key_grabs: Vec<KeyGrab>,
53+
}
54+
55+
#[derive(Debug, Default)]
56+
struct Clients(HashMap<UniqueName<'static>, Client>);
57+
58+
impl Clients {
59+
fn get(&mut self, name: &UniqueName<'_>) -> &mut Client {
60+
self.0.entry(name.to_owned()).or_default()
61+
}
62+
63+
fn remove(&mut self, name: &UniqueName<'_>) -> bool {
64+
self.0.remove(&name.to_owned()).is_some()
65+
}
66+
}
67+
68+
#[derive(Debug)]
69+
pub struct A11yKeyboardMonitorState {
70+
executor: ThreadPool,
71+
clients: Arc<Mutex<Clients>>,
72+
active_virtual_mods: HashSet<Keysym>,
73+
conn: Arc<OnceLock<zbus::Connection>>,
74+
}
75+
76+
impl A11yKeyboardMonitorState {
77+
pub fn new(executor: &ThreadPool) -> Self {
78+
let clients = Arc::new(Mutex::new(Clients::default()));
79+
let clients_clone = clients.clone();
80+
let conn_cell = Arc::new(OnceLock::new());
81+
let conn_cell_clone = conn_cell.clone();
82+
executor.spawn_ok(async move {
83+
match serve(clients_clone).await {
84+
Ok(conn) => {
85+
conn_cell_clone.set(conn).unwrap();
86+
}
87+
Err(err) => {
88+
tracing::error!("Failed to serve `org.freedesktop.a11y.Manager`: {err}");
89+
}
90+
}
91+
});
92+
Self {
93+
executor: executor.clone(),
94+
clients,
95+
active_virtual_mods: HashSet::new(),
96+
conn: conn_cell,
97+
}
98+
}
99+
100+
pub fn has_virtual_mod(&self, keysym: Keysym) -> bool {
101+
self.clients
102+
.lock()
103+
.unwrap()
104+
.0
105+
.values()
106+
.any(|client| client.virtual_mods.contains(&keysym))
107+
}
108+
109+
pub fn add_active_virtual_mod(&mut self, keysym: Keysym) {
110+
self.active_virtual_mods.insert(keysym);
111+
}
112+
113+
pub fn remove_active_virtual_mod(&mut self, keysym: Keysym) -> bool {
114+
self.active_virtual_mods.remove(&keysym)
115+
}
116+
117+
pub fn has_keyboard_grab(&self) -> bool {
118+
self.clients
119+
.lock()
120+
.unwrap()
121+
.0
122+
.values()
123+
.any(|client| client.grabbed)
124+
}
125+
126+
/// Key grab exists for mods, key, with active virtual mods
127+
pub fn has_key_grab(&self, mods: u32, key: Keysym) -> bool {
128+
self.clients
129+
.lock()
130+
.unwrap()
131+
.0
132+
.values()
133+
.flat_map(|client| &client.key_grabs)
134+
.any(|grab| {
135+
grab.mods == mods
136+
&& grab.virtual_mods == self.active_virtual_mods
137+
&& grab.key == key
138+
})
139+
}
140+
141+
pub fn key_event(
142+
&self,
143+
modifiers: &smithay::input::keyboard::ModifiersState,
144+
keysym: &smithay::input::keyboard::KeysymHandle,
145+
state: smithay::backend::input::KeyState,
146+
) {
147+
let Some(conn) = self.conn.get() else {
148+
return;
149+
};
150+
151+
// Test if any client is watching key input
152+
if !self
153+
.clients
154+
.lock()
155+
.unwrap()
156+
.0
157+
.values()
158+
.any(|client| client.watched)
159+
{
160+
return;
161+
}
162+
163+
let signal_context = SignalEmitter::new(conn, "/org/freedesktop/a11y/Manager").unwrap();
164+
let released = match state {
165+
KeyState::Pressed => false,
166+
KeyState::Released => true,
167+
};
168+
let unichar = {
169+
let xkb = keysym.xkb().lock().unwrap();
170+
unsafe { xkb.state() }.key_get_utf32(keysym.raw_code())
171+
};
172+
let future = KeyboardMonitor::key_event(
173+
signal_context,
174+
released,
175+
modifiers.serialized.layout_effective,
176+
keysym.modified_sym().raw(),
177+
unichar,
178+
keysym.raw_code().raw() as u16,
179+
);
180+
self.executor.spawn_ok(async {
181+
future.await;
182+
});
183+
}
184+
}
185+
186+
struct KeyboardMonitor {
187+
clients: Arc<Mutex<Clients>>,
188+
}
189+
190+
#[zbus::interface(name = "org.freedesktop.a11y.KeyboardMonitor")]
191+
impl KeyboardMonitor {
192+
fn grab_keyboard(&mut self, #[zbus(header)] header: Header<'_>) {
193+
if let Some(sender) = header.sender() {
194+
let mut clients = self.clients.lock().unwrap();
195+
clients.get(sender).grabbed = true;
196+
eprintln!("grab keyboard by {}", sender);
197+
}
198+
}
199+
200+
fn ungrab_keyboard(&mut self, #[zbus(header)] header: Header<'_>) {
201+
if let Some(sender) = header.sender() {
202+
let mut clients = self.clients.lock().unwrap();
203+
clients.get(sender).grabbed = false;
204+
eprintln!("ungrab keyboard by {}", sender);
205+
}
206+
}
207+
208+
fn watch_keyboard(&mut self, #[zbus(header)] header: Header<'_>) {
209+
if let Some(sender) = header.sender() {
210+
let mut clients = self.clients.lock().unwrap();
211+
clients.get(sender).watched = true;
212+
eprintln!("watch keyboard by {}", sender);
213+
}
214+
}
215+
216+
fn unwatch_keyboard(&mut self, #[zbus(header)] header: Header<'_>) {
217+
if let Some(sender) = header.sender() {
218+
let mut clients = self.clients.lock().unwrap();
219+
clients.get(sender).watched = false;
220+
eprintln!("unwatch keyboard by {}", sender);
221+
}
222+
}
223+
224+
fn set_key_grabs(
225+
&self,
226+
#[zbus(header)] header: Header<'_>,
227+
virtual_mods: Vec<u32>,
228+
keystrokes: Vec<(u32, u32)>,
229+
) {
230+
let virtual_mods = virtual_mods
231+
.into_iter()
232+
.map(Keysym::from)
233+
.collect::<Vec<_>>();
234+
let key_grabs = keystrokes
235+
.into_iter()
236+
.map(|(k, mods)| KeyGrab::new(&virtual_mods, Keysym::from(k), mods))
237+
.collect::<Vec<_>>();
238+
239+
if let Some(sender) = header.sender() {
240+
let mut clients = self.clients.lock().unwrap();
241+
let client = clients.get(sender);
242+
eprintln!(
243+
"key grabs set by {}: {:?}",
244+
sender,
245+
(&virtual_mods, &key_grabs)
246+
);
247+
client.virtual_mods = virtual_mods.into_iter().collect::<HashSet<_>>();
248+
client.key_grabs = key_grabs;
249+
}
250+
}
251+
252+
// TODO signal
253+
#[zbus(signal)]
254+
async fn key_event(
255+
ctx: SignalEmitter<'_>,
256+
released: bool,
257+
state: u32,
258+
keysym: u32,
259+
unichar: u32,
260+
keycode: u16,
261+
) -> zbus::Result<()>;
262+
}
263+
264+
async fn serve(clients: Arc<Mutex<Clients>>) -> zbus::Result<zbus::Connection> {
265+
let keyboard_monitor = KeyboardMonitor { clients };
266+
zbus::connection::Builder::session()?
267+
.name("org.freedesktop.a11y.Manager")?
268+
.serve_at("/org/freedesktop/a11y/Manager", keyboard_monitor)?
269+
.build()
270+
.await
271+
}

src/dbus/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use futures_util::stream::StreamExt;
66
use std::collections::HashMap;
77
use zbus::blocking::{fdo::DBusProxy, Connection};
88

9+
pub mod a11y_keyboard_monitor;
910
#[cfg(feature = "systemd")]
1011
pub mod logind;
1112
mod name_owners;

src/input/mod.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,12 +1579,16 @@ impl State {
15791579
|| matches!(f, KeyboardFocusTarget::XWaylandGrab(_))
15801580
});
15811581

1582+
// TODO update keyboard monitor
15821583
self.common.atspi_ei.input(
15831584
modifiers,
15841585
&handle,
15851586
event.state(),
15861587
event.time() as u64 * 1000,
15871588
);
1589+
self.common
1590+
.a11y_keyboard_monitor_state
1591+
.key_event(modifiers, &handle, event.state());
15881592

15891593
// Leave move overview mode, if any modifier was released
15901594
if let Some(Trigger::KeyboardMove(action_modifiers)) =
@@ -1747,11 +1751,15 @@ impl State {
17471751
}
17481752

17491753
if event.state() == KeyState::Released {
1750-
let removed = self
1754+
let mut removed = self
17511755
.common
17521756
.atspi_ei
17531757
.active_virtual_mods
17541758
.remove(&event.key_code());
1759+
removed |= self
1760+
.common
1761+
.a11y_keyboard_monitor_state
1762+
.remove_active_virtual_mod(handle.modified_sym());
17551763
// If `Caps_Lock` is a virtual modifier, and is in locked state, clear it
17561764
if removed && handle.modified_sym() == Keysym::Caps_Lock {
17571765
if (modifiers.serialized.locked & 2) != 0 {
@@ -1777,16 +1785,23 @@ impl State {
17771785
}
17781786
}
17791787
} else if event.state() == KeyState::Pressed
1780-
&& self
1788+
&& (self
17811789
.common
17821790
.atspi_ei
17831791
.virtual_mods
17841792
.contains(&event.key_code())
1793+
|| self
1794+
.common
1795+
.a11y_keyboard_monitor_state
1796+
.has_virtual_mod(handle.modified_sym()))
17851797
{
17861798
self.common
17871799
.atspi_ei
17881800
.active_virtual_mods
17891801
.insert(event.key_code());
1802+
self.common
1803+
.a11y_keyboard_monitor_state
1804+
.add_active_virtual_mod(handle.modified_sym());
17901805

17911806
tracing::debug!(
17921807
"active virtual mods: {:?}",
@@ -1822,10 +1837,15 @@ impl State {
18221837
}
18231838

18241839
if self.common.atspi_ei.has_keyboard_grab()
1840+
|| self.common.a11y_keyboard_monitor_state.has_keyboard_grab()
18251841
|| self
18261842
.common
18271843
.atspi_ei
18281844
.has_key_grab(modifiers.serialized.layout_effective, event.key_code())
1845+
|| self
1846+
.common
1847+
.a11y_keyboard_monitor_state
1848+
.has_key_grab(modifiers.serialized.layout_effective, handle.modified_sym())
18291849
{
18301850
return FilterResult::Intercept(None);
18311851
}

src/state.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
x11::X11State,
99
},
1010
config::{Config, OutputConfig, OutputState, ScreenFilter},
11+
dbus::a11y_keyboard_monitor::A11yKeyboardMonitorState,
1112
input::{gestures::GestureState, PointerFocusState},
1213
shell::{grabs::SeatMoveGrabState, CosmicSurface, SeatExt, Shell},
1314
utils::prelude::OutputExt,
@@ -238,6 +239,7 @@ pub struct Common {
238239
pub xdg_decoration_state: XdgDecorationState,
239240
pub overlap_notify_state: OverlapNotifyState,
240241
pub a11y_state: A11yState,
242+
pub a11y_keyboard_monitor_state: A11yKeyboardMonitorState,
241243

242244
// shell-related wayland state
243245
pub xdg_shell_state: XdgShellState,
@@ -654,6 +656,8 @@ impl State {
654656

655657
let a11y_state = A11yState::new::<State, _>(dh, client_is_privileged);
656658

659+
let a11y_keyboard_monitor_state = A11yKeyboardMonitorState::new(&async_executor);
660+
657661
// TODO: Restrict to only specific client?
658662
let atspi_state = AtspiState::new::<State, _>(dh, |_| true);
659663

@@ -712,6 +716,7 @@ impl State {
712716
xdg_foreign_state,
713717
workspace_state,
714718
a11y_state,
719+
a11y_keyboard_monitor_state,
715720
xwayland_scale: None,
716721
xwayland_state: None,
717722
xwayland_shell_state,

0 commit comments

Comments
 (0)