Files
rustdesk/flutter/lib/common/widgets/setting_widgets.dart
RustDesk 5faf0ad3cf terminal works basically. (#12189)
* terminal works basically.
todo:
- persistent
- sessions restore
- web
- mobile

* missed terminal persistent option change

* android sdk 34 -> 35

* +#![cfg_attr(lt_1_77, feature(c_str_literals))]

* fixing ci

* fix ci

* fix ci for android

* try "Fix Android SDK Platform 35"

* fix android 34

* revert flutter_plugin_android_lifecycle to 2.0.17 which used in rustdesk 1.4.0

* refactor, but break something of desktop terminal (new tab showing loading)

* fix connecting...
2025-07-01 13:12:55 +08:00

342 lines
11 KiB
Dart

import 'package:debounce_throttle/debounce_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
customImageQualityWidget(
{required double initQuality,
required double initFps,
required Function(double)? setQuality,
required Function(double)? setFps,
required bool showFps,
required bool showMoreQuality}) {
if (initQuality < kMinQuality ||
initQuality > (showMoreQuality ? kMaxMoreQuality : kMaxQuality)) {
initQuality = kDefaultQuality;
}
if (initFps < kMinFps || initFps > kMaxFps) {
initFps = kDefaultFps;
}
final qualityValue = initQuality.obs;
final fpsValue = initFps.obs;
final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality);
final debouncerQuality = Debouncer<double>(
Duration(milliseconds: 1000),
onChanged: setQuality,
initialValue: qualityValue.value,
);
final debouncerFps = Debouncer<double>(
Duration(milliseconds: 1000),
onChanged: setFps,
initialValue: fpsValue.value,
);
onMoreChanged(bool? value) {
if (value == null) return;
moreQualityChecked.value = value;
if (!value && qualityValue.value > 100) {
qualityValue.value = 100;
}
debouncerQuality.value = qualityValue.value;
}
return Column(
children: [
Obx(() => Row(
children: [
Expanded(
flex: 3,
child: Slider(
value: qualityValue.value,
min: kMinQuality,
max: moreQualityChecked.value ? kMaxMoreQuality : kMaxQuality,
divisions: moreQualityChecked.value
? ((kMaxMoreQuality - kMinQuality) / 10).round()
: ((kMaxQuality - kMinQuality) / 5).round(),
onChanged: setQuality == null
? null
: (double value) async {
qualityValue.value = value;
debouncerQuality.value = value;
},
),
),
Expanded(
flex: 1,
child: Text(
'${qualityValue.value.round()}%',
style: const TextStyle(fontSize: 15),
)),
Expanded(
flex: isMobile ? 2 : 1,
child: Text(
translate('Bitrate'),
style: const TextStyle(fontSize: 15),
)),
// mobile doesn't have enough space
if (showMoreQuality && !isMobile)
Expanded(
flex: 1,
child: Row(
children: [
Checkbox(
value: moreQualityChecked.value,
onChanged: onMoreChanged,
),
Expanded(
child: Text(translate('More')),
)
],
))
],
)),
if (showMoreQuality && isMobile)
Obx(() => Row(
children: [
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Checkbox(
value: moreQualityChecked.value,
onChanged: onMoreChanged,
),
),
),
Expanded(
child: Text(translate('More')),
)
],
)),
if (showFps)
Obx(() => Row(
children: [
Expanded(
flex: 3,
child: Slider(
value: fpsValue.value,
min: kMinFps,
max: kMaxFps,
divisions: ((kMaxFps - kMinFps) / 5).round(),
onChanged: setFps == null
? null
: (double value) async {
fpsValue.value = value;
debouncerFps.value = value;
},
),
),
Expanded(
flex: 1,
child: Text(
'${fpsValue.value.round()}',
style: const TextStyle(fontSize: 15),
)),
Expanded(
flex: 2,
child: Text(
translate('FPS'),
style: const TextStyle(fontSize: 15),
))
],
)),
],
);
}
customImageQualitySetting() {
final qualityKey = 'custom_image_quality';
final fpsKey = 'custom-fps';
final initQuality =
(double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ??
kDefaultQuality);
final isQuanlityFixed = isOptionFixed(qualityKey);
final initFps =
(double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ??
kDefaultFps);
final isFpsFixed = isOptionFixed(fpsKey);
return customImageQualityWidget(
initQuality: initQuality,
initFps: initFps,
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);
}
List<Widget> ServerConfigImportExportWidgets(
List<TextEditingController> controllers,
List<RxString> errMsgs,
) {
import() {
Clipboard.getData(Clipboard.kTextPlain).then((value) {
importConfig(controllers, errMsgs, value?.text);
});
}
export() {
final text = ServerConfig(
idServer: controllers[0].text.trim(),
relayServer: controllers[1].text.trim(),
apiServer: controllers[2].text.trim(),
key: controllers[3].text.trim())
.encode();
debugPrint("ServerConfig export: $text");
Clipboard.setData(ClipboardData(text: text));
showToast(translate('Export server configuration successfully'));
}
return [
Tooltip(
message: translate('Import server config'),
child: IconButton(
icon: Icon(Icons.paste, color: Colors.grey), onPressed: import),
),
Tooltip(
message: translate('Export Server Config'),
child: IconButton(
icon: Icon(Icons.copy, color: Colors.grey), onPressed: export))
];
}
List<(String, String)> otherDefaultSettings() {
List<(String, String)> v = [
('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', kOptionEnableFileCopyPaste),
('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', kOptionSwapLeftRightMouse),
if (isDesktop)
(
'Show displays as individual windows',
kKeyShowDisplaysAsIndividualWindows
),
if (isDesktop)
(
'Use all my displays for the remote session',
kKeyUseAllMyDisplaysForTheRemoteSession
),
('Keep terminal sessions on disconnect', kOptionTerminalPersistent),
];
return v;
}
class TrackpadSpeedWidget extends StatefulWidget {
final SimpleWrapper<int> value;
// If null, no debouncer will be applied.
final Function(int)? onDebouncer;
TrackpadSpeedWidget({Key? key, required this.value, this.onDebouncer});
@override
TrackpadSpeedWidgetState createState() => TrackpadSpeedWidgetState();
}
class TrackpadSpeedWidgetState extends State<TrackpadSpeedWidget> {
final TextEditingController _controller = TextEditingController();
late final Debouncer<int> debouncerSpeed;
set value(int v) => widget.value.value = v;
int get value => widget.value.value;
void updateValue(int newValue) {
setState(() {
value = newValue.clamp(kMinTrackpadSpeed, kMaxTrackpadSpeed);
// Scale the trackpad speed value to a percentage for display purposes.
_controller.text = value.toString();
if (widget.onDebouncer != null) {
debouncerSpeed.setValue(value);
}
});
}
@override
void initState() {
super.initState();
debouncerSpeed = Debouncer<int>(
Duration(milliseconds: 1000),
onChanged: widget.onDebouncer,
initialValue: widget.value.value,
);
}
@override
Widget build(BuildContext context) {
if (_controller.text.isEmpty) {
_controller.text = value.toString();
}
return Row(
children: [
Expanded(
flex: 3,
child: Slider(
value: value.toDouble(),
min: kMinTrackpadSpeed.toDouble(),
max: kMaxTrackpadSpeed.toDouble(),
divisions: ((kMaxTrackpadSpeed - kMinTrackpadSpeed) / 10).round(),
onChanged: (double v) => updateValue(v.round()),
),
),
Expanded(
flex: 1,
child: Row(
children: [
SizedBox(
width: 56,
child: TextField(
controller: _controller,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
onSubmitted: (text) {
int? v = int.tryParse(text);
if (v != null) {
updateValue(v);
}
},
style: const TextStyle(fontSize: 13),
decoration: InputDecoration(
contentPadding:
EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
),
),
).marginOnly(right: 8.0),
Text(
'%',
style: const TextStyle(fontSize: 15),
)
],
)),
],
);
}
}