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
18 changed files with 707 additions and 364 deletions

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