//! Keybind-related code use crate::{config::KEYBINDS, conn_info::Connection, debug, error::Result, WM}; use xcb::x::{ GrabKey, GrabMode, KeyPressEvent, Mapping, MappingNotifyEvent, ModMask, UngrabKey, GRAB_ANY, }; use xkeysym::Keysym; impl WM<'_> { /// Dispatch the given keypress event according to [`self::KEYBINDS`] pub fn handle_key_press(&mut self, e: &KeyPressEvent) { let Some(sym) = self .conn .keyboard_state .keycode_to_keysym(e.detail().into(), 0) else { return; // probably not bound }; KEYBINDS.dispatch( self, sym, ModMask::from_bits_truncate(e.state().bits()) .difference(self.conn.keyboard_state.numlock_mask() | ModMask::LOCK), ); } /// Update our keyboard info when the mapping changes. pub fn handle_mapping_notify(&mut self, e: &MappingNotifyEvent) -> Result<()> { if e.request() == Mapping::Keyboard { grab(&mut self.conn)?; } Ok(()) } } /// Refresh our keyboard info, and ensure that we get events for bound keys. pub fn grab(conn: &mut Connection<'_>) -> Result<()> { // Refresh keyboard state conn.refresh_keyboard_info()?; // Ungrab all keys conn.send_request(&UngrabKey { key: GRAB_ANY, grab_window: conn.root(), modifiers: ModMask::ANY, }); // Bind all of the keycodes which have keysyms we see in our binds. for (keycode, keysym) in conn.keyboard_state.iter_keycodes_keysyms() { for bind in KEYBINDS.binds() { if bind.key == keysym { // grab key with any combination of modifiers for modmask in [ ModMask::empty(), ModMask::LOCK, conn.keyboard_state.numlock_mask(), conn.keyboard_state.numlock_mask() | ModMask::LOCK, ] { conn.send_request(&GrabKey { grab_window: conn.root(), #[allow(clippy::cast_possible_truncation)] key: keycode.raw() as u8, modifiers: bind.modifiers | modmask, owner_events: true, pointer_mode: GrabMode::Async, keyboard_mode: GrabMode::Async, }); } } } } // Ensure all requests succeeded Ok(()) } /// A key bound to some action pub struct Keybind { pub modifiers: ModMask, pub key: Keysym, pub action: &'static dyn Fn(&mut WM<'_>), } /// A set of keybinds. Currently, there is only one instance of this defined statically: [`crate::config::KEYBINDS`]. pub struct Keybinds(pub &'static [Keybind]); impl Keybinds { /// Get an iterator over all bound keys pub fn binds(&self) -> impl Iterator { self.0.iter() } /// Attempt to run the action for the matching keybind, if it exists. pub fn dispatch(&self, wm: &mut WM<'_>, key: Keysym, modifiers: ModMask) { debug!("received {key:?}"); if let Some(bind) = self .binds() .find(|b| b.key == key && modifiers.contains(b.modifiers)) { debug!("found action"); (bind.action)(wm); } } } impl std::fmt::Debug for Keybind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Keybind") .field("modifiers", &self.modifiers) .field("key", &self.key) .finish_non_exhaustive() } }