diff --git a/Cargo.lock b/Cargo.lock index a6ed80add..6b5654a74 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 f48a47d9b..5e6d509e5 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 b0028b564..fec03d34e 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 164fb1c17..01e9b67d2 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 840290b2b..7a8f6668e 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 748b30105..836c645fe 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 0e3b79ab3..ff687eee2 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 28c9362ed..520c9dca1 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 ea1543faa..56fc4caef 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 dec00f21e..e711f5826 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 c51e9a524..5aca57986 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 05dfa3270..000000000 --- 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 bc06828a5..9a5a76250 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 91437b2af..aa03e1d23 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 87e687936..364e552a3 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 8acc30991..f9558bed9 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 1be29dd07..ad4a2f93a 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 8ae2feecd..73a38dacd 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 7dd0fb9a9..3553a5b17 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 de9f8922b..8a57378ea 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 62cb7b6b5..422851c77 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 f608753e1..244cb9047 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 5c6ba1da7..82d91993e 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 0aac8bc8a..dea8197b2 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 0b18683a2..490fb32e0 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 7f07657eb..7a78e2090 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 e6b2bd01d..f37d84b60 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 b06c0e7f3..4b0d6ccb3 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 ce7804a65..a294acac2 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 014dcb20e..55570dc40 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 2062ba31d..90548afc7 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 e63b6290e..c01212184 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 38bfc9359..e2d912c63 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 aae950c2d..8a0beef98 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 7ff160e6d..870167eae 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 1a446317d..bcb79c522 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]