summaryrefslogtreecommitdiff
path: root/src/keys.rs
blob: aa84da6c57c95cc7c4cf0d7b3276f2abb31258e5 (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
105
106
107
108
109
110
111
112
113
114
115
116
//! Keybind-related code

use crate::{config::KEYBINDS, conn_info::Connection, debug, error::Result, WM};
use xcb::x::{
    GrabKey, GrabMode, KeyPressEvent, Mapping, MappingNotifyEvent, ModMask, UngrabKey, GRAB_ANY,
};
use xkeysym::Keysym;

impl WM<'_> {
    /// Dispatch the given keypress event according to [`self::KEYBINDS`]
    pub fn handle_key_press(&mut self, e: &KeyPressEvent) {
        let Some(sym) = self
            .conn
            .keyboard_state
            .keycode_to_keysym(e.detail().into(), 0)
        else {
            return; // probably not bound
        };

        KEYBINDS.dispatch(
            self,
            sym,
            ModMask::from_bits_truncate(e.state().bits())
                .difference(self.conn.keyboard_state.numlock_mask() | ModMask::LOCK),
        );
    }

    /// Update our keyboard info when the mapping changes.
    pub fn handle_mapping_notify(&mut self, e: &MappingNotifyEvent) -> Result<()> {
        if e.request() == Mapping::Keyboard {
            grab(&mut self.conn)?;
        }

        Ok(())
    }
}

/// Refresh our keyboard info, and ensure that we get events for bound keys.
pub fn grab(conn: &mut Connection<'_>) -> Result<()> {
    // Refresh keyboard state
    conn.refresh_keyboard_info()?;

    // Ungrab all keys
    conn.send_request(&UngrabKey {
        key: GRAB_ANY,
        grab_window: conn.root(),
        modifiers: ModMask::ANY,
    });

    // Bind all of the keycodes which have keysyms we see in our binds.
    for (keycode, keysym) in conn.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,
                    conn.keyboard_state.numlock_mask(),
                    conn.keyboard_state.numlock_mask() | ModMask::LOCK,
                ] {
                    conn.send_request(&GrabKey {
                        grab_window: conn.root(),
                        #[allow(clippy::cast_possible_truncation)]
                        key: keycode.raw() as u8,
                        modifiers: bind.modifiers | modmask,
                        owner_events: true,
                        pointer_mode: GrabMode::Async,
                        keyboard_mode: GrabMode::Async,
                    });
                }
            }
        }
    }

    // Ensure all requests succeeded

    Ok(())
}

/// A key bound to some action
pub struct Keybind {
    pub modifiers: ModMask,
    pub key: Keysym,
    pub action: &'static dyn Fn(&mut WM<'_>),
}

/// A set of keybinds. Currently, there is only one instance of this defined statically: [`crate::config::KEYBINDS`].
pub struct Keybinds(pub &'static [Keybind]);

impl Keybinds {
    /// Get an iterator over all bound keys
    pub fn binds(&self) -> impl Iterator<Item = &Keybind> {
        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) {
        debug!("received {key:?}");
        if let Some(bind) = self
            .binds()
            .find(|b| b.key == key && modifiers.contains(b.modifiers))
        {
            debug!("found action");
            (bind.action)(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_non_exhaustive()
    }
}