From a603e046e32f93867c13e29b158453c8c7fc84af Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 17 May 2023 23:19:20 +0800 Subject: [PATCH] refactor resolution, mid commit Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 40 +++++++-- libs/hbb_common/protos/message.proto | 4 + libs/hbb_common/src/config.rs | 9 ++ src/client.rs | 28 ++++++- src/core_main.rs | 4 +- src/platform/windows.rs | 4 + src/server/connection.rs | 70 +++++++--------- src/server/video_service.rs | 83 ++++++++++++++++++- src/ui_session_interface.rs | 9 +- 9 files changed, 196 insertions(+), 55 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index b10d3245f..2575ddadd 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -29,6 +29,10 @@ const _kKeyLegacyMode = 'legacy'; const _kKeyMapMode = 'map'; const _kKeyTranslateMode = 'translate'; +const _kResolutionOrigin = 'Origin'; +const _kResolutionCustom = 'Custom'; +const _kResolutionFitLocal = 'FitLocal'; + class MenubarState { final kStoreKey = 'remoteMenubarState'; late RxBool show; @@ -692,6 +696,8 @@ class _DisplayMenuState extends State<_DisplayMenu> { int get windowId => stateGlobal.windowId; Map get perms => widget.ffi.ffiModel.permissions; + RxBool _isOrignalResolution = true.obs; + RxBool _isFitLocalResolution = false.obs; PeerInfo get pi => widget.ffi.ffiModel.pi; FfiModel get ffiModel => widget.ffi.ffiModel; @@ -943,6 +949,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { final groupValue = "${display.width}x${display.height}"; onChanged(String? value) async { if (value == null) return; + final list = value.split('x'); if (list.length == 2) { final w = int.tryParse(list[0]); @@ -964,14 +971,37 @@ class _DisplayMenuState extends State<_DisplayMenu> { return _SubmenuButton( ffi: widget.ffi, - menuChildren: resolutions - .map((e) => RdoMenuButton( - value: '${e.width}x${e.height}', + menuChildren: [ + RdoMenuButton( + value: _kResolutionOrigin, groupValue: groupValue, onChanged: onChanged, ffi: widget.ffi, - child: Text('${e.width}x${e.height}'))) - .toList(), + child: Text('Origin'), + ), + RdoMenuButton( + value: _kResolutionFitLocal, + groupValue: groupValue, + onChanged: onChanged, + ffi: widget.ffi, + child: Text('Fit local'), + ), + // RdoMenuButton( + // value: _kResolutionCustom, + // groupValue: groupValue, + // onChanged: onChanged, + // ffi: widget.ffi, + // child: Text('Custom resolution'), + // ), + ] + + resolutions + .map((e) => RdoMenuButton( + value: '${e.width}x${e.height}', + groupValue: groupValue, + onChanged: onChanged, + ffi: widget.ffi, + child: Text('${e.width}x${e.height}'))) + .toList(), child: Text(translate("Resolution"))); } diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 0b0de28a1..1d8e76af4 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -41,6 +41,7 @@ message DisplayInfo { string name = 5; bool online = 6; bool cursor_embedded = 7; + Resolution original_resolution = 8; } message PortForward { @@ -444,6 +445,8 @@ message SwitchDisplay { int32 height = 5; bool cursor_embedded = 6; SupportedResolutions resolutions = 7; + // Do not care about the origin point for now. + Resolution original_resolution = 8; } message PermissionInfo { @@ -501,6 +504,7 @@ message OptionMessage { SupportedDecoding supported_decoding = 10; int32 custom_fps = 11; BoolOption disable_keyboard = 12; + Resolution custom_resolution = 13; } message TestDelay { diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 224709911..16d606c78 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -191,6 +191,12 @@ pub struct Config2 { pub options: HashMap, } +#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] +pub struct Resolution { + pub w: i32, + pub h: i32, +} + #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct PeerConfig { #[serde(default, deserialize_with = "deserialize_vec_u8")] @@ -246,6 +252,9 @@ pub struct PeerConfig { #[serde(flatten)] pub view_only: ViewOnly, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_resolution: Option, + // The other scalar value must before this #[serde(default, deserialize_with = "PeerConfig::deserialize_options")] pub options: HashMap, // not use delete to represent default values diff --git a/src/client.rs b/src/client.rs index 8ec021f55..a43dbcca1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -31,9 +31,11 @@ use hbb_common::{ allow_err, anyhow::{anyhow, Context}, bail, - config::{Config, PeerConfig, PeerInfoSerde, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT}, + config::{ + Config, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT, + }, get_version_number, log, - message_proto::{option_message::BoolOption, *}, + message_proto::{option_message::BoolOption, Resolution as ProtoResolution, *}, protobuf::Message as _, rand, rendezvous_proto::*, @@ -1400,6 +1402,16 @@ impl LoginConfigHandler { msg.disable_clipboard = BoolOption::Yes.into(); n += 1; } + if let Some(r) = self.get_custom_resolution() { + if r.0 > 0 && r.1 > 0 { + msg.custom_resolution = Some(ProtoResolution { + width: r.0, + height: r.1, + ..Default::default() + }) + .into(); + } + } msg.supported_decoding = hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(Some(&self.id))); n += 1; @@ -1571,6 +1583,18 @@ impl LoginConfigHandler { } } + #[inline] + pub fn get_custom_resolution(&self) -> Option<(i32, i32)> { + self.config.custom_resolution.as_ref().map(|r| (r.w, r.h)) + } + + #[inline] + pub fn set_custom_resolution(&mut self, wh: Option<(i32, i32)>) { + let mut config = self.load_config(); + config.custom_resolution = wh.map(|r| Resolution { w: r.0, h: r.1 }); + self.save_config(config); + } + /// Get user name. /// Return the name of the given peer. If the peer has no name, return the name in the config. /// diff --git a/src/core_main.rs b/src/core_main.rs index 4aad72b90..92b56da82 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -1,10 +1,10 @@ #[cfg(not(debug_assertions))] #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::platform::breakdown_callback; +use hbb_common::log; #[cfg(not(debug_assertions))] #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::platform::register_breakdown_handler; -use hbb_common::{allow_err, log}; /// shared by flutter and sciter main function /// @@ -270,7 +270,7 @@ fn init_plugins(args: &Vec) { crate::plugin::init(); } } else if "--service" == (&args[0] as &str) { - allow_err!(crate::plugin::remove_uninstalled()); + hbb_common::allow_err!(crate::plugin::remove_uninstalled()); } } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 147d42502..97a4d9c13 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1888,6 +1888,10 @@ pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType< if FALSE == EnumDisplaySettingsW(NULL as _, ENUM_CURRENT_SETTINGS, &mut dm) { bail!("EnumDisplaySettingsW failed, errno={}", GetLastError()); } + + // to-do: check if need change + println!("REMOVE ME ========================== dm ({},{}) to ({},{})", dm.dmPelsWidth, dm.dmPelsHeight, width, height); + let wname = wide_string(name); let len = if wname.len() <= dm.dmDeviceName.len() { wname.len() diff --git a/src/server/connection.rs b/src/server/connection.rs index e537f744d..d1da5c079 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -179,8 +179,6 @@ pub struct Connection { #[cfg(windows)] portable: PortableState, from_switch: bool, - #[cfg(not(any(target_os = "android", target_os = "ios")))] - origin_resolution: HashMap, voice_call_request_timestamp: Option, audio_input_device_before_voice_call: Option, options_in_login: Option, @@ -306,8 +304,6 @@ impl Connection { #[cfg(windows)] portable: Default::default(), from_switch: false, - #[cfg(not(any(target_os = "android", target_os = "ios")))] - origin_resolution: Default::default(), audio_sender: None, voice_call_request_timestamp: None, audio_input_device_before_voice_call: None, @@ -631,15 +627,18 @@ impl Connection { conn.post_conn_audit(json!({ "action": "close", })); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - conn.reset_resolution(); - ALIVE_CONNS.lock().unwrap().retain(|&c| c != id); + let mut active_conns_lock = ALIVE_CONNS.lock().unwrap(); + active_conns_lock.retain(|&c| c != id); if let Some(s) = conn.server.upgrade() { let mut s = s.write().unwrap(); s.remove_connection(&conn.inner); #[cfg(not(any(target_os = "android", target_os = "ios")))] try_stop_record_cursor_pos(); } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if active_conns_lock.is_empty() { + video_service::reset_resolutions(); + } log::info!("#{} connection loop exited", id); } @@ -1893,25 +1892,7 @@ impl Connection { } } #[cfg(not(any(target_os = "android", target_os = "ios")))] - Some(misc::Union::ChangeResolution(r)) => { - if self.keyboard { - if let Ok(name) = video_service::get_current_display_name() { - if let Ok(current) = crate::platform::current_resolution(&name) { - if let Err(e) = crate::platform::change_resolution( - &name, - r.width as _, - r.height as _, - ) { - log::error!("change resolution failed:{:?}", e); - } else { - if !self.origin_resolution.contains_key(&name) { - self.origin_resolution.insert(name, current); - } - } - } - } - } - } + Some(misc::Union::ChangeResolution(r)) => self.change_resolution(&r), #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] Some(misc::Union::PluginRequest(p)) => { @@ -1953,6 +1934,24 @@ impl Connection { true } + fn change_resolution(&mut self, r: &Resolution) { + if self.keyboard { + if let Ok(name) = video_service::get_current_display_name() { + if let Err(e) = + crate::platform::change_resolution(&name, r.width as _, r.height as _) + { + log::error!( + "Failed to change resolution '{}' to ({},{}):{:?}", + &name, + r.width, + r.height, + e + ); + } + } + } + } + pub async fn handle_voice_call(&mut self, accepted: bool) { if let Some(ts) = self.voice_call_request_timestamp.take() { let msg = new_voice_call_response(ts.get(), accepted); @@ -2147,6 +2146,11 @@ impl Connection { } } } + if let Some(custom_resolution) = o.custom_resolution.as_ref() { + if custom_resolution.width > 0 && custom_resolution.height > 0 { + self.change_resolution(&custom_resolution); + } + } if self.keyboard { if let Ok(q) = o.block_input.enum_value() { match q { @@ -2262,20 +2266,6 @@ impl Connection { } } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn reset_resolution(&self) { - self.origin_resolution - .iter() - .map(|(name, r)| { - if let Err(e) = - crate::platform::change_resolution(&name, r.width as _, r.height as _) - { - log::error!("change resolution failed:{:?}", e); - } - }) - .count(); - } - #[cfg(not(any(target_os = "android", target_os = "ios")))] fn release_pressed_modifiers(&mut self) { for modifier in self.pressed_modifiers.iter() { diff --git a/src/server/video_service.rs b/src/server/video_service.rs index bee115fbb..00b75c8d2 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -62,8 +62,55 @@ lazy_static::lazy_static! { pub static ref IS_UAC_RUNNING: Arc> = Default::default(); pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc> = Default::default(); pub static ref LAST_SYNC_DISPLAYS: Arc>> = Default::default(); + static ref ORIGINAL_RESOLUTIONS: Arc>> = Default::default(); } +#[inline] +pub fn set_original_resolution(display_name: &str, wh: (i32, i32)) -> (i32, i32) { + let mut original_resolutions = ORIGINAL_RESOLUTIONS.write().unwrap(); + match original_resolutions.get(display_name) { + Some(r) => r.clone(), + None => { + original_resolutions.insert(display_name.to_owned(), wh.clone()); + wh + } + } +} + +#[inline] +fn get_original_resolution(display_name: &str) -> Option<(i32, i32)> { + ORIGINAL_RESOLUTIONS + .read() + .unwrap() + .get(display_name) + .map(|r| r.clone()) +} + +#[inline] +fn get_or_set_original_resolution(display_name: &str, wh: (i32, i32)) -> (i32, i32) { + let r = get_original_resolution(display_name); + if let Some(r) = r { + return r; + } + set_original_resolution(display_name, wh) +} + +#[inline] +pub fn reset_resolutions() { + for (name, (w, h)) in ORIGINAL_RESOLUTIONS.read().unwrap().iter() { + if let Err(e) = crate::platform::change_resolution(name, *w as _, *h as _) { + log::error!( + "Failed to reset resolution of display '{}' to ({},{}): {}", + name, + w, + h, + e + ); + } + } +} + +#[inline] fn is_capturer_mag_supported() -> bool { #[cfg(windows)] return scrap::CapturerMag::is_supported(); @@ -71,22 +118,27 @@ fn is_capturer_mag_supported() -> bool { false } +#[inline] pub fn capture_cursor_embedded() -> bool { scrap::is_cursor_embedded() } +#[inline] pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option) { FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).unwrap() } +#[inline] pub fn set_privacy_mode_conn_id(conn_id: i32) { *PRIVACY_MODE_CONN_ID.lock().unwrap() = conn_id } +#[inline] pub fn get_privacy_mode_conn_id() -> i32 { *PRIVACY_MODE_CONN_ID.lock().unwrap() } +#[inline] pub fn is_privacy_mode_supported() -> bool { #[cfg(windows)] return *IS_CAPTURER_MAGNIFIER_SUPPORTED @@ -491,6 +543,10 @@ fn run(sp: GenericService) -> ResultType<()> { if *SWITCH.lock().unwrap() { log::debug!("Broadcasting display switch"); let mut misc = Misc::new(); + let display_name = get_current_display_name(); + + // to-do: check if is virtual display + misc.set_switch_display(SwitchDisplay { display: c.current as _, x: c.origin.0 as _, @@ -500,12 +556,19 @@ fn run(sp: GenericService) -> ResultType<()> { cursor_embedded: capture_cursor_embedded(), #[cfg(not(any(target_os = "android", target_os = "ios")))] resolutions: Some(SupportedResolutions { - resolutions: get_current_display_name() - .map(|name| crate::platform::resolutions(&name)) + resolutions: display_name + .as_ref() + .map(|name| crate::platform::resolutions(name)) .unwrap_or(vec![]), ..SupportedResolutions::default() }) .into(), + original_resolution: Some(update_get_original_resolution( + &display_name.unwrap_or_default(), + c.width, + c.height, + )) + .into(), ..Default::default() }); let mut msg_out = Message::new(); @@ -820,6 +883,16 @@ pub fn handle_one_frame_encoded( Ok(send_conn_ids) } +#[inline] +fn update_get_original_resolution(display_name: &str, w: usize, h: usize) -> Resolution { + let wh = get_or_set_original_resolution(display_name, (w as _, h as _)); + Resolution { + width: wh.0, + height: wh.1, + ..Default::default() + } +} + pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { let mut displays = Vec::new(); let mut primary = 0; @@ -835,6 +908,12 @@ pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { name: d.name(), online: d.is_online(), cursor_embedded: false, + original_resolution: Some(update_get_original_resolution( + &d.name(), + d.width(), + d.height(), + )) + .into(), ..Default::default() }); } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index a804484a0..3d5442ae0 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -88,10 +88,7 @@ impl Session { } pub fn is_port_forward(&self) -> bool { - let conn_type = self.lc - .read() - .unwrap() - .conn_type; + let conn_type = self.lc.read().unwrap().conn_type; conn_type == ConnType::PORT_FORWARD || conn_type == ConnType::RDP } @@ -833,6 +830,10 @@ impl Session { } pub fn change_resolution(&self, width: i32, height: i32) { + self.lc + .write() + .unwrap() + .set_custom_resolution(Some((width, height))); let mut misc = Misc::new(); misc.set_change_resolution(Resolution { width,