Merge branch 'master' into master

This commit is contained in:
yuluo
2024-04-23 03:03:45 +08:00
committed by GitHub
157 changed files with 6056 additions and 2087 deletions

View File

@@ -69,44 +69,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
);
}
Widget buildPresetPasswordWarning() {
return FutureBuilder<bool>(
future: bind.isPresetPassword(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // Show a loading spinner while waiting for the Future to complete
} else if (snapshot.hasError) {
return Text(
'Error: ${snapshot.error}'); // Show an error message if the Future completed with an error
} else if (snapshot.hasData && snapshot.data == true) {
return Container(
color: Colors.yellow,
child: Column(
children: [
Align(
child: Text(
translate("Security Alert"),
style: TextStyle(
color: Colors.red,
fontSize: 20,
fontWeight: FontWeight.bold,
),
)).paddingOnly(bottom: 8),
Text(
translate("preset_password_warning"),
style: TextStyle(color: Colors.red),
)
],
).paddingAll(8),
); // Show a warning message if the Future completed with true
} else {
return SizedBox
.shrink(); // Show nothing if the Future completed with false or null
}
},
);
}
Widget buildLeftPane(BuildContext context) {
final isIncomingOnly = bind.isIncomingOnly();
final isOutgoingOnly = bind.isOutgoingOnly();
@@ -115,22 +77,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
if (bind.isCustomClient())
Align(
alignment: Alignment.center,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
launchUrl(Uri.parse('https://rustdesk.com'));
},
child: Opacity(
opacity: 0.5,
child: Text(
translate("powered_by_me"),
overflow: TextOverflow.clip,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 9, decoration: TextDecoration.underline),
)),
),
).marginOnly(top: 6),
child: loadPowered(context),
),
Align(
alignment: Alignment.center,
@@ -298,19 +245,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
RxBool hover = false.obs;
return InkWell(
onTap: DesktopTabPage.onAddSetting,
child: Obx(
() => CircleAvatar(
radius: 15,
backgroundColor: hover.value
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).colorScheme.background,
child: Tooltip(
message: translate('Settings'),
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value ? textColor : textColor?.withOpacity(0.5),
)),
child: Tooltip(
message: translate('Settings'),
child: Obx(
() => CircleAvatar(
radius: 15,
backgroundColor: hover.value
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).colorScheme.background,
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value ? textColor : textColor?.withOpacity(0.5),
),
),
),
),
onHover: (value) => hover.value = value,
@@ -371,31 +319,33 @@ class _DesktopHomePageState extends State<DesktopHomePage>
),
AnimatedRotationWidget(
onPressed: () => bind.mainUpdateTemporaryPassword(),
child: Obx(() => RotatedBox(
quarterTurns: 2,
child: Tooltip(
message: translate('Refresh Password'),
child: Icon(
Icons.refresh,
color: refreshHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
)))),
child: Tooltip(
message: translate('Refresh Password'),
child: Obx(() => RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.refresh,
color: refreshHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
))),
),
onHover: (value) => refreshHover.value = value,
).marginOnly(right: 8, top: 4),
if (!bind.isDisableSettings())
InkWell(
child: Obx(
() => Tooltip(
message: translate('Change Password'),
child: Icon(
Icons.edit,
color: editHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
)).marginOnly(right: 8, top: 4),
child: Tooltip(
message: translate('Change Password'),
child: Obx(
() => Icon(
Icons.edit,
color: editHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
).marginOnly(right: 8, top: 4),
),
),
onTap: () => DesktopSettingPage.switch2page(0),
onHover: (value) => editHover.value = value,

View File

@@ -400,11 +400,20 @@ class _GeneralState extends State<_General> {
Widget hwcodec() {
final hwcodec = bind.mainHasHwcodec();
final gpucodec = bind.mainHasGpucodec();
final vram = bind.mainHasVram();
return Offstage(
offstage: !(hwcodec || gpucodec),
offstage: !(hwcodec || vram),
child: _Card(title: 'Hardware Codec', children: [
_OptionCheckBox(context, 'Enable hardware codec', 'enable-hwcodec')
_OptionCheckBox(
context,
'Enable hardware codec',
'enable-hwcodec',
update: () {
if (mainGetBoolOptionSync('enable-hwcodec')) {
bind.mainCheckHwcodec();
}
},
)
]),
);
}
@@ -835,6 +844,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
...directIp(context),
whitelist(),
...autoDisconnect(context),
if (bind.mainIsInstalled())
_OptionCheckBox(context, 'allow-only-conn-window-open-tip',
'allow-only-conn-window-open',
reverse: false, enabled: enabled),
]);
}

