oxwm

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