From 6a16a03d094c50eaddc77747637c07c3d5021faa Mon Sep 17 00:00:00 2001 From: Kingtous Date: Thu, 18 May 2023 19:49:36 +0800 Subject: [PATCH 01/28] fix: add port forward in client --- src/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.rs b/src/client.rs index 72195a54c..8ec021f55 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1351,7 +1351,7 @@ impl LoginConfigHandler { /// /// * `ignore_default` - If `true`, ignore the default value of the option. fn get_option_message(&self, ignore_default: bool) -> Option { - if self.conn_type.eq(&ConnType::FILE_TRANSFER) || self.conn_type.eq(&ConnType::PORT_FORWARD) + if self.conn_type.eq(&ConnType::FILE_TRANSFER) || self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP) { return None; } @@ -1412,7 +1412,7 @@ impl LoginConfigHandler { } pub fn get_option_message_after_login(&self) -> Option { - if self.conn_type.eq(&ConnType::FILE_TRANSFER) || self.conn_type.eq(&ConnType::PORT_FORWARD) + if self.conn_type.eq(&ConnType::FILE_TRANSFER) || self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP) { return None; } @@ -1689,7 +1689,7 @@ impl LoginConfigHandler { show_hidden: !self.get_option("remote_show_hidden").is_empty(), ..Default::default() }), - ConnType::PORT_FORWARD => lr.set_port_forward(PortForward { + ConnType::PORT_FORWARD | ConnType::RDP => lr.set_port_forward(PortForward { host: self.port_forward.0.clone(), port: self.port_forward.1, ..Default::default() From a603e046e32f93867c13e29b158453c8c7fc84af Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 17 May 2023 23:19:20 +0800 Subject: [PATCH 02/28] 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, From 07500013ff67d035d31d3bef71194c5d5a6f5cea Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 18 May 2023 16:17:51 +0800 Subject: [PATCH 03/28] resolution, mid commit, to debug Signed-off-by: fufesou --- .../lib/desktop/pages/connection_page.dart | 2 - .../lib/desktop/widgets/remote_toolbar.dart | 456 +++++++++++++----- flutter/lib/models/model.dart | 49 +- src/client/io_loop.rs | 8 + src/flutter.rs | 10 + src/flutter_ffi.rs | 22 +- src/ui_session_interface.rs | 9 +- 7 files changed, 394 insertions(+), 162 deletions(-) diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 3f6bb7d16..d709d3c52 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -19,8 +19,6 @@ import '../../common/widgets/peer_tab_page.dart'; import '../../models/platform_model.dart'; import '../widgets/button.dart'; -import 'package:flutter_hbb/common/widgets/dialog.dart'; - /// Connection page for connecting to a remote peer. class ConnectionPage extends StatefulWidget { const ConnectionPage({Key? key}) : super(key: key); diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 2575ddadd..da91e0dce 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -664,71 +664,24 @@ class _ControlMenu extends StatelessWidget { } } -class _DisplayMenu extends StatefulWidget { +class ScreenAdjustor { final String id; final FFI ffi; - final MenubarState state; - final Function(bool) setFullscreen; - final Widget pluginItem; - _DisplayMenu( - {Key? key, - required this.id, - required this.ffi, - required this.state, - required this.setFullscreen}) - : pluginItem = LocationItem.createLocationItem( - id, - ffi, - kLocationClientRemoteToolbarDisplay, - true, - ), - super(key: key); - - @override - State<_DisplayMenu> createState() => _DisplayMenuState(); -} - -class _DisplayMenuState extends State<_DisplayMenu> { + final VoidCallback cbExitFullscreen; window_size.Screen? _screen; + ScreenAdjustor({ + required this.id, + required this.ffi, + required this.cbExitFullscreen, + }); + bool get isFullscreen => stateGlobal.fullscreen; - 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; - FFI get ffi => widget.ffi; - String get id => widget.id; - - @override - Widget build(BuildContext context) { - _updateScreen(); - return _IconSubmenuButton( - tooltip: 'Display Settings', - svg: "assets/display.svg", - ffi: widget.ffi, - color: _MenubarTheme.blueColor, - hoverColor: _MenubarTheme.hoverBlueColor, - menuChildren: [ - adjustWindow(), - viewStyle(), - scrollStyle(), - imageQuality(), - codec(), - resolutions(), - Divider(), - toggles(), - widget.pluginItem, - ]); - } - adjustWindow() { return futureBuilder( - future: _isWindowCanBeAdjusted(), + future: isWindowCanBeAdjusted(), hasData: (data) { final visible = data as bool; if (!visible) return Offstage(); @@ -736,18 +689,18 @@ class _DisplayMenuState extends State<_DisplayMenu> { children: [ MenuButton( child: Text(translate('Adjust Window')), - onPressed: _doAdjustWindow, - ffi: widget.ffi), + onPressed: doAdjustWindow, + ffi: ffi), Divider(), ], ); }); } - _doAdjustWindow() async { - await _updateScreen(); + doAdjustWindow() async { + await updateScreen(); if (_screen != null) { - widget.setFullscreen(false); + cbExitFullscreen(); double scale = _screen!.scaleFactor; final wndRect = await WindowController.fromWindowId(windowId).getFrame(); final mediaSize = MediaQueryData.fromWindow(ui.window).size; @@ -758,7 +711,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { double magicHeight = wndRect.bottom - wndRect.top - mediaSize.height * scale; - final canvasModel = widget.ffi.canvasModel; + final canvasModel = ffi.canvasModel; final width = (canvasModel.getDisplayWidth() * canvasModel.scale + CanvasModel.leftToEdge + CanvasModel.rightToEdge) * @@ -793,7 +746,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { } } - _updateScreen() async { + updateScreen() async { final v = await rustDeskWinManager.call( WindowType.Main, kWindowGetWindowInfo, ''); final String valueStr = v; @@ -813,8 +766,8 @@ class _DisplayMenuState extends State<_DisplayMenu> { } } - Future _isWindowCanBeAdjusted() async { - final viewStyle = await bind.sessionGetViewStyle(id: widget.id) ?? ''; + Future isWindowCanBeAdjusted() async { + final viewStyle = await bind.sessionGetViewStyle(id: id) ?? ''; if (viewStyle != kRemoteViewStyleOriginal) { return false; } @@ -833,7 +786,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { selfHeight = _screen!.frame.height; } - final canvasModel = widget.ffi.canvasModel; + final canvasModel = ffi.canvasModel; final displayWidth = canvasModel.getDisplayWidth(); final displayHeight = canvasModel.getDisplayHeight(); final requiredWidth = @@ -843,6 +796,77 @@ class _DisplayMenuState extends State<_DisplayMenu> { return selfWidth > (requiredWidth * scale) && selfHeight > (requiredHeight * scale); } +} + +class _DisplayMenu extends StatefulWidget { + final String id; + final FFI ffi; + final MenubarState state; + final Function(bool) setFullscreen; + final Widget pluginItem; + _DisplayMenu( + {Key? key, + required this.id, + required this.ffi, + required this.state, + required this.setFullscreen}) + : pluginItem = LocationItem.createLocationItem( + id, + ffi, + kLocationClientRemoteToolbarDisplay, + true, + ), + super(key: key); + + @override + State<_DisplayMenu> createState() => _DisplayMenuState(); +} + +class _DisplayMenuState extends State<_DisplayMenu> { + late final ScreenAdjustor _screenAdjustor = ScreenAdjustor( + id: widget.id, + ffi: widget.ffi, + cbExitFullscreen: () => widget.setFullscreen(false), + ); + + bool get isFullscreen => stateGlobal.fullscreen; + int get windowId => stateGlobal.windowId; + Map get perms => widget.ffi.ffiModel.permissions; + PeerInfo get pi => widget.ffi.ffiModel.pi; + FfiModel get ffiModel => widget.ffi.ffiModel; + FFI get ffi => widget.ffi; + String get id => widget.id; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + _screenAdjustor.updateScreen(); + return _IconSubmenuButton( + tooltip: 'Display Settings', + svg: "assets/display.svg", + ffi: widget.ffi, + color: _MenubarTheme.blueColor, + hoverColor: _MenubarTheme.hoverBlueColor, + menuChildren: [ + _screenAdjustor.adjustWindow(), + viewStyle(), + scrollStyle(), + imageQuality(), + codec(), + _ResolutionsMenu( + id: widget.id, + ffi: widget.ffi, + screenAdjustor: _screenAdjustor, + ), + Divider(), + toggles(), + widget.pluginItem, + ]); + } viewStyle() { return futureBuilder( @@ -941,70 +965,6 @@ class _DisplayMenuState extends State<_DisplayMenu> { }); } - resolutions() { - final resolutions = pi.resolutions; - final visible = ffiModel.keyboard && resolutions.length > 1; - if (!visible) return Offstage(); - final display = ffiModel.display; - 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]); - final h = int.tryParse(list[1]); - if (w != null && h != null) { - await bind.sessionChangeResolution( - id: widget.id, width: w, height: h); - Future.delayed(Duration(seconds: 3), () async { - final display = ffiModel.display; - if (w == display.width && h == display.height) { - if (await _isWindowCanBeAdjusted()) { - _doAdjustWindow(); - } - } - }); - } - } - } - - return _SubmenuButton( - ffi: widget.ffi, - menuChildren: [ - RdoMenuButton( - value: _kResolutionOrigin, - groupValue: groupValue, - onChanged: onChanged, - ffi: widget.ffi, - 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"))); - } - toggles() { return futureBuilder( future: toolbarDisplayToggle(context, id, ffi), @@ -1023,6 +983,242 @@ class _DisplayMenuState extends State<_DisplayMenu> { } } +class _ResolutionsMenu extends StatefulWidget { + final String id; + final FFI ffi; + final ScreenAdjustor screenAdjustor; + + _ResolutionsMenu({ + Key? key, + required this.id, + required this.ffi, + required this.screenAdjustor, + }) : super(key: key); + + @override + State<_ResolutionsMenu> createState() => _ResolutionsMenuState(); +} + +class _ResolutionsMenuState extends State<_ResolutionsMenu> { + String _groupValue = ''; + Resolution? _localResolution; + late final _customWidth = + TextEditingController(text: display.width.toString()); + late final _customHeight = + TextEditingController(text: display.height.toString()); + + PeerInfo get pi => widget.ffi.ffiModel.pi; + FfiModel get ffiModel => widget.ffi.ffiModel; + Display get display => ffiModel.display; + List get resolutions => pi.resolutions; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final visible = ffiModel.keyboard && resolutions.length > 1; + if (!visible) return Offstage(); + _groupValue = "${display.width}x${display.height}"; + + _getLocalResolution(); + return _SubmenuButton( + ffi: widget.ffi, + menuChildren: [ + _OriginalResolutionMenuButton(), + _FitLocalResolutionMenuButton(), + _customResolutionMenuButton(), + ] + + _supportedResolutionMenuButtons(), + child: Text(translate("Resolution"))); + } + + _getLocalResolution() { + _localResolution = null; + final String currentDisplay = bind.mainGetCurrentDisplay(); + if (currentDisplay.isNotEmpty) { + try { + final display = json.decode(currentDisplay); + if (display['w'] != null && display['h'] != null) { + _localResolution = Resolution(display['w'], display['h']); + } + } catch (e) { + debugPrint('Failed to decode $currentDisplay, $e'); + } + } + } + + _onChanged(String? value) async { + if (value == null) return; + + int? w; + int? h; + if (value == _kResolutionOrigin) { + w = display.originalWidth; + h = display.originalHeight; + } else if (value == _kResolutionFitLocal) { + final resolution = _getBestFitResolution(); + if (resolution != null) { + w = resolution.width; + h = resolution.height; + } + } else if (value == _kResolutionCustom) { + debugPrint( + 'REMOVE ME ======================= ${_customWidth.value} ${_customHeight.value}'); + w = int.tryParse(_customWidth.value as String); + h = int.tryParse(_customHeight.value as String); + } else { + final list = value.split('x'); + if (list.length == 2) { + w = int.tryParse(list[0]); + h = int.tryParse(list[1]); + } + } + + if (w != null && h != null) { + await bind.sessionChangeResolution( + id: widget.id, + width: w, + height: h, + ); + Future.delayed(Duration(seconds: 3), () async { + final display = ffiModel.display; + if (w == display.width && h == display.height) { + if (await widget.screenAdjustor.isWindowCanBeAdjusted()) { + widget.screenAdjustor.doAdjustWindow(); + } + } + }); + } + } + + Widget _OriginalResolutionMenuButton() { + return Offstage( + offstage: display.isOriginalResolution, + child: RdoMenuButton( + value: _kResolutionOrigin, + groupValue: _groupValue, + onChanged: _onChanged, + ffi: widget.ffi, + child: Text( + '${translate('Original')} ${display.originalWidth}x${display.originalHeight}'), + ), + ); + } + + Widget _FitLocalResolutionMenuButton() { + return Offstage( + offstage: _isRemoteResolutionFitLocal(), + child: RdoMenuButton( + value: _kResolutionFitLocal, + groupValue: _groupValue, + onChanged: _onChanged, + ffi: widget.ffi, + child: Text( + '${translate('Fit Local')} ${display.originalWidth}x${display.originalHeight}'), + ), + ); + } + + List _supportedResolutionMenuButtons() => 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(); + + Widget _customResolutionMenuButton() { + return Offstage( + offstage: _isRemoteResolutionFitLocal(), + child: RdoMenuButton( + value: _kResolutionCustom, + groupValue: _groupValue, + onChanged: _onChanged, + ffi: widget.ffi, + child: _customResolutionWidget(), + ), + ); + } + + Widget _customResolutionWidget() { + return Column( + children: [ + Text(translate('Custom')), + SizedBox( + width: 5, + ), + _resolutionInput(_customWidth), + SizedBox( + width: 3, + ), + Text('x'), + SizedBox( + width: 3, + ), + _resolutionInput(_customHeight), + ], + ); + } + + TextField _resolutionInput(TextEditingController controller) { + return TextField( + decoration: InputDecoration( + border: InputBorder.none, + isDense: true, + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(4), + FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), + ], + controller: controller, + ); + } + + Resolution? _getBestFitResolution() { + if (_localResolution == null) { + return null; + } + + squareDistance(Resolution lhs, Resolution rhs) => + (lhs.width - rhs.width) * (lhs.width - rhs.width) + + (lhs.height - rhs.height) * (lhs.height - rhs.height); + + Resolution? res; + for (final r in resolutions) { + if (r.width <= _localResolution!.width && + r.height <= _localResolution!.height) { + if (res == null) { + res = r; + } else { + if (squareDistance(r, _localResolution!) < + squareDistance(res, _localResolution!)) { + res = r; + } + } + } + } + return res; + } + + bool _isRemoteResolutionFitLocal() { + if (_localResolution == null) { + return true; + } + final bestFitResolution = _getBestFitResolution(); + if (bestFitResolution == null) { + return true; + } + return bestFitResolution.width == display.width && + bestFitResolution.height == display.height; + } +} + class _KeyboardMenu extends StatelessWidget { final String id; final FFI ffi; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index e94452bd7..a57a51752 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -295,11 +295,15 @@ class FfiModel with ChangeNotifier { handleSwitchDisplay(Map evt, String peerId) { _pi.currentDisplay = int.parse(evt['display']); var newDisplay = Display(); - newDisplay.x = double.parse(evt['x']); - newDisplay.y = double.parse(evt['y']); - newDisplay.width = int.parse(evt['width']); - newDisplay.height = int.parse(evt['height']); - newDisplay.cursorEmbedded = int.parse(evt['cursor_embedded']) == 1; + newDisplay.x = double.tryParse(evt['x']) ?? newDisplay.x; + newDisplay.y = double.tryParse(evt['y']) ?? newDisplay.y; + newDisplay.width = int.tryParse(evt['width']) ?? newDisplay.width; + newDisplay.height = int.tryParse(evt['height']) ?? newDisplay.height; + newDisplay.cursorEmbedded = int.tryParse(evt['cursor_embedded']) == 1; + newDisplay.originalWidth = + int.tryParse(evt['original_width']) ?? newDisplay.originalWidth; + newDisplay.originalHeight = + int.tryParse(evt['original_height']) ?? newDisplay.originalHeight; _updateCurDisplay(peerId, newDisplay); @@ -466,14 +470,7 @@ class FfiModel with ChangeNotifier { _pi.displays = []; List displays = json.decode(evt['displays']); for (int i = 0; i < displays.length; ++i) { - Map d0 = displays[i]; - var d = Display(); - d.x = d0['x'].toDouble(); - d.y = d0['y'].toDouble(); - d.width = d0['width']; - d.height = d0['height']; - d.cursorEmbedded = d0['cursor_embedded'] == 1; - _pi.displays.add(d); + _pi.displays.add(evtToDisplay(displays[i])); } stateGlobal.displaysCount.value = _pi.displays.length; if (_pi.currentDisplay < _pi.displays.length) { @@ -533,20 +530,25 @@ class FfiModel with ChangeNotifier { } } + Display evtToDisplay(Map evt) { + var d = Display(); + d.x = evt['x']?.toDouble() ?? d.x; + d.y = evt['y']?.toDouble() ?? d.y; + d.width = evt['width'] ?? d.width; + d.height = evt['height'] ?? d.height; + d.cursorEmbedded = evt['cursor_embedded'] == 1; + d.originalWidth = evt['original_width'] ?? d.originalWidth; + d.originalHeight = evt['original_height'] ?? d.originalHeight; + return d; + } + /// Handle the peer info synchronization event based on [evt]. handleSyncPeerInfo(Map evt, String peerId) async { if (evt['displays'] != null) { List displays = json.decode(evt['displays']); List newDisplays = []; for (int i = 0; i < displays.length; ++i) { - Map d0 = displays[i]; - var d = Display(); - d.x = d0['x'].toDouble(); - d.y = d0['y'].toDouble(); - d.width = d0['width']; - d.height = d0['height']; - d.cursorEmbedded = d0['cursor_embedded'] == 1; - newDisplays.add(d); + newDisplays.add(evtToDisplay(displays[i])); } _pi.displays = newDisplays; stateGlobal.displaysCount.value = _pi.displays.length; @@ -1718,6 +1720,8 @@ class Display { int width = 0; int height = 0; bool cursorEmbedded = false; + int originalWidth = 0; + int originalHeight = 0; Display() { width = (isDesktop || isWebDesktop) @@ -1740,6 +1744,9 @@ class Display { other.width == width && other.height == height && other.cursorEmbedded == cursorEmbedded; + + bool get isOriginalResolution => + width == originalWidth && height == originalHeight; } class Resolution { diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index d38e7f104..597c720ab 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1211,6 +1211,14 @@ impl Remote { s.cursor_embedded, ); } + let custom_resolution = if s.width != s.original_resolution.width + || s.height != s.original_resolution.height + { + Some((s.width, s.height)) + } else { + None + }; + self.handler.set_custom_resolution(custom_resolution); } Some(misc::Union::CloseReason(c)) => { self.handler.msgbox("error", "Connection Error", &c, ""); diff --git a/src/flutter.rs b/src/flutter.rs index f6461b744..1748d63df 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -286,6 +286,8 @@ impl FlutterHandler { h.insert("width", d.width); h.insert("height", d.height); h.insert("cursor_embedded", if d.cursor_embedded { 1 } else { 0 }); + h.insert("original_width", d.original_resolution.width); + h.insert("original_height", d.original_resolution.height); msg_vec.push(h); } serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned()) @@ -618,6 +620,14 @@ impl InvokeUiSession for FlutterHandler { .to_string(), ), ("resolutions", &resolutions), + ( + "original_width", + &display.original_resolution.width.to_string(), + ), + ( + "original_height", + &display.original_resolution.height.to_string(), + ), ], ); } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 9aadca1bc..d072c58a3 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -880,6 +880,18 @@ pub fn main_handle_relay_id(id: String) -> String { handle_relay_id(id) } +pub fn main_get_current_display() -> SyncReturn { + let display_info = match crate::video_service::get_current_display() { + Ok((_, _, display)) => serde_json::to_string(&HashMap::from([ + ("w", display.width()), + ("h", display.height()), + ])) + .unwrap_or_default(), + Err(..) => "".to_string(), + }; + SyncReturn(display_info) +} + pub fn session_add_port_forward( id: String, local_port: i32, @@ -1426,10 +1438,10 @@ pub fn plugin_event(_id: String, _peer: String, _event: Vec) { } } -pub fn plugin_register_event_stream(id: String, event2ui: StreamSink) { +pub fn plugin_register_event_stream(_id: String, _event2ui: StreamSink) { #[cfg(feature = "plugin_framework")] { - crate::plugin::native_handlers::session::session_register_event_stream(id, event2ui); + crate::plugin::native_handlers::session::session_register_event_stream(_id, _event2ui); } } @@ -1577,16 +1589,16 @@ pub fn plugin_list_reload() { } } -pub fn plugin_install(id: String, b: bool) { +pub fn plugin_install(_id: String, _b: bool) { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - if b { + if _b { if let Err(e) = crate::plugin::install_plugin(&id) { log::error!("Failed to install plugin '{}': {}", id, e); } } else { - crate::plugin::uninstall_plugin(&id, true); + crate::plugin::uninstall_plugin(&_id, true); } } } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 3d5442ae0..8a43642d8 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -829,11 +829,12 @@ impl Session { } } + #[inline] + pub fn set_custom_resolution(&mut self, wh: Option<(i32, i32)>) { + self.lc.write().unwrap().set_custom_resolution(wh); + } + 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, From c6ccee67aae888584b81d8928c53802915806139 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 18 May 2023 21:25:48 +0800 Subject: [PATCH 04/28] remember resolution, mid commit Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 57 +++++----- flutter/lib/models/model.dart | 21 +++- src/platform/windows.rs | 75 ++++++++----- src/server/video_service.rs | 105 ++++++++++++------ 4 files changed, 165 insertions(+), 93 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index da91e0dce..6d48777ed 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1019,17 +1019,22 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { @override Widget build(BuildContext context) { - final visible = ffiModel.keyboard && resolutions.length > 1; + final isVirtualDisplay = display.isVirtualDisplayResolution; + final visible = + ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1); if (!visible) return Offstage(); - _groupValue = "${display.width}x${display.height}"; - + _groupValue = '${display.width}x${display.height}'; _getLocalResolution(); + final showOriginalBtn = + display.isOriginalResolutionSet && !display.isOriginalResolution; + final showFitLocalBtn = !_isRemoteResolutionFitLocal(); + return _SubmenuButton( ffi: widget.ffi, menuChildren: [ - _OriginalResolutionMenuButton(), - _FitLocalResolutionMenuButton(), - _customResolutionMenuButton(), + _OriginalResolutionMenuButton(showOriginalBtn), + _FitLocalResolutionMenuButton(showFitLocalBtn), + _customResolutionMenuButton(isVirtualDisplay), ] + _supportedResolutionMenuButtons(), child: Text(translate("Resolution"))); @@ -1065,8 +1070,6 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { h = resolution.height; } } else if (value == _kResolutionCustom) { - debugPrint( - 'REMOVE ME ======================= ${_customWidth.value} ${_customHeight.value}'); w = int.tryParse(_customWidth.value as String); h = int.tryParse(_customHeight.value as String); } else { @@ -1094,9 +1097,9 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } } - Widget _OriginalResolutionMenuButton() { + Widget _OriginalResolutionMenuButton(bool showOriginalBtn) { return Offstage( - offstage: display.isOriginalResolution, + offstage: !showOriginalBtn, child: RdoMenuButton( value: _kResolutionOrigin, groupValue: _groupValue, @@ -1108,16 +1111,16 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { ); } - Widget _FitLocalResolutionMenuButton() { + Widget _FitLocalResolutionMenuButton(bool showFitLocalBtn) { return Offstage( - offstage: _isRemoteResolutionFitLocal(), + offstage: !showFitLocalBtn, child: RdoMenuButton( value: _kResolutionFitLocal, groupValue: _groupValue, onChanged: _onChanged, ffi: widget.ffi, child: Text( - '${translate('Fit Local')} ${display.originalWidth}x${display.originalHeight}'), + '${translate('Fit Local')} ${_localResolution?.width ?? 0}x${_localResolution?.height ?? 0}'), ), ); } @@ -1131,9 +1134,9 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { child: Text('${e.width}x${e.height}'))) .toList(); - Widget _customResolutionMenuButton() { + Widget _customResolutionMenuButton(bool showCustomBtn) { return Offstage( - offstage: _isRemoteResolutionFitLocal(), + offstage: !showCustomBtn, child: RdoMenuButton( value: _kResolutionCustom, groupValue: _groupValue, @@ -1148,18 +1151,18 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { return Column( children: [ Text(translate('Custom')), - SizedBox( + Container( width: 5, ), - _resolutionInput(_customWidth), - SizedBox( + // _resolutionInput(_customWidth), + Container( width: 3, ), Text('x'), - SizedBox( + Container( width: 3, ), - _resolutionInput(_customHeight), + // _resolutionInput(_customHeight), ], ); } @@ -1185,21 +1188,21 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { return null; } + if (display.isVirtualDisplayResolution) { + return _localResolution!; + } + squareDistance(Resolution lhs, Resolution rhs) => (lhs.width - rhs.width) * (lhs.width - rhs.width) + (lhs.height - rhs.height) * (lhs.height - rhs.height); - Resolution? res; + Resolution res = Resolution(display.width, display.height); for (final r in resolutions) { if (r.width <= _localResolution!.width && r.height <= _localResolution!.height) { - if (res == null) { + if (squareDistance(r, _localResolution!) < + squareDistance(res, _localResolution!)) { res = r; - } else { - if (squareDistance(r, _localResolution!) < - squareDistance(res, _localResolution!)) { - res = r; - } } } } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index a57a51752..294b348b9 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -301,9 +301,9 @@ class FfiModel with ChangeNotifier { newDisplay.height = int.tryParse(evt['height']) ?? newDisplay.height; newDisplay.cursorEmbedded = int.tryParse(evt['cursor_embedded']) == 1; newDisplay.originalWidth = - int.tryParse(evt['original_width']) ?? newDisplay.originalWidth; + int.tryParse(evt['original_width']) ?? kInvalidResolutionValue; newDisplay.originalHeight = - int.tryParse(evt['original_height']) ?? newDisplay.originalHeight; + int.tryParse(evt['original_height']) ?? kInvalidResolutionValue; _updateCurDisplay(peerId, newDisplay); @@ -537,8 +537,8 @@ class FfiModel with ChangeNotifier { d.width = evt['width'] ?? d.width; d.height = evt['height'] ?? d.height; d.cursorEmbedded = evt['cursor_embedded'] == 1; - d.originalWidth = evt['original_width'] ?? d.originalWidth; - d.originalHeight = evt['original_height'] ?? d.originalHeight; + d.originalWidth = evt['original_width'] ?? kInvalidResolutionValue; + d.originalHeight = evt['original_height'] ?? kInvalidResolutionValue; return d; } @@ -1714,14 +1714,17 @@ class FFI { } } +const kInvalidResolutionValue = -1; +const kVirtualDisplayResolutionValue = 0; + class Display { double x = 0; double y = 0; int width = 0; int height = 0; bool cursorEmbedded = false; - int originalWidth = 0; - int originalHeight = 0; + int originalWidth = kInvalidResolutionValue; + int originalHeight = kInvalidResolutionValue; Display() { width = (isDesktop || isWebDesktop) @@ -1745,6 +1748,12 @@ class Display { other.height == height && other.cursorEmbedded == cursorEmbedded; + bool get isOriginalResolutionSet => + originalWidth != kInvalidResolutionValue && + originalHeight != kInvalidResolutionValue; + bool get isVirtualDisplayResolution => + originalWidth == kVirtualDisplayResolutionValue && + originalHeight == kVirtualDisplayResolutionValue; bool get isOriginalResolution => width == originalWidth && height == originalHeight; } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 97a4d9c13..431bfd929 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -56,6 +56,10 @@ use windows_service::{ use winreg::enums::*; use winreg::RegKey; +// This string is defined here. +// https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 +const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; + pub fn get_cursor_pos() -> Option<(i32, i32)> { unsafe { #[allow(invalid_value)] @@ -1831,21 +1835,25 @@ pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> { Ok(()) } +#[inline] +fn str_to_device_name(name: &str) -> [u16; 32] { + let mut device_name: Vec = wide_string(name); + if device_name.len() < 32 { + device_name.resize(32, 0); + } + let mut result = [0; 32]; + result.copy_from_slice(&device_name[..32]); + result +} + pub fn resolutions(name: &str) -> Vec { unsafe { let mut dm: DEVMODEW = std::mem::zeroed(); - let wname = wide_string(name); - let len = if wname.len() <= dm.dmDeviceName.len() { - wname.len() - } else { - dm.dmDeviceName.len() - }; - std::ptr::copy_nonoverlapping(wname.as_ptr(), dm.dmDeviceName.as_mut_ptr(), len); - dm.dmSize = std::mem::size_of::() as _; let mut v = vec![]; let mut num = 0; + let device_name = str_to_device_name(name); loop { - if EnumDisplaySettingsW(NULL as _, num, &mut dm) == 0 { + if EnumDisplaySettingsW(device_name.as_ptr(), num, &mut dm) == 0 { break; } let r = Resolution { @@ -1866,8 +1874,8 @@ pub fn current_resolution(name: &str) -> ResultType { unsafe { let mut dm: DEVMODEW = std::mem::zeroed(); dm.dmSize = std::mem::size_of::() as _; - let wname = wide_string(name); - if EnumDisplaySettingsW(wname.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) == 0 { + let device_name = str_to_device_name(name); + if EnumDisplaySettingsW(device_name.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) == 0 { bail!( "failed to get currrent resolution, errno={}", GetLastError() @@ -1882,29 +1890,46 @@ pub fn current_resolution(name: &str) -> ResultType { } } +pub fn is_virtual_display(name: &str) -> ResultType { + let device_name = str_to_device_name(name); + let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; + dd.cb = std::mem::size_of::() as _; + let ok = unsafe { EnumDisplayDevicesW(device_name.as_ptr(), 0, &mut dd as _, 0) }; + if ok == FALSE { + bail!( + "enumerate display devices with device name '{}', errno {}", + name, + unsafe { GetLastError() } + ); + } + match std::string::String::from_utf16(&dd.DeviceString) { + Ok(s) => Ok(&s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING), + Err(e) => bail!( + "convert the device string of '{}' to string: {}", + name, + e + ), + } +} + pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> { + let device_name = str_to_device_name(name); unsafe { let mut dm: DEVMODEW = std::mem::zeroed(); - if FALSE == EnumDisplaySettingsW(NULL as _, ENUM_CURRENT_SETTINGS, &mut dm) { + if FALSE == EnumDisplaySettingsW(device_name.as_ptr() 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() - } else { - dm.dmDeviceName.len() - }; - std::ptr::copy_nonoverlapping(wname.as_ptr(), dm.dmDeviceName.as_mut_ptr(), len); - dm.dmSize = std::mem::size_of::() as _; + // dmPelsWidth and dmPelsHeight is the same to width and height + // Because this process is running in dpi awareness mode. + if dm.dmPelsWidth == width as u32 && dm.dmPelsHeight == height as u32 { + return Ok(()); + } dm.dmPelsWidth = width as _; dm.dmPelsHeight = height as _; dm.dmFields = DM_PELSHEIGHT | DM_PELSWIDTH; let res = ChangeDisplaySettingsExW( - wname.as_ptr(), + device_name.as_ptr(), &mut dm, NULL as _, CDS_UPDATEREGISTRY | CDS_GLOBAL | CDS_RESET, diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 00b75c8d2..5a63a60a6 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -25,9 +25,12 @@ use crate::virtual_display_manager; use crate::{platform::windows::is_process_consent_running, privacy_win_mag}; #[cfg(windows)] use hbb_common::get_version_number; -use hbb_common::tokio::sync::{ - mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - Mutex as TokioMutex, +use hbb_common::{ + protobuf::MessageField, + tokio::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + Mutex as TokioMutex, + }, }; #[cfg(not(windows))] use scrap::Capturer; @@ -65,8 +68,9 @@ lazy_static::lazy_static! { static ref ORIGINAL_RESOLUTIONS: Arc>> = Default::default(); } +// Not virtual display #[inline] -pub fn set_original_resolution(display_name: &str, wh: (i32, i32)) -> (i32, i32) { +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(), @@ -77,8 +81,9 @@ pub fn set_original_resolution(display_name: &str, wh: (i32, i32)) -> (i32, i32) } } +// Not virtual display #[inline] -fn get_original_resolution(display_name: &str) -> Option<(i32, i32)> { +fn get_original_resolution_(display_name: &str) -> Option<(i32, i32)> { ORIGINAL_RESOLUTIONS .read() .unwrap() @@ -86,13 +91,25 @@ fn get_original_resolution(display_name: &str) -> Option<(i32, i32)> { .map(|r| r.clone()) } +// Not virtual display #[inline] -fn get_or_set_original_resolution(display_name: &str, wh: (i32, i32)) -> (i32, i32) { - let r = get_original_resolution(display_name); +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) + set_original_resolution_(display_name, wh) +} + +// Not virtual display +#[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() + } } #[inline] @@ -543,10 +560,13 @@ 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 - + let display_name = get_current_display_name().unwrap_or_default(); + println!( + "REMOVE ME ============================ display_name: {:?}, is_virtual: {}", + display_name, + is_virtual_display(&display_name) + ); + let original_resolution = get_original_resolution(&display_name, c.width, c.height); misc.set_switch_display(SwitchDisplay { display: c.current as _, x: c.origin.0 as _, @@ -556,19 +576,15 @@ fn run(sp: GenericService) -> ResultType<()> { cursor_embedded: capture_cursor_embedded(), #[cfg(not(any(target_os = "android", target_os = "ios")))] resolutions: Some(SupportedResolutions { - resolutions: display_name - .as_ref() - .map(|name| crate::platform::resolutions(name)) - .unwrap_or(vec![]), + resolutions: if display_name.is_empty() { + vec![] + } else { + crate::platform::resolutions(&display_name) + }, ..SupportedResolutions::default() }) .into(), - original_resolution: Some(update_get_original_resolution( - &display_name.unwrap_or_default(), - c.width, - c.height, - )) - .into(), + original_resolution, ..Default::default() }); let mut msg_out = Message::new(); @@ -884,15 +900,37 @@ pub fn handle_one_frame_encoded( } #[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() +fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageField { + Some(if is_virtual_display(&display_name) { + Resolution { + width: 0, + height: 0, + ..Default::default() + } + } else { + update_get_original_resolution_(&display_name, w, h) + }) + .into() +} + +#[inline] +#[cfg(target_os = "windows")] +fn is_virtual_display(name: &str) -> bool { + match crate::platform::windows::is_virtual_display(&name) { + Ok(b) => b, + Err(e) => { + log::error!("Failed to check is virtual display for '{}': {}", &name, e); + false + } } } +#[inline] +#[cfg(not(target_os = "windows"))] +fn is_virtual_display(_name: &str) -> bool { + false +} + pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { let mut displays = Vec::new(); let mut primary = 0; @@ -900,20 +938,17 @@ pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { if d.is_primary() { primary = i; } + let display_name = d.name(); + let original_resolution = get_original_resolution(&display_name, d.width(), d.height()); displays.push(DisplayInfo { x: d.origin().0 as _, y: d.origin().1 as _, width: d.width() as _, height: d.height() as _, - name: d.name(), + name: display_name, online: d.is_online(), cursor_embedded: false, - original_resolution: Some(update_get_original_resolution( - &d.name(), - d.width(), - d.height(), - )) - .into(), + original_resolution, ..Default::default() }); } From b8ea705a2183d2d98b5e2c5f164f1d360c96b90f Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 18 May 2023 21:44:09 +0800 Subject: [PATCH 05/28] fix check virtual display on windows Signed-off-by: fufesou --- src/platform/windows.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 431bfd929..dd91a5c25 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1891,25 +1891,25 @@ pub fn current_resolution(name: &str) -> ResultType { } pub fn is_virtual_display(name: &str) -> ResultType { - let device_name = str_to_device_name(name); let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; - dd.cb = std::mem::size_of::() as _; - let ok = unsafe { EnumDisplayDevicesW(device_name.as_ptr(), 0, &mut dd as _, 0) }; - if ok == FALSE { - bail!( - "enumerate display devices with device name '{}', errno {}", - name, - unsafe { GetLastError() } - ); - } - match std::string::String::from_utf16(&dd.DeviceString) { - Ok(s) => Ok(&s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING), - Err(e) => bail!( - "convert the device string of '{}' to string: {}", - name, - e - ), + dd.cb = std::mem::size_of::() as DWORD; + let mut i_dev_num = 0; + loop { + let result = unsafe { EnumDisplayDevicesW(null_mut(), i_dev_num, &mut dd, 0) }; + if result == 0 { + break; + } + if let Ok(device_name) = String::from_utf16(&dd.DeviceName) { + if device_name == name { + return match std::string::String::from_utf16(&dd.DeviceString) { + Ok(s) => Ok(&s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING), + Err(e) => bail!("convert the device string of '{}' to string: {}", name, e), + }; + } + } + i_dev_num += 1; } + bail!("No such display '{}'", name) } pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> { From ac96ddaecb400778a55305e98d8ef34000fcac60 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 18 May 2023 21:57:18 +0800 Subject: [PATCH 06/28] windows, fix display name comparation Signed-off-by: fufesou --- src/platform/windows.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index dd91a5c25..46f92d511 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1890,6 +1890,17 @@ pub fn current_resolution(name: &str) -> ResultType { } } +#[inline] +fn is_device_name(device_name: &str, name: &str) -> bool { + if name.len() == device_name.len() { + name == device_name + } else if name.len() > device_name.len() { + false + } else { + &device_name[..name.len()] == name && device_name.as_bytes()[name.len() as usize] == 0 + } +} + pub fn is_virtual_display(name: &str) -> ResultType { let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; dd.cb = std::mem::size_of::() as DWORD; @@ -1900,7 +1911,7 @@ pub fn is_virtual_display(name: &str) -> ResultType { break; } if let Ok(device_name) = String::from_utf16(&dd.DeviceName) { - if device_name == name { + if is_device_name(&device_name, name) { return match std::string::String::from_utf16(&dd.DeviceString) { Ok(s) => Ok(&s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING), Err(e) => bail!("convert the device string of '{}' to string: {}", name, e), From c4cefdb54b6b3a8608150e5d59aea1c6b6adc681 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 18 May 2023 22:56:16 +0800 Subject: [PATCH 07/28] fix mismatch of current display index and current display name Signed-off-by: fufesou --- src/server/connection.rs | 2 ++ src/server/video_service.rs | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index d1da5c079..200a6dfb5 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1043,6 +1043,8 @@ impl Connection { }) .into(); #[cfg(not(any(target_os = "android", target_os = "ios")))] + video_service::try_reset_current_display(); + #[cfg(not(any(target_os = "android", target_os = "ios")))] { pi.resolutions = Some(SupportedResolutions { resolutions: video_service::get_current_display_name() diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 5a63a60a6..747fff73e 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -561,11 +561,6 @@ fn run(sp: GenericService) -> ResultType<()> { log::debug!("Broadcasting display switch"); let mut misc = Misc::new(); let display_name = get_current_display_name().unwrap_or_default(); - println!( - "REMOVE ME ============================ display_name: {:?}, is_virtual: {}", - display_name, - is_virtual_display(&display_name) - ); let original_resolution = get_original_resolution(&display_name, c.width, c.height); misc.set_switch_display(SwitchDisplay { display: c.current as _, @@ -967,6 +962,15 @@ pub fn is_inited_msg() -> Option { None } +// switch to primary display if long time (30 seconds) no users +#[inline] +pub fn try_reset_current_display() { + if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 { + *CURRENT_DISPLAY.lock().unwrap() = usize::MAX; + } + *LAST_ACTIVE.lock().unwrap() = time::Instant::now(); +} + pub async fn get_displays() -> ResultType<(usize, Vec)> { #[cfg(target_os = "linux")] { @@ -974,10 +978,6 @@ pub async fn get_displays() -> ResultType<(usize, Vec)> { return super::wayland::get_displays().await; } } - // switch to primary display if long time (30 seconds) no users - if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 { - *CURRENT_DISPLAY.lock().unwrap() = usize::MAX; - } Ok(get_displays_2(&try_get_displays()?)) } @@ -1071,6 +1071,8 @@ pub fn get_current_display() -> ResultType<(usize, usize, Display)> { get_current_display_2(try_get_displays()?) } +// `try_reset_current_display` is needed because `get_displays` may change the current display, +// which may cause the mismatch of current display and the current display name. pub fn get_current_display_name() -> ResultType { Ok(get_current_display_2(try_get_displays()?)?.2.name()) } From d339fd178bf66ed806fca792e705e8c4fb5ac809 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 18 May 2023 23:45:38 +0800 Subject: [PATCH 08/28] remove custom resolution Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 184 +++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 6d48777ed..17b1ce540 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -29,10 +29,6 @@ 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; @@ -1002,10 +998,6 @@ class _ResolutionsMenu extends StatefulWidget { class _ResolutionsMenuState extends State<_ResolutionsMenu> { String _groupValue = ''; Resolution? _localResolution; - late final _customWidth = - TextEditingController(text: display.width.toString()); - late final _customHeight = - TextEditingController(text: display.height.toString()); PeerInfo get pi => widget.ffi.ffiModel.pi; FfiModel get ffiModel => widget.ffi.ffiModel; @@ -1020,8 +1012,9 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { @override Widget build(BuildContext context) { final isVirtualDisplay = display.isVirtualDisplayResolution; - final visible = - ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1); + // final visible = + // ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1); + final visible = ffiModel.keyboard && resolutions.length > 1; if (!visible) return Offstage(); _groupValue = '${display.width}x${display.height}'; _getLocalResolution(); @@ -1034,12 +1027,22 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { menuChildren: [ _OriginalResolutionMenuButton(showOriginalBtn), _FitLocalResolutionMenuButton(showFitLocalBtn), - _customResolutionMenuButton(isVirtualDisplay), + // _customResolutionMenuButton(isVirtualDisplay), + _menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay), ] + _supportedResolutionMenuButtons(), child: Text(translate("Resolution"))); } + _menuDivider( + bool showOriginalBtn, bool showFitLocalBtn, bool isVirtualDisplay) { + return Offstage( + // offstage: !(showOriginalBtn || showFitLocalBtn || isVirtualDisplay), + offstage: !(showOriginalBtn || showFitLocalBtn), + child: Divider(), + ); + } + _getLocalResolution() { _localResolution = null; final String currentDisplay = bind.mainGetCurrentDisplay(); @@ -1057,53 +1060,38 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { _onChanged(String? value) async { if (value == null) return; - - int? w; - int? h; - if (value == _kResolutionOrigin) { - w = display.originalWidth; - h = display.originalHeight; - } else if (value == _kResolutionFitLocal) { - final resolution = _getBestFitResolution(); - if (resolution != null) { - w = resolution.width; - h = resolution.height; - } - } else if (value == _kResolutionCustom) { - w = int.tryParse(_customWidth.value as String); - h = int.tryParse(_customHeight.value as String); - } else { - final list = value.split('x'); - if (list.length == 2) { - w = int.tryParse(list[0]); - h = int.tryParse(list[1]); + final list = value.split('x'); + if (list.length == 2) { + final w = int.tryParse(list[0]); + final h = int.tryParse(list[1]); + if (w != null && h != null) { + await _changeResolution(w, h); } } + } - if (w != null && h != null) { - await bind.sessionChangeResolution( - id: widget.id, - width: w, - height: h, - ); - Future.delayed(Duration(seconds: 3), () async { - final display = ffiModel.display; - if (w == display.width && h == display.height) { - if (await widget.screenAdjustor.isWindowCanBeAdjusted()) { - widget.screenAdjustor.doAdjustWindow(); - } + _changeResolution(int w, int h) async { + await bind.sessionChangeResolution( + id: widget.id, + width: w, + height: h, + ); + Future.delayed(Duration(seconds: 3), () async { + final display = ffiModel.display; + if (w == display.width && h == display.height) { + if (await widget.screenAdjustor.isWindowCanBeAdjusted()) { + widget.screenAdjustor.doAdjustWindow(); } - }); - } + } + }); } Widget _OriginalResolutionMenuButton(bool showOriginalBtn) { return Offstage( offstage: !showOriginalBtn, - child: RdoMenuButton( - value: _kResolutionOrigin, - groupValue: _groupValue, - onChanged: _onChanged, + child: MenuButton( + onPressed: () => + _changeResolution(display.originalWidth, display.originalHeight), ffi: widget.ffi, child: Text( '${translate('Original')} ${display.originalWidth}x${display.originalHeight}'), @@ -1114,10 +1102,13 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { Widget _FitLocalResolutionMenuButton(bool showFitLocalBtn) { return Offstage( offstage: !showFitLocalBtn, - child: RdoMenuButton( - value: _kResolutionFitLocal, - groupValue: _groupValue, - onChanged: _onChanged, + child: MenuButton( + onPressed: () { + final resolution = _getBestFitResolution(); + if (resolution != null) { + _changeResolution(resolution.width, resolution.height); + } + }, ffi: widget.ffi, child: Text( '${translate('Fit Local')} ${_localResolution?.width ?? 0}x${_localResolution?.height ?? 0}'), @@ -1137,50 +1128,61 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { Widget _customResolutionMenuButton(bool showCustomBtn) { return Offstage( offstage: !showCustomBtn, - child: RdoMenuButton( - value: _kResolutionCustom, - groupValue: _groupValue, - onChanged: _onChanged, + child: MenuButton( + onPressed: () => _customResolutionDialog(), ffi: widget.ffi, - child: _customResolutionWidget(), + child: + Text('${translate('Custom')} ${display.width}x${display.height}'), ), ); } - Widget _customResolutionWidget() { - return Column( - children: [ - Text(translate('Custom')), - Container( - width: 5, - ), - // _resolutionInput(_customWidth), - Container( - width: 3, - ), - Text('x'), - Container( - width: 3, - ), - // _resolutionInput(_customHeight), - ], - ); - } + _customResolutionDialog() async { + OverlayDialogManager dialogManager = widget.ffi.dialogManager; + dialogManager.dismissAll(); + dialogManager.show((setState, close, context) { + cancel() { + close(); + } - TextField _resolutionInput(TextEditingController controller) { - return TextField( - decoration: InputDecoration( - border: InputBorder.none, - isDense: true, - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(4), - FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), - ], - controller: controller, - ); + return CustomAlertDialog( + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.password_rounded, color: MyTheme.accent), + Text(translate('Custom width x height')).paddingOnly(left: 10), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Slider( + value: display.width.toDouble(), + min: 256, + max: 8192, + divisions: 1, + onChanged: (double value) {}, + ), + Slider( + value: display.height.toDouble(), + min: 256, + max: 8192, + divisions: 1, + onChanged: (double value) {}, + ), + ], + ), + actions: [ + dialogButton( + 'Cancel', + icon: Icon(Icons.close_rounded), + onPressed: cancel, + isOutline: true, + ), + ], + onCancel: cancel, + ); + }); } Resolution? _getBestFitResolution() { From 99ad6254c12d06a10410dc684baec29424e3a96e Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 18 May 2023 23:57:48 +0800 Subject: [PATCH 09/28] add translations Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 27 ++++++++++--------- res/lang.py | 2 +- src/lang/ca.rs | 3 +++ src/lang/cn.rs | 3 +++ src/lang/cs.rs | 3 +++ src/lang/da.rs | 3 +++ src/lang/de.rs | 3 +++ src/lang/el.rs | 3 +++ src/lang/en.rs | 3 +++ src/lang/eo.rs | 3 +++ src/lang/es.rs | 3 +++ src/lang/fa.rs | 3 +++ src/lang/fr.rs | 3 +++ src/lang/hu.rs | 3 +++ src/lang/id.rs | 3 +++ src/lang/it.rs | 3 +++ src/lang/ja.rs | 3 +++ src/lang/ko.rs | 3 +++ src/lang/kz.rs | 3 +++ src/lang/lt.rs | 3 +++ src/lang/nl.rs | 3 +++ src/lang/pl.rs | 3 +++ src/lang/pt_PT.rs | 3 +++ src/lang/ptbr.rs | 3 +++ src/lang/ro.rs | 3 +++ src/lang/ru.rs | 3 +++ src/lang/sk.rs | 3 +++ src/lang/sl.rs | 3 +++ src/lang/sq.rs | 3 +++ src/lang/sr.rs | 3 +++ src/lang/sv.rs | 3 +++ src/lang/template.rs | 3 +++ src/lang/th.rs | 3 +++ src/lang/tr.rs | 3 +++ src/lang/tw.rs | 3 +++ src/lang/ua.rs | 3 +++ src/lang/vn.rs | 3 +++ 37 files changed, 120 insertions(+), 14 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 17b1ce540..19dd79a2f 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1023,15 +1023,16 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { final showFitLocalBtn = !_isRemoteResolutionFitLocal(); return _SubmenuButton( - ffi: widget.ffi, - menuChildren: [ - _OriginalResolutionMenuButton(showOriginalBtn), - _FitLocalResolutionMenuButton(showFitLocalBtn), - // _customResolutionMenuButton(isVirtualDisplay), - _menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay), - ] + - _supportedResolutionMenuButtons(), - child: Text(translate("Resolution"))); + ffi: widget.ffi, + menuChildren: [ + _OriginalResolutionMenuButton(showOriginalBtn), + _FitLocalResolutionMenuButton(showFitLocalBtn), + // _customResolutionMenuButton(isVirtualDisplay), + _menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay), + ] + + _supportedResolutionMenuButtons(), + child: Text(translate("Resolution")), + ); } _menuDivider( @@ -1094,7 +1095,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { _changeResolution(display.originalWidth, display.originalHeight), ffi: widget.ffi, child: Text( - '${translate('Original')} ${display.originalWidth}x${display.originalHeight}'), + '${translate('resolution_original_tip')} ${display.originalWidth}x${display.originalHeight}'), ), ); } @@ -1111,7 +1112,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { }, ffi: widget.ffi, child: Text( - '${translate('Fit Local')} ${_localResolution?.width ?? 0}x${_localResolution?.height ?? 0}'), + '${translate('resolution_fit_local_tip')} ${_localResolution?.width ?? 0}x${_localResolution?.height ?? 0}'), ), ); } @@ -1131,8 +1132,8 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { child: MenuButton( onPressed: () => _customResolutionDialog(), ffi: widget.ffi, - child: - Text('${translate('Custom')} ${display.width}x${display.height}'), + child: Text( + '${translate('resolution_custom_tip')} ${display.width}x${display.height}'), ), ); } diff --git a/res/lang.py b/res/lang.py index 85fd08654..74492818a 100644 --- a/res/lang.py +++ b/res/lang.py @@ -36,7 +36,7 @@ def main(): def expand(): - for fn in glob.glob('./src/lang/*'): + for fn in glob.glob('./src/lang/*.rs'): lang = os.path.basename(fn)[:-3] if lang in ['en','template']: continue print(lang) diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 0d876d6f2..5ca5cb6f1 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 447460174..b325dee22 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "启用"), ("Disable", "禁用"), ("Options", "选项"), + ("resolution_original_tip", "原始分辨率"), + ("resolution_fit_local_tip", "适应本地分辨率"), + ("resolution_custom_tip", "自定义分辨率"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index e38e7620f..0596c3590 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index e863f534f..3db456bfe 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 50f8f3e7e..199ff886c 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Aktivieren"), ("Disable", "Deaktivieren"), ("Options", "Einstellungen"), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 40c3d30d6..fafcf1698 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 93a56544a..8e86125c6 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -66,5 +66,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("xorg_not_found_text_tip", "Please install Xorg"), ("no_desktop_title_tip", "No desktop is available"), ("no_desktop_text_tip", "Please install GNOME desktop"), + ("resolution_original_tip", "Original resolution"), + ("resolution_fit_local_tip", "Fit local resolution"), + ("resolution_custom_tip", "Custom resolution"), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 61e57d5f5..a18cc6ee0 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 6daf738ba..2b4b20115 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Habilitar"), ("Disable", "Inhabilitar"), ("Options", "Opciones"), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index eb35b0ff1..b44e796fd 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "فعال کردن"), ("Disable", "غیر فعال کردن"), ("Options", "گزینه ها"), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 645a088bc..a1482a04b 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 3fca393b1..5828efb08 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index e746d509d..b90c96ec6 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index a0f2e9bab..e3c59bdae 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Abilita"), ("Disable", "Disabilita"), ("Options", "Opzioni"), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index cf49377ec..4e8267aea 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index eab5f189f..32c34d086 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index c0d90b433..e345c993c 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 953d0ad43..fe1336848 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 92f2c9557..bdcc4314c 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Activeer"), ("Disable", "Deactiveer"), ("Options", "Opties"), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 11bf58f19..9dffed6e9 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Włącz"), ("Disable", "Wyłącz"), ("Options", "Opcje"), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 07bb8a065..e28f8b1cc 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 5627a5f2a..e3fa71ad9 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Habilitar"), ("Disable", "Desabilitar"), ("Options", "Opções"), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 04d9f9d22..81665e73a 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index c2517df99..7d277450e 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Включить"), ("Disable", "Отключить"), ("Options", "Настройки"), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 0e2559193..cea678364 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index c6188aaf8..35edd0568 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index c55a4741e..c4e248ea2 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 3f37e23c7..5fc30af0b 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 5a76a1677..78331d448 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index f61635f9a..a20769abb 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index ab4ec4993..3ef4142c4 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 3fd57d1cd..7572cbee1 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index b9cdb60dd..f624b2006 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 68580c73b..2f98d9fe2 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 9e64ca784..6781ca2d5 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -506,5 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", ""), ("Disable", ""), ("Options", ""), + ("resolution_original_tip", ""), + ("resolution_fit_local_tip", ""), + ("resolution_custom_tip", ""), ].iter().cloned().collect(); } From 700a59ea72ffb73f14933a4c80781f02b9212746 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 18 May 2023 09:38:35 -0700 Subject: [PATCH 10/28] remove unused custom resolution ui Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 19dd79a2f..bba146fa8 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1126,66 +1126,6 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { child: Text('${e.width}x${e.height}'))) .toList(); - Widget _customResolutionMenuButton(bool showCustomBtn) { - return Offstage( - offstage: !showCustomBtn, - child: MenuButton( - onPressed: () => _customResolutionDialog(), - ffi: widget.ffi, - child: Text( - '${translate('resolution_custom_tip')} ${display.width}x${display.height}'), - ), - ); - } - - _customResolutionDialog() async { - OverlayDialogManager dialogManager = widget.ffi.dialogManager; - dialogManager.dismissAll(); - dialogManager.show((setState, close, context) { - cancel() { - close(); - } - - return CustomAlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.password_rounded, color: MyTheme.accent), - Text(translate('Custom width x height')).paddingOnly(left: 10), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Slider( - value: display.width.toDouble(), - min: 256, - max: 8192, - divisions: 1, - onChanged: (double value) {}, - ), - Slider( - value: display.height.toDouble(), - min: 256, - max: 8192, - divisions: 1, - onChanged: (double value) {}, - ), - ], - ), - actions: [ - dialogButton( - 'Cancel', - icon: Icon(Icons.close_rounded), - onPressed: cancel, - isOutline: true, - ), - ], - onCancel: cancel, - ); - }); - } - Resolution? _getBestFitResolution() { if (_localResolution == null) { return null; From c11873ad92f93e08f75cc6d7a25df5cb688ba308 Mon Sep 17 00:00:00 2001 From: jimmy Date: Thu, 18 May 2023 19:44:24 +0200 Subject: [PATCH 11/28] update lang FR --- src/lang/fr.rs | 70 +++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 645a088bc..09a84035e 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -75,7 +75,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Do you want to enter again?", "Voulez-vous participer à nouveau ?"), ("Connection Error", "Erreur de connexion"), ("Error", "Erreur"), - ("Reset by the peer", "La connexion a été fermée par le pair"), + ("Reset by the peer", "La connexion a été fermée par la machine distante"), ("Connecting...", "Connexion..."), ("Connection in progress. Please wait.", "Connexion en cours. Veuillez patienter."), ("Please try 1 minute later", "Réessayez dans une minute"), @@ -210,7 +210,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Settings", "Paramètres"), ("Username", " Nom d'utilisateur"), ("Invalid port", "Port invalide"), - ("Closed manually by the peer", "Fermé manuellement par le pair"), + ("Closed manually by the peer", "Fermé manuellement par la machine distante"), ("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"), ("Run without install", "Exécuter sans installer"), ("Connect via relay", "Connexion via relais"), @@ -246,7 +246,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("install_daemon_tip", "Pour une exécution au démarrage du système, vous devez installer le service système."), ("Remote ID", "ID de l'appareil à distance"), ("Paste", "Coller"), - ("Paste here?", "Coller ici ?"), + ("Paste here?", "Coller ici?"), ("Are you sure to close the connection?", "Êtes-vous sûr de fermer la connexion?"), ("Download new version", "Télécharger la nouvelle version"), ("Touch mode", "Mode tactile"), @@ -300,11 +300,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Succeeded", "Succès"), ("Someone turns on privacy mode, exit", "Quelqu'un active le mode de confidentialité, quittez"), ("Unsupported", "Non pris en charge"), - ("Peer denied", "Pair refusé"), + ("Peer denied", "Machine distante refusée"), ("Please install plugins", "Veuillez installer les plugins"), - ("Peer exit", "Sortie des pairs"), + ("Peer exit", ""), ("Failed to turn off", "Échec de la désactivation"), - ("Turned off", "Éteindre"), + ("Turned off", "Désactivé"), ("In privacy mode", "en mode privé"), ("Out privacy mode", "hors mode de confidentialité"), ("Language", "Langue"), @@ -366,7 +366,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Deny remote access", "Interdir l'accès distant"), ("Use IP Whitelisting", "Utiliser une liste blanche d'IP"), ("Network", "Réseau"), - ("Enable RDP", "Activer RDP"), + ("Enable RDP", "Activer connection RDP"), ("Pin menubar", "Épingler la barre de menus"), ("Unpin menubar", "Détacher la barre de menu"), ("Recording", "Enregistrement"), @@ -377,8 +377,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop session recording", "Stopper l'enregistrement"), ("Enable Recording Session", "Activer l'enregistrement de session"), ("Allow recording session", "Autoriser l'enregistrement de session"), - ("Enable LAN Discovery", "Activer la découverte réseau local"), - ("Deny LAN Discovery", "Interdir la découverte réseau local"), + ("Enable LAN Discovery", "Activer la découverte sur réseau local"), + ("Deny LAN Discovery", "Interdir la découverte sur réseau local"), ("Write a message", "Ecrire un message"), ("Prompt", ""), ("Please wait for confirmation of UAC...", "Veuillez attendre la confirmation de l'UAC..."), @@ -392,7 +392,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland nécessite Ubuntu 21.04 ou une version supérieure."), ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland nécessite une version supérieure de la distribution Linux. Veuillez essayer le bureau X11 ou changer votre système d'exploitation."), ("JumpLink", "Afficher"), - ("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (opérer du côté pair)."), + ("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (côté machine distante)."), ("Show RustDesk", "Afficher RustDesk"), ("This PC", "Ce PC"), ("or", "ou"), @@ -481,30 +481,30 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils."), ("View Mode", "Mode vue"), ("login_linux_tip", "Se connecter au compte Linux distant"), - ("verify_rustdesk_password_tip", ""), - ("remember_account_tip", ""), - ("os_account_desk_tip", ""), - ("OS Account", ""), - ("another_user_login_title_tip", ""), - ("another_user_login_text_tip", ""), - ("xorg_not_found_title_tip", ""), - ("xorg_not_found_text_tip", ""), - ("no_desktop_title_tip", ""), - ("no_desktop_text_tip", ""), - ("No need to elevate", ""), - ("System Sound", ""), - ("Default", ""), - ("New RDP", ""), - ("Fingerprint", ""), - ("Copy Fingerprint", ""), - ("no fingerprints", ""), - ("Select a peer", ""), - ("Select peers", ""), - ("Plugins", ""), - ("Uninstall", ""), - ("Update", ""), - ("Enable", ""), - ("Disable", ""), - ("Options", ""), + ("verify_rustdesk_password_tip", "Vérifier le mot de passe RustDesk"), + ("remember_account_tip", "Se souvenir de ce compte"), + ("os_account_desk_tip", "Ce compte est utilisé pour se connecter au système d'exploitation distant et activer la session de bureau en mode sans affichage"), + ("OS Account", "Compte système d'exploitation"), + ("another_user_login_title_tip", "Un autre utilisateur est déjà connecté"), + ("another_user_login_text_tip", "Déconnexion"), + ("xorg_not_found_title_tip", "Xorg introuvable"), + ("xorg_not_found_text_tip", "Veuillez installer Xorg"), + ("no_desktop_title_tip", "Aucun gestionaire de bureau n'est disponible"), + ("no_desktop_text_tip", "Veuillez installer le gestionaire de bureau GNOME"), + ("No need to elevate", "Pas besoin de permissions administrateur"), + ("System Sound", "Son système"), + ("Default", "Défaut"), + ("New RDP", "Nouvel RDP"), + ("Fingerprint", "Empreinte digitale"), + ("Copy Fingerprint", "Copier empreinte digitale"), + ("no fingerprints", "Pas d'empreintes digitales"), + ("Select a peer", "Sélectionnez la machine distante"), + ("Select peers", "Sélectionnez des machines distantes"), + ("Plugins", "Plugins"), + ("Uninstall", "Désinstaller"), + ("Update", "Mise à jour"), + ("Enable", "Activé"), + ("Disable", "Desactivé"), + ("Options", "Options"), ].iter().cloned().collect(); } From 8353afb02d79218f7445f1901126d00de81f6f7e Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 09:51:01 +0800 Subject: [PATCH 12/28] fix build Signed-off-by: fufesou --- src/server.rs | 6 +++--- src/server/connection.rs | 2 ++ src/server/video_service.rs | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/server.rs b/src/server.rs index 66095fd9e..2b833576a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,12 +8,10 @@ use std::{ use bytes::Bytes; pub use connection::*; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use hbb_common::config::Config2; use hbb_common::tcp::{self, new_listener}; use hbb_common::{ allow_err, - anyhow::{anyhow, Context}, + anyhow::Context, bail, config::{Config, CONNECT_TIMEOUT, RELAY_PORT}, log, @@ -25,6 +23,8 @@ use hbb_common::{ timeout, tokio, ResultType, Stream, }; #[cfg(not(any(target_os = "android", target_os = "ios")))] +use hbb_common::{anyhow::anyhow, config::Config2}; +#[cfg(not(any(target_os = "android", target_os = "ios")))] use service::ServiceTmpl; use service::{GenericService, Service, Subscriber}; diff --git a/src/server/connection.rs b/src/server/connection.rs index 200a6dfb5..69cb56102 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1936,6 +1936,7 @@ impl Connection { true } + #[cfg(not(any(target_os = "android", target_os = "ios")))] fn change_resolution(&mut self, r: &Resolution) { if self.keyboard { if let Ok(name) = video_service::get_current_display_name() { @@ -2148,6 +2149,7 @@ impl Connection { } } } + #[cfg(not(any(target_os = "android", target_os = "ios")))] if let Some(custom_resolution) = o.custom_resolution.as_ref() { if custom_resolution.width > 0 && custom_resolution.height > 0 { self.change_resolution(&custom_resolution); diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 747fff73e..18b400fca 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -113,6 +113,7 @@ fn update_get_original_resolution_(display_name: &str, w: usize, h: usize) -> Re } #[inline] +#[cfg(not(any(target_os = "android", target_os = "ios")))] 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 _) { From 8c0fa989b65ac21e78ff6b5406cabb1d497848e1 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 11:10:24 +0800 Subject: [PATCH 13/28] fix macro_role: serde_field_string, add config tests Signed-off-by: fufesou --- libs/hbb_common/src/config.rs | 46 +++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 16d606c78..fff6fc5a1 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -105,12 +105,9 @@ macro_rules! serde_field_string { where D: de::Deserializer<'de>, { - let s: &str = de::Deserialize::deserialize(deserializer).unwrap_or_default(); - Ok(if s.is_empty() { - Self::$default_func() - } else { - s.to_owned() - }) + let s: String = + de::Deserialize::deserialize(deserializer).unwrap_or(Self::$default_func()); + Ok(s) } }; } @@ -252,7 +249,11 @@ pub struct PeerConfig { #[serde(flatten)] pub view_only: ViewOnly, - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_resolution" + )] pub custom_resolution: Option, // The other scalar value must before this @@ -1495,6 +1496,7 @@ deserialize_default!(deserialize_option_string, Option); deserialize_default!(deserialize_hashmap_string_string, HashMap); deserialize_default!(deserialize_hashmap_string_bool, HashMap); deserialize_default!(deserialize_hashmap_string_configoidcprovider, HashMap); +deserialize_default!(deserialize_option_resolution, Option); #[cfg(test)] mod tests { @@ -1543,4 +1545,34 @@ mod tests { }) ); } + + #[test] + fn test_peer_config_deserialize() { + let default_peer_config = toml::from_str::("").unwrap(); + // test custom_resolution + { + let wrong_type_str = r#" + view_style = "adaptive" + scroll_style = "scrollbar" + custom_resolution = true + "#; + let mut compare_config = default_peer_config.clone(); + compare_config.view_style = "adaptive".to_string(); + compare_config.scroll_style = "scrollbar".to_string(); + let cfg = toml::from_str::(wrong_type_str); + assert_eq!(cfg, Ok(compare_config), "Failed to test wrong_type_str"); + + let wrong_field_str = r#" + [custom_resolution] + w = 1920 + h = 1080 + hello = "world" + [ui_flutter] + "#; + let mut compare_config = default_peer_config.clone(); + compare_config.custom_resolution = Some(Resolution { w: 1920, h: 1080 }); + let cfg = toml::from_str::(wrong_field_str); + assert_eq!(cfg, Ok(compare_config), "Failed to test wrong_field_str"); + } + } } From 6b4cc6443f6df44d002953c2a6140d593704a75d Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 11:43:35 +0800 Subject: [PATCH 14/28] fix resolutions query on linux Signed-off-by: fufesou --- libs/hbb_common/src/config.rs | 14 +++++++------- src/platform/linux.rs | 8 +++++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index fff6fc5a1..bf946f4f0 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1556,11 +1556,11 @@ mod tests { scroll_style = "scrollbar" custom_resolution = true "#; - let mut compare_config = default_peer_config.clone(); - compare_config.view_style = "adaptive".to_string(); - compare_config.scroll_style = "scrollbar".to_string(); + let mut cfg_to_compare = default_peer_config.clone(); + cfg_to_compare.view_style = "adaptive".to_string(); + cfg_to_compare.scroll_style = "scrollbar".to_string(); let cfg = toml::from_str::(wrong_type_str); - assert_eq!(cfg, Ok(compare_config), "Failed to test wrong_type_str"); + assert_eq!(cfg, Ok(cfg_to_compare), "Failed to test wrong_type_str"); let wrong_field_str = r#" [custom_resolution] @@ -1569,10 +1569,10 @@ mod tests { hello = "world" [ui_flutter] "#; - let mut compare_config = default_peer_config.clone(); - compare_config.custom_resolution = Some(Resolution { w: 1920, h: 1080 }); + let mut cfg_to_compare = default_peer_config.clone(); + cfg_to_compare.custom_resolution = Some(Resolution { w: 1920, h: 1080 }); let cfg = toml::from_str::(wrong_field_str); - assert_eq!(cfg, Ok(compare_config), "Failed to test wrong_field_str"); + assert_eq!(cfg, Ok(cfg_to_compare), "Failed to test wrong_field_str"); } } } diff --git a/src/platform/linux.rs b/src/platform/linux.rs index edb61c075..e7a4bc518 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -774,6 +774,12 @@ pub fn resolutions(name: &str) -> Vec { Virtual2 disconnected (normal left inverted right x axis y axis) Virtual3 disconnected (normal left inverted right x axis y axis) + Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384 + eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 344mm x 193mm + 1920x1080 60.01*+ 60.01 59.97 59.96 59.93 + 1680x1050 59.95 59.88 + 1600x1024 60.17 + XWAYLAND0 connected primary 1920x984+0+0 (normal left inverted right x axis y axis) 0mm x 0mm Virtual1 connected primary 1920x984+0+0 (normal left inverted right x axis y axis) 0mm x 0mm HDMI-0 connected (normal left inverted right x axis y axis) @@ -783,7 +789,7 @@ pub fn resolutions(name: &str) -> Vec { if let Some(caps) = re.captures(&xrandr_output) { if let Some(resolutions) = caps.name("resolutions") { let resolution_pat = - r"\s*(?P\d+)x(?P\d+)\s+(?P(\d+\.\d+[* ]*)+)\s*\n"; + r"\s*(?P\d+)x(?P\d+)\s+(?P(\d+\.\d+[*+ ]*)+)\s*\n"; let resolution_re = Regex::new(&format!(r"{}", resolution_pat)).unwrap(); for resolution_caps in resolution_re.captures_iter(resolutions.as_str()) { if let Some((width, height)) = From eceae8ac781d12503203d2d0031026787b3158dd Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 11:56:36 +0800 Subject: [PATCH 15/28] simple refact, more flexible Signed-off-by: fufesou --- src/platform/linux.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/linux.rs b/src/platform/linux.rs index e7a4bc518..ea0ec0f81 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -789,7 +789,7 @@ pub fn resolutions(name: &str) -> Vec { if let Some(caps) = re.captures(&xrandr_output) { if let Some(resolutions) = caps.name("resolutions") { let resolution_pat = - r"\s*(?P\d+)x(?P\d+)\s+(?P(\d+\.\d+[*+ ]*)+)\s*\n"; + r"\s*(?P\d+)x(?P\d+)\s+(?P(\d+\.\d+[\S ]*)+)\s*\n"; let resolution_re = Regex::new(&format!(r"{}", resolution_pat)).unwrap(); for resolution_caps in resolution_re.captures_iter(resolutions.as_str()) { if let Some((width, height)) = From 9cce0798b635e652ca14d20ec04cb27f5f761cdb Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 19 May 2023 12:03:16 +0800 Subject: [PATCH 16/28] set hw_pixfmt nv12 and wait more time for check porcess Signed-off-by: 21pages --- libs/scrap/src/common/hwcodec.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/libs/scrap/src/common/hwcodec.rs b/libs/scrap/src/common/hwcodec.rs index 8daa6551c..cda849f4e 100644 --- a/libs/scrap/src/common/hwcodec.rs +++ b/libs/scrap/src/common/hwcodec.rs @@ -23,7 +23,7 @@ use hwcodec::{ const CFG_KEY_ENCODER: &str = "bestHwEncoders"; const CFG_KEY_DECODER: &str = "bestHwDecoders"; -const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_YUV420P; +const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_NV12; pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30]; const DEFAULT_GOP: i32 = i32::MAX; const DEFAULT_HW_QUALITY: Quality = Quality_Default; @@ -327,6 +327,8 @@ pub fn check_config_process() { use hbb_common::sysinfo::{ProcessExt, System, SystemExt}; std::thread::spawn(move || { + // Remove to avoid checking process errors + // But when the program is just started, the configuration file has not been updated, and the new connection will read an empty configuration HwCodecConfig::remove(); if let Ok(exe) = std::env::current_exe() { if let Some(file_name) = exe.file_name().to_owned() { @@ -339,9 +341,16 @@ pub fn check_config_process() { } } if let Ok(mut child) = std::process::Command::new(exe).arg(arg).spawn() { - let second = 3; - std::thread::sleep(std::time::Duration::from_secs(second)); - // kill: Different platforms have different results + // wait up to 10 seconds + for _ in 0..10 { + std::thread::sleep(std::time::Duration::from_secs(1)); + if let Ok(Some(status)) = child.try_wait() { + if status.success() { + HwCodecConfig::refresh(); + } + break; + } + } allow_err!(child.kill()); std::thread::sleep(std::time::Duration::from_millis(30)); match child.try_wait() { From 67644d58a30dfe1b2fc7fc1ef76b94b46636e559 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 12:05:42 +0800 Subject: [PATCH 17/28] refact, better regex Signed-off-by: fufesou --- src/platform/linux.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/linux.rs b/src/platform/linux.rs index ea0ec0f81..945712703 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -789,7 +789,7 @@ pub fn resolutions(name: &str) -> Vec { if let Some(caps) = re.captures(&xrandr_output) { if let Some(resolutions) = caps.name("resolutions") { let resolution_pat = - r"\s*(?P\d+)x(?P\d+)\s+(?P(\d+\.\d+[\S ]*)+)\s*\n"; + r"\s*(?P\d+)x(?P\d+)\s+(?P(\d+\.\d+\D*)+)\s*\n"; let resolution_re = Regex::new(&format!(r"{}", resolution_pat)).unwrap(); for resolution_caps in resolution_re.captures_iter(resolutions.as_str()) { if let Some((width, height)) = From e50d50f177fcaa6339e4cb3ff9d96ee3af02d94c Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 12:57:03 +0800 Subject: [PATCH 18/28] try fix build ios Signed-off-by: fufesou --- src/client/io_loop.rs | 1 + src/flutter_ffi.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 597c720ab..368ea18e5 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -18,6 +18,7 @@ use hbb_common::protobuf::Message as _; use hbb_common::rendezvous_proto::ConnType; #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::sleep; +#[cfg(not(target_os = "ios"))] use hbb_common::tokio::sync::mpsc::error::TryRecvError; #[cfg(windows)] use hbb_common::tokio::sync::Mutex as TokioMutex; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d072c58a3..7d8cb5081 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -881,6 +881,7 @@ pub fn main_handle_relay_id(id: String) -> String { } pub fn main_get_current_display() -> SyncReturn { + #[cfg(not(target_os = "ios"))] let display_info = match crate::video_service::get_current_display() { Ok((_, _, display)) => serde_json::to_string(&HashMap::from([ ("w", display.width()), @@ -889,6 +890,8 @@ pub fn main_get_current_display() -> SyncReturn { .unwrap_or_default(), Err(..) => "".to_string(), }; + #[cfg(target_os = "ios")] + let display_info = "".to_owned(); SyncReturn(display_info) } From cbd53e38288fad7619a4dd191245a0a602c93361 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 19 May 2023 16:19:50 +0800 Subject: [PATCH 19/28] remove . for show_monitors_tip --- src/lang/en.rs | 2 +- src/lang/fr.rs | 2 +- src/lang/ptbr.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/en.rs b/src/lang/en.rs index 8e86125c6..2e644414f 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."), ("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."), ("identical_file_tip", "This file is identical with the peer's one."), - ("show_monitors_tip", "Show monitors in toolbar."), + ("show_monitors_tip", "Show monitors in toolbar"), ("enter_rustdesk_passwd_tip", "Enter RustDesk password"), ("remember_rustdesk_passwd_tip", "Remember RustDesk password"), ("login_linux_tip", "You need to login to remote Linux account to enable a X desktop session"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index dfcef4fc9..2e97fe715 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -478,7 +478,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty Password", "Mot de passe non spécifié"), ("Me", "Moi"), ("identical_file_tip", "Ce fichier est identique à celui du pair."), - ("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils."), + ("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils"), ("View Mode", "Mode vue"), ("login_linux_tip", "Se connecter au compte Linux distant"), ("verify_rustdesk_password_tip", "Vérifier le mot de passe RustDesk"), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index e3fa71ad9..152b0d1be 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -478,7 +478,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty Password", "Senha Vazia"), ("Me", "Eu"), ("identical_file_tip", "Este arquivo é idêntico ao do parceiro."), - ("show_monitors_tip", "Mostrar monitores na barra de ferramentas."), + ("show_monitors_tip", "Mostrar monitores na barra de ferramentas"), ("View Mode", "Modo de Visualização"), ("login_linux_tip", "Você precisa fazer login na conta Linux remota para habilitar uma sessão de desktop X"), ("verify_rustdesk_password_tip", "Verifique a senha do RustDesk"), From 366af2016c70c3b14c8a2b4e8979c856036eae76 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Fri, 19 May 2023 10:53:12 +0200 Subject: [PATCH 20/28] Update de.rs --- src/lang/de.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 199ff886c..17e0e7253 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -506,8 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Aktivieren"), ("Disable", "Deaktivieren"), ("Options", "Einstellungen"), - ("resolution_original_tip", ""), - ("resolution_fit_local_tip", ""), - ("resolution_custom_tip", ""), + ("resolution_original_tip", "Originalauflösung"), + ("resolution_fit_local_tip", "Lokale Auflösung anpassen"), + ("resolution_custom_tip", "Benutzerdefinierte Auflösung"), ].iter().cloned().collect(); } From 5f10d1aae6d4a2964111f30739ef7876311744b7 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 02:34:39 -0700 Subject: [PATCH 21/28] support custom resolution ui Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 105 ++++++++++++++---- 1 file changed, 86 insertions(+), 19 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index bba146fa8..9f05b3f73 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -995,10 +995,19 @@ class _ResolutionsMenu extends StatefulWidget { State<_ResolutionsMenu> createState() => _ResolutionsMenuState(); } +const double _kCustonResolutionEditingWidth = 42; +const _kCustomResolutionValue = 'custom'; +String? _lastResolutionGroupValue; + class _ResolutionsMenuState extends State<_ResolutionsMenu> { String _groupValue = ''; Resolution? _localResolution; + late final TextEditingController _customWidth = + TextEditingController(text: display.width.toString()); + late final TextEditingController _customHeight = + TextEditingController(text: display.height.toString()); + PeerInfo get pi => widget.ffi.ffiModel.pi; FfiModel get ffiModel => widget.ffi.ffiModel; Display get display => ffiModel.display; @@ -1012,22 +1021,20 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { @override Widget build(BuildContext context) { final isVirtualDisplay = display.isVirtualDisplayResolution; - // final visible = - // ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1); - final visible = ffiModel.keyboard && resolutions.length > 1; + final visible = + ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1); if (!visible) return Offstage(); - _groupValue = '${display.width}x${display.height}'; _getLocalResolution(); final showOriginalBtn = display.isOriginalResolutionSet && !display.isOriginalResolution; final showFitLocalBtn = !_isRemoteResolutionFitLocal(); - + _setGroupValue(); return _SubmenuButton( ffi: widget.ffi, menuChildren: [ _OriginalResolutionMenuButton(showOriginalBtn), _FitLocalResolutionMenuButton(showFitLocalBtn), - // _customResolutionMenuButton(isVirtualDisplay), + _customResolutionMenuButton(isVirtualDisplay), _menuDivider(showOriginalBtn, showFitLocalBtn, isVirtualDisplay), ] + _supportedResolutionMenuButtons(), @@ -1035,6 +1042,14 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { ); } + _setGroupValue() { + if (_lastResolutionGroupValue == _kCustomResolutionValue) { + _groupValue = _kCustomResolutionValue; + } else { + _groupValue = '${display.width}x${display.height}'; + } + } + _menuDivider( bool showOriginalBtn, bool showFitLocalBtn, bool isVirtualDisplay) { return Offstage( @@ -1060,12 +1075,24 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } _onChanged(String? value) async { + _lastResolutionGroupValue = value; if (value == null) return; - final list = value.split('x'); - if (list.length == 2) { - final w = int.tryParse(list[0]); - final h = int.tryParse(list[1]); - if (w != null && h != null) { + + int? w; + int? h; + if (value == _kCustomResolutionValue) { + w = int.tryParse(_customWidth.text); + h = int.tryParse(_customHeight.text); + } else { + final list = value.split('x'); + if (list.length == 2) { + w = int.tryParse(list[0]); + h = int.tryParse(list[1]); + } + } + + if (w != null && h != null) { + if (w != display.width && h != display.height) { await _changeResolution(w, h); } } @@ -1117,6 +1144,46 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { ); } + Widget _customResolutionMenuButton(isVirtualDisplay) { + return RdoMenuButton( + value: _kCustomResolutionValue, + groupValue: _groupValue, + onChanged: _onChanged, + ffi: widget.ffi, + child: Row( + children: [ + Text('${translate('resolution_custom_tip')} '), + SizedBox( + width: _kCustonResolutionEditingWidth, + child: _resolutionInput(_customWidth), + ), + Text(' x '), + SizedBox( + width: _kCustonResolutionEditingWidth, + child: _resolutionInput(_customHeight), + ), + ], + ), + ); + } + + TextField _resolutionInput(TextEditingController controller) { + return TextField( + decoration: InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.fromLTRB(3, 3, 3, 3), + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(4), + FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), + ], + controller: controller, + ); + } + List _supportedResolutionMenuButtons() => resolutions .map((e) => RdoMenuButton( value: '${e.width}x${e.height}', @@ -1655,14 +1722,14 @@ class RdoMenuButton extends StatelessWidget { final ValueChanged? onChanged; final Widget? child; final FFI ffi; - const RdoMenuButton( - {Key? key, - required this.value, - required this.groupValue, - required this.onChanged, - required this.child, - required this.ffi}) - : super(key: key); + const RdoMenuButton({ + Key? key, + required this.value, + required this.groupValue, + required this.child, + required this.ffi, + this.onChanged, + }) : super(key: key); @override Widget build(BuildContext context) { From df2de0fd619b8a4f7ce20703c46e0967fb67169e Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 20:48:47 +0800 Subject: [PATCH 22/28] windows, custom resolution Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 52 +++--- flutter/lib/models/model.dart | 3 + flutter/lib/models/state_model.dart | 24 ++- src/platform/windows.rs | 35 ---- src/server/connection.rs | 10 ++ src/server/video_service.rs | 24 +-- src/virtual_display_manager.rs | 164 ++++++++++++++++-- 7 files changed, 218 insertions(+), 94 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 9f05b3f73..1f4f74a33 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -997,7 +997,6 @@ class _ResolutionsMenu extends StatefulWidget { const double _kCustonResolutionEditingWidth = 42; const _kCustomResolutionValue = 'custom'; -String? _lastResolutionGroupValue; class _ResolutionsMenuState extends State<_ResolutionsMenu> { String _groupValue = ''; @@ -1043,7 +1042,9 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } _setGroupValue() { - if (_lastResolutionGroupValue == _kCustomResolutionValue) { + final lastGroupValue = + stateGlobal.getLastResolutionGroupValue(widget.id, pi.currentDisplay); + if (lastGroupValue == _kCustomResolutionValue) { _groupValue = _kCustomResolutionValue; } else { _groupValue = '${display.width}x${display.height}'; @@ -1053,8 +1054,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { _menuDivider( bool showOriginalBtn, bool showFitLocalBtn, bool isVirtualDisplay) { return Offstage( - // offstage: !(showOriginalBtn || showFitLocalBtn || isVirtualDisplay), - offstage: !(showOriginalBtn || showFitLocalBtn), + offstage: !(showOriginalBtn || showFitLocalBtn || isVirtualDisplay), child: Divider(), ); } @@ -1075,7 +1075,8 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } _onChanged(String? value) async { - _lastResolutionGroupValue = value; + stateGlobal.setLastResolutionGroupValue( + widget.id, pi.currentDisplay, value); if (value == null) return; int? w; @@ -1092,7 +1093,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } if (w != null && h != null) { - if (w != display.width && h != display.height) { + if (w != display.width || h != display.height) { await _changeResolution(w, h); } } @@ -1145,24 +1146,27 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } Widget _customResolutionMenuButton(isVirtualDisplay) { - return RdoMenuButton( - value: _kCustomResolutionValue, - groupValue: _groupValue, - onChanged: _onChanged, - ffi: widget.ffi, - child: Row( - children: [ - Text('${translate('resolution_custom_tip')} '), - SizedBox( - width: _kCustonResolutionEditingWidth, - child: _resolutionInput(_customWidth), - ), - Text(' x '), - SizedBox( - width: _kCustonResolutionEditingWidth, - child: _resolutionInput(_customHeight), - ), - ], + return Offstage( + offstage: !isVirtualDisplay, + child: RdoMenuButton( + value: _kCustomResolutionValue, + groupValue: _groupValue, + onChanged: _onChanged, + ffi: widget.ffi, + child: Row( + children: [ + Text('${translate('resolution_custom_tip')} '), + SizedBox( + width: _kCustonResolutionEditingWidth, + child: _resolutionInput(_customWidth), + ), + Text(' x '), + SizedBox( + width: _kCustonResolutionEditingWidth, + child: _resolutionInput(_customHeight), + ), + ], + ), ), ); } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 294b348b9..a99b0ddd7 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -503,6 +503,9 @@ class FfiModel with ChangeNotifier { } } } + + stateGlobal.resetLastResolutionGroupValues(peerId); + notifyListeners(); } diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index 187b1ffc5..72b78dbc2 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -12,12 +12,14 @@ class StateGlobal { bool _maximize = false; bool grabKeyboard = false; final RxBool _showTabBar = true.obs; - final RxBool _showResizeEdge = true.obs; final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize); final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth); final RxBool showRemoteMenuBar = false.obs; final RxInt displaysCount = 0.obs; + // Use for desktop -> remote toolbar -> resolution + final Map> _lastResolutionGroupValues = {}; + int get windowId => _windowId; bool get fullscreen => _fullscreen; bool get maximize => _maximize; @@ -26,6 +28,22 @@ class StateGlobal { RxDouble get resizeEdgeSize => _resizeEdgeSize; RxDouble get windowBorderWidth => _windowBorderWidth; + resetLastResolutionGroupValues(String peerId) { + _lastResolutionGroupValues[peerId] = {}; + } + + setLastResolutionGroupValue( + String peerId, int currentDisplay, String? value) { + if (!_lastResolutionGroupValues.containsKey(peerId)) { + _lastResolutionGroupValues[peerId] = {}; + } + _lastResolutionGroupValues[peerId]![currentDisplay] = value; + } + + String? getLastResolutionGroupValue(String peerId, int currentDisplay) { + return _lastResolutionGroupValues[peerId]?[currentDisplay]; + } + setWindowId(int id) => _windowId = id; setMaximize(bool v) { if (_maximize != v && !_fullscreen) { @@ -33,12 +51,12 @@ class StateGlobal { _resizeEdgeSize.value = _maximize ? kMaximizeEdgeSize : kWindowEdgeSize; } } + setFullscreen(bool v) { if (_fullscreen != v) { _fullscreen = v; _showTabBar.value = !_fullscreen; - _resizeEdgeSize.value = - fullscreen + _resizeEdgeSize.value = fullscreen ? kFullScreenEdgeSize : _maximize ? kMaximizeEdgeSize diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 46f92d511..e2bb2ca83 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -56,10 +56,6 @@ use windows_service::{ use winreg::enums::*; use winreg::RegKey; -// This string is defined here. -// https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 -const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; - pub fn get_cursor_pos() -> Option<(i32, i32)> { unsafe { #[allow(invalid_value)] @@ -1890,38 +1886,7 @@ pub fn current_resolution(name: &str) -> ResultType { } } -#[inline] -fn is_device_name(device_name: &str, name: &str) -> bool { - if name.len() == device_name.len() { - name == device_name - } else if name.len() > device_name.len() { - false - } else { - &device_name[..name.len()] == name && device_name.as_bytes()[name.len() as usize] == 0 - } -} -pub fn is_virtual_display(name: &str) -> ResultType { - let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; - dd.cb = std::mem::size_of::() as DWORD; - let mut i_dev_num = 0; - loop { - let result = unsafe { EnumDisplayDevicesW(null_mut(), i_dev_num, &mut dd, 0) }; - if result == 0 { - break; - } - if let Ok(device_name) = String::from_utf16(&dd.DeviceName) { - if is_device_name(&device_name, name) { - return match std::string::String::from_utf16(&dd.DeviceString) { - Ok(s) => Ok(&s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING), - Err(e) => bail!("convert the device string of '{}' to string: {}", name, e), - }; - } - } - i_dev_num += 1; - } - bail!("No such display '{}'", name) -} pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> { let device_name = str_to_device_name(name); diff --git a/src/server/connection.rs b/src/server/connection.rs index 69cb56102..9bffebf4f 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1940,6 +1940,16 @@ impl Connection { fn change_resolution(&mut self, r: &Resolution) { if self.keyboard { if let Ok(name) = video_service::get_current_display_name() { + #[cfg(target_os = "windows")] + if let Some(_ok) = + crate::virtual_display_manager::change_resolution_if_is_virtual_display( + &name, + r.width as _, + r.height as _, + ) + { + return; + } if let Err(e) = crate::platform::change_resolution(&name, r.width as _, r.height as _) { diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 18b400fca..6bb491758 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -897,7 +897,11 @@ pub fn handle_one_frame_encoded( #[inline] fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageField { - Some(if is_virtual_display(&display_name) { + #[cfg(target_os = "windows")] + let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name); + #[cfg(not(target_os = "windows"))] + let is_virtual_display = false; + Some(if is_virtual_display { Resolution { width: 0, height: 0, @@ -909,24 +913,6 @@ fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageFie .into() } -#[inline] -#[cfg(target_os = "windows")] -fn is_virtual_display(name: &str) -> bool { - match crate::platform::windows::is_virtual_display(&name) { - Ok(b) => b, - Err(e) => { - log::error!("Failed to check is virtual display for '{}': {}", &name, e); - false - } - } -} - -#[inline] -#[cfg(not(target_os = "windows"))] -fn is_virtual_display(_name: &str) -> bool { - false -} - pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { let mut displays = Vec::new(); let mut primary = 0; diff --git a/src/virtual_display_manager.rs b/src/virtual_display_manager.rs index 08cc0d485..03cf86e10 100644 --- a/src/virtual_display_manager.rs +++ b/src/virtual_display_manager.rs @@ -1,6 +1,6 @@ use hbb_common::{allow_err, bail, lazy_static, log, ResultType}; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, sync::{Arc, Mutex}, }; @@ -16,8 +16,8 @@ lazy_static::lazy_static! { #[derive(Default)] struct VirtualDisplayManager { - headless_index: Option, - peer_required_indices: HashSet, + headless_index_name: Option<(u32, String)>, + peer_index_name: HashMap, } impl VirtualDisplayManager { @@ -54,14 +54,16 @@ pub fn plug_in_headless() -> ResultType<()> { height: 1080, sync: 60, }]; + let device_names = windows::get_device_names(); VirtualDisplayManager::plug_in_monitor(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, &modes)?; - manager.headless_index = Some(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS); + let device_name = get_new_device_name(&device_names); + manager.headless_index_name = Some((VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, device_name)); Ok(()) } pub fn plug_out_headless() -> bool { let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - if let Some(index) = manager.headless_index.take() { + if let Some((index, _)) = manager.headless_index_name.take() { if let Err(e) = virtual_display::plug_out_monitor(index) { log::error!("Plug out monitor failed {}", e); } @@ -71,19 +73,33 @@ pub fn plug_out_headless() -> bool { } } -pub fn plug_in_peer_required( - modes: Vec>, -) -> ResultType> { +fn get_new_device_name(device_names: &HashSet) -> String { + let device_names_af = windows::get_device_names(); + let diff_names: Vec<_> = device_names_af.difference(&device_names).collect(); + if diff_names.len() != 1 { + log::error!( + "Failed to get diff device names after plugin virtual display : {:?}", + &diff_names + ); + "".to_string() + } else { + diff_names[0].clone() + } +} + +pub fn plug_in_peer_request(modes: Vec>) -> ResultType> { let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); VirtualDisplayManager::prepare_driver()?; let mut indices: Vec = Vec::new(); for m in modes.iter() { for idx in VIRTUAL_DISPLAY_START_FOR_PEER..VIRTUAL_DISPLAY_MAX_COUNT { - if !manager.peer_required_indices.contains(&idx) { + if !manager.peer_index_name.contains_key(&idx) { + let device_names = windows::get_device_names(); match VirtualDisplayManager::plug_in_monitor(idx, m) { Ok(_) => { - manager.peer_required_indices.insert(idx); + let device_name = get_new_device_name(&device_names); + manager.peer_index_name.insert(idx, device_name); indices.push(idx); } Err(e) => { @@ -97,13 +113,135 @@ pub fn plug_in_peer_required( Ok(indices) } -pub fn plug_out_peer_required(modes: &[u32]) -> ResultType<()> { +pub fn plug_out_peer_request(modes: &[u32]) -> ResultType<()> { let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); for idx in modes.iter() { - if manager.peer_required_indices.contains(idx) { + if manager.peer_index_name.contains_key(idx) { allow_err!(virtual_display::plug_out_monitor(*idx)); - manager.peer_required_indices.remove(idx); + manager.peer_index_name.remove(idx); } } Ok(()) } + +pub fn is_virtual_display(name: &str) -> bool { + let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + if let Some((_, device_name)) = &lock.headless_index_name { + if windows::is_device_name(device_name, name) { + return true; + } + } + for (k, v) in lock.peer_index_name.iter() { + if windows::is_device_name(v, name) { + return true; + } + } + false +} + +fn change_resolution(index: u32, w: u32, h: u32) -> bool { + let modes = [virtual_display::MonitorMode { + width: w, + height: h, + sync: 60, + }]; + match virtual_display::update_monitor_modes(index, &modes) { + Ok(_) => true, + Err(e) => { + log::error!("Update monitor {} modes {:?} failed: {}", index, &modes, e); + false + } + } +} + +pub fn change_resolution_if_is_virtual_display(name: &str, w: u32, h: u32) -> Option { + let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); + if let Some((index, device_name)) = &lock.headless_index_name { + if windows::is_device_name(device_name, name) { + return Some(change_resolution(*index, w, h)); + } + } + + for (k, v) in lock.peer_index_name.iter() { + if windows::is_device_name(v, name) { + return Some(change_resolution(*k, w, h)); + } + } + None +} + +mod windows { + use std::{collections::HashSet, ptr::null_mut}; + use winapi::{ + shared::minwindef::{DWORD, FALSE}, + um::{ + wingdi::{ + DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_MIRRORING_DRIVER, + }, + winuser::{EnumDisplayDevicesW, EnumDisplaySettingsExW, ENUM_CURRENT_SETTINGS}, + }, + }; + + // This string is defined here. + // https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 + const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; + + #[inline] + pub(super) fn is_device_name(device_name: &str, name: &str) -> bool { + if name.len() == device_name.len() { + name == device_name + } else if name.len() > device_name.len() { + false + } else { + &device_name[..name.len()] == name && device_name.as_bytes()[name.len() as usize] == 0 + } + } + + pub(super) fn get_device_names() -> HashSet { + let mut device_names = HashSet::new(); + let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; + dd.cb = std::mem::size_of::() as DWORD; + let mut i_dev_num = 0; + loop { + let result = unsafe { EnumDisplayDevicesW(null_mut(), i_dev_num, &mut dd, 0) }; + if result == 0 { + break; + } + i_dev_num += 1; + + if 0 == (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) + || (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) > 0 + { + continue; + } + + let mut dm: DEVMODEW = unsafe { std::mem::zeroed() }; + dm.dmSize = std::mem::size_of::() as _; + dm.dmDriverExtra = 0; + let ok = unsafe { + EnumDisplaySettingsExW( + dd.DeviceName.as_ptr(), + ENUM_CURRENT_SETTINGS, + &mut dm as _, + 0, + ) + }; + if ok == FALSE { + continue; + } + if dm.dmPelsHeight == 0 || dm.dmPelsWidth == 0 { + continue; + } + + if let (Ok(device_name), Ok(device_string)) = ( + String::from_utf16(&dd.DeviceName), + String::from_utf16(&dd.DeviceString), + ) { + if &device_string[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING { + device_names.insert(device_name); + } + } + } + device_names + } +} From 88cf61953b9ab4b73e9933a78e89044701688483 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 21:08:51 +0800 Subject: [PATCH 23/28] trivial changes Signed-off-by: fufesou --- src/virtual_display_manager.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/virtual_display_manager.rs b/src/virtual_display_manager.rs index 03cf86e10..be6640309 100644 --- a/src/virtual_display_manager.rs +++ b/src/virtual_display_manager.rs @@ -74,17 +74,23 @@ pub fn plug_out_headless() -> bool { } fn get_new_device_name(device_names: &HashSet) -> String { - let device_names_af = windows::get_device_names(); - let diff_names: Vec<_> = device_names_af.difference(&device_names).collect(); - if diff_names.len() != 1 { - log::error!( - "Failed to get diff device names after plugin virtual display : {:?}", - &diff_names - ); - "".to_string() - } else { - diff_names[0].clone() + for _ in 0..3 { + let device_names_af = windows::get_device_names(); + let diff_names: Vec<_> = device_names_af.difference(&device_names).collect(); + if diff_names.len() == 1 { + return diff_names[0].clone(); + } else if diff_names.len() > 1 { + log::error!( + "Failed to get diff device names after plugin virtual display, more than one diff names: {:?}", + &diff_names + ); + return "".to_string(); + } + // Sleep is needed here to wait for the virtual display to be ready. + std::thread::sleep(std::time::Duration::from_millis(50)); } + log::error!("Failed to get diff device names after plugin virtual display",); + "".to_string() } pub fn plug_in_peer_request(modes: Vec>) -> ResultType> { @@ -214,7 +220,7 @@ mod windows { { continue; } - + let mut dm: DEVMODEW = unsafe { std::mem::zeroed() }; dm.dmSize = std::mem::size_of::() as _; dm.dmDriverExtra = 0; From 0f07b71f184ad667da23937a7483931eca9e8e30 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 19 May 2023 23:38:18 +0800 Subject: [PATCH 24/28] fix build windows i686 Signed-off-by: fufesou --- libs/hbb_common/src/config.rs | 2 +- src/server/connection.rs | 2 +- src/server/video_service.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index bf946f4f0..693acb8f4 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1456,7 +1456,7 @@ impl ConfigOidc { fn _load_env(mut self) -> Self { use std::env; - for (k, mut v) in &mut self.providers { + for (k, v) in &mut self.providers { if let Ok(client_id) = env::var(format!("OIDC-{}-CLIENT-ID", k.to_uppercase())) { v.client_id = client_id; } diff --git a/src/server/connection.rs b/src/server/connection.rs index 9bffebf4f..8c211df39 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1940,7 +1940,7 @@ impl Connection { fn change_resolution(&mut self, r: &Resolution) { if self.keyboard { if let Ok(name) = video_service::get_current_display_name() { - #[cfg(target_os = "windows")] + #[cfg(all(windows, feature = "virtual_display_driver"))] if let Some(_ok) = crate::virtual_display_manager::change_resolution_if_is_virtual_display( &name, diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 6bb491758..b76ee3fc6 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -897,9 +897,9 @@ pub fn handle_one_frame_encoded( #[inline] fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageField { - #[cfg(target_os = "windows")] + #[cfg(all(windows, feature = "virtual_display_driver"))] let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name); - #[cfg(not(target_os = "windows"))] + #[cfg(not(all(windows, feature = "virtual_display_driver")))] let is_virtual_display = false; Some(if is_virtual_display { Resolution { From 100ea34baaca8950c529302e777eade805a6e40f Mon Sep 17 00:00:00 2001 From: "Lauren N. Liberda" Date: Sat, 20 May 2023 07:19:28 +0200 Subject: [PATCH 25/28] allow building scrap with pkg-config libraries Signed-off-by: Lauren N. Liberda --- libs/scrap/Cargo.toml | 2 ++ libs/scrap/build.rs | 30 +++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/libs/scrap/Cargo.toml b/libs/scrap/Cargo.toml index 5a32a38b7..5d5968b1f 100644 --- a/libs/scrap/Cargo.toml +++ b/libs/scrap/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" [features] wayland = ["gstreamer", "gstreamer-app", "gstreamer-video", "dbus", "tracing"] mediacodec = ["ndk"] +linux-pkg-config = ["dep:pkg-config"] [dependencies] block = "0.1" @@ -43,6 +44,7 @@ quest = "0.3" [build-dependencies] target_build_utils = "0.3" bindgen = "0.65" +pkg-config = { version = "0.3.27", optional = true } [target.'cfg(target_os = "linux")'.dependencies] dbus = { version = "0.9", optional = true } diff --git a/libs/scrap/build.rs b/libs/scrap/build.rs index b0d800545..3e08469b0 100644 --- a/libs/scrap/build.rs +++ b/libs/scrap/build.rs @@ -1,8 +1,28 @@ use std::{ env, fs, path::{Path, PathBuf}, + println, }; +#[cfg(all(target_os = "linux", feature = "linux-pkg-config"))] +fn link_pkg_config(name: &str) -> Vec { + // sometimes an override is needed + let pc_name = match name { + "libvpx" => "vpx", + _ => name, + }; + let lib = pkg_config::probe_library(pc_name) + .expect(format!( + "unable to find '{pc_name}' development headers with pkg-config (feature linux-pkg-config is enabled). + try installing '{pc_name}-dev' from your system package manager.").as_str()); + + lib.include_paths +} +#[cfg(not(all(target_os = "linux", feature = "linux-pkg-config")))] +fn link_pkg_config(_name: &str) -> Vec { + unimplemented!() +} + /// Link vcppkg package. fn link_vcpkg(mut path: PathBuf, name: &str) -> PathBuf { let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); @@ -102,8 +122,16 @@ fn link_homebrew_m1(name: &str) -> PathBuf { } /// Find package. By default, it will try to find vcpkg first, then homebrew(currently only for Mac M1). +/// If building for linux and feature "linux-pkg-config" is enabled, will try to use pkg-config +/// unless check fails (e.g. NO_PKG_CONFIG_libyuv=1) fn find_package(name: &str) -> Vec { - if let Ok(vcpkg_root) = std::env::var("VCPKG_ROOT") { + let no_pkg_config_var_name = format!("NO_PKG_CONFIG_{name}"); + println!("cargo:rerun-if-env-changed={no_pkg_config_var_name}"); + if cfg!(all(target_os = "linux", feature = "linux-pkg-config")) + && std::env::var(no_pkg_config_var_name).as_deref() != Ok("1") { + + link_pkg_config(name) + } else if let Ok(vcpkg_root) = std::env::var("VCPKG_ROOT") { vec![link_vcpkg(vcpkg_root.into(), name)] } else { // Try using homebrew From 548899174af148986143c32d26e4677b3d229b9e Mon Sep 17 00:00:00 2001 From: "Lauren N. Liberda" Date: Sat, 20 May 2023 07:20:24 +0200 Subject: [PATCH 26/28] expose linux-pkg-config features in app Signed-off-by: Lauren N. Liberda --- Cargo.lock | 4 +++- Cargo.toml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d795b8a02..70344666e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3558,9 +3558,10 @@ dependencies = [ [[package]] name = "magnum-opus" version = "0.4.0" -source = "git+https://github.com/rustdesk/magnum-opus#79be072c939168e907fe851690759dcfd6a326af" +source = "git+https://github.com/rustdesk/magnum-opus#5cd2bf989c148662fa3a2d9d539a71d71fd1d256" dependencies = [ "bindgen 0.59.2", + "pkg-config", "target_build_utils", ] @@ -5431,6 +5432,7 @@ dependencies = [ "log", "ndk 0.7.0", "num_cpus", + "pkg-config", "quest", "repng", "serde 1.0.163", diff --git a/Cargo.toml b/Cargo.toml index 5c9535a5a..eed897e01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ mediacodec = ["scrap/mediacodec"] linux_headless = ["pam" ] virtual_display_driver = ["virtual_display"] plugin_framework = [] +linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 8c3f34ceb21af1b6d44ba520f968152538cb3a21 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Sat, 20 May 2023 10:21:13 +0200 Subject: [PATCH 27/28] Update italian langauge --- src/lang/it.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index e3c59bdae..4ec9f2ba6 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -506,8 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Abilita"), ("Disable", "Disabilita"), ("Options", "Opzioni"), - ("resolution_original_tip", ""), - ("resolution_fit_local_tip", ""), - ("resolution_custom_tip", ""), + ("resolution_original_tip", "Risoluzione originale"), + ("resolution_fit_local_tip", "Adatta risoluzione locale"), + ("resolution_custom_tip", "Risoluzione personalizzata"), ].iter().cloned().collect(); } From 0769e519fc628cf1fe4efaf250227453f2f73830 Mon Sep 17 00:00:00 2001 From: "Miguel F. G" <116861809+flusheDData@users.noreply.github.com> Date: Sat, 20 May 2023 11:35:29 +0200 Subject: [PATCH 28/28] Update es.rs New tips added --- src/lang/es.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/es.rs b/src/lang/es.rs index 2b4b20115..79eaba72d 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -506,8 +506,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Habilitar"), ("Disable", "Inhabilitar"), ("Options", "Opciones"), - ("resolution_original_tip", ""), - ("resolution_fit_local_tip", ""), - ("resolution_custom_tip", ""), + ("resolution_original_tip", "Resolución original"), + ("resolution_fit_local_tip", "Ajustar resolución local"), + ("resolution_custom_tip", "Resolución personalizada"), ].iter().cloned().collect(); }