use std::process::Command; use crate::{error::*, WM}; use xcb::{ x::{ GetKeyboardMapping, GetKeyboardMappingReply, GetModifierMapping, GrabKey, GrabMode, KeyPressEvent, Mapping, MappingNotifyEvent, ModMask, UngrabKey, GRAB_ANY, }, Connection, }; use xkeysym::{KeyCode, Keysym, RawKeyCode}; impl WM<'_> { /// Handles a key press event by dispatching according to [`self::KEYBINDS`] pub(crate) fn handle_key_press(&mut self, e: KeyPressEvent) -> Result<()> { let Some(sym) = self.keyboard_state.keycode_to_keysym(e.detail().into(), 0) else { return Ok(()); // probably not bound }; KEYBINDS.dispatch( self, sym, ModMask::from_bits_truncate(e.state().bits()) .difference(self.keyboard_state.numlock_mask() | ModMask::LOCK), ); Ok(()) } /// Handles a mapping notify event by updating our keyboard setup if needed. pub(crate) fn handle_mapping_notify(&mut self, e: MappingNotifyEvent) -> Result<()> { if e.request() == Mapping::Keyboard { self.grab_keys()?; } Ok(()) } /// Grab all keys specified by [`self::KEYBINDS`], ensuring we get events for them. pub(crate) fn grab_keys(&mut self) -> Result<()> { // Refresh keyboard state self.keyboard_state = KeyboardInfo::new_with(self.conn)?; // Ungrab all keys self.conn.send_request(&UngrabKey { key: GRAB_ANY, grab_window: self.root, modifiers: ModMask::ANY, }); // Bind all of the keycodes which have keysyms we see in our binds. for (keycode, keysym) in self.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, self.keyboard_state.numlock_mask(), self.keyboard_state.numlock_mask() | ModMask::LOCK, ] { self.conn.send_request(&GrabKey { grab_window: self.root, key: keycode.raw() as u8, modifiers: bind.modifiers | modmask, owner_events: true, pointer_mode: GrabMode::Async, keyboard_mode: GrabMode::Async, }); } } } } // Ensure all requests succeeded self.conn.flush()?; Ok(()) } } /// Cached information about our keyboard layout. pub struct KeyboardInfo { min_keycode: RawKeyCode, max_keycode: RawKeyCode, numlock_mask: ModMask, mapping: GetKeyboardMappingReply, } impl KeyboardInfo { /// Query information about the keyboard layout from the given connection. pub fn new_with(conn: &Connection) -> Result { let min_keycode = conn.get_setup().min_keycode(); let max_keycode = conn.get_setup().max_keycode(); let mapping = conn.wait_for_reply(conn.send_request(&GetKeyboardMapping { first_keycode: min_keycode, count: max_keycode - min_keycode + 1, }))?; let mut this = Self { min_keycode: min_keycode as u32, max_keycode: max_keycode as u32, numlock_mask: ModMask::empty(), mapping, }; let numlock_keycode = this.keysym_to_keycode(Keysym::Num_Lock)?; let mod_map = conn.wait_for_reply(conn.send_request(&GetModifierMapping {}))?; let keypermod = mod_map.keycodes().len() / 8; for i in 0..8 { for j in 0..keypermod { if mod_map.keycodes()[i * keypermod + j] as u32 == numlock_keycode.raw() { this.numlock_mask = ModMask::from_bits(1 << i).expect("x11 has unrecognised modifier"); } } } Ok(this) } /// Get the modifier mask being used for numlock, which varies. pub fn numlock_mask(&self) -> ModMask { self.numlock_mask } /// Iterate over all keycodes and their bound keysyms. /// This is likely to contain duplicate pairs. pub(crate) fn iter_keycodes_keysyms(&self) -> impl Iterator + '_ { (0..self.mapping.keysyms_per_keycode()) .flat_map(|shift| { (self.min_keycode..self.max_keycode).map(move |keycode| (shift, keycode)) }) .flat_map(|(shift, keycode)| -> Option<_> { Some(( keycode.into(), self.keycode_to_keysym(keycode.into(), shift)?, )) }) } /// Lookup the first keycode which has the given keysym pub(crate) fn keysym_to_keycode(&self, keysym: Keysym) -> Result { for shift in 0..self.mapping.keysyms_per_keycode() { for keycode in self.min_keycode..self.max_keycode { if self.mapping.keysyms()[(keycode as usize - self.min_keycode as usize) * self.mapping.keysyms_per_keycode() as usize + shift as usize] == keysym.raw() { return Ok(keycode.into()); } } } Ok(KeyCode::new(0)) } /// Lookup the keysym in the given column for the given keycode pub fn keycode_to_keysym(&self, keycode: KeyCode, col: u8) -> Option { xkeysym::keysym( keycode, col, self.min_keycode.into(), self.mapping.keysyms_per_keycode(), self.mapping.keysyms(), ) } } /// A bound key pub struct Keybind { modifiers: ModMask, key: Keysym, action: &'static dyn Fn(&mut 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() } } /// A set of keybinds. Currently, there is only one instance of this defined statically: [`self::KEYBINDS`]. pub struct Keybinds(&'static [Keybind]); /// The keybinds to use. const KEYBINDS: Keybinds = Keybinds(&[Keybind { modifiers: ModMask::CONTROL, key: Keysym::t, action: &|_| { // TODO: disown this process, probably using another way to spawn commands if let Err(e) = Command::new("xterm").spawn() { dbg!(e); } }, }]); 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) { if let Some(bind) = self .binds() .find(|b| b.key == key && modifiers.contains(b.modifiers)) { (bind.action)(wm); } } }