diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index bc6a4b22b..f2b3db7ad 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -6,6 +6,7 @@ import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/consts.dart'; +import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; @@ -802,3 +803,106 @@ List toolbarKeyboardToggles(FFI ffi) { } return v; } + +bool showVirtualDisplayMenu(FFI ffi) { + if (ffi.ffiModel.pi.platform != kPeerPlatformWindows) { + return false; + } + if (!ffi.ffiModel.pi.isInstalled) { + return false; + } + if (ffi.ffiModel.pi.isRustDeskIdd || ffi.ffiModel.pi.isAmyuniIdd) { + return true; + } + return false; +} + +List getVirtualDisplayMenuChildren( + FFI ffi, String id, VoidCallback? clickCallBack) { + if (!showVirtualDisplayMenu(ffi)) { + return []; + } + final pi = ffi.ffiModel.pi; + final privacyModeState = PrivacyModeState.find(id); + if (pi.isRustDeskIdd) { + final virtualDisplays = ffi.ffiModel.pi.RustDeskVirtualDisplays; + final children = []; + for (var i = 0; i < kMaxVirtualDisplayCount; i++) { + children.add(Obx(() => CkbMenuButton( + value: virtualDisplays.contains(i + 1), + onChanged: privacyModeState.isNotEmpty + ? null + : (bool? value) async { + if (value != null) { + bind.sessionToggleVirtualDisplay( + sessionId: ffi.sessionId, index: i + 1, on: value); + clickCallBack?.call(); + } + }, + child: Text('${translate('Virtual display')} ${i + 1}'), + ffi: ffi, + ))); + } + children.add(Divider()); + children.add(Obx(() => MenuButton( + onPressed: privacyModeState.isNotEmpty + ? null + : () { + bind.sessionToggleVirtualDisplay( + sessionId: ffi.sessionId, + index: kAllVirtualDisplay, + on: false); + clickCallBack?.call(); + }, + ffi: ffi, + child: Text(translate('Plug out all')), + ))); + return children; + } + if (pi.isAmyuniIdd) { + final count = ffi.ffiModel.pi.amyuniVirtualDisplayCount; + final children = [ + Obx(() => Row( + children: [ + TextButton( + onPressed: privacyModeState.isNotEmpty || count == 0 + ? null + : () { + bind.sessionToggleVirtualDisplay( + sessionId: ffi.sessionId, index: 0, on: false); + clickCallBack?.call(); + }, + child: Icon(Icons.remove), + ), + Text(count.toString()), + TextButton( + onPressed: privacyModeState.isNotEmpty || count == 4 + ? null + : () { + bind.sessionToggleVirtualDisplay( + sessionId: ffi.sessionId, index: 0, on: true); + clickCallBack?.call(); + }, + child: Icon(Icons.add), + ), + ], + )), + Divider(), + Obx(() => MenuButton( + onPressed: privacyModeState.isNotEmpty || count == 0 + ? null + : () { + bind.sessionToggleVirtualDisplay( + sessionId: ffi.sessionId, + index: kAllVirtualDisplay, + on: false); + clickCallBack?.call(); + }, + ffi: ffi, + child: Text(translate('Plug out all')), + )), + ]; + return children; + } + return []; +} diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index ff3017634..b54ff15dd 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -136,6 +136,8 @@ const String kOptionAllowRemoveWallpaper = "allow-remove-wallpaper"; const String kOptionStopService = "stop-service"; const String kOptionDirectxCapture = "enable-directx-capture"; const String kOptionAllowRemoteCmModification = "allow-remote-cm-modification"; +const String kOptionHideServerSetting = "hide-server-settings"; +const String kOptionHideProxySetting = "hide-proxy-settings"; const String kOptionToggleViewOnly = "view-only"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 505a98f17..a8ed3f082 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1274,9 +1274,9 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { bool enabled = !locked; final scrollController = ScrollController(); final hideServer = - bind.mainGetLocalOption(key: "hide-server-settings") == 'Y'; + bind.mainGetLocalOption(key: kOptionHideServerSetting) == 'Y'; final hideProxy = - bind.mainGetLocalOption(key: "hide-proxy-settings") == 'Y'; + bind.mainGetLocalOption(key: kOptionHideProxySetting) == 'Y'; return DesktopScrollWrapper( scrollController: scrollController, child: ListView( @@ -2328,35 +2328,40 @@ void changeSocks5Proxy() async { children: [ Row( children: [ - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Align( - alignment: Alignment.centerRight, - child: Row( - children: [ - Text( - translate('Server'), - ).marginOnly(right: 4), - Tooltip( - waitDuration: Duration(milliseconds: 0), - message: translate("default_proxy_tip"), - child: Icon( - Icons.help_outline_outlined, - size: 16, - color: Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.5), + if (!isMobile) + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 140), + child: Align( + alignment: Alignment.centerRight, + child: Row( + children: [ + Text( + translate('Server'), + ).marginOnly(right: 4), + Tooltip( + waitDuration: Duration(milliseconds: 0), + message: translate("default_proxy_tip"), + child: Icon( + Icons.help_outline_outlined, + size: 16, + color: Theme.of(context) + .textTheme + .titleLarge + ?.color + ?.withOpacity(0.5), + ), ), - ), - ], - )).marginOnly(right: 10), - ), + ], + )).marginOnly(right: 10), + ), Expanded( child: TextField( decoration: InputDecoration( errorText: proxyMsg.isNotEmpty ? proxyMsg : null, + labelText: isMobile ? translate('Server') : null, + helperText: + isMobile ? translate("default_proxy_tip") : null, + helperMaxLines: isMobile ? 3 : null, ), controller: proxyController, autofocus: true, @@ -2367,15 +2372,19 @@ void changeSocks5Proxy() async { ).marginOnly(bottom: 8), Row( children: [ - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Text( - '${translate("Username")}:', - textAlign: TextAlign.right, - ).marginOnly(right: 10)), + if (!isMobile) + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 140), + child: Text( + '${translate("Username")}:', + textAlign: TextAlign.right, + ).marginOnly(right: 10)), Expanded( child: TextField( controller: userController, + decoration: InputDecoration( + labelText: isMobile ? translate('Username') : null, + ), enabled: !isOptFixed, ), ), @@ -2383,16 +2392,18 @@ void changeSocks5Proxy() async { ).marginOnly(bottom: 8), Row( children: [ - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Text( - '${translate("Password")}:', - textAlign: TextAlign.right, - ).marginOnly(right: 10)), + if (!isMobile) + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 140), + child: Text( + '${translate("Password")}:', + textAlign: TextAlign.right, + ).marginOnly(right: 10)), Expanded( child: Obx(() => TextField( obscureText: obscure.value, decoration: InputDecoration( + labelText: isMobile ? translate('Password') : null, suffixIcon: IconButton( onPressed: () => obscure.value = !obscure.value, icon: Icon(obscure.value diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index d90e24f53..9db65e072 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1052,15 +1052,11 @@ class _DisplayMenuState extends State<_DisplayMenu> { ffi: widget.ffi, screenAdjustor: _screenAdjustor, ), - if (pi.isRustDeskIdd) - _RustDeskVirtualDisplayMenu( - id: widget.id, - ffi: widget.ffi, - ), - if (pi.isAmyuniIdd) - _AmyuniVirtualDisplayMenu( - id: widget.id, + if (showVirtualDisplayMenu(ffi)) + _SubmenuButton( ffi: widget.ffi, + menuChildren: getVirtualDisplayMenuChildren(ffi, id, null), + child: Text(translate("Virtual display")), ), cursorToggles(), Divider(), @@ -1559,155 +1555,6 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } } -class _RustDeskVirtualDisplayMenu extends StatefulWidget { - final String id; - final FFI ffi; - - _RustDeskVirtualDisplayMenu({ - Key? key, - required this.id, - required this.ffi, - }) : super(key: key); - - @override - State<_RustDeskVirtualDisplayMenu> createState() => - _RustDeskVirtualDisplayMenuState(); -} - -class _RustDeskVirtualDisplayMenuState - extends State<_RustDeskVirtualDisplayMenu> { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - if (widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) { - return Offstage(); - } - if (!widget.ffi.ffiModel.pi.isInstalled) { - return Offstage(); - } - - final virtualDisplays = widget.ffi.ffiModel.pi.RustDeskVirtualDisplays; - final privacyModeState = PrivacyModeState.find(widget.id); - - final children = []; - for (var i = 0; i < kMaxVirtualDisplayCount; i++) { - children.add(Obx(() => CkbMenuButton( - value: virtualDisplays.contains(i + 1), - onChanged: privacyModeState.isNotEmpty - ? null - : (bool? value) async { - if (value != null) { - bind.sessionToggleVirtualDisplay( - sessionId: widget.ffi.sessionId, - index: i + 1, - on: value); - } - }, - child: Text('${translate('Virtual display')} ${i + 1}'), - ffi: widget.ffi, - ))); - } - children.add(Divider()); - children.add(Obx(() => MenuButton( - onPressed: privacyModeState.isNotEmpty - ? null - : () { - bind.sessionToggleVirtualDisplay( - sessionId: widget.ffi.sessionId, - index: kAllVirtualDisplay, - on: false); - }, - ffi: widget.ffi, - child: Text(translate('Plug out all')), - ))); - return _SubmenuButton( - ffi: widget.ffi, - menuChildren: children, - child: Text(translate("Virtual display")), - ); - } -} - -class _AmyuniVirtualDisplayMenu extends StatefulWidget { - final String id; - final FFI ffi; - - _AmyuniVirtualDisplayMenu({ - Key? key, - required this.id, - required this.ffi, - }) : super(key: key); - - @override - State<_AmyuniVirtualDisplayMenu> createState() => - _AmiyuniVirtualDisplayMenuState(); -} - -class _AmiyuniVirtualDisplayMenuState extends State<_AmyuniVirtualDisplayMenu> { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - if (widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) { - return Offstage(); - } - if (!widget.ffi.ffiModel.pi.isInstalled) { - return Offstage(); - } - - final count = widget.ffi.ffiModel.pi.amyuniVirtualDisplayCount; - final privacyModeState = PrivacyModeState.find(widget.id); - - final children = [ - Obx(() => Row( - children: [ - TextButton( - onPressed: privacyModeState.isNotEmpty || count == 0 - ? null - : () => bind.sessionToggleVirtualDisplay( - sessionId: widget.ffi.sessionId, index: 0, on: false), - child: Icon(Icons.remove), - ), - Text(count.toString()), - TextButton( - onPressed: privacyModeState.isNotEmpty || count == 4 - ? null - : () => bind.sessionToggleVirtualDisplay( - sessionId: widget.ffi.sessionId, index: 0, on: true), - child: Icon(Icons.add), - ), - ], - )), - Divider(), - Obx(() => MenuButton( - onPressed: privacyModeState.isNotEmpty || count == 0 - ? null - : () { - bind.sessionToggleVirtualDisplay( - sessionId: widget.ffi.sessionId, - index: kAllVirtualDisplay, - on: false); - }, - ffi: widget.ffi, - child: Text(translate('Plug out all')), - )), - ]; - - return _SubmenuButton( - ffi: widget.ffi, - menuChildren: children, - child: Text(translate("Virtual display")), - ); - } -} - class _KeyboardMenu extends StatelessWidget { final String id; final FFI ffi; diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 77d8eaff3..30598085f 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -1158,14 +1158,110 @@ void showOptions( ); } + var popupDialogMenus = List.empty(growable: true); + final resolution = getResolutionMenu(gFFI, id); + if (resolution != null) { + popupDialogMenus.add(ListTile( + contentPadding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + title: resolution.child, + onTap: () { + close(); + resolution.onPressed(); + }, + )); + } + final virtualDisplayMenu = getVirtualDisplayMenu(gFFI, id); + if (virtualDisplayMenu != null) { + popupDialogMenus.add(ListTile( + contentPadding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + title: virtualDisplayMenu.child, + onTap: () { + close(); + virtualDisplayMenu.onPressed(); + }, + )); + } + if (popupDialogMenus.isNotEmpty) { + popupDialogMenus.add(const Divider(color: MyTheme.border)); + } + return CustomAlertDialog( content: Column( mainAxisSize: MainAxisSize.min, - children: displays + radios + toggles + [privacyModeWidget]), + children: displays + + radios + + popupDialogMenus + + toggles + + [privacyModeWidget]), ); }, clickMaskDismiss: true, backDismiss: true); } +TTextMenu? getVirtualDisplayMenu(FFI ffi, String id) { + if (!showVirtualDisplayMenu(ffi)) { + return null; + } + return TTextMenu( + child: Text(translate("Virtual display")), + onPressed: () { + ffi.dialogManager.show((setState, close, context) { + final children = getVirtualDisplayMenuChildren(ffi, id, close); + return CustomAlertDialog( + title: Text(translate('Virtual display')), + content: Column( + mainAxisSize: MainAxisSize.min, + children: children, + ), + ); + }, clickMaskDismiss: true, backDismiss: true); + }, + ); +} + +TTextMenu? getResolutionMenu(FFI ffi, String id) { + final ffiModel = ffi.ffiModel; + final pi = ffiModel.pi; + final resolutions = pi.resolutions; + final display = pi.tryGetDisplayIfNotAllDisplay(display: pi.currentDisplay); + + final visible = + ffiModel.keyboard && (resolutions.length > 1) && display != null; + if (!visible) return null; + + return TTextMenu( + child: Text(translate("Resolution")), + onPressed: () { + ffi.dialogManager.show((setState, close, context) { + final children = resolutions + .map((e) => getRadio( + Text('${e.width}x${e.height}'), + '${e.width}x${e.height}', + '${display.width}x${display.height}', + (value) { + close(); + bind.sessionChangeResolution( + sessionId: ffi.sessionId, + display: pi.currentDisplay, + width: e.width, + height: e.height, + ); + }, + )) + .toList(); + return CustomAlertDialog( + title: Text(translate('Resolution')), + content: Column( + mainAxisSize: MainAxisSize.min, + children: children, + ), + ); + }, clickMaskDismiss: true, backDismiss: true); + }, + ); +} + void sendPrompt(bool isMac, String key) { final old = isMac ? gFFI.inputModel.command : gFFI.inputModel.ctrl; if (isMac) { diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 909ca0285..2fc776348 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:settings_ui/settings_ui.dart'; @@ -83,6 +84,8 @@ class _SettingsState extends State with WidgetsBindingObserver { var _fingerprint = ""; var _buildDate = ""; var _autoDisconnectTimeout = ""; + var _hideServer = false; + var _hideProxy = false; @override void initState() { @@ -109,6 +112,8 @@ class _SettingsState extends State with WidgetsBindingObserver { bind.mainGetOptionSync(key: kOptionAllowAutoDisconnect)); _autoDisconnectTimeout = bind.mainGetOptionSync(key: kOptionAutoDisconnectTimeout); + _hideServer = bind.mainGetLocalOption(key: kOptionHideServerSetting) == 'Y'; + _hideProxy = bind.mainGetLocalOption(key: kOptionHideProxySetting) == 'Y'; () async { var update = false; @@ -553,13 +558,20 @@ class _SettingsState extends State with WidgetsBindingObserver { ], ), SettingsSection(title: Text(translate("Settings")), tiles: [ - if (!disabledSettings) + if (!disabledSettings && !_hideServer) SettingsTile( title: Text(translate('ID/Relay Server')), leading: Icon(Icons.cloud), onPressed: (context) { showServerSettings(gFFI.dialogManager); }), + if (!isIOS && !_hideProxy) + SettingsTile( + title: Text(translate('Socks5/Http(s) Proxy')), + leading: Icon(Icons.network_ping), + onPressed: (context) { + changeSocks5Proxy(); + }), SettingsTile( title: Text(translate('Language')), leading: Icon(Icons.translate), diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 5f85d9758..ead6b4168 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -415,33 +415,44 @@ pub fn install_path() -> String { #[inline] pub fn get_socks() -> Vec { - #[cfg(any(target_os = "android", target_os = "ios"))] - return Vec::new(); #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - let s = ipc::get_socks(); - match s { - None => Vec::new(), - Some(s) => { - let mut v = Vec::new(); - v.push(s.proxy); - v.push(s.username); - v.push(s.password); - v - } + let s = ipc::get_socks(); + #[cfg(target_os = "android")] + let s = Config::get_socks(); + #[cfg(target_os = "ios")] + let s: Option = None; + match s { + None => Vec::new(), + Some(s) => { + let mut v = Vec::new(); + v.push(s.proxy); + v.push(s.username); + v.push(s.password); + v } } } #[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn set_socks(proxy: String, username: String, password: String) { - ipc::set_socks(config::Socks5Server { + let socks = config::Socks5Server { proxy, username, password, - }) - .ok(); + }; + #[cfg(not(any(target_os = "android", target_os = "ios")))] + ipc::set_socks(socks).ok(); + #[cfg(target_os = "android")] + { + if socks.proxy.is_empty() { + Config::set_socks(None); + } else { + Config::set_socks(Some(socks)); + } + crate::common::test_nat_type(); + crate::RendezvousMediator::restart(); + log::info!("socks updated"); + } } #[inline] @@ -454,9 +465,6 @@ pub fn get_proxy_status() -> bool { return false; } -#[cfg(any(target_os = "android", target_os = "ios"))] -pub fn set_socks(_: String, _: String, _: String) {} - #[cfg(not(any(target_os = "android", target_os = "ios")))] #[inline] pub fn is_installed() -> bool {