oxwm

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