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);
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' : '';

View File

@ -333,6 +333,7 @@ class _AddressBookState extends State<AddressBook> {
@protected
MenuEntryBase<String> syncMenuItem() {
final isOptFixed = isOptionFixed(syncAbOption);
return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate('Sync with recent sessions'),
@ -343,11 +344,13 @@ class _AddressBookState extends State<AddressBook> {
gFFI.abModel.setShouldAsync(v);
},
dismissOnClicked: true,
enabled: (!isOptFixed).obs,
);
}
@protected
MenuEntryBase<String> sortMenuItem() {
final isOptFixed = isOptionFixed(sortAbTagsOption);
return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate('Sort tags'),
@ -355,15 +358,17 @@ class _AddressBookState extends State<AddressBook> {
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<String> filterMenuItem() {
final isOptFixed = isOptionFixed(filterAbTagOption);
return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate('Filter by intersection'),
@ -371,10 +376,11 @@ class _AddressBookState extends State<AddressBook> {
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,
);
}

View File

@ -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<String> 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<String> 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();
}),
],

View File

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

View File

@ -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<double>(
Duration(milliseconds: 1000),
onChanged: (double v) {
setQuality(v);
},
onChanged: setQuality,
initialValue: qualityValue.value,
);
final debouncerFps = Debouncer<double>(
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<Widget> 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',

View File

@ -327,7 +327,7 @@ Future<List<TRadioMenu<String>>> toolbarCodec(
final alternativeCodecs =
await bind.sessionAlternativeCodecs(sessionId: sessionId);
final groupValue = await bind.sessionGetOption(
sessionId: sessionId, arg: 'codec-preference') ??
sessionId: sessionId, arg: kOptionCodecPreference) ??
'';
final List<bool> codecs = [];
try {
@ -349,7 +349,7 @@ Future<List<TRadioMenu<String>>> 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);
}

View File

@ -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 ================================

View File

@ -332,22 +332,23 @@ class _GeneralState extends State<_General> {
setState(() {});
}
final isOptFixed = isOptionFixed(kCommConfKeyTheme);
return _Card(title: 'Theme', children: [
_Radio<String>(context,
value: 'light',
groupValue: current,
label: 'Light',
onChanged: onChanged),
onChanged: isOptFixed ? null : onChanged),
_Radio<String>(context,
value: 'dark',
groupValue: current,
label: 'Dark',
onChanged: onChanged),
onChanged: isOptFixed ? null : onChanged),
_Radio<String>(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<String, String> langsMap = {for (var v in langsList) v[0]: v[1]};
List<String> keys = langsMap.keys.toList();
List<String> 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 = <String>[
'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<T>(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<T>(BuildContext context,
return GestureDetector(
child: Row(
children: [
Radio<T>(value: value, groupValue: groupValue, onChanged: onChange),
Radio<T>(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),
);
}

View File

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

View File

@ -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);
}

View File

@ -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);
}
}
})

View File

@ -100,8 +100,8 @@ class _SettingsState extends State<SettingsPage> 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<SettingsPage> 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<SettingsPage> 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<SettingsPage> 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<SettingsPage> 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<SettingsPage> 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<SettingsPage> 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<SettingsPage> 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<dynamic>;
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:

View File

@ -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;
}

View File

@ -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;

View File

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

View File

@ -916,7 +916,7 @@ impl Config {
#[inline]
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>) {
@ -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<i32> {
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<String, String> {
let mut mp: HashMap<String, String> = 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<HashMap<String, String>>,
defaults: &RwLock<HashMap<String, String>>,
k: &str,
v: &str,
) -> bool {
fn is_option_can_save(overwrite: &RwLock<HashMap<String, String>>, 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();

View File

@ -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()

View File

@ -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<String> {
@ -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<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> {
#[cfg(target_os = "ios")]
let display_info = "".to_owned();