summaryrefslogtreecommitdiff
path: root/src/conn_info/keys.rs
blob: 1be3dfe2b5455c6801b2a87d802402dfe629f519 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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(),
        )
    }
}