diff options
Diffstat (limited to 'src/conn_info/keys.rs')
-rw-r--r-- | src/conn_info/keys.rs | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/src/conn_info/keys.rs b/src/conn_info/keys.rs new file mode 100644 index 0000000..1be3dfe --- /dev/null +++ b/src/conn_info/keys.rs @@ -0,0 +1,104 @@ +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<RawKeyCode>, + + /// 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<Self> { + 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<Item = (KeyCode, Keysym)> + '_ { + (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<KeyCode> { + 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<Keysym> { + xkeysym::keysym( + keycode, + col, + (*self.keycodes.start()).into(), + self.mapping.keysyms_per_keycode(), + self.mapping.keysyms(), + ) + } +} |