From 6e44a91d0beb16bd1548cbb7de3813d29398d2f5 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 28 Mar 2024 11:38:11 +0800 Subject: [PATCH] Refact. Flutter web desktop (#7539) * Refact. Flutter web desktop Signed-off-by: fufesou * Flutter web, prevent default context menu Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/common.dart | 78 ++++++++++------ flutter/lib/common/widgets/address_book.dart | 4 +- flutter/lib/common/widgets/dialog.dart | 9 +- flutter/lib/common/widgets/my_group.dart | 7 +- flutter/lib/common/widgets/overlay.dart | 2 +- flutter/lib/common/widgets/peer_card.dart | 26 +++--- flutter/lib/common/widgets/peer_tab_page.dart | 11 ++- flutter/lib/common/widgets/peers_view.dart | 4 +- flutter/lib/common/widgets/remote_input.dart | 12 +-- .../lib/common/widgets/setting_widgets.dart | 6 +- flutter/lib/common/widgets/toolbar.dart | 12 +-- flutter/lib/consts.dart | 2 - .../desktop/pages/desktop_setting_page.dart | 5 +- .../lib/desktop/pages/desktop_tab_page.dart | 2 - flutter/lib/desktop/pages/remote_page.dart | 27 +++--- .../lib/desktop/widgets/remote_toolbar.dart | 33 +++++-- flutter/lib/main.dart | 5 +- .../lib/mobile/pages/file_manager_page.dart | 1 - flutter/lib/models/input_model.dart | 22 +++-- flutter/lib/models/model.dart | 28 +++--- flutter/lib/models/web_model.dart | 4 +- flutter/lib/native/common.dart | 3 + flutter/lib/utils/image.dart | 7 ++ flutter/lib/web/bridge.dart | 35 ++++---- flutter/lib/web/common.dart | 4 + flutter/web/js/src/connection.ts | 78 +++++++++++++++- flutter/web/js/src/globals.js | 88 ++++++++++++++++--- 27 files changed, 362 insertions(+), 153 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index b1c3ad8a0..fc5b537e1 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -29,6 +29,8 @@ import '../consts.dart'; import 'common/widgets/overlay.dart'; import 'mobile/pages/file_manager_page.dart'; import 'mobile/pages/remote_page.dart'; +import 'desktop/pages/remote_page.dart' as desktop_remote; +import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; import 'models/input_model.dart'; import 'models/model.dart'; import 'models/platform_model.dart'; @@ -48,7 +50,7 @@ final isMacOS = isMacOS_; final isLinux = isLinux_; final isDesktop = isDesktop_; final isWeb = isWeb_; -var isWebDesktop = false; +final isWebDesktop = isWebDesktop_; var isMobile = isAndroid || isIOS; var version = ''; int androidVersion = 0; @@ -60,6 +62,8 @@ DesktopType? desktopType; bool get isMainDesktopWindow => desktopType == DesktopType.main || desktopType == DesktopType.cm; +String get screenInfo => screenInfo_; + /// Check if the app is running with single view mode. bool isSingleViewApp() { return desktopType == DesktopType.cm; @@ -233,11 +237,13 @@ class MyTheme { ); static SwitchThemeData switchTheme() { - return SwitchThemeData(splashRadius: isDesktop ? 0 : kRadialReactionRadius); + return SwitchThemeData( + splashRadius: (isDesktop || isWebDesktop) ? 0 : kRadialReactionRadius); } static RadioThemeData radioTheme() { - return RadioThemeData(splashRadius: isDesktop ? 0 : kRadialReactionRadius); + return RadioThemeData( + splashRadius: (isDesktop || isWebDesktop) ? 0 : kRadialReactionRadius); } // Checkbox @@ -286,7 +292,7 @@ class MyTheme { static EdgeInsets dialogContentPadding({bool actions = true}) { final double p = dialogPadding; - return isDesktop + return (isDesktop || isWebDesktop) ? EdgeInsets.fromLTRB(p, p, p, actions ? (p - 4) : p) : EdgeInsets.fromLTRB(p, p, p, actions ? (p / 2) : p); } @@ -294,12 +300,12 @@ class MyTheme { static EdgeInsets dialogActionsPadding() { final double p = dialogPadding; - return isDesktop + return (isDesktop || isWebDesktop) ? EdgeInsets.fromLTRB(p, 0, p, (p - 4)) : EdgeInsets.fromLTRB(p, 0, (p - mobileTextButtonPaddingLR), (p / 2)); } - static EdgeInsets dialogButtonPadding = isDesktop + static EdgeInsets dialogButtonPadding = (isDesktop || isWebDesktop) ? EdgeInsets.only(left: dialogPadding) : EdgeInsets.only(left: dialogPadding / 3); @@ -371,10 +377,10 @@ class MyTheme { labelColor: Colors.black87, ), tooltipTheme: tooltipTheme(), - splashColor: isDesktop ? Colors.transparent : null, - highlightColor: isDesktop ? Colors.transparent : null, - splashFactory: isDesktop ? NoSplash.splashFactory : null, - textButtonTheme: isDesktop + splashColor: (isDesktop || isWebDesktop) ? Colors.transparent : null, + highlightColor: (isDesktop || isWebDesktop) ? Colors.transparent : null, + splashFactory: (isDesktop || isWebDesktop) ? NoSplash.splashFactory : null, + textButtonTheme: (isDesktop || isWebDesktop) ? TextButtonThemeData( style: TextButton.styleFrom( splashFactory: NoSplash.splashFactory, @@ -414,7 +420,9 @@ class MyTheme { color: Colors.white, shape: RoundedRectangleBorder( side: BorderSide( - color: isDesktop ? Color(0xFFECECEC) : Colors.transparent), + color: (isDesktop || isWebDesktop) + ? Color(0xFFECECEC) + : Colors.transparent), borderRadius: BorderRadius.all(Radius.circular(8.0)), )), ).copyWith( @@ -440,7 +448,7 @@ class MyTheme { ), ), scrollbarTheme: scrollbarThemeDark, - inputDecorationTheme: isDesktop + inputDecorationTheme: (isDesktop || isWebDesktop) ? InputDecorationTheme( fillColor: Color(0xFF24252B), filled: true, @@ -467,10 +475,10 @@ class MyTheme { labelColor: Colors.white70, ), tooltipTheme: tooltipTheme(), - splashColor: isDesktop ? Colors.transparent : null, - highlightColor: isDesktop ? Colors.transparent : null, - splashFactory: isDesktop ? NoSplash.splashFactory : null, - textButtonTheme: isDesktop + splashColor: (isDesktop || isWebDesktop) ? Colors.transparent : null, + highlightColor: (isDesktop || isWebDesktop) ? Colors.transparent : null, + splashFactory: (isDesktop || isWebDesktop) ? NoSplash.splashFactory : null, + textButtonTheme: (isDesktop || isWebDesktop) ? TextButtonThemeData( style: TextButton.styleFrom( splashFactory: NoSplash.splashFactory, @@ -818,7 +826,7 @@ class OverlayDialogManager { Offstage( offstage: !showCancel, child: Center( - child: isDesktop + child: (isDesktop || isWebDesktop) ? dialogButton('Cancel', onPressed: cancel) : TextButton( style: flatButtonStyle, @@ -1293,7 +1301,7 @@ class AndroidPermissionManager { } static Future check(String type) { - if (isDesktop) { + if (isDesktop || isWeb) { return Future.value(true); } return gFFI.invokeMethod("check_permission", type); @@ -1307,7 +1315,7 @@ class AndroidPermissionManager { /// We use XXPermissions to request permissions, /// for supported types, see https://github.com/getActivity/XXPermissions/blob/e46caea32a64ad7819df62d448fb1c825481cd28/library/src/main/java/com/hjq/permissions/Permission.java static Future request(String type) { - if (isDesktop) { + if (isDesktop || isWeb) { return Future.value(true); } @@ -2197,13 +2205,29 @@ connect(BuildContext context, String id, ), ); } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => RemotePage( - id: id, password: password, isSharedPassword: isSharedPassword), - ), - ); + if (isWebDesktop) { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => desktop_remote.RemotePage( + key: ValueKey(id), + id: id, + toolbarState: ToolbarState(), + password: password, + forceRelay: forceRelay, + isSharedPassword: isSharedPassword, + ), + ), + ); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => RemotePage( + id: id, password: password, isSharedPassword: isSharedPassword), + ), + ); + } } } @@ -2398,7 +2422,7 @@ Widget dialogButton(String text, Widget? icon, TextStyle? style, ButtonStyle? buttonStyle}) { - if (isDesktop) { + if (isDesktop || isWebDesktop) { if (isOutline) { return icon == null ? OutlinedButton( diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index eae305bff..8a8992a1e 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -63,7 +63,7 @@ class _AddressBookState extends State { retry: null, // remove retry close: () => gFFI.abModel.currentAbPushError.value = ''), Expanded( - child: isDesktop + child: (isDesktop || isWebDesktop) ? _buildAddressBookDesktop() : _buildAddressBookMobile()) ], @@ -311,7 +311,7 @@ class _AddressBookState extends State { return tagBuilder(e); }); final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); - return isDesktop + return (isDesktop || isWebDesktop) ? gridView : LimitedBox(maxHeight: maxHeight, child: gridView); }); diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index d2c808adc..be1df6539 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; @@ -81,7 +80,7 @@ void changeIdDialog() { final Iterable violations = rules.where((r) => !r.validate(newId)); if (violations.isNotEmpty) { setState(() { - msg = isDesktop + msg = (isDesktop || isWebDesktop) ? '${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}' : violations.map((r) => r.name).join(', '); }); @@ -106,7 +105,7 @@ void changeIdDialog() { } setState(() { isInProgress = false; - msg = isDesktop + msg = (isDesktop || isWebDesktop) ? '${translate('Prompt')}: ${translate(status)}' : translate(status); }); @@ -143,7 +142,7 @@ void changeIdDialog() { const SizedBox( height: 8.0, ), - isDesktop + (isDesktop || isWebDesktop) ? Obx(() => Wrap( runSpacing: 8, spacing: 4, @@ -1109,7 +1108,7 @@ void showRequestElevationDialog( errorText: errPwd.isEmpty ? null : errPwd.value, ), ], - ).marginOnly(left: isDesktop ? 35 : 0), + ).marginOnly(left: (isDesktop || isWebDesktop) ? 35 : 0), ).marginOnly(top: 10), ], ), diff --git a/flutter/lib/common/widgets/my_group.dart b/flutter/lib/common/widgets/my_group.dart index c2e64e931..fd8b9eeb8 100644 --- a/flutter/lib/common/widgets/my_group.dart +++ b/flutter/lib/common/widgets/my_group.dart @@ -47,7 +47,10 @@ class _MyGroupState extends State { err: gFFI.groupModel.groupLoadError, retry: null, close: () => gFFI.groupModel.groupLoadError.value = ''), - Expanded(child: isDesktop ? _buildDesktop() : _buildMobile()) + Expanded( + child: (isDesktop || isWebDesktop) + ? _buildDesktop() + : _buildMobile()) ], ); }); @@ -164,7 +167,7 @@ class _MyGroupState extends State { itemCount: items.length, itemBuilder: (context, index) => _buildUserItem(items[index])); var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); - return isDesktop + return (isDesktop || isWebDesktop) ? listView : LimitedBox(maxHeight: maxHeight, child: listView); }); diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index c90709826..1df7f0317 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -54,7 +54,7 @@ class DraggableChatWindow extends StatelessWidget { resizeToAvoidBottomInset: false, appBar: CustomAppBar( onPanUpdate: onPanUpdate, - appBar: isDesktop + appBar: (isDesktop || isWebDesktop) ? _buildDesktopAppBar(context) : _buildMobileAppBar(context), ), diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 5530d9b86..3efebf4c7 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -52,7 +51,7 @@ class _PeerCardState extends State<_PeerCard> @override Widget build(BuildContext context) { super.build(context); - if (isDesktop) { + if (isDesktop || isWebDesktop) { return _buildDesktop(); } else { return _buildMobile(); @@ -883,8 +882,7 @@ class RecentPeerCard extends BasePeerCard { menuItems.add(_createShortCutAction(peer.id)); } menuItems.add(MenuEntryDivider()); - if (!isWeb) { - // TODO: support web version + if (isDesktop || isWebDesktop) { menuItems.add(_renameAction(peer.id)); } if (await bind.mainPeerHasPassword(id: peer.id)) { @@ -940,8 +938,7 @@ class FavoritePeerCard extends BasePeerCard { menuItems.add(_createShortCutAction(peer.id)); } menuItems.add(MenuEntryDivider()); - if (!isWeb) { - // TODO: support web version + if (isDesktop || isWebDesktop) { menuItems.add(_renameAction(peer.id)); } if (await bind.mainPeerHasPassword(id: peer.id)) { @@ -1046,8 +1043,7 @@ class AddressBookPeerCard extends BasePeerCard { } if (gFFI.abModel.current.canWrite()) { menuItems.add(MenuEntryDivider()); - if (!isWeb) { - // TODO: support web version + if (isDesktop || isWebDesktop) { menuItems.add(_renameAction(peer.id)); } if (gFFI.abModel.current.isPersonal() && peer.hash.isNotEmpty) { @@ -1249,7 +1245,7 @@ void _rdpDialog(String id) async { ).marginOnly(bottom: isDesktop ? 8 : 0), Row( children: [ - isDesktop + (isDesktop || isWebDesktop) ? ConstrainedBox( constraints: const BoxConstraints(minWidth: 140), child: Text( @@ -1260,15 +1256,17 @@ void _rdpDialog(String id) async { Expanded( child: TextField( decoration: InputDecoration( - labelText: isDesktop ? null : translate('Username')), + labelText: (isDesktop || isWebDesktop) + ? null + : translate('Username')), controller: userController, ), ), ], - ).marginOnly(bottom: isDesktop ? 8 : 0), + ).marginOnly(bottom: (isDesktop || isWebDesktop) ? 8 : 0), Row( children: [ - isDesktop + (isDesktop || isWebDesktop) ? ConstrainedBox( constraints: const BoxConstraints(minWidth: 140), child: Text( @@ -1280,7 +1278,9 @@ void _rdpDialog(String id) async { child: Obx(() => TextField( obscureText: secure.value, decoration: InputDecoration( - labelText: isDesktop ? null : translate('Password'), + labelText: (isDesktop || isWebDesktop) + ? null + : translate('Password'), suffixIcon: IconButton( onPressed: () => secure.value = !secure.value, icon: Icon(secure.value diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 069178802..8840fa474 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -37,7 +37,7 @@ class _TabEntry { } EdgeInsets? _menuPadding() { - return isDesktop ? kDesktopMenuPadding : null; + return (isDesktop || isWebDesktop) ? kDesktopMenuPadding : null; } class _PeerTabPageState extends State @@ -113,7 +113,9 @@ class _PeerTabPageState extends State SizedBox( height: 32, child: Container( - padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2), + padding: (isDesktop || isWebDesktop) + ? null + : EdgeInsets.symmetric(horizontal: 2), child: selectionWrap(Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -127,7 +129,7 @@ class _PeerTabPageState extends State ], )), ), - ).paddingOnly(right: isDesktop ? 12 : 0), + ).paddingOnly(right: (isDesktop || isWebDesktop) ? 12 : 0), _createPeersView(), ], ); @@ -195,7 +197,8 @@ class _PeerTabPageState extends State } } return Expanded( - child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0)); + child: child.marginSymmetric( + vertical: (isDesktop || isWebDesktop) ? 12.0 : 6.0)); } Widget _createRefresh( diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index 2c553d464..dd2902e54 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -78,7 +78,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener { LoadEvent.lan: 'empty_lan_tip', LoadEvent.addressBook: 'empty_address_book_tip', }); - final space = isDesktop ? 12.0 : 8.0; + final space = (isDesktop || isWebDesktop) ? 12.0 : 8.0; final _curPeers = {}; var _lastChangeTime = DateTime.now(); var _lastQueryPeers = {}; @@ -200,7 +200,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener { Provider.of(context, listen: false).currentTab; final hideAbTagsPanel = bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty; - return isDesktop + return (isDesktop || isWebDesktop) ? Obx( () => SizedBox( width: peerCardUiType.value != PeerUiType.list diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart index e073bffc9..374ba1dce 100644 --- a/flutter/lib/common/widgets/remote_input.dart +++ b/flutter/lib/common/widgets/remote_input.dart @@ -77,7 +77,7 @@ class _RawTouchGestureDetectorRegionState FFI get ffi => widget.ffi; FfiModel get ffiModel => widget.ffiModel; InputModel get inputModel => widget.inputModel; - bool get handleTouch => isDesktop || ffiModel.touchMode; + bool get handleTouch => (isDesktop || isWebDesktop) || ffiModel.touchMode; SessionID get sessionId => ffi.sessionId; @override @@ -183,7 +183,7 @@ class _RawTouchGestureDetectorRegionState if (lastDeviceKind != PointerDeviceKind.touch) { return; } - if (isDesktop || !ffiModel.touchMode) { + if ((isDesktop || isWebDesktop) || !ffiModel.touchMode) { inputModel.tap(MouseButtons.right); } } @@ -262,7 +262,7 @@ class _RawTouchGestureDetectorRegionState if (lastDeviceKind != PointerDeviceKind.touch) { return; } - if (isDesktop) { + if ((isDesktop || isWebDesktop)) { final scale = ((d.scale - _scale) * 1000).toInt(); _scale = d.scale; @@ -286,7 +286,7 @@ class _RawTouchGestureDetectorRegionState if (lastDeviceKind != PointerDeviceKind.touch) { return; } - if (isDesktop) { + if ((isDesktop || isWebDesktop)) { bind.sessionSendPointer( sessionId: sessionId, msg: json.encode( @@ -409,7 +409,9 @@ class RawPointerMouseRegion extends StatelessWidget { onPointerPanZoomUpdate: inputModel.onPointerPanZoomUpdate, onPointerPanZoomEnd: inputModel.onPointerPanZoomEnd, child: MouseRegion( - cursor: cursor ?? MouseCursor.defer, + cursor: inputModel.isViewOnly + ? MouseCursor.defer + : (cursor ?? MouseCursor.defer), onEnter: onEnter, onExit: onExit, child: child, diff --git a/flutter/lib/common/widgets/setting_widgets.dart b/flutter/lib/common/widgets/setting_widgets.dart index 6c8702b21..c6c8f6443 100644 --- a/flutter/lib/common/widgets/setting_widgets.dart +++ b/flutter/lib/common/widgets/setting_widgets.dart @@ -209,10 +209,10 @@ List ServerConfigImportExportWidgets( List<(String, String)> otherDefaultSettings() { List<(String, String)> v = [ ('View Mode', 'view_only'), - if (isDesktop) ('show_monitors_tip', kKeyShowMonitorsToolbar), - if (isDesktop) ('Collapse toolbar', 'collapse_toolbar'), + if ((isDesktop || isWebDesktop)) ('show_monitors_tip', kKeyShowMonitorsToolbar), + if ((isDesktop || isWebDesktop)) ('Collapse toolbar', 'collapse_toolbar'), ('Show remote cursor', 'show_remote_cursor'), - if (isDesktop) ('Zoom cursor', 'zoom-cursor'), + if ((isDesktop || isWebDesktop)) ('Zoom cursor', 'zoom-cursor'), ('Show quality monitor', 'show_quality_monitor'), ('Mute', 'disable_audio'), if (isDesktop) ('Enable file copy and paste', 'enable_file_transfer'), diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 0fcc7e2a4..a8638a302 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -94,7 +94,7 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { Text(translate(pi.isHeadless ? 'OS Account' : 'OS Password')), ]), trailingIcon: Transform.scale( - scale: isDesktop ? 0.8 : 1, + scale: (isDesktop || isWebDesktop) ? 0.8 : 1, child: IconButton( onPressed: () { if (isMobile && Navigator.canPop(context)) { @@ -160,7 +160,7 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { ); } // divider - if (isDesktop) { + if (isDesktop || isWebDesktop) { v.add(TTextMenu(child: Offstage(), onPressed: () {}, divider: true)); } // ctrlAltDel @@ -229,7 +229,7 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { )); } // record - if (!isDesktop && + if (!(isDesktop || isWeb) && (ffi.recordingModel.start || (perms["recording"] != false))) { v.add(TTextMenu( child: Row( @@ -250,7 +250,7 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { onPressed: () => ffi.recordingModel.toggle())); } // fingerprint - if (!isDesktop) { + if (!(isDesktop || isWebDesktop)) { v.add(TTextMenu( child: Text(translate('Copy Fingerprint')), onPressed: () => onCopyFingerprint(FingerprintState.find(id).value), @@ -511,8 +511,8 @@ Future> toolbarDisplayToggle( child: Text(translate('Show displays as individual windows')))); } - final screenList = await getScreenRectList(); - if (useTextureRender && pi.isSupportMultiDisplay && screenList.length > 1) { + final isMultiScreens = !isWeb && (await getScreenRectList()).length > 1; + if (useTextureRender && pi.isSupportMultiDisplay && isMultiScreens) { final value = bind.sessionGetUseAllMyDisplaysForTheRemoteSession( sessionId: ffi.sessionId) == 'Y'; diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index a06ff5900..0e4a39d0a 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/models/state_model.dart'; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 4b24b54fc..bf30450a3 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -114,7 +114,7 @@ class _DesktopSettingPageState extends State if (!bind.isIncomingOnly()) _TabInfo( 'Display', Icons.desktop_windows_outlined, Icons.desktop_windows), - if (!bind.isIncomingOnly() && bind.pluginFeatureIsEnabled()) + if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled()) _TabInfo('Plugin', Icons.extension_outlined, Icons.extension), if (!bind.isDisableAccount()) _TabInfo('Account', Icons.person_outline, Icons.person), @@ -129,7 +129,8 @@ class _DesktopSettingPageState extends State if (!bind.isOutgoingOnly() && !bind.isDisableSettings()) _Safety(), if (!bind.isDisableSettings()) _Network(), if (!bind.isIncomingOnly()) _Display(), - if (!bind.isIncomingOnly() && bind.pluginFeatureIsEnabled()) _Plugin(), + if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled()) + _Plugin(), if (!bind.isDisableAccount()) _Account(), _About(), ]; diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart index 0b3053ab6..46d15be54 100644 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/consts.dart'; diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 3fb56d262..23d597937 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -35,13 +35,13 @@ class RemotePage extends StatefulWidget { RemotePage({ Key? key, required this.id, - required this.sessionId, - required this.tabWindowId, - required this.display, - required this.displays, - required this.password, required this.toolbarState, - required this.tabController, + this.sessionId, + this.tabWindowId, + this.password, + this.display, + this.displays, + this.tabController, this.switchUuid, this.forceRelay, this.isSharedPassword, @@ -58,7 +58,7 @@ class RemotePage extends StatefulWidget { final bool? forceRelay; final bool? isSharedPassword; final SimpleWrapper?> _lastState = SimpleWrapper(null); - final DesktopTabController tabController; + final DesktopTabController? tabController; FFI get ffi => (_lastState.value! as _RemotePageState)._ffi; @@ -129,7 +129,7 @@ class _RemotePageState extends State } _ffi.ffiModel.updateEventListener(sessionId, widget.id); - bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); + if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); _ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId); // Session option should be set after models.dart/FFI.start _showRemoteCursor.value = bind.sessionGetToggleOptionSync( @@ -150,7 +150,7 @@ class _RemotePageState extends State // } _blockableOverlayState.applyFfi(_ffi); - widget.tabController.onSelected?.call(widget.id); + widget.tabController?.onSelected?.call(widget.id); } @override @@ -431,9 +431,9 @@ class _RemotePageState extends State Widget getBodyForDesktop(BuildContext context) { var paints = [ MouseRegion(onEnter: (evt) { - bind.hostStopSystemKeyPropagate(stopped: false); + if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: false); }, onExit: (evt) { - bind.hostStopSystemKeyPropagate(stopped: true); + if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: true); }, child: LayoutBuilder(builder: (context, constraints) { Future.delayed(Duration.zero, () { Provider.of(context, listen: false).updateViewStyle(); @@ -669,6 +669,11 @@ class _ImagePaintState extends State { MouseCursor _buildCursorOfCache( CursorModel cursor, double scale, CursorData? cache) { + // TODO: web cursor + if (isWeb) { + return MouseCursor.defer; + } + if (cache == null) { return MouseCursor.defer; } else { diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 3f4ade694..3618632ce 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -491,7 +491,7 @@ class _RemoteToolbarState extends State { toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi)); toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi)); } - toolbarItems.add(_RecordMenu()); + if (!isWeb) toolbarItems.add(_RecordMenu()); toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi)); final toolbarBorderRadius = BorderRadius.all(Radius.circular(4.0)); return Column( @@ -940,13 +940,12 @@ class ScreenAdjustor { } updateScreen() async { - final v = await rustDeskWinManager.call( - WindowType.Main, kWindowGetWindowInfo, ''); - final String valueStr = v.result; - if (valueStr.isEmpty) { + final String info = + isWeb ? screenInfo : await _getScreenInfoDesktop() ?? ''; + if (info.isEmpty) { _screen = null; } else { - final screenMap = jsonDecode(valueStr); + final screenMap = jsonDecode(info); _screen = window_size.Screen( Rect.fromLTRB(screenMap['frame']['l'], screenMap['frame']['t'], screenMap['frame']['r'], screenMap['frame']['b']), @@ -959,15 +958,23 @@ class ScreenAdjustor { } } + _getScreenInfoDesktop() async { + final v = await rustDeskWinManager.call( + WindowType.Main, kWindowGetWindowInfo, ''); + return v.result; + } + Future isWindowCanBeAdjusted() async { final viewStyle = await bind.sessionGetViewStyle(sessionId: ffi.sessionId) ?? ''; if (viewStyle != kRemoteViewStyleOriginal) { return false; } - final remoteCount = RemoteCountState.find().value; - if (remoteCount != 1) { - return false; + if (!isWeb) { + final remoteCount = RemoteCountState.find().value; + if (remoteCount != 1) { + return false; + } } if (_screen == null) { return false; @@ -1325,6 +1332,14 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { final display = json.decode(mainDisplay); if (display['w'] != null && display['h'] != null) { _localResolution = Resolution(display['w'], display['h']); + if (isWeb) { + if (display['scaleFactor'] != null) { + _localResolution = Resolution( + (display['w'] / display['scaleFactor']).toInt(), + (display['h'] / display['scaleFactor']).toInt(), + ); + } + } } } catch (e) { debugPrint('Failed to decode $mainDisplay, $e'); diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 25089871e..3df2e04b8 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -125,10 +125,7 @@ void runMainApp(bool startService) async { await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]); gFFI.userModel.refreshCurrentUser(); runApp(App()); - if (isWeb) { - // Web does not support window manager. - return; - } + // Set window option. WindowOptions windowOptions = getHiddenTitleBarWindowOptions(); windowManager.waitUntilReadyToShow(windowOptions, () async { diff --git a/flutter/lib/mobile/pages/file_manager_page.dart b/flutter/lib/mobile/pages/file_manager_page.dart index 5a504e172..b74d44484 100644 --- a/flutter/lib/mobile/pages/file_manager_page.dart +++ b/flutter/lib/mobile/pages/file_manager_page.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_hbb/models/file_model.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; import 'package:toggle_switch/toggle_switch.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index dcda7b6a9..6d52c4658 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -193,6 +193,7 @@ class InputModel { bool get keyboardPerm => parent.target!.ffiModel.keyboard; String get id => parent.target?.id ?? ''; String? get peerPlatform => parent.target?.ffiModel.pi.platform; + bool get isViewOnly => parent.target!.ffiModel.viewOnly; InputModel(this.parent) { sessionId = parent.target!.sessionId; @@ -207,7 +208,7 @@ class InputModel { updateKeyboardMode() async { // * Currently mobile does not enable map mode - if (isDesktop) { + if (isDesktop || isWebDesktop) { if (keyboardMode.isEmpty) { keyboardMode = await bind.sessionGetKeyboardMode(sessionId: sessionId) ?? @@ -217,7 +218,8 @@ class InputModel { } KeyEventResult handleRawKeyEvent(RawKeyEvent e) { - if (isDesktop && !isInputSourceFlutter) { + if (isViewOnly) return KeyEventResult.handled; + if ((isDesktop || isWebDesktop) && !isInputSourceFlutter) { return KeyEventResult.handled; } @@ -256,7 +258,7 @@ class InputModel { } // * Currently mobile does not enable map mode - if (isDesktop && keyboardMode == 'map') { + if ((isDesktop || isWebDesktop) && keyboardMode == 'map') { mapKeyboardMode(e); } else { legacyKeyboardMode(e); @@ -467,6 +469,7 @@ class InputModel { void onPointHoverImage(PointerHoverEvent e) { _stopFling = true; + if (isViewOnly) return; if (e.kind != ui.PointerDeviceKind.mouse) return; if (!isPhysicalMouse.value) { isPhysicalMouse.value = true; @@ -479,7 +482,7 @@ class InputModel { void onPointerPanZoomStart(PointerPanZoomStartEvent e) { _lastScale = 1.0; _stopFling = true; - + if (isViewOnly) return; if (peerPlatform == kPeerPlatformAndroid) { handlePointerEvent('touch', 'pan_start', e.position); } @@ -487,6 +490,7 @@ class InputModel { // https://docs.flutter.dev/release/breaking-changes/trackpad-gestures void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) { + if (isViewOnly) return; if (peerPlatform != kPeerPlatformAndroid) { final scale = ((e.scale - _lastScale) * 1000).toInt(); _lastScale = e.scale; @@ -612,6 +616,7 @@ class InputModel { void onPointDownImage(PointerDownEvent e) { debugPrint("onPointDownImage ${e.kind}"); _stopFling = true; + if (isViewOnly) return; if (e.kind != ui.PointerDeviceKind.mouse) { if (isPhysicalMouse.value) { isPhysicalMouse.value = false; @@ -623,6 +628,7 @@ class InputModel { } void onPointUpImage(PointerUpEvent e) { + if (isViewOnly) return; if (e.kind != ui.PointerDeviceKind.mouse) return; if (isPhysicalMouse.value) { handleMouse(_getMouseEvent(e, _kMouseEventUp), e.position); @@ -630,6 +636,7 @@ class InputModel { } void onPointMoveImage(PointerMoveEvent e) { + if (isViewOnly) return; if (e.kind != ui.PointerDeviceKind.mouse) return; if (isPhysicalMouse.value) { handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position); @@ -637,6 +644,7 @@ class InputModel { } void onPointerSignalImage(PointerSignalEvent e) { + if (isViewOnly) return; if (e is PointerScrollEvent) { var dx = e.scrollDelta.dx.toInt(); var dy = e.scrollDelta.dy.toInt(); @@ -902,9 +910,11 @@ class InputModel { int minX = rect.left.toInt(); // https://github.com/rustdesk/rustdesk/issues/6678 // For Windows, [0,maxX], [0,maxY] should be set to enable window snapping. - int maxX = (rect.left + rect.width).toInt() - (peerPlatform == kPeerPlatformWindows ? 0 : 1); + int maxX = (rect.left + rect.width).toInt() - + (peerPlatform == kPeerPlatformWindows ? 0 : 1); int minY = rect.top.toInt(); - int maxY = (rect.top + rect.height).toInt() - (peerPlatform == kPeerPlatformWindows ? 0 : 1); + int maxY = (rect.top + rect.height).toInt() - + (peerPlatform == kPeerPlatformWindows ? 0 : 1); evtX = trySetNearestRange(evtX, minX, maxX, 5); evtY = trySetNearestRange(evtY, minY, maxY, 5); if (kind == kPointerEventKindMouse) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index a58a46db5..0ed6fc8e8 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -429,7 +429,7 @@ class FfiModel with ChangeNotifier { } handleAliasChanged(Map evt) { - if (!isDesktop) return; + if (!(isDesktop || isWebDesktop)) return; final String peerId = evt['id']; final String alias = evt['alias']; String label = getDesktopTabLabel(peerId, alias); @@ -767,7 +767,7 @@ class FfiModel with ChangeNotifier { _pi.isSet.value = true; stateGlobal.resetLastResolutionGroupValues(peerId); - if (isDesktop) { + if (isDesktop || isWebDesktop) { checkDesktopKeyboardMode(); } @@ -1114,7 +1114,7 @@ class ImageModel with ChangeNotifier { update(ui.Image? image) async { if (_image == null && image != null) { - if (isWebDesktop || isDesktop) { + if (isDesktop || isWebDesktop) { await parent.target?.canvasModel.updateViewStyle(); await parent.target?.canvasModel.updateScrollStyle(); } else { @@ -1288,18 +1288,15 @@ class CanvasModel with ChangeNotifier { double get scrollX => _scrollX; double get scrollY => _scrollY; - static double get leftToEdge => (isDesktop || isWebDesktop) - ? windowBorderWidth + kDragToResizeAreaPadding.left - : 0; - static double get rightToEdge => (isDesktop || isWebDesktop) - ? windowBorderWidth + kDragToResizeAreaPadding.right - : 0; - static double get topToEdge => (isDesktop || isWebDesktop) + static double get leftToEdge => + isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.left : 0; + static double get rightToEdge => + isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.right : 0; + static double get topToEdge => isDesktop ? tabBarHeight + windowBorderWidth + kDragToResizeAreaPadding.top : 0; - static double get bottomToEdge => (isDesktop || isWebDesktop) - ? windowBorderWidth + kDragToResizeAreaPadding.bottom - : 0; + static double get bottomToEdge => + isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.bottom : 0; updateViewStyle({refreshMousePos = true}) async { Size getSize() { @@ -1422,7 +1419,7 @@ class CanvasModel with ChangeNotifier { // If keyboard is not permitted, do not move cursor when mouse is moving. if (parent.target != null && parent.target!.ffiModel.keyboard) { // Draw cursor if is not desktop. - if (!isDesktop) { + if (!(isDesktop || isWebDesktop)) { parent.target!.cursorModel.moveLocal(x, y); } else { try { @@ -2495,7 +2492,8 @@ class PeerInfo with ChangeNotifier { List get virtualDisplays => List.from( platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []); - bool get isSupportMultiDisplay => isDesktop && isSupportMultiUiSession; + bool get isSupportMultiDisplay => + (isDesktop || isWebDesktop) && isSupportMultiUiSession; bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false; diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart index 5c44859ce..4896781a9 100644 --- a/flutter/lib/models/web_model.dart +++ b/flutter/lib/models/web_model.dart @@ -98,9 +98,11 @@ class PlatformFFI { sessionId: sessionId, display: display, ptr: ptr); Future init(String appType) async { - isWebDesktop = !context.callMethod('isMobile'); context.callMethod('init'); version = getByName('version'); + window.onContextMenu.listen((event) { + event.preventDefault(); + }); context['onRegisteredEvent'] = (String message) { try { diff --git a/flutter/lib/native/common.dart b/flutter/lib/native/common.dart index 4c92ff26c..d3888a245 100644 --- a/flutter/lib/native/common.dart +++ b/flutter/lib/native/common.dart @@ -6,5 +6,8 @@ final isWindows_ = Platform.isWindows; final isMacOS_ = Platform.isMacOS; final isLinux_ = Platform.isLinux; final isWeb_ = false; +final isWebDesktop_ = false; final isDesktop_ = Platform.isWindows || Platform.isMacOS || Platform.isLinux; + +String get screenInfo_ => ''; diff --git a/flutter/lib/utils/image.dart b/flutter/lib/utils/image.dart index a153dbc63..2a208a683 100644 --- a/flutter/lib/utils/image.dart +++ b/flutter/lib/utils/image.dart @@ -3,6 +3,8 @@ import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; +import 'package:flutter_hbb/common.dart'; + Future decodeImageFromPixels( Uint8List pixels, int width, @@ -79,6 +81,11 @@ class ImagePainter extends CustomPainter { paint.filterQuality = FilterQuality.high; } } + // It's strange that if (scale < 0.5 && paint.filterQuality == FilterQuality.medium) + // The canvas.drawImage will not work on web + if (isWeb) { + paint.filterQuality = FilterQuality.high; + } canvas.drawImage( image!, Offset(x.toInt().toDouble(), y.toInt().toDouble()), paint); } diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 42935c782..15ae78d2c 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -5,6 +5,8 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:uuid/uuid.dart'; +import 'package:flutter_hbb/consts.dart'; + final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); @@ -237,7 +239,6 @@ class RustdeskImpl { Future sessionGetViewStyle( {required UuidValue sessionId, dynamic hint}) { - // TODO: default values return Future(() => js.context.callMethod('getByName', ['option:session', 'view_style'])); } @@ -252,7 +253,6 @@ class RustdeskImpl { Future sessionGetScrollStyle( {required UuidValue sessionId, dynamic hint}) { - // TODO: default values return Future(() => js.context.callMethod('getByName', ['option:session', 'scroll_style'])); } @@ -266,9 +266,7 @@ class RustdeskImpl { } Future sessionGetImageQuality( - // TODO: default values - {required UuidValue sessionId, - dynamic hint}) { + {required UuidValue sessionId, dynamic hint}) { return Future(() => js.context .callMethod('getByName', ['option:session', 'image_quality'])); } @@ -283,9 +281,9 @@ class RustdeskImpl { Future sessionGetKeyboardMode( {required UuidValue sessionId, dynamic hint}) { - // TODO: default values - return Future(() => js.context - .callMethod('getByName', ['option:session', 'keyboard_mode'])); + final mode = + js.context.callMethod('getByName', ['option:session', 'keyboard_mode']); + return Future(() => mode == '' ? null : mode); } Future sessionSetKeyboardMode( @@ -345,7 +343,7 @@ class RustdeskImpl { bool sessionIsKeyboardModeSupported( {required UuidValue sessionId, required String mode, dynamic hint}) { - throw UnimplementedError(); + return mode == kKeyLegacyMode; } Future sessionSetCustomImageQuality( @@ -748,8 +746,7 @@ class RustdeskImpl { } Future mainCheckConnectStatus({dynamic hint}) { - return Future( - () => js.context.callMethod('setByName', ["check_conn_status"])); + throw UnimplementedError(); } Future mainIsUsingPublicServer({dynamic hint}) { @@ -929,12 +926,14 @@ class RustdeskImpl { Future mainSetUserDefaultOption( {required String key, required String value, dynamic hint}) { - // TODO: do we need the default option? - throw UnimplementedError(); + return js.context.callMethod('getByName', [ + 'option:user:default', + jsonEncode({'name': key, 'value': value}) + ]); } String mainGetUserDefaultOption({required String key, dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', ['option:user:default', key]); } Future mainHandleRelayId({required String id, dynamic hint}) { @@ -946,7 +945,7 @@ class RustdeskImpl { } String mainGetMainDisplay({dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', ['main_display']); } String mainGetDisplays({dynamic hint}) { @@ -1399,7 +1398,7 @@ class RustdeskImpl { } bool mainHasPixelbufferTextureRender({dynamic hint}) { - throw UnimplementedError(); + return false; } bool mainHasFileClipboard({dynamic hint}) { @@ -1553,7 +1552,9 @@ class RustdeskImpl { } String mainSupportedInputSource({dynamic hint}) { - return jsonEncode(['Input source 2', 'input_source_2_tip']); + return jsonEncode([ + ['Input source 2', 'input_source_2_tip'] + ]); } Future mainGenerate2Fa({dynamic hint}) { diff --git a/flutter/lib/web/common.dart b/flutter/lib/web/common.dart index bab3684ed..93b53f948 100644 --- a/flutter/lib/web/common.dart +++ b/flutter/lib/web/common.dart @@ -1,3 +1,4 @@ +import 'dart:js' as js; final isAndroid_ = false; final isIOS_ = false; @@ -5,5 +6,8 @@ final isWindows_ = false; final isMacOS_ = false; final isLinux_ = false; final isWeb_ = true; +final isWebDesktop_ = !js.context.callMethod('isMobile'); final isDesktop_ = false; + +String get screenInfo_ => js.context.callMethod('getByName', ['screen_info']); diff --git a/flutter/web/js/src/connection.ts b/flutter/web/js/src/connection.ts index 3f9de4131..9eb24caec 100644 --- a/flutter/web/js/src/connection.ts +++ b/flutter/web/js/src/connection.ts @@ -652,7 +652,7 @@ export default class Connection { } getOption(name: string): any { - return this._options[name]; + return this._options[name] ?? globals.getUserDefaultOption(name); } getToggleOption(name: string): Boolean { @@ -839,6 +839,52 @@ export default class Connection { } toggleOption(name: string) { + + // } else if name == "block-input" { + // option.block_input = BoolOption::Yes.into(); + // } else if name == "unblock-input" { + // option.block_input = BoolOption::No.into(); + // } else if name == "show-quality-monitor" { + // config.show_quality_monitor.v = !config.show_quality_monitor.v; + // } else if name == "allow_swap_key" { + // config.allow_swap_key.v = !config.allow_swap_key.v; + // } else if name == "view-only" { + // config.view_only.v = !config.view_only.v; + // let f = |b: bool| { + // if b { + // BoolOption::Yes.into() + // } else { + // BoolOption::No.into() + // } + // }; + // if config.view_only.v { + // option.disable_keyboard = f(true); + // option.disable_clipboard = f(true); + // option.show_remote_cursor = f(true); + // option.enable_file_transfer = f(false); + // option.lock_after_session_end = f(false); + // } else { + // option.disable_keyboard = f(false); + // option.disable_clipboard = f(self.get_toggle_option("disable-clipboard")); + // option.show_remote_cursor = f(self.get_toggle_option("show-remote-cursor")); + // option.enable_file_transfer = f(self.config.enable_file_transfer.v); + // option.lock_after_session_end = f(self.config.lock_after_session_end.v); + // } + // } else { + // let is_set = self + // .options + // .get(&name) + // .map(|o| !o.is_empty()) + // .unwrap_or(false); + // if is_set { + // self.config.options.remove(&name); + // } else { + // self.config.options.insert(name, "Y".to_owned()); + // } + // self.config.store(&self.id); + // return None; + // } + const v = !this._options[name]; const option = message.OptionMessage.fromPartial({}); const v2 = v @@ -860,13 +906,43 @@ export default class Connection { case "privacy-mode": option.privacy_mode = v2; break; + case "enable-file-transfer": + option.enable_file_transfer = v2; + break; case "block-input": option.block_input = message.OptionMessage_BoolOption.Yes; break; case "unblock-input": option.block_input = message.OptionMessage_BoolOption.No; break; + case "show-quality-monitor": + case "allow-swap-key": + break; + case "view-only": + if (v) { + option.disable_keyboard = message.OptionMessage_BoolOption.Yes; + option.disable_clipboard = message.OptionMessage_BoolOption.Yes; + option.show_remote_cursor = message.OptionMessage_BoolOption.Yes; + option.enable_file_transfer = message.OptionMessage_BoolOption.No; + option.lock_after_session_end = message.OptionMessage_BoolOption.No; + } else { + option.disable_keyboard = message.OptionMessage_BoolOption.No; + option.disable_clipboard = this.getToggleOption("disable-clipboard") + ? message.OptionMessage_BoolOption.Yes + : message.OptionMessage_BoolOption.No; + option.show_remote_cursor = this.getToggleOption("show-remote-cursor") + ? message.OptionMessage_BoolOption.Yes + : message.OptionMessage_BoolOption.No; + option.enable_file_transfer = this.getToggleOption("enable-file-transfer") + ? message.OptionMessage_BoolOption.Yes + : message.OptionMessage_BoolOption.No; + option.lock_after_session_end = this.getToggleOption("lock-after-session-end") + ? message.OptionMessage_BoolOption.Yes + : message.OptionMessage_BoolOption.No; + } + break; default: + this.setOption(name, this._options[name] ? undefined : "Y"); return; } if (name.indexOf("block-input") < 0) this.setOption(name, v); diff --git a/flutter/web/js/src/globals.js b/flutter/web/js/src/globals.js index ffb445ccb..68c665f64 100644 --- a/flutter/web/js/src/globals.js +++ b/flutter/web/js/src/globals.js @@ -211,7 +211,7 @@ window.setByName = (name, value) => { curConn.refresh(); break; case 'reconnect': - curConn.reconnect(); + curConn?.reconnect(); break; case 'toggle_option': curConn.toggleOption(value); @@ -244,6 +244,7 @@ window.setByName = (name, value) => { curConn.inputString(value); break; case 'send_mouse': + if (!curConn) return; let mask = 0; value = JSON.parse(value); switch (value.type) { @@ -288,6 +289,9 @@ window.setByName = (name, value) => { value = JSON.parse(value); localStorage.setItem(name + ':' + value.name, value.value); break; + case 'option:user:default': + setUserDefaultOption(value); + break; case 'option:session': value = JSON.parse(value); curConn.setOption(value.name, value.value); @@ -295,12 +299,11 @@ window.setByName = (name, value) => { case 'option:peer': setPeerOption(value); break; + case 'option:toggle': + return curConn.toggleOption(value); case 'input_os_password': curConn.inputOsPassword(value); break; - case 'check_conn_status': - curConn.checkConnStatus(); - break; case 'session_add_sync': return sessionAdd(value); case 'session_start': @@ -374,8 +377,14 @@ function _getByName(name, arg) { case 'translate': arg = JSON.parse(arg); return translate(arg.locale, arg.text); + case 'option:user:default': + return getUserDefaultOption(arg); case 'option:session': - return curConn.getOption(arg); + if (curConn) { + return curConn.getOption(arg); + } else { + return getUserDefaultOption(arg); + } case 'option:peer': return getPeerOption(arg); case 'option:toggle': @@ -412,6 +421,28 @@ function _getByName(name, arg) { return getAuditServer(arg); case 'alternative_codecs': return getAlternativeCodecs(); + case 'screen_info': + return JSON.stringify({ + frame: { + l: window.screenX, + t: window.screenY, + r: window.screenX + window.innerWidth, + b: window.screenY + window.innerHeight, + }, + visibleFrame: { + l: window.screen.availLeft, + t: window.screen.availTop, + r: window.screen.availLeft + window.screen.availWidth, + b: window.screen.availTop + window.screen.availHeight, + }, + scaleFactor: window.devicePixelRatio, + }); + case 'main_display': + return JSON.stringify({ + w: window.screen.availWidth, + h: window.screen.availHeight, + scaleFactor: window.devicePixelRatio, + }); } return ''; } @@ -521,20 +552,52 @@ export function getVersionNumber(v) { } } +// ========================== options begin ========================== +function setUserDefaultOption(value) { + try { + const ojb = JSON.parse(value); + const userDefaultOptions = JSON.parse(localStorage.getItem('user-default-options')) || {}; + userDefaultOptions[ojb.name] = ojb.value; + localStorage.setItem('user-default-options', JSON.stringify(userDefaultOptions)); + } + catch (e) { + console.error('Failed to set user default options: ' + e.message); + } +} + +export function getUserDefaultOption(value) { + const defaultOptions = { + 'view_style': 'original', + 'scroll_style': 'scrollauto', + 'image_quality': 'balanced', + 'codec-preference': 'auto', + 'custom_image_quality': '50', + 'custom-fps': '30', + }; + try { + const userDefaultOptions = JSON.parse(localStorage.getItem('user-default-options')) || {}; + return userDefaultOptions[value] || defaultOptions[value] || ''; + } + catch (e) { + console.error('Failed to get user default options: ' + e.message); + return defaultOptions[value] || ''; + } +} + function getPeerOption(value) { try { const obj = JSON.parse(value); const options = getPeers()[obj.id] || {}; - return options[obj.name] || ''; + return options[obj.name] ?? getUserDefaultOption(obj.name); } catch (e) { console.error('Failed to get peer option: "' + value + '", ' + e.message); } } -function setPeerOption(value) { +function setPeerOption(param) { try { - const obj = JSON.parse(value); + const obj = JSON.parse(param); const id = obj.id; const name = obj.name; const value = obj.value; @@ -554,6 +617,7 @@ function setPeerOption(value) { console.error('Failed to set peer option: "' + value + '", ' + e.message); } } +// ========================= options end =========================== // ========================== peers begin ========================== function getRecentPeers() { @@ -668,10 +732,10 @@ function increasePort(host, offset) { function getAlternativeCodecs() { return JSON.stringify({ - vp8: 1, - av1: 0, - h264: 1, - h265: 1, + vp8: true, + av1: false, + h264: false, + h265: false, }); } // ========================== settings end ===========================