penrose/core/
bindings.rs

1//! Setting up and responding to user defined key/mouse bindings
2use crate::{
3    core::{State, Xid},
4    pure::geometry::Point,
5    x::XConn,
6    Error, Result,
7};
8#[cfg(feature = "keysyms")]
9use penrose_keysyms::XKeySym;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12use std::{collections::HashMap, convert::TryFrom, fmt, process::Command};
13use strum::{EnumIter, IntoEnumIterator};
14use tracing::trace;
15
16/// Run the xmodmap command to dump the system keymap table.
17///
18/// This is done in a form that we can load in and convert back to key
19/// codes. This lets the user define key bindings in the way that they
20/// would expect while also ensuring that it is easy to debug any odd
21/// issues with bindings by referring the user to the xmodmap output.
22///
23/// # Panics
24/// This function will panic if it is unable to fetch keycodes using the xmodmap
25/// binary on your system or if the output of `xmodmap -pke` is not valid
26pub fn keycodes_from_xmodmap() -> Result<HashMap<String, u8>> {
27    let output = Command::new("xmodmap").arg("-pke").output()?;
28    let m = String::from_utf8(output.stdout)?
29        .lines()
30        .flat_map(|l| {
31            let mut words = l.split_whitespace(); // keycode <code> = <names ...>
32            let key_code: u8 = match words.nth(1) {
33                Some(word) => match word.parse() {
34                    Ok(val) => val,
35                    Err(e) => panic!("{}", e),
36                },
37                None => panic!("unexpected output format from xmodmap -pke"),
38            };
39            words.skip(1).map(move |name| (name.into(), key_code))
40        })
41        .collect();
42
43    Ok(m)
44}
45
46fn parse_binding(pattern: &str, known_codes: &HashMap<String, u8>) -> Result<KeyCode> {
47    let mut parts: Vec<&str> = pattern.split('-').collect();
48    let name = parts.remove(parts.len() - 1);
49
50    match known_codes.get(name) {
51        Some(code) => {
52            let mask = parts
53                .iter()
54                .map(|&s| ModifierKey::try_from(s))
55                .try_fold(0, |acc, v| v.map(|inner| acc | u16::from(inner)))?;
56
57            trace!(?pattern, mask, code, "parsed keybinding");
58            Ok(KeyCode { mask, code: *code })
59        }
60
61        None => Err(Error::UnknownKeyName {
62            name: name.to_owned(),
63        }),
64    }
65}
66
67/// Parse string format key bindings into [KeyCode] based [KeyBindings] using
68/// the command line `xmodmap` utility.
69///
70/// See [keycodes_from_xmodmap] for details of how `xmodmap` is used.
71pub fn parse_keybindings_with_xmodmap<S, X>(
72    str_bindings: HashMap<S, Box<dyn KeyEventHandler<X>>>,
73) -> Result<KeyBindings<X>>
74where
75    S: AsRef<str>,
76    X: XConn,
77{
78    let m = keycodes_from_xmodmap()?;
79
80    str_bindings
81        .into_iter()
82        .map(|(s, v)| parse_binding(s.as_ref(), &m).map(|k| (k, v)))
83        .collect()
84}
85
86/// Some action to be run by a user key binding
87pub trait KeyEventHandler<X>
88where
89    X: XConn,
90{
91    /// Call this handler with the current window manager state
92    fn call(&mut self, state: &mut State<X>, x: &X) -> Result<()>;
93}
94
95impl<X: XConn> fmt::Debug for Box<dyn KeyEventHandler<X>> {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.debug_struct("KeyEventHandler").finish()
98    }
99}
100
101impl<F, X> KeyEventHandler<X> for F
102where
103    F: FnMut(&mut State<X>, &X) -> Result<()>,
104    X: XConn,
105{
106    fn call(&mut self, state: &mut State<X>, x: &X) -> Result<()> {
107        (self)(state, x)
108    }
109}
110
111/// User defined key bindings
112pub type KeyBindings<X> = HashMap<KeyCode, Box<dyn KeyEventHandler<X>>>;
113
114/// An action to be run in response to a mouse event
115pub trait MouseEventHandler<X>
116where
117    X: XConn,
118{
119    /// Called when the [MouseState] associated with this handler is seen with a button press or
120    /// release.
121    fn on_mouse_event(&mut self, evt: &MouseEvent, state: &mut State<X>, x: &X) -> Result<()>;
122
123    /// Called when the [ModifierKey]s associated with this handler are seen when the mouse is
124    /// moving.
125    fn on_motion(&mut self, evt: &MotionNotifyEvent, state: &mut State<X>, x: &X) -> Result<()>;
126}
127
128impl<X: XConn> fmt::Debug for Box<dyn MouseEventHandler<X>> {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        f.debug_struct("MouseEventHandler").finish()
131    }
132}
133
134impl<F, X> MouseEventHandler<X> for F
135where
136    F: FnMut(&mut State<X>, &X) -> Result<()>,
137    X: XConn,
138{
139    fn on_mouse_event(&mut self, evt: &MouseEvent, state: &mut State<X>, x: &X) -> Result<()> {
140        if evt.kind == MouseEventKind::Press {
141            (self)(state, x)
142        } else {
143            Ok(())
144        }
145    }
146
147    fn on_motion(&mut self, _: &MotionNotifyEvent, _: &mut State<X>, _: &X) -> Result<()> {
148        Ok(())
149    }
150}
151
152/// Convert a [KeyEventHandler] to a [MouseEventHandler] that runs on button `Press` events.
153///
154/// This allows for running pre-existing simple key handlers that do not care about press/release
155/// or motion behaviour as a simplified mouse event handler.
156///
157/// ## Example
158/// ```rust
159/// use penrose::builtin::actions::floating::sink_all;
160/// use penrose::core::bindings::{click_handler, MouseEventHandler};
161/// use penrose::x11rb::RustConn;
162///
163/// let handler: Box<dyn MouseEventHandler<RustConn>> =  click_handler(sink_all());
164/// ```
165pub fn click_handler<X: XConn + 'static>(
166    kh: Box<dyn KeyEventHandler<X>>,
167) -> Box<dyn MouseEventHandler<X>> {
168    Box::new(MouseWrapper { inner: kh })
169}
170
171struct MouseWrapper<X: XConn> {
172    inner: Box<dyn KeyEventHandler<X>>,
173}
174
175impl<X: XConn> MouseEventHandler<X> for MouseWrapper<X> {
176    fn on_mouse_event(&mut self, evt: &MouseEvent, state: &mut State<X>, x: &X) -> Result<()> {
177        if evt.kind == MouseEventKind::Press {
178            self.inner.call(state, x)
179        } else {
180            Ok(())
181        }
182    }
183
184    fn on_motion(&mut self, _: &MotionNotifyEvent, _: &mut State<X>, _: &X) -> Result<()> {
185        Ok(())
186    }
187}
188
189/// User defined mouse bindings
190pub type MouseBindings<X> = HashMap<MouseState, Box<dyn MouseEventHandler<X>>>;
191
192/// Abstraction layer for working with key presses
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum KeyPress {
195    /// A raw character key
196    Utf8(String),
197    /// Return / enter key
198    Return,
199    /// Escape
200    Escape,
201    /// Tab
202    Tab,
203    /// Backspace
204    Backspace,
205    /// Delete
206    Delete,
207    /// PageUp
208    PageUp,
209    /// PageDown
210    PageDown,
211    /// Up
212    Up,
213    /// Down
214    Down,
215    /// Left
216    Left,
217    /// Right
218    Right,
219}
220
221#[cfg(feature = "keysyms")]
222impl TryFrom<XKeySym> for KeyPress {
223    type Error = std::string::FromUtf8Error;
224
225    fn try_from(s: XKeySym) -> std::result::Result<KeyPress, Self::Error> {
226        Ok(match s {
227            XKeySym::XK_Return | XKeySym::XK_KP_Enter | XKeySym::XK_ISO_Enter => KeyPress::Return,
228            XKeySym::XK_Escape => KeyPress::Escape,
229            XKeySym::XK_Tab | XKeySym::XK_ISO_Left_Tab | XKeySym::XK_KP_Tab => KeyPress::Tab,
230            XKeySym::XK_BackSpace => KeyPress::Backspace,
231            XKeySym::XK_Delete | XKeySym::XK_KP_Delete => KeyPress::Delete,
232            XKeySym::XK_Page_Up | XKeySym::XK_KP_Page_Up => KeyPress::PageUp,
233            XKeySym::XK_Page_Down | XKeySym::XK_KP_Page_Down => KeyPress::PageDown,
234            XKeySym::XK_Up | XKeySym::XK_KP_Up => KeyPress::Up,
235            XKeySym::XK_Down | XKeySym::XK_KP_Down => KeyPress::Down,
236            XKeySym::XK_Left | XKeySym::XK_KP_Left => KeyPress::Left,
237            XKeySym::XK_Right | XKeySym::XK_KP_Right => KeyPress::Right,
238            s => KeyPress::Utf8(s.as_utf8_string()?),
239        })
240    }
241}
242
243/// A u16 X key-code bitmask
244pub type KeyCodeMask = u16;
245
246/// A u8 X key-code enum value
247pub type KeyCodeValue = u8;
248
249/// A key press and held modifiers
250#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
251#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
252pub struct KeyCode {
253    /// The held modifier mask
254    pub mask: KeyCodeMask,
255    /// The key code that was held
256    pub code: KeyCodeValue,
257}
258
259impl KeyCode {
260    /// Create a new [KeyCode] from this one that removes the given mask
261    pub fn ignoring_modifier(&self, mask: KeyCodeMask) -> KeyCode {
262        KeyCode {
263            mask: self.mask & !mask,
264            code: self.code,
265        }
266    }
267}
268
269/// Known mouse buttons for binding actions
270#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
271#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
272pub enum MouseButton {
273    /// 1
274    #[default]
275    Left,
276    /// 2
277    Middle,
278    /// 3
279    Right,
280    /// 4
281    ScrollUp,
282    /// 5
283    ScrollDown,
284}
285
286impl From<MouseButton> for u8 {
287    fn from(b: MouseButton) -> u8 {
288        match b {
289            MouseButton::Left => 1,
290            MouseButton::Middle => 2,
291            MouseButton::Right => 3,
292            MouseButton::ScrollUp => 4,
293            MouseButton::ScrollDown => 5,
294        }
295    }
296}
297
298impl TryFrom<u8> for MouseButton {
299    type Error = Error;
300
301    fn try_from(n: u8) -> Result<Self> {
302        match n {
303            1 => Ok(Self::Left),
304            2 => Ok(Self::Middle),
305            3 => Ok(Self::Right),
306            4 => Ok(Self::ScrollUp),
307            5 => Ok(Self::ScrollDown),
308            _ => Err(Error::UnknownMouseButton { button: n }),
309        }
310    }
311}
312
313/// Known modifier keys for bindings
314#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
315#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
316pub enum ModifierKey {
317    /// Control
318    Ctrl,
319    /// Alt
320    Alt,
321    /// Shift
322    Shift,
323    /// Meta / super / windows
324    Meta,
325}
326
327impl ModifierKey {
328    fn was_held(&self, mask: u16) -> bool {
329        mask & u16::from(*self) > 0
330    }
331}
332
333impl From<ModifierKey> for u16 {
334    fn from(m: ModifierKey) -> u16 {
335        (match m {
336            ModifierKey::Shift => 1 << 0,
337            ModifierKey::Ctrl => 1 << 2,
338            ModifierKey::Alt => 1 << 3,
339            ModifierKey::Meta => 1 << 6,
340        }) as u16
341    }
342}
343
344impl TryFrom<&str> for ModifierKey {
345    type Error = Error;
346
347    fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
348        match s {
349            "C" => Ok(Self::Ctrl),
350            "A" => Ok(Self::Alt),
351            "S" => Ok(Self::Shift),
352            "M" => Ok(Self::Meta),
353            _ => Err(Error::UnknownModifier { name: s.to_owned() }),
354        }
355    }
356}
357
358/// A mouse state specification indicating the button and modifiers held
359#[derive(Debug, PartialEq, Eq, Hash, Clone)]
360#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
361pub struct MouseState {
362    /// The [MouseButton] being held
363    pub button: MouseButton,
364    /// All [ModifierKey]s being held
365    pub modifiers: Vec<ModifierKey>,
366}
367
368impl MouseState {
369    /// Construct a new MouseState
370    pub fn new(button: MouseButton, mut modifiers: Vec<ModifierKey>) -> Self {
371        modifiers.sort();
372        Self { button, modifiers }
373    }
374
375    /// Parse raw mouse state values into a [MouseState]
376    pub fn from_detail_and_state(detail: u8, state: u16) -> Result<Self> {
377        Ok(Self {
378            button: MouseButton::try_from(detail)?,
379            modifiers: ModifierKey::iter().filter(|m| m.was_held(state)).collect(),
380        })
381    }
382
383    /// The xcb bitmask for this [MouseState]
384    pub fn mask(&self) -> u16 {
385        self.modifiers
386            .iter()
387            .fold(0, |acc, &val| acc | u16::from(val))
388    }
389
390    /// The xcb button ID for this [MouseState]
391    pub fn button(&self) -> u8 {
392        self.button.into()
393    }
394}
395
396/// The types of mouse events represented by a MouseEvent
397#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
398#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
399pub enum MouseEventKind {
400    /// A button was pressed
401    Press,
402    /// A button was released
403    Release,
404}
405
406/// Data from a button press or motion-notify event
407#[derive(Debug, Clone, PartialEq, Eq, Hash)]
408#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
409pub struct MouseEventData {
410    /// The ID of the window that was contained the click
411    pub id: Xid,
412    /// Absolute coordinate of the event
413    pub rpt: Point,
414    /// Coordinate of the event relative to top-left of the window itself
415    pub wpt: Point,
416}
417
418/// A mouse movement or button event
419#[derive(Debug, Clone, PartialEq, Eq, Hash)]
420#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
421pub struct MouseEvent {
422    /// The details of which window the event applies to and where the event occurred
423    pub data: MouseEventData,
424    /// The modifier and button code that was received
425    pub state: MouseState,
426    /// Was this press or release
427    pub kind: MouseEventKind,
428}
429
430impl MouseEvent {
431    /// Construct a new [MouseEvent] from raw data
432    pub fn new(
433        id: Xid,
434        rx: i16,
435        ry: i16,
436        ex: i16,
437        ey: i16,
438        state: MouseState,
439        kind: MouseEventKind,
440    ) -> Self {
441        MouseEvent {
442            data: MouseEventData {
443                id,
444                rpt: Point::new(rx as i32, ry as i32),
445                wpt: Point::new(ex as i32, ey as i32),
446            },
447            state,
448            kind,
449        }
450    }
451}
452
453/// Mouse motion with a held button and optional modifiers
454#[derive(Debug, Clone, PartialEq, Eq, Hash)]
455#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
456pub struct MotionNotifyEvent {
457    /// The details of which window the event applies to and where the event occurred
458    pub data: MouseEventData,
459    /// All [ModifierKey]s being held
460    pub modifiers: Vec<ModifierKey>,
461}
462
463impl MotionNotifyEvent {
464    /// Construct a new [MotionNotifyEvent] from raw data
465    pub fn new(id: Xid, rx: i16, ry: i16, ex: i16, ey: i16, modifiers: Vec<ModifierKey>) -> Self {
466        MotionNotifyEvent {
467            data: MouseEventData {
468                id,
469                rpt: Point::new(rx as i32, ry as i32),
470                wpt: Point::new(ex as i32, ey as i32),
471            },
472            modifiers,
473        }
474    }
475}