1use xkbcommon::xkb::{self, Keycode, Keysym, State};
2
3use super::DOUBLE_CLICK_DISTANCE;
4use crate::{Keystroke, Modifiers, Pixels, Point};
5
6impl Keystroke {
7 pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
8 let mut modifiers = modifiers;
9
10 let key_utf32 = state.key_get_utf32(keycode);
11 let key_utf8 = state.key_get_utf8(keycode);
12 let key_sym = state.key_get_one_sym(keycode);
13
14 // The logic here tries to replicate the logic in `../mac/events.rs`
15 // "Consumed" modifiers are modifiers that have been used to translate a key, for example
16 // pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
17 // Notes:
18 // - macOS gets the key character directly ("."), xkb gives us the key name ("period")
19 // - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
20 // - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
21
22 let mut handle_consumed_modifiers = true;
23 let key = match key_sym {
24 Keysym::Return => "enter".to_owned(),
25 Keysym::Prior => "pageup".to_owned(),
26 Keysym::Next => "pagedown".to_owned(),
27
28 Keysym::comma => ",".to_owned(),
29 Keysym::period => ".".to_owned(),
30 Keysym::less => "<".to_owned(),
31 Keysym::greater => ">".to_owned(),
32 Keysym::slash => "/".to_owned(),
33 Keysym::question => "?".to_owned(),
34
35 Keysym::semicolon => ";".to_owned(),
36 Keysym::colon => ":".to_owned(),
37 Keysym::apostrophe => "'".to_owned(),
38 Keysym::quotedbl => "\"".to_owned(),
39
40 Keysym::bracketleft => "[".to_owned(),
41 Keysym::braceleft => "{".to_owned(),
42 Keysym::bracketright => "]".to_owned(),
43 Keysym::braceright => "}".to_owned(),
44 Keysym::backslash => "\\".to_owned(),
45 Keysym::bar => "|".to_owned(),
46
47 Keysym::grave => "`".to_owned(),
48 Keysym::asciitilde => "~".to_owned(),
49 Keysym::exclam => "!".to_owned(),
50 Keysym::at => "@".to_owned(),
51 Keysym::numbersign => "#".to_owned(),
52 Keysym::dollar => "$".to_owned(),
53 Keysym::percent => "%".to_owned(),
54 Keysym::asciicircum => "^".to_owned(),
55 Keysym::ampersand => "&".to_owned(),
56 Keysym::asterisk => "*".to_owned(),
57 Keysym::parenleft => "(".to_owned(),
58 Keysym::parenright => ")".to_owned(),
59 Keysym::minus => "-".to_owned(),
60 Keysym::underscore => "_".to_owned(),
61 Keysym::equal => "=".to_owned(),
62 Keysym::plus => "+".to_owned(),
63
64 Keysym::ISO_Left_Tab => {
65 handle_consumed_modifiers = false;
66 "tab".to_owned()
67 }
68
69 _ => {
70 handle_consumed_modifiers = false;
71 xkb::keysym_get_name(key_sym).to_lowercase()
72 }
73 };
74
75 // Ignore control characters (and DEL) for the purposes of ime_key,
76 // but if key_utf32 is 0 then assume it isn't one
77 let ime_key = ((key_utf32 == 0 || (key_utf32 >= 32 && key_utf32 != 127))
78 && !key_utf8.is_empty())
79 .then_some(key_utf8);
80
81 if handle_consumed_modifiers {
82 let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
83 let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
84
85 if modifiers.shift && is_shift_consumed {
86 modifiers.shift = false;
87 }
88 }
89
90 Keystroke {
91 modifiers,
92 key,
93 ime_key,
94 }
95 }
96}
97
98pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
99 let diff = a - b;
100 diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::{px, Point};
107
108 #[test]
109 fn test_is_within_click_distance() {
110 let zero = Point::new(px(0.0), px(0.0));
111 assert_eq!(
112 is_within_click_distance(zero, Point::new(px(5.0), px(5.0))),
113 true
114 );
115 assert_eq!(
116 is_within_click_distance(zero, Point::new(px(-4.9), px(5.0))),
117 true
118 );
119 assert_eq!(
120 is_within_click_distance(Point::new(px(3.0), px(2.0)), Point::new(px(-2.0), px(-2.0))),
121 true
122 );
123 assert_eq!(
124 is_within_click_distance(zero, Point::new(px(5.0), px(5.1))),
125 false
126 );
127 }
128}