oxwm

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