diff --git a/Cargo.lock b/Cargo.lock index a6ed80ad..6b5654a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,21 +689,6 @@ dependencies = [ "cc", ] -[[package]] -name = "cocoa" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667fdc068627a2816b9ff831201dd9864249d6ee8d190b9532357f1fc0f61ea7" -dependencies = [ - "bitflags", - "block", - "core-foundation 0.9.3", - "core-graphics 0.21.0", - "foreign-types", - "libc", - "objc", -] - [[package]] name = "cocoa" version = "0.24.0" @@ -821,18 +806,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-graphics" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a67c4378cf203eace8fb6567847eb641fd6ff933c1145a115c6ee820ebb978" -dependencies = [ - "bitflags", - "core-foundation 0.9.3", - "foreign-types", - "libc", -] - [[package]] name = "core-graphics" version = "0.22.3" @@ -1394,12 +1367,33 @@ dependencies = [ "log", "objc", "pkg-config", + "rdev", "serde 1.0.139", "serde_derive", "unicode-segmentation", "winapi 0.3.9", ] +[[package]] +name = "enum-map" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ddfe61e8040145222887d0d32a939c70c8cae681490d72fb868305e9b40ced8" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d1c54e25a57236a790ecf051c2befbb57740c9b86c4273eac378ba84d620d6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "enum_dispatch" version = "0.3.8" @@ -3921,15 +3915,17 @@ dependencies = [ [[package]] name = "rdev" -version = "0.5.0-1" -source = "git+https://github.com/open-trade/rdev#a9b6ea462956f289b4a48e81f2ea7dda33cd8047" +version = "0.5.0-2" +source = "git+https://github.com/asur4s/rdev#895c8fb1a6106714793e8877d35d2b7a1c57ce9c" dependencies = [ - "cocoa 0.22.0", - "core-foundation 0.7.0", - "core-foundation-sys 0.7.0", - "core-graphics 0.19.2", + "cocoa", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", + "core-graphics 0.22.3", + "enum-map", "lazy_static", "libc", + "widestring 1.0.2", "winapi 0.3.9", "x11", ] @@ -4158,7 +4154,7 @@ dependencies = [ "cfg-if 1.0.0", "clap 3.2.12", "clipboard", - "cocoa 0.24.0", + "cocoa", "core-foundation 0.9.3", "core-graphics 0.22.3", "cpal", @@ -4186,6 +4182,7 @@ dependencies = [ "mouce", "num_cpus", "objc", + "once_cell", "parity-tokio-ipc", "rdev", "repng", @@ -4205,6 +4202,7 @@ dependencies = [ "sys-locale", "sysinfo", "system_shutdown", + "tfc", "tray-item", "trayicon", "uuid", @@ -4930,6 +4928,16 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +[[package]] +name = "tfc" +version = "0.6.1" +source = "git+https://github.com/asur4s/The-Fat-Controller#25bfa7ef1cb0bd0b522cc4155dea6b99673bcfd4" +dependencies = [ + "core-graphics 0.22.3", + "unicode-segmentation", + "winapi 0.3.9", +] + [[package]] name = "thiserror" version = "1.0.31" @@ -5143,7 +5151,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76863575f7842ed64fda361f417a787efa82811b4617267709066969cd4ccf3b" dependencies = [ - "cocoa 0.24.0", + "cocoa", "core-graphics 0.22.3", "gtk", "libappindicator", @@ -5565,6 +5573,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.2.8" @@ -5638,7 +5652,7 @@ checksum = "0c643e10139d127d30d6d753398c8a6f0a43532e8370f6c9d29ebbff29b984ab" dependencies = [ "bitflags", "err-derive", - "widestring", + "widestring 0.4.3", "winapi 0.3.9", ] @@ -5765,7 +5779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" dependencies = [ "bitflags", - "cocoa 0.24.0", + "cocoa", "core-foundation 0.9.3", "core-graphics 0.22.3", "core-video-sys", diff --git a/Cargo.toml b/Cargo.toml index f48a47d9..5e6d509e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,9 @@ sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" sys-locale = "0.2" enigo = { path = "libs/enigo", features = [ "with_serde" ] } clipboard = { path = "libs/clipboard" } -rdev = { git = "https://github.com/open-trade/rdev" } +rdev = { git = "https://github.com/asur4s/rdev" } +tfc = { git = "https://github.com/asur4s/The-Fat-Controller" } +once_cell = "1.13.0" ctrlc = "3.2" arboard = "2.0" #minreq = { version = "2.4", features = ["punycode", "https-native"] } @@ -105,6 +107,7 @@ async-process = "1.3" mouce = { git="https://github.com/fufesou/mouce.git" } evdev = { git="https://github.com/fufesou/evdev" } + [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.11" jni = "0.19" diff --git a/libs/enigo/Cargo.toml b/libs/enigo/Cargo.toml index b0028b56..fec03d34 100644 --- a/libs/enigo/Cargo.toml +++ b/libs/enigo/Cargo.toml @@ -22,6 +22,7 @@ appveyor = { repository = "pythoneer/enigo-85xiy" } serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } log = "0.4" +rdev = { git = "https://github.com/asur4s/rdev" } hbb_common = { path = "../hbb_common" } [features] diff --git a/libs/enigo/src/lib.rs b/libs/enigo/src/lib.rs index 164fb1c1..01e9b67d 100644 --- a/libs/enigo/src/lib.rs +++ b/libs/enigo/src/lib.rs @@ -257,7 +257,7 @@ pub enum Key { Backspace, /// caps lock key CapsLock, - #[deprecated(since = "0.0.12", note = "now renamed to Meta")] + // #[deprecated(since = "0.0.12", note = "now renamed to Meta")] /// command key on macOS (super key on Linux, windows key on Windows) Command, /// control key @@ -314,14 +314,14 @@ pub enum Key { Shift, /// space key Space, - #[deprecated(since = "0.0.12", note = "now renamed to Meta")] + // #[deprecated(since = "0.0.12", note = "now renamed to Meta")] /// super key on linux (command key on macOS, windows key on Windows) Super, /// tab key (tabulator) Tab, /// up arrow key UpArrow, - #[deprecated(since = "0.0.12", note = "now renamed to Meta")] + // #[deprecated(since = "0.0.12", note = "now renamed to Meta")] /// windows key on Windows (super key on Linux, command key on macOS) Windows, /// diff --git a/libs/enigo/src/linux/nix_impl.rs b/libs/enigo/src/linux/nix_impl.rs index 840290b2..7a8f6668 100644 --- a/libs/enigo/src/linux/nix_impl.rs +++ b/libs/enigo/src/linux/nix_impl.rs @@ -167,12 +167,7 @@ impl KeyboardControllable for Enigo { } } fn key_click(&mut self, key: Key) { - if self.is_x11 { - self.xdo.key_click(key) - } else { - if let Some(keyboard) = &mut self.uinput_keyboard { - keyboard.key_click(key) - } - } + self.key_down(key).ok(); + self.key_up(key); } } diff --git a/libs/enigo/src/linux/pynput.rs b/libs/enigo/src/linux/pynput.rs index 748b3010..836c645f 100644 --- a/libs/enigo/src/linux/pynput.rs +++ b/libs/enigo/src/linux/pynput.rs @@ -67,16 +67,16 @@ impl EnigoPynput { Key::Space => "space", Key::Tab => "Tab", Key::UpArrow => "Up", - Key::Numpad0 => "0", - Key::Numpad1 => "1", - Key::Numpad2 => "2", - Key::Numpad3 => "3", - Key::Numpad4 => "4", - Key::Numpad5 => "5", - Key::Numpad6 => "6", - Key::Numpad7 => "7", - Key::Numpad8 => "8", - Key::Numpad9 => "9", + Key::Numpad0 => "KP_0", + Key::Numpad1 => "KP_1", + Key::Numpad2 => "KP_2", + Key::Numpad3 => "KP_3", + Key::Numpad4 => "KP_4", + Key::Numpad5 => "KP_5", + Key::Numpad6 => "KP_6", + Key::Numpad7 => "KP_7", + Key::Numpad8 => "KP_8", + Key::Numpad9 => "KP_9", Key::Decimal => "KP_Decimal", Key::Cancel => "Cancel", Key::Clear => "Clear", @@ -110,7 +110,6 @@ impl EnigoPynput { return true; } }; - log::info!("send pynput: {:?}", &s); return self.tx.send((PyMsg::Str(s), is_press)).is_ok(); } } diff --git a/libs/enigo/src/linux/xdo.rs b/libs/enigo/src/linux/xdo.rs index 0e3b79ab..ff687eee 100644 --- a/libs/enigo/src/linux/xdo.rs +++ b/libs/enigo/src/linux/xdo.rs @@ -370,4 +370,19 @@ impl KeyboardControllable for EnigoXdo { } } } + + fn key_sequence_parse(&mut self, sequence: &str) + where + Self: Sized, + { + self.key_sequence_parse_try(sequence) + .expect("Could not parse sequence"); + } + + fn key_sequence_parse_try(&mut self, sequence: &str) -> Result<(), crate::dsl::ParseError> + where + Self: Sized, + { + crate::dsl::eval(self, sequence) + } } diff --git a/libs/enigo/src/macos/macos_impl.rs b/libs/enigo/src/macos/macos_impl.rs index 28c9362e..520c9dca 100644 --- a/libs/enigo/src/macos/macos_impl.rs +++ b/libs/enigo/src/macos/macos_impl.rs @@ -1,5 +1,4 @@ use core_graphics; - // TODO(dustin): use only the things i need use self::core_graphics::display::*; diff --git a/libs/enigo/src/win/win_impl.rs b/libs/enigo/src/win/win_impl.rs index ea1543fa..56fc4cae 100644 --- a/libs/enigo/src/win/win_impl.rs +++ b/libs/enigo/src/win/win_impl.rs @@ -1,9 +1,8 @@ -use winapi; - use self::winapi::ctypes::c_int; use self::winapi::shared::{basetsd::ULONG_PTR, minwindef::*, windef::*}; use self::winapi::um::winbase::*; use self::winapi::um::winuser::*; +use winapi; use crate::win::keycodes::*; use crate::{Key, KeyboardControllable, MouseButton, MouseControllable}; @@ -200,7 +199,7 @@ impl KeyboardControllable for Enigo { fn key_down(&mut self, key: Key) -> crate::ResultType { let code = self.key_to_keycode(key); if code == 0 || code == 65535 { - return Err("".into()); + return Err("".into()); } let res = keybd_event(0, code, 0); if res == 0 { @@ -227,7 +226,8 @@ impl KeyboardControllable for Enigo { } impl Enigo { - /// Gets the (width, height) of the main display in screen coordinates (pixels). + /// Gets the (width, height) of the main display in screen coordinates + /// (pixels). /// /// # Example /// diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index dec00f21..e711f582 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -105,6 +105,13 @@ message MouseEvent { repeated ControlKey modifiers = 4; } +enum KeyboardMode{ + Legacy = 0; + Map = 1; + Translate = 2; + Auto = 3; +} + enum ControlKey { Unknown = 0; Alt = 1; @@ -198,6 +205,7 @@ message KeyEvent { string seq = 6; } repeated ControlKey modifiers = 8; + KeyboardMode mode = 9; } message CursorData { diff --git a/pynput_service.py b/pynput_service.py index c51e9a52..5aca5798 100644 --- a/pynput_service.py +++ b/pynput_service.py @@ -145,7 +145,11 @@ class MyController(Controller): or (keycode_flag == False and keycode == list(keycode_set)[0] and len(keycode_set) == 1): deakkey_chr = str(key).replace("'", '') keysym = DEAD_KEYS[deakkey_chr] - keycode, shift_state = self.keyboard_mapping[keysym][0] + # shift_state = 0 + keycode, shift_state = list( + filter(lambda x: x[1] == 0, + self.keyboard_mapping[keysym]) + )[0] # If the key has a virtual key code, use that immediately with # fake_input; fake input,being an X server extension, has access to diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 05dfa327..00000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "1.62.0" diff --git a/src/lang/cn.rs b/src/lang/cn.rs index bc06828a..9a5a7625 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", "忽略电池优化"), ("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的RustDesk应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"), ("Connection not allowed", "对方不允许连接"), + ("Legacy mode", "传统模式"), + ("Map mode", "1:1传输"), + ("Translate mode", "翻译模式"), ("Use temporary password", "使用临时密码"), ("Use permanent password", "使用固定密码"), ("Use both passwords", "同时使用两种密码"), diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 91437b2a..aa03e1d2 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", ""), ("Use permanent password", ""), ("Use both passwords", ""), diff --git a/src/lang/da.rs b/src/lang/da.rs index 87e68793..364e552a 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", ""), ("Use permanent password", ""), ("Use both passwords", ""), diff --git a/src/lang/de.rs b/src/lang/de.rs index 8acc3099..f9558bed 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", "Batterieoptimierung ignorieren"), ("android_open_battery_optimizations_tip", "Möchten Sie die Batterieopimierungs-Einstellungen öffnen?"), ("Connection not allowed", "Verbindung abgelehnt"), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", "Temporäres Passwort verwenden"), ("Use permanent password", "Dauerhaftes Passwort verwenden"), ("Use both passwords", "Beide Passwörter verwenden"), diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 1be29dd0..ad4a2f93 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", ""), ("Use permanent password", ""), ("Use both passwords", ""), diff --git a/src/lang/es.rs b/src/lang/es.rs index 8ae2feec..73a38dac 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -287,6 +287,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"), ("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Connection not allowed", "Conexión no disponible"), ("Use temporary password", "Usar contraseña temporal"), ("Use permanent password", "Usar contraseña permamente"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 7dd0fb9a..3553a5b1 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", ""), ("Use permanent password", ""), ("Use both passwords", ""), diff --git a/src/lang/hu.rs b/src/lang/hu.rs index de9f8922..8a57378e 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", ""), ("Use permanent password", ""), ("Use both passwords", ""), diff --git a/src/lang/id.rs b/src/lang/id.rs index 62cb7b6b..422851c7 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -287,6 +287,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"), ("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Connection not allowed", "Koneksi tidak dijinkan"), ("Use temporary password", "Gunakan kata sandi sementara"), ("Use permanent password", "Gunakan kata sandi permanaen"), diff --git a/src/lang/it.rs b/src/lang/it.rs index f608753e..244cb904 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -299,5 +299,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Are you sure you want to restart", "Sei sicuro di voler riavviare?"), ("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"), ("remote_restarting_tip", "Riavviare il dispositivo remoto"), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 5c6ba1da..82d91993 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"), ("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"), ("Connection not allowed", "接続が許可されていません"), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", "使い捨てのパスワードを使用"), ("Use permanent password", "固定のパスワードを使用"), ("Use both passwords", "どちらのパスワードも使用"), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 0aac8bc8..dea8197b 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", ""), ("Use permanent password", ""), ("Use both passwords", ""), diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 0b18683a..490fb32e 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"), ("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек "), ("Connection not allowed", "Подключение не разрешено"), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", "Использовать временный пароль"), ("Use permanent password", "Использовать постоянный пароль"), ("Use both passwords", "Использовать оба пароля"), diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 7f07657e..7a78e209 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", ""), ("Use permanent password", ""), ("Use both passwords", ""), diff --git a/src/lang/template.rs b/src/lang/template.rs index e6b2bd01..f37d84b6 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", ""), ("Use permanent password", ""), ("Use both passwords", ""), diff --git a/src/lang/tr.rs b/src/lang/tr.rs index b06c0e7f..4b0d6ccb 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -287,6 +287,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Connection not allowed", "bağlantıya izin verilmedi"), ("Use temporary password", "Geçici şifre kullan"), ("Use permanent password", "Kalıcı şifre kullan"), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index ce7804a6..a294acac 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", "忽略電池優化"), ("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"), ("Connection not allowed", "對方不允許連接"), + ("Legacy mode", "傳統模式"), + ("Map mode", "1:1傳輸"), + ("Translate mode", "翻譯模式"), ("Use temporary password", "使用臨時密碼"), ("Use permanent password", "使用固定密碼"), ("Use both passwords", "同時使用兩種密碼"), diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 014dcb20..55570dc4 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"), ("android_open_battery_optimizations_tip", "Nếu bạn muốn tắt tính năng này, vui lòng chuyển đến trang cài đặt ứng dụng RustDesk tiếp theo, tìm và nhập [Pin], Bỏ chọn [Không hạn chế]"), ("Connection not allowed", "Kết nối không đuợc phép"), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), ("Use temporary password", "Sử dụng mật khẩu tạm thời"), ("Use permanent password", "Sử dụng mật khẩu vĩnh viễn"), ("Use both passwords", "Sử dụng cả hai mật khẩu"), diff --git a/src/server/connection.rs b/src/server/connection.rs index 2062ba31..90548afc 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -6,7 +6,7 @@ use crate::common::update_clipboard; use crate::video_service; #[cfg(any(target_os = "android", target_os = "ios"))] use crate::{common::MOBILE_INFO2, mobile::connection_manager::start_channel}; -use crate::{ipc, VERSION}; +use crate::{ipc}; use hbb_common::{ config::Config, fs, @@ -448,11 +448,12 @@ impl Connection { handle_mouse(&msg, id); } MessageInput::Key((mut msg, press)) => { - if press { + // todo: press and down have similar meanings. + if press && msg.mode.unwrap() == KeyboardMode::Legacy { msg.down = true; } handle_key(&msg); - if press { + if press && msg.mode.unwrap() == KeyboardMode::Legacy { msg.down = false; handle_key(&msg); } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index e63b6290..c0121218 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -3,11 +3,13 @@ use super::*; use dispatch::Queue; use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable}; use hbb_common::{config::COMPRESS_LEVEL, protobuf::EnumOrUnknown}; +use rdev::{simulate, EventType, Key as RdevKey}; use std::{ convert::TryFrom, sync::atomic::{AtomicBool, Ordering}, time::Instant, }; +use tfc::{traits::*, Context}; #[derive(Default)] struct StateCursor { @@ -145,7 +147,8 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()> msg = cached.clone(); } else { let mut data = crate::get_cursor_data(hcursor)?; - data.colors = hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into(); + data.colors = + hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into(); let mut tmp = Message::new(); tmp.set_cursor_data(data); msg = Arc::new(tmp); @@ -177,6 +180,7 @@ lazy_static::lazy_static! { }; static ref KEYS_DOWN: Arc>> = Default::default(); static ref LATEST_INPUT: Arc> = Default::default(); + static ref KBD_CONTEXT: Mutex = Mutex::new(Context::new().expect("kbd context error")); } static EXITING: AtomicBool = AtomicBool::new(false); @@ -466,10 +470,14 @@ pub async fn lock_screen() { // loginctl lock-session also not work, they both work run rustdesk from cmd std::thread::spawn(|| { let mut key_event = KeyEvent::new(); - key_event.down = true; + key_event.set_chr('l' as _); key_event.modifiers.push(ControlKey::Meta.into()); + key_event.mode = KeyboardMode::Legacy.into(); + + key_event.down = true; handle_key(&key_event); + key_event.down = false; handle_key(&key_event); }); @@ -477,10 +485,13 @@ pub async fn lock_screen() { // CGSession -suspend not real lock screen, it is user switch std::thread::spawn(|| { let mut key_event = KeyEvent::new(); - key_event.down = true; + key_event.set_chr('q' as _); key_event.modifiers.push(ControlKey::Meta.into()); key_event.modifiers.push(ControlKey::Control.into()); + key_event.mode = KeyboardMode::Legacy.into(); + + key_event.down = true; handle_key(&key_event); key_event.down = false; handle_key(&key_event); @@ -597,19 +608,89 @@ pub fn handle_key(evt: &KeyEvent) { handle_key_(evt); } -fn handle_key_(evt: &KeyEvent) { - if EXITING.load(Ordering::SeqCst) { - return; +fn rdev_key_down_or_up(key: RdevKey, down_or_up: bool) { + let event_type = match down_or_up { + true => EventType::KeyPress(key), + false => EventType::KeyRelease(key), + }; + let delay = std::time::Duration::from_millis(20); + match simulate(&event_type) { + Ok(()) => (), + Err(_simulate_error) => { + log::error!("Could not send {:?}", &event_type); + } } + // Let ths OS catchup (at least MacOS) + std::thread::sleep(delay); +} + +fn rdev_key_click(key: RdevKey) { + rdev_key_down_or_up(key, true); + rdev_key_down_or_up(key, false); +} + +fn sync_status(evt: &KeyEvent) -> (bool, bool) { + let mut en = ENIGO.lock().unwrap(); + + // remote caps status + let caps_locking = evt + .modifiers + .iter() + .position(|&r| r == ControlKey::CapsLock.into()) + .is_some(); + // remote numpad status + let num_locking = evt + .modifiers + .iter() + .position(|&r| r == ControlKey::NumLock.into()) + .is_some(); + + let click_capslock = (caps_locking && !en.get_key_state(enigo::Key::CapsLock)) + || (!caps_locking && en.get_key_state(enigo::Key::CapsLock)); + let click_numlock = (num_locking && !en.get_key_state(enigo::Key::NumLock)) + || (!num_locking && en.get_key_state(enigo::Key::NumLock)); + return (click_capslock, click_numlock); +} + +fn map_keyboard_mode(evt: &KeyEvent) { + // map mode(1): Send keycode according to the peer platform. + let (click_capslock, click_numlock) = sync_status(evt); + if click_capslock { + rdev_key_click(RdevKey::CapsLock); + } + if click_numlock { + rdev_key_click(RdevKey::NumLock); + } + rdev_key_down_or_up(RdevKey::Unknown(evt.chr()), evt.down); + return; +} + +fn legacy_keyboard_mode(evt: &KeyEvent) { + let (click_capslock, click_numlock) = sync_status(evt); + #[cfg(windows)] crate::platform::windows::try_change_desktop(); let mut en = ENIGO.lock().unwrap(); + if click_capslock { + en.key_click(Key::CapsLock); + } + if click_numlock { + en.key_click(Key::NumLock); + } // disable numlock if press home etc when numlock is on, // because we will get numpad value (7,8,9 etc) if not #[cfg(windows)] let mut disable_numlock = false; #[cfg(target_os = "macos")] en.reset_flag(); + // When long-pressed the command key, then press and release + // the Tab key, there should be CGEventFlagCommand in the flag. + #[cfg(target_os = "macos")] + for ck in evt.modifiers.iter(){ + if let Some(key) = KEY_MAP.get(&ck.value()){ + en.add_flag(key); + } + } #[cfg(not(target_os = "macos"))] let mut to_release = Vec::new(); #[cfg(not(target_os = "macos"))] @@ -637,8 +718,6 @@ fn handle_key_(evt: &KeyEvent) { continue; } } - #[cfg(target_os = "macos")] - en.add_flag(key); #[cfg(not(target_os = "macos"))] { if key == &Key::CapsLock { @@ -748,6 +827,35 @@ fn handle_key_(evt: &KeyEvent) { } } +fn translate_keyboard_mode(evt: &KeyEvent) { + let chr = char::from_u32(evt.chr()).unwrap_or_default(); + // down(true)->press && press(false)-> release + if evt.down && !evt.press { + KBD_CONTEXT.lock().unwrap().unicode_char(chr).expect("unicode_char_down error"); + } +} + +fn handle_key_(evt: &KeyEvent) { + if EXITING.load(Ordering::SeqCst) { + return; + } + + match evt.mode.unwrap() { + KeyboardMode::Legacy => { + legacy_keyboard_mode(evt); + } + KeyboardMode::Map => { + map_keyboard_mode(evt); + } + KeyboardMode::Translate => { + translate_keyboard_mode(evt); + } + _ => { + legacy_keyboard_mode(evt); + } + } +} + #[tokio::main(flavor = "current_thread")] async fn lock_screen_2() { lock_screen().await; @@ -759,3 +867,52 @@ async fn send_sas() -> ResultType<()> { timeout(1000, stream.send(&crate::ipc::Data::SAS)).await??; Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + use rdev::{listen, Event, EventType, Key}; + use std::sync::mpsc; + + #[test] + fn test_handle_key() { + // listen + let (tx, rx) = mpsc::channel(); + std::thread::spawn(move || { + std::env::set_var("KEYBOARD_ONLY", "y"); + let func = move |event: Event| { + tx.send(event).ok(); + }; + if let Err(error) = listen(func) { + println!("Error: {:?}", error); + } + }); + // set key/char base on char + let mut evt = KeyEvent::new(); + evt.set_chr(66); + evt.mode = KeyboardMode::Legacy.into(); + + evt.modifiers.push(ControlKey::CapsLock.into()); + + // press + evt.down = true; + handle_key(&evt); + if let Ok(listen_evt) = rx.recv() { + assert_eq!(listen_evt.event_type, EventType::KeyPress(Key::Num1)) + } + // release + evt.down = false; + handle_key(&evt); + if let Ok(listen_evt) = rx.recv() { + assert_eq!(listen_evt.event_type, EventType::KeyRelease(Key::Num1)) + } + } + #[test] + fn test_get_key_state() { + let mut en = ENIGO.lock().unwrap(); + println!( + "[*] test_get_key_state: {:?}", + en.get_key_state(enigo::Key::NumLock) + ); + } +} diff --git a/src/ui/cm.rs b/src/ui/cm.rs index 38bfc935..e2d912c6 100644 --- a/src/ui/cm.rs +++ b/src/ui/cm.rs @@ -1,5 +1,4 @@ use crate::ipc::{self, new_listener, Connection, Data}; -use crate::VERSION; #[cfg(windows)] use clipboard::{ create_cliprdr_context, empty_clipboard, get_rx_clip_client, server_clip_file, set_conn_enabled, diff --git a/src/ui/common.tis b/src/ui/common.tis index aae950c2..8a0beef9 100644 --- a/src/ui/common.tis +++ b/src/ui/common.tis @@ -155,6 +155,96 @@ var svg_send = var svg_chat = ; +var svg_keyboard = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +; function scrollToBottom(el) { var y = el.box(#height, #content) - el.box(#height, #client); diff --git a/src/ui/header.tis b/src/ui/header.tis index 7ff160e6..870167ea 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -139,11 +139,23 @@ class Header: Reactor.Component { {svg_chat} {svg_action} {svg_display} + {svg_keyboard} + {this.renderKeyboardPop()} {this.renderDisplayPop()} {this.renderActionPop()} ; } + function renderKeyboardPop(){ + return + +
  • {svg_checkmark}{translate('Legacy mode')}
  • +
  • {svg_checkmark}{translate('Map mode')}
  • +
  • {svg_checkmark}{translate('Translate mode')}
  • + +
    ; + } + function renderDisplayPop() { var codecs = handler.supported_hwcodec(); var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]); @@ -263,6 +275,11 @@ class Header: Reactor.Component { me.popup(menu); } + event click $(#keyboard) (_, me) { + var menu = $(menu#keyboard-options); + me.popup(menu); + } + event click $(#screen) (_, me) { if (pi.current_display == me.index) return; handler.switch_display(me.index); @@ -352,6 +369,17 @@ class Header: Reactor.Component { toggleMenuState(); } } + + event click $(menu#keyboard-options>li) (_, me) { + if (me.id == "legacy") { + handler.save_keyboard_mode("legacy"); + } else if (me.id == "map") { + handler.save_keyboard_mode("map"); + } else if (me.id == "translate") { + handler.save_keyboard_mode("translate"); + } + toggleMenuState() + } } function handle_custom_image_quality() { @@ -375,12 +403,17 @@ function toggleMenuState() { var s = handler.get_view_style(); if (!s) s = "original"; values.push(s); + var k = handler.get_keyboard_mode(); + values.push(k); var c = handler.get_option("codec-preference"); if (!c) c = "auto"; values.push(c); for (var el in $$(menu#display-options li)) { el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); } + for (var el in $$(menu#keyboard-options>li)) { + el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); + } for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) { var el = self.select('#' + id); if (el) { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 1a446317..bcb79c52 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, ops::Deref, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -46,6 +46,7 @@ use hbb_common::{ }, get_version_number, }; +use rdev::{Event, EventType::*, Key as RdevKey, Keyboard as RdevKeyboard, KeyboardState}; #[cfg(windows)] use crate::clipboard_file::*; @@ -59,6 +60,8 @@ type Video = AssetPtr; lazy_static::lazy_static! { static ref ENIGO: Arc> = Arc::new(Mutex::new(Enigo::new())); static ref VIDEO: Arc>> = Default::default(); + static ref TO_RELEASE: Arc>> = Arc::new(Mutex::new(HashSet::::new())); + static ref KEYBOARD: Arc> = Arc::new(Mutex::new(RdevKeyboard::new().unwrap())); } fn get_key_state(key: enigo::Key) -> bool { @@ -235,6 +238,8 @@ impl sciter::EventHandler for Handler { fn get_remember(); fn peer_platform(); fn set_write_override(i32, i32, bool, bool, bool); + fn get_keyboard_mode(); + fn save_keyboard_mode(String); fn has_hwcodec(); fn supported_hwcodec(); fn change_prefer_codec(); @@ -282,222 +287,53 @@ impl Handler { } log::info!("keyboard hooked"); let mut me = self.clone(); - let peer = self.peer_platform(); - let is_win = peer == "Windows"; #[cfg(windows)] crate::platform::windows::enable_lowlevel_keyboard(std::ptr::null_mut() as _); std::thread::spawn(move || { // This will block. - std::env::set_var("KEYBOARD_ONLY", "y"); // pass to rdev - use rdev::{EventType::*, *}; + std::env::set_var("KEYBOARD_ONLY", "y"); + lazy_static::lazy_static! { + static ref MUTEX_SPECIAL_KEYS: Mutex> = { + let mut m = HashMap::new(); + m.insert(RdevKey::ShiftLeft, false); + m.insert(RdevKey::ShiftRight, false); + m.insert(RdevKey::ControlLeft, false); + m.insert(RdevKey::ControlRight, false); + m.insert(RdevKey::Alt, false); + m.insert(RdevKey::AltGr, false); + m.insert(RdevKey::MetaLeft, false); + m.insert(RdevKey::MetaRight, false); + Mutex::new(m) + }; + } + let func = move |evt: Event| { if !IS_IN.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) { return; } - let (key, down) = match evt.event_type { - KeyPress(k) => (k, 1), - KeyRelease(k) => (k, 0), + let (_key, down) = match evt.event_type { + KeyPress(k) => { + // keyboard long press + if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&k) { + if *MUTEX_SPECIAL_KEYS.lock().unwrap().get(&k).unwrap() { + return; + } + MUTEX_SPECIAL_KEYS.lock().unwrap().insert(k, true); + } + (k, true) + } + KeyRelease(k) => { + // keyboard long press + if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&k) { + MUTEX_SPECIAL_KEYS.lock().unwrap().insert(k, false); + } + (k, false) + } _ => return, }; - let alt = get_key_state(enigo::Key::Alt); - #[cfg(windows)] - let ctrl = { - let mut tmp = get_key_state(enigo::Key::Control); - unsafe { - if IS_ALT_GR { - if alt || key == Key::AltGr { - if tmp { - tmp = false; - } - } else { - IS_ALT_GR = false; - } - } - } - tmp - }; - #[cfg(not(windows))] - let ctrl = get_key_state(enigo::Key::Control); - let shift = get_key_state(enigo::Key::Shift); - #[cfg(windows)] - let command = crate::platform::windows::get_win_key_state(); - #[cfg(not(windows))] - let command = get_key_state(enigo::Key::Meta); - let control_key = match key { - Key::Alt => Some(ControlKey::Alt), - Key::AltGr => Some(ControlKey::RAlt), - Key::Backspace => Some(ControlKey::Backspace), - Key::ControlLeft => { - // when pressing AltGr, an extra VK_LCONTROL with a special - // scancode with bit 9 set is sent, let's ignore this. - #[cfg(windows)] - if evt.scan_code & 0x200 != 0 { - unsafe { - IS_ALT_GR = true; - } - return; - } - Some(ControlKey::Control) - } - Key::ControlRight => Some(ControlKey::RControl), - Key::DownArrow => Some(ControlKey::DownArrow), - Key::Escape => Some(ControlKey::Escape), - Key::F1 => Some(ControlKey::F1), - Key::F10 => Some(ControlKey::F10), - Key::F11 => Some(ControlKey::F11), - Key::F12 => Some(ControlKey::F12), - Key::F2 => Some(ControlKey::F2), - Key::F3 => Some(ControlKey::F3), - Key::F4 => Some(ControlKey::F4), - Key::F5 => Some(ControlKey::F5), - Key::F6 => Some(ControlKey::F6), - Key::F7 => Some(ControlKey::F7), - Key::F8 => Some(ControlKey::F8), - Key::F9 => Some(ControlKey::F9), - Key::LeftArrow => Some(ControlKey::LeftArrow), - Key::MetaLeft => Some(ControlKey::Meta), - Key::MetaRight => Some(ControlKey::RWin), - Key::Return => Some(ControlKey::Return), - Key::RightArrow => Some(ControlKey::RightArrow), - Key::ShiftLeft => Some(ControlKey::Shift), - Key::ShiftRight => Some(ControlKey::RShift), - Key::Space => Some(ControlKey::Space), - Key::Tab => Some(ControlKey::Tab), - Key::UpArrow => Some(ControlKey::UpArrow), - Key::Delete => { - if is_win && ctrl && alt { - me.ctrl_alt_del(); - return; - } - Some(ControlKey::Delete) - } - Key::Apps => Some(ControlKey::Apps), - Key::Cancel => Some(ControlKey::Cancel), - Key::Clear => Some(ControlKey::Clear), - Key::Kana => Some(ControlKey::Kana), - Key::Hangul => Some(ControlKey::Hangul), - Key::Junja => Some(ControlKey::Junja), - Key::Final => Some(ControlKey::Final), - Key::Hanja => Some(ControlKey::Hanja), - Key::Hanji => Some(ControlKey::Hanja), - Key::Convert => Some(ControlKey::Convert), - Key::Print => Some(ControlKey::Print), - Key::Select => Some(ControlKey::Select), - Key::Execute => Some(ControlKey::Execute), - Key::PrintScreen => Some(ControlKey::Snapshot), - Key::Help => Some(ControlKey::Help), - Key::Sleep => Some(ControlKey::Sleep), - Key::Separator => Some(ControlKey::Separator), - Key::KpReturn => Some(ControlKey::NumpadEnter), - Key::Kp0 => Some(ControlKey::Numpad0), - Key::Kp1 => Some(ControlKey::Numpad1), - Key::Kp2 => Some(ControlKey::Numpad2), - Key::Kp3 => Some(ControlKey::Numpad3), - Key::Kp4 => Some(ControlKey::Numpad4), - Key::Kp5 => Some(ControlKey::Numpad5), - Key::Kp6 => Some(ControlKey::Numpad6), - Key::Kp7 => Some(ControlKey::Numpad7), - Key::Kp8 => Some(ControlKey::Numpad8), - Key::Kp9 => Some(ControlKey::Numpad9), - Key::KpDivide => Some(ControlKey::Divide), - Key::KpMultiply => Some(ControlKey::Multiply), - Key::KpDecimal => Some(ControlKey::Decimal), - Key::KpMinus => Some(ControlKey::Subtract), - Key::KpPlus => Some(ControlKey::Add), - Key::CapsLock | Key::NumLock | Key::ScrollLock => { - return; - } - Key::Home => Some(ControlKey::Home), - Key::End => Some(ControlKey::End), - Key::Insert => Some(ControlKey::Insert), - Key::PageUp => Some(ControlKey::PageUp), - Key::PageDown => Some(ControlKey::PageDown), - Key::Pause => Some(ControlKey::Pause), - _ => None, - }; - let mut key_event = KeyEvent::new(); - if let Some(k) = control_key { - key_event.set_control_key(k); - } else { - let mut chr = match evt.name { - Some(ref s) => { - if s.len() <= 2 { - // exclude chinese characters - s.chars().next().unwrap_or('\0') - } else { - '\0' - } - } - _ => '\0', - }; - if chr == '·' { - // special for Chinese - chr = '`'; - } - if chr == '\0' { - chr = match key { - Key::Num1 => '1', - Key::Num2 => '2', - Key::Num3 => '3', - Key::Num4 => '4', - Key::Num5 => '5', - Key::Num6 => '6', - Key::Num7 => '7', - Key::Num8 => '8', - Key::Num9 => '9', - Key::Num0 => '0', - Key::KeyA => 'a', - Key::KeyB => 'b', - Key::KeyC => 'c', - Key::KeyD => 'd', - Key::KeyE => 'e', - Key::KeyF => 'f', - Key::KeyG => 'g', - Key::KeyH => 'h', - Key::KeyI => 'i', - Key::KeyJ => 'j', - Key::KeyK => 'k', - Key::KeyL => 'l', - Key::KeyM => 'm', - Key::KeyN => 'n', - Key::KeyO => 'o', - Key::KeyP => 'p', - Key::KeyQ => 'q', - Key::KeyR => 'r', - Key::KeyS => 's', - Key::KeyT => 't', - Key::KeyU => 'u', - Key::KeyV => 'v', - Key::KeyW => 'w', - Key::KeyX => 'x', - Key::KeyY => 'y', - Key::KeyZ => 'z', - Key::Comma => ',', - Key::Dot => '.', - Key::SemiColon => ';', - Key::Quote => '\'', - Key::LeftBracket => '[', - Key::RightBracket => ']', - Key::BackSlash => '\\', - Key::Minus => '-', - Key::Equal => '=', - Key::BackQuote => '`', - _ => '\0', - } - } - if chr != '\0' { - if chr == 'l' && is_win && command { - me.lock_screen(); - return; - } - key_event.set_chr(chr as _); - } else { - log::error!("Unknown key {:?}", evt); - return; - } - } - me.key_down_or_up(down, key_event, alt, ctrl, shift, command); + + me.key_down_or_up(down, _key, evt); }; if let Err(error) = rdev::listen(func) { log::error!("rdev: {:?}", error); @@ -513,6 +349,16 @@ impl Handler { return self.lc.read().unwrap().image_quality.clone(); } + fn get_keyboard_mode(&mut self) -> String { + return std::env::var("KEYBOARD_MODE") + .unwrap_or(String::from("legacy")) + .to_lowercase(); + } + + fn save_keyboard_mode(&mut self, value: String) { + std::env::set_var("KEYBOARD_MODE", value); + } + fn get_custom_image_quality(&mut self) -> Value { let mut v = Value::array(0); for x in self.lc.read().unwrap().custom_image_quality.iter() { @@ -946,6 +792,9 @@ impl Handler { } fn leave(&mut self) { + for key in TO_RELEASE.lock().unwrap().iter() { + self.map_keyboard_mode(false, *key, None) + } #[cfg(windows)] crate::platform::windows::stop_system_key_propagate(false); IS_IN.store(false, Ordering::SeqCst); @@ -969,7 +818,7 @@ impl Handler { command = true; } } - + send_mouse(mask, x, y, alt, ctrl, shift, command, self); // on macos, ctrl + left button down = right button down, up won't emit, so we need to // emit up myself if peer is not macos @@ -1129,18 +978,25 @@ impl Handler { if self.peer_platform() == "Windows" { let mut key_event = KeyEvent::new(); key_event.set_control_key(ControlKey::CtrlAltDel); - self.key_down_or_up(1, key_event, false, false, false, false); + // todo + key_event.down = true; + self.send_key_event(key_event, KeyboardMode::Legacy); } else { let mut key_event = KeyEvent::new(); key_event.set_control_key(ControlKey::Delete); - self.key_down_or_up(3, key_event, true, true, false, false); + self.legacy_modifiers(&mut key_event, true, true, false, false); + // todo + key_event.press = true; + self.send_key_event(key_event, KeyboardMode::Legacy); } } fn lock_screen(&mut self) { let mut key_event = KeyEvent::new(); key_event.set_control_key(ControlKey::LockScreen); - self.key_down_or_up(1, key_event, false, false, false, false); + // todo + key_event.down = true; + self.send_key_event(key_event, KeyboardMode::Legacy); } fn transfer_file(&mut self) { @@ -1159,17 +1015,130 @@ impl Handler { } } - fn key_down_or_up( - &mut self, - down_or_up: i32, - evt: KeyEvent, + fn send_key_event(&mut self, mut evt: KeyEvent, keyboard_mode: KeyboardMode) { + // mode: legacy(0), map(1), translate(2), auto(3) + evt.mode = keyboard_mode.into(); + let mut msg_out = Message::new(); + msg_out.set_key_event(evt); + self.send(Data::Message(msg_out)); + } + + #[allow(dead_code)] + fn convert_numpad_keys(&mut self, key: RdevKey) -> RdevKey { + if get_key_state(enigo::Key::NumLock) { + return key; + } + match key { + RdevKey::Kp0 => RdevKey::Insert, + RdevKey::KpDecimal => RdevKey::Delete, + RdevKey::Kp1 => RdevKey::End, + RdevKey::Kp2 => RdevKey::DownArrow, + RdevKey::Kp3 => RdevKey::PageDown, + RdevKey::Kp4 => RdevKey::LeftArrow, + RdevKey::Kp5 => RdevKey::Clear, + RdevKey::Kp6 => RdevKey::RightArrow, + RdevKey::Kp7 => RdevKey::Home, + RdevKey::Kp8 => RdevKey::UpArrow, + RdevKey::Kp9 => RdevKey::PageUp, + _ => key, + } + } + + fn map_keyboard_mode(&mut self, down_or_up: bool, key: RdevKey, _evt: Option) { + // map mode(1): Send keycode according to the peer platform. + #[cfg(target_os = "windows")] + let key = if let Some(e) = _evt { + rdev::get_win_key(e.code.into(), e.scan_code) + } else { + key + }; + + let peer = self.peer_platform(); + + let mut key_event = KeyEvent::new(); + // According to peer platform. + let keycode: u32 = if peer == "Linux" { + rdev::linux_keycode_from_key(key).unwrap_or_default().into() + } else if peer == "Windows" { + #[cfg(not(windows))] + let key = self.convert_numpad_keys(key); + rdev::win_keycode_from_key(key).unwrap_or_default().into() + } else { + rdev::macos_keycode_from_key(key).unwrap_or_default().into() + }; + + key_event.set_chr(keycode); + key_event.down = down_or_up; + + if get_key_state(enigo::Key::CapsLock) { + key_event.modifiers.push(ControlKey::CapsLock.into()); + } + if get_key_state(enigo::Key::NumLock) { + key_event.modifiers.push(ControlKey::NumLock.into()); + } + + self.send_key_event(key_event, KeyboardMode::Map); + } + + fn translate_keyboard_mode(&mut self, down_or_up: bool, key: RdevKey, evt: Event) { + // translate mode(2): locally generated characters are send to the peer. + + // get char + let string = match KEYBOARD.lock() { + Ok(mut keyboard) => { + let string = keyboard.add(&evt.event_type).unwrap_or_default(); + if keyboard.is_dead() && string == "" && down_or_up == true { + return; + } + string + } + Err(_) => "".to_owned(), + }; + + // maybe two string + let chars = if string == "" { + None + } else { + let chars: Vec = string.chars().collect(); + Some(chars) + }; + + if let Some(chars) = chars { + for chr in chars { + dbg!(chr); + + let mut key_event = KeyEvent::new(); + key_event.set_chr(chr as _); + key_event.down = true; + key_event.press = false; + + self.send_key_event(key_event, KeyboardMode::Translate); + } + } else { + let success = if down_or_up == true { + TO_RELEASE.lock().unwrap().insert(key) + } else { + TO_RELEASE.lock().unwrap().remove(&key) + }; + + // AltGr && LeftControl(SpecialKey) without action + if key == RdevKey::AltGr || evt.scan_code == 541 { + return; + } + if success{ + self.map_keyboard_mode(down_or_up, key, None); + } + } + } + + fn legacy_modifiers( + &self, + key_event: &mut KeyEvent, alt: bool, ctrl: bool, shift: bool, command: bool, ) { - let mut key_event = evt; - if alt && !crate::is_control_key(&key_event, &ControlKey::Alt) && !crate::is_control_key(&key_event, &ControlKey::RAlt) @@ -1198,19 +1167,247 @@ impl Handler { key_event.modifiers.push(ControlKey::CapsLock.into()); } if self.peer_platform() != "Mac OS" { - if get_key_state(enigo::Key::NumLock) && common::valid_for_numlock(&key_event) { + if get_key_state(enigo::Key::NumLock) { key_event.modifiers.push(ControlKey::NumLock.into()); } } - if down_or_up == 1 { - key_event.down = true; - } else if down_or_up == 3 { - key_event.press = true; + } + + fn legacy_keyboard_mode(&mut self, down_or_up: bool, key: RdevKey, evt: Event) { + // legacy mode(0): Generate characters locally, look for keycode on other side. + let peer = self.peer_platform(); + let is_win = peer == "Windows"; + + let alt = get_key_state(enigo::Key::Alt); + #[cfg(windows)] + let ctrl = { + let mut tmp = get_key_state(enigo::Key::Control) || get_key_state(enigo::Key::RightControl); + unsafe { + if IS_ALT_GR { + if alt || key == RdevKey::AltGr { + if tmp { + tmp = false; + } + } else { + IS_ALT_GR = false; + } + } + } + tmp + }; + #[cfg(not(windows))] + let ctrl = get_key_state(enigo::Key::Control) || get_key_state(enigo::Key::RightControl); + let shift = get_key_state(enigo::Key::Shift) || get_key_state(enigo::Key::RightShift); + #[cfg(windows)] + let command = crate::platform::windows::get_win_key_state(); + #[cfg(not(windows))] + let command = get_key_state(enigo::Key::Meta); + let control_key = match key { + RdevKey::Alt => Some(ControlKey::Alt), + RdevKey::AltGr => Some(ControlKey::RAlt), + RdevKey::Backspace => Some(ControlKey::Backspace), + RdevKey::ControlLeft => { + // when pressing AltGr, an extra VK_LCONTROL with a special + // scancode with bit 9 set is sent, let's ignore this. + #[cfg(windows)] + if evt.scan_code & 0x200 != 0 { + unsafe { + IS_ALT_GR = true; + } + return; + } + Some(ControlKey::Control) + } + RdevKey::ControlRight => Some(ControlKey::RControl), + RdevKey::DownArrow => Some(ControlKey::DownArrow), + RdevKey::Escape => Some(ControlKey::Escape), + RdevKey::F1 => Some(ControlKey::F1), + RdevKey::F10 => Some(ControlKey::F10), + RdevKey::F11 => Some(ControlKey::F11), + RdevKey::F12 => Some(ControlKey::F12), + RdevKey::F2 => Some(ControlKey::F2), + RdevKey::F3 => Some(ControlKey::F3), + RdevKey::F4 => Some(ControlKey::F4), + RdevKey::F5 => Some(ControlKey::F5), + RdevKey::F6 => Some(ControlKey::F6), + RdevKey::F7 => Some(ControlKey::F7), + RdevKey::F8 => Some(ControlKey::F8), + RdevKey::F9 => Some(ControlKey::F9), + RdevKey::LeftArrow => Some(ControlKey::LeftArrow), + RdevKey::MetaLeft => Some(ControlKey::Meta), + RdevKey::MetaRight => Some(ControlKey::RWin), + RdevKey::Return => Some(ControlKey::Return), + RdevKey::RightArrow => Some(ControlKey::RightArrow), + RdevKey::ShiftLeft => Some(ControlKey::Shift), + RdevKey::ShiftRight => Some(ControlKey::RShift), + RdevKey::Space => Some(ControlKey::Space), + RdevKey::Tab => Some(ControlKey::Tab), + RdevKey::UpArrow => Some(ControlKey::UpArrow), + RdevKey::Delete => { + if is_win && ctrl && alt { + self.ctrl_alt_del(); + return; + } + Some(ControlKey::Delete) + } + RdevKey::Apps => Some(ControlKey::Apps), + RdevKey::Cancel => Some(ControlKey::Cancel), + RdevKey::Clear => Some(ControlKey::Clear), + RdevKey::Kana => Some(ControlKey::Kana), + RdevKey::Hangul => Some(ControlKey::Hangul), + RdevKey::Junja => Some(ControlKey::Junja), + RdevKey::Final => Some(ControlKey::Final), + RdevKey::Hanja => Some(ControlKey::Hanja), + RdevKey::Hanji => Some(ControlKey::Hanja), + RdevKey::Convert => Some(ControlKey::Convert), + RdevKey::Print => Some(ControlKey::Print), + RdevKey::Select => Some(ControlKey::Select), + RdevKey::Execute => Some(ControlKey::Execute), + RdevKey::PrintScreen => Some(ControlKey::Snapshot), + RdevKey::Help => Some(ControlKey::Help), + RdevKey::Sleep => Some(ControlKey::Sleep), + RdevKey::Separator => Some(ControlKey::Separator), + RdevKey::KpReturn => Some(ControlKey::NumpadEnter), + RdevKey::Kp0 => Some(ControlKey::Numpad0), + RdevKey::Kp1 => Some(ControlKey::Numpad1), + RdevKey::Kp2 => Some(ControlKey::Numpad2), + RdevKey::Kp3 => Some(ControlKey::Numpad3), + RdevKey::Kp4 => Some(ControlKey::Numpad4), + RdevKey::Kp5 => Some(ControlKey::Numpad5), + RdevKey::Kp6 => Some(ControlKey::Numpad6), + RdevKey::Kp7 => Some(ControlKey::Numpad7), + RdevKey::Kp8 => Some(ControlKey::Numpad8), + RdevKey::Kp9 => Some(ControlKey::Numpad9), + RdevKey::KpDivide => Some(ControlKey::Divide), + RdevKey::KpMultiply => Some(ControlKey::Multiply), + RdevKey::KpDecimal => Some(ControlKey::Decimal), + RdevKey::KpMinus => Some(ControlKey::Subtract), + RdevKey::KpPlus => Some(ControlKey::Add), + RdevKey::CapsLock | RdevKey::NumLock | RdevKey::ScrollLock => { + return; + } + RdevKey::Home => Some(ControlKey::Home), + RdevKey::End => Some(ControlKey::End), + RdevKey::Insert => Some(ControlKey::Insert), + RdevKey::PageUp => Some(ControlKey::PageUp), + RdevKey::PageDown => Some(ControlKey::PageDown), + RdevKey::Pause => Some(ControlKey::Pause), + _ => None, + }; + let mut key_event = KeyEvent::new(); + if let Some(k) = control_key { + key_event.set_control_key(k); + } else { + let mut chr = match evt.name { + Some(ref s) => { + if s.len() <= 2 { + // exclude chinese characters + s.chars().next().unwrap_or('\0') + } else { + '\0' + } + } + _ => '\0', + }; + if chr == '·' { + // special for Chinese + chr = '`'; + } + if chr == '\0' { + chr = match key { + RdevKey::Num1 => '1', + RdevKey::Num2 => '2', + RdevKey::Num3 => '3', + RdevKey::Num4 => '4', + RdevKey::Num5 => '5', + RdevKey::Num6 => '6', + RdevKey::Num7 => '7', + RdevKey::Num8 => '8', + RdevKey::Num9 => '9', + RdevKey::Num0 => '0', + RdevKey::KeyA => 'a', + RdevKey::KeyB => 'b', + RdevKey::KeyC => 'c', + RdevKey::KeyD => 'd', + RdevKey::KeyE => 'e', + RdevKey::KeyF => 'f', + RdevKey::KeyG => 'g', + RdevKey::KeyH => 'h', + RdevKey::KeyI => 'i', + RdevKey::KeyJ => 'j', + RdevKey::KeyK => 'k', + RdevKey::KeyL => 'l', + RdevKey::KeyM => 'm', + RdevKey::KeyN => 'n', + RdevKey::KeyO => 'o', + RdevKey::KeyP => 'p', + RdevKey::KeyQ => 'q', + RdevKey::KeyR => 'r', + RdevKey::KeyS => 's', + RdevKey::KeyT => 't', + RdevKey::KeyU => 'u', + RdevKey::KeyV => 'v', + RdevKey::KeyW => 'w', + RdevKey::KeyX => 'x', + RdevKey::KeyY => 'y', + RdevKey::KeyZ => 'z', + RdevKey::Comma => ',', + RdevKey::Dot => '.', + RdevKey::SemiColon => ';', + RdevKey::Quote => '\'', + RdevKey::LeftBracket => '[', + RdevKey::RightBracket => ']', + RdevKey::BackSlash => '\\', + RdevKey::Minus => '-', + RdevKey::Equal => '=', + RdevKey::BackQuote => '`', + _ => '\0', + } + } + if chr != '\0' { + if chr == 'l' && is_win && command { + self.lock_screen(); + return; + } + key_event.set_chr(chr as _); + } else { + log::error!("Unknown key {:?}", evt); + return; + } + } + + self.legacy_modifiers(&mut key_event, alt, ctrl, shift, command); + + if down_or_up == true { + key_event.down = true; + } + self.send_key_event(key_event, KeyboardMode::Legacy) + } + + fn key_down_or_up(&mut self, down_or_up: bool, key: RdevKey, evt: Event) { + // Call different functions according to keyboard mode. + let mode = match self.get_keyboard_mode().as_str() { + "map" => KeyboardMode::Map, + "legacy" => KeyboardMode::Legacy, + "translate" => KeyboardMode::Translate, + _ => KeyboardMode::Legacy, + }; + + match mode { + KeyboardMode::Map => { + if down_or_up == true { + TO_RELEASE.lock().unwrap().insert(key); + } else { + TO_RELEASE.lock().unwrap().remove(&key); + } + self.map_keyboard_mode(down_or_up, key, Some(evt)); + } + KeyboardMode::Legacy => self.legacy_keyboard_mode(down_or_up, key, evt), + KeyboardMode::Translate => { + self.translate_keyboard_mode(down_or_up, key, evt); + } + _ => self.legacy_keyboard_mode(down_or_up, key, evt), } - let mut msg_out = Message::new(); - msg_out.set_key_event(key_event); - log::debug!("{:?}", msg_out); - self.send(Data::Message(msg_out)); } #[inline]