From cea123c79f5f0e39ed394df0f60f0d404949ee27 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 14 Feb 2023 19:20:22 +0800 Subject: [PATCH 01/13] more lang in setup.nsi --- res/setup.nsi | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/res/setup.nsi b/res/setup.nsi index 5410e0ff5..635851d0a 100644 --- a/res/setup.nsi +++ b/res/setup.nsi @@ -56,8 +56,74 @@ InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}" #################################################################### # Language -!insertmacro MUI_LANGUAGE "English" +!insertmacro MUI_LANGUAGE "English" ; The first language is the default language +!insertmacro MUI_LANGUAGE "French" +!insertmacro MUI_LANGUAGE "German" +!insertmacro MUI_LANGUAGE "Spanish" +!insertmacro MUI_LANGUAGE "SpanishInternational" !insertmacro MUI_LANGUAGE "SimpChinese" +!insertmacro MUI_LANGUAGE "TradChinese" +!insertmacro MUI_LANGUAGE "Japanese" +!insertmacro MUI_LANGUAGE "Korean" +!insertmacro MUI_LANGUAGE "Italian" +!insertmacro MUI_LANGUAGE "Dutch" +!insertmacro MUI_LANGUAGE "Danish" +!insertmacro MUI_LANGUAGE "Swedish" +!insertmacro MUI_LANGUAGE "Norwegian" +!insertmacro MUI_LANGUAGE "NorwegianNynorsk" +!insertmacro MUI_LANGUAGE "Finnish" +!insertmacro MUI_LANGUAGE "Greek" +!insertmacro MUI_LANGUAGE "Russian" +!insertmacro MUI_LANGUAGE "Portuguese" +!insertmacro MUI_LANGUAGE "PortugueseBR" +!insertmacro MUI_LANGUAGE "Polish" +!insertmacro MUI_LANGUAGE "Ukrainian" +!insertmacro MUI_LANGUAGE "Czech" +!insertmacro MUI_LANGUAGE "Slovak" +!insertmacro MUI_LANGUAGE "Croatian" +!insertmacro MUI_LANGUAGE "Bulgarian" +!insertmacro MUI_LANGUAGE "Hungarian" +!insertmacro MUI_LANGUAGE "Thai" +!insertmacro MUI_LANGUAGE "Romanian" +!insertmacro MUI_LANGUAGE "Latvian" +!insertmacro MUI_LANGUAGE "Macedonian" +!insertmacro MUI_LANGUAGE "Estonian" +!insertmacro MUI_LANGUAGE "Turkish" +!insertmacro MUI_LANGUAGE "Lithuanian" +!insertmacro MUI_LANGUAGE "Slovenian" +!insertmacro MUI_LANGUAGE "Serbian" +!insertmacro MUI_LANGUAGE "SerbianLatin" +!insertmacro MUI_LANGUAGE "Arabic" +!insertmacro MUI_LANGUAGE "Farsi" +!insertmacro MUI_LANGUAGE "Hebrew" +!insertmacro MUI_LANGUAGE "Indonesian" +!insertmacro MUI_LANGUAGE "Mongolian" +!insertmacro MUI_LANGUAGE "Luxembourgish" +!insertmacro MUI_LANGUAGE "Albanian" +!insertmacro MUI_LANGUAGE "Breton" +!insertmacro MUI_LANGUAGE "Belarusian" +!insertmacro MUI_LANGUAGE "Icelandic" +!insertmacro MUI_LANGUAGE "Malay" +!insertmacro MUI_LANGUAGE "Bosnian" +!insertmacro MUI_LANGUAGE "Kurdish" +!insertmacro MUI_LANGUAGE "Irish" +!insertmacro MUI_LANGUAGE "Uzbek" +!insertmacro MUI_LANGUAGE "Galician" +!insertmacro MUI_LANGUAGE "Afrikaans" +!insertmacro MUI_LANGUAGE "Catalan" +!insertmacro MUI_LANGUAGE "Esperanto" +!insertmacro MUI_LANGUAGE "Asturian" +!insertmacro MUI_LANGUAGE "Basque" +!insertmacro MUI_LANGUAGE "Pashto" +!insertmacro MUI_LANGUAGE "ScotsGaelic" +!insertmacro MUI_LANGUAGE "Georgian" +!insertmacro MUI_LANGUAGE "Vietnamese" +!insertmacro MUI_LANGUAGE "Welsh" +!insertmacro MUI_LANGUAGE "Armenian" +!insertmacro MUI_LANGUAGE "Corsican" +!insertmacro MUI_LANGUAGE "Tatar" +!insertmacro MUI_LANGUAGE "Hindi" + #################################################################### # Sections From 50f751c21521fd63985a9123f05bb706c048ba37 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 10 Feb 2023 09:03:19 +0800 Subject: [PATCH 02/13] temp commit Signed-off-by: fufesou --- src/keyboard.rs | 10 ++++++---- src/server/input_service.rs | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index 105b84400..9ca5a16f0 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -759,10 +759,12 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option) { match &event.unicode { Some(unicode_info) => { - for code in &unicode_info.unicode { - let mut evt = key_event.clone(); - evt.set_unicode(*code as _); - events.push(evt); + if let Some(name) = unicode_info.name { + if name.len() > 0 { + let mut evt = key_event.clone(); + evt.set_seq(name); + events.push(evt); + } } } None => {} diff --git a/src/server/input_service.rs b/src/server/input_service.rs index edf0ef497..2b19bbaff 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1093,6 +1093,9 @@ fn translate_keyboard_mode(evt: &KeyEvent) { #[cfg(target_os = "windows")] allow_err!(rdev::simulate_unicode(_unicode as _)); } + Some(key_event::Union::Seq(seq)) => { + ENIGO.lock().unwrap().key_sequence(&seq); + } Some(key_event::Union::Chr(..)) => { #[cfg(target_os = "windows")] From e24f5e7eed10b321500fb6fdfe64d7e8bb766d87 Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 13 Feb 2023 14:55:57 +0800 Subject: [PATCH 03/13] mid commit Signed-off-by: fufesou --- libs/hbb_common/protos/message.proto | 2 ++ src/keyboard.rs | 33 ++++------------------------ src/server/input_service.rs | 7 +++--- 3 files changed, 9 insertions(+), 33 deletions(-) diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index ed2706382..7e3d0b0a4 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -201,6 +201,8 @@ message KeyEvent { bool press = 2; oneof union { ControlKey control_key = 3; + // high word, sym key code. win: virtual-key code, linux: keysym ?, macos: + // low word, position key code. win: scancode, linux: key code, macos: key code uint32 chr = 4; uint32 unicode = 5; string seq = 6; diff --git a/src/keyboard.rs b/src/keyboard.rs index 9ca5a16f0..02f34132f 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -785,34 +785,9 @@ fn is_hot_key_modifiers_down() -> bool { return false; } -pub fn translate_virtual_keycode(event: &Event, mut key_event: KeyEvent) -> Option { - match event.event_type { - EventType::KeyPress(..) => { - key_event.down = true; - } - EventType::KeyRelease(..) => { - key_event.down = false; - } - _ => return None, - }; - - let mut peer = get_peer_platform().to_lowercase(); - peer.retain(|c| !c.is_whitespace()); - - // #[cfg(target_os = "windows")] - // let keycode = match peer.as_str() { - // "windows" => event.code, - // "macos" => { - // if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" { - // rdev::win_scancode_to_macos_iso_code(event.scan_code)? - // } else { - // rdev::win_scancode_to_macos_code(event.scan_code)? - // } - // } - // _ => rdev::win_scancode_to_linux_code(event.scan_code)?, - // }; - - key_event.set_chr(event.code as _); +pub fn translate_vk_scan_code(event: &Event, mut key_event: KeyEvent) -> Option { + let mut key_event = map_keyboard_mode(event, key_event)?; + key_event.set_chr((key_event.chr() & 0x0000FFFF) | ((event.code as u32) << 16)); Some(key_event) } @@ -853,7 +828,7 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { - #[cfg(target_os = "windows")] - allow_err!(rdev::simulate_unicode(_unicode as _)); - } Some(key_event::Union::Seq(seq)) => { ENIGO.lock().unwrap().key_sequence(&seq); } @@ -1101,6 +1097,9 @@ fn translate_keyboard_mode(evt: &KeyEvent) { #[cfg(target_os = "windows")] translate_process_virtual_keycode(evt.chr(), evt.down) } + Some(key_event::Union::Unicode(..)) => { + // Do not handle unicode for now. + } _ => { log::debug!("Unreachable. Unexpected key event {:?}", &evt); } From 50ce57024c74ed9faab2099ed5c544be4e51e3a4 Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 13 Feb 2023 16:26:14 +0800 Subject: [PATCH 04/13] macos, win, translate mode, Signed-off-by: fufesou --- src/keyboard.rs | 1 + src/server/input_service.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index 02f34132f..8aa5f72d2 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -787,6 +787,7 @@ fn is_hot_key_modifiers_down() -> bool { pub fn translate_vk_scan_code(event: &Event, mut key_event: KeyEvent) -> Option { let mut key_event = map_keyboard_mode(event, key_event)?; + #[cfg(target_os = "windows")] key_event.set_chr((key_event.chr() & 0x0000FFFF) | ((event.code as u32) << 16)); Some(key_event) } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 0f40cb7d6..18ff433a3 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1082,9 +1082,14 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { } #[cfg(target_os = "windows")] -fn translate_process_virtual_keycode(vk: u32, down: bool) { +fn translate_process_code(code: u32, down: bool) { crate::platform::windows::try_change_desktop(); - sim_rdev_rawkey_virtual(vk, down); + let vk_code = + + match code >> 16 { + 0 => sim_rdev_rawkey_position(code, down), + vk_code => sim_rdev_rawkey_virtual(vk_code, down), + }; } fn translate_keyboard_mode(evt: &KeyEvent) { @@ -1095,7 +1100,9 @@ fn translate_keyboard_mode(evt: &KeyEvent) { Some(key_event::Union::Chr(..)) => { #[cfg(target_os = "windows")] - translate_process_virtual_keycode(evt.chr(), evt.down) + translate_process_code(evt.chr(), evt.down); + #[cfg(not(target_os = "windows"))] + sim_rdev_rawkey_position(code, down); } Some(key_event::Union::Unicode(..)) => { // Do not handle unicode for now. From 7dfcc401e5d59b53e6243211639ef990cc4a2384 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 14 Feb 2023 15:42:02 +0800 Subject: [PATCH 05/13] translate mode, mac --> win, init debug Signed-off-by: fufesou --- Cargo.lock | 3 +- .../lib/desktop/widgets/remote_menubar.dart | 4 +- src/flutter_ffi.rs | 1 - src/keyboard.rs | 95 ++++++++++++++----- src/server/input_service.rs | 6 +- 5 files changed, 77 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0f66e287..2fcdef290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4554,12 +4554,13 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#cedc4e62744566775026af4b434ef799804c1130" +source = "git+https://github.com/fufesou/rdev#593f0ba37139ed6f4f88a4120e972612ec4b1c6f" dependencies = [ "cocoa", "core-foundation 0.9.3", "core-foundation-sys 0.8.3", "core-graphics 0.22.3", + "dispatch", "enum-map", "epoll", "inotify", diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 6bb49000b..9f8265fec 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1510,8 +1510,8 @@ class _RemoteMenubarState extends State { if (bind.sessionIsKeyboardModeSupported( id: widget.id, mode: mode.key)) { if (mode.key == 'translate') { - if (!Platform.isWindows || - widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) { + if (Platform.isLinux || + widget.ffi.ffiModel.pi.platform == kPeerPlatformLinux) { continue; } } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index b4e79b361..0e307abe3 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -20,7 +20,6 @@ use std::{ os::raw::c_char, str::FromStr, }; -use crate::ui_session_interface::InvokeUiSession; // use crate::hbbs_http::account::AuthResult; diff --git a/src/keyboard.rs b/src/keyboard.rs index 8aa5f72d2..7e4ba2b39 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -18,6 +18,13 @@ use std::{ #[cfg(windows)] static mut IS_ALT_GR: bool = false; +#[allow(dead_code)] +const OS_LOWER_WINDOWS: &str = "windows"; +#[allow(dead_code)] +const OS_LOWER_LINUX: &str = "linux"; +#[allow(dead_code)] +const OS_LOWER_MACOS: &str = "macos"; + #[cfg(any(target_os = "windows", target_os = "macos"))] static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false); @@ -202,6 +209,9 @@ pub fn update_grab_get_key_name() { #[cfg(target_os = "windows")] static mut IS_0X021D_DOWN: bool = false; +#[cfg(target_os = "macos")] +static mut IS_LEFT_OPTION_DOWN: bool = false; + pub fn start_grab_loop() { #[cfg(any(target_os = "windows", target_os = "macos"))] std::thread::spawn(move || { @@ -213,6 +223,7 @@ pub fn start_grab_loop() { let mut _keyboard_mode = KeyboardMode::Map; let _scan_code = event.scan_code; + let _code = event.code; let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) { _keyboard_mode = client::process_event(&event, None); if is_press { @@ -246,6 +257,13 @@ pub fn start_grab_loop() { } } + #[cfg(target_os = "macos")] + unsafe { + if _code as u32 == rdev::kVK_Option { + IS_LEFT_OPTION_DOWN = is_press; + } + } + return res; }; let func = move |event: Event| match event.event_type { @@ -253,11 +271,13 @@ pub fn start_grab_loop() { EventType::KeyRelease(key) => try_handle_keyboard(event, key, false), _ => Some(event), }; + #[cfg(target_os = "macos")] + rdev::set_is_main_thread(false); + #[cfg(target_os = "windows")] + rdev::set_event_popup(false); if let Err(error) = rdev::grab(func) { log::error!("rdev Error: {:?}", error) } - #[cfg(target_os = "windows")] - rdev::set_event_popup(false); }); #[cfg(target_os = "linux")] @@ -395,13 +415,16 @@ pub fn event_to_key_events( _ => {} } + let mut peer = get_peer_platform().to_lowercase(); + peer.retain(|c| !c.is_whitespace()); + key_event.mode = keyboard_mode.into(); let mut key_events = match keyboard_mode { - KeyboardMode::Map => match map_keyboard_mode(event, key_event) { + KeyboardMode::Map => match map_keyboard_mode(peer.as_str(), event, key_event) { Some(event) => [event].to_vec(), None => Vec::new(), }, - KeyboardMode::Translate => translate_keyboard_mode(event, key_event), + KeyboardMode::Translate => translate_keyboard_mode(peer.as_str(), event, key_event), _ => { #[cfg(not(any(target_os = "android", target_os = "ios")))] { @@ -424,7 +447,6 @@ pub fn event_to_key_events( } } } - key_events } @@ -698,7 +720,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec Option { +pub fn map_keyboard_mode(peer: &str, event: &Event, mut key_event: KeyEvent) -> Option { match event.event_type { EventType::KeyPress(..) => { key_event.down = true; @@ -709,12 +731,9 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option return None, }; - let mut peer = get_peer_platform().to_lowercase(); - peer.retain(|c| !c.is_whitespace()); - #[cfg(target_os = "windows")] - let keycode = match peer.as_str() { - "windows" => { + let keycode = match peer { + OS_LOWER_WINDOWS => { // https://github.com/rustdesk/rustdesk/issues/1371 // Filter scancodes that are greater than 255 and the hight word is not 0xE0. if event.scan_code > 255 && (event.scan_code >> 8) != 0xE0 { @@ -722,7 +741,7 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option { + OS_LOWER_MACOS => { if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" { rdev::win_scancode_to_macos_iso_code(event.scan_code)? } else { @@ -732,15 +751,15 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option rdev::win_scancode_to_linux_code(event.scan_code)?, }; #[cfg(target_os = "macos")] - let keycode = match peer.as_str() { - "windows" => rdev::macos_code_to_win_scancode(event.code as _)?, - "macos" => event.code as _, + let keycode = match peer { + OS_LOWER_WINDOWS => rdev::macos_code_to_win_scancode(event.code as _)?, + OS_LOWER_MACOS => event.code as _, _ => rdev::macos_code_to_linux_code(event.code as _)?, }; #[cfg(target_os = "linux")] - let keycode = match peer.as_str() { - "windows" => rdev::linux_code_to_win_scancode(event.code as _)?, - "macos" => { + let keycode = match peer { + OS_LOWER_WINDOWS => rdev::linux_code_to_win_scancode(event.code as _)?, + OS_LOWER_MACOS => { if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" { rdev::linux_code_to_macos_iso_code(event.code as _)? } else { @@ -759,10 +778,10 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option) { match &event.unicode { Some(unicode_info) => { - if let Some(name) = unicode_info.name { + if let Some(name) = &unicode_info.name { if name.len() > 0 { let mut evt = key_event.clone(); - evt.set_seq(name); + evt.set_seq(name.to_string()); events.push(evt); } } @@ -785,21 +804,42 @@ fn is_hot_key_modifiers_down() -> bool { return false; } -pub fn translate_vk_scan_code(event: &Event, mut key_event: KeyEvent) -> Option { +#[inline] +#[cfg(target_os = "windows")] +pub fn translate_key_code(event: &Event, mut key_event: KeyEvent) -> Option { let mut key_event = map_keyboard_mode(event, key_event)?; - #[cfg(target_os = "windows")] key_event.set_chr((key_event.chr() & 0x0000FFFF) | ((event.code as u32) << 16)); Some(key_event) } -pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { +#[inline] +#[cfg(not(target_os = "windows"))] +pub fn translate_key_code(peer: &str, event: &Event, key_event: KeyEvent) -> Option { + map_keyboard_mode(peer, event, key_event) +} + +pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -> Vec { let mut events: Vec = Vec::new(); if let Some(unicode_info) = &event.unicode { if unicode_info.is_dead { + #[cfg(target_os = "macos")] + if peer != OS_LOWER_MACOS && unsafe { IS_LEFT_OPTION_DOWN } { + // try clear dead key state + // rdev::clear_dead_key_state(); + } else { + return events; + } + #[cfg(not(target_os = "macos"))] return events; } } + #[cfg(target_os = "macos")] + // ignore right option key + if event.code as u32 == rdev::kVK_RightOption { + return events; + } + #[cfg(target_os = "windows")] unsafe { if event.scan_code == 0x021D { @@ -825,11 +865,16 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { - ENIGO.lock().unwrap().key_sequence(&seq); + ENIGO.lock().unwrap().key_sequence(seq); } Some(key_event::Union::Chr(..)) => { #[cfg(target_os = "windows")] translate_process_code(evt.chr(), evt.down); #[cfg(not(target_os = "windows"))] - sim_rdev_rawkey_position(code, down); + sim_rdev_rawkey_position(evt.chr(), evt.down); } Some(key_event::Union::Unicode(..)) => { // Do not handle unicode for now. From b2d13647be0a84be2047194f7786346bbbd049f2 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 14 Feb 2023 15:58:36 +0800 Subject: [PATCH 06/13] translate mode, mac --> win, debug 2 Signed-off-by: fufesou --- libs/enigo/src/win/win_impl.rs | 42 ++++++++++++++++------------------ src/keyboard.rs | 4 ++-- src/server/input_service.rs | 2 -- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/libs/enigo/src/win/win_impl.rs b/libs/enigo/src/win/win_impl.rs index 2e1108b9e..115cb9789 100644 --- a/libs/enigo/src/win/win_impl.rs +++ b/libs/enigo/src/win/win_impl.rs @@ -39,7 +39,7 @@ fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) -> DWORD { unsafe { SendInput(1, &mut input as LPINPUT, size_of::() as c_int) } } -fn keybd_event(flags: u32, vk: u16, scan: u16) -> DWORD { +fn keybd_event(mut flags: u32, vk: u16, scan: u16) -> DWORD { let mut scan = scan; unsafe { // https://github.com/rustdesk/rustdesk/issues/366 @@ -52,35 +52,33 @@ fn keybd_event(flags: u32, vk: u16, scan: u16) -> DWORD { scan = MapVirtualKeyExW(vk as _, 0, LAYOUT) as _; } } - let mut input: INPUT = unsafe { std::mem::MaybeUninit::zeroed().assume_init() }; - input.type_ = INPUT_KEYBOARD; + + if flags & KEYEVENTF_UNICODE == 0 { + if scan >> 8 == 0xE0 || scan >> 8 == 0xE1 { + flags |= winapi::um::winuser::KEYEVENTF_EXTENDEDKEY; + } + } + let mut union: INPUT_u = unsafe { std::mem::zeroed() }; unsafe { - let dst_ptr = (&mut input.u as *mut _) as *mut u8; - let flags = match vk as _ { - winapi::um::winuser::VK_HOME | - winapi::um::winuser::VK_UP | - winapi::um::winuser::VK_PRIOR | - winapi::um::winuser::VK_LEFT | - winapi::um::winuser::VK_RIGHT | - winapi::um::winuser::VK_END | - winapi::um::winuser::VK_DOWN | - winapi::um::winuser::VK_NEXT | - winapi::um::winuser::VK_INSERT | - winapi::um::winuser::VK_DELETE => flags | winapi::um::winuser::KEYEVENTF_EXTENDEDKEY, - _ => flags, - }; - - let k = KEYBDINPUT { + *union.ki_mut() = KEYBDINPUT { wVk: vk, wScan: scan, dwFlags: flags, time: 0, dwExtraInfo: ENIGO_INPUT_EXTRA_VALUE, }; - let src_ptr = (&k as *const _) as *const u8; - std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, size_of::()); } - unsafe { SendInput(1, &mut input as LPINPUT, size_of::() as c_int) } + let mut inputs = [INPUT { + type_: INPUT_KEYBOARD, + u: union, + }; 1]; + unsafe { + SendInput( + inputs.len() as UINT, + inputs.as_mut_ptr(), + size_of::() as c_int, + ) + } } fn get_error() -> String { diff --git a/src/keyboard.rs b/src/keyboard.rs index 7e4ba2b39..4dcbe5c97 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -806,8 +806,8 @@ fn is_hot_key_modifiers_down() -> bool { #[inline] #[cfg(target_os = "windows")] -pub fn translate_key_code(event: &Event, mut key_event: KeyEvent) -> Option { - let mut key_event = map_keyboard_mode(event, key_event)?; +pub fn translate_key_code(peer: &str, event: &Event, key_event: KeyEvent) -> Option { + let mut key_event = map_keyboard_mode(peer, event, key_event)?; key_event.set_chr((key_event.chr() & 0x0000FFFF) | ((event.code as u32) << 16)); Some(key_event) } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 59f503a14..67267bd94 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1084,8 +1084,6 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { #[cfg(target_os = "windows")] fn translate_process_code(code: u32, down: bool) { crate::platform::windows::try_change_desktop(); - let vk_code = - match code >> 16 { 0 => sim_rdev_rawkey_position(code, down), vk_code => sim_rdev_rawkey_virtual(vk_code, down), From a20f6b7d5e442f305d4058818d80243093c9a2eb Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 14 Feb 2023 17:11:27 +0800 Subject: [PATCH 07/13] translate mode, fix win dead key Signed-off-by: fufesou --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2fcdef290..b308de149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4554,7 +4554,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#593f0ba37139ed6f4f88a4120e972612ec4b1c6f" +source = "git+https://github.com/fufesou/rdev#5b9fb5e42117f44e0ce0fe7cf2bddf270c75f1dc" dependencies = [ "cocoa", "core-foundation 0.9.3", From e24f72040e5577d6ed44c73f4b45635b712a19f7 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 14 Feb 2023 22:09:25 +0800 Subject: [PATCH 08/13] translate mode, trivial changes Signed-off-by: fufesou --- flutter/lib/desktop/widgets/remote_menubar.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 9f8265fec..1a1a558fa 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1515,8 +1515,11 @@ class _RemoteMenubarState extends State { continue; } } - list.add(MenuEntryRadioOption( - text: translate(mode.menu), value: mode.key)); + var text = translate(mode.menu); + if (mode.key == 'translate') { + text = '$text beta legacy 2'; + } + list.add(MenuEntryRadioOption(text: text, value: mode.key)); } } return list; From 16dd1f3c797c7a6015d9cfadef4ea33e1f8d6d67 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 14 Feb 2023 22:20:12 +0800 Subject: [PATCH 09/13] translate mode, trivial changes Signed-off-by: fufesou --- flutter/lib/desktop/widgets/remote_menubar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 1a1a558fa..66a13f606 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1517,7 +1517,7 @@ class _RemoteMenubarState extends State { } var text = translate(mode.menu); if (mode.key == 'translate') { - text = '$text beta legacy 2'; + text = '$text beta'; } list.add(MenuEntryRadioOption(text: text, value: mode.key)); } From 20be9e10b11e02b6e463ce3390abe0ab2670cfc7 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 14 Feb 2023 11:50:04 +0800 Subject: [PATCH 10/13] opt: scrollable on menubar, avoid overflow --- flutter/lib/desktop/widgets/remote_menubar.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 66a13f606..c68b394e4 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -439,9 +439,12 @@ class _RemoteMenubarState extends State { color: Colors.white, border: Border.all(color: MyTheme.border), ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: menubarItems, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: menubarItems, + ), )), _buildDraggableShowHide(context), ])); From 8df357c9411faa4a23908a3aeb6fd72414634f60 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Wed, 15 Feb 2023 15:03:19 +0800 Subject: [PATCH 11/13] refactor: use listview for file lists --- flutter/lib/consts.dart | 12 + .../lib/desktop/pages/file_manager_page.dart | 298 ++++++++++-------- flutter/lib/models/file_model.dart | 4 + 3 files changed, 186 insertions(+), 128 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 26e25a209..2b4bc7f32 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -50,6 +50,18 @@ const int kMobileMaxDisplayHeight = 1280; const int kDesktopMaxDisplayWidth = 1920; const int kDesktopMaxDisplayHeight = 1080; +const double kDesktopFileTransferNameColWidth = 200; +const double kDesktopFileTransferModifiedColWidth = 120; +const double kDesktopFileTransferRowHeight = 25.0; +const double kDesktopFileTransferHeaderHeight = 25.0; + +// https://en.wikipedia.org/wiki/Non-breaking_space +const int $nbsp = 0x00A0; + +extension StringExtension on String { + String get nonBreaking => replaceAll(' ', String.fromCharCode($nbsp)); +} + const Size kConnectionManagerWindowSize = Size(300, 400); // Tabbar transition duration, now we remove the duration const Duration kTabTransitionDuration = Duration.zero; diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 27bb0377d..fef0dd3d3 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -236,10 +236,7 @@ class _FileManagerPageState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: SingleChildScrollView( - controller: scrollController, - child: _buildDataTable(context, isLocal, scrollController), - ), + child: _buildFileList(context, isLocal, scrollController), ) ], )), @@ -248,25 +245,11 @@ class _FileManagerPageState extends State ); } - Widget _buildDataTable( + Widget _buildFileList( BuildContext context, bool isLocal, ScrollController scrollController) { - const rowHeight = 25.0; final fd = model.getCurrentDir(isLocal); final entries = fd.entries; - final sortIndex = (SortBy style) { - switch (style) { - case SortBy.name: - return 0; - case SortBy.type: - return 0; - case SortBy.modified: - return 1; - case SortBy.size: - return 2; - } - }(model.getSortStyle(isLocal)); - final sortAscending = - isLocal ? model.localSortAscending : model.remoteSortAscending; + final selectedEntries = getSelectedItems(isLocal); return MouseRegion( onEnter: (evt) { @@ -287,7 +270,6 @@ class _FileManagerPageState extends State onNext: (buffer) { debugPrint("searching next for $buffer"); assert(buffer.length == 1); - final selectedEntries = getSelectedItems(isLocal); assert(selectedEntries.length <= 1); var skipCount = 0; if (selectedEntries.items.isNotEmpty) { @@ -312,7 +294,8 @@ class _FileManagerPageState extends State return; } _jumpToEntry( - isLocal, searchResult.first, scrollController, rowHeight, buffer); + isLocal, searchResult.first, scrollController, + kDesktopFileTransferRowHeight, buffer); }, onSearch: (buffer) { debugPrint("searching for $buffer"); @@ -327,7 +310,8 @@ class _FileManagerPageState extends State return; } _jumpToEntry( - isLocal, searchResult.first, scrollController, rowHeight, buffer); + isLocal, searchResult.first, scrollController, + kDesktopFileTransferRowHeight, buffer); }, child: ObxValue( (searchText) { @@ -336,118 +320,120 @@ class _FileManagerPageState extends State return element.name.contains(searchText.value); }).toList(growable: false) : entries; - return DataTable( - key: ValueKey(isLocal ? 0 : 1), - showCheckboxColumn: false, - dataRowHeight: rowHeight, - headingRowHeight: 30, - horizontalMargin: 8, - columnSpacing: 8, - showBottomBorder: true, - sortColumnIndex: sortIndex, - sortAscending: sortAscending, - columns: [ - DataColumn( - label: Text( - translate("Name"), - ).marginSymmetric(horizontal: 4), - onSort: (columnIndex, ascending) { - model.changeSortStyle(SortBy.name, - isLocal: isLocal, ascending: ascending); - }), - DataColumn( - label: Text( - translate("Modified"), - ), - onSort: (columnIndex, ascending) { - model.changeSortStyle(SortBy.modified, - isLocal: isLocal, ascending: ascending); - }), - DataColumn( - label: Text(translate("Size")), - onSort: (columnIndex, ascending) { - model.changeSortStyle(SortBy.size, - isLocal: isLocal, ascending: ascending); - }), - ], - rows: filteredEntries.map((entry) { + final rows = filteredEntries.map((entry) { final sizeStr = entry.isFile ? readableFileSize(entry.size.toDouble()) : ""; final lastModifiedStr = entry.isDrive ? " " : "${entry.lastModified().toString().replaceAll(".000", "")} "; - return DataRow( - key: ValueKey(entry.name), - onSelectChanged: (s) { - _onSelectedChanged(getSelectedItems(isLocal), - filteredEntries, entry, isLocal); - }, - selected: getSelectedItems(isLocal).contains(entry), - cells: [ - DataCell( - Container( - width: 200, - child: Tooltip( - waitDuration: Duration(milliseconds: 500), - message: entry.name, - child: Row(children: [ - entry.isDrive - ? Image( - image: iconHardDrive, - fit: BoxFit.scaleDown, - color: Theme.of(context) - .iconTheme - .color - ?.withOpacity(0.7)) - .paddingAll(4) - : Icon( - entry.isFile - ? Icons.feed_outlined - : Icons.folder, - size: 20, - color: Theme.of(context) - .iconTheme - .color - ?.withOpacity(0.7), - ).marginSymmetric(horizontal: 2), - Expanded( - child: Text(entry.name, - overflow: TextOverflow.ellipsis)) - ]), + final isSelected = selectedEntries.contains(entry); + return SizedBox( + key: ValueKey(entry.name), + height: kDesktopFileTransferRowHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const Divider( + height: 1, + ), + Expanded( + child: Ink( + decoration: isSelected + ? BoxDecoration(color: Theme.of(context).hoverColor) + : null, + child: InkWell( + child: Row(children: [ + GestureDetector( + child: Container( + width: kDesktopFileTransferNameColWidth, + child: Tooltip( + waitDuration: Duration(milliseconds: 500), + message: entry.name, + child: Row(children: [ + entry.isDrive + ? Image( + image: iconHardDrive, + fit: BoxFit.scaleDown, + color: Theme.of(context) + .iconTheme + .color + ?.withOpacity(0.7)) + .paddingAll(4) + : Icon( + entry.isFile + ? Icons.feed_outlined + : Icons.folder, + size: 20, + color: Theme.of(context) + .iconTheme + .color + ?.withOpacity(0.7), + ).marginSymmetric(horizontal: 2), + Expanded( + child: Text(entry.name.nonBreaking, + overflow: TextOverflow.ellipsis)) + ]), + )), + onTap: () { + final items = getSelectedItems(isLocal); + // handle double click + if (_checkDoubleClick(entry)) { + openDirectory(entry.path, isLocal: isLocal); + items.clear(); + return; + } + _onSelectedChanged( + items, filteredEntries, entry, isLocal); + }, + ), + GestureDetector( + child: SizedBox( + width: kDesktopFileTransferModifiedColWidth, + child: Tooltip( + waitDuration: Duration(milliseconds: 500), + message: lastModifiedStr, + child: Text( + lastModifiedStr, + style: TextStyle( + fontSize: 12, color: MyTheme.darkGray), + )), )), - onTap: () { - final items = getSelectedItems(isLocal); - - // handle double click - if (_checkDoubleClick(entry)) { - openDirectory(entry.path, isLocal: isLocal); - items.clear(); - return; - } - _onSelectedChanged( - items, filteredEntries, entry, isLocal); - }, + GestureDetector( + child: Tooltip( + waitDuration: Duration(milliseconds: 500), + message: sizeStr, + child: Text( + sizeStr, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10, + color: MyTheme.darkGray), + ))), + ]), + ), ), - DataCell(FittedBox( - child: Tooltip( - waitDuration: Duration(milliseconds: 500), - message: lastModifiedStr, - child: Text( - lastModifiedStr, - style: TextStyle( - fontSize: 12, color: MyTheme.darkGray), - )))), - DataCell(Tooltip( - waitDuration: Duration(milliseconds: 500), - message: sizeStr, - child: Text( - sizeStr, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 10, color: MyTheme.darkGray), - ))), - ]); - }).toList(growable: false), + ), + ], + ), + ); + }).toList(growable: false); + + return Column( + children: [ + // Header + _buildFileBrowserHeader(context, isLocal), + // Body + Expanded( + child: ListView.builder( + controller: scrollController, + itemExtent: kDesktopFileTransferRowHeight, + itemBuilder: (context, index) { + return rows[index]; + }, + itemCount: rows.length, + ), + ), + ], ); }, isLocal ? _searchTextLocal : _searchTextRemote, @@ -1133,4 +1119,60 @@ class _FileManagerPageState extends State } }); } + + Widget headerItemFunc( + double? width, SortBy sortBy, String name, bool isLocal) { + final headerTextStyle = + Theme.of(context).dataTableTheme.headingTextStyle ?? TextStyle(); + return ObxValue>( + (ascending) => InkWell( + onTap: () { + if (ascending.value == null) { + ascending.value = true; + } else { + ascending.value = !ascending.value!; + } + model.changeSortStyle(sortBy, + isLocal: isLocal, ascending: ascending.value!); + }, + child: SizedBox( + width: width, + height: kDesktopFileTransferHeaderHeight, + child: Row( + children: [ + Text( + name, + style: headerTextStyle, + ).marginSymmetric( + horizontal: sortBy == SortBy.name ? 4 : 0.0), + ascending.value != null + ? Icon(ascending.value! + ? Icons.arrow_upward + : Icons.arrow_downward) + : const Offstage() + ], + ), + ), + ), () { + if (model.getSortStyle(isLocal) == sortBy) { + return model.getSortAscending(isLocal).obs; + } else { + return Rx(null); + } + }()); + } + + Widget _buildFileBrowserHeader(BuildContext context, bool isLocal) { + return Row( + children: [ + headerItemFunc(kDesktopFileTransferNameColWidth, SortBy.name, + translate("Name"), isLocal), + headerItemFunc(kDesktopFileTransferModifiedColWidth, SortBy.modified, + translate("Modified"), isLocal), + Expanded( + child: + headerItemFunc(null, SortBy.size, translate("Size"), isLocal)) + ], + ); + } } diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 18d42d143..5817e54fe 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -75,6 +75,10 @@ class FileModel extends ChangeNotifier { return isLocal ? _localSortStyle : _remoteSortStyle; } + bool getSortAscending(bool isLocal) { + return isLocal ? _localSortAscending : _remoteSortAscending; + } + FileDirectory _currentLocalDir = FileDirectory(); FileDirectory get currentLocalDir => _currentLocalDir; From 66378f63d9bb329bfa659380fc6b6de17f17b37d Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 15 Feb 2023 15:25:28 +0800 Subject: [PATCH 12/13] fix macos command-tab Signed-off-by: fufesou --- src/server/input_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 67267bd94..917a815bb 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -719,7 +719,7 @@ fn reset_input() { let _lock = VIRTUAL_INPUT_MTX.lock(); VIRTUAL_INPUT = VirtualInput::new( CGEventSourceStateID::Private, - CGEventTapLocation::AnnotatedSession, + CGEventTapLocation::Session, ) .ok(); } From 2047fd822b97659f291d02c6f573503a03eb2b8e Mon Sep 17 00:00:00 2001 From: Kingtous Date: Wed, 15 Feb 2023 16:44:40 +0800 Subject: [PATCH 13/13] opt: early unlock frame --- flutter/lib/models/model.dart | 10 ++++++---- flutter/lib/utils/image.dart | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 8cf90eba9..a1d9ff0df 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -438,15 +438,17 @@ class ImageModel with ChangeNotifier { } final pid = parent.target?.id; - ui.decodeImageFromPixels( + img.decodeImageFromPixels( rgba, parent.target?.ffiModel.display.width ?? 0, parent.target?.ffiModel.display.height ?? 0, - isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888, (image) { + isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888, + onPixelsCopied: () { + // Unlock the rgba memory from rust codes. + platformFFI.nextRgba(id); + }).then((image) { if (parent.target?.id != pid) return; try { - // Unlock the rgba memory from rust codes. - platformFFI.nextRgba(id); // my throw exception, because the listener maybe already dispose update(image); } catch (e) { diff --git a/flutter/lib/utils/image.dart b/flutter/lib/utils/image.dart index 7a6bcbc15..a153dbc63 100644 --- a/flutter/lib/utils/image.dart +++ b/flutter/lib/utils/image.dart @@ -11,6 +11,7 @@ Future decodeImageFromPixels( int? rowBytes, int? targetWidth, int? targetHeight, + VoidCallback? onPixelsCopied, bool allowUpscaling = true, }) async { if (targetWidth != null) { @@ -22,6 +23,7 @@ Future decodeImageFromPixels( final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(pixels); + onPixelsCopied?.call(); final ui.ImageDescriptor descriptor = ui.ImageDescriptor.raw( buffer, width: width,