// https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.freedesktop.a11y.xml use futures_executor::ThreadPool; use smithay::{ backend::input::KeyState, input::keyboard::{KeysymHandle, ModifiersState}, }; use std::{ collections::{HashMap, HashSet}, sync::{Arc, Mutex, OnceLock}, }; use tracing::debug; use xkbcommon::xkb::Keysym; use zbus::{ message::Header, names::{UniqueName, WellKnownName}, object_server::SignalEmitter, }; use super::name_owners::NameOwners; static ALLOWED_NAMES: &'static [WellKnownName] = &[WellKnownName::from_static_str_unchecked( "org.gnome.Orca.KeyboardMonitor", )]; // As defined in at-spi2-core const ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START: u32 = 15; #[derive(PartialEq, Eq, Debug)] struct KeyGrab { pub mods: u32, pub virtual_mods: HashSet, pub key: Keysym, } impl KeyGrab { fn new(virtual_mods: &[Keysym], key: Keysym, raw_mods: u32) -> Self { let mods = raw_mods & ((1 << ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START) - 1); let virtual_mods = virtual_mods .iter() .copied() .enumerate() .filter(|(i, _)| { raw_mods & (1 << (ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START + *i as u32)) != 0 }) .map(|(_, x)| x) .collect(); Self { mods, virtual_mods, key, } } } #[derive(Debug, Default)] struct Client { grabbed: bool, watched: bool, virtual_mods: HashSet, key_grabs: Vec, } #[derive(Debug, Default)] struct Clients(HashMap, Client>); impl Clients { fn get(&mut self, name: &UniqueName<'_>) -> &mut Client { self.0.entry(name.to_owned()).or_default() } } #[derive(Debug)] pub struct A11yKeyboardMonitorState { executor: ThreadPool, clients: Arc>, active_virtual_mods: HashSet, conn: Arc>, name_owners: Arc>, } impl A11yKeyboardMonitorState { pub fn new(executor: &ThreadPool) -> Self { let clients = Arc::new(Mutex::new(Clients::default())); let clients_clone = clients.clone(); let conn_cell = Arc::new(OnceLock::new()); let conn_cell_clone = conn_cell.clone(); let name_owners_cell = Arc::new(OnceLock::new()); let name_owners_cell_clone = name_owners_cell.clone(); let executor_clone = executor.clone(); executor.spawn_ok(async move { match serve(clients_clone, &executor_clone).await { Ok((conn, name_owners)) => { conn_cell_clone.set(conn).unwrap(); name_owners_cell_clone.set(name_owners).unwrap(); } Err(err) => { tracing::error!("Failed to serve `org.freedesktop.a11y.Manager`: {err}"); } } }); Self { executor: executor.clone(), clients, active_virtual_mods: HashSet::new(), conn: conn_cell, name_owners: name_owners_cell, } } pub fn has_virtual_mod(&self, keysym: Keysym) -> bool { self.clients .lock() .unwrap() .0 .values() .any(|client| client.virtual_mods.contains(&keysym)) } pub fn add_active_virtual_mod(&mut self, keysym: Keysym) { self.active_virtual_mods.insert(keysym); } pub fn remove_active_virtual_mod(&mut self, keysym: Keysym) -> bool { self.active_virtual_mods.remove(&keysym) } pub fn active_virtual_mods(&self) -> &HashSet { &self.active_virtual_mods } pub fn has_keyboard_grab(&self) -> bool { self.clients .lock() .unwrap() .0 .values() .any(|client| client.grabbed) } /// Key grab exists for mods, key, with active virtual mods pub fn has_key_grab(&self, modifiers: &ModifiersState, key: Keysym) -> bool { self.clients .lock() .unwrap() .0 .values() .flat_map(|client| &client.key_grabs) .any(|grab| { grab.mods == modifiers.serialized.depressed && grab.virtual_mods == self.active_virtual_mods && grab.key == key }) } pub fn key_event(&self, modifiers: &ModifiersState, keysym: &KeysymHandle, state: KeyState) { let Some(conn) = self.conn.get() else { return; }; let clients = self.clients.lock().unwrap(); for (unique_name, client) in clients.0.iter() { if !client.watched && !self.has_key_grab(modifiers, keysym.modified_sym()) { continue; } let mut signal_context = SignalEmitter::new(conn, "/org/freedesktop/a11y/Manager").unwrap(); // Instead of sending signal to all clients, send only to authorized // clients with registed watches. signal_context = signal_context.set_destination(unique_name.clone().into()); let released = match state { KeyState::Pressed => false, KeyState::Released => true, }; let unichar = { let xkb = keysym.xkb().lock().unwrap(); unsafe { xkb.state() }.key_get_utf32(keysym.raw_code()) }; let future = KeyboardMonitor::key_event( signal_context, released, modifiers.serialized.depressed, keysym.modified_sym().raw(), unichar, keysym.raw_code().raw() as u16, ); self.executor.spawn_ok(async { let _ = future.await; }); } } pub fn refresh(&mut self) { // Remove clients and associated grabs when unique names are no longer // present on bus, or no longer hold approved name on bus. if let Some(name_owners) = self.name_owners.get() { self.clients .lock() .unwrap() .0 .retain(|k, _| name_owners.check_owner_no_poll(k, ALLOWED_NAMES)) } } } struct KeyboardMonitor { clients: Arc>, name_owners: NameOwners, } impl KeyboardMonitor { async fn check_sender_allowed(&self, sender: &UniqueName<'_>) -> zbus::fdo::Result<()> { if self.name_owners.check_owner(sender, ALLOWED_NAMES).await { Ok(()) } else { Err(zbus::fdo::Error::AccessDenied("Access denied".to_string())) } } } #[zbus::interface(name = "org.freedesktop.a11y.KeyboardMonitor")] impl KeyboardMonitor { async fn grab_keyboard(&mut self, #[zbus(header)] header: Header<'_>) -> zbus::fdo::Result<()> { if let Some(sender) = header.sender() { self.check_sender_allowed(sender).await?; let mut clients = self.clients.lock().unwrap(); clients.get(sender).grabbed = true; debug!("grab keyboard by {}", sender); } Ok(()) } async fn ungrab_keyboard( &mut self, #[zbus(header)] header: Header<'_>, ) -> zbus::fdo::Result<()> { if let Some(sender) = header.sender() { self.check_sender_allowed(sender).await?; let mut clients = self.clients.lock().unwrap(); clients.get(sender).grabbed = false; debug!("ungrab keyboard by {}", sender); } Ok(()) } async fn watch_keyboard( &mut self, #[zbus(header)] header: Header<'_>, ) -> zbus::fdo::Result<()> { if let Some(sender) = header.sender() { self.check_sender_allowed(sender).await?; let mut clients = self.clients.lock().unwrap(); clients.get(sender).watched = true; debug!("watch keyboard by {}", sender); } Ok(()) } async fn unwatch_keyboard( &mut self, #[zbus(header)] header: Header<'_>, ) -> zbus::fdo::Result<()> { if let Some(sender) = header.sender() { self.check_sender_allowed(sender).await?; let mut clients = self.clients.lock().unwrap(); clients.get(sender).watched = false; debug!("unwatch keyboard by {}", sender); } Ok(()) } async fn set_key_grabs( &self, #[zbus(header)] header: Header<'_>, virtual_mods: Vec, keystrokes: Vec<(u32, u32)>, ) -> zbus::fdo::Result<()> { let virtual_mods = virtual_mods .into_iter() .map(Keysym::from) .collect::>(); let key_grabs = keystrokes .into_iter() .map(|(k, mods)| KeyGrab::new(&virtual_mods, Keysym::from(k), mods)) .collect::>(); if let Some(sender) = header.sender() { self.check_sender_allowed(sender).await?; let mut clients = self.clients.lock().unwrap(); let client = clients.get(sender); debug!( "key grabs set by {}: {:?}", sender, (&virtual_mods, &key_grabs) ); client.virtual_mods = virtual_mods.into_iter().collect::>(); client.key_grabs = key_grabs; } Ok(()) } #[zbus(signal)] async fn key_event( ctx: SignalEmitter<'_>, released: bool, state: u32, keysym: u32, unichar: u32, keycode: u16, ) -> zbus::Result<()>; } async fn serve( clients: Arc>, executor: &ThreadPool, ) -> zbus::Result<(zbus::Connection, NameOwners)> { let conn = zbus::Connection::session().await?; let name_owners = NameOwners::new(&conn, executor).await?; let keyboard_monitor = KeyboardMonitor { clients, name_owners: name_owners.clone(), }; conn.object_server() .at("/org/freedesktop/a11y/Manager", keyboard_monitor) .await?; conn.request_name("org.freedesktop.a11y.Manager").await?; Ok((conn, name_owners)) }