srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/keysyms.rs
blob: 0baa6bc2542bbc08880e46d0c10b21af71942993 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! A small, hand-maintained keysym <-> name table covering the keys used in
//! practice by window-manager keybindings (letters, digits, navigation,
//! function keys). Not a full xkbcommon-level keymap - see module docs in
//! `lib.rs` for why that trade-off was made for this pass.

pub fn keysym_to_name(keysym: u32) -> Option<String> {
    Some(match keysym {
        0x0020 => "Space".to_string(),
        0x0061..=0x007a => (((keysym - 0x0061) as u8 + b'a') as char).to_string(),
        0x0030..=0x0039 => (((keysym - 0x0030) as u8 + b'0') as char).to_string(),
        0xff0d => "Return".to_string(),
        0xff1b => "Escape".to_string(),
        0xff09 => "Tab".to_string(),
        0xff08 => "BackSpace".to_string(),
        0xffff => "Delete".to_string(),
        0xff51 => "Left".to_string(),
        0xff52 => "Up".to_string(),
        0xff53 => "Right".to_string(),
        0xff54 => "Down".to_string(),
        0xff55 => "Prior".to_string(),
        0xff56 => "Next".to_string(),
        0xff50 => "Home".to_string(),
        0xff57 => "End".to_string(),
        0xffbe..=0xffc9 => format!("F{}", keysym - 0xffbe + 1),
        0x1008ff13 => "XF86AudioRaiseVolume".to_string(),
        0x1008ff11 => "XF86AudioLowerVolume".to_string(),
        0x1008ff12 => "XF86AudioMute".to_string(),
        0x1008ff02 => "XF86MonBrightnessUp".to_string(),
        0x1008ff03 => "XF86MonBrightnessDown".to_string(),
        _ => return None,
    })
}

/// Case-insensitive: config authors reasonably write both `"Space"` and
/// `"space"`, and X11 key names aren't consistently capitalized in the wild.
pub fn name_to_keysym(name: &str) -> Option<u32> {
    match name.to_ascii_lowercase().as_str() {
        "space" => return Some(0x0020),
        "return" | "enter" => return Some(0xff0d),
        "escape" => return Some(0xff1b),
        "tab" => return Some(0xff09),
        "backspace" => return Some(0xff08),
        "delete" => return Some(0xffff),
        "left" => return Some(0xff51),
        "up" => return Some(0xff52),
        "right" => return Some(0xff53),
        "down" => return Some(0xff54),
        "prior" | "pageup" => return Some(0xff55),
        "next" | "pagedown" => return Some(0xff56),
        "home" => return Some(0xff50),
        "end" => return Some(0xff57),
        "xf86audioraisevolume" => return Some(0x1008ff13),
        "xf86audiolowervolume" => return Some(0x1008ff11),
        "xf86audiomute" => return Some(0x1008ff12),
        "xf86monbrightnessup" => return Some(0x1008ff02),
        "xf86monbrightnessdown" => return Some(0x1008ff03),
        _ => {}
    }
    if name.len() == 1 {
        let c = name.chars().next().unwrap().to_ascii_lowercase();
        if c.is_ascii_lowercase() {
            return Some(0x0061 + (c as u32 - 'a' as u32));
        }
        if c.is_ascii_digit() {
            return Some(0x0030 + (c as u32 - '0' as u32));
        }
    }
    if let Some(rest) = name.to_ascii_lowercase().strip_prefix('f') {
        if let Ok(n) = rest.parse::<u32>() {
            if (1..=12).contains(&n) {
                return Some(0xffbe + n - 1);
            }
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn letters_and_digits_roundtrip() {
        for c in 'a'..='z' {
            let name = c.to_string();
            let ks = name_to_keysym(&name).unwrap();
            assert_eq!(keysym_to_name(ks), Some(name));
        }
        for c in '0'..='9' {
            let name = c.to_string();
            let ks = name_to_keysym(&name).unwrap();
            assert_eq!(keysym_to_name(ks), Some(name));
        }
    }

    #[test]
    fn lowercase_names_resolve_case_insensitively() {
        // Config authors reasonably write "Mod4+Shift+space"; this must
        // resolve to the same keysym as "Space".
        assert_eq!(name_to_keysym("space"), name_to_keysym("Space"));
        assert_eq!(name_to_keysym("return"), name_to_keysym("Return"));
        assert_eq!(name_to_keysym("f5"), name_to_keysym("F5"));
    }

    #[test]
    fn named_keys_roundtrip() {
        for name in ["Return", "Escape", "Tab", "Left", "Right", "Up", "Down", "F1", "F12"] {
            let ks = name_to_keysym(name).unwrap();
            assert_eq!(keysym_to_name(ks), Some(name.to_string()));
        }
    }
}