summaryrefslogtreecommitdiff
path: root/src/conn_info/keys.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/conn_info/keys.rs')
-rw-r--r--src/conn_info/keys.rs104
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(),
+ )
+ }
+}