View File

@@ -568,6 +568,47 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
}
}
// Compatible with MenuEntrySwitch, it uses value instead of getter
class MenuEntrySwitchSync<T> extends MenuEntrySwitchBase<T> {
final SwitchSetter setter;
final RxBool _curOption = false.obs;
MenuEntrySwitchSync({
required SwitchType switchType,
required String text,
required bool currentValue,
required this.setter,
Rx<TextStyle>? textStyle,
EdgeInsets? padding,
dismissOnClicked = false,
RxBool? enabled,
dismissCallback,
}) : super(
switchType: switchType,
text: text,
textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked,
enabled: enabled,
dismissCallback: dismissCallback,
) {
_curOption.value = currentValue;
}
@override
RxBool get curOption => _curOption;
@override
setOption(bool? option) async {
if (option != null) {
await setter(option);
// Notice: no ensure with getter, best used on menus that are destroyed on click
if (_curOption.value != option) {
_curOption.value = option;
}
}
}
}
typedef Switch2Getter = RxBool Function();
typedef Switch2Setter = Future<void> Function(bool);

View File

@@ -636,7 +636,7 @@ class _MonitorMenu extends StatelessWidget {
menuStyle: MenuStyle(
padding:
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
menuChildren: [buildMonitorSubmenuWidget()]);
menuChildrenGetter: () => [buildMonitorSubmenuWidget()]);
}
Widget buildMultiMonitorMenu() {
@@ -843,17 +843,17 @@ class _ControlMenu extends StatelessWidget {
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
ffi: ffi,
menuChildren: toolbarControls(context, id, ffi).map((e) {
if (e.divider) {
return Divider();
} else {
return MenuButton(
child: e.child,
onPressed: e.onPressed,
ffi: ffi,
trailingIcon: e.trailingIcon);
}
}).toList());
menuChildrenGetter: () => toolbarControls(context, id, ffi).map((e) {
if (e.divider) {
return Divider();
} else {
return MenuButton(
child: e.child,
onPressed: e.onPressed,
ffi: ffi,
trailingIcon: e.trailingIcon);
}
}).toList());
}
}
@@ -1046,53 +1046,61 @@ class _DisplayMenuState extends State<_DisplayMenu> {
Widget build(BuildContext context) {
_screenAdjustor.updateScreen();
final menuChildren = <Widget>[
_screenAdjustor.adjustWindow(context),
viewStyle(),
scrollStyle(),
imageQuality(),
codec(),
_ResolutionsMenu(
id: widget.id,
ffi: widget.ffi,
screenAdjustor: _screenAdjustor,
),
// We may add this feature if it is needed and we have an EV certificate.
// _VirtualDisplayMenu(
// id: widget.id,
// ffi: widget.ffi,
// ),
Divider(),
toggles(),
];
// privacy mode
if (ffiModel.keyboard && pi.features.privacyMode) {
final privacyModeState = PrivacyModeState.find(id);
final privacyModeList =
toolbarPrivacyMode(privacyModeState, context, id, ffi);
if (privacyModeList.length == 1) {
menuChildren.add(CkbMenuButton(
value: privacyModeList[0].value,
onChanged: privacyModeList[0].onChanged,
child: privacyModeList[0].child,
ffi: ffi));
} else if (privacyModeList.length > 1) {
menuChildren.addAll([
Divider(),
_SubmenuButton(
ffi: widget.ffi,
child: Text(translate('Privacy mode')),
menuChildren: privacyModeList
.map((e) => CkbMenuButton(
value: e.value,
onChanged: e.onChanged,
child: e.child,
ffi: ffi))
.toList()),
]);
menuChildrenGetter() {
final menuChildren = <Widget>[
_screenAdjustor.adjustWindow(context),
viewStyle(),
scrollStyle(),
imageQuality(),
codec(),
_ResolutionsMenu(
id: widget.id,
ffi: widget.ffi,
screenAdjustor: _screenAdjustor,
),
if (pi.isRustDeskIdd)
_RustDeskVirtualDisplayMenu(
id: widget.id,
ffi: widget.ffi,
),
if (pi.isAmyuniIdd)
_AmyuniVirtualDisplayMenu(
id: widget.id,
ffi: widget.ffi,
),
Divider(),
toggles(),
];
// privacy mode
if (ffiModel.keyboard && pi.features.privacyMode) {
final privacyModeState = PrivacyModeState.find(id);
final privacyModeList =
toolbarPrivacyMode(privacyModeState, context, id, ffi);
if (privacyModeList.length == 1) {
menuChildren.add(CkbMenuButton(
value: privacyModeList[0].value,
onChanged: privacyModeList[0].onChanged,
child: privacyModeList[0].child,
ffi: ffi));
} else if (privacyModeList.length > 1) {
menuChildren.addAll([
Divider(),
_SubmenuButton(
ffi: widget.ffi,
child: Text(translate('Privacy mode')),
menuChildren: privacyModeList
.map((e) => CkbMenuButton(
value: e.value,
onChanged: e.onChanged,
child: e.child,
ffi: ffi))
.toList()),
]);
}
}
menuChildren.add(widget.pluginItem);
return menuChildren;
}
menuChildren.add(widget.pluginItem);
return _IconSubmenuButton(
tooltip: 'Display Settings',
@@ -1100,7 +1108,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
ffi: widget.ffi,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuChildren: menuChildren,
menuChildrenGetter: menuChildrenGetter,
);
}
@@ -1537,21 +1545,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
}
}
class _VirtualDisplayMenu extends StatefulWidget {
class _RustDeskVirtualDisplayMenu extends StatefulWidget {
final String id;
final FFI ffi;
_VirtualDisplayMenu({
_RustDeskVirtualDisplayMenu({
Key? key,
required this.id,
required this.ffi,
}) : super(key: key);
@override
State<_VirtualDisplayMenu> createState() => _VirtualDisplayMenuState();
State<_RustDeskVirtualDisplayMenu> createState() =>
_RustDeskVirtualDisplayMenuState();
}
class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
class _RustDeskVirtualDisplayMenuState
extends State<_RustDeskVirtualDisplayMenu> {
@override
void initState() {
super.initState();
@@ -1566,7 +1576,7 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
return Offstage();
}
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
final virtualDisplays = widget.ffi.ffiModel.pi.RustDeskVirtualDisplays;
final privacyModeState = PrivacyModeState.find(widget.id);
final children = <Widget>[];
@@ -1608,6 +1618,82 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
}
}
class _AmyuniVirtualDisplayMenu extends StatefulWidget {
final String id;
final FFI ffi;
_AmyuniVirtualDisplayMenu({
Key? key,
required this.id,
required this.ffi,
}) : super(key: key);
@override
State<_AmyuniVirtualDisplayMenu> createState() =>
_AmiyuniVirtualDisplayMenuState();
}
class _AmiyuniVirtualDisplayMenuState extends State<_AmyuniVirtualDisplayMenu> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
if (widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) {
return Offstage();
}
if (!widget.ffi.ffiModel.pi.isInstalled) {
return Offstage();
}
final count = widget.ffi.ffiModel.pi.amyuniVirtualDisplayCount;
final privacyModeState = PrivacyModeState.find(widget.id);
final children = <Widget>[
Obx(() => Row(
children: [
TextButton(
onPressed: privacyModeState.isNotEmpty || count == 0
? null
: () => bind.sessionToggleVirtualDisplay(
sessionId: widget.ffi.sessionId, index: 0, on: false),
child: Icon(Icons.remove),
),
Text(count.toString()),
TextButton(
onPressed: privacyModeState.isNotEmpty || count == 4
? null
: () => bind.sessionToggleVirtualDisplay(
sessionId: widget.ffi.sessionId, index: 0, on: true),
child: Icon(Icons.add),
),
],
)),
Divider(),
Obx(() => MenuButton(
onPressed: privacyModeState.isNotEmpty || count == 0
? null
: () {
bind.sessionToggleVirtualDisplay(
sessionId: widget.ffi.sessionId,
index: kAllVirtualDisplay,
on: false);
},
ffi: widget.ffi,
child: Text(translate('Plug out all')),
)),
];
return _SubmenuButton(
ffi: widget.ffi,
menuChildren: children,
child: Text(translate("Virtual display")),
);
}
}
class _KeyboardMenu extends StatelessWidget {
final String id;
final FFI ffi;
@@ -1623,19 +1709,7 @@ class _KeyboardMenu extends StatelessWidget {
Widget build(BuildContext context) {
var ffiModel = Provider.of<FfiModel>(context);
if (!ffiModel.keyboard) return Offstage();
// If use flutter to grab keys, we can only use one mode.
// Map mode and Legacy mode, at least one of them is supported.
String? modeOnly;
if (isInputSourceFlutter) {
if (bind.sessionIsKeyboardModeSupported(
sessionId: ffi.sessionId, mode: kKeyMapMode)) {
modeOnly = kKeyMapMode;
} else if (bind.sessionIsKeyboardModeSupported(
sessionId: ffi.sessionId, mode: kKeyLegacyMode)) {
modeOnly = kKeyLegacyMode;
}
}
final toolbarToggles = toolbarKeyboardToggles(ffi)
toolbarToggles() => toolbarKeyboardToggles(ffi)
.map((e) => CkbMenuButton(
value: e.value, onChanged: e.onChanged, child: e.child, ffi: ffi))
.toList();
@@ -1645,18 +1719,18 @@ class _KeyboardMenu extends StatelessWidget {
ffi: ffi,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuChildren: [
keyboardMode(modeOnly),
localKeyboardType(),
inputSource(),
Divider(),
viewMode(),
Divider(),
...toolbarToggles,
]);
menuChildrenGetter: () => [
keyboardMode(),
localKeyboardType(),
inputSource(),
Divider(),
viewMode(),
Divider(),
...toolbarToggles(),
]);
}
keyboardMode(String? modeOnly) {
keyboardMode() {
return futureBuilder(future: () async {
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??
kKeyLegacyMode;
@@ -1676,6 +1750,19 @@ class _KeyboardMenu extends StatelessWidget {
await ffi.inputModel.updateKeyboardMode();
}
// If use flutter to grab keys, we can only use one mode.
// Map mode and Legacy mode, at least one of them is supported.
String? modeOnly;
if (isInputSourceFlutter) {
if (bind.sessionIsKeyboardModeSupported(
sessionId: ffi.sessionId, mode: kKeyMapMode)) {
modeOnly = kKeyMapMode;
} else if (bind.sessionIsKeyboardModeSupported(
sessionId: ffi.sessionId, mode: kKeyLegacyMode)) {
modeOnly = kKeyLegacyMode;
}
}
for (InputModeMenu mode in modes) {
if (modeOnly != null && mode.key != modeOnly) {
continue;
@@ -1804,7 +1891,7 @@ class _ChatMenuState extends State<_ChatMenu> {
ffi: widget.ffi,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuChildren: [textChat(), voiceCall()]);
menuChildrenGetter: () => [textChat(), voiceCall()]);
}
textChat() {
@@ -2008,7 +2095,7 @@ class _IconSubmenuButton extends StatefulWidget {
final Widget? icon;
final Color color;
final Color hoverColor;
final List<Widget> menuChildren;
final List<Widget> Function() menuChildrenGetter;
final MenuStyle? menuStyle;
final FFI ffi;
final double? width;
@@ -2020,7 +2107,7 @@ class _IconSubmenuButton extends StatefulWidget {
required this.tooltip,
required this.color,
required this.hoverColor,
required this.menuChildren,
required this.menuChildrenGetter,
required this.ffi,
this.menuStyle,
this.width,
@@ -2064,7 +2151,8 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
color: hover ? widget.hoverColor : widget.color,
),
child: icon))),
menuChildren: widget.menuChildren
menuChildren: widget
.menuChildrenGetter()
.map((e) => _buildPointerTrackWidget(e, widget.ffi))
.toList()));
return MenuBar(children: [

View File

@@ -865,6 +865,7 @@ class _ListView extends StatelessWidget {
label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
tabType: controller.tabType,
selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon,
closable: tab.closable,
@@ -896,6 +897,7 @@ class _Tab extends StatefulWidget {
final int index;
final String tabInfoKey;
final Rx<String> label;
final DesktopTabType tabType;
final IconData? selectedIcon;
final IconData? unselectedIcon;
final bool closable;
@@ -914,6 +916,7 @@ class _Tab extends StatefulWidget {
required this.index,
required this.tabInfoKey,
required this.label,
required this.tabType,
this.selectedIcon,
this.unselectedIcon,
this.tabBuilder,
@@ -953,7 +956,9 @@ class _TabState extends State<_Tab> with RestorationMixin {
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
child: Tooltip(
message: translate(widget.label.value),
message: widget.tabType == DesktopTabType.main
? ''
: translate(widget.label.value),
child: Text(
translate(widget.label.value),
textAlign: TextAlign.center,