oxwm

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