oxwm

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