use std::ops::RangeInclusive; use crate::Result; use xcb::{ x::{GetKeyboardMapping, GetKeyboardMappingReply, GetModifierMapping, ModMask}, Connection, }; use xkeysym::{KeyCode, Keysym, RawKeyCode}; /// Cached information about our keyboard layout. pub struct KeyboardInfo { /// The range of keycodes used keycodes: RangeInclusive, /// The `ModMask` corresponding to `NumLock`. /// This varies sometimes, and we need to know to ignore it. numlock_mask: ModMask, /// The mapping from keycodes to (multiple) key symbols 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 { keycodes: RawKeyCode::from(min_keycode)..=RawKeyCode::from(max_keycode), numlock_mask: ModMask::empty(), mapping, }; let Some(numlock_keycode) = this.keysym_to_keycode(Keysym::Num_Lock) else { // No numlock button, so no modmask for numlock return Ok(this); }; 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 RawKeyCode::from(mod_map.keycodes()[i * keypermod + j]) == numlock_keycode.raw() { this.numlock_mask = ModMask::from_bits(1 << i).expect("x11 returned unrecognised modifier"); } } } Ok(this) } /// Get the modifier mask being used for numlock pub const fn numlock_mask(&self) -> ModMask { self.numlock_mask } /// Iterate over all keycodes and their bound keysyms. /// This is likely to contain duplicate pairs. pub fn iter_keycodes_keysyms(&self) -> impl Iterator + '_ { (0..self.mapping.keysyms_per_keycode()) .flat_map(|shift| self.keycodes.clone().map(move |keycode| (shift, keycode))) .filter_map(|(shift, keycode)| -> Option<_> { Some(( keycode.into(), self.keycode_to_keysym(keycode.into(), shift)?, )) }) } /// Lookup the first keycode which has the given keysym in any column pub(crate) fn keysym_to_keycode(&self, keysym: Keysym) -> Option { for shift in 0..self.mapping.keysyms_per_keycode() { for keycode in self.keycodes.clone() { if self.mapping.keysyms()[(keycode as usize - *self.keycodes.start() as usize) * self.mapping.keysyms_per_keycode() as usize + shift as usize] == keysym.raw() { return Some(keycode.into()); } } } None } /// 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.keycodes.start()).into(), self.mapping.keysyms_per_keycode(), self.mapping.keysyms(), ) } }