From 2459bcd206d3505d3635c7629c0ac2c9a74b0313 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 23 Feb 2024 22:49:53 +0800 Subject: [PATCH] Fix. Multi sub windows, sync peer options (#7247) * Fix. Multi window, sync peer options Signed-off-by: fufesou * Remove unused `use` Signed-off-by: fufesou --------- Signed-off-by: fufesou --- .../lib/common/widgets/setting_widgets.dart | 2 +- flutter/lib/common/widgets/toolbar.dart | 2 +- flutter/lib/consts.dart | 5 +- .../lib/desktop/widgets/remote_toolbar.dart | 2 +- flutter/lib/models/model.dart | 16 +- src/flutter.rs | 150 ++++++++++++------ src/flutter_ffi.rs | 16 +- 7 files changed, 136 insertions(+), 57 deletions(-) diff --git a/flutter/lib/common/widgets/setting_widgets.dart b/flutter/lib/common/widgets/setting_widgets.dart index 7b7aab4d4..6c8702b21 100644 --- a/flutter/lib/common/widgets/setting_widgets.dart +++ b/flutter/lib/common/widgets/setting_widgets.dart @@ -221,7 +221,7 @@ List<(String, String)> otherDefaultSettings() { ('Privacy mode', 'privacy_mode'), if (isMobile) ('Touch mode', 'touch-mode'), ('True color (4:4:4)', 'i444'), - ('Reverse mouse wheel', 'reverse_mouse_wheel'), + ('Reverse mouse wheel', kKeyReverseMouseWheel), ('swap-left-right-mouse', 'swap-left-right-mouse'), if (isDesktop && useTextureRender) ( diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index f7f1c728a..638d62cd4 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -655,7 +655,7 @@ List toolbarKeyboardToggles(FFI ffi) { var optionValue = bind.sessionGetReverseMouseWheelSync(sessionId: sessionId) ?? ''; if (optionValue == '') { - optionValue = bind.mainGetUserDefaultOption(key: 'reverse_mouse_wheel'); + optionValue = bind.mainGetUserDefaultOption(key: kKeyReverseMouseWheel); } onChanged(bool? value) async { if (value == null) return; diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index fe4052d99..bf893951d 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -23,7 +23,8 @@ const String kPlatformAdditionsHeadless = "headless"; const String kPlatformAdditionsIsInstalled = "is_installed"; const String kPlatformAdditionsVirtualDisplays = "virtual_displays"; const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard"; -const String kPlatformAdditionsSupportedPrivacyModeImpl = "supported_privacy_mode_impl"; +const String kPlatformAdditionsSupportedPrivacyModeImpl = + "supported_privacy_mode_impl"; const String kPeerPlatformWindows = "Windows"; const String kPeerPlatformLinux = "Linux"; @@ -68,6 +69,7 @@ const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs"; const String kOptionOpenInTabs = "allow-open-in-tabs"; const String kOptionOpenInWindows = "allow-open-in-windows"; const String kOptionForceAlwaysRelay = "force-always-relay"; +const String kOptionViewOnly = "view-only"; const String kUniLinksPrefix = "rustdesk://"; const String kUrlActionClose = "close"; @@ -86,6 +88,7 @@ const String kKeyShowDisplaysAsIndividualWindows = const String kKeyUseAllMyDisplaysForTheRemoteSession = 'use_all_my_displays_for_the_remote_session'; const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar'; +const String kKeyReverseMouseWheel = "reverse_mouse_wheel"; // the executable name of the portable version const String kEnvPortableExecutable = "RUSTDESK_APPNAME"; diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index f53b16abc..bc5703b17 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1732,7 +1732,7 @@ class _KeyboardMenu extends StatelessWidget { ? (value) async { if (value == null) return; await bind.sessionToggleOption( - sessionId: ffi.sessionId, value: 'view-only'); + sessionId: ffi.sessionId, value: kOptionViewOnly); ffiModel.setViewOnly(id, value); } : null, diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index aef3d6158..7cf99c8c4 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -357,12 +357,26 @@ class FfiModel with ChangeNotifier { if (isDesktop) { gFFI.cmFileModel.onFileTransferLog(evt); } + } else if (name == 'sync_peer_option') { + _handleSyncPeerOption(evt, peerId); } else { debugPrint('Unknown event name: $name'); } }; } + _handleSyncPeerOption(Map evt, String peer) { + final k = evt['k']; + final v = evt['v']; + if (k == kOptionViewOnly) { + setViewOnly(peer, v as bool); + } else if (k == 'keyboard_mode') { + parent.target?.inputModel.updateKeyboardMode(); + } else if (k == 'input_source') { + stateGlobal.getInputSource(force: true); + } + } + onUrlSchemeReceived(Map evt) { final url = evt['url'].toString().trim(); if (url.startsWith(kUniLinksPrefix) && handleUriLink(uriString: url)) { @@ -728,7 +742,7 @@ class FfiModel with ChangeNotifier { setViewOnly( peerId, bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: 'view-only')); + sessionId: sessionId, arg: kOptionViewOnly)); } if (connType == ConnType.defaultConn) { final platformAdditions = evt['platform_additions']; diff --git a/src/flutter.rs b/src/flutter.rs index 98fd2fbc6..abcb225e9 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -13,6 +13,7 @@ use hbb_common::{ anyhow::anyhow, bail, config::LocalConfig, get_version_number, log, message_proto::*, rendezvous_proto::ConnType, ResultType, }; +use serde::Serialize; use serde_json::json; #[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))] @@ -471,12 +472,19 @@ impl FlutterHandler { /// /// * `name` - The name of the event. /// * `event` - Fields of the event content. - pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) { - let mut h: HashMap<&str, &str> = event.iter().cloned().collect(); + pub fn push_event(&self, name: &str, event: &[(&str, V)], excludes: &[&SessionID]) + where + V: Sized + Serialize + Clone, + { + let mut h: HashMap<&str, serde_json::Value> = + event.iter().map(|(k, v)| (*k, json!(*v))).collect(); debug_assert!(h.get("name").is_none()); - h.insert("name", name); + h.insert("name", json!(name)); let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned()); - for (_, session) in self.session_handlers.read().unwrap().iter() { + for (sid, session) in self.session_handlers.read().unwrap().iter() { + if excludes.contains(&sid) { + continue; + } if let Some(stream) = &session.event_stream { stream.add(EventToUI::Event(out.clone())); } @@ -539,7 +547,7 @@ impl InvokeUiSession for FlutterHandler { let colors = hbb_common::compress::decompress(&cd.colors); self.push_event( "cursor_data", - vec![ + &[ ("id", &cd.id.to_string()), ("hotx", &cd.hotx.to_string()), ("hoty", &cd.hoty.to_string()), @@ -550,17 +558,19 @@ impl InvokeUiSession for FlutterHandler { &serde_json::ser::to_string(&colors).unwrap_or("".to_owned()), ), ], + &[], ); } fn set_cursor_id(&self, id: String) { - self.push_event("cursor_id", vec![("id", &id.to_string())]); + self.push_event("cursor_id", &[("id", &id.to_string())], &[]); } fn set_cursor_position(&self, cp: CursorPosition) { self.push_event( "cursor_position", - vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())], + &[("x", &cp.x.to_string()), ("y", &cp.y.to_string())], + &[], ); } @@ -568,11 +578,11 @@ impl InvokeUiSession for FlutterHandler { fn set_display(&self, _x: i32, _y: i32, _w: i32, _h: i32, _cursor_embedded: bool) {} fn update_privacy_mode(&self) { - self.push_event("update_privacy_mode", [].into()); + self.push_event::<&str>("update_privacy_mode", &[], &[]); } fn set_permission(&self, name: &str, value: bool) { - self.push_event("permission", vec![(name, &value.to_string())]); + self.push_event("permission", &[(name, &value.to_string())], &[]); } // unused in flutter @@ -582,7 +592,7 @@ impl InvokeUiSession for FlutterHandler { const NULL: String = String::new(); self.push_event( "update_quality_status", - vec![ + &[ ("speed", &status.speed.map_or(NULL, |it| it)), ( "fps", @@ -599,38 +609,42 @@ impl InvokeUiSession for FlutterHandler { ), ("chroma", &status.chroma.map_or(NULL, |it| it.to_string())), ], + &[], ); } fn set_connection_type(&self, is_secured: bool, direct: bool) { self.push_event( "connection_ready", - vec![ + &[ ("secure", &is_secured.to_string()), ("direct", &direct.to_string()), ], + &[], ); } fn set_fingerprint(&self, fingerprint: String) { - self.push_event("fingerprint", vec![("fingerprint", &fingerprint)]); + self.push_event("fingerprint", &[("fingerprint", &fingerprint)], &[]); } fn job_error(&self, id: i32, err: String, file_num: i32) { self.push_event( "job_error", - vec![ + &[ ("id", &id.to_string()), ("err", &err), ("file_num", &file_num.to_string()), ], + &[], ); } fn job_done(&self, id: i32, file_num: i32) { self.push_event( "job_done", - vec![("id", &id.to_string()), ("file_num", &file_num.to_string())], + &[("id", &id.to_string()), ("file_num", &file_num.to_string())], + &[], ); } @@ -638,7 +652,7 @@ impl InvokeUiSession for FlutterHandler { fn clear_all_jobs(&self) {} fn load_last_job(&self, _cnt: i32, job_json: &str) { - self.push_event("load_last_job", vec![("value", job_json)]); + self.push_event("load_last_job", &[("value", job_json)], &[]); } fn update_folder_files( @@ -653,15 +667,17 @@ impl InvokeUiSession for FlutterHandler { if only_count { self.push_event( "update_folder_files", - vec![("info", &make_fd_flutter(id, entries, only_count))], + &[("info", &make_fd_flutter(id, entries, only_count))], + &[], ); } else { self.push_event( "file_dir", - vec![ - ("value", &crate::common::make_fd_to_json(id, path, entries)), + &[ ("is_local", "false"), + ("value", &crate::common::make_fd_to_json(id, path, entries)), ], + &[], ); } } @@ -682,25 +698,27 @@ impl InvokeUiSession for FlutterHandler { ) { self.push_event( "override_file_confirm", - vec![ + &[ ("id", &id.to_string()), ("file_num", &file_num.to_string()), ("read_path", &to), ("is_upload", &is_upload.to_string()), ("is_identical", &is_identical.to_string()), ], + &[], ); } fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64) { self.push_event( "job_progress", - vec![ + &[ ("id", &id.to_string()), ("file_num", &file_num.to_string()), ("speed", &speed.to_string()), ("finished_size", &finished_size.to_string()), ], + &[], ); } @@ -796,7 +814,7 @@ impl InvokeUiSession for FlutterHandler { } self.push_event( "peer_info", - vec![ + &[ ("username", &pi.username), ("hostname", &pi.hostname), ("platform", &pi.platform), @@ -808,6 +826,7 @@ impl InvokeUiSession for FlutterHandler { ("resolutions", &resolutions), ("platform_additions", &pi.platform_additions), ], + &[], ); } @@ -815,14 +834,16 @@ impl InvokeUiSession for FlutterHandler { self.peer_info.write().unwrap().displays = displays.clone(); self.push_event( "sync_peer_info", - vec![("displays", &Self::make_displays_msg(displays))], + &[("displays", &Self::make_displays_msg(displays))], + &[], ); } fn set_platform_additions(&self, data: &str) { self.push_event( "sync_platform_additions", - vec![("platform_additions", &data)], + &[("platform_additions", &data)], + &[], ) } @@ -837,10 +858,11 @@ impl InvokeUiSession for FlutterHandler { } self.push_event( "set_multiple_windows_session", - vec![( + &[( "windows_sessions", &serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned()), )], + &[], ); } @@ -850,29 +872,30 @@ impl InvokeUiSession for FlutterHandler { let has_retry = if retry { "true" } else { "" }; self.push_event( "msgbox", - vec![ + &[ ("type", msgtype), ("title", title), ("text", text), ("link", link), ("hasRetry", has_retry), ], + &[], ); } fn cancel_msgbox(&self, tag: &str) { - self.push_event("cancel_msgbox", vec![("tag", tag)]); + self.push_event("cancel_msgbox", &[("tag", tag)], &[]); } fn new_message(&self, msg: String) { - self.push_event("chat_client_mode", vec![("text", &msg)]); + self.push_event("chat_client_mode", &[("text", &msg)], &[]); } fn switch_display(&self, display: &SwitchDisplay) { let resolutions = serialize_resolutions(&display.resolutions.resolutions); self.push_event( "switch_display", - vec![ + &[ ("display", &display.display.to_string()), ("x", &display.x.to_string()), ("y", &display.y.to_string()), @@ -899,46 +922,49 @@ impl InvokeUiSession for FlutterHandler { &display.original_resolution.height.to_string(), ), ], + &[], ); } fn update_block_input_state(&self, on: bool) { self.push_event( "update_block_input_state", - [("input_state", if on { "on" } else { "off" })].into(), + &[("input_state", if on { "on" } else { "off" })], + &[], ); } #[cfg(any(target_os = "android", target_os = "ios"))] fn clipboard(&self, content: String) { - self.push_event("clipboard", vec![("content", &content)]); + self.push_event("clipboard", &[("content", &content)], &[]); } fn switch_back(&self, peer_id: &str) { - self.push_event("switch_back", [("peer_id", peer_id)].into()); + self.push_event("switch_back", &[("peer_id", peer_id)], &[]); } fn portable_service_running(&self, running: bool) { self.push_event( "portable_service_running", - [("running", running.to_string().as_str())].into(), + &[("running", running.to_string().as_str())], + &[], ); } fn on_voice_call_started(&self) { - self.push_event("on_voice_call_started", [].into()); + self.push_event::<&str>("on_voice_call_started", &[], &[]); } fn on_voice_call_closed(&self, reason: &str) { - let _res = self.push_event("on_voice_call_closed", [("reason", reason)].into()); + let _res = self.push_event("on_voice_call_closed", &[("reason", reason)], &[]); } fn on_voice_call_waiting(&self) { - self.push_event("on_voice_call_waiting", [].into()); + self.push_event::<&str>("on_voice_call_waiting", &[], &[]); } fn on_voice_call_incoming(&self) { - self.push_event("on_voice_call_incoming", [].into()); + self.push_event::<&str>("on_voice_call_incoming", &[], &[]); } #[inline] @@ -1129,6 +1155,7 @@ pub mod connection_manager { use hbb_common::log; #[cfg(any(target_os = "android"))] use scrap::android::call_main_service_set_by_name; + use serde_json::json; use crate::ui_cm_interface::InvokeUiCM; @@ -1149,50 +1176,54 @@ pub mod connection_manager { log::debug!("call_service_set_by_name fail,{}", e); } // send to UI, refresh widget - self.push_event("add_connection", vec![("client", &client_json)]); + self.push_event("add_connection", &[("client", &client_json)]); } fn remove_connection(&self, id: i32, close: bool) { self.push_event( "on_client_remove", - vec![("id", &id.to_string()), ("close", &close.to_string())], + &[("id", &id.to_string()), ("close", &close.to_string())], ); } fn new_message(&self, id: i32, text: String) { self.push_event( "chat_server_mode", - vec![("id", &id.to_string()), ("text", &text)], + &[("id", &id.to_string()), ("text", &text)], ); } fn change_theme(&self, dark: String) { - self.push_event("theme", vec![("dark", &dark)]); + self.push_event("theme", &[("dark", &dark)]); } fn change_language(&self) { - self.push_event("language", vec![]); + self.push_event::<&str>("language", &[]); } fn show_elevation(&self, show: bool) { - self.push_event("show_elevation", vec![("show", &show.to_string())]); + self.push_event("show_elevation", &[("show", &show.to_string())]); } fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) { let client_json = serde_json::to_string(&client).unwrap_or("".into()); - self.push_event("update_voice_call_state", vec![("client", &client_json)]); + self.push_event("update_voice_call_state", &[("client", &client_json)]); } fn file_transfer_log(&self, action: &str, log: &str) { - self.push_event("cm_file_transfer_log", vec![(action, log)]); + self.push_event("cm_file_transfer_log", &[(action, log)]); } } impl FlutterHandler { - fn push_event(&self, name: &str, event: Vec<(&str, &str)>) { - let mut h: HashMap<&str, &str> = event.iter().cloned().collect(); + fn push_event(&self, name: &str, event: &[(&str, V)]) + where + V: Sized + serde::Serialize + Clone, + { + let mut h: HashMap<&str, serde_json::Value> = + event.iter().map(|(k, v)| (*k, json!(*v))).collect(); debug_assert!(h.get("name").is_none()); - h.insert("name", name); + h.insert("name", json!(name)); if let Some(s) = GLOBAL_EVENT_STREAM.read().unwrap().get(super::APP_TYPE_CM) { s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned())); @@ -1442,7 +1473,7 @@ pub fn get_adapter_luid() -> Option { #[inline] pub fn push_session_event(session_id: &SessionID, name: &str, event: Vec<(&str, &str)>) { if let Some(s) = sessions::get_session_by_session_id(session_id) { - s.push_event(name, event); + s.push_event(name, &event, &[]); } } @@ -1580,6 +1611,29 @@ pub fn get_cur_session() -> Option { sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap()) } +#[inline] +pub fn try_sync_peer_option( + session: &FlutterSession, + cur_id: &SessionID, + key: &str, + _value: Option, +) { + let mut event = Vec::new(); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if key == "view-only" { + event = vec![ + ("k", json!(key.to_string())), + ("v", json!(session.lc.read().unwrap().view_only.v)), + ]; + } + if ["keyboard_mode", "input_source"].contains(&key) { + event = vec![("k", json!(key.to_string())), ("v", json!(""))]; + } + if !event.is_empty() { + session.push_event("sync_peer_option", &event, &[cur_id]); + } +} + // sessions mod is used to avoid the big lock of sessions' map. pub mod sessions { #[cfg(feature = "flutter_texture_render")] diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 0a9b6183c..33ecfd472 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1,8 +1,9 @@ use crate::{ client::file_trait::FileManager, - common::is_keyboard_mode_supported, - common::make_fd_to_json, - flutter::{self, session_add, session_add_existed, session_start_, sessions}, + common::{is_keyboard_mode_supported, make_fd_to_json}, + flutter::{ + self, session_add, session_add_existed, session_start_, sessions, try_sync_peer_option, + }, input::*, ui_interface::{self, *}, }; @@ -228,6 +229,7 @@ pub fn session_toggle_option(session_id: SessionID, value: String) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { log::warn!("toggle option {}", &value); session.toggle_option(value.clone()); + try_sync_peer_option(&session, &session_id, &value, None); } #[cfg(not(any(target_os = "android", target_os = "ios")))] if sessions::get_session_by_session_id(&session_id).is_some() && value == "disable-clipboard" { @@ -340,6 +342,7 @@ pub fn session_set_keyboard_mode(session_id: SessionID, value: String) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.save_keyboard_mode(value.clone()); _mode_updated = true; + try_sync_peer_option(&session, &session_id, "keyboard_mode", None); } #[cfg(windows)] if _mode_updated { @@ -884,7 +887,12 @@ pub fn main_get_input_source() -> SyncReturn { pub fn main_set_input_source(session_id: SessionID, value: String) { #[cfg(not(any(target_os = "android", target_os = "ios")))] - change_input_source(session_id, value); + { + change_input_source(session_id, value); + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + try_sync_peer_option(&session, &session_id, "input_source", None); + } + } } pub fn main_get_my_id() -> String {