oxwm

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