autopilot/
key.rs

1// Copyright 2018, 2019, 2020 Michael Sanders
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT License <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7//
8//! This module contains functions for controlling the keyboard.
9extern crate rand;
10
11#[cfg(target_os = "macos")]
12use core_graphics::event;
13#[cfg(target_os = "macos")]
14use core_graphics::event::{CGEvent, CGEventFlags, CGKeyCode};
15#[cfg(target_os = "macos")]
16use core_graphics::event_source::CGEventSource;
17#[cfg(target_os = "macos")]
18use core_graphics::event_source::CGEventSourceStateID::HIDSystemState;
19#[cfg(target_os = "linux")]
20use internal;
21
22use self::rand::Rng;
23
24/// Device-independent modifier flags.
25#[derive(Copy, Clone, Debug, PartialEq)]
26pub enum Flag {
27    Shift,
28    Control,
29    Alt,
30    Meta,
31
32    // Special key identifiers.
33    Help,
34}
35
36/// Device-independent key codes.
37#[derive(Copy, Clone, Debug, PartialEq)]
38pub enum KeyCode {
39    F1,
40    F2,
41    F3,
42    F4,
43    F5,
44    F6,
45    F7,
46    F8,
47    F9,
48    F10,
49    F11,
50    F12,
51    F13,
52    F14,
53    F15,
54    F16,
55    F17,
56    F18,
57    F19,
58    F20,
59    F21,
60    F22,
61    F23,
62    F24,
63    LeftArrow,
64    Control,
65    RightArrow,
66    DownArrow,
67    End,
68    UpArrow,
69    PageUp,
70    Alt,
71    Return,
72    PageDown,
73    Delete,
74    // Insert doesn't exist on macOS
75    #[cfg(not(target_os = "macos"))]
76    Insert,
77    Home,
78    Escape,
79    Backspace,
80    Meta,
81    CapsLock,
82    Shift,
83    Tab,
84    Space,
85    PrintScreen,
86    ScrollLock,
87    Pause,
88    Num0,
89    Num1,
90    Num2,
91    Num3,
92    Num4,
93    Num5,
94    Num6,
95    Num7,
96    Num8,
97    Num9,
98    NumDecimal,
99    NumLock,
100    NumDivide,
101    NumMultiply,
102    NumSubtract,
103    NumAdd,
104    NumEnter,
105}
106
107pub trait KeyCodeConvertible {
108    #[cfg(target_os = "macos")]
109    fn code(&self) -> CGKeyCode;
110    #[cfg(target_os = "linux")]
111    fn code(&self) -> XKeyCode;
112    #[cfg(windows)]
113    fn code(&self) -> WinKeyCode;
114    fn character(&self) -> Option<char> {
115        None
116    }
117    fn flags(&self) -> &[Flag] {
118        &[]
119    }
120}
121
122#[derive(Copy, Clone, Debug)]
123pub struct Character(pub char);
124
125#[derive(Copy, Clone, Debug)]
126pub struct Code(pub KeyCode);
127
128/// Attempts to simulate typing a string at the given WPM, or as fast as
129/// possible if the WPM is 0.
130pub fn type_string(string: &str, flags: &[Flag], wpm: f64, noise: f64) {
131    let cpm = wpm * 5.0;
132    let cps = cpm / 60.0;
133    let ms_per_character: u64 = if cps == 0.0 {
134        0
135    } else {
136        (1000.0 / cps).round() as u64
137    };
138    let ms_per_stroke = (ms_per_character as f64 / 2.0).round() as u64;
139
140    for c in string.chars() {
141        let tolerance = (noise * ms_per_character as f64).round() as u64;
142        let noise = if tolerance > 0 {
143            rand::thread_rng().gen_range(0, tolerance)
144        } else {
145            0
146        };
147
148        tap(&Character(c), flags, ms_per_stroke, ms_per_stroke);
149        std::thread::sleep(std::time::Duration::from_millis(ms_per_stroke + noise));
150    }
151}
152
153/// Convenience wrapper around `toggle()` that holds down and then releases the
154/// given key and modifier flags. Delay between pressing and releasing the key
155/// can be controlled using the `delay_ms` parameter. Delay between pressing and
156/// releasing modifiers can be controlled using the `modifier_delay_ms`
157/// parameter.
158pub fn tap<T: KeyCodeConvertible + Copy>(
159    key: &T,
160    flags: &[Flag],
161    delay_ms: u64,
162    modifier_delay_ms: u64,
163) {
164    toggle(key, true, flags, modifier_delay_ms);
165    std::thread::sleep(std::time::Duration::from_millis(delay_ms));
166    toggle(key, false, flags, modifier_delay_ms);
167}
168
169/// Holds down the given key or keycode if `down` is `true`, or releases it if
170/// not. Characters are converted to a keycode corresponding to the current
171/// keyboard layout. Delay between pressing and releasing the modifier keys can
172/// be controlled using the `modifier_delay_ms` parameter.
173pub fn toggle<T: KeyCodeConvertible>(key: &T, down: bool, flags: &[Flag], modifier_delay_ms: u64) {
174    let key_flags = key.character().map(flags_for_char).unwrap_or(&[]);
175    let mut appended_flags: Vec<Flag> = Vec::with_capacity(flags.len() + key_flags.len());
176    appended_flags.extend_from_slice(flags);
177    for flag in key_flags.iter() {
178        if !flags.contains(flag) {
179            appended_flags.push(*flag);
180        }
181    }
182    system_toggle(key, down, &appended_flags, modifier_delay_ms);
183}
184
185#[cfg(target_os = "macos")]
186fn char_to_key_code(character: char) -> CGKeyCode {
187    use core_graphics::event::EventField;
188    let source = CGEventSource::new(HIDSystemState).unwrap();
189    let event = CGEvent::new_keyboard_event(source, 0, true).unwrap();
190    let mut buf = [0; 2];
191    event.set_string_from_utf16_unchecked(character.encode_utf16(&mut buf));
192    event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE) as CGKeyCode
193}
194
195#[cfg(target_os = "linux")]
196fn char_to_key_code(character: char) -> XKeyCode {
197    match character {
198        ' ' => XKeyCode::from(x11::keysym::XK_space),
199        '!' => XKeyCode::from(x11::keysym::XK_exclam),
200        '"' => XKeyCode::from(x11::keysym::XK_quotedbl),
201        '#' => XKeyCode::from(x11::keysym::XK_numbersign),
202        '$' => XKeyCode::from(x11::keysym::XK_dollar),
203        '%' => XKeyCode::from(x11::keysym::XK_percent),
204        '&' => XKeyCode::from(x11::keysym::XK_ampersand),
205        '(' => XKeyCode::from(x11::keysym::XK_parenleft),
206        ')' => XKeyCode::from(x11::keysym::XK_parenright),
207        '*' => XKeyCode::from(x11::keysym::XK_asterisk),
208        '+' => XKeyCode::from(x11::keysym::XK_plus),
209        ',' => XKeyCode::from(x11::keysym::XK_comma),
210        '-' => XKeyCode::from(x11::keysym::XK_minus),
211        '.' => XKeyCode::from(x11::keysym::XK_period),
212        '/' => XKeyCode::from(x11::keysym::XK_slash),
213        ':' => XKeyCode::from(x11::keysym::XK_colon),
214        ';' => XKeyCode::from(x11::keysym::XK_semicolon),
215        '<' => XKeyCode::from(x11::keysym::XK_less),
216        '=' => XKeyCode::from(x11::keysym::XK_equal),
217        '>' => XKeyCode::from(x11::keysym::XK_greater),
218        '?' => XKeyCode::from(x11::keysym::XK_question),
219        '@' => XKeyCode::from(x11::keysym::XK_at),
220        '[' => XKeyCode::from(x11::keysym::XK_bracketleft),
221        '\'' => XKeyCode::from(x11::keysym::XK_apostrophe),
222        '\\' => XKeyCode::from(x11::keysym::XK_backslash),
223        '\n' => XKeyCode::from(x11::keysym::XK_Return),
224        '\t' => XKeyCode::from(x11::keysym::XK_Tab),
225        ']' => XKeyCode::from(x11::keysym::XK_bracketright),
226        '^' => XKeyCode::from(x11::keysym::XK_asciicircum),
227        '_' => XKeyCode::from(x11::keysym::XK_underscore),
228        '`' => XKeyCode::from(x11::keysym::XK_grave),
229        '{' => XKeyCode::from(x11::keysym::XK_braceleft),
230        '|' => XKeyCode::from(x11::keysym::XK_bar),
231        '}' => XKeyCode::from(x11::keysym::XK_braceright),
232        '~' => XKeyCode::from(x11::keysym::XK_asciitilde),
233        _ => unsafe {
234            let mut buf = [0; 2];
235            x11::xlib::XStringToKeysym(
236                character.encode_utf8(&mut buf).as_ptr() as *const libc::c_char
237            ) as XKeyCode
238        },
239    }
240}
241
242#[cfg(target_os = "macos")]
243fn flags_for_char<'a>(_character: char) -> &'a [Flag] {
244    &[]
245}
246
247#[cfg(windows)]
248fn flags_for_char<'a>(_character: char) -> &'a [Flag] {
249    &[]
250}
251
252#[cfg(target_os = "linux")]
253fn flags_for_char<'a>(character: char) -> &'a [Flag] {
254    const UPPERCASE_CHARACTERS: &[char] = &[
255        '!', '#', '$', '%', '&', '(', ')', '*', '+', ':', '<', '>', '?', '@', '{', '|', '}', '~',
256        '_', '^', '"',
257    ];
258    if character.is_uppercase() || UPPERCASE_CHARACTERS.contains(&character) {
259        &[Flag::Shift]
260    } else {
261        &[]
262    }
263}
264
265impl KeyCodeConvertible for Character {
266    fn character(&self) -> Option<char> {
267        Some(self.0)
268    }
269
270    #[cfg(target_os = "macos")]
271    fn code(&self) -> CGKeyCode {
272        char_to_key_code(self.0)
273    }
274
275    #[cfg(windows)]
276    fn code(&self) -> WinKeyCode {
277        panic!("Unsupported OS")
278    }
279
280    #[cfg(target_os = "linux")]
281    fn code(&self) -> XKeyCode {
282        char_to_key_code(self.0)
283    }
284}
285
286impl KeyCodeConvertible for Code {
287    #[cfg(target_os = "macos")]
288    fn code(&self) -> CGKeyCode {
289        CGKeyCode::from(self.0)
290    }
291
292    #[cfg(windows)]
293    fn code(&self) -> WinKeyCode {
294        WinKeyCode::from(self.0)
295    }
296
297    #[cfg(target_os = "linux")]
298    fn code(&self) -> XKeyCode {
299        XKeyCode::from(self.0)
300    }
301}
302
303#[cfg(target_os = "macos")]
304impl From<Flag> for CGEventFlags {
305    fn from(flag: Flag) -> CGEventFlags {
306        match flag {
307            Flag::Shift => event::CGEventFlags::CGEventFlagShift,
308            Flag::Control => event::CGEventFlags::CGEventFlagControl,
309            Flag::Alt => event::CGEventFlags::CGEventFlagAlternate,
310            Flag::Meta => event::CGEventFlags::CGEventFlagCommand,
311            Flag::Help => event::CGEventFlags::CGEventFlagHelp,
312        }
313    }
314}
315
316/// Mapped to Apple Magic Keyboard, numpad keys may not work with non-English layouts
317#[cfg(target_os = "macos")]
318impl From<KeyCode> for CGKeyCode {
319    fn from(code: KeyCode) -> CGKeyCode {
320        match code {
321            KeyCode::F1 => event::KeyCode::F1,
322            KeyCode::F2 => event::KeyCode::F2,
323            KeyCode::F3 => event::KeyCode::F3,
324            KeyCode::F4 => event::KeyCode::F4,
325            KeyCode::F5 => event::KeyCode::F5,
326            KeyCode::F6 => event::KeyCode::F6,
327            KeyCode::F7 => event::KeyCode::F7,
328            KeyCode::F8 => event::KeyCode::F8,
329            KeyCode::F9 => event::KeyCode::F9,
330            KeyCode::F10 => event::KeyCode::F10,
331            KeyCode::F11 => event::KeyCode::F11,
332            KeyCode::F12 => event::KeyCode::F12,
333            KeyCode::F13 => event::KeyCode::F13,
334            KeyCode::F14 => event::KeyCode::F14,
335            KeyCode::F15 => event::KeyCode::F15,
336            KeyCode::F16 => event::KeyCode::F16,
337            KeyCode::F17 => event::KeyCode::F17,
338            KeyCode::F18 => event::KeyCode::F18,
339            KeyCode::F19 => event::KeyCode::F19,
340            KeyCode::F20 => event::KeyCode::F20,
341            KeyCode::F21 | KeyCode::F22 | KeyCode::F23 | KeyCode::F24 => 0,
342            KeyCode::LeftArrow => event::KeyCode::LEFT_ARROW,
343            KeyCode::Control => event::KeyCode::CONTROL,
344            KeyCode::RightArrow => event::KeyCode::RIGHT_ARROW,
345            KeyCode::DownArrow => event::KeyCode::DOWN_ARROW,
346            KeyCode::End => event::KeyCode::END,
347            KeyCode::UpArrow => event::KeyCode::UP_ARROW,
348            KeyCode::PageUp => event::KeyCode::PAGE_UP,
349            KeyCode::Alt => event::KeyCode::OPTION,
350            KeyCode::Return => event::KeyCode::RETURN,
351            KeyCode::PageDown => event::KeyCode::PAGE_DOWN,
352            KeyCode::Delete => event::KeyCode::DELETE,
353            KeyCode::Home => event::KeyCode::HOME,
354            KeyCode::Escape => event::KeyCode::ESCAPE,
355            KeyCode::Backspace => event::KeyCode::DELETE,
356            KeyCode::Meta => event::KeyCode::COMMAND,
357            KeyCode::CapsLock => event::KeyCode::CAPS_LOCK,
358            KeyCode::Shift => event::KeyCode::SHIFT,
359            KeyCode::Tab => event::KeyCode::TAB,
360            KeyCode::Space => event::KeyCode::SPACE,
361            KeyCode::PrintScreen => event::KeyCode::F13,
362            KeyCode::ScrollLock => event::KeyCode::F14,
363            KeyCode::Pause => event::KeyCode::F15,
364            KeyCode::Num0 => 0x1D,
365            KeyCode::Num1 => 0x12,
366            KeyCode::Num2 => 0x13,
367            KeyCode::Num3 => 0x14,
368            KeyCode::Num4 => 0x15,
369            KeyCode::Num5 => 0x17,
370            KeyCode::Num6 => 0x16,
371            KeyCode::Num7 => 0x1A,
372            KeyCode::Num8 => 0x1C,
373            KeyCode::Num9 => 0x19,
374            KeyCode::NumDecimal => 0x41,
375            KeyCode::NumLock => 0x47,
376            KeyCode::NumDivide => 0x4B,
377            KeyCode::NumMultiply => 0x43,
378            KeyCode::NumSubtract => 0x4E,
379            KeyCode::NumAdd => 0x45,
380            KeyCode::NumEnter => 0x4C,
381        }
382    }
383}
384
385#[cfg(target_os = "macos")]
386fn cg_event_mask_for_flags(flags: &[Flag]) -> CGEventFlags {
387    flags
388        .iter()
389        .map(|&x| CGEventFlags::from(x))
390        .fold(event::CGEventFlags::CGEventFlagNull, |x, y| {
391            x | y as CGEventFlags
392        })
393}
394
395#[cfg(target_os = "macos")]
396fn system_toggle<T: KeyCodeConvertible>(
397    key: &T,
398    down: bool,
399    flags: &[Flag],
400    _modifier_delay_ms: u64,
401) {
402    use core_graphics::event::CGEventType::*;
403    use core_graphics::event::{CGEventTapLocation, CGEventType};
404    let source = CGEventSource::new(HIDSystemState).unwrap();
405
406    if flags.is_empty() {
407        if let Some(character) = key.character() {
408            let mut buf = [0; 2];
409            let event = CGEvent::new_keyboard_event(source, 0, down).unwrap();
410            event.set_string_from_utf16_unchecked(character.encode_utf16(&mut buf));
411            event.post(CGEventTapLocation::HID);
412            return;
413        }
414    }
415
416    let code = key.code();
417    if code != 0 {
418        let event = CGEvent::new_keyboard_event(source, code, down).unwrap();
419        let event_type: CGEventType = if down { KeyDown } else { KeyUp };
420        event.set_type(event_type);
421        event.set_flags(cg_event_mask_for_flags(flags));
422        event.post(CGEventTapLocation::HID);
423    }
424}
425
426#[cfg(windows)]
427type WinKeyCode = i32;
428
429#[cfg(windows)]
430impl From<Flag> for WinKeyCode {
431    fn from(flag: Flag) -> WinKeyCode {
432        use winapi::um::winuser;
433        let win_code = match flag {
434            Flag::Shift => winuser::VK_SHIFT,
435            Flag::Control => winuser::VK_CONTROL,
436            Flag::Alt => winuser::VK_MENU,
437            Flag::Meta => winuser::VK_LWIN,
438            Flag::Help => winuser::VK_HELP,
439        };
440        win_code as WinKeyCode
441    }
442}
443
444#[cfg(windows)]
445impl From<KeyCode> for WinKeyCode {
446    fn from(code: KeyCode) -> WinKeyCode {
447        use winapi::um::winuser;
448        let win_code = match code {
449            KeyCode::F1 => winuser::VK_F1,
450            KeyCode::F2 => winuser::VK_F2,
451            KeyCode::F3 => winuser::VK_F3,
452            KeyCode::F4 => winuser::VK_F4,
453            KeyCode::F5 => winuser::VK_F5,
454            KeyCode::F6 => winuser::VK_F6,
455            KeyCode::F7 => winuser::VK_F7,
456            KeyCode::F8 => winuser::VK_F8,
457            KeyCode::F9 => winuser::VK_F9,
458            KeyCode::F10 => winuser::VK_F10,
459            KeyCode::F11 => winuser::VK_F11,
460            KeyCode::F12 => winuser::VK_F12,
461            KeyCode::F13 => winuser::VK_F13,
462            KeyCode::F14 => winuser::VK_F14,
463            KeyCode::F15 => winuser::VK_F15,
464            KeyCode::F16 => winuser::VK_F16,
465            KeyCode::F17 => winuser::VK_F17,
466            KeyCode::F18 => winuser::VK_F18,
467            KeyCode::F19 => winuser::VK_F19,
468            KeyCode::F20 => winuser::VK_F20,
469            KeyCode::F21 => winuser::VK_F21,
470            KeyCode::F22 => winuser::VK_F22,
471            KeyCode::F23 => winuser::VK_F23,
472            KeyCode::F24 => winuser::VK_F24,
473            KeyCode::LeftArrow => winuser::VK_LEFT,
474            KeyCode::Control => winuser::VK_CONTROL,
475            KeyCode::RightArrow => winuser::VK_RIGHT,
476            KeyCode::DownArrow => winuser::VK_DOWN,
477            KeyCode::End => winuser::VK_END,
478            KeyCode::UpArrow => winuser::VK_UP,
479            KeyCode::PageUp => winuser::VK_PRIOR,
480            KeyCode::Alt => winuser::VK_MENU,
481            KeyCode::Return => winuser::VK_RETURN,
482            KeyCode::PageDown => winuser::VK_NEXT,
483            KeyCode::Delete => winuser::VK_DELETE,
484            KeyCode::Insert => winuser::VK_INSERT,
485            KeyCode::Home => winuser::VK_HOME,
486            KeyCode::Escape => winuser::VK_ESCAPE,
487            KeyCode::Backspace => winuser::VK_BACK,
488            KeyCode::Meta => winuser::VK_LWIN,
489            KeyCode::CapsLock => winuser::VK_CAPITAL,
490            KeyCode::Shift => winuser::VK_SHIFT,
491            KeyCode::Tab => winuser::VK_TAB,
492            KeyCode::Space => winuser::VK_SPACE,
493            KeyCode::PrintScreen => winuser::VK_SNAPSHOT,
494            KeyCode::ScrollLock => winuser::VK_SCROLL,
495            KeyCode::Pause => winuser::VK_PAUSE,
496            KeyCode::Num0 => winuser::VK_NUMPAD0,
497            KeyCode::Num1 => winuser::VK_NUMPAD1,
498            KeyCode::Num2 => winuser::VK_NUMPAD2,
499            KeyCode::Num3 => winuser::VK_NUMPAD3,
500            KeyCode::Num4 => winuser::VK_NUMPAD4,
501            KeyCode::Num5 => winuser::VK_NUMPAD5,
502            KeyCode::Num6 => winuser::VK_NUMPAD6,
503            KeyCode::Num7 => winuser::VK_NUMPAD7,
504            KeyCode::Num8 => winuser::VK_NUMPAD8,
505            KeyCode::Num9 => winuser::VK_NUMPAD9,
506            KeyCode::NumDecimal => winuser::VK_DECIMAL,
507            KeyCode::NumLock => winuser::VK_NUMLOCK,
508            KeyCode::NumDivide => winuser::VK_DIVIDE,
509            KeyCode::NumMultiply => winuser::VK_MULTIPLY,
510            KeyCode::NumSubtract => winuser::VK_SUBTRACT,
511            KeyCode::NumAdd => winuser::VK_ADD,
512            KeyCode::NumEnter => winuser::VK_RETURN,
513        };
514        win_code as WinKeyCode
515    }
516}
517
518#[cfg(windows)]
519fn win_send_key_event(keycode: WinKeyCode, down: bool, delay_ms: u64) {
520    use winapi::um::winuser::{keybd_event, KEYEVENTF_KEYUP};
521    let flags = if down { 0 } else { KEYEVENTF_KEYUP };
522    unsafe { keybd_event(keycode as u8, 0, flags, 0) };
523    std::thread::sleep(std::time::Duration::from_millis(delay_ms));
524}
525
526#[cfg(windows)]
527fn system_toggle<T: KeyCodeConvertible>(
528    key: &T,
529    down: bool,
530    flags: &[Flag],
531    modifier_delay_ms: u64,
532) {
533    use winapi::um::winuser::{
534        SendInput, INPUT, INPUT_u, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP, KEYEVENTF_UNICODE,
535    };
536    use std::mem::size_of;
537
538    for &flag in flags.iter() {
539        win_send_key_event(WinKeyCode::from(flag), down, modifier_delay_ms);
540    }
541    if let Some(character) = key.character() {
542        let flags = if down { 0 } else { KEYEVENTF_KEYUP };
543        let mut buf = [0; 2];
544        for word in character.encode_utf16(&mut buf) {
545            let mut input = INPUT {
546                type_: INPUT_KEYBOARD,
547                u: unsafe {
548                    std::mem::transmute_copy(&(
549                        KEYBDINPUT {
550                            wVk: 0,
551                            wScan: *word,
552                            dwFlags: KEYEVENTF_UNICODE | flags,
553                            time: 0,
554                            dwExtraInfo: 0,
555                        },
556                        [0; size_of::<INPUT_u>() - size_of::<KEYBDINPUT>()],
557                    ))
558                },
559            };
560            unsafe {
561                SendInput(1, &mut input, std::mem::size_of::<INPUT>() as i32);
562            }
563        }
564    } else {
565        win_send_key_event(key.code(), down, 0);
566    }
567}
568
569#[cfg(target_os = "linux")]
570type XKeyCode = u64;
571
572#[cfg(target_os = "linux")]
573impl From<Flag> for XKeyCode {
574    fn from(flag: Flag) -> XKeyCode {
575        let x_code = match flag {
576            Flag::Shift => x11::keysym::XK_Shift_L,
577            Flag::Control => x11::keysym::XK_Control_L,
578            Flag::Alt => x11::keysym::XK_Alt_L,
579            Flag::Meta => x11::keysym::XK_Meta_L,
580            Flag::Help => x11::keysym::XK_Help,
581        };
582        XKeyCode::from(x_code)
583    }
584}
585
586#[cfg(target_os = "linux")]
587impl From<KeyCode> for XKeyCode {
588    fn from(code: KeyCode) -> XKeyCode {
589        let x_code = match code {
590            KeyCode::F1 => x11::keysym::XK_F1,
591            KeyCode::F2 => x11::keysym::XK_F2,
592            KeyCode::F3 => x11::keysym::XK_F3,
593            KeyCode::F4 => x11::keysym::XK_F4,
594            KeyCode::F5 => x11::keysym::XK_F5,
595            KeyCode::F6 => x11::keysym::XK_F6,
596            KeyCode::F7 => x11::keysym::XK_F7,
597            KeyCode::F8 => x11::keysym::XK_F8,
598            KeyCode::F9 => x11::keysym::XK_F9,
599            KeyCode::F10 => x11::keysym::XK_F10,
600            KeyCode::F11 => x11::keysym::XK_F11,
601            KeyCode::F12 => x11::keysym::XK_F12,
602            KeyCode::F13 => x11::keysym::XK_F13,
603            KeyCode::F14 => x11::keysym::XK_F14,
604            KeyCode::F15 => x11::keysym::XK_F15,
605            KeyCode::F16 => x11::keysym::XK_F16,
606            KeyCode::F17 => x11::keysym::XK_F17,
607            KeyCode::F18 => x11::keysym::XK_F18,
608            KeyCode::F19 => x11::keysym::XK_F19,
609            KeyCode::F20 => x11::keysym::XK_F20,
610            KeyCode::F21 => x11::keysym::XK_F21,
611            KeyCode::F22 => x11::keysym::XK_F22,
612            KeyCode::F23 => x11::keysym::XK_F23,
613            KeyCode::F24 => x11::keysym::XK_F24,
614            KeyCode::LeftArrow => x11::keysym::XK_Left,
615            KeyCode::Control => x11::keysym::XK_Control_L,
616            KeyCode::RightArrow => x11::keysym::XK_Right,
617            KeyCode::DownArrow => x11::keysym::XK_Down,
618            KeyCode::End => x11::keysym::XK_End,
619            KeyCode::UpArrow => x11::keysym::XK_Up,
620            KeyCode::PageUp => x11::keysym::XK_Page_Up,
621            KeyCode::Alt => x11::keysym::XK_Alt_L,
622            KeyCode::Return => x11::keysym::XK_Return,
623            KeyCode::PageDown => x11::keysym::XK_Page_Down,
624            KeyCode::Delete => x11::keysym::XK_Delete,
625            KeyCode::Insert => x11::keysym::XK_Insert,
626            KeyCode::Home => x11::keysym::XK_Home,
627            KeyCode::Escape => x11::keysym::XK_Escape,
628            KeyCode::Backspace => x11::keysym::XK_BackSpace,
629            KeyCode::Meta => x11::keysym::XK_Meta_L,
630            KeyCode::CapsLock => x11::keysym::XK_Caps_Lock,
631            KeyCode::Shift => x11::keysym::XK_Shift_L,
632            KeyCode::Tab => x11::keysym::XK_Tab,
633            KeyCode::Space => x11::keysym::XK_space,
634            KeyCode::PrintScreen => x11::keysym::XK_Print,
635            KeyCode::ScrollLock => x11::keysym::XK_Scroll_Lock,
636            KeyCode::Pause => x11::keysym::XK_Pause,
637            KeyCode::Num0 => x11::keysym::XK_KP_0,
638            KeyCode::Num1 => x11::keysym::XK_KP_1,
639            KeyCode::Num2 => x11::keysym::XK_KP_2,
640            KeyCode::Num3 => x11::keysym::XK_KP_3,
641            KeyCode::Num4 => x11::keysym::XK_KP_4,
642            KeyCode::Num5 => x11::keysym::XK_KP_5,
643            KeyCode::Num6 => x11::keysym::XK_KP_6,
644            KeyCode::Num7 => x11::keysym::XK_KP_7,
645            KeyCode::Num8 => x11::keysym::XK_KP_8,
646            KeyCode::Num9 => x11::keysym::XK_KP_9,
647            KeyCode::NumDecimal => x11::keysym::XK_KP_Decimal,
648            KeyCode::NumLock => x11::keysym::XK_Num_Lock,
649            KeyCode::NumDivide => x11::keysym::XK_KP_Divide,
650            KeyCode::NumMultiply => x11::keysym::XK_KP_Multiply,
651            KeyCode::NumSubtract => x11::keysym::XK_KP_Subtract,
652            KeyCode::NumAdd => x11::keysym::XK_KP_Add,
653            KeyCode::NumEnter => x11::keysym::XK_KP_Enter,
654        };
655        XKeyCode::from(x_code)
656    }
657}
658
659#[cfg(target_os = "linux")]
660fn x_send_key_event(
661    display: *mut x11::xlib::Display,
662    keycode: XKeyCode,
663    down: bool,
664    delay_ms: u64,
665) {
666    unsafe {
667        XTestFakeKeyEvent(
668            display,
669            x11::xlib::XKeysymToKeycode(display, keycode as libc::c_ulong),
670            down as i32,
671            x11::xlib::CurrentTime,
672        );
673        x11::xlib::XFlush(display);
674    };
675
676    std::thread::sleep(std::time::Duration::from_millis(delay_ms));
677}
678
679#[cfg(target_os = "linux")]
680fn system_toggle<T: KeyCodeConvertible>(
681    key: &T,
682    down: bool,
683    flags: &[Flag],
684    modifier_delay_ms: u64,
685) {
686    internal::X_MAIN_DISPLAY.with(|display| {
687        for &flag in flags.iter() {
688            x_send_key_event(
689                display.as_ptr(),
690                XKeyCode::from(flag),
691                down,
692                modifier_delay_ms,
693            );
694        }
695        x_send_key_event(display.as_ptr(), key.code(), down, 0);
696    })
697}
698
699#[cfg(target_os = "linux")]
700extern "C" {
701    fn XTestFakeKeyEvent(
702        display: *mut x11::xlib::Display,
703        keycode: u8,
704        is_press: i32,
705        delay: x11::xlib::Time,
706    ) -> i32;
707}