Skip to content

Commit 6290928

Browse files
committed
WIP org.freedesktop.a11y.Manager dbus protocol
1 parent a57f4a8 commit 6290928

File tree

6 files changed

+318
-0
lines changed

6 files changed

+318
-0
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ rand = "0.9.0"
6464
reis = { version = "0.4", features = ["calloop"] }
6565
# CLI arguments
6666
clap_lex = "0.7"
67+
futures-executor = "0.3.31"
6768

6869
[dependencies.id_tree]
6970
branch = "feature/copy_clone"

src/dbus/a11y_keyboard_monitor.rs

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
// https://gitlab.gnome.org/mwcampbell/mutter/-/blob/newton/data/dbus-interfaces/org.freedesktop.a11y.xml
2+
//
3+
// TODO: Restrict protocol acccess?
4+
5+
use futures_executor::ThreadPool;
6+
use smithay::backend::input::KeyState;
7+
use std::collections::HashMap;
8+
use std::collections::HashSet;
9+
use std::sync::{Arc, Mutex};
10+
use std::{error::Error, future::pending};
11+
use xkbcommon::xkb::{self, Keysym};
12+
use zbus::message::Header;
13+
use zbus::names::UniqueName;
14+
use zbus::SignalContext;
15+
use std::sync::OnceLock;
16+
17+
// As defined in at-spi2-core
18+
const ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START: u32 = 15;
19+
20+
#[derive(Debug, Default)]
21+
struct Client {
22+
grabbed: bool,
23+
watched: bool,
24+
virtual_modifiers: Vec<Keysym>,
25+
keystrokes: Vec<(Keysym, Modifiers)>,
26+
}
27+
28+
#[derive(Debug, Default)]
29+
struct Clients(HashMap<UniqueName<'static>, Client>);
30+
31+
impl Clients {
32+
fn get(&mut self, name: &UniqueName<'_>) -> &mut Client {
33+
self.0.entry(name.to_owned()).or_default()
34+
}
35+
36+
fn remove(&mut self, name: &UniqueName<'_>) -> bool {
37+
self.0.remove(&name.to_owned()).is_some()
38+
}
39+
}
40+
41+
#[derive(Debug)]
42+
pub struct A11yKeyboardMonitorState {
43+
executor: ThreadPool,
44+
clients: Arc<Mutex<Clients>>,
45+
active_virtual_modifiers: HashSet<Keysym>,
46+
conn: Arc<OnceLock<zbus::Connection>>,
47+
}
48+
49+
impl A11yKeyboardMonitorState {
50+
pub fn new() -> Self {
51+
let clients = Arc::new(Mutex::new(Clients::default()));
52+
let clients_clone = clients.clone();
53+
let executor = ThreadPool::builder().pool_size(1).create().unwrap();
54+
let conn_cell = Arc::new(OnceLock::new());
55+
let conn_cell_clone = conn_cell.clone();
56+
executor.spawn_ok(async move {
57+
match serve(clients_clone).await {
58+
Ok(conn) => {
59+
conn_cell_clone.set(conn).unwrap();
60+
}
61+
Err(err) => {
62+
tracing::error!("Failed to serve `org.freedesktop.a11y.Manager`: {err}");
63+
}
64+
}
65+
});
66+
Self {
67+
clients,
68+
executor,
69+
active_virtual_modifiers: HashSet::new(),
70+
conn: conn_cell,
71+
}
72+
}
73+
74+
pub fn has_virtual_modifier(&self, keysym: Keysym) -> bool {
75+
self.clients
76+
.lock()
77+
.unwrap()
78+
.0
79+
.values()
80+
.any(|client| client.virtual_modifiers.contains(&keysym))
81+
}
82+
83+
pub fn key_event(
84+
&self,
85+
modifiers: &smithay::input::keyboard::ModifiersState,
86+
keysym: &smithay::input::keyboard::KeysymHandle,
87+
state: smithay::backend::input::KeyState,
88+
unichar: char,
89+
) {
90+
let Some(conn) = self.conn.get() else {
91+
return;
92+
};
93+
94+
// Test if any client is watching key input
95+
if !self
96+
.clients
97+
.lock()
98+
.unwrap()
99+
.0
100+
.values()
101+
.any(|client| client.watched)
102+
{
103+
return;
104+
}
105+
106+
let signal_context = SignalContext::new(conn, "/org/freedesktop/a11y/Manager").unwrap();
107+
let released = match state {
108+
KeyState::Pressed => false,
109+
KeyState::Released => true,
110+
};
111+
// XXX keysym and keycode?
112+
// XXX need to add virtual modifiers?
113+
let future = KeyboardMonitor::key_event(
114+
signal_context,
115+
released,
116+
modifiers.serialized.depressed,
117+
keysym.modified_sym().raw(),
118+
unichar as u32,
119+
keysym.raw_code().raw() as u16,
120+
);
121+
self.executor.spawn_ok(async {
122+
future.await;
123+
});
124+
}
125+
}
126+
127+
trait A11yKeyboardMonitorHandler {}
128+
129+
struct KeyboardMonitor {
130+
clients: Arc<Mutex<Clients>>,
131+
//mod_names: Vec<String>,
132+
}
133+
134+
impl KeyboardMonitor {
135+
fn map_mods(&self, virtual_mods: &[Keysym], mods: u32) -> Vec<&str> {
136+
/*
137+
// TODO warn unrecognized modifier
138+
self.mod_names
139+
.iter()
140+
.map(|name| name.as_str())
141+
.enumerate()
142+
.filter(|(i, _)| {
143+
(*i as u32) < ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START && (mods & (1 << i) != 0)
144+
})
145+
.map(|(_, name)| name)
146+
// XXX unwrap
147+
.chain(
148+
virtual_mods
149+
.iter()
150+
.enumerate()
151+
.filter(|(i, _)| {
152+
mods & (1 << (*i as u32 + ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START)) != 0
153+
})
154+
.map(|(_, keysym)| keysym.name().unwrap()),
155+
)
156+
.collect()
157+
*/
158+
Vec::new()
159+
}
160+
}
161+
162+
#[zbus::interface(name = "org.freedesktop.a11y.KeyboardMonitor")]
163+
impl KeyboardMonitor {
164+
fn grab_keyboard(&mut self, #[zbus(header)] header: Header<'_>) {
165+
if let Some(sender) = header.sender() {
166+
let mut clients = self.clients.lock().unwrap();
167+
clients.get(sender).grabbed = true;
168+
eprintln!("grab keyboard by {}", sender);
169+
}
170+
}
171+
172+
fn ungrab_keyboard(&mut self, #[zbus(header)] header: Header<'_>) {
173+
if let Some(sender) = header.sender() {
174+
let mut clients = self.clients.lock().unwrap();
175+
clients.get(sender).grabbed = false;
176+
eprintln!("ungrab keyboard by {}", sender);
177+
}
178+
}
179+
180+
fn watch_keyboard(&mut self, #[zbus(header)] header: Header<'_>) {
181+
if let Some(sender) = header.sender() {
182+
let mut clients = self.clients.lock().unwrap();
183+
clients.get(sender).watched = true;
184+
eprintln!("watch keyboard by {}", sender);
185+
}
186+
}
187+
188+
fn unwatch_keyboard(&mut self, #[zbus(header)] header: Header<'_>) {
189+
if let Some(sender) = header.sender() {
190+
let mut clients = self.clients.lock().unwrap();
191+
clients.get(sender).watched = false;
192+
eprintln!("unwatch keyboard by {}", sender);
193+
}
194+
}
195+
196+
fn set_key_grabs(&self, virtual_modifiers: Vec<u32>, keystrokes: Vec<(u32, u32)>) {
197+
let virtual_modifiers = virtual_modifiers
198+
.into_iter()
199+
.map(Keysym::from)
200+
.collect::<Vec<_>>();
201+
let keystrokes = keystrokes
202+
.into_iter()
203+
.map(|(k, mods)| (Keysym::from(k), self.map_mods(&virtual_modifiers, mods)))
204+
.collect::<Vec<_>>();
205+
dbg!(virtual_modifiers, keystrokes);
206+
}
207+
208+
// TODO signal
209+
#[zbus(signal)]
210+
async fn key_event(
211+
ctx: SignalContext<'_>,
212+
released: bool,
213+
state: u32,
214+
keysym: u32,
215+
unichar: u32,
216+
keycode: u16,
217+
) -> zbus::Result<()>;
218+
}
219+
220+
#[derive(Debug)]
221+
struct Modifiers(u32);
222+
223+
enum Event {
224+
GrabKeyboard,
225+
UngrabKeyboard,
226+
WatchKeyboard,
227+
UnwatchKeyboard,
228+
SetKeyGrabs {
229+
virtual_modifiers: Vec<Keysym>,
230+
keystrokes: Vec<(Keysym, Modifiers)>,
231+
},
232+
}
233+
234+
/*
235+
pub fn spawn() -> calloop::channel::Channel<Event> {
236+
let (sender, channel) = calloop::channel::channel();
237+
238+
// TODO: Use calloop executor when it's confirmed to no longer be buggy
239+
std::thread::spawn(|| {
240+
let res = futures_executor::block_on(async {
241+
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
242+
// XXX
243+
// multiple layout mods?
244+
let keymap = xkb::Keymap::new_from_names(
245+
&context,
246+
"",
247+
"",
248+
"",
249+
"",
250+
None,
251+
xkb::KEYMAP_COMPILE_NO_FLAGS,
252+
)
253+
//xkb::Keymap::new_from_names(&context, "", "pc105", "us", "", None, xkb::KEYMAP_COMPILE_NO_FLAGS)
254+
//xkb::Keymap::new_from_names(&context, "", "pc105", "ru", "", None, xkb::KEYMAP_COMPILE_NO_FLAGS)
255+
//xkb::Keymap::new_from_names(&context, "", "pc105", "es,de,jp", "", None, xkb::KEYMAP_COMPILE_NO_FLAGS)
256+
.unwrap();
257+
let mut mod_names = Vec::new();
258+
for i in 0.. {
259+
let name = keymap.mod_get_name(i);
260+
if name == "" {
261+
break;
262+
}
263+
mod_names.push(name.to_string());
264+
}
265+
dbg!(&mod_names);
266+
267+
let keyboard_monitor = KeyboardMonitor { mod_names };
268+
let _conn = zbus::connection::Builder::session()?
269+
.name("org.freedesktop.a11y.Manager")?
270+
.serve_at("/org/freedesktop/a11y/Manager", keyboard_monitor)?
271+
.build()
272+
.await?;
273+
274+
pending::<zbus::Result<()>>().await
275+
});
276+
if let Err(err) = res {
277+
tracing::error!("Failed to serve `org.freedesktop.a11y.Manager`: {err}");
278+
}
279+
});
280+
281+
channel
282+
}
283+
*/
284+
285+
/*
286+
async fn serve() -> zbus::Result<()> {
287+
let keyboard_monitor = KeyboardMonitor {};
288+
let _conn = zbus::connection::Builder::session()?
289+
.name("org.freedesktop.a11y.Manager")?
290+
.serve_at("/org/freedesktop/a11y/Manager", keyboard_monitor)?
291+
.build()
292+
.await?;
293+
294+
pending::<zbus::Result<()>>().await
295+
}
296+
*/
297+
298+
async fn serve(
299+
clients: Arc<Mutex<Clients>>,
300+
) -> zbus::Result<zbus::Connection> {
301+
let keyboard_monitor = KeyboardMonitor {clients};
302+
zbus::connection::Builder::session()?
303+
.name("org.freedesktop.a11y.Manager")?
304+
.serve_at("/org/freedesktop/a11y/Manager", keyboard_monitor)?
305+
.build()
306+
.await
307+
}

src/dbus/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use calloop::{InsertError, LoopHandle, RegistrationToken};
44
use std::collections::HashMap;
55
use zbus::blocking::{fdo::DBusProxy, Connection};
66

7+
pub mod a11y_keyboard_monitor;
78
mod power;
89

910
pub fn init(evlh: &LoopHandle<'static, State>) -> Result<Vec<RegistrationToken>> {

src/input/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,12 +1557,15 @@ impl State {
15571557
.unwrap_or(false)
15581558
});
15591559

1560+
// TODO update keyboard monitor
15601561
self.common.atspi_ei.input(
15611562
modifiers,
15621563
&handle,
15631564
event.state(),
15641565
event.time() as u64 * 1000,
15651566
);
1567+
let unichar = '\0'; // XXX
1568+
self.common.a11y_keyboard_monitor_state.key_event(modifiers, &handle, event.state(), unichar);
15661569

15671570
// Leave move overview mode, if any modifier was released
15681571
if let Some(Trigger::KeyboardMove(action_modifiers)) =

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,
@@ -233,6 +234,7 @@ pub struct Common {
233234
pub xdg_decoration_state: XdgDecorationState,
234235
pub overlap_notify_state: OverlapNotifyState,
235236
pub a11y_state: A11yState,
237+
pub a11y_keyboard_monitor_state: A11yKeyboardMonitorState,
236238

237239
// shell-related wayland state
238240
pub xdg_shell_state: XdgShellState,
@@ -604,6 +606,8 @@ impl State {
604606

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

609+
let a11y_keyboard_monitor_state = A11yKeyboardMonitorState::new();
610+
607611
// TODO: Restrict to only specific client?
608612
let atspi_state = AtspiState::new::<State, _>(dh, |_| true);
609613

@@ -661,6 +665,7 @@ impl State {
661665
xdg_foreign_state,
662666
workspace_state,
663667
a11y_state,
668+
a11y_keyboard_monitor_state,
664669
xwayland_scale: None,
665670
xwayland_state: None,
666671
xwayland_shell_state,

0 commit comments

Comments
 (0)