util.rs

  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}