diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index c9f781555..a55bf30cd 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -550,7 +550,8 @@ class MyTheme { Get.changeThemeMode(mode); if (desktopType == DesktopType.main || isAndroid || isIOS) { if (mode == ThemeMode.system) { - await bind.mainSetLocalOption(key: kCommConfKeyTheme, value: ''); + await bind.mainSetLocalOption( + key: kCommConfKeyTheme, value: defaultOptionTheme); } else { await bind.mainSetLocalOption( key: kCommConfKeyTheme, value: mode.toShortString()); @@ -1421,13 +1422,13 @@ bool option2bool(String option, String value) { String bool2option(String option, bool b) { String res; if (option.startsWith('enable-')) { - res = b ? '' : 'N'; + res = b ? defaultOptionYes : 'N'; } else if (option.startsWith('allow-') || option == "stop-service" || option == "direct-server" || option == "stop-rendezvous-service" || option == kOptionForceAlwaysRelay) { - res = b ? 'Y' : ''; + res = b ? 'Y' : defaultOptionNo; } else { assert(false); res = b ? 'Y' : 'N'; @@ -3281,3 +3282,14 @@ setResizable(bool resizable) { windowManager.setResizable(resizable); } } + +isOptionFixed(String key) => bind.mainIsOptionFixed(key: key); + +final isCustomClient = bind.isCustomClient(); +get defaultOptionLang => isCustomClient ? 'default' : ''; +get defaultOptionTheme => isCustomClient ? 'system' : ''; +get defaultOptionYes => isCustomClient ? 'Y' : ''; +get defaultOptionNo => isCustomClient ? 'N' : ''; +get defaultOptionWhitelist => isCustomClient ? ',' : ''; +get defaultOptionAccessMode => isCustomClient ? 'custom' : ''; +get defaultOptionApproveMode => isCustomClient ? 'password-click' : ''; diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 2b7f3842e..b0a2128d8 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -333,6 +333,7 @@ class _AddressBookState extends State { @protected MenuEntryBase syncMenuItem() { + final isOptFixed = isOptionFixed(syncAbOption); return MenuEntrySwitch( switchType: SwitchType.scheckbox, text: translate('Sync with recent sessions'), @@ -343,11 +344,13 @@ class _AddressBookState extends State { gFFI.abModel.setShouldAsync(v); }, dismissOnClicked: true, + enabled: (!isOptFixed).obs, ); } @protected MenuEntryBase sortMenuItem() { + final isOptFixed = isOptionFixed(sortAbTagsOption); return MenuEntrySwitch( switchType: SwitchType.scheckbox, text: translate('Sort tags'), @@ -355,15 +358,17 @@ class _AddressBookState extends State { return shouldSortTags(); }, setter: (bool v) async { - bind.mainSetLocalOption(key: sortAbTagsOption, value: v ? 'Y' : ''); + bind.mainSetLocalOption(key: sortAbTagsOption, value: v ? 'Y' : defaultOptionNo); gFFI.abModel.sortTags.value = v; }, dismissOnClicked: true, + enabled: (!isOptFixed).obs, ); } @protected MenuEntryBase filterMenuItem() { + final isOptFixed = isOptionFixed(filterAbTagOption); return MenuEntrySwitch( switchType: SwitchType.scheckbox, text: translate('Filter by intersection'), @@ -371,10 +376,11 @@ class _AddressBookState extends State { return filterAbTagByIntersection(); }, setter: (bool v) async { - bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : ''); + bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : defaultOptionNo); gFFI.abModel.filterByIntersection.value = v; }, dismissOnClicked: true, + enabled: (!isOptFixed).obs, ); } diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index b98b2b248..cfafdc166 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -177,11 +177,14 @@ void changeIdDialog() { } void changeWhiteList({Function()? callback}) async { - var newWhiteList = (await bind.mainGetOption(key: 'whitelist')).split(','); - var newWhiteListField = newWhiteList.join('\n'); + final curWhiteList = await bind.mainGetOption(key: kOptionWhitelist); + var newWhiteListField = curWhiteList == defaultOptionWhitelist + ? '' + : curWhiteList.split(',').join('\n'); var controller = TextEditingController(text: newWhiteListField); var msg = ""; var isInProgress = false; + final isOptFixed = isOptionFixed(kOptionWhitelist); gFFI.dialogManager.show((setState, close, context) { return CustomAlertDialog( title: Text(translate("IP Whitelisting")), @@ -214,14 +217,15 @@ void changeWhiteList({Function()? callback}) async { ), actions: [ dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("Clear", onPressed: () async { - await bind.mainSetOption(key: 'whitelist', value: ''); + dialogButton("Clear", onPressed: isOptFixed ? null : () async { + await bind.mainSetOption( + key: kOptionWhitelist, value: defaultOptionWhitelist); callback?.call(); close(); }, isOutline: true), dialogButton( "OK", - onPressed: () async { + onPressed: isOptFixed ? null : () async { setState(() { msg = ""; isInProgress = true; @@ -248,7 +252,11 @@ void changeWhiteList({Function()? callback}) async { } newWhiteList = ips.join(','); } - await bind.mainSetOption(key: 'whitelist', value: newWhiteList); + if (newWhiteList.trim().isEmpty) { + newWhiteList = defaultOptionWhitelist; + } + await bind.mainSetOption( + key: kOptionWhitelist, value: newWhiteList); callback?.call(); close(); }, @@ -298,7 +306,7 @@ Future changeDirectAccessPort( dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("OK", onPressed: () async { await bind.mainSetOption( - key: 'direct-access-port', value: controller.text); + key: kOptionDirectAccessPort, value: controller.text); close(); }), ], @@ -345,7 +353,7 @@ Future changeAutoDisconnectTimeout(String old) async { dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("OK", onPressed: () async { await bind.mainSetOption( - key: 'auto-disconnect-timeout', value: controller.text); + key: kOptionAutoDisconnectTimeout, value: controller.text); close(); }), ], diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 7011e722e..f4a41008e 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -537,7 +537,7 @@ class _PeerTabPageState extends State ), onTap: () async { await bind.mainSetLocalOption( - key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? "" : "Y"); + key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? defaultOptionNo : "Y"); hideAbTagsPanel.value = !hideAbTagsPanel.value; }); } diff --git a/flutter/lib/common/widgets/setting_widgets.dart b/flutter/lib/common/widgets/setting_widgets.dart index 56340ec54..552323090 100644 --- a/flutter/lib/common/widgets/setting_widgets.dart +++ b/flutter/lib/common/widgets/setting_widgets.dart @@ -10,8 +10,8 @@ import 'package:get/get.dart'; customImageQualityWidget( {required double initQuality, required double initFps, - required Function(double) setQuality, - required Function(double) setFps, + required Function(double)? setQuality, + required Function(double)? setFps, required bool showFps, required bool showMoreQuality}) { if (initQuality < kMinQuality || @@ -27,16 +27,12 @@ customImageQualityWidget( final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality); final debouncerQuality = Debouncer( Duration(milliseconds: 1000), - onChanged: (double v) { - setQuality(v); - }, + onChanged: setQuality, initialValue: qualityValue.value, ); final debouncerFps = Debouncer( Duration(milliseconds: 1000), - onChanged: (double v) { - setFps(v); - }, + onChanged: setFps, initialValue: fpsValue.value, ); @@ -62,10 +58,12 @@ customImageQualityWidget( divisions: moreQualityChecked.value ? ((kMaxMoreQuality - kMinQuality) / 10).round() : ((kMaxQuality - kMinQuality) / 5).round(), - onChanged: (double value) async { - qualityValue.value = value; - debouncerQuality.value = value; - }, + onChanged: setQuality == null + ? null + : (double value) async { + qualityValue.value = value; + debouncerQuality.value = value; + }, ), ), Expanded( @@ -124,10 +122,12 @@ customImageQualityWidget( min: kMinFps, max: kMaxFps, divisions: ((kMaxFps - kMinFps) / 5).round(), - onChanged: (double value) async { - fpsValue.value = value; - debouncerFps.value = value; - }, + onChanged: setFps == null + ? null + : (double value) async { + fpsValue.value = value; + debouncerFps.value = value; + }, ), ), Expanded( @@ -152,21 +152,29 @@ customImageQualitySetting() { final qualityKey = 'custom_image_quality'; final fpsKey = 'custom-fps'; - var initQuality = + final initQuality = (double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ?? kDefaultQuality); - var initFps = (double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? - kDefaultFps); + final isQuanlityFixed = isOptionFixed(qualityKey); + final initFps = + (double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? + kDefaultFps); + final isFpsFixed = isOptionFixed(fpsKey); return customImageQualityWidget( initQuality: initQuality, initFps: initFps, - setQuality: (v) { - bind.mainSetUserDefaultOption(key: qualityKey, value: v.toString()); - }, - setFps: (v) { - bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString()); - }, + setQuality: isQuanlityFixed + ? null + : (v) { + bind.mainSetUserDefaultOption( + key: qualityKey, value: v.toString()); + }, + setFps: isFpsFixed + ? null + : (v) { + bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString()); + }, showFps: true, showMoreQuality: true); } @@ -208,23 +216,25 @@ List ServerConfigImportExportWidgets( List<(String, String)> otherDefaultSettings() { List<(String, String)> v = [ - ('View Mode', 'view_only'), - if ((isDesktop || isWebDesktop)) ('show_monitors_tip', kKeyShowMonitorsToolbar), - if ((isDesktop || isWebDesktop)) ('Collapse toolbar', 'collapse_toolbar'), - ('Show remote cursor', 'show_remote_cursor'), - ('Follow remote cursor', 'follow_remote_cursor'), - ('Follow remote window focus', 'follow_remote_window'), - 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'), - ('Disable clipboard', 'disable_clipboard'), - ('Lock after session end', 'lock_after_session_end'), - ('Privacy mode', 'privacy_mode'), - if (isMobile) ('Touch mode', 'touch-mode'), - ('True color (4:4:4)', 'i444'), + ('View Mode', kOptionViewOnly), + if ((isDesktop || isWebDesktop)) + ('show_monitors_tip', kKeyShowMonitorsToolbar), + if ((isDesktop || isWebDesktop)) + ('Collapse toolbar', kOptionCollapseToolbar), + ('Show remote cursor', kOptionShowRemoteCursor), + ('Follow remote cursor', kOptionFollowRemoteCursor), + ('Follow remote window focus', kOptionFollowRemoteWindow), + if ((isDesktop || isWebDesktop)) ('Zoom cursor', kOptionZoomCursor), + ('Show quality monitor', kOptionShowQualityMonitor), + ('Mute', kOptionDisableAudio), + if (isDesktop) ('Enable file copy and paste', kOptionEnableFileTransfer), + ('Disable clipboard', kOptionDisableClipboard), + ('Lock after session end', kOptionLockAfterSessionEnd), + ('Privacy mode', kOptionPrivacyMode), + if (isMobile) ('Touch mode', kOptionTouchMode), + ('True color (4:4:4)', kOptionI444), ('Reverse mouse wheel', kKeyReverseMouseWheel), - ('swap-left-right-mouse', 'swap-left-right-mouse'), + ('swap-left-right-mouse', kOptionSwapLeftRightMouse), if (isDesktop && useTextureRender) ( 'Show displays as individual windows', diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 5999fdbf4..db90237f9 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -327,7 +327,7 @@ Future>> toolbarCodec( final alternativeCodecs = await bind.sessionAlternativeCodecs(sessionId: sessionId); final groupValue = await bind.sessionGetOption( - sessionId: sessionId, arg: 'codec-preference') ?? + sessionId: sessionId, arg: kOptionCodecPreference) ?? ''; final List codecs = []; try { @@ -349,7 +349,7 @@ Future>> toolbarCodec( onChanged(String? value) async { if (value == null) return; await bind.sessionPeerOption( - sessionId: sessionId, name: 'codec-preference', value: value); + sessionId: sessionId, name: kOptionCodecPreference, value: value); bind.sessionChangePreferCodec(sessionId: sessionId); } diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 926dd32af..e23d036e6 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -68,11 +68,53 @@ const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window"; const String kWindowEventGetCachedSessionData = "get_cached_session_data"; const String kWindowEventOpenMonitorSession = "open_monitor_session"; +const String kOptionViewStyle = "view_style"; +const String kOptionScrollStyle = "scroll_style"; +const String kOptionImageQuality = "image_quality"; const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs"; const String kOptionOpenInTabs = "allow-open-in-tabs"; const String kOptionOpenInWindows = "allow-open-in-windows"; const String kOptionForceAlwaysRelay = "force-always-relay"; -const String kOptionViewOnly = "view-only"; +const String kOptionViewOnly = "view_only"; +const String kOptionEnableLanDiscovery = "enable-lan-discovery"; +const String kOptionWhitelist = "whitelist"; +const String kOptionEnableAbr = "enable-abr"; +const String kOptionEnableRecordSession = "enable-record-session"; +const String kOptionDirectServer = "direct-server"; +const String kOptionDirectAccessPort = "direct-access-port"; +const String kOptionAllowAutoDisconnect = "allow-auto-disconnect"; +const String kOptionAutoDisconnectTimeout = "auto-disconnect-timeout"; +const String kOptionEnableHwcodec = "enable-hwcodec"; +const String kOptionAllowAutoRecordIncoming = "allow-auto-record-incoming"; +const String kOptionVideoSaveDirectory = "video-save-directory"; +const String kOptionAccessMode = "access-mode"; +const String kOptionEnableKeyboard = "enable-keyboard"; +// "Settings -> Security -> Permissions" +const String kOptionEnableClipboard = "enable-clipboard"; +const String kOptionEnableFileTransfer = "enable-file-transfer"; +const String kOptionEnableAudio = "enable-audio"; +const String kOptionEnableTunnel = "enable-tunnel"; +const String kOptionEnableRemoteRestart = "enable-remote-restart"; +const String kOptionEnableBlockInput = "enable-block-input"; +const String kOptionAllowRemoteConfigModification = + "allow-remote-config-modification"; +const String kOptionVerificationMethod = "verification-method"; +const String kOptionApproveMode = "approve-mode"; +const String kOptionCollapseToolbar = "collapse_toolbar"; +const String kOptionShowRemoteCursor = "show_remote_cursor"; +const String kOptionFollowRemoteCursor = "follow_remote_cursor"; +const String kOptionFollowRemoteWindow = "follow_remote_window"; +const String kOptionZoomCursor = "zoom-cursor"; +const String kOptionShowQualityMonitor = "show_quality_monitor"; +const String kOptionDisableAudio = "disable_audio"; +// "Settings -> Display -> Other default options" +const String kOptionDisableClipboard = "disable_clipboard"; +const String kOptionLockAfterSessionEnd = "lock_after_session_end"; +const String kOptionPrivacyMode = "privacy_mode"; +const String kOptionTouchMode = "touch-mode"; +const String kOptionI444 = "i444"; +const String kOptionSwapLeftRightMouse = 'swap-left-right-mouse'; +const String kOptionCodecPreference = 'codec-preference'; const String kUrlActionClose = "close"; @@ -208,12 +250,6 @@ const kRemoteImageQualityLow = 'low'; /// [kRemoteImageQualityCustom] Custom image quality. const kRemoteImageQualityCustom = 'custom'; -/// [kRemoteAudioGuestToHost] Guest to host audio mode(default). -const kRemoteAudioGuestToHost = 'guest-to-host'; - -/// [kRemoteAudioDualWay] dual-way audio mode(default). -const kRemoteAudioDualWay = 'dual-way'; - const kIgnoreDpi = true; // ================================ mobile ================================ diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 0ffcee0d0..26a69fa53 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -332,22 +332,23 @@ class _GeneralState extends State<_General> { setState(() {}); } + final isOptFixed = isOptionFixed(kCommConfKeyTheme); return _Card(title: 'Theme', children: [ _Radio(context, value: 'light', groupValue: current, label: 'Light', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: 'dark', groupValue: current, label: 'Dark', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: 'system', groupValue: current, label: 'Follow System', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), ]); } @@ -547,21 +548,24 @@ class _GeneralState extends State<_General> { )).marginOnly(left: 10), ), ElevatedButton( - onPressed: () async { - String? initialDirectory; - if (await Directory.fromUri(Uri.directory(user_dir)) - .exists()) { - initialDirectory = user_dir; - } - String? selectedDirectory = await FilePicker.platform - .getDirectoryPath(initialDirectory: initialDirectory); - if (selectedDirectory != null) { - await bind.mainSetOption( - key: 'video-save-directory', - value: selectedDirectory); - setState(() {}); - } - }, + onPressed: isOptionFixed(kOptionVideoSaveDirectory) + ? null + : () async { + String? initialDirectory; + if (await Directory.fromUri(Uri.directory(user_dir)) + .exists()) { + initialDirectory = user_dir; + } + String? selectedDirectory = + await FilePicker.platform.getDirectoryPath( + initialDirectory: initialDirectory); + if (selectedDirectory != null) { + await bind.mainSetOption( + key: kOptionVideoSaveDirectory, + value: selectedDirectory); + setState(() {}); + } + }, child: Text(translate('Change'))) .marginOnly(left: 5), ], @@ -580,12 +584,13 @@ class _GeneralState extends State<_General> { Map langsMap = {for (var v in langsList) v[0]: v[1]}; List keys = langsMap.keys.toList(); List values = langsMap.values.toList(); - keys.insert(0, ''); + keys.insert(0, defaultOptionLang); values.insert(0, translate('Default')); String currentKey = bind.mainGetLocalOption(key: kCommConfKeyLang); if (!keys.contains(currentKey)) { - currentKey = ''; + currentKey = defaultOptionLang; } + final isOptFixed = isOptionFixed(kCommConfKeyLang); return ComboBox( keys: keys, values: values, @@ -595,6 +600,7 @@ class _GeneralState extends State<_General> { reloadAllWindows(); bind.mainChangeLanguage(lang: key); }, + enabled: !isOptFixed, ).marginOnly(left: _kContentHMargin); }); } @@ -728,7 +734,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { return _Card(title: 'Permissions', children: [ ComboBox( keys: [ - '', + defaultOptionAccessMode, 'full', 'view', ], @@ -737,37 +743,39 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { translate('Full Access'), translate('Screen Share'), ], - enabled: enabled, + enabled: enabled && !isOptionFixed(kOptionAccessMode), initialKey: initialKey, onChanged: (mode) async { - await bind.mainSetOption(key: 'access-mode', value: mode); + await bind.mainSetOption(key: kOptionAccessMode, value: mode); setState(() {}); }).marginOnly(left: _kContentHMargin), Column( children: [ - _OptionCheckBox(context, 'Enable keyboard/mouse', 'enable-keyboard', + _OptionCheckBox( + context, 'Enable keyboard/mouse', kOptionEnableKeyboard, enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable clipboard', 'enable-clipboard', + _OptionCheckBox(context, 'Enable clipboard', kOptionEnableClipboard, enabled: enabled, fakeValue: fakeValue), _OptionCheckBox( - context, 'Enable file transfer', 'enable-file-transfer', + context, 'Enable file transfer', kOptionEnableFileTransfer, enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable audio', 'enable-audio', - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable TCP tunneling', 'enable-tunnel', + _OptionCheckBox(context, 'Enable audio', kOptionEnableAudio, enabled: enabled, fakeValue: fakeValue), _OptionCheckBox( - context, 'Enable remote restart', 'enable-remote-restart', + context, 'Enable TCP tunneling', kOptionEnableTunnel, enabled: enabled, fakeValue: fakeValue), _OptionCheckBox( - context, 'Enable recording session', 'enable-record-session', + context, 'Enable remote restart', kOptionEnableRemoteRestart, + enabled: enabled, fakeValue: fakeValue), + _OptionCheckBox( + context, 'Enable recording session', kOptionEnableRecordSession, enabled: enabled, fakeValue: fakeValue), if (isWindows) - _OptionCheckBox( - context, 'Enable blocking user input', 'enable-block-input', + _OptionCheckBox(context, 'Enable blocking user input', + kOptionEnableBlockInput, enabled: enabled, fakeValue: fakeValue), _OptionCheckBox(context, 'Enable remote configuration modification', - 'allow-remote-config-modification', + kOptionAllowRemoteConfigModification, enabled: enabled, fakeValue: fakeValue), ], ), @@ -801,14 +809,15 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { value: value, groupValue: currentValue, label: value, - onChanged: ((value) { - () async { - await model.setVerificationMethod( - passwordKeys[passwordValues.indexOf(value)]); - await model.updatePasswordModel(); - }(); - }), - enabled: !locked, + onChanged: locked + ? null + : ((value) { + () async { + await model.setVerificationMethod( + passwordKeys[passwordValues.indexOf(value)]); + await model.updatePasswordModel(); + }(); + }), )) .toList(); @@ -842,7 +851,11 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { )) .toList(); - final modeKeys = ['password', 'click', '']; + final modeKeys = [ + 'password', + 'click', + defaultOptionApproveMode + ]; final modeValues = [ translate('Accept sessions via password'), translate('Accept sessions via click'), @@ -852,9 +865,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { if (!modeKeys.contains(modeInitialKey)) modeInitialKey = ''; final usePassword = model.approveMode != 'click'; + final isApproveModeFixed = isOptionFixed(kOptionApproveMode); return _Card(title: 'Password', children: [ ComboBox( - enabled: !locked, + enabled: !locked && !isApproveModeFixed, keys: modeKeys, values: modeValues, initialKey: modeInitialKey, @@ -930,15 +944,17 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { update() => setState(() {}); RxBool applyEnabled = false.obs; return [ - _OptionCheckBox(context, 'Enable direct IP access', 'direct-server', + _OptionCheckBox(context, 'Enable direct IP access', kOptionDirectServer, update: update, enabled: !locked), () { // Simple temp wrapper for PR check tmpWrapper() { - bool enabled = option2bool( - 'direct-server', bind.mainGetOptionSync(key: 'direct-server')); + bool enabled = option2bool(kOptionDirectServer, + bind.mainGetOptionSync(key: kOptionDirectServer)); if (!enabled) applyEnabled.value = false; - controller.text = bind.mainGetOptionSync(key: 'direct-access-port'); + controller.text = + bind.mainGetOptionSync(key: kOptionDirectAccessPort); + final isOptFixed = isOptionFixed(kOptionDirectAccessPort); return Offstage( offstage: !enabled, child: _SubLabeledWidget( @@ -949,7 +965,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { width: 95, child: TextField( controller: controller, - enabled: enabled && !locked, + enabled: enabled && !locked && !isOptFixed, onChanged: (_) => applyEnabled.value = true, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp( @@ -963,11 +979,14 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { ).marginOnly(right: 15), ), Obx(() => ElevatedButton( - onPressed: applyEnabled.value && enabled && !locked + onPressed: applyEnabled.value && + enabled && + !locked && + !isOptFixed ? () async { applyEnabled.value = false; await bind.mainSetOption( - key: 'direct-access-port', + key: kOptionDirectAccessPort, value: controller.text); } : null, @@ -976,7 +995,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { ), )) ]), - enabled: enabled && !locked, + enabled: enabled && !locked && !isOptFixed, ), ); } @@ -990,17 +1009,19 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { bool enabled = !locked; // Simple temp wrapper for PR check tmpWrapper() { - RxBool hasWhitelist = - bind.mainGetOptionSync(key: 'whitelist').isNotEmpty.obs; + RxBool hasWhitelist = (bind.mainGetOptionSync(key: kOptionWhitelist) != + defaultOptionWhitelist) + .obs; update() async { - hasWhitelist.value = - bind.mainGetOptionSync(key: 'whitelist').isNotEmpty; + hasWhitelist.value = bind.mainGetOptionSync(key: kOptionWhitelist) != + defaultOptionWhitelist; } onChanged(bool? checked) async { changeWhiteList(callback: update); } + final isOptFixed = isOptionFixed(kOptionWhitelist); return GestureDetector( child: Tooltip( message: translate('whitelist_tip'), @@ -1008,13 +1029,16 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { children: [ Checkbox( value: hasWhitelist.value, - onChanged: enabled ? onChanged : null) + onChanged: enabled && !isOptFixed ? onChanged : null) .marginOnly(right: 5), Offstage( offstage: !hasWhitelist.value, - child: const Icon(Icons.warning_amber_rounded, - color: Color.fromARGB(255, 255, 204, 0)) - .marginOnly(right: 5), + child: MouseRegion( + child: const Icon(Icons.warning_amber_rounded, + color: Color.fromARGB(255, 255, 204, 0)) + .marginOnly(right: 5), + cursor: SystemMouseCursors.click, + ), ), Expanded( child: Text( @@ -1025,9 +1049,11 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { ], )), ), - onTap: () { - onChanged(!hasWhitelist.value); - }, + onTap: enabled + ? () { + onChanged(!hasWhitelist.value); + } + : null, ).marginOnly(left: _kCheckBoxLeftMargin); } @@ -1078,16 +1104,17 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { TextEditingController controller = TextEditingController(); update() => setState(() {}); RxBool applyEnabled = false.obs; - final optionKey = 'allow-auto-disconnect'; - final timeoutKey = 'auto-disconnect-timeout'; return [ - _OptionCheckBox(context, 'auto_disconnect_option_tip', optionKey, + _OptionCheckBox( + context, 'auto_disconnect_option_tip', kOptionAllowAutoDisconnect, update: update, enabled: !locked), () { - bool enabled = - option2bool(optionKey, bind.mainGetOptionSync(key: optionKey)); + bool enabled = option2bool(kOptionAllowAutoDisconnect, + bind.mainGetOptionSync(key: kOptionAllowAutoDisconnect)); if (!enabled) applyEnabled.value = false; - controller.text = bind.mainGetOptionSync(key: timeoutKey); + controller.text = + bind.mainGetOptionSync(key: kOptionAutoDisconnectTimeout); + final isOptFixed = isOptionFixed(kOptionAutoDisconnectTimeout); return Offstage( offstage: !enabled, child: _SubLabeledWidget( @@ -1098,7 +1125,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { width: 95, child: TextField( controller: controller, - enabled: enabled && !locked, + enabled: enabled && !locked && isOptFixed, onChanged: (_) => applyEnabled.value = true, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp( @@ -1112,19 +1139,21 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { ).marginOnly(right: 15), ), Obx(() => ElevatedButton( - onPressed: applyEnabled.value && enabled && !locked - ? () async { - applyEnabled.value = false; - await bind.mainSetOption( - key: timeoutKey, value: controller.text); - } - : null, + onPressed: + applyEnabled.value && enabled && !locked && !isOptFixed + ? () async { + applyEnabled.value = false; + await bind.mainSetOption( + key: kOptionAutoDisconnectTimeout, + value: controller.text); + } + : null, child: Text( translate('Apply'), ), )) ]), - enabled: enabled && !locked, + enabled: enabled && !locked && !isOptFixed, ), ); }(), @@ -1273,46 +1302,47 @@ class _DisplayState extends State<_Display> { } Widget viewStyle(BuildContext context) { - final key = 'view_style'; + final isOptFixed = isOptionFixed(kOptionViewStyle); onChanged(String value) async { - await bind.mainSetUserDefaultOption(key: key, value: value); + await bind.mainSetUserDefaultOption(key: kOptionViewStyle, value: value); setState(() {}); } - final groupValue = bind.mainGetUserDefaultOption(key: key); + final groupValue = bind.mainGetUserDefaultOption(key: kOptionViewStyle); return _Card(title: 'Default View Style', children: [ _Radio(context, value: kRemoteViewStyleOriginal, groupValue: groupValue, label: 'Scale original', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: kRemoteViewStyleAdaptive, groupValue: groupValue, label: 'Scale adaptive', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), ]); } Widget scrollStyle(BuildContext context) { - final key = 'scroll_style'; + final isOptFixed = isOptionFixed(kOptionScrollStyle); onChanged(String value) async { - await bind.mainSetUserDefaultOption(key: key, value: value); + await bind.mainSetUserDefaultOption( + key: kOptionScrollStyle, value: value); setState(() {}); } - final groupValue = bind.mainGetUserDefaultOption(key: key); + final groupValue = bind.mainGetUserDefaultOption(key: kOptionScrollStyle); return _Card(title: 'Default Scroll Style', children: [ _Radio(context, value: kRemoteScrollStyleAuto, groupValue: groupValue, label: 'ScrollAuto', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: kRemoteScrollStyleBar, groupValue: groupValue, label: 'Scrollbar', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), ]); } @@ -1323,28 +1353,29 @@ class _DisplayState extends State<_Display> { setState(() {}); } + final isOptFixed = isOptionFixed(key); final groupValue = bind.mainGetUserDefaultOption(key: key); return _Card(title: 'Default Image Quality', children: [ _Radio(context, value: kRemoteImageQualityBest, groupValue: groupValue, label: 'Good image quality', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: kRemoteImageQualityBalanced, groupValue: groupValue, label: 'Balanced', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: kRemoteImageQualityLow, groupValue: groupValue, label: 'Optimize reaction time', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: kRemoteImageQualityCustom, groupValue: groupValue, label: 'Custom', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), Offstage( offstage: groupValue != kRemoteImageQualityCustom, child: customImageQualitySetting(), @@ -1353,14 +1384,16 @@ class _DisplayState extends State<_Display> { } Widget codec(BuildContext context) { - final key = 'codec-preference'; onChanged(String value) async { - await bind.mainSetUserDefaultOption(key: key, value: value); + await bind.mainSetUserDefaultOption( + key: kOptionCodecPreference, value: value); setState(() {}); } - final groupValue = bind.mainGetUserDefaultOption(key: key); + final groupValue = + bind.mainGetUserDefaultOption(key: kOptionCodecPreference); var hwRadios = []; + final isOptFixed = isOptionFixed(kOptionCodecPreference); try { final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings()); final h264 = codecsJson['h264'] ?? false; @@ -1370,14 +1403,14 @@ class _DisplayState extends State<_Display> { value: 'h264', groupValue: groupValue, label: 'H264', - onChanged: onChanged)); + onChanged: isOptFixed ? null : onChanged)); } if (h265) { hwRadios.add(_Radio(context, value: 'h265', groupValue: groupValue, label: 'H265', - onChanged: onChanged)); + onChanged: isOptFixed ? null : onChanged)); } } catch (e) { debugPrint("failed to parse supported hwdecodings, err=$e"); @@ -1387,22 +1420,22 @@ class _DisplayState extends State<_Display> { value: 'auto', groupValue: groupValue, label: 'Auto', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: 'vp8', groupValue: groupValue, label: 'VP8', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: 'vp9', groupValue: groupValue, label: 'VP9', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), _Radio(context, value: 'av1', groupValue: groupValue, label: 'AV1', - onChanged: onChanged), + onChanged: isOptFixed ? null : onChanged), ...hwRadios, ]); } @@ -1445,22 +1478,29 @@ class _DisplayState extends State<_Display> { Widget otherRow(String label, String key) { final value = bind.mainGetUserDefaultOption(key: key) == 'Y'; + final isOptFixed = isOptionFixed(key); onChanged(bool b) async { - await bind.mainSetUserDefaultOption(key: key, value: b ? 'Y' : ''); + await bind.mainSetUserDefaultOption( + key: key, + value: b + ? 'Y' + : (key == kOptionEnableFileTransfer ? 'N' : defaultOptionNo)); setState(() {}); } return GestureDetector( child: Row( children: [ - Checkbox(value: value, onChanged: (_) => onChanged(!value)) + Checkbox( + value: value, + onChanged: isOptFixed ? null : (_) => onChanged(!value)) .marginOnly(right: 5), Expanded( child: Text(translate(label)), ) ], ).marginOnly(left: _kCheckBoxLeftMargin), - onTap: () => onChanged(!value)); + onTap: isOptFixed ? null : () => onChanged(!value)); } Widget other(BuildContext context) { @@ -1772,6 +1812,7 @@ Widget _OptionCheckBox(BuildContext context, String label, String key, bool isServer = true}) { bool value = isServer ? mainGetBoolOptionSync(key) : mainGetLocalBoolOptionSync(key); + final isOptFixed = isOptionFixed(key); if (reverse) value = !value; var ref = value.obs; onChanged(option) async { @@ -1801,7 +1842,9 @@ Widget _OptionCheckBox(BuildContext context, String label, String key, child: Obx( () => Row( children: [ - Checkbox(value: ref.value, onChanged: enabled ? onChanged : null) + Checkbox( + value: ref.value, + onChanged: enabled && !isOptFixed ? onChanged : null) .marginOnly(right: 5), Offstage( offstage: !ref.value || checkedIcon == null, @@ -1815,7 +1858,7 @@ Widget _OptionCheckBox(BuildContext context, String label, String key, ], ), ).marginOnly(left: _kCheckBoxLeftMargin), - onTap: enabled + onTap: enabled && !isOptFixed ? () { onChanged(!ref.value); } @@ -1828,10 +1871,9 @@ Widget _Radio(BuildContext context, {required T value, required T groupValue, required String label, - required Function(T value) onChanged, - bool autoNewLine = true, - bool enabled = true}) { - var onChange = enabled + required Function(T value)? onChanged, + bool autoNewLine = true}) { + final onChange2 = onChanged != null ? (T? value) { if (value != null) { onChanged(value); @@ -1841,18 +1883,18 @@ Widget _Radio(BuildContext context, return GestureDetector( child: Row( children: [ - Radio(value: value, groupValue: groupValue, onChanged: onChange), + Radio(value: value, groupValue: groupValue, onChanged: onChange2), Expanded( child: Text(translate(label), overflow: autoNewLine ? null : TextOverflow.ellipsis, style: TextStyle( fontSize: _kContentFontSize, - color: disabledTextColor(context, enabled))) + color: disabledTextColor(context, onChange2 != null))) .marginOnly(left: 5), ), ], ).marginOnly(left: _kRadioLeftMargin), - onTap: () => onChange?.call(value), + onTap: () => onChange2?.call(value), ); } diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 350bdf79d..31063775b 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -428,7 +428,7 @@ class _ConnectionTabPageState extends State { } ConnectionTypeState.init(id); _toolbarState.setShow( - bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y'); + bind.mainGetUserDefaultOption(key: kOptionCollapseToolbar) != 'Y'); tabController.add(TabInfo( key: id, label: id, diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 4ef62e493..fca64f832 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -54,7 +54,7 @@ class ToolbarState { _initSet(bool s, bool p) { // Show remubar when connection is established. show = - RxBool(bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y'); + RxBool(bind.mainGetUserDefaultOption(key: kOptionCollapseToolbar) != 'Y'); _pin = RxBool(p); } diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index 7b011f5f8..b5bd1e4f2 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -39,6 +39,7 @@ class ServerPage extends StatefulWidget implements PageShape { final approveMode = gFFI.serverModel.approveMode; final verificationMethod = gFFI.serverModel.verificationMethod; final showPasswordOption = approveMode != 'click'; + final isApproveModeFixed = isOptionFixed(kOptionApproveMode); return [ PopupMenuItem( enabled: gFFI.serverModel.connectStatus > 0, @@ -50,16 +51,19 @@ class ServerPage extends StatefulWidget implements PageShape { value: 'AcceptSessionsViaPassword', child: listTile( 'Accept sessions via password', approveMode == 'password'), + enabled: !isApproveModeFixed, ), PopupMenuItem( value: 'AcceptSessionsViaClick', child: listTile('Accept sessions via click', approveMode == 'click'), + enabled: !isApproveModeFixed, ), PopupMenuItem( value: "AcceptSessionsViaBoth", child: listTile("Accept sessions via both", approveMode != 'password' && approveMode != 'click'), + enabled: !isApproveModeFixed, ), if (showPasswordOption) const PopupMenuDivider(), if (showPasswordOption && @@ -116,7 +120,7 @@ class ServerPage extends StatefulWidget implements PageShape { } else if (value == "Click") { gFFI.serverModel.setApproveMode('click'); } else { - gFFI.serverModel.setApproveMode(''); + gFFI.serverModel.setApproveMode(defaultOptionApproveMode); } } }) diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 48c150a40..3585beb8d 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -100,8 +100,8 @@ class _SettingsState extends State with WidgetsBindingObserver { _denyLANDiscovery = denyLanDiscovery; } - final onlyWhiteList = - (await bind.mainGetOption(key: 'whitelist')).isNotEmpty; + final onlyWhiteList = (await bind.mainGetOption(key: kOptionWhitelist)) != + defaultOptionWhitelist; if (onlyWhiteList != _onlyWhiteList) { update = true; _onlyWhiteList = onlyWhiteList; @@ -143,7 +143,7 @@ class _SettingsState extends State with WidgetsBindingObserver { } final directAccessPort = - await bind.mainGetOption(key: 'direct-access-port'); + await bind.mainGetOption(key: kOptionDirectAccessPort); if (directAccessPort != _directAccessPort) { update = true; _directAccessPort = directAccessPort; @@ -257,16 +257,18 @@ class _SettingsState extends State with WidgetsBindingObserver { SettingsTile.switchTile( title: Text(translate('Deny LAN discovery')), initialValue: _denyLANDiscovery, - onToggle: (v) async { - await bind.mainSetOption( - key: "enable-lan-discovery", - value: bool2option("enable-lan-discovery", !v)); - final newValue = !option2bool('enable-lan-discovery', - await bind.mainGetOption(key: 'enable-lan-discovery')); - setState(() { - _denyLANDiscovery = newValue; - }); - }, + onToggle: isOptionFixed(kOptionEnableLanDiscovery) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionEnableLanDiscovery, + value: bool2option(kOptionEnableLanDiscovery, !v)); + final newValue = !option2bool(kOptionEnableLanDiscovery, + await bind.mainGetOption(key: kOptionEnableLanDiscovery)); + setState(() { + _denyLANDiscovery = newValue; + }); + }, ), SettingsTile.switchTile( title: Row(children: [ @@ -279,42 +281,51 @@ class _SettingsState extends State with WidgetsBindingObserver { ]), initialValue: _onlyWhiteList, onToggle: (_) async { - update() async { - final onlyWhiteList = - (await bind.mainGetOption(key: 'whitelist')).isNotEmpty; - if (onlyWhiteList != _onlyWhiteList) { - setState(() { - _onlyWhiteList = onlyWhiteList; - }); - } - } + update() async { + final onlyWhiteList = + (await bind.mainGetOption(key: kOptionWhitelist)) != + defaultOptionWhitelist; + if (onlyWhiteList != _onlyWhiteList) { + setState(() { + _onlyWhiteList = onlyWhiteList; + }); + } + } - changeWhiteList(callback: update); - }, + changeWhiteList(callback: update); + }, ), SettingsTile.switchTile( title: Text('${translate('Adaptive bitrate')} (beta)'), initialValue: _enableAbr, - onToggle: (v) async { - await bind.mainSetOption(key: "enable-abr", value: v ? "" : "N"); - final newValue = await bind.mainGetOption(key: "enable-abr") != "N"; - setState(() { - _enableAbr = newValue; - }); - }, + onToggle: isOptionFixed(kOptionEnableAbr) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionEnableAbr, value: v ? defaultOptionYes : "N"); + final newValue = + await bind.mainGetOption(key: kOptionEnableAbr) != "N"; + setState(() { + _enableAbr = newValue; + }); + }, ), SettingsTile.switchTile( title: Text(translate('Enable recording session')), initialValue: _enableRecordSession, - onToggle: (v) async { - await bind.mainSetOption( - key: "enable-record-session", value: v ? "" : "N"); - final newValue = - await bind.mainGetOption(key: "enable-record-session") != "N"; - setState(() { - _enableRecordSession = newValue; - }); - }, + onToggle: isOptionFixed(kOptionEnableRecordSession) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionEnableRecordSession, + value: v ? defaultOptionYes : "N"); + final newValue = + await bind.mainGetOption(key: kOptionEnableRecordSession) != + "N"; + setState(() { + _enableRecordSession = newValue; + }); + }, ), SettingsTile.switchTile( title: Row( @@ -341,21 +352,27 @@ class _SettingsState extends State with WidgetsBindingObserver { Icons.edit, size: 20, ), - onPressed: () async { - final port = await changeDirectAccessPort( - _localIP, _directAccessPort); - setState(() { - _directAccessPort = port; - }); - })) + onPressed: isOptionFixed(kOptionDirectAccessPort) + ? null + : () async { + final port = await changeDirectAccessPort( + _localIP, _directAccessPort); + setState(() { + _directAccessPort = port; + }); + })) ]), initialValue: _enableDirectIPAccess, - onToggle: (_) async { - _enableDirectIPAccess = !_enableDirectIPAccess; - String value = bool2option('direct-server', _enableDirectIPAccess); - await bind.mainSetOption(key: 'direct-server', value: value); - setState(() {}); - }, + onToggle: isOptionFixed(kOptionDirectServer) + ? null + : (_) async { + _enableDirectIPAccess = !_enableDirectIPAccess; + String value = + bool2option(kOptionDirectServer, _enableDirectIPAccess); + await bind.mainSetOption( + key: kOptionDirectServer, value: value); + setState(() {}); + }, ), SettingsTile.switchTile( title: Row( @@ -382,22 +399,27 @@ class _SettingsState extends State with WidgetsBindingObserver { Icons.edit, size: 20, ), - onPressed: () async { - final timeout = await changeAutoDisconnectTimeout( - _autoDisconnectTimeout); - setState(() { - _autoDisconnectTimeout = timeout; - }); - })) + onPressed: isOptionFixed(kOptionAutoDisconnectTimeout) + ? null + : () async { + final timeout = await changeAutoDisconnectTimeout( + _autoDisconnectTimeout); + setState(() { + _autoDisconnectTimeout = timeout; + }); + })) ]), initialValue: _allowAutoDisconnect, - onToggle: (_) async { - _allowAutoDisconnect = !_allowAutoDisconnect; - String value = - bool2option('allow-auto-disconnect', _allowAutoDisconnect); - await bind.mainSetOption(key: 'allow-auto-disconnect', value: value); - setState(() {}); - }, + onToggle: isOptionFixed(kOptionAllowAutoDisconnect) + ? null + : (_) async { + _allowAutoDisconnect = !_allowAutoDisconnect; + String value = bool2option( + kOptionAllowAutoDisconnect, _allowAutoDisconnect); + await bind.mainSetOption( + key: kOptionAllowAutoDisconnect, value: value); + setState(() {}); + }, ) ]; if (_hasIgnoreBattery) { @@ -526,15 +548,19 @@ class _SettingsState extends State with WidgetsBindingObserver { SettingsTile.switchTile( title: Text(translate('Enable hardware codec')), initialValue: _enableHardwareCodec, - onToggle: (v) async { - await bind.mainSetOption( - key: "enable-hwcodec", value: v ? "" : "N"); - final newValue = - await bind.mainGetOption(key: "enable-hwcodec") != "N"; - setState(() { - _enableHardwareCodec = newValue; - }); - }, + onToggle: isOptionFixed(kOptionEnableHwcodec) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionEnableHwcodec, + value: v ? defaultOptionYes : "N"); + final newValue = + await bind.mainGetOption(key: kOptionEnableHwcodec) != + "N"; + setState(() { + _enableHardwareCodec = newValue; + }); + }, ), ]), if (isAndroid && !outgoingOnly) @@ -551,18 +577,21 @@ class _SettingsState extends State with WidgetsBindingObserver { child: Text("${translate("Directory")}: ${data.data}")), future: bind.mainVideoSaveDirectory(root: false)), initialValue: _autoRecordIncomingSession, - onToggle: (v) async { - await bind.mainSetOption( - key: "allow-auto-record-incoming", - value: bool2option("allow-auto-record-incoming", v)); - final newValue = option2bool( - 'allow-auto-record-incoming', - await bind.mainGetOption( - key: 'allow-auto-record-incoming')); - setState(() { - _autoRecordIncomingSession = newValue; - }); - }, + onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionAllowAutoRecordIncoming, + value: + bool2option(kOptionAllowAutoRecordIncoming, v)); + final newValue = option2bool( + kOptionAllowAutoRecordIncoming, + await bind.mainGetOption( + key: kOptionAllowAutoRecordIncoming)); + setState(() { + _autoRecordIncomingSession = newValue; + }); + }, ), ], ), @@ -661,29 +690,32 @@ void showServerSettings(OverlayDialogManager dialogManager) async { void showLanguageSettings(OverlayDialogManager dialogManager) async { try { final langs = json.decode(await bind.mainGetLangs()) as List; - var lang = bind.mainGetLocalOption(key: "lang"); + var lang = bind.mainGetLocalOption(key: kCommConfKeyLang); dialogManager.show((setState, close, context) { setLang(v) async { if (lang != v) { setState(() { lang = v; }); - await bind.mainSetLocalOption(key: "lang", value: v); + await bind.mainSetLocalOption(key: kCommConfKeyLang, value: v); HomePage.homeKey.currentState?.refreshPages(); Future.delayed(Duration(milliseconds: 200), close); } } + final isOptFixed = isOptionFixed(kCommConfKeyLang); return CustomAlertDialog( content: Column( children: [ - getRadio(Text(translate('Default')), '', lang, setLang), + getRadio(Text(translate('Default')), defaultOptionLang, lang, + isOptFixed ? null : setLang), Divider(color: MyTheme.border), ] + langs.map((e) { final key = e[0] as String; final name = e[1] as String; - return getRadio(Text(translate(name)), key, lang, setLang); + return getRadio(Text(translate(name)), key, lang, + isOptFixed ? null : setLang); }).toList(), ), ); @@ -707,13 +739,15 @@ void showThemeSettings(OverlayDialogManager dialogManager) async { } } + final isOptFixed = isOptionFixed(kCommConfKeyTheme); return CustomAlertDialog( content: Column(children: [ - getRadio( - Text(translate('Light')), ThemeMode.light, themeMode, setTheme), - getRadio(Text(translate('Dark')), ThemeMode.dark, themeMode, setTheme), + getRadio(Text(translate('Light')), ThemeMode.light, themeMode, + isOptFixed ? null : setTheme), + getRadio(Text(translate('Dark')), ThemeMode.dark, themeMode, + isOptFixed ? null : setTheme), getRadio(Text(translate('Follow System')), ThemeMode.system, themeMode, - setTheme) + isOptFixed ? null : setTheme) ]), ); }, backDismiss: true, clickMaskDismiss: true); @@ -801,11 +835,14 @@ class __DisplayPageState extends State<_DisplayPage> { _RadioEntry('Scale original', kRemoteViewStyleOriginal), _RadioEntry('Scale adaptive', kRemoteViewStyleAdaptive) ], - getter: () => bind.mainGetUserDefaultOption(key: 'view_style'), - asyncSetter: (value) async { - await bind.mainSetUserDefaultOption( - key: 'view_style', value: value); - }, + getter: () => + bind.mainGetUserDefaultOption(key: kOptionViewStyle), + asyncSetter: isOptionFixed(kOptionViewStyle) + ? null + : (value) async { + await bind.mainSetUserDefaultOption( + key: kOptionViewStyle, value: value); + }, ), _getPopupDialogRadioEntry( title: 'Default Image Quality', @@ -816,16 +853,19 @@ class __DisplayPageState extends State<_DisplayPage> { _RadioEntry('Custom', kRemoteImageQualityCustom), ], getter: () { - final v = bind.mainGetUserDefaultOption(key: 'image_quality'); + final v = + bind.mainGetUserDefaultOption(key: kOptionImageQuality); showCustomImageQuality.value = v == kRemoteImageQualityCustom; return v; }, - asyncSetter: (value) async { - await bind.mainSetUserDefaultOption( - key: 'image_quality', value: value); - showCustomImageQuality.value = - value == kRemoteImageQualityCustom; - }, + asyncSetter: isOptionFixed(kOptionImageQuality) + ? null + : (value) async { + await bind.mainSetUserDefaultOption( + key: kOptionImageQuality, value: value); + showCustomImageQuality.value = + value == kRemoteImageQualityCustom; + }, tail: customImageQualitySetting(), showTail: showCustomImageQuality, notCloseValue: kRemoteImageQualityCustom, @@ -834,11 +874,13 @@ class __DisplayPageState extends State<_DisplayPage> { title: 'Default Codec', list: codecList, getter: () => - bind.mainGetUserDefaultOption(key: 'codec-preference'), - asyncSetter: (value) async { - await bind.mainSetUserDefaultOption( - key: 'codec-preference', value: value); - }, + bind.mainGetUserDefaultOption(key: kOptionCodecPreference), + asyncSetter: isOptionFixed(kOptionCodecPreference) + ? null + : (value) async { + await bind.mainSetUserDefaultOption( + key: kOptionCodecPreference, value: value); + }, ), ], ), @@ -853,13 +895,17 @@ class __DisplayPageState extends State<_DisplayPage> { SettingsTile otherRow(String label, String key) { final value = bind.mainGetUserDefaultOption(key: key) == 'Y'; + final isOptFixed = isOptionFixed(key); return SettingsTile.switchTile( initialValue: value, title: Text(translate(label)), - onToggle: (b) async { - await bind.mainSetUserDefaultOption(key: key, value: b ? 'Y' : ''); - setState(() {}); - }, + onToggle: isOptFixed + ? null + : (b) async { + await bind.mainSetUserDefaultOption( + key: key, value: b ? 'Y' : defaultOptionNo); + setState(() {}); + }, ); } } @@ -877,7 +923,7 @@ _getPopupDialogRadioEntry({ required String title, required List<_RadioEntry> list, required _RadioEntryGetter getter, - required _RadioEntrySetter asyncSetter, + required _RadioEntrySetter? asyncSetter, Widget? tail, RxBool? showTail, String? notCloseValue, @@ -897,21 +943,23 @@ _getPopupDialogRadioEntry({ void showDialog() async { gFFI.dialogManager.show((setState, close, context) { - onChanged(String? value) async { - if (value == null) return; - await asyncSetter(value); - init(); - if (value != notCloseValue) { - close(); - } - } + final onChanged = asyncSetter == null + ? null + : (String? value) async { + if (value == null) return; + await asyncSetter(value); + init(); + if (value != notCloseValue) { + close(); + } + }; return CustomAlertDialog( content: Obx( () => Column(children: [ ...list .map((e) => getRadio(Text(translate(e.label)), e.value, - groupValue.value, (String? value) => onChanged(value))) + groupValue.value, onChanged)) .toList(), Offstage( offstage: diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 4dfec2690..c3852bd2b 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -509,7 +509,7 @@ class AbModel { } void setShouldAsync(bool v) async { - await bind.mainSetLocalOption(key: syncAbOption, value: v ? 'Y' : ''); + await bind.mainSetLocalOption(key: syncAbOption, value: v ? 'Y' : defaultOptionNo); _syncAllFromRecent = true; _timerCounter = 0; } diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 5fc669756..f495c5519 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -77,7 +77,7 @@ class ServerModel with ChangeNotifier { String get approveMode => _approveMode; setVerificationMethod(String method) async { - await bind.mainSetOption(key: "verification-method", value: method); + await bind.mainSetOption(key: kOptionVerificationMethod, value: method); /* if (method != kUsePermanentPassword) { await bind.mainSetOption( @@ -99,7 +99,7 @@ class ServerModel with ChangeNotifier { } setApproveMode(String mode) async { - await bind.mainSetOption(key: 'approve-mode', value: mode); + await bind.mainSetOption(key: kOptionApproveMode, value: mode); /* if (mode != 'password') { await bind.mainSetOption( @@ -283,7 +283,7 @@ class ServerModel with ChangeNotifier { } _audioOk = !_audioOk; - bind.mainSetOption(key: "enable-audio", value: _audioOk ? '' : 'N'); + bind.mainSetOption(key: "enable-audio", value: _audioOk ? defaultOptionYes : 'N'); notifyListeners(); } @@ -302,7 +302,7 @@ class ServerModel with ChangeNotifier { } _fileOk = !_fileOk; - bind.mainSetOption(key: "enable-file-transfer", value: _fileOk ? '' : 'N'); + bind.mainSetOption(key: kOptionEnableFileTransfer, value: _fileOk ? defaultOptionYes : 'N'); notifyListeners(); } @@ -312,7 +312,7 @@ class ServerModel with ChangeNotifier { } if (_inputOk) { parent.target?.invokeMethod("stop_input"); - bind.mainSetOption(key: "enable-keyboard", value: 'N'); + bind.mainSetOption(key: kOptionEnableKeyboard, value: 'N'); } else { if (parent.target != null) { /// the result of toggle-on depends on user actions in the settings page. @@ -445,7 +445,7 @@ class ServerModel with ChangeNotifier { break; case "input": if (_inputOk != value) { - bind.mainSetOption(key: "enable-keyboard", value: value ? '' : 'N'); + bind.mainSetOption(key: kOptionEnableKeyboard, value: value ? defaultOptionYes : 'N'); } _inputOk = value; break; diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 7cf6aca3f..540867ec9 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -1610,5 +1610,9 @@ class RustdeskImpl { throw UnimplementedError(); } + bool mainIsOptionFixed({required String key, dynamic hint}) { + throw UnimplementedError(); + } + void dispose() {} } diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 1660fbb92..2a76f6926 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -916,7 +916,7 @@ impl Config { #[inline] fn purify_options(v: &mut HashMap) { - v.retain(|k, v| is_option_can_save(&OVERWRITE_SETTINGS, &DEFAULT_SETTINGS, k, v)); + v.retain(|k, _| is_option_can_save(&OVERWRITE_SETTINGS, k)); } pub fn set_options(mut v: HashMap) { @@ -940,7 +940,7 @@ impl Config { } pub fn set_option(k: String, v: String) { - if !is_option_can_save(&OVERWRITE_SETTINGS, &DEFAULT_SETTINGS, &k, &v) { + if !is_option_can_save(&OVERWRITE_SETTINGS, &k) { return; } let mut config = CONFIG2.write().unwrap(); @@ -1194,36 +1194,36 @@ impl PeerConfig { serde_field_string!( default_view_style, deserialize_view_style, - UserDefaultConfig::read("view_style") + UserDefaultConfig::read(keys::OPTION_VIEW_STYLE) ); serde_field_string!( default_scroll_style, deserialize_scroll_style, - UserDefaultConfig::read("scroll_style") + UserDefaultConfig::read(keys::OPTION_SCROLL_STYLE) ); serde_field_string!( default_image_quality, deserialize_image_quality, - UserDefaultConfig::read("image_quality") + UserDefaultConfig::read(keys::OPTION_IMAGE_QUALITY) ); serde_field_string!( default_reverse_mouse_wheel, deserialize_reverse_mouse_wheel, - UserDefaultConfig::read("reverse_mouse_wheel") + UserDefaultConfig::read(keys::OPTION_REVERSE_MOUSE_WHEEL) ); serde_field_string!( default_displays_as_individual_windows, deserialize_displays_as_individual_windows, - UserDefaultConfig::read("displays_as_individual_windows") + UserDefaultConfig::read(keys::OPTION_DISPLAYS_AS_INDIVIDUAL_WINDOWS) ); serde_field_string!( default_use_all_my_displays_for_the_remote_session, deserialize_use_all_my_displays_for_the_remote_session, - UserDefaultConfig::read("use_all_my_displays_for_the_remote_session") + UserDefaultConfig::read(keys::OPTION_USE_ALL_MY_DISPLAYS_FOR_THE_REMOTE_SESSION) ); fn default_custom_image_quality() -> Vec { - let f: f64 = UserDefaultConfig::read("custom_image_quality") + let f: f64 = UserDefaultConfig::read(keys::OPTION_CUSTOM_IMAGE_QUALITY) .parse() .unwrap_or(50.0); vec![f as _] @@ -1244,12 +1244,12 @@ impl PeerConfig { fn default_options() -> HashMap { let mut mp: HashMap = Default::default(); [ - "codec-preference", - "custom-fps", - "zoom-cursor", - "touch-mode", - "i444", - "swap-left-right-mouse", + keys::OPTION_CODEC_PREFERENCE, + keys::OPTION_CUSTOM_FPS, + keys::OPTION_ZOOM_CURSOR, + keys::OPTION_TOUCH_MODE, + keys::OPTION_I444, + keys::OPTION_SWAP_LEFT_RIGHT_MOUSE, ] .map(|key| { mp.insert(key.to_owned(), UserDefaultConfig::read(key)); @@ -1415,10 +1415,17 @@ impl LocalConfig { } pub fn set_option(k: String, v: String) { - if !is_option_can_save(&OVERWRITE_LOCAL_SETTINGS, &DEFAULT_LOCAL_SETTINGS, &k, &v) { + if !is_option_can_save(&OVERWRITE_LOCAL_SETTINGS, &k) { return; } let mut config = LOCAL_CONFIG.write().unwrap(); + // The custom client will explictly set "default" as the default language. + let is_custom_client_default_lang = k == keys::OPTION_LANGUAGE && v == "default"; + if is_custom_client_default_lang { + config.options.insert(k, "".to_owned()); + config.store(); + return; + } let v2 = if v.is_empty() { None } else { Some(&v) }; if v2 != config.options.get(&k) { if v2.is_none() { @@ -1572,15 +1579,19 @@ impl UserDefaultConfig { pub fn get(&self, key: &str) -> String { match key { - "view_style" => self.get_string(key, "original", vec!["adaptive"]), - "scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]), - "image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]), - "codec-preference" => { + keys::OPTION_VIEW_STYLE => self.get_string(key, "original", vec!["adaptive"]), + keys::OPTION_SCROLL_STYLE => self.get_string(key, "scrollauto", vec!["scrollbar"]), + keys::OPTION_IMAGE_QUALITY => { + self.get_string(key, "balanced", vec!["best", "low", "custom"]) + } + keys::OPTION_CODEC_PREFERENCE => { self.get_string(key, "auto", vec!["vp8", "vp9", "av1", "h264", "h265"]) } - "custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 0xFFF as f64), - "custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0), - "enable_file_transfer" => self.get_string(key, "Y", vec![""]), + keys::OPTION_CUSTOM_IMAGE_QUALITY => { + self.get_double_string(key, 50.0, 10.0, 0xFFF as f64) + } + keys::OPTION_CUSTOM_FPS => self.get_double_string(key, 30.0, 5.0, 120.0), + keys::OPTION_ENABLE_FILE_TRANSFER => self.get_string(key, "Y", vec!["", "N"]), _ => self .get_after(key) .map(|v| v.to_string()) @@ -1589,12 +1600,7 @@ impl UserDefaultConfig { } pub fn set(&mut self, key: String, value: String) { - if !is_option_can_save( - &OVERWRITE_DISPLAY_SETTINGS, - &DEFAULT_DISPLAY_SETTINGS, - &key, - &value, - ) { + if !is_option_can_save(&OVERWRITE_DISPLAY_SETTINGS, &key) { return; } if value.is_empty() { @@ -1917,18 +1923,10 @@ fn get_or( } #[inline] -fn is_option_can_save( - overwrite: &RwLock>, - defaults: &RwLock>, - k: &str, - v: &str, -) -> bool { +fn is_option_can_save(overwrite: &RwLock>, k: &str) -> bool { if overwrite.read().unwrap().contains_key(k) { return false; } - if defaults.read().unwrap().get(k).map_or(false, |x| x == v) { - return false; - } true } @@ -1984,6 +1982,136 @@ pub fn is_disable_installation() -> bool { is_some_hard_opton("disable-installation") } +pub mod keys { + pub const OPTION_VIEW_ONLY: &str = "view_only"; + pub const OPTION_SHOW_MONITORS_TOOLBAR: &str = "show_monitors_toolbar"; + pub const OPTION_COLLAPSE_TOOLBAR: &str = "collapse_toolbar"; + pub const OPTION_SHOW_REMOTE_CURSOR: &str = "show_remote_cursor"; + pub const OPTION_FOLLOW_REMOTE_CURSOR: &str = "follow_remote_cursor"; + pub const OPTION_FOLLOW_REMOTE_WINDOW: &str = "follow_remote_window"; + pub const OPTION_ZOOM_CURSOR: &str = "zoom-cursor"; + pub const OPTION_SHOW_QUALITY_MONITOR: &str = "show_quality_monitor"; + pub const OPTION_DISABLE_AUDIO: &str = "disable_audio"; + pub const OPTION_DISABLE_CLIPBOARD: &str = "disable_clipboard"; + pub const OPTION_LOCK_AFTER_SESSION_END: &str = "lock_after_session_end"; + pub const OPTION_PRIVACY_MODE: &str = "privacy_mode"; + pub const OPTION_TOUCH_MODE: &str = "touch-mode"; + pub const OPTION_I444: &str = "i444"; + pub const OPTION_REVERSE_MOUSE_WHEEL: &str = "reverse_mouse_wheel"; + pub const OPTION_SWAP_LEFT_RIGHT_MOUSE: &str = "swap-left-right-mouse"; + pub const OPTION_DISPLAYS_AS_INDIVIDUAL_WINDOWS: &str = "displays_as_individual_windows"; + pub const OPTION_USE_ALL_MY_DISPLAYS_FOR_THE_REMOTE_SESSION: &str = + "use_all_my_displays_for_the_remote_session"; + pub const OPTION_VIEW_STYLE: &str = "view_style"; + pub const OPTION_SCROLL_STYLE: &str = "scroll_style"; + pub const OPTION_IMAGE_QUALITY: &str = "image_quality"; + pub const OPTION_CUSTOM_IMAGE_QUALITY: &str = "custom_image_quality"; + pub const OPTION_CUSTOM_FPS: &str = "custom-fps"; + pub const OPTION_CODEC_PREFERENCE: &str = "codec-preference"; + pub const OPTION_THEME: &str = "theme"; + pub const OPTION_LANGUAGE: &str = "lang"; + pub const OPTION_ENABLE_CONFIRM_CLOSING_TABS: &str = "enable-confirm-closing-tabs"; + pub const OPTION_ENABLE_OPEN_NEW_CONNECTIONS_IN_TABS: &str = + "enable-open-new-connections-in-tabs"; + pub const OPTION_ENABLE_CHECK_UPDATE: &str = "enable-check-update"; + pub const OPTION_SYNC_AB_WITH_RECENT_SESSIONS: &str = "sync-ab-with-recent-sessions"; + pub const OPTION_SYNC_AB_TAGS: &str = "sync-ab-tags"; + pub const OPTION_FILTER_AB_BY_INTERSECTION: &str = "filter-ab-by-intersection"; + pub const OPTION_ACCESS_MODE: &str = "access-mode"; + pub const OPTION_ENABLE_KEYBOARD: &str = "enable-keyboard"; + pub const OPTION_ENABLE_CLIPBOARD: &str = "enable-clipboard"; + pub const OPTION_ENABLE_FILE_TRANSFER: &str = "enable-file-transfer"; + pub const OPTION_ENABLE_AUDIO: &str = "enable-audio"; + pub const OPTION_ENABLE_TUNNEL: &str = "enable-tunnel"; + pub const OPTION_ENABLE_REMOTE_RESTART: &str = "enable-remote-restart"; + pub const OPTION_ENABLE_RECORD_SESSION: &str = "enable-record-session"; + pub const OPTION_ENABLE_BLOCK_INPUT: &str = "enable-block-input"; + pub const OPTION_ALLOW_REMOTE_CONFIG_MODIFICATION: &str = "allow-remote-config-modification"; + pub const OPTION_ENABLE_LAN_DISCOVERY: &str = "enable-lan-discovery"; + pub const OPTION_DIRECT_SERVER: &str = "direct-server"; + pub const OPTION_DIRECT_ACCESS_PORT: &str = "direct-access-port"; + pub const OPTION_WHITELIST: &str = "whitelist"; + pub const OPTION_ALLOW_AUTO_DISCONNECT: &str = "allow-auto-disconnect"; + pub const OPTION_AUTO_DISCONNECT_TIMEOUT: &str = "auto-disconnect-timeout"; + pub const OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN: &str = "allow-only-conn-window-open"; + pub const OPTION_ALLOW_AUTO_RECORD_INCOMING: &str = "allow-auto-record-incoming"; + pub const OPTION_VIDEO_SAVE_DIRECTORY: &str = "video-save-directory"; + pub const OPTION_ENABLE_ABR: &str = "enable-abr"; + pub const OPTION_ALLOW_REMOVE_WALLPAPER: &str = "allow-remove-wallpaper"; + pub const OPTION_ALLOW_ALWAYS_SOFTWARE_RENDER: &str = "allow-always-software-render"; + pub const OPTION_ALLOW_LINUX_HEADLESS: &str = "allow-linux-headless"; + pub const OPTION_ENABLE_HWCODEC: &str = "enable-hwcodec"; + pub const OPTION_APPROVE_MODE: &str = "approve-mode"; + + // DEFAULT_DISPLAY_SETTINGS, OVERWRITE_DISPLAY_SETTINGS + pub const KEYS_DISPLAY_SETTINGS: &[&str] = &[ + OPTION_VIEW_ONLY, + OPTION_SHOW_MONITORS_TOOLBAR, + OPTION_COLLAPSE_TOOLBAR, + OPTION_SHOW_REMOTE_CURSOR, + OPTION_FOLLOW_REMOTE_CURSOR, + OPTION_FOLLOW_REMOTE_WINDOW, + OPTION_ZOOM_CURSOR, + OPTION_SHOW_QUALITY_MONITOR, + OPTION_DISABLE_AUDIO, + OPTION_ENABLE_FILE_TRANSFER, + OPTION_DISABLE_CLIPBOARD, + OPTION_LOCK_AFTER_SESSION_END, + OPTION_PRIVACY_MODE, + OPTION_TOUCH_MODE, + OPTION_I444, + OPTION_REVERSE_MOUSE_WHEEL, + OPTION_SWAP_LEFT_RIGHT_MOUSE, + OPTION_DISPLAYS_AS_INDIVIDUAL_WINDOWS, + OPTION_USE_ALL_MY_DISPLAYS_FOR_THE_REMOTE_SESSION, + OPTION_VIEW_STYLE, + OPTION_SCROLL_STYLE, + OPTION_IMAGE_QUALITY, + OPTION_CUSTOM_IMAGE_QUALITY, + OPTION_CUSTOM_FPS, + OPTION_CODEC_PREFERENCE, + ]; + // DEFAULT_LOCAL_SETTINGS, OVERWRITE_LOCAL_SETTINGS + pub const KEYS_LOCAL_SETTINGS: &[&str] = &[ + OPTION_THEME, + OPTION_LANGUAGE, + OPTION_ENABLE_CONFIRM_CLOSING_TABS, + OPTION_ENABLE_OPEN_NEW_CONNECTIONS_IN_TABS, + OPTION_ENABLE_CHECK_UPDATE, + OPTION_SYNC_AB_WITH_RECENT_SESSIONS, + OPTION_SYNC_AB_TAGS, + OPTION_FILTER_AB_BY_INTERSECTION, + ]; + // DEFAULT_SETTINGS, OVERWRITE_SETTINGS + pub const KEYS_SETTINGS: &[&str] = &[ + OPTION_ACCESS_MODE, + OPTION_ENABLE_KEYBOARD, + OPTION_ENABLE_CLIPBOARD, + OPTION_ENABLE_FILE_TRANSFER, + OPTION_ENABLE_AUDIO, + OPTION_ENABLE_TUNNEL, + OPTION_ENABLE_REMOTE_RESTART, + OPTION_ENABLE_RECORD_SESSION, + OPTION_ENABLE_BLOCK_INPUT, + OPTION_ALLOW_REMOTE_CONFIG_MODIFICATION, + OPTION_ENABLE_LAN_DISCOVERY, + OPTION_DIRECT_SERVER, + OPTION_DIRECT_ACCESS_PORT, + OPTION_WHITELIST, + OPTION_ALLOW_AUTO_DISCONNECT, + OPTION_AUTO_DISCONNECT_TIMEOUT, + OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN, + OPTION_ALLOW_AUTO_RECORD_INCOMING, + OPTION_VIDEO_SAVE_DIRECTORY, + OPTION_ENABLE_ABR, + OPTION_ALLOW_REMOVE_WALLPAPER, + OPTION_ALLOW_ALWAYS_SOFTWARE_RENDER, + OPTION_ALLOW_LINUX_HEADLESS, + OPTION_ENABLE_HWCODEC, + OPTION_APPROVE_MODE, + ]; +} + #[cfg(test)] mod tests { use super::*; @@ -2022,6 +2150,10 @@ mod tests { .write() .unwrap() .insert("b".to_string(), "c".to_string()); + OVERWRITE_SETTINGS + .write() + .unwrap() + .insert("c".to_string(), "f".to_string()); OVERWRITE_SETTINGS .write() .unwrap() @@ -2042,7 +2174,7 @@ mod tests { res.insert("d".to_owned(), "c".to_string()); res.insert("c".to_owned(), "a".to_string()); res.insert("f".to_owned(), "a".to_string()); - res.insert("c".to_owned(), "d".to_string()); + res.insert("e".to_owned(), "d".to_string()); Config::purify_options(&mut res); assert!(res.len() == 2); res.insert("b".to_owned(), "c".to_string()); @@ -2055,11 +2187,11 @@ mod tests { assert!(res.len() == 2); let res = Config::get_options(); assert!(res["a"] == "b"); - assert!(res["c"] == "a"); + assert!(res["c"] == "f"); assert!(res["b"] == "c"); assert!(res["d"] == "c"); assert!(Config::get_option("a") == "b"); - assert!(Config::get_option("c") == "a"); + assert!(Config::get_option("c") == "f"); assert!(Config::get_option("b") == "c"); assert!(Config::get_option("d") == "c"); DEFAULT_SETTINGS.write().unwrap().clear(); diff --git a/src/common.rs b/src/common.rs index 25433fa4c..86e53256a 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,5 +1,6 @@ use std::{ borrow::Cow, + collections::HashMap, future::Future, sync::{Arc, Mutex, RwLock}, task::Poll, @@ -1597,22 +1598,41 @@ pub fn read_custom_client(config: &str) { *config::APP_NAME.write().unwrap() = app_name.to_owned(); } } + + let mut map_display_settings = HashMap::new(); + for s in config::keys::KEYS_DISPLAY_SETTINGS { + map_display_settings.insert(s.replace("_", "-"), s); + } + let mut map_local_settings = HashMap::new(); + for s in config::keys::KEYS_LOCAL_SETTINGS { + map_local_settings.insert(s.replace("_", "-"), s); + } + let mut map_settings = HashMap::new(); + for s in config::keys::KEYS_SETTINGS { + map_settings.insert(s.replace("_", "-"), s); + } + if let Some(default_settings) = data.remove("default-settings") { if let Some(default_settings) = default_settings.as_object() { for (k, v) in default_settings { let Some(v) = v.as_str() else { continue; }; - if k.starts_with("$$") { + if let Some(k2) = map_display_settings.get(k) { config::DEFAULT_DISPLAY_SETTINGS .write() .unwrap() - .insert(k.clone(), v[2..].to_owned()); - } else if k.starts_with("$") { + .insert(k2.to_string(), v.to_owned()); + } else if let Some(k2) = map_local_settings.get(k) { config::DEFAULT_LOCAL_SETTINGS .write() .unwrap() - .insert(k.clone(), v[1..].to_owned()); + .insert(k2.to_string(), v.to_owned()); + } else if let Some(k2) = map_settings.get(k) { + config::DEFAULT_SETTINGS + .write() + .unwrap() + .insert(k2.to_string(), v.to_owned()); } else { config::DEFAULT_SETTINGS .write() @@ -1628,16 +1648,21 @@ pub fn read_custom_client(config: &str) { let Some(v) = v.as_str() else { continue; }; - if k.starts_with("$$") { + if let Some(k2) = map_display_settings.get(k) { config::OVERWRITE_DISPLAY_SETTINGS .write() .unwrap() - .insert(k.clone(), v[2..].to_owned()); - } else if k.starts_with("$") { + .insert(k2.to_string(), v.to_owned()); + } else if let Some(k2) = map_local_settings.get(k) { config::OVERWRITE_LOCAL_SETTINGS .write() .unwrap() - .insert(k.clone(), v[1..].to_owned()); + .insert(k2.to_string(), v.to_owned()); + } else if let Some(k2) = map_settings.get(k) { + config::OVERWRITE_SETTINGS + .write() + .unwrap() + .insert(k2.to_string(), v.to_owned()); } else { config::OVERWRITE_SETTINGS .write() diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 073389d0d..f339c729e 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -939,7 +939,6 @@ pub fn main_handle_wayland_screencast_restore_token(_key: String, _value: String } else { "".to_owned() } - } pub fn main_get_input_source() -> SyncReturn { @@ -1194,6 +1193,23 @@ pub fn main_handle_relay_id(id: String) -> String { handle_relay_id(&id).to_owned() } +pub fn main_is_option_fixed(key: String) -> SyncReturn { + SyncReturn( + config::OVERWRITE_DISPLAY_SETTINGS + .read() + .unwrap() + .contains_key(&key) + || config::OVERWRITE_LOCAL_SETTINGS + .read() + .unwrap() + .contains_key(&key) + || config::OVERWRITE_SETTINGS + .read() + .unwrap() + .contains_key(&key), + ) +} + pub fn main_get_main_display() -> SyncReturn { #[cfg(target_os = "ios")] let display_info = "".to_owned();