oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
9,593 bytes raw
1
use std::collections::HashMap;
2
use std::io::{ErrorKind, Result};
3
use std::process::Command;
4
5
use serde::Deserialize;
6
use x11rb::connection::Connection;
7
use x11rb::protocol::xproto::*;
8
9
use crate::errors::X11Error;
10
use crate::keyboard::keysyms::{self, Keysym};
11
12
#[derive(Debug, Copy, Clone, Deserialize, PartialEq)]
13
pub enum KeyAction {
14
    Spawn,
15
    SpawnTerminal,
16
    KillClient,
17
    FocusStack,
18
    MoveStack,
19
    Quit,
20
    Restart,
21
    Recompile,
22
    ViewTag,
23
    ToggleView,
24
    MoveToTag,
25
    ToggleTag,
26
    ToggleGaps,
27
    ToggleFullScreen,
28
    ToggleFloating,
29
    ChangeLayout,
30
    CycleLayout,
31
    FocusMonitor,
32
    TagMonitor,
33
    ShowKeybindOverlay,
34
    SetMasterFactor,
35
    IncNumMaster,
36
    None,
37
}
38
39
#[derive(Debug, Clone)]
40
pub enum Arg {
41
    None,
42
    Int(i32),
43
    Str(String),
44
    Array(Vec<String>),
45
}
46
47
impl Arg {
48
    pub const fn none() -> Self {
49
        Arg::None
50
    }
51
}
52
53
#[derive(Clone, Debug)]
54
pub struct KeyPress {
55
    pub(crate) modifiers: Vec<KeyButMask>,
56
    pub(crate) keysym: Keysym,
57
}
58
59
#[derive(Clone)]
60
pub struct KeyBinding {
61
    pub(crate) keys: Vec<KeyPress>,
62
    pub(crate) func: KeyAction,
63
    pub(crate) arg: Arg,
64
}
65
66
impl KeyBinding {
67
    pub fn new(keys: Vec<KeyPress>, func: KeyAction, arg: Arg) -> Self {
68
        Self { keys, func, arg }
69
    }
70
71
    pub fn single_key(
72
        modifiers: Vec<KeyButMask>,
73
        keysym: Keysym,
74
        func: KeyAction,
75
        arg: Arg,
76
    ) -> Self {
77
        Self {
78
            keys: vec![KeyPress { modifiers, keysym }],
79
            func,
80
            arg,
81
        }
82
    }
83
}
84
85
pub type Key = KeyBinding;
86
87
#[derive(Debug, Clone)]
88
pub enum KeychordState {
89
    Idle,
90
    InProgress {
91
        candidates: Vec<usize>,
92
        keys_pressed: usize,
93
    },
94
}
95
96
pub enum KeychordResult {
97
    Completed(KeyAction, Arg),
98
    InProgress(Vec<usize>),
99
    None,
100
    Cancelled,
101
}
102
103
pub fn modifiers_to_mask(modifiers: &[KeyButMask]) -> u16 {
104
    modifiers
105
        .iter()
106
        .fold(0u16, |acc, &modifier| acc | u16::from(modifier))
107
}
108
109
fn build_keysym_maps(
110
    connection: &impl Connection,
111
) -> std::result::Result<(HashMap<Keysym, Vec<Keycode>>, HashMap<Keycode, Keysym>), X11Error> {
112
    let setup = connection.setup();
113
    let min_keycode = setup.min_keycode;
114
    let max_keycode = setup.max_keycode;
115
116
    let keyboard_mapping = connection
117
        .get_keyboard_mapping(min_keycode, max_keycode - min_keycode + 1)?
118
        .reply()?;
119
120
    let mut keysym_to_keycode: HashMap<Keysym, Vec<Keycode>> = HashMap::new();
121
    let mut keycode_to_keysym: HashMap<Keycode, Keysym> = HashMap::new();
122
    let keysyms_per_keycode = keyboard_mapping.keysyms_per_keycode;
123
124
    for keycode in min_keycode..=max_keycode {
125
        let index = (keycode - min_keycode) as usize * keysyms_per_keycode as usize;
126
127
        for i in 0..keysyms_per_keycode as usize {
128
            if let Some(&keysym) = keyboard_mapping.keysyms.get(index + i) {
129
                if keysym != 0 {
130
                    keysym_to_keycode
131
                        .entry(keysym)
132
                        .or_insert_with(Vec::new)
133
                        .push(keycode);
134
                    keycode_to_keysym.entry(keycode).or_insert(keysym);
135
                }
136
            }
137
        }
138
    }
139
140
    Ok((keysym_to_keycode, keycode_to_keysym))
141
}
142
143
pub fn setup_keybinds(
144
    connection: &impl Connection,
145
    root: Window,
146
    keybindings: &[KeyBinding],
147
) -> std::result::Result<(), X11Error> {
148
    use std::collections::HashSet;
149
150
    let (keysym_to_keycode, _) = build_keysym_maps(connection)?;
151
    let mut grabbed_keys: HashSet<(u16, Keycode)> = HashSet::new();
152
153
    let ignore_modifiers = [
154
        0,
155
        u16::from(ModMask::LOCK),
156
        u16::from(ModMask::M2),
157
        u16::from(ModMask::LOCK | ModMask::M2),
158
    ];
159
160
    for keybinding in keybindings {
161
        if keybinding.keys.is_empty() {
162
            continue;
163
        }
164
165
        let first_key = &keybinding.keys[0];
166
        let modifier_mask = modifiers_to_mask(&first_key.modifiers);
167
168
        if let Some(keycodes) = keysym_to_keycode.get(&first_key.keysym) {
169
            if let Some(&keycode) = keycodes.first() {
170
                for &ignore_mask in &ignore_modifiers {
171
                    let grab_mask = modifier_mask | ignore_mask;
172
                    let key_tuple = (grab_mask, keycode);
173
174
                    if grabbed_keys.insert(key_tuple) {
175
                        connection.grab_key(
176
                            false,
177
                            root,
178
                            grab_mask.into(),
179
                            keycode,
180
                            GrabMode::ASYNC,
181
                            GrabMode::ASYNC,
182
                        )?;
183
                    }
184
                }
185
            }
186
        }
187
    }
188
189
    Ok(())
190
}
191
192
pub fn handle_key_press(
193
    event: KeyPressEvent,
194
    keybindings: &[KeyBinding],
195
    keychord_state: &KeychordState,
196
    connection: &impl Connection,
197
) -> std::result::Result<KeychordResult, X11Error> {
198
    let (_, keycode_to_keysym) = build_keysym_maps(connection)?;
199
    let event_keysym = keycode_to_keysym.get(&event.detail).copied().unwrap_or(0);
200
201
    if event_keysym == keysyms::XK_ESCAPE {
202
        return Ok(match keychord_state {
203
            KeychordState::InProgress { .. } => KeychordResult::Cancelled,
204
            KeychordState::Idle => KeychordResult::None,
205
        });
206
    }
207
208
    Ok(match keychord_state {
209
        KeychordState::Idle => handle_first_key(event, event_keysym, keybindings),
210
        KeychordState::InProgress {
211
            candidates,
212
            keys_pressed,
213
        } => handle_next_key(event, event_keysym, keybindings, candidates, *keys_pressed),
214
    })
215
}
216
217
fn handle_first_key(
218
    event: KeyPressEvent,
219
    event_keysym: Keysym,
220
    keybindings: &[KeyBinding],
221
) -> KeychordResult {
222
    let mut candidates = Vec::new();
223
224
    let clean_state = event.state & !(u16::from(ModMask::LOCK) | u16::from(ModMask::M2));
225
226
    for (keybinding_index, keybinding) in keybindings.iter().enumerate() {
227
        if keybinding.keys.is_empty() {
228
            continue;
229
        }
230
231
        let first_key = &keybinding.keys[0];
232
        let modifier_mask = modifiers_to_mask(&first_key.modifiers);
233
234
        if event_keysym == first_key.keysym && clean_state == modifier_mask.into() {
235
            if keybinding.keys.len() == 1 {
236
                return KeychordResult::Completed(keybinding.func, keybinding.arg.clone());
237
            } else {
238
                candidates.push(keybinding_index);
239
            }
240
        }
241
    }
242
243
    if candidates.is_empty() {
244
        KeychordResult::None
245
    } else {
246
        KeychordResult::InProgress(candidates)
247
    }
248
}
249
250
fn handle_next_key(
251
    event: KeyPressEvent,
252
    event_keysym: Keysym,
253
    keybindings: &[KeyBinding],
254
    candidates: &[usize],
255
    keys_pressed: usize,
256
) -> KeychordResult {
257
    let mut new_candidates = Vec::new();
258
259
    let clean_state = event.state & !(u16::from(ModMask::LOCK) | u16::from(ModMask::M2));
260
261
    for &candidate_index in candidates {
262
        let keybinding = &keybindings[candidate_index];
263
264
        if keys_pressed >= keybinding.keys.len() {
265
            continue;
266
        }
267
268
        let next_key = &keybinding.keys[keys_pressed];
269
        let required_mask = modifiers_to_mask(&next_key.modifiers);
270
271
        let modifiers_match = if next_key.modifiers.is_empty() {
272
            true
273
        } else {
274
            (clean_state & required_mask) == required_mask.into()
275
        };
276
277
        if event_keysym == next_key.keysym && modifiers_match {
278
            if keys_pressed + 1 == keybinding.keys.len() {
279
                return KeychordResult::Completed(keybinding.func, keybinding.arg.clone());
280
            } else {
281
                new_candidates.push(candidate_index);
282
            }
283
        }
284
    }
285
286
    if new_candidates.is_empty() {
287
        KeychordResult::Cancelled
288
    } else {
289
        KeychordResult::InProgress(new_candidates)
290
    }
291
}
292
293
pub fn handle_spawn_action(action: KeyAction, arg: &Arg, selected_monitor: usize) -> Result<()> {
294
    if let KeyAction::Spawn = action {
295
        match arg {
296
            Arg::Str(command) => match Command::new(command.as_str()).spawn() {
297
                Err(error) if error.kind() == ErrorKind::NotFound => {
298
                    eprintln!(
299
                        "KeyAction::Spawn failed: could not spawn \"{}\", command not found",
300
                        command
301
                    );
302
                }
303
                Err(error) => Err(error)?,
304
                _ => (),
305
            },
306
            Arg::Array(command) => {
307
                let Some((cmd, args)) = command.split_first() else {
308
                    return Ok(());
309
                };
310
311
                let mut args_vec: Vec<String> = args.to_vec();
312
313
                let is_dmenu = cmd.contains("dmenu");
314
                let has_monitor_flag = args.iter().any(|arg| arg == "-m");
315
316
                if is_dmenu && !has_monitor_flag {
317
                    args_vec.insert(0, selected_monitor.to_string());
318
                    args_vec.insert(0, "-m".to_string());
319
                }
320
321
                let args_str: Vec<&str> = args_vec.iter().map(|s| s.as_str()).collect();
322
                match Command::new(cmd.as_str()).args(&args_str).spawn() {
323
                    Err(error) if error.kind() == ErrorKind::NotFound => {
324
                        eprintln!(
325
                            "KeyAction::Spawn failed: could not spawn \"{}\", command not found",
326
                            cmd
327
                        );
328
                    }
329
                    Err(error) => Err(error)?,
330
                    _ => (),
331
                }
332
            }
333
            _ => {}
334
        }
335
    }
336
337
    Ok(())
338
}