load keyboard layout for mac
This commit is contained in:
parent
7b1806b0f3
commit
780b57f597
126
Cargo.lock
generated
126
Cargo.lock
generated
@ -470,9 +470,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.2"
|
||||
version = "4.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5"
|
||||
checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
@ -601,8 +601,8 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mach",
|
||||
"ndk",
|
||||
"ndk-glue",
|
||||
"ndk 0.4.0",
|
||||
"ndk-glue 0.4.0",
|
||||
"nix 0.23.1",
|
||||
"oboe",
|
||||
"parking_lot",
|
||||
@ -671,8 +671,18 @@ version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.10.2",
|
||||
"darling_macro 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4"
|
||||
dependencies = [
|
||||
"darling_core 0.13.1",
|
||||
"darling_macro 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -689,13 +699,38 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.10.2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b"
|
||||
dependencies = [
|
||||
"darling_core 0.13.1",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@ -2116,7 +2151,20 @@ checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"jni-sys",
|
||||
"ndk-sys",
|
||||
"ndk-sys 0.2.2",
|
||||
"num_enum",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"jni-sys",
|
||||
"ndk-sys 0.3.0",
|
||||
"num_enum",
|
||||
"thiserror",
|
||||
]
|
||||
@ -2130,9 +2178,23 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
"ndk-macro",
|
||||
"ndk-sys",
|
||||
"ndk 0.4.0",
|
||||
"ndk-macro 0.2.0",
|
||||
"ndk-sys 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-glue"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04c0d14b0858eb9962a5dac30b809b19f19da7e4547d64af2b0bb051d2e55d79"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk 0.6.0",
|
||||
"ndk-macro 0.3.0",
|
||||
"ndk-sys 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2141,19 +2203,41 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.10.2",
|
||||
"proc-macro-crate 0.1.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-macro"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
|
||||
dependencies = [
|
||||
"darling 0.13.1",
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97"
|
||||
dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.37"
|
||||
@ -2371,13 +2455,13 @@ checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
|
||||
|
||||
[[package]]
|
||||
name = "oboe"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e15e22bc67e047fe342a32ecba55f555e3be6166b04dd157cd0f803dfa9f48e1"
|
||||
checksum = "2463c8f2e19b4e0d0710a21f8e4011501ff28db1c95d7a5482a553b2100502d2"
|
||||
dependencies = [
|
||||
"jni",
|
||||
"ndk",
|
||||
"ndk-glue",
|
||||
"ndk 0.6.0",
|
||||
"ndk-glue 0.6.0",
|
||||
"num-derive",
|
||||
"num-traits 0.2.14",
|
||||
"oboe-sys",
|
||||
@ -2385,9 +2469,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oboe-sys"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "338142ae5ab0aaedc8275aa8f67f460e43ae0fca76a695a742d56da0a269eadc"
|
||||
checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@ -2400,9 +2484,9 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
@ -2958,7 +3042,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rdev"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/open-trade/rdev#2a3205a13102907da2442a369f8b704601eecc9d"
|
||||
source = "git+https://github.com/open-trade/rdev#341a4237d2cecfca20f59febddc2ddde29c84449"
|
||||
dependencies = [
|
||||
"cocoa 0.22.0",
|
||||
"core-foundation 0.7.0",
|
||||
|
@ -5,18 +5,73 @@ use core_graphics;
|
||||
use self::core_graphics::display::*;
|
||||
use self::core_graphics::event::*;
|
||||
use self::core_graphics::event_source::*;
|
||||
use std::collections::HashMap as Map;
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::*;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use crate::macos::keycodes::*;
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use objc::runtime::Class;
|
||||
|
||||
struct MyCGEvent;
|
||||
type TISInputSourceRef = *mut c_void;
|
||||
type CFDataRef = *const c_void;
|
||||
type OptionBits = u32;
|
||||
type OSStatus = i32;
|
||||
type UniChar = u16;
|
||||
type UniCharCount = usize;
|
||||
type Boolean = c_uchar;
|
||||
type CFStringEncoding = u32;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct __CFString([u8; 0]);
|
||||
type CFStringRef = *const __CFString;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kCFStringEncodingUTF8: u32 = 134_217_984;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kUCKeyActionDisplay: u16 = 3;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kUCKeyTranslateDeadKeysBit: OptionBits = 1 << 31;
|
||||
const BUF_LEN: usize = 4;
|
||||
|
||||
#[allow(improper_ctypes)]
|
||||
#[allow(non_snake_case)]
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
fn CFDataGetBytePtr(theData: CFDataRef) -> *const u8;
|
||||
fn TISCopyCurrentKeyboardInputSource() -> TISInputSourceRef;
|
||||
fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
|
||||
fn TISCopyCurrentASCIICapableKeyboardLayoutInputSource() -> TISInputSourceRef;
|
||||
static kTISPropertyUnicodeKeyLayoutData: *mut c_void;
|
||||
static kTISPropertyInputSourceID: *mut c_void;
|
||||
fn UCKeyTranslate(
|
||||
keyLayoutPtr: *const u8, //*const UCKeyboardLayout,
|
||||
virtualKeyCode: u16,
|
||||
keyAction: u16,
|
||||
modifierKeyState: u32,
|
||||
keyboardType: u32,
|
||||
keyTranslateOptions: OptionBits,
|
||||
deadKeyState: *mut u32,
|
||||
maxStringLength: UniCharCount,
|
||||
actualStringLength: *mut UniCharCount,
|
||||
unicodeString: *mut [UniChar; BUF_LEN],
|
||||
) -> OSStatus;
|
||||
fn LMGetKbdType() -> u8;
|
||||
fn CFStringGetCString(
|
||||
theString: CFStringRef,
|
||||
buffer: *mut c_char,
|
||||
bufferSize: CFIndex,
|
||||
encoding: CFStringEncoding,
|
||||
) -> Boolean;
|
||||
|
||||
fn CGEventPost(tapLocation: CGEventTapLocation, event: *mut MyCGEvent);
|
||||
// Actually return CFDataRef which is const here, but for coding convienence, return *mut c_void
|
||||
fn TISGetInputSourceProperty(source: TISInputSourceRef, property: *const c_void)
|
||||
-> *mut c_void;
|
||||
// not present in servo/core-graphics
|
||||
fn CGEventCreateScrollWheelEvent(
|
||||
source: &CGEventSourceRef,
|
||||
@ -51,6 +106,7 @@ pub struct Enigo {
|
||||
last_click_time: Option<std::time::Instant>,
|
||||
multiple_click: i64,
|
||||
flags: CGEventFlags,
|
||||
char_to_vkey_map: Map<String, Map<char, CGKeyCode>>,
|
||||
}
|
||||
|
||||
impl Enigo {
|
||||
@ -102,6 +158,7 @@ impl Default for Enigo {
|
||||
multiple_click: 1,
|
||||
last_click_time: None,
|
||||
flags: CGEventFlags::CGEventFlagNull,
|
||||
char_to_vkey_map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -433,7 +490,32 @@ impl Enigo {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn map_key_board(&self, ch: char) -> CGKeyCode {
|
||||
fn map_key_board(&mut self, ch: char) -> CGKeyCode {
|
||||
let mut code = 0;
|
||||
unsafe {
|
||||
let (keyboard, layout) = get_layout();
|
||||
if !keyboard.is_null() && !layout.is_null() {
|
||||
let name_ref = TISGetInputSourceProperty(keyboard, kTISPropertyInputSourceID);
|
||||
if !name_ref.is_null() {
|
||||
let name = get_string(name_ref as _);
|
||||
if let Some(name) = name {
|
||||
if let Some(m) = self.char_to_vkey_map.get(&name) {
|
||||
code = *m.get(&ch).unwrap_or(&0);
|
||||
} else {
|
||||
let m = get_map(&name, layout);
|
||||
code = *m.get(&ch).unwrap_or(&0);
|
||||
self.char_to_vkey_map.insert(name.clone(), m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
}
|
||||
if code > 0 {
|
||||
return code;
|
||||
}
|
||||
match ch {
|
||||
'a' => kVK_ANSI_A,
|
||||
'b' => kVK_ANSI_B,
|
||||
@ -487,4 +569,99 @@ impl Enigo {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_string(cf_string: CFStringRef) -> Option<String> {
|
||||
if !cf_string.is_null() {
|
||||
let mut buf: [i8; 255] = [0; 255];
|
||||
let success = CFStringGetCString(
|
||||
cf_string,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as _,
|
||||
kCFStringEncodingUTF8,
|
||||
);
|
||||
if success != 0 {
|
||||
let name: &CStr = CStr::from_ptr(buf.as_ptr());
|
||||
if let Ok(name) = name.to_str() {
|
||||
return Some(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_layout() -> (TISInputSourceRef, *const u8) {
|
||||
let mut keyboard = TISCopyCurrentKeyboardInputSource();
|
||||
let mut layout = null_mut();
|
||||
if !keyboard.is_null() {
|
||||
layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
|
||||
}
|
||||
if layout.is_null() {
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
// https://github.com/microsoft/vscode/issues/23833
|
||||
keyboard = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if !keyboard.is_null() {
|
||||
layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
|
||||
}
|
||||
}
|
||||
if layout.is_null() {
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
keyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
|
||||
if !keyboard.is_null() {
|
||||
layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
|
||||
}
|
||||
}
|
||||
if layout.is_null() {
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
return (null_mut(), null_mut());
|
||||
}
|
||||
let layout_ptr = CFDataGetBytePtr(layout as _);
|
||||
if layout_ptr.is_null() {
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
return (null_mut(), null_mut());
|
||||
}
|
||||
(keyboard, layout_ptr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_map(name: &str, layout: *const u8) -> Map<char, CGKeyCode> {
|
||||
log::info!("Create keyboard map for {}", name);
|
||||
let mut keys_down: u32 = 0;
|
||||
let mut map = Map::new();
|
||||
for keycode in 0..128 {
|
||||
let mut buff = [0_u16; BUF_LEN];
|
||||
let kb_type = unsafe { LMGetKbdType() };
|
||||
let mut length: UniCharCount = 0;
|
||||
let _retval = unsafe {
|
||||
UCKeyTranslate(
|
||||
layout,
|
||||
keycode,
|
||||
kUCKeyActionDisplay as _,
|
||||
0,
|
||||
kb_type as _,
|
||||
kUCKeyTranslateDeadKeysBit as _,
|
||||
&mut keys_down,
|
||||
BUF_LEN,
|
||||
&mut length,
|
||||
&mut buff,
|
||||
)
|
||||
};
|
||||
if length > 0 {
|
||||
if let Ok(str) = String::from_utf16(&buff[..length]) {
|
||||
if let Some(chr) = str.chars().next() {
|
||||
map.insert(chr, keycode as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
unsafe impl Send for Enigo {}
|
||||
|
@ -40,7 +40,7 @@ struct Input {
|
||||
time: i64,
|
||||
}
|
||||
|
||||
const KEY_CHAR_START: i32 = 9999;
|
||||
const KEY_CHAR_START: u64 = 9999;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MouseCursorSub {
|
||||
@ -163,7 +163,7 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ENIGO: Arc<Mutex<Enigo>> = Arc::new(Mutex::new(Enigo::new()));
|
||||
static ref KEYS_DOWN: Arc<Mutex<HashMap<i32, Instant>>> = Default::default();
|
||||
static ref KEYS_DOWN: Arc<Mutex<HashMap<u64, Instant>>> = Default::default();
|
||||
static ref LATEST_INPUT: Arc<Mutex<Input>> = Default::default();
|
||||
}
|
||||
static EXITING: AtomicBool = AtomicBool::new(false);
|
||||
@ -246,6 +246,11 @@ pub fn fix_key_down_timeout_at_exit() {
|
||||
log::info!("fix_key_down_timeout_at_exit");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_layout(key: u32) -> Key {
|
||||
Key::Layout(std::char::from_u32(key).unwrap_or('\0'))
|
||||
}
|
||||
|
||||
fn fix_key_down_timeout(force: bool) {
|
||||
if KEYS_DOWN.lock().unwrap().is_empty() {
|
||||
return;
|
||||
@ -256,13 +261,13 @@ fn fix_key_down_timeout(force: bool) {
|
||||
if force || value.elapsed().as_millis() >= 3_000 {
|
||||
KEYS_DOWN.lock().unwrap().remove(&key);
|
||||
let key = if key < KEY_CHAR_START {
|
||||
if let Some(key) = KEY_MAP.get(&key) {
|
||||
if let Some(key) = KEY_MAP.get(&(key as _)) {
|
||||
Some(*key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(Key::Layout(((key - KEY_CHAR_START) as u8) as _))
|
||||
Some(get_layout((key - KEY_CHAR_START) as _))
|
||||
};
|
||||
if let Some(key) = key {
|
||||
let func = move || {
|
||||
@ -604,10 +609,13 @@ fn handle_key_(evt: &KeyEvent) {
|
||||
}
|
||||
if evt.down {
|
||||
allow_err!(en.key_down(key.clone()));
|
||||
KEYS_DOWN.lock().unwrap().insert(ck.value(), Instant::now());
|
||||
KEYS_DOWN
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(ck.value() as _, Instant::now());
|
||||
} else {
|
||||
en.key_up(key.clone());
|
||||
KEYS_DOWN.lock().unwrap().remove(&ck.value());
|
||||
KEYS_DOWN.lock().unwrap().remove(&(ck.value() as _));
|
||||
}
|
||||
} else if ck.value() == ControlKey::CtrlAltDel.value() {
|
||||
// have to spawn new thread because send_sas is tokio_main, the caller can not be tokio_main.
|
||||
@ -621,17 +629,17 @@ fn handle_key_(evt: &KeyEvent) {
|
||||
}
|
||||
Some(key_event::Union::chr(chr)) => {
|
||||
if evt.down {
|
||||
allow_err!(en.key_down(Key::Layout(chr as u8 as _)));
|
||||
allow_err!(en.key_down(get_layout(chr)));
|
||||
KEYS_DOWN
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(chr as i32 + KEY_CHAR_START, Instant::now());
|
||||
.insert(chr as u64 + KEY_CHAR_START, Instant::now());
|
||||
} else {
|
||||
en.key_up(Key::Layout(chr as u8 as _));
|
||||
en.key_up(get_layout(chr));
|
||||
KEYS_DOWN
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(&(chr as i32 + KEY_CHAR_START));
|
||||
.remove(&(chr as u64 + KEY_CHAR_START));
|
||||
}
|
||||
}
|
||||
Some(key_event::Union::unicode(chr)) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user