Fix/custom client advanced settings (#8066)

* fix: custom client, advanced settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* refact: custom client, default options

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* fix: cargo test

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* refact: remove prefix $ and unify option keys

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* refact: custom client, advanced options

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* debug custom client, advanced settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* custom client, advanced settings. Add filter-transfer to display settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* custom client, advanced settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* fix: custom client, advanced settings, codec

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* fix: custom client, advanced settings, whitelist

Signed-off-by: fufesou <shuanglongchen@yeah.net>

---------

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2024-05-17 14:19:11 +08:00 committed by GitHub
parent 3a4390e0c7
commit 8357d4675a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 707 additions and 364 deletions

View File

@ -550,7 +550,8 @@ class MyTheme {
Get.changeThemeMode(mode); Get.changeThemeMode(mode);
if (desktopType == DesktopType.main || isAndroid || isIOS) { if (desktopType == DesktopType.main || isAndroid || isIOS) {
if (mode == ThemeMode.system) { if (mode == ThemeMode.system) {
await bind.mainSetLocalOption(key: kCommConfKeyTheme, value: ''); await bind.mainSetLocalOption(
key: kCommConfKeyTheme, value: defaultOptionTheme);
} else { } else {
await bind.mainSetLocalOption( await bind.mainSetLocalOption(
key: kCommConfKeyTheme, value: mode.toShortString()); key: kCommConfKeyTheme, value: mode.toShortString());
@ -1421,13 +1422,13 @@ bool option2bool(String option, String value) {
String bool2option(String option, bool b) { String bool2option(String option, bool b) {
String res; String res;
if (option.startsWith('enable-')) { if (option.startsWith('enable-')) {
res = b ? '' : 'N'; res = b ? defaultOptionYes : 'N';
} else if (option.startsWith('allow-') || } else if (option.startsWith('allow-') ||
option == "stop-service" || option == "stop-service" ||
option == "direct-server" || option == "direct-server" ||
option == "stop-rendezvous-service" || option == "stop-rendezvous-service" ||
option == kOptionForceAlwaysRelay) { option == kOptionForceAlwaysRelay) {
res = b ? 'Y' : ''; res = b ? 'Y' : defaultOptionNo;
} else { } else {
assert(false); assert(false);
res = b ? 'Y' : 'N'; res = b ? 'Y' : 'N';
@ -3281,3 +3282,14 @@ setResizable(bool resizable) {
windowManager.setResizable(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' : '';

View File

@ -333,6 +333,7 @@ class _AddressBookState extends State<AddressBook> {
@protected @protected
MenuEntryBase<String> syncMenuItem() { MenuEntryBase<String> syncMenuItem() {
final isOptFixed = isOptionFixed(syncAbOption);
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translate('Sync with recent sessions'), text: translate('Sync with recent sessions'),
@ -343,11 +344,13 @@ class _AddressBookState extends State<AddressBook> {
gFFI.abModel.setShouldAsync(v); gFFI.abModel.setShouldAsync(v);
}, },
dismissOnClicked: true, dismissOnClicked: true,
enabled: (!isOptFixed).obs,
); );
} }
@protected @protected
MenuEntryBase<String> sortMenuItem() { MenuEntryBase<String> sortMenuItem() {
final isOptFixed = isOptionFixed(sortAbTagsOption);
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translate('Sort tags'), text: translate('Sort tags'),
@ -355,15 +358,17 @@ class _AddressBookState extends State<AddressBook> {
return shouldSortTags(); return shouldSortTags();
}, },
setter: (bool v) async { setter: (bool v) async {
bind.mainSetLocalOption(key: sortAbTagsOption, value: v ? 'Y' : ''); bind.mainSetLocalOption(key: sortAbTagsOption, value: v ? 'Y' : defaultOptionNo);
gFFI.abModel.sortTags.value = v; gFFI.abModel.sortTags.value = v;
}, },
dismissOnClicked: true, dismissOnClicked: true,
enabled: (!isOptFixed).obs,
); );
} }
@protected @protected
MenuEntryBase<String> filterMenuItem() { MenuEntryBase<String> filterMenuItem() {
final isOptFixed = isOptionFixed(filterAbTagOption);
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translate('Filter by intersection'), text: translate('Filter by intersection'),
@ -371,10 +376,11 @@ class _AddressBookState extends State<AddressBook> {
return filterAbTagByIntersection(); return filterAbTagByIntersection();
}, },
setter: (bool v) async { setter: (bool v) async {
bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : ''); bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : defaultOptionNo);
gFFI.abModel.filterByIntersection.value = v; gFFI.abModel.filterByIntersection.value = v;
}, },
dismissOnClicked: true, dismissOnClicked: true,
enabled: (!isOptFixed).obs,
); );
} }

View File

@ -177,11 +177,14 @@ void changeIdDialog() {
} }
void changeWhiteList({Function()? callback}) async { void changeWhiteList({Function()? callback}) async {
var newWhiteList = (await bind.mainGetOption(key: 'whitelist')).split(','); final curWhiteList = await bind.mainGetOption(key: kOptionWhitelist);
var newWhiteListField = newWhiteList.join('\n'); var newWhiteListField = curWhiteList == defaultOptionWhitelist
? ''
: curWhiteList.split(',').join('\n');
var controller = TextEditingController(text: newWhiteListField); var controller = TextEditingController(text: newWhiteListField);
var msg = ""; var msg = "";
var isInProgress = false; var isInProgress = false;
final isOptFixed = isOptionFixed(kOptionWhitelist);
gFFI.dialogManager.show((setState, close, context) { gFFI.dialogManager.show((setState, close, context) {
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("IP Whitelisting")), title: Text(translate("IP Whitelisting")),
@ -214,14 +217,15 @@ void changeWhiteList({Function()? callback}) async {
), ),
actions: [ actions: [
dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("Clear", onPressed: () async { dialogButton("Clear", onPressed: isOptFixed ? null : () async {
await bind.mainSetOption(key: 'whitelist', value: ''); await bind.mainSetOption(
key: kOptionWhitelist, value: defaultOptionWhitelist);
callback?.call(); callback?.call();
close(); close();
}, isOutline: true), }, isOutline: true),
dialogButton( dialogButton(
"OK", "OK",
onPressed: () async { onPressed: isOptFixed ? null : () async {
setState(() { setState(() {
msg = ""; msg = "";
isInProgress = true; isInProgress = true;
@ -248,7 +252,11 @@ void changeWhiteList({Function()? callback}) async {
} }
newWhiteList = ips.join(','); 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(); callback?.call();
close(); close();
}, },
@ -298,7 +306,7 @@ Future<String> changeDirectAccessPort(
dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("OK", onPressed: () async { dialogButton("OK", onPressed: () async {
await bind.mainSetOption( await bind.mainSetOption(
key: 'direct-access-port', value: controller.text); key: kOptionDirectAccessPort, value: controller.text);
close(); close();
}), }),
], ],
@ -345,7 +353,7 @@ Future<String> changeAutoDisconnectTimeout(String old) async {
dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("OK", onPressed: () async { dialogButton("OK", onPressed: () async {
await bind.mainSetOption( await bind.mainSetOption(
key: 'auto-disconnect-timeout', value: controller.text); key: kOptionAutoDisconnectTimeout, value: controller.text);
close(); close();
}), }),
], ],

View File

@ -537,7 +537,7 @@ class _PeerTabPageState extends State<PeerTabPage>
), ),
onTap: () async { onTap: () async {
await bind.mainSetLocalOption( await bind.mainSetLocalOption(
key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? "" : "Y"); key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? defaultOptionNo : "Y");
hideAbTagsPanel.value = !hideAbTagsPanel.value; hideAbTagsPanel.value = !hideAbTagsPanel.value;
}); });
} }

View File

@ -10,8 +10,8 @@ import 'package:get/get.dart';
customImageQualityWidget( customImageQualityWidget(
{required double initQuality, {required double initQuality,
required double initFps, required double initFps,
required Function(double) setQuality, required Function(double)? setQuality,
required Function(double) setFps, required Function(double)? setFps,
required bool showFps, required bool showFps,
required bool showMoreQuality}) { required bool showMoreQuality}) {
if (initQuality < kMinQuality || if (initQuality < kMinQuality ||
@ -27,16 +27,12 @@ customImageQualityWidget(
final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality); final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality);
final debouncerQuality = Debouncer<double>( final debouncerQuality = Debouncer<double>(
Duration(milliseconds: 1000), Duration(milliseconds: 1000),
onChanged: (double v) { onChanged: setQuality,
setQuality(v);
},
initialValue: qualityValue.value, initialValue: qualityValue.value,
); );
final debouncerFps = Debouncer<double>( final debouncerFps = Debouncer<double>(
Duration(milliseconds: 1000), Duration(milliseconds: 1000),
onChanged: (double v) { onChanged: setFps,
setFps(v);
},
initialValue: fpsValue.value, initialValue: fpsValue.value,
); );
@ -62,10 +58,12 @@ customImageQualityWidget(
divisions: moreQualityChecked.value divisions: moreQualityChecked.value
? ((kMaxMoreQuality - kMinQuality) / 10).round() ? ((kMaxMoreQuality - kMinQuality) / 10).round()
: ((kMaxQuality - kMinQuality) / 5).round(), : ((kMaxQuality - kMinQuality) / 5).round(),
onChanged: (double value) async { onChanged: setQuality == null
qualityValue.value = value; ? null
debouncerQuality.value = value; : (double value) async {
}, qualityValue.value = value;
debouncerQuality.value = value;
},
), ),
), ),
Expanded( Expanded(
@ -124,10 +122,12 @@ customImageQualityWidget(
min: kMinFps, min: kMinFps,
max: kMaxFps, max: kMaxFps,
divisions: ((kMaxFps - kMinFps) / 5).round(), divisions: ((kMaxFps - kMinFps) / 5).round(),
onChanged: (double value) async { onChanged: setFps == null
fpsValue.value = value; ? null
debouncerFps.value = value; : (double value) async {
}, fpsValue.value = value;
debouncerFps.value = value;
},
), ),
), ),
Expanded( Expanded(
@ -152,21 +152,29 @@ customImageQualitySetting() {
final qualityKey = 'custom_image_quality'; final qualityKey = 'custom_image_quality';
final fpsKey = 'custom-fps'; final fpsKey = 'custom-fps';
var initQuality = final initQuality =
(double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ?? (double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ??
kDefaultQuality); kDefaultQuality);
var initFps = (double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? final isQuanlityFixed = isOptionFixed(qualityKey);
kDefaultFps); final initFps =
(double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ??
kDefaultFps);
final isFpsFixed = isOptionFixed(fpsKey);
return customImageQualityWidget( return customImageQualityWidget(
initQuality: initQuality, initQuality: initQuality,
initFps: initFps, initFps: initFps,
setQuality: (v) { setQuality: isQuanlityFixed
bind.mainSetUserDefaultOption(key: qualityKey, value: v.toString()); ? null
}, : (v) {
setFps: (v) { bind.mainSetUserDefaultOption(
bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString()); key: qualityKey, value: v.toString());
}, },
setFps: isFpsFixed
? null
: (v) {
bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString());
},
showFps: true, showFps: true,
showMoreQuality: true); showMoreQuality: true);
} }
@ -208,23 +216,25 @@ List<Widget> ServerConfigImportExportWidgets(
List<(String, String)> otherDefaultSettings() { List<(String, String)> otherDefaultSettings() {
List<(String, String)> v = [ List<(String, String)> v = [
('View Mode', 'view_only'), ('View Mode', kOptionViewOnly),
if ((isDesktop || isWebDesktop)) ('show_monitors_tip', kKeyShowMonitorsToolbar), if ((isDesktop || isWebDesktop))
if ((isDesktop || isWebDesktop)) ('Collapse toolbar', 'collapse_toolbar'), ('show_monitors_tip', kKeyShowMonitorsToolbar),
('Show remote cursor', 'show_remote_cursor'), if ((isDesktop || isWebDesktop))
('Follow remote cursor', 'follow_remote_cursor'), ('Collapse toolbar', kOptionCollapseToolbar),
('Follow remote window focus', 'follow_remote_window'), ('Show remote cursor', kOptionShowRemoteCursor),
if ((isDesktop || isWebDesktop)) ('Zoom cursor', 'zoom-cursor'), ('Follow remote cursor', kOptionFollowRemoteCursor),
('Show quality monitor', 'show_quality_monitor'), ('Follow remote window focus', kOptionFollowRemoteWindow),
('Mute', 'disable_audio'), if ((isDesktop || isWebDesktop)) ('Zoom cursor', kOptionZoomCursor),
if (isDesktop) ('Enable file copy and paste', 'enable_file_transfer'), ('Show quality monitor', kOptionShowQualityMonitor),
('Disable clipboard', 'disable_clipboard'), ('Mute', kOptionDisableAudio),
('Lock after session end', 'lock_after_session_end'), if (isDesktop) ('Enable file copy and paste', kOptionEnableFileTransfer),
('Privacy mode', 'privacy_mode'), ('Disable clipboard', kOptionDisableClipboard),
if (isMobile) ('Touch mode', 'touch-mode'), ('Lock after session end', kOptionLockAfterSessionEnd),
('True color (4:4:4)', 'i444'), ('Privacy mode', kOptionPrivacyMode),
if (isMobile) ('Touch mode', kOptionTouchMode),
('True color (4:4:4)', kOptionI444),
('Reverse mouse wheel', kKeyReverseMouseWheel), ('Reverse mouse wheel', kKeyReverseMouseWheel),
('swap-left-right-mouse', 'swap-left-right-mouse'), ('swap-left-right-mouse', kOptionSwapLeftRightMouse),
if (isDesktop && useTextureRender) if (isDesktop && useTextureRender)
( (
'Show displays as individual windows', 'Show displays as individual windows',

View File

@ -327,7 +327,7 @@ Future<List<TRadioMenu<String>>> toolbarCodec(
final alternativeCodecs = final alternativeCodecs =
await bind.sessionAlternativeCodecs(sessionId: sessionId); await bind.sessionAlternativeCodecs(sessionId: sessionId);
final groupValue = await bind.sessionGetOption( final groupValue = await bind.sessionGetOption(
sessionId: sessionId, arg: 'codec-preference') ?? sessionId: sessionId, arg: kOptionCodecPreference) ??
''; '';
final List<bool> codecs = []; final List<bool> codecs = [];
try { try {
@ -349,7 +349,7 @@ Future<List<TRadioMenu<String>>> toolbarCodec(
onChanged(String? value) async { onChanged(String? value) async {
if (value == null) return; if (value == null) return;
await bind.sessionPeerOption( await bind.sessionPeerOption(
sessionId: sessionId, name: 'codec-preference', value: value); sessionId: sessionId, name: kOptionCodecPreference, value: value);
bind.sessionChangePreferCodec(sessionId: sessionId); bind.sessionChangePreferCodec(sessionId: sessionId);
} }

View File

@ -68,11 +68,53 @@ const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
const String kWindowEventGetCachedSessionData = "get_cached_session_data"; const String kWindowEventGetCachedSessionData = "get_cached_session_data";
const String kWindowEventOpenMonitorSession = "open_monitor_session"; 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 kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
const String kOptionOpenInTabs = "allow-open-in-tabs"; const String kOptionOpenInTabs = "allow-open-in-tabs";
const String kOptionOpenInWindows = "allow-open-in-windows"; const String kOptionOpenInWindows = "allow-open-in-windows";
const String kOptionForceAlwaysRelay = "force-always-relay"; 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"; const String kUrlActionClose = "close";
@ -208,12 +250,6 @@ const kRemoteImageQualityLow = 'low';
/// [kRemoteImageQualityCustom] Custom image quality. /// [kRemoteImageQualityCustom] Custom image quality.
const kRemoteImageQualityCustom = 'custom'; 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; const kIgnoreDpi = true;
// ================================ mobile ================================ // ================================ mobile ================================

View File

@ -332,22 +332,23 @@ class _GeneralState extends State<_General> {
setState(() {}); setState(() {});
} }
final isOptFixed = isOptionFixed(kCommConfKeyTheme);
return _Card(title: 'Theme', children: [ return _Card(title: 'Theme', children: [
_Radio<String>(context, _Radio<String>(context,
value: 'light', value: 'light',
groupValue: current, groupValue: current,
label: 'Light', label: 'Light',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio<String>(context, _Radio<String>(context,
value: 'dark', value: 'dark',
groupValue: current, groupValue: current,
label: 'Dark', label: 'Dark',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio<String>(context, _Radio<String>(context,
value: 'system', value: 'system',
groupValue: current, groupValue: current,
label: 'Follow System', label: 'Follow System',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
]); ]);
} }
@ -547,21 +548,24 @@ class _GeneralState extends State<_General> {
)).marginOnly(left: 10), )).marginOnly(left: 10),
), ),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: isOptionFixed(kOptionVideoSaveDirectory)
String? initialDirectory; ? null
if (await Directory.fromUri(Uri.directory(user_dir)) : () async {
.exists()) { String? initialDirectory;
initialDirectory = user_dir; if (await Directory.fromUri(Uri.directory(user_dir))
} .exists()) {
String? selectedDirectory = await FilePicker.platform initialDirectory = user_dir;
.getDirectoryPath(initialDirectory: initialDirectory); }
if (selectedDirectory != null) { String? selectedDirectory =
await bind.mainSetOption( await FilePicker.platform.getDirectoryPath(
key: 'video-save-directory', initialDirectory: initialDirectory);
value: selectedDirectory); if (selectedDirectory != null) {
setState(() {}); await bind.mainSetOption(
} key: kOptionVideoSaveDirectory,
}, value: selectedDirectory);
setState(() {});
}
},
child: Text(translate('Change'))) child: Text(translate('Change')))
.marginOnly(left: 5), .marginOnly(left: 5),
], ],
@ -580,12 +584,13 @@ class _GeneralState extends State<_General> {
Map<String, String> langsMap = {for (var v in langsList) v[0]: v[1]}; Map<String, String> langsMap = {for (var v in langsList) v[0]: v[1]};
List<String> keys = langsMap.keys.toList(); List<String> keys = langsMap.keys.toList();
List<String> values = langsMap.values.toList(); List<String> values = langsMap.values.toList();
keys.insert(0, ''); keys.insert(0, defaultOptionLang);
values.insert(0, translate('Default')); values.insert(0, translate('Default'));
String currentKey = bind.mainGetLocalOption(key: kCommConfKeyLang); String currentKey = bind.mainGetLocalOption(key: kCommConfKeyLang);
if (!keys.contains(currentKey)) { if (!keys.contains(currentKey)) {
currentKey = ''; currentKey = defaultOptionLang;
} }
final isOptFixed = isOptionFixed(kCommConfKeyLang);
return ComboBox( return ComboBox(
keys: keys, keys: keys,
values: values, values: values,
@ -595,6 +600,7 @@ class _GeneralState extends State<_General> {
reloadAllWindows(); reloadAllWindows();
bind.mainChangeLanguage(lang: key); bind.mainChangeLanguage(lang: key);
}, },
enabled: !isOptFixed,
).marginOnly(left: _kContentHMargin); ).marginOnly(left: _kContentHMargin);
}); });
} }
@ -728,7 +734,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
return _Card(title: 'Permissions', children: [ return _Card(title: 'Permissions', children: [
ComboBox( ComboBox(
keys: [ keys: [
'', defaultOptionAccessMode,
'full', 'full',
'view', 'view',
], ],
@ -737,37 +743,39 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
translate('Full Access'), translate('Full Access'),
translate('Screen Share'), translate('Screen Share'),
], ],
enabled: enabled, enabled: enabled && !isOptionFixed(kOptionAccessMode),
initialKey: initialKey, initialKey: initialKey,
onChanged: (mode) async { onChanged: (mode) async {
await bind.mainSetOption(key: 'access-mode', value: mode); await bind.mainSetOption(key: kOptionAccessMode, value: mode);
setState(() {}); setState(() {});
}).marginOnly(left: _kContentHMargin), }).marginOnly(left: _kContentHMargin),
Column( Column(
children: [ children: [
_OptionCheckBox(context, 'Enable keyboard/mouse', 'enable-keyboard', _OptionCheckBox(
context, 'Enable keyboard/mouse', kOptionEnableKeyboard,
enabled: enabled, fakeValue: fakeValue), enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable clipboard', 'enable-clipboard', _OptionCheckBox(context, 'Enable clipboard', kOptionEnableClipboard,
enabled: enabled, fakeValue: fakeValue), enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox( _OptionCheckBox(
context, 'Enable file transfer', 'enable-file-transfer', context, 'Enable file transfer', kOptionEnableFileTransfer,
enabled: enabled, fakeValue: fakeValue), enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable audio', 'enable-audio', _OptionCheckBox(context, 'Enable audio', kOptionEnableAudio,
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable TCP tunneling', 'enable-tunnel',
enabled: enabled, fakeValue: fakeValue), enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox( _OptionCheckBox(
context, 'Enable remote restart', 'enable-remote-restart', context, 'Enable TCP tunneling', kOptionEnableTunnel,
enabled: enabled, fakeValue: fakeValue), enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox( _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), enabled: enabled, fakeValue: fakeValue),
if (isWindows) if (isWindows)
_OptionCheckBox( _OptionCheckBox(context, 'Enable blocking user input',
context, 'Enable blocking user input', 'enable-block-input', kOptionEnableBlockInput,
enabled: enabled, fakeValue: fakeValue), enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable remote configuration modification', _OptionCheckBox(context, 'Enable remote configuration modification',
'allow-remote-config-modification', kOptionAllowRemoteConfigModification,
enabled: enabled, fakeValue: fakeValue), enabled: enabled, fakeValue: fakeValue),
], ],
), ),
@ -801,14 +809,15 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
value: value, value: value,
groupValue: currentValue, groupValue: currentValue,
label: value, label: value,
onChanged: ((value) { onChanged: locked
() async { ? null
await model.setVerificationMethod( : ((value) {
passwordKeys[passwordValues.indexOf(value)]); () async {
await model.updatePasswordModel(); await model.setVerificationMethod(
}(); passwordKeys[passwordValues.indexOf(value)]);
}), await model.updatePasswordModel();
enabled: !locked, }();
}),
)) ))
.toList(); .toList();
@ -842,7 +851,11 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
)) ))
.toList(); .toList();
final modeKeys = ['password', 'click', '']; final modeKeys = <String>[
'password',
'click',
defaultOptionApproveMode
];
final modeValues = [ final modeValues = [
translate('Accept sessions via password'), translate('Accept sessions via password'),
translate('Accept sessions via click'), translate('Accept sessions via click'),
@ -852,9 +865,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
if (!modeKeys.contains(modeInitialKey)) modeInitialKey = ''; if (!modeKeys.contains(modeInitialKey)) modeInitialKey = '';
final usePassword = model.approveMode != 'click'; final usePassword = model.approveMode != 'click';
final isApproveModeFixed = isOptionFixed(kOptionApproveMode);
return _Card(title: 'Password', children: [ return _Card(title: 'Password', children: [
ComboBox( ComboBox(
enabled: !locked, enabled: !locked && !isApproveModeFixed,
keys: modeKeys, keys: modeKeys,
values: modeValues, values: modeValues,
initialKey: modeInitialKey, initialKey: modeInitialKey,
@ -930,15 +944,17 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
update() => setState(() {}); update() => setState(() {});
RxBool applyEnabled = false.obs; RxBool applyEnabled = false.obs;
return [ return [
_OptionCheckBox(context, 'Enable direct IP access', 'direct-server', _OptionCheckBox(context, 'Enable direct IP access', kOptionDirectServer,
update: update, enabled: !locked), update: update, enabled: !locked),
() { () {
// Simple temp wrapper for PR check // Simple temp wrapper for PR check
tmpWrapper() { tmpWrapper() {
bool enabled = option2bool( bool enabled = option2bool(kOptionDirectServer,
'direct-server', bind.mainGetOptionSync(key: 'direct-server')); bind.mainGetOptionSync(key: kOptionDirectServer));
if (!enabled) applyEnabled.value = false; 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( return Offstage(
offstage: !enabled, offstage: !enabled,
child: _SubLabeledWidget( child: _SubLabeledWidget(
@ -949,7 +965,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
width: 95, width: 95,
child: TextField( child: TextField(
controller: controller, controller: controller,
enabled: enabled && !locked, enabled: enabled && !locked && !isOptFixed,
onChanged: (_) => applyEnabled.value = true, onChanged: (_) => applyEnabled.value = true,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.allow(RegExp( FilteringTextInputFormatter.allow(RegExp(
@ -963,11 +979,14 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
).marginOnly(right: 15), ).marginOnly(right: 15),
), ),
Obx(() => ElevatedButton( Obx(() => ElevatedButton(
onPressed: applyEnabled.value && enabled && !locked onPressed: applyEnabled.value &&
enabled &&
!locked &&
!isOptFixed
? () async { ? () async {
applyEnabled.value = false; applyEnabled.value = false;
await bind.mainSetOption( await bind.mainSetOption(
key: 'direct-access-port', key: kOptionDirectAccessPort,
value: controller.text); value: controller.text);
} }
: null, : 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; bool enabled = !locked;
// Simple temp wrapper for PR check // Simple temp wrapper for PR check
tmpWrapper() { tmpWrapper() {
RxBool hasWhitelist = RxBool hasWhitelist = (bind.mainGetOptionSync(key: kOptionWhitelist) !=
bind.mainGetOptionSync(key: 'whitelist').isNotEmpty.obs; defaultOptionWhitelist)
.obs;
update() async { update() async {
hasWhitelist.value = hasWhitelist.value = bind.mainGetOptionSync(key: kOptionWhitelist) !=
bind.mainGetOptionSync(key: 'whitelist').isNotEmpty; defaultOptionWhitelist;
} }
onChanged(bool? checked) async { onChanged(bool? checked) async {
changeWhiteList(callback: update); changeWhiteList(callback: update);
} }
final isOptFixed = isOptionFixed(kOptionWhitelist);
return GestureDetector( return GestureDetector(
child: Tooltip( child: Tooltip(
message: translate('whitelist_tip'), message: translate('whitelist_tip'),
@ -1008,13 +1029,16 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
children: [ children: [
Checkbox( Checkbox(
value: hasWhitelist.value, value: hasWhitelist.value,
onChanged: enabled ? onChanged : null) onChanged: enabled && !isOptFixed ? onChanged : null)
.marginOnly(right: 5), .marginOnly(right: 5),
Offstage( Offstage(
offstage: !hasWhitelist.value, offstage: !hasWhitelist.value,
child: const Icon(Icons.warning_amber_rounded, child: MouseRegion(
color: Color.fromARGB(255, 255, 204, 0)) child: const Icon(Icons.warning_amber_rounded,
.marginOnly(right: 5), color: Color.fromARGB(255, 255, 204, 0))
.marginOnly(right: 5),
cursor: SystemMouseCursors.click,
),
), ),
Expanded( Expanded(
child: Text( child: Text(
@ -1025,9 +1049,11 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
], ],
)), )),
), ),
onTap: () { onTap: enabled
onChanged(!hasWhitelist.value); ? () {
}, onChanged(!hasWhitelist.value);
}
: null,
).marginOnly(left: _kCheckBoxLeftMargin); ).marginOnly(left: _kCheckBoxLeftMargin);
} }
@ -1078,16 +1104,17 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
TextEditingController controller = TextEditingController(); TextEditingController controller = TextEditingController();
update() => setState(() {}); update() => setState(() {});
RxBool applyEnabled = false.obs; RxBool applyEnabled = false.obs;
final optionKey = 'allow-auto-disconnect';
final timeoutKey = 'auto-disconnect-timeout';
return [ return [
_OptionCheckBox(context, 'auto_disconnect_option_tip', optionKey, _OptionCheckBox(
context, 'auto_disconnect_option_tip', kOptionAllowAutoDisconnect,
update: update, enabled: !locked), update: update, enabled: !locked),
() { () {
bool enabled = bool enabled = option2bool(kOptionAllowAutoDisconnect,
option2bool(optionKey, bind.mainGetOptionSync(key: optionKey)); bind.mainGetOptionSync(key: kOptionAllowAutoDisconnect));
if (!enabled) applyEnabled.value = false; if (!enabled) applyEnabled.value = false;
controller.text = bind.mainGetOptionSync(key: timeoutKey); controller.text =
bind.mainGetOptionSync(key: kOptionAutoDisconnectTimeout);
final isOptFixed = isOptionFixed(kOptionAutoDisconnectTimeout);
return Offstage( return Offstage(
offstage: !enabled, offstage: !enabled,
child: _SubLabeledWidget( child: _SubLabeledWidget(
@ -1098,7 +1125,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
width: 95, width: 95,
child: TextField( child: TextField(
controller: controller, controller: controller,
enabled: enabled && !locked, enabled: enabled && !locked && isOptFixed,
onChanged: (_) => applyEnabled.value = true, onChanged: (_) => applyEnabled.value = true,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.allow(RegExp( FilteringTextInputFormatter.allow(RegExp(
@ -1112,19 +1139,21 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
).marginOnly(right: 15), ).marginOnly(right: 15),
), ),
Obx(() => ElevatedButton( Obx(() => ElevatedButton(
onPressed: applyEnabled.value && enabled && !locked onPressed:
? () async { applyEnabled.value && enabled && !locked && !isOptFixed
applyEnabled.value = false; ? () async {
await bind.mainSetOption( applyEnabled.value = false;
key: timeoutKey, value: controller.text); await bind.mainSetOption(
} key: kOptionAutoDisconnectTimeout,
: null, value: controller.text);
}
: null,
child: Text( child: Text(
translate('Apply'), translate('Apply'),
), ),
)) ))
]), ]),
enabled: enabled && !locked, enabled: enabled && !locked && !isOptFixed,
), ),
); );
}(), }(),
@ -1273,46 +1302,47 @@ class _DisplayState extends State<_Display> {
} }
Widget viewStyle(BuildContext context) { Widget viewStyle(BuildContext context) {
final key = 'view_style'; final isOptFixed = isOptionFixed(kOptionViewStyle);
onChanged(String value) async { onChanged(String value) async {
await bind.mainSetUserDefaultOption(key: key, value: value); await bind.mainSetUserDefaultOption(key: kOptionViewStyle, value: value);
setState(() {}); setState(() {});
} }
final groupValue = bind.mainGetUserDefaultOption(key: key); final groupValue = bind.mainGetUserDefaultOption(key: kOptionViewStyle);
return _Card(title: 'Default View Style', children: [ return _Card(title: 'Default View Style', children: [
_Radio(context, _Radio(context,
value: kRemoteViewStyleOriginal, value: kRemoteViewStyleOriginal,
groupValue: groupValue, groupValue: groupValue,
label: 'Scale original', label: 'Scale original',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio(context, _Radio(context,
value: kRemoteViewStyleAdaptive, value: kRemoteViewStyleAdaptive,
groupValue: groupValue, groupValue: groupValue,
label: 'Scale adaptive', label: 'Scale adaptive',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
]); ]);
} }
Widget scrollStyle(BuildContext context) { Widget scrollStyle(BuildContext context) {
final key = 'scroll_style'; final isOptFixed = isOptionFixed(kOptionScrollStyle);
onChanged(String value) async { onChanged(String value) async {
await bind.mainSetUserDefaultOption(key: key, value: value); await bind.mainSetUserDefaultOption(
key: kOptionScrollStyle, value: value);
setState(() {}); setState(() {});
} }
final groupValue = bind.mainGetUserDefaultOption(key: key); final groupValue = bind.mainGetUserDefaultOption(key: kOptionScrollStyle);
return _Card(title: 'Default Scroll Style', children: [ return _Card(title: 'Default Scroll Style', children: [
_Radio(context, _Radio(context,
value: kRemoteScrollStyleAuto, value: kRemoteScrollStyleAuto,
groupValue: groupValue, groupValue: groupValue,
label: 'ScrollAuto', label: 'ScrollAuto',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio(context, _Radio(context,
value: kRemoteScrollStyleBar, value: kRemoteScrollStyleBar,
groupValue: groupValue, groupValue: groupValue,
label: 'Scrollbar', label: 'Scrollbar',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
]); ]);
} }
@ -1323,28 +1353,29 @@ class _DisplayState extends State<_Display> {
setState(() {}); setState(() {});
} }
final isOptFixed = isOptionFixed(key);
final groupValue = bind.mainGetUserDefaultOption(key: key); final groupValue = bind.mainGetUserDefaultOption(key: key);
return _Card(title: 'Default Image Quality', children: [ return _Card(title: 'Default Image Quality', children: [
_Radio(context, _Radio(context,
value: kRemoteImageQualityBest, value: kRemoteImageQualityBest,
groupValue: groupValue, groupValue: groupValue,
label: 'Good image quality', label: 'Good image quality',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio(context, _Radio(context,
value: kRemoteImageQualityBalanced, value: kRemoteImageQualityBalanced,
groupValue: groupValue, groupValue: groupValue,
label: 'Balanced', label: 'Balanced',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio(context, _Radio(context,
value: kRemoteImageQualityLow, value: kRemoteImageQualityLow,
groupValue: groupValue, groupValue: groupValue,
label: 'Optimize reaction time', label: 'Optimize reaction time',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio(context, _Radio(context,
value: kRemoteImageQualityCustom, value: kRemoteImageQualityCustom,
groupValue: groupValue, groupValue: groupValue,
label: 'Custom', label: 'Custom',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
Offstage( Offstage(
offstage: groupValue != kRemoteImageQualityCustom, offstage: groupValue != kRemoteImageQualityCustom,
child: customImageQualitySetting(), child: customImageQualitySetting(),
@ -1353,14 +1384,16 @@ class _DisplayState extends State<_Display> {
} }
Widget codec(BuildContext context) { Widget codec(BuildContext context) {
final key = 'codec-preference';
onChanged(String value) async { onChanged(String value) async {
await bind.mainSetUserDefaultOption(key: key, value: value); await bind.mainSetUserDefaultOption(
key: kOptionCodecPreference, value: value);
setState(() {}); setState(() {});
} }
final groupValue = bind.mainGetUserDefaultOption(key: key); final groupValue =
bind.mainGetUserDefaultOption(key: kOptionCodecPreference);
var hwRadios = []; var hwRadios = [];
final isOptFixed = isOptionFixed(kOptionCodecPreference);
try { try {
final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings()); final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings());
final h264 = codecsJson['h264'] ?? false; final h264 = codecsJson['h264'] ?? false;
@ -1370,14 +1403,14 @@ class _DisplayState extends State<_Display> {
value: 'h264', value: 'h264',
groupValue: groupValue, groupValue: groupValue,
label: 'H264', label: 'H264',
onChanged: onChanged)); onChanged: isOptFixed ? null : onChanged));
} }
if (h265) { if (h265) {
hwRadios.add(_Radio(context, hwRadios.add(_Radio(context,
value: 'h265', value: 'h265',
groupValue: groupValue, groupValue: groupValue,
label: 'H265', label: 'H265',
onChanged: onChanged)); onChanged: isOptFixed ? null : onChanged));
} }
} catch (e) { } catch (e) {
debugPrint("failed to parse supported hwdecodings, err=$e"); debugPrint("failed to parse supported hwdecodings, err=$e");
@ -1387,22 +1420,22 @@ class _DisplayState extends State<_Display> {
value: 'auto', value: 'auto',
groupValue: groupValue, groupValue: groupValue,
label: 'Auto', label: 'Auto',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio(context, _Radio(context,
value: 'vp8', value: 'vp8',
groupValue: groupValue, groupValue: groupValue,
label: 'VP8', label: 'VP8',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio(context, _Radio(context,
value: 'vp9', value: 'vp9',
groupValue: groupValue, groupValue: groupValue,
label: 'VP9', label: 'VP9',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
_Radio(context, _Radio(context,
value: 'av1', value: 'av1',
groupValue: groupValue, groupValue: groupValue,
label: 'AV1', label: 'AV1',
onChanged: onChanged), onChanged: isOptFixed ? null : onChanged),
...hwRadios, ...hwRadios,
]); ]);
} }
@ -1445,22 +1478,29 @@ class _DisplayState extends State<_Display> {
Widget otherRow(String label, String key) { Widget otherRow(String label, String key) {
final value = bind.mainGetUserDefaultOption(key: key) == 'Y'; final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
final isOptFixed = isOptionFixed(key);
onChanged(bool b) async { 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(() {}); setState(() {});
} }
return GestureDetector( return GestureDetector(
child: Row( child: Row(
children: [ children: [
Checkbox(value: value, onChanged: (_) => onChanged(!value)) Checkbox(
value: value,
onChanged: isOptFixed ? null : (_) => onChanged(!value))
.marginOnly(right: 5), .marginOnly(right: 5),
Expanded( Expanded(
child: Text(translate(label)), child: Text(translate(label)),
) )
], ],
).marginOnly(left: _kCheckBoxLeftMargin), ).marginOnly(left: _kCheckBoxLeftMargin),
onTap: () => onChanged(!value)); onTap: isOptFixed ? null : () => onChanged(!value));
} }
Widget other(BuildContext context) { Widget other(BuildContext context) {
@ -1772,6 +1812,7 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
bool isServer = true}) { bool isServer = true}) {
bool value = bool value =
isServer ? mainGetBoolOptionSync(key) : mainGetLocalBoolOptionSync(key); isServer ? mainGetBoolOptionSync(key) : mainGetLocalBoolOptionSync(key);
final isOptFixed = isOptionFixed(key);
if (reverse) value = !value; if (reverse) value = !value;
var ref = value.obs; var ref = value.obs;
onChanged(option) async { onChanged(option) async {
@ -1801,7 +1842,9 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
child: Obx( child: Obx(
() => Row( () => Row(
children: [ children: [
Checkbox(value: ref.value, onChanged: enabled ? onChanged : null) Checkbox(
value: ref.value,
onChanged: enabled && !isOptFixed ? onChanged : null)
.marginOnly(right: 5), .marginOnly(right: 5),
Offstage( Offstage(
offstage: !ref.value || checkedIcon == null, offstage: !ref.value || checkedIcon == null,
@ -1815,7 +1858,7 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
], ],
), ),
).marginOnly(left: _kCheckBoxLeftMargin), ).marginOnly(left: _kCheckBoxLeftMargin),
onTap: enabled onTap: enabled && !isOptFixed
? () { ? () {
onChanged(!ref.value); onChanged(!ref.value);
} }
@ -1828,10 +1871,9 @@ Widget _Radio<T>(BuildContext context,
{required T value, {required T value,
required T groupValue, required T groupValue,
required String label, required String label,
required Function(T value) onChanged, required Function(T value)? onChanged,
bool autoNewLine = true, bool autoNewLine = true}) {
bool enabled = true}) { final onChange2 = onChanged != null
var onChange = enabled
? (T? value) { ? (T? value) {
if (value != null) { if (value != null) {
onChanged(value); onChanged(value);
@ -1841,18 +1883,18 @@ Widget _Radio<T>(BuildContext context,
return GestureDetector( return GestureDetector(
child: Row( child: Row(
children: [ children: [
Radio<T>(value: value, groupValue: groupValue, onChanged: onChange), Radio<T>(value: value, groupValue: groupValue, onChanged: onChange2),
Expanded( Expanded(
child: Text(translate(label), child: Text(translate(label),
overflow: autoNewLine ? null : TextOverflow.ellipsis, overflow: autoNewLine ? null : TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: _kContentFontSize, fontSize: _kContentFontSize,
color: disabledTextColor(context, enabled))) color: disabledTextColor(context, onChange2 != null)))
.marginOnly(left: 5), .marginOnly(left: 5),
), ),
], ],
).marginOnly(left: _kRadioLeftMargin), ).marginOnly(left: _kRadioLeftMargin),
onTap: () => onChange?.call(value), onTap: () => onChange2?.call(value),
); );
} }

View File

@ -428,7 +428,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
} }
ConnectionTypeState.init(id); ConnectionTypeState.init(id);
_toolbarState.setShow( _toolbarState.setShow(
bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y'); bind.mainGetUserDefaultOption(key: kOptionCollapseToolbar) != 'Y');
tabController.add(TabInfo( tabController.add(TabInfo(
key: id, key: id,
label: id, label: id,

View File

@ -54,7 +54,7 @@ class ToolbarState {
_initSet(bool s, bool p) { _initSet(bool s, bool p) {
// Show remubar when connection is established. // Show remubar when connection is established.
show = show =
RxBool(bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y'); RxBool(bind.mainGetUserDefaultOption(key: kOptionCollapseToolbar) != 'Y');
_pin = RxBool(p); _pin = RxBool(p);
} }

View File

@ -39,6 +39,7 @@ class ServerPage extends StatefulWidget implements PageShape {
final approveMode = gFFI.serverModel.approveMode; final approveMode = gFFI.serverModel.approveMode;
final verificationMethod = gFFI.serverModel.verificationMethod; final verificationMethod = gFFI.serverModel.verificationMethod;
final showPasswordOption = approveMode != 'click'; final showPasswordOption = approveMode != 'click';
final isApproveModeFixed = isOptionFixed(kOptionApproveMode);
return [ return [
PopupMenuItem( PopupMenuItem(
enabled: gFFI.serverModel.connectStatus > 0, enabled: gFFI.serverModel.connectStatus > 0,
@ -50,16 +51,19 @@ class ServerPage extends StatefulWidget implements PageShape {
value: 'AcceptSessionsViaPassword', value: 'AcceptSessionsViaPassword',
child: listTile( child: listTile(
'Accept sessions via password', approveMode == 'password'), 'Accept sessions via password', approveMode == 'password'),
enabled: !isApproveModeFixed,
), ),
PopupMenuItem( PopupMenuItem(
value: 'AcceptSessionsViaClick', value: 'AcceptSessionsViaClick',
child: child:
listTile('Accept sessions via click', approveMode == 'click'), listTile('Accept sessions via click', approveMode == 'click'),
enabled: !isApproveModeFixed,
), ),
PopupMenuItem( PopupMenuItem(
value: "AcceptSessionsViaBoth", value: "AcceptSessionsViaBoth",
child: listTile("Accept sessions via both", child: listTile("Accept sessions via both",
approveMode != 'password' && approveMode != 'click'), approveMode != 'password' && approveMode != 'click'),
enabled: !isApproveModeFixed,
), ),
if (showPasswordOption) const PopupMenuDivider(), if (showPasswordOption) const PopupMenuDivider(),
if (showPasswordOption && if (showPasswordOption &&
@ -116,7 +120,7 @@ class ServerPage extends StatefulWidget implements PageShape {
} else if (value == "Click") { } else if (value == "Click") {
gFFI.serverModel.setApproveMode('click'); gFFI.serverModel.setApproveMode('click');
} else { } else {
gFFI.serverModel.setApproveMode(''); gFFI.serverModel.setApproveMode(defaultOptionApproveMode);
} }
} }
}) })

View File

@ -100,8 +100,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_denyLANDiscovery = denyLanDiscovery; _denyLANDiscovery = denyLanDiscovery;
} }
final onlyWhiteList = final onlyWhiteList = (await bind.mainGetOption(key: kOptionWhitelist)) !=
(await bind.mainGetOption(key: 'whitelist')).isNotEmpty; defaultOptionWhitelist;
if (onlyWhiteList != _onlyWhiteList) { if (onlyWhiteList != _onlyWhiteList) {
update = true; update = true;
_onlyWhiteList = onlyWhiteList; _onlyWhiteList = onlyWhiteList;
@ -143,7 +143,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
} }
final directAccessPort = final directAccessPort =
await bind.mainGetOption(key: 'direct-access-port'); await bind.mainGetOption(key: kOptionDirectAccessPort);
if (directAccessPort != _directAccessPort) { if (directAccessPort != _directAccessPort) {
update = true; update = true;
_directAccessPort = directAccessPort; _directAccessPort = directAccessPort;
@ -257,16 +257,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
SettingsTile.switchTile( SettingsTile.switchTile(
title: Text(translate('Deny LAN discovery')), title: Text(translate('Deny LAN discovery')),
initialValue: _denyLANDiscovery, initialValue: _denyLANDiscovery,
onToggle: (v) async { onToggle: isOptionFixed(kOptionEnableLanDiscovery)
await bind.mainSetOption( ? null
key: "enable-lan-discovery", : (v) async {
value: bool2option("enable-lan-discovery", !v)); await bind.mainSetOption(
final newValue = !option2bool('enable-lan-discovery', key: kOptionEnableLanDiscovery,
await bind.mainGetOption(key: 'enable-lan-discovery')); value: bool2option(kOptionEnableLanDiscovery, !v));
setState(() { final newValue = !option2bool(kOptionEnableLanDiscovery,
_denyLANDiscovery = newValue; await bind.mainGetOption(key: kOptionEnableLanDiscovery));
}); setState(() {
}, _denyLANDiscovery = newValue;
});
},
), ),
SettingsTile.switchTile( SettingsTile.switchTile(
title: Row(children: [ title: Row(children: [
@ -279,42 +281,51 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
]), ]),
initialValue: _onlyWhiteList, initialValue: _onlyWhiteList,
onToggle: (_) async { onToggle: (_) async {
update() async { update() async {
final onlyWhiteList = final onlyWhiteList =
(await bind.mainGetOption(key: 'whitelist')).isNotEmpty; (await bind.mainGetOption(key: kOptionWhitelist)) !=
if (onlyWhiteList != _onlyWhiteList) { defaultOptionWhitelist;
setState(() { if (onlyWhiteList != _onlyWhiteList) {
_onlyWhiteList = onlyWhiteList; setState(() {
}); _onlyWhiteList = onlyWhiteList;
} });
} }
}
changeWhiteList(callback: update); changeWhiteList(callback: update);
}, },
), ),
SettingsTile.switchTile( SettingsTile.switchTile(
title: Text('${translate('Adaptive bitrate')} (beta)'), title: Text('${translate('Adaptive bitrate')} (beta)'),
initialValue: _enableAbr, initialValue: _enableAbr,
onToggle: (v) async { onToggle: isOptionFixed(kOptionEnableAbr)
await bind.mainSetOption(key: "enable-abr", value: v ? "" : "N"); ? null
final newValue = await bind.mainGetOption(key: "enable-abr") != "N"; : (v) async {
setState(() { await bind.mainSetOption(
_enableAbr = newValue; key: kOptionEnableAbr, value: v ? defaultOptionYes : "N");
}); final newValue =
}, await bind.mainGetOption(key: kOptionEnableAbr) != "N";
setState(() {
_enableAbr = newValue;
});
},
), ),
SettingsTile.switchTile( SettingsTile.switchTile(
title: Text(translate('Enable recording session')), title: Text(translate('Enable recording session')),
initialValue: _enableRecordSession, initialValue: _enableRecordSession,
onToggle: (v) async { onToggle: isOptionFixed(kOptionEnableRecordSession)
await bind.mainSetOption( ? null
key: "enable-record-session", value: v ? "" : "N"); : (v) async {
final newValue = await bind.mainSetOption(
await bind.mainGetOption(key: "enable-record-session") != "N"; key: kOptionEnableRecordSession,
setState(() { value: v ? defaultOptionYes : "N");
_enableRecordSession = newValue; final newValue =
}); await bind.mainGetOption(key: kOptionEnableRecordSession) !=
}, "N";
setState(() {
_enableRecordSession = newValue;
});
},
), ),
SettingsTile.switchTile( SettingsTile.switchTile(
title: Row( title: Row(
@ -341,21 +352,27 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
Icons.edit, Icons.edit,
size: 20, size: 20,
), ),
onPressed: () async { onPressed: isOptionFixed(kOptionDirectAccessPort)
final port = await changeDirectAccessPort( ? null
_localIP, _directAccessPort); : () async {
setState(() { final port = await changeDirectAccessPort(
_directAccessPort = port; _localIP, _directAccessPort);
}); setState(() {
})) _directAccessPort = port;
});
}))
]), ]),
initialValue: _enableDirectIPAccess, initialValue: _enableDirectIPAccess,
onToggle: (_) async { onToggle: isOptionFixed(kOptionDirectServer)
_enableDirectIPAccess = !_enableDirectIPAccess; ? null
String value = bool2option('direct-server', _enableDirectIPAccess); : (_) async {
await bind.mainSetOption(key: 'direct-server', value: value); _enableDirectIPAccess = !_enableDirectIPAccess;
setState(() {}); String value =
}, bool2option(kOptionDirectServer, _enableDirectIPAccess);
await bind.mainSetOption(
key: kOptionDirectServer, value: value);
setState(() {});
},
), ),
SettingsTile.switchTile( SettingsTile.switchTile(
title: Row( title: Row(
@ -382,22 +399,27 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
Icons.edit, Icons.edit,
size: 20, size: 20,
), ),
onPressed: () async { onPressed: isOptionFixed(kOptionAutoDisconnectTimeout)
final timeout = await changeAutoDisconnectTimeout( ? null
_autoDisconnectTimeout); : () async {
setState(() { final timeout = await changeAutoDisconnectTimeout(
_autoDisconnectTimeout = timeout; _autoDisconnectTimeout);
}); setState(() {
})) _autoDisconnectTimeout = timeout;
});
}))
]), ]),
initialValue: _allowAutoDisconnect, initialValue: _allowAutoDisconnect,
onToggle: (_) async { onToggle: isOptionFixed(kOptionAllowAutoDisconnect)
_allowAutoDisconnect = !_allowAutoDisconnect; ? null
String value = : (_) async {
bool2option('allow-auto-disconnect', _allowAutoDisconnect); _allowAutoDisconnect = !_allowAutoDisconnect;
await bind.mainSetOption(key: 'allow-auto-disconnect', value: value); String value = bool2option(
setState(() {}); kOptionAllowAutoDisconnect, _allowAutoDisconnect);
}, await bind.mainSetOption(
key: kOptionAllowAutoDisconnect, value: value);
setState(() {});
},
) )
]; ];
if (_hasIgnoreBattery) { if (_hasIgnoreBattery) {
@ -526,15 +548,19 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
SettingsTile.switchTile( SettingsTile.switchTile(
title: Text(translate('Enable hardware codec')), title: Text(translate('Enable hardware codec')),
initialValue: _enableHardwareCodec, initialValue: _enableHardwareCodec,
onToggle: (v) async { onToggle: isOptionFixed(kOptionEnableHwcodec)
await bind.mainSetOption( ? null
key: "enable-hwcodec", value: v ? "" : "N"); : (v) async {
final newValue = await bind.mainSetOption(
await bind.mainGetOption(key: "enable-hwcodec") != "N"; key: kOptionEnableHwcodec,
setState(() { value: v ? defaultOptionYes : "N");
_enableHardwareCodec = newValue; final newValue =
}); await bind.mainGetOption(key: kOptionEnableHwcodec) !=
}, "N";
setState(() {
_enableHardwareCodec = newValue;
});
},
), ),
]), ]),
if (isAndroid && !outgoingOnly) if (isAndroid && !outgoingOnly)
@ -551,18 +577,21 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
child: Text("${translate("Directory")}: ${data.data}")), child: Text("${translate("Directory")}: ${data.data}")),
future: bind.mainVideoSaveDirectory(root: false)), future: bind.mainVideoSaveDirectory(root: false)),
initialValue: _autoRecordIncomingSession, initialValue: _autoRecordIncomingSession,
onToggle: (v) async { onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming)
await bind.mainSetOption( ? null
key: "allow-auto-record-incoming", : (v) async {
value: bool2option("allow-auto-record-incoming", v)); await bind.mainSetOption(
final newValue = option2bool( key: kOptionAllowAutoRecordIncoming,
'allow-auto-record-incoming', value:
await bind.mainGetOption( bool2option(kOptionAllowAutoRecordIncoming, v));
key: 'allow-auto-record-incoming')); final newValue = option2bool(
setState(() { kOptionAllowAutoRecordIncoming,
_autoRecordIncomingSession = newValue; await bind.mainGetOption(
}); key: kOptionAllowAutoRecordIncoming));
}, setState(() {
_autoRecordIncomingSession = newValue;
});
},
), ),
], ],
), ),
@ -661,29 +690,32 @@ void showServerSettings(OverlayDialogManager dialogManager) async {
void showLanguageSettings(OverlayDialogManager dialogManager) async { void showLanguageSettings(OverlayDialogManager dialogManager) async {
try { try {
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>; final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
var lang = bind.mainGetLocalOption(key: "lang"); var lang = bind.mainGetLocalOption(key: kCommConfKeyLang);
dialogManager.show((setState, close, context) { dialogManager.show((setState, close, context) {
setLang(v) async { setLang(v) async {
if (lang != v) { if (lang != v) {
setState(() { setState(() {
lang = v; lang = v;
}); });
await bind.mainSetLocalOption(key: "lang", value: v); await bind.mainSetLocalOption(key: kCommConfKeyLang, value: v);
HomePage.homeKey.currentState?.refreshPages(); HomePage.homeKey.currentState?.refreshPages();
Future.delayed(Duration(milliseconds: 200), close); Future.delayed(Duration(milliseconds: 200), close);
} }
} }
final isOptFixed = isOptionFixed(kCommConfKeyLang);
return CustomAlertDialog( return CustomAlertDialog(
content: Column( content: Column(
children: [ children: [
getRadio(Text(translate('Default')), '', lang, setLang), getRadio(Text(translate('Default')), defaultOptionLang, lang,
isOptFixed ? null : setLang),
Divider(color: MyTheme.border), Divider(color: MyTheme.border),
] + ] +
langs.map((e) { langs.map((e) {
final key = e[0] as String; final key = e[0] as String;
final name = e[1] 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(), }).toList(),
), ),
); );
@ -707,13 +739,15 @@ void showThemeSettings(OverlayDialogManager dialogManager) async {
} }
} }
final isOptFixed = isOptionFixed(kCommConfKeyTheme);
return CustomAlertDialog( return CustomAlertDialog(
content: Column(children: [ content: Column(children: [
getRadio( getRadio(Text(translate('Light')), ThemeMode.light, themeMode,
Text(translate('Light')), ThemeMode.light, themeMode, setTheme), isOptFixed ? null : setTheme),
getRadio(Text(translate('Dark')), ThemeMode.dark, themeMode, setTheme), getRadio(Text(translate('Dark')), ThemeMode.dark, themeMode,
isOptFixed ? null : setTheme),
getRadio(Text(translate('Follow System')), ThemeMode.system, themeMode, getRadio(Text(translate('Follow System')), ThemeMode.system, themeMode,
setTheme) isOptFixed ? null : setTheme)
]), ]),
); );
}, backDismiss: true, clickMaskDismiss: true); }, backDismiss: true, clickMaskDismiss: true);
@ -801,11 +835,14 @@ class __DisplayPageState extends State<_DisplayPage> {
_RadioEntry('Scale original', kRemoteViewStyleOriginal), _RadioEntry('Scale original', kRemoteViewStyleOriginal),
_RadioEntry('Scale adaptive', kRemoteViewStyleAdaptive) _RadioEntry('Scale adaptive', kRemoteViewStyleAdaptive)
], ],
getter: () => bind.mainGetUserDefaultOption(key: 'view_style'), getter: () =>
asyncSetter: (value) async { bind.mainGetUserDefaultOption(key: kOptionViewStyle),
await bind.mainSetUserDefaultOption( asyncSetter: isOptionFixed(kOptionViewStyle)
key: 'view_style', value: value); ? null
}, : (value) async {
await bind.mainSetUserDefaultOption(
key: kOptionViewStyle, value: value);
},
), ),
_getPopupDialogRadioEntry( _getPopupDialogRadioEntry(
title: 'Default Image Quality', title: 'Default Image Quality',
@ -816,16 +853,19 @@ class __DisplayPageState extends State<_DisplayPage> {
_RadioEntry('Custom', kRemoteImageQualityCustom), _RadioEntry('Custom', kRemoteImageQualityCustom),
], ],
getter: () { getter: () {
final v = bind.mainGetUserDefaultOption(key: 'image_quality'); final v =
bind.mainGetUserDefaultOption(key: kOptionImageQuality);
showCustomImageQuality.value = v == kRemoteImageQualityCustom; showCustomImageQuality.value = v == kRemoteImageQualityCustom;
return v; return v;
}, },
asyncSetter: (value) async { asyncSetter: isOptionFixed(kOptionImageQuality)
await bind.mainSetUserDefaultOption( ? null
key: 'image_quality', value: value); : (value) async {
showCustomImageQuality.value = await bind.mainSetUserDefaultOption(
value == kRemoteImageQualityCustom; key: kOptionImageQuality, value: value);
}, showCustomImageQuality.value =
value == kRemoteImageQualityCustom;
},
tail: customImageQualitySetting(), tail: customImageQualitySetting(),
showTail: showCustomImageQuality, showTail: showCustomImageQuality,
notCloseValue: kRemoteImageQualityCustom, notCloseValue: kRemoteImageQualityCustom,
@ -834,11 +874,13 @@ class __DisplayPageState extends State<_DisplayPage> {
title: 'Default Codec', title: 'Default Codec',
list: codecList, list: codecList,
getter: () => getter: () =>
bind.mainGetUserDefaultOption(key: 'codec-preference'), bind.mainGetUserDefaultOption(key: kOptionCodecPreference),
asyncSetter: (value) async { asyncSetter: isOptionFixed(kOptionCodecPreference)
await bind.mainSetUserDefaultOption( ? null
key: 'codec-preference', value: value); : (value) async {
}, await bind.mainSetUserDefaultOption(
key: kOptionCodecPreference, value: value);
},
), ),
], ],
), ),
@ -853,13 +895,17 @@ class __DisplayPageState extends State<_DisplayPage> {
SettingsTile otherRow(String label, String key) { SettingsTile otherRow(String label, String key) {
final value = bind.mainGetUserDefaultOption(key: key) == 'Y'; final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
final isOptFixed = isOptionFixed(key);
return SettingsTile.switchTile( return SettingsTile.switchTile(
initialValue: value, initialValue: value,
title: Text(translate(label)), title: Text(translate(label)),
onToggle: (b) async { onToggle: isOptFixed
await bind.mainSetUserDefaultOption(key: key, value: b ? 'Y' : ''); ? null
setState(() {}); : (b) async {
}, await bind.mainSetUserDefaultOption(
key: key, value: b ? 'Y' : defaultOptionNo);
setState(() {});
},
); );
} }
} }
@ -877,7 +923,7 @@ _getPopupDialogRadioEntry({
required String title, required String title,
required List<_RadioEntry> list, required List<_RadioEntry> list,
required _RadioEntryGetter getter, required _RadioEntryGetter getter,
required _RadioEntrySetter asyncSetter, required _RadioEntrySetter? asyncSetter,
Widget? tail, Widget? tail,
RxBool? showTail, RxBool? showTail,
String? notCloseValue, String? notCloseValue,
@ -897,21 +943,23 @@ _getPopupDialogRadioEntry({
void showDialog() async { void showDialog() async {
gFFI.dialogManager.show((setState, close, context) { gFFI.dialogManager.show((setState, close, context) {
onChanged(String? value) async { final onChanged = asyncSetter == null
if (value == null) return; ? null
await asyncSetter(value); : (String? value) async {
init(); if (value == null) return;
if (value != notCloseValue) { await asyncSetter(value);
close(); init();
} if (value != notCloseValue) {
} close();
}
};
return CustomAlertDialog( return CustomAlertDialog(
content: Obx( content: Obx(
() => Column(children: [ () => Column(children: [
...list ...list
.map((e) => getRadio(Text(translate(e.label)), e.value, .map((e) => getRadio(Text(translate(e.label)), e.value,
groupValue.value, (String? value) => onChanged(value))) groupValue.value, onChanged))
.toList(), .toList(),
Offstage( Offstage(
offstage: offstage:

View File

@ -509,7 +509,7 @@ class AbModel {
} }
void setShouldAsync(bool v) async { void setShouldAsync(bool v) async {
await bind.mainSetLocalOption(key: syncAbOption, value: v ? 'Y' : ''); await bind.mainSetLocalOption(key: syncAbOption, value: v ? 'Y' : defaultOptionNo);
_syncAllFromRecent = true; _syncAllFromRecent = true;
_timerCounter = 0; _timerCounter = 0;
} }

View File

@ -77,7 +77,7 @@ class ServerModel with ChangeNotifier {
String get approveMode => _approveMode; String get approveMode => _approveMode;
setVerificationMethod(String method) async { setVerificationMethod(String method) async {
await bind.mainSetOption(key: "verification-method", value: method); await bind.mainSetOption(key: kOptionVerificationMethod, value: method);
/* /*
if (method != kUsePermanentPassword) { if (method != kUsePermanentPassword) {
await bind.mainSetOption( await bind.mainSetOption(
@ -99,7 +99,7 @@ class ServerModel with ChangeNotifier {
} }
setApproveMode(String mode) async { setApproveMode(String mode) async {
await bind.mainSetOption(key: 'approve-mode', value: mode); await bind.mainSetOption(key: kOptionApproveMode, value: mode);
/* /*
if (mode != 'password') { if (mode != 'password') {
await bind.mainSetOption( await bind.mainSetOption(
@ -283,7 +283,7 @@ class ServerModel with ChangeNotifier {
} }
_audioOk = !_audioOk; _audioOk = !_audioOk;
bind.mainSetOption(key: "enable-audio", value: _audioOk ? '' : 'N'); bind.mainSetOption(key: "enable-audio", value: _audioOk ? defaultOptionYes : 'N');
notifyListeners(); notifyListeners();
} }
@ -302,7 +302,7 @@ class ServerModel with ChangeNotifier {
} }
_fileOk = !_fileOk; _fileOk = !_fileOk;
bind.mainSetOption(key: "enable-file-transfer", value: _fileOk ? '' : 'N'); bind.mainSetOption(key: kOptionEnableFileTransfer, value: _fileOk ? defaultOptionYes : 'N');
notifyListeners(); notifyListeners();
} }
@ -312,7 +312,7 @@ class ServerModel with ChangeNotifier {
} }
if (_inputOk) { if (_inputOk) {
parent.target?.invokeMethod("stop_input"); parent.target?.invokeMethod("stop_input");
bind.mainSetOption(key: "enable-keyboard", value: 'N'); bind.mainSetOption(key: kOptionEnableKeyboard, value: 'N');
} else { } else {
if (parent.target != null) { if (parent.target != null) {
/// the result of toggle-on depends on user actions in the settings page. /// the result of toggle-on depends on user actions in the settings page.
@ -445,7 +445,7 @@ class ServerModel with ChangeNotifier {
break; break;
case "input": case "input":
if (_inputOk != value) { if (_inputOk != value) {
bind.mainSetOption(key: "enable-keyboard", value: value ? '' : 'N'); bind.mainSetOption(key: kOptionEnableKeyboard, value: value ? defaultOptionYes : 'N');
} }
_inputOk = value; _inputOk = value;
break; break;

View File

@ -1610,5 +1610,9 @@ class RustdeskImpl {
throw UnimplementedError(); throw UnimplementedError();
} }
bool mainIsOptionFixed({required String key, dynamic hint}) {
throw UnimplementedError();
}
void dispose() {} void dispose() {}
} }

View File

@ -916,7 +916,7 @@ impl Config {
#[inline] #[inline]
fn purify_options(v: &mut HashMap<String, String>) { fn purify_options(v: &mut HashMap<String, String>) {
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<String, String>) { pub fn set_options(mut v: HashMap<String, String>) {
@ -940,7 +940,7 @@ impl Config {
} }
pub fn set_option(k: String, v: String) { 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; return;
} }
let mut config = CONFIG2.write().unwrap(); let mut config = CONFIG2.write().unwrap();
@ -1194,36 +1194,36 @@ impl PeerConfig {
serde_field_string!( serde_field_string!(
default_view_style, default_view_style,
deserialize_view_style, deserialize_view_style,
UserDefaultConfig::read("view_style") UserDefaultConfig::read(keys::OPTION_VIEW_STYLE)
); );
serde_field_string!( serde_field_string!(
default_scroll_style, default_scroll_style,
deserialize_scroll_style, deserialize_scroll_style,
UserDefaultConfig::read("scroll_style") UserDefaultConfig::read(keys::OPTION_SCROLL_STYLE)
); );
serde_field_string!( serde_field_string!(
default_image_quality, default_image_quality,
deserialize_image_quality, deserialize_image_quality,
UserDefaultConfig::read("image_quality") UserDefaultConfig::read(keys::OPTION_IMAGE_QUALITY)
); );
serde_field_string!( serde_field_string!(
default_reverse_mouse_wheel, default_reverse_mouse_wheel,
deserialize_reverse_mouse_wheel, deserialize_reverse_mouse_wheel,
UserDefaultConfig::read("reverse_mouse_wheel") UserDefaultConfig::read(keys::OPTION_REVERSE_MOUSE_WHEEL)
); );
serde_field_string!( serde_field_string!(
default_displays_as_individual_windows, default_displays_as_individual_windows,
deserialize_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!( serde_field_string!(
default_use_all_my_displays_for_the_remote_session, default_use_all_my_displays_for_the_remote_session,
deserialize_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<i32> { fn default_custom_image_quality() -> Vec<i32> {
let f: f64 = UserDefaultConfig::read("custom_image_quality") let f: f64 = UserDefaultConfig::read(keys::OPTION_CUSTOM_IMAGE_QUALITY)
.parse() .parse()
.unwrap_or(50.0); .unwrap_or(50.0);
vec![f as _] vec![f as _]
@ -1244,12 +1244,12 @@ impl PeerConfig {
fn default_options() -> HashMap<String, String> { fn default_options() -> HashMap<String, String> {
let mut mp: HashMap<String, String> = Default::default(); let mut mp: HashMap<String, String> = Default::default();
[ [
"codec-preference", keys::OPTION_CODEC_PREFERENCE,
"custom-fps", keys::OPTION_CUSTOM_FPS,
"zoom-cursor", keys::OPTION_ZOOM_CURSOR,
"touch-mode", keys::OPTION_TOUCH_MODE,
"i444", keys::OPTION_I444,
"swap-left-right-mouse", keys::OPTION_SWAP_LEFT_RIGHT_MOUSE,
] ]
.map(|key| { .map(|key| {
mp.insert(key.to_owned(), UserDefaultConfig::read(key)); mp.insert(key.to_owned(), UserDefaultConfig::read(key));
@ -1415,10 +1415,17 @@ impl LocalConfig {
} }
pub fn set_option(k: String, v: String) { 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; return;
} }
let mut config = LOCAL_CONFIG.write().unwrap(); 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) }; let v2 = if v.is_empty() { None } else { Some(&v) };
if v2 != config.options.get(&k) { if v2 != config.options.get(&k) {
if v2.is_none() { if v2.is_none() {
@ -1572,15 +1579,19 @@ impl UserDefaultConfig {
pub fn get(&self, key: &str) -> String { pub fn get(&self, key: &str) -> String {
match key { match key {
"view_style" => self.get_string(key, "original", vec!["adaptive"]), keys::OPTION_VIEW_STYLE => self.get_string(key, "original", vec!["adaptive"]),
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]), keys::OPTION_SCROLL_STYLE => self.get_string(key, "scrollauto", vec!["scrollbar"]),
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]), keys::OPTION_IMAGE_QUALITY => {
"codec-preference" => { self.get_string(key, "balanced", vec!["best", "low", "custom"])
}
keys::OPTION_CODEC_PREFERENCE => {
self.get_string(key, "auto", vec!["vp8", "vp9", "av1", "h264", "h265"]) 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), keys::OPTION_CUSTOM_IMAGE_QUALITY => {
"custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0), self.get_double_string(key, 50.0, 10.0, 0xFFF as f64)
"enable_file_transfer" => self.get_string(key, "Y", vec![""]), }
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 _ => self
.get_after(key) .get_after(key)
.map(|v| v.to_string()) .map(|v| v.to_string())
@ -1589,12 +1600,7 @@ impl UserDefaultConfig {
} }
pub fn set(&mut self, key: String, value: String) { pub fn set(&mut self, key: String, value: String) {
if !is_option_can_save( if !is_option_can_save(&OVERWRITE_DISPLAY_SETTINGS, &key) {
&OVERWRITE_DISPLAY_SETTINGS,
&DEFAULT_DISPLAY_SETTINGS,
&key,
&value,
) {
return; return;
} }
if value.is_empty() { if value.is_empty() {
@ -1917,18 +1923,10 @@ fn get_or(
} }
#[inline] #[inline]
fn is_option_can_save( fn is_option_can_save(overwrite: &RwLock<HashMap<String, String>>, k: &str) -> bool {
overwrite: &RwLock<HashMap<String, String>>,
defaults: &RwLock<HashMap<String, String>>,
k: &str,
v: &str,
) -> bool {
if overwrite.read().unwrap().contains_key(k) { if overwrite.read().unwrap().contains_key(k) {
return false; return false;
} }
if defaults.read().unwrap().get(k).map_or(false, |x| x == v) {
return false;
}
true true
} }
@ -1984,6 +1982,136 @@ pub fn is_disable_installation() -> bool {
is_some_hard_opton("disable-installation") 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -2022,6 +2150,10 @@ mod tests {
.write() .write()
.unwrap() .unwrap()
.insert("b".to_string(), "c".to_string()); .insert("b".to_string(), "c".to_string());
OVERWRITE_SETTINGS
.write()
.unwrap()
.insert("c".to_string(), "f".to_string());
OVERWRITE_SETTINGS OVERWRITE_SETTINGS
.write() .write()
.unwrap() .unwrap()
@ -2042,7 +2174,7 @@ mod tests {
res.insert("d".to_owned(), "c".to_string()); res.insert("d".to_owned(), "c".to_string());
res.insert("c".to_owned(), "a".to_string()); res.insert("c".to_owned(), "a".to_string());
res.insert("f".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); Config::purify_options(&mut res);
assert!(res.len() == 2); assert!(res.len() == 2);
res.insert("b".to_owned(), "c".to_string()); res.insert("b".to_owned(), "c".to_string());
@ -2055,11 +2187,11 @@ mod tests {
assert!(res.len() == 2); assert!(res.len() == 2);
let res = Config::get_options(); let res = Config::get_options();
assert!(res["a"] == "b"); assert!(res["a"] == "b");
assert!(res["c"] == "a"); assert!(res["c"] == "f");
assert!(res["b"] == "c"); assert!(res["b"] == "c");
assert!(res["d"] == "c"); assert!(res["d"] == "c");
assert!(Config::get_option("a") == "b"); 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("b") == "c");
assert!(Config::get_option("d") == "c"); assert!(Config::get_option("d") == "c");
DEFAULT_SETTINGS.write().unwrap().clear(); DEFAULT_SETTINGS.write().unwrap().clear();

View File

@ -1,5 +1,6 @@
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::HashMap,
future::Future, future::Future,
sync::{Arc, Mutex, RwLock}, sync::{Arc, Mutex, RwLock},
task::Poll, task::Poll,
@ -1597,22 +1598,41 @@ pub fn read_custom_client(config: &str) {
*config::APP_NAME.write().unwrap() = app_name.to_owned(); *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) = data.remove("default-settings") {
if let Some(default_settings) = default_settings.as_object() { if let Some(default_settings) = default_settings.as_object() {
for (k, v) in default_settings { for (k, v) in default_settings {
let Some(v) = v.as_str() else { let Some(v) = v.as_str() else {
continue; continue;
}; };
if k.starts_with("$$") { if let Some(k2) = map_display_settings.get(k) {
config::DEFAULT_DISPLAY_SETTINGS config::DEFAULT_DISPLAY_SETTINGS
.write() .write()
.unwrap() .unwrap()
.insert(k.clone(), v[2..].to_owned()); .insert(k2.to_string(), v.to_owned());
} else if k.starts_with("$") { } else if let Some(k2) = map_local_settings.get(k) {
config::DEFAULT_LOCAL_SETTINGS config::DEFAULT_LOCAL_SETTINGS
.write() .write()
.unwrap() .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 { } else {
config::DEFAULT_SETTINGS config::DEFAULT_SETTINGS
.write() .write()
@ -1628,16 +1648,21 @@ pub fn read_custom_client(config: &str) {
let Some(v) = v.as_str() else { let Some(v) = v.as_str() else {
continue; continue;
}; };
if k.starts_with("$$") { if let Some(k2) = map_display_settings.get(k) {
config::OVERWRITE_DISPLAY_SETTINGS config::OVERWRITE_DISPLAY_SETTINGS
.write() .write()
.unwrap() .unwrap()
.insert(k.clone(), v[2..].to_owned()); .insert(k2.to_string(), v.to_owned());
} else if k.starts_with("$") { } else if let Some(k2) = map_local_settings.get(k) {
config::OVERWRITE_LOCAL_SETTINGS config::OVERWRITE_LOCAL_SETTINGS
.write() .write()
.unwrap() .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 { } else {
config::OVERWRITE_SETTINGS config::OVERWRITE_SETTINGS
.write() .write()

View File

@ -939,7 +939,6 @@ pub fn main_handle_wayland_screencast_restore_token(_key: String, _value: String
} else { } else {
"".to_owned() "".to_owned()
} }
} }
pub fn main_get_input_source() -> SyncReturn<String> { pub fn main_get_input_source() -> SyncReturn<String> {
@ -1194,6 +1193,23 @@ pub fn main_handle_relay_id(id: String) -> String {
handle_relay_id(&id).to_owned() handle_relay_id(&id).to_owned()
} }
pub fn main_is_option_fixed(key: String) -> SyncReturn<bool> {
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<String> { pub fn main_get_main_display() -> SyncReturn<String> {
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
let display_info = "".to_owned(); let display_info = "".to_owned();