Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	Cargo.lock
#	src/server/connection.rs
This commit is contained in:
mcfans
2023-11-07 12:13:15 +08:00
166 changed files with 10361 additions and 6739 deletions

View File

@@ -50,6 +50,7 @@ class _ConnectionPageState extends State<ConnectionPage>
return list.sublist(0, n);
}
}
bool isPeersLoading = false;
bool isPeersLoaded = false;
@@ -81,7 +82,7 @@ class _ConnectionPageState extends State<ConnectionPage>
if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>();
}
if (Get.isRegistered<TextEditingController>()){
if (Get.isRegistered<TextEditingController>()) {
Get.delete<TextEditingController>();
}
super.dispose();
@@ -157,9 +158,9 @@ class _ConnectionPageState extends State<ConnectionPage>
await Future.delayed(Duration(milliseconds: 100));
peers = await getAllPeers();
setState(() {
isPeersLoading = false;
isPeersLoaded = true;
});
isPeersLoading = false;
isPeersLoaded = true;
});
}
/// UI for the remote ID TextField.
@@ -177,148 +178,173 @@ class _ConnectionPageState extends State<ConnectionPage>
Row(
children: [
Expanded(
child: AutoSizeText(
translate('Control Remote Desktop'),
maxLines: 1,
style: Theme.of(context)
.textTheme
.titleLarge
?.merge(TextStyle(height: 1)),
),
),
child: Row(
children: [
AutoSizeText(
translate('Control Remote Desktop'),
maxLines: 1,
style: Theme.of(context)
.textTheme
.titleLarge
?.merge(TextStyle(height: 1)),
).marginOnly(right: 4),
Tooltip(
waitDuration: Duration(milliseconds: 0),
message: translate("id_input_tip"),
child: Icon(
Icons.help_outline_outlined,
size: 16,
color: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5),
),
),
],
)),
],
).marginOnly(bottom: 15),
Row(
children: [
Expanded(
child:
Autocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<Peer>.empty();
}
else if (peers.isEmpty && !isPeersLoaded) {
Peer emptyPeer = Peer(
id: '',
username: '',
hostname: '',
alias: '',
platform: '',
tags: [],
hash: '',
forceAlwaysRelay: false,
rdpPort: '',
rdpUsername: '',
loginName: '',
child: Autocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<Peer>.empty();
} else if (peers.isEmpty && !isPeersLoaded) {
Peer emptyPeer = Peer(
id: '',
username: '',
hostname: '',
alias: '',
platform: '',
tags: [],
hash: '',
forceAlwaysRelay: false,
rdpPort: '',
rdpUsername: '',
loginName: '',
);
return [emptyPeer];
} else {
String textWithoutSpaces =
textEditingValue.text.replaceAll(" ", "");
if (int.tryParse(textWithoutSpaces) != null) {
textEditingValue = TextEditingValue(
text: textWithoutSpaces,
selection: textEditingValue.selection,
);
return [emptyPeer];
}
else {
String textWithoutSpaces = textEditingValue.text.replaceAll(" ", "");
if (int.tryParse(textWithoutSpaces) != null) {
textEditingValue = TextEditingValue(
text: textWithoutSpaces,
selection: textEditingValue.selection,
);
}
String textToFind = textEditingValue.text.toLowerCase();
String textToFind = textEditingValue.text.toLowerCase();
return peers.where((peer) =>
peer.id.toLowerCase().contains(textToFind) ||
peer.username.toLowerCase().contains(textToFind) ||
peer.hostname.toLowerCase().contains(textToFind) ||
peer.alias.toLowerCase().contains(textToFind))
.toList();
return peers
.where((peer) =>
peer.id.toLowerCase().contains(textToFind) ||
peer.username
.toLowerCase()
.contains(textToFind) ||
peer.hostname
.toLowerCase()
.contains(textToFind) ||
peer.alias.toLowerCase().contains(textToFind))
.toList();
}
},
fieldViewBuilder: (
BuildContext context,
TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted,
) {
fieldTextEditingController.text = _idController.text;
Get.put<TextEditingController>(fieldTextEditingController);
fieldFocusNode.addListener(() async {
_idInputFocused.value = fieldFocusNode.hasFocus;
if (fieldFocusNode.hasFocus && !isPeersLoading) {
_fetchPeers();
}
},
});
final textLength =
fieldTextEditingController.value.text.length;
// select all to facilitate removing text, just following the behavior of address input of chrome
fieldTextEditingController.selection =
TextSelection(baseOffset: 0, extentOffset: textLength);
return Obx(() => TextField(
autocorrect: false,
enableSuggestions: false,
keyboardType: TextInputType.visiblePassword,
focusNode: fieldFocusNode,
style: const TextStyle(
fontFamily: 'WorkSans',
fontSize: 22,
height: 1.4,
),
maxLines: 1,
cursorColor:
Theme.of(context).textTheme.titleLarge?.color,
decoration: InputDecoration(
filled: false,
counterText: '',
hintText: _idInputFocused.value
? null
: translate('Enter Remote ID'),
contentPadding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 13)),
controller: fieldTextEditingController,
inputFormatters: [IDTextInputFormatter()],
onChanged: (v) {
_idController.id = v;
},
));
},
onSelected: (option) {
setState(() {
_idController.id = option.id;
FocusScope.of(context).unfocus();
});
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<Peer> onSelected,
Iterable<Peer> options) {
double maxHeight = options.length * 50;
maxHeight = maxHeight > 200 ? 200 : maxHeight;
fieldViewBuilder: (BuildContext context,
TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode ,
VoidCallback onFieldSubmitted,
) {
fieldTextEditingController.text = _idController.text;
Get.put<TextEditingController>(fieldTextEditingController);
fieldFocusNode.addListener(() async {
_idInputFocused.value = fieldFocusNode.hasFocus;
if (fieldFocusNode.hasFocus && !isPeersLoading){
_fetchPeers();
}
});
final textLength = fieldTextEditingController.value.text.length;
// select all to facilitate removing text, just following the behavior of address input of chrome
fieldTextEditingController.selection = TextSelection(baseOffset: 0, extentOffset: textLength);
return Obx(() =>
TextField(
maxLength: 90,
autocorrect: false,
enableSuggestions: false,
keyboardType: TextInputType.visiblePassword,
focusNode: fieldFocusNode,
style: const TextStyle(
fontFamily: 'WorkSans',
fontSize: 22,
height: 1.4,
),
maxLines: 1,
cursorColor: Theme.of(context).textTheme.titleLarge?.color,
decoration: InputDecoration(
filled: false,
counterText: '',
hintText: _idInputFocused.value
? null
: translate('Enter Remote ID'),
contentPadding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 13)),
controller: fieldTextEditingController,
inputFormatters: [IDTextInputFormatter()],
onChanged: (v) {
_idController.id = v;
},
));
},
onSelected: (option) {
setState(() {
_idController.id = option.id;
FocusScope.of(context).unfocus();
});
},
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<Peer> onSelected, Iterable<Peer> options) {
double maxHeight = options.length * 50;
maxHeight = maxHeight > 200 ? 200 : maxHeight;
return Align(
alignment: Alignment.topLeft,
child: ClipRRect(
return Align(
alignment: Alignment.topLeft,
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Material(
elevation: 4,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: maxHeight,
maxWidth: 319,
),
child: peers.isEmpty && isPeersLoading
? Container(
height: 80,
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
)
: Padding(
padding: const EdgeInsets.only(top: 5),
child: ListView(
children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(),
elevation: 4,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: maxHeight,
maxWidth: 319,
),
child: peers.isEmpty && isPeersLoading
? Container(
height: 80,
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
))
: Padding(
padding: const EdgeInsets.only(top: 5),
child: ListView(
children: options
.map((peer) => AutocompletePeerTile(
onSelect: () =>
onSelected(peer),
peer: peer))
.toList(),
),
),
),
),
)),
);
},
)
),
)),
);
},
)),
],
),
Padding(
@@ -329,7 +355,7 @@ class _ConnectionPageState extends State<ConnectionPage>
Button(
isOutline: true,
onTap: () => onConnect(isFileTransfer: true),
text: "Transfer File",
text: "Transfer file",
),
const SizedBox(
width: 17,
@@ -382,7 +408,7 @@ class _ConnectionPageState extends State<ConnectionPage>
onTap: () async {
await start_service(true);
},
child: Text(translate("Start Service"),
child: Text(translate("Start service"),
style: TextStyle(
decoration: TextDecoration.underline,
fontSize: em)))

View File

@@ -336,11 +336,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
if (Platform.isWindows) {
if (!bind.mainIsInstalled()) {
return buildInstallCard(
"", "install_tip", "Install", bind.mainGotoInstall);
return buildInstallCard("", "install_tip", "Install", () async {
await rustDeskWinManager.closeAllSubWindows();
bind.mainGotoInstall();
});
} else if (bind.mainIsInstalledLowerVersion()) {
return buildInstallCard("Status", "Your installation is lower version.",
"Click to upgrade", bind.mainUpdateMe);
return buildInstallCard(
"Status", "Your installation is lower version.", "Click to upgrade",
() async {
await rustDeskWinManager.closeAllSubWindows();
bind.mainUpdateMe();
});
}
} else if (Platform.isMacOS) {
if (!bind.mainIsCanScreenRecording(prompt: false)) {
@@ -384,13 +390,16 @@ class _DesktopHomePageState extends State<DesktopHomePage>
final keyShowSelinuxHelpTip = "show-selinux-help-tip";
if (bind.mainGetLocalOption(key: keyShowSelinuxHelpTip) != 'N') {
LinuxCards.add(buildInstallCard(
"Warning", "selinux_tip", "", () async {},
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
help: 'Help',
link:
'https://rustdesk.com/docs/en/client/linux/#permissions-issue',
closeButton: true,
closeOption: keyShowSelinuxHelpTip,
"Warning",
"selinux_tip",
"",
() async {},
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
help: 'Help',
link:
'https://rustdesk.com/docs/en/client/linux/#permissions-issue',
closeButton: true,
closeOption: keyShowSelinuxHelpTip,
));
}
}
@@ -418,7 +427,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Widget buildInstallCard(String title, String content, String btnText,
GestureTapCallback onPressed,
{double marginTop = 20.0, String? help, String? link, bool? closeButton, String? closeOption}) {
{double marginTop = 20.0,
String? help,
String? link,
bool? closeButton,
String? closeOption}) {
void closeCard() async {
if (closeOption != null) {
await bind.mainSetLocalOption(key: closeOption, value: 'N');
@@ -439,89 +452,90 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Container(
margin: EdgeInsets.only(top: marginTop),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color.fromARGB(255, 226, 66, 188),
Color.fromARGB(255, 244, 114, 124),
],
)),
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: (title.isNotEmpty
? <Widget>[
Center(
child: Text(
translate(title),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15),
).marginOnly(bottom: 6)),
]
: <Widget>[]) +
<Widget>[
Text(
translate(content),
style: TextStyle(
height: 1.5,
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 13),
).marginOnly(bottom: 20)
] +
(btnText.isNotEmpty
? <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FixedWidthButton(
width: 150,
padding: 8,
isOutline: true,
text: translate(btnText),
textColor: Colors.white,
borderColor: Colors.white,
textSize: 20,
radius: 10,
onTap: onPressed,
)
])
]
: <Widget>[]) +
(help != null
? <Widget>[
Center(
child: InkWell(
onTap: () async =>
await launchUrl(Uri.parse(link!)),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color.fromARGB(255, 226, 66, 188),
Color.fromARGB(255, 244, 114, 124),
],
)),
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: (title.isNotEmpty
? <Widget>[
Center(
child: Text(
translate(help),
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.white,
fontSize: 12),
)).marginOnly(top: 6)),
]
: <Widget>[]))),
translate(title),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15),
).marginOnly(bottom: 6)),
]
: <Widget>[]) +
<Widget>[
Text(
translate(content),
style: TextStyle(
height: 1.5,
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 13),
).marginOnly(bottom: 20)
] +
(btnText.isNotEmpty
? <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FixedWidthButton(
width: 150,
padding: 8,
isOutline: true,
text: translate(btnText),
textColor: Colors.white,
borderColor: Colors.white,
textSize: 20,
radius: 10,
onTap: onPressed,
)
])
]
: <Widget>[]) +
(help != null
? <Widget>[
Center(
child: InkWell(
onTap: () async =>
await launchUrl(Uri.parse(link!)),
child: Text(
translate(help),
style: TextStyle(
decoration:
TextDecoration.underline,
color: Colors.white,
fontSize: 12),
)).marginOnly(top: 6)),
]
: <Widget>[]))),
),
if (closeButton != null && closeButton == true)
Positioned(
top: 18,
right: 0,
child: IconButton(
icon: Icon(
Icons.close,
color: Colors.white,
size: 20,
Positioned(
top: 18,
right: 0,
child: IconButton(
icon: Icon(
Icons.close,
color: Colors.white,
size: 20,
),
onPressed: closeCard,
),
onPressed: closeCard,
),
),
],
);
}

View File

@@ -632,23 +632,27 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
}).marginOnly(left: _kContentHMargin),
Column(
children: [
_OptionCheckBox(context, 'Enable Keyboard/Mouse', 'enable-keyboard',
_OptionCheckBox(context, 'Enable keyboard/mouse', 'enable-keyboard',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable Clipboard', 'enable-clipboard',
_OptionCheckBox(context, 'Enable clipboard', 'enable-clipboard',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(
context, 'Enable File Transfer', 'enable-file-transfer',
context, 'Enable file transfer', 'enable-file-transfer',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable Audio', 'enable-audio',
_OptionCheckBox(context, 'Enable audio', 'enable-audio',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable TCP Tunneling', 'enable-tunnel',
_OptionCheckBox(context, 'Enable TCP tunneling', 'enable-tunnel',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(
context, 'Enable Remote Restart', 'enable-remote-restart',
context, 'Enable remote restart', 'enable-remote-restart',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(
context, 'Enable Recording Session', 'enable-record-session',
context, 'Enable recording session', 'enable-record-session',
enabled: enabled, fakeValue: fakeValue),
if (Platform.isWindows)
_OptionCheckBox(
context, 'Enable blocking user input', 'enable-block-input',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable remote configuration modification',
'allow-remote-config-modification',
enabled: enabled, fakeValue: fakeValue),
@@ -769,7 +773,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
bool enabled = !locked;
return _Card(title: 'Security', children: [
shareRdp(context, enabled),
_OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery',
_OptionCheckBox(context, 'Deny LAN discovery', 'enable-lan-discovery',
reverse: true, enabled: enabled),
...directIp(context),
whitelist(),
@@ -809,7 +813,7 @@ 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', 'direct-server',
update: update, enabled: !locked),
() {
// Simple temp wrapper for PR check
@@ -1320,6 +1324,7 @@ class _DisplayState extends State<_Display> {
otherRow('Lock after session end', 'lock_after_session_end'),
otherRow('Privacy mode', 'privacy_mode'),
otherRow('Reverse mouse wheel', 'reverse_mouse_wheel'),
otherRow('True color (4:4:4)', 'i444'),
];
if (useTextureRender) {
children.add(otherRow('Show displays as individual windows',

View File

@@ -15,7 +15,7 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../consts.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
@@ -91,7 +91,7 @@ class _FileManagerPageState extends State<FileManagerPage>
});
Get.put(_ffi, tag: 'ft_${widget.id}');
if (!Platform.isLinux) {
Wakelock.enable();
WakelockPlus.enable();
}
debugPrint("File manager page init success with id ${widget.id}");
_ffi.dialogManager.setOverlayState(_overlayKeyState);
@@ -104,7 +104,7 @@ class _FileManagerPageState extends State<FileManagerPage>
_ffi.close();
_ffi.dialogManager.dismissAll();
if (!Platform.isLinux) {
Wakelock.disable();
WakelockPlus.disable();
}
Get.delete<FFI>(tag: 'ft_${widget.id}');
});
@@ -1126,10 +1126,11 @@ class _FileManagerViewState extends State<FileManagerView> {
void _onSelectedChanged(SelectedItems selectedItems, List<Entry> entries,
Entry entry, bool isLocal) {
final isCtrlDown = RawKeyboard.instance.keysPressed
.contains(LogicalKeyboardKey.controlLeft);
final isCtrlDown = RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.controlLeft) ||
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.controlRight);
final isShiftDown =
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft);
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft) ||
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftRight);
if (isCtrlDown) {
if (selectedItems.items.contains(entry)) {
selectedItems.remove(entry);

View File

@@ -8,7 +8,7 @@ import 'package:flutter_custom_cursor/cursor_manager.dart'
as custom_cursor_manager;
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
import 'package:flutter_improved_scrolling/flutter_improved_scrolling.dart';
@@ -123,7 +123,7 @@ class _RemotePageState extends State<RemotePage>
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
if (!Platform.isLinux) {
Wakelock.enable();
WakelockPlus.enable();
}
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
@@ -183,7 +183,7 @@ class _RemotePageState extends State<RemotePage>
_isWindowBlur = false;
}
if (!Platform.isLinux) {
Wakelock.enable();
WakelockPlus.enable();
}
}
@@ -192,7 +192,7 @@ class _RemotePageState extends State<RemotePage>
void onWindowMaximize() {
super.onWindowMaximize();
if (!Platform.isLinux) {
Wakelock.enable();
WakelockPlus.enable();
}
}
@@ -200,7 +200,7 @@ class _RemotePageState extends State<RemotePage>
void onWindowMinimize() {
super.onWindowMinimize();
if (!Platform.isLinux) {
Wakelock.disable();
WakelockPlus.disable();
}
}
@@ -228,7 +228,7 @@ class _RemotePageState extends State<RemotePage>
overlays: SystemUiOverlay.values);
}
if (!Platform.isLinux) {
await Wakelock.disable();
await WakelockPlus.disable();
}
await Get.delete<FFI>(tag: widget.id);
removeSharedStates(widget.id);

View File

@@ -386,7 +386,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
pi.platform == kPeerPlatformMacOS)) {
menu.add(MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Restart Remote Device'),
translate('Restart remote device'),
style: style,
),
proc: () => showRestartRemoteDevice(

View File

@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/cm_file_model.dart';
import 'package:flutter_hbb/utils/platform_channel.dart';
import 'package:get/get.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
@@ -482,8 +483,8 @@ class _CmHeaderState extends State<_CmHeader>
client.type_() != ClientType.file),
child: IconButton(
onPressed: () => checkClickTime(client.id, () {
if (client.type_() != ClientType.file) {
gFFI.chatModel.toggleCMSidePage();
if (client.type_() == ClientType.file) {
gFFI.chatModel.toggleCMFilePage();
} else {
gFFI.chatModel
.toggleCMChatPage(MessageKey(client.peerId, client.id));
@@ -519,6 +520,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
Function(bool)? onTap, String tooltipText) {
return Tooltip(
message: "$tooltipText: ${enabled ? "ON" : "OFF"}",
waitDuration: Duration.zero,
child: Container(
decoration: BoxDecoration(
color: enabled ? MyTheme.accent : Colors.grey[700],
@@ -535,7 +537,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
child: Icon(
iconData,
color: Colors.white,
size: 32,
),
),
],
@@ -547,9 +548,11 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
@override
Widget build(BuildContext context) {
final crossAxisCount = 4;
final spacing = 10.0;
return Container(
width: double.infinity,
height: 200.0,
height: 160.0,
margin: EdgeInsets.all(5.0),
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(
@@ -574,10 +577,10 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
).marginOnly(left: 4.0, bottom: 8.0),
Expanded(
child: GridView.count(
crossAxisCount: 3,
padding: EdgeInsets.symmetric(horizontal: 20.0),
mainAxisSpacing: 20.0,
crossAxisSpacing: 20.0,
crossAxisCount: crossAxisCount,
padding: EdgeInsets.symmetric(horizontal: spacing),
mainAxisSpacing: spacing,
crossAxisSpacing: spacing,
children: [
buildPermissionIcon(
client.keyboard,
@@ -589,7 +592,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.keyboard = enabled;
});
},
translate('Allow using keyboard and mouse'),
translate('Enable keyboard/mouse'),
),
buildPermissionIcon(
client.clipboard,
@@ -601,7 +604,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.clipboard = enabled;
});
},
translate('Allow using clipboard'),
translate('Enable clipboard'),
),
buildPermissionIcon(
client.audio,
@@ -613,7 +616,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.audio = enabled;
});
},
translate('Allow hearing sound'),
translate('Enable audio'),
),
buildPermissionIcon(
client.file,
@@ -625,7 +628,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.file = enabled;
});
},
translate('Allow file copy and paste'),
translate('Enable file copy and paste'),
),
buildPermissionIcon(
client.restart,
@@ -637,7 +640,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.restart = enabled;
});
},
translate('Allow remote restart'),
translate('Enable remote restart'),
),
buildPermissionIcon(
client.recording,
@@ -649,8 +652,24 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.recording = enabled;
});
},
translate('Allow recording session'),
)
translate('Enable recording session'),
),
// only windows support block input
if (Platform.isWindows)
buildPermissionIcon(
client.blockInput,
Icons.block,
(enabled) {
bind.cmSwitchPermission(
connId: client.id,
name: "block_input",
enabled: enabled);
setState(() {
client.blockInput = enabled;
});
},
translate('Enable blocking user input'),
)
],
),
),
@@ -975,6 +994,49 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
);
}
iconLabel(CmFileLog item) {
switch (item.action) {
case CmFileAction.none:
return Container();
case CmFileAction.localToRemote:
case CmFileAction.remoteToLocal:
return Column(
children: [
Transform.rotate(
angle: item.action == CmFileAction.remoteToLocal ? 0 : pi,
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
),
Text(item.action == CmFileAction.remoteToLocal
? translate('Send')
: translate('Receive'))
],
);
case CmFileAction.remove:
return Column(
children: [
Icon(
Icons.delete,
color: Theme.of(context).tabBarTheme.labelColor,
),
Text(translate('Delete'))
],
);
case CmFileAction.createDir:
return Column(
children: [
Icon(
Icons.create_new_folder,
color: Theme.of(context).tabBarTheme.labelColor,
),
Text(translate('Create Folder'))
],
);
}
}
Widget statusList() {
return PreferredSize(
preferredSize: const Size(200, double.infinity),
@@ -983,7 +1045,7 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
child: Obx(
() {
final jobTable = gFFI.cmFileModel.currentJobTable;
statusListView(List<JobProgress> jobs) => ListView.builder(
statusListView(List<CmFileLog> jobs) => ListView.builder(
controller: ScrollController(),
itemBuilder: (BuildContext context, int index) {
final item = jobs[index];
@@ -998,22 +1060,7 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
children: [
SizedBox(
width: 50,
child: Column(
children: [
Transform.rotate(
angle: item.isRemoteToLocal ? 0 : pi,
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
),
Text(item.isRemoteToLocal
? translate('Send')
: translate('Receive'))
],
),
child: iconLabel(item),
).paddingOnly(left: 15),
const SizedBox(
width: 16.0,
@@ -1048,8 +1095,9 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
),
),
Offstage(
offstage:
item.state == JobState.inProgress,
offstage: !(item.isTransfer() &&
item.state !=
JobState.inProgress),
child: Text(
translate(
item.display(),

View File

@@ -107,7 +107,7 @@ class _ToolbarTheme {
static const double dividerHeight = 12.0;
static const double buttonSize = 32;
static const double buttonHMargin = 3;
static const double buttonHMargin = 2;
static const double buttonVMargin = 6;
static const double iconRadius = 8;
static const double elevation = 3;
@@ -125,12 +125,13 @@ class _ToolbarTheme {
: EdgeInsets.fromLTRB(6, 14, 6, 14);
static const double menuButtonBorderRadius = 3.0;
static get borderColor =>
MyTheme.currentThemeMode() == ThemeMode.light ? bordLight : bordDark;
static final defaultMenuStyle = MenuStyle(
side: MaterialStateProperty.all(BorderSide(
width: 1,
color: MyTheme.currentThemeMode() == ThemeMode.light
? _ToolbarTheme.bordLight
: _ToolbarTheme.bordDark,
color: borderColor,
)),
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_ToolbarTheme.menuBorderRadius))),
@@ -141,6 +142,19 @@ class _ToolbarTheme {
padding: MaterialStatePropertyAll(EdgeInsets.zero),
overlayColor: MaterialStatePropertyAll(Colors.transparent),
);
static Widget borderWrapper(Widget child, BorderRadius borderRadius) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: borderColor,
width: 1,
),
borderRadius: borderRadius,
),
child: child,
);
}
}
typedef DismissFunc = void Function();
@@ -420,6 +434,9 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
if (show.isTrue && _dragging.isFalse) {
triggerAutoHide();
}
final borderRadius = BorderRadius.vertical(
bottom: Radius.circular(5),
);
return Align(
alignment: FractionalOffset(_fractionX.value, 0),
child: Offstage(
@@ -427,6 +444,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
child: Material(
elevation: _ToolbarTheme.elevation,
shadowColor: MyTheme.color(context).shadow,
borderRadius: borderRadius,
child: _DraggableShowHide(
sessionId: widget.ffi.sessionId,
dragging: _dragging,
@@ -434,6 +452,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
show: show,
setFullscreen: _setFullscreen,
setMinimize: _minimize,
borderRadius: borderRadius,
),
),
),
@@ -475,13 +494,14 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
}
toolbarItems.add(_RecordMenu());
toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
final toolbarBorderRadius = BorderRadius.all(Radius.circular(4.0));
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Material(
elevation: _ToolbarTheme.elevation,
shadowColor: MyTheme.color(context).shadow,
borderRadius: BorderRadius.all(Radius.circular(4.0)),
borderRadius: toolbarBorderRadius,
color: Theme.of(context)
.menuBarTheme
.style
@@ -491,13 +511,15 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
scrollDirection: Axis.horizontal,
child: Theme(
data: themeData(),
child: Row(
children: [
SizedBox(width: _ToolbarTheme.buttonHMargin * 2),
...toolbarItems,
SizedBox(width: _ToolbarTheme.buttonHMargin * 2)
],
),
child: _ToolbarTheme.borderWrapper(
Row(
children: [
SizedBox(width: _ToolbarTheme.buttonHMargin * 2),
...toolbarItems,
SizedBox(width: _ToolbarTheme.buttonHMargin * 2)
],
),
toolbarBorderRadius),
),
),
),
@@ -598,14 +620,19 @@ class _MonitorMenu extends StatelessWidget {
useTextureRender && ffi.ffiModel.pi.isSupportMultiDisplay;
@override
Widget build(BuildContext context) =>
showMonitorsToolbar ? buildMultiMonitorMenu() : buildMonitorMenu();
Widget build(BuildContext context) => showMonitorsToolbar
? buildMultiMonitorMenu()
: Obx(() => buildMonitorMenu());
Widget buildMonitorMenu() {
final width = SimpleWrapper<double>(0);
final monitorsIcon =
globalMonitorsWidget(width, Colors.white, Colors.black38);
return _IconSubmenuButton(
tooltip: 'Select Monitor',
icon: icon(),
icon: monitorsIcon,
ffi: ffi,
width: width.value,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuStyle: MenuStyle(
@@ -644,26 +671,37 @@ class _MonitorMenu extends StatelessWidget {
child: Text(translate('Show displays as individual windows')));
}
buildOneMonitorButton(i, curDisplay) => Text(
'${i + 1}',
style: TextStyle(
color: i == curDisplay
? _ToolbarTheme.blueColor
: _ToolbarTheme.inactiveColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
);
List<Widget> buildMonitorList(bool isMulti) {
final List<Widget> monitorList = [];
final pi = ffi.ffiModel.pi;
getMonitorText(int i) {
if (i == kAllDisplayValue) {
if (pi.displays.length == 2) {
return '1|2';
} else {
return 'ALL';
}
} else {
return (i + 1).toString();
}
}
buildMonitorButton(int i) => Obx(() {
RxInt display = CurrentDisplayState.find(id);
final isAllMonitors = i == kAllDisplayValue;
final width = SimpleWrapper<double>(0);
Widget? monitorsIcon;
if (isAllMonitors) {
monitorsIcon = globalMonitorsWidget(
width, Colors.white, _ToolbarTheme.blueColor);
}
return _IconMenuButton(
tooltip: isMulti ? '' : '#${i + 1} monitor',
tooltip: isMulti
? ''
: isAllMonitors
? 'all monitors'
: '#${i + 1} monitor',
hMargin: isMulti ? null : 6,
vMargin: isMulti ? null : 12,
topLevel: false,
@@ -673,33 +711,25 @@ class _MonitorMenu extends StatelessWidget {
hoverColor: i == display.value
? _ToolbarTheme.hoverBlueColor
: _ToolbarTheme.hoverInactiveColor,
icon: Container(
alignment: AlignmentDirectional.center,
constraints:
const BoxConstraints(minHeight: _ToolbarTheme.height),
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter:
ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(
() => Text(
getMonitorText(i),
style: TextStyle(
color: i == display.value
? _ToolbarTheme.blueColor
: _ToolbarTheme.inactiveColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
width: isAllMonitors ? width.value : null,
icon: isAllMonitors
? monitorsIcon
: Container(
alignment: AlignmentDirectional.center,
constraints:
const BoxConstraints(minHeight: _ToolbarTheme.height),
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter:
ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(() => buildOneMonitorButton(i, display.value)),
],
),
),
],
),
),
onPressed: () => onPressed(i, pi),
);
});
@@ -713,26 +743,69 @@ class _MonitorMenu extends StatelessWidget {
return monitorList;
}
icon() {
final pi = ffi.ffiModel.pi;
globalMonitorsWidget(
SimpleWrapper<double> width, Color activeTextColor, Color activeBgColor) {
getMonitors() {
final pi = ffi.ffiModel.pi;
RxInt display = CurrentDisplayState.find(id);
final rect = ffi.ffiModel.globalDisplaysRect();
if (rect == null) {
return Offstage();
}
final scale = _ToolbarTheme.buttonSize / rect.height * 0.75;
final startY = (_ToolbarTheme.buttonSize - rect.height * scale) * 0.5;
final startX = startY;
final children = <Widget>[];
for (var i = 0; i < pi.displays.length; i++) {
final d = pi.displays[i];
final fontSize = (d.width * scale < d.height * scale
? d.width * scale
: d.height * scale) *
0.65;
children.add(Positioned(
left: (d.x - rect.left) * scale + startX,
top: (d.y - rect.top) * scale + startY,
width: d.width * scale,
height: d.height * scale,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
width: 1.0,
),
color: display.value == i ? activeBgColor : Colors.white,
),
child: Center(
child: Text(
'${i + 1}',
style: TextStyle(
color: display.value == i
? activeTextColor
: _ToolbarTheme.inactiveColor,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
)),
),
));
}
width.value = rect.width * scale + startX * 2;
return SizedBox(
width: width.value,
height: rect.height * scale + startY * 2,
child: Stack(
children: children,
),
);
}
return Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(() {
RxInt display = CurrentDisplayState.find(id);
return Text(
'${display.value == kAllDisplayValue ? 'A' : '${display.value + 1}'}/${pi.displays.length}',
style: const TextStyle(
color: _ToolbarTheme.blueColor,
fontSize: 8,
fontWeight: FontWeight.bold,
),
);
}),
SizedBox(height: _ToolbarTheme.buttonSize),
getMonitors(),
],
);
}
@@ -1150,8 +1223,9 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
@override
Widget build(BuildContext context) {
final isVirtualDisplay = ffiModel.isVirtualDisplayResolution;
final visible =
ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1);
final visible = ffiModel.keyboard &&
(isVirtualDisplay || resolutions.length > 1) &&
pi.currentDisplay != kAllDisplayValue;
if (!visible) return Offstage();
final showOriginalBtn =
ffiModel.isOriginalResolutionSet && !ffiModel.isOriginalResolution;
@@ -1761,6 +1835,7 @@ class _IconMenuButton extends StatefulWidget {
final double? hMargin;
final double? vMargin;
final bool topLevel;
final double? width;
const _IconMenuButton({
Key? key,
this.assetName,
@@ -1772,6 +1847,7 @@ class _IconMenuButton extends StatefulWidget {
this.hMargin,
this.vMargin,
this.topLevel = true,
this.width,
}) : super(key: key);
@override
@@ -1792,7 +1868,7 @@ class _IconMenuButtonState extends State<_IconMenuButton> {
height: _ToolbarTheme.buttonSize,
);
var button = SizedBox(
width: _ToolbarTheme.buttonSize,
width: widget.width ?? _ToolbarTheme.buttonSize,
height: _ToolbarTheme.buttonSize,
child: MenuItemButton(
style: ButtonStyle(
@@ -1839,18 +1915,20 @@ class _IconSubmenuButton extends StatefulWidget {
final List<Widget> menuChildren;
final MenuStyle? menuStyle;
final FFI ffi;
final double? width;
_IconSubmenuButton(
{Key? key,
this.svg,
this.icon,
required this.tooltip,
required this.color,
required this.hoverColor,
required this.menuChildren,
required this.ffi,
this.menuStyle})
: super(key: key);
_IconSubmenuButton({
Key? key,
this.svg,
this.icon,
required this.tooltip,
required this.color,
required this.hoverColor,
required this.menuChildren,
required this.ffi,
this.menuStyle,
this.width,
}) : super(key: key);
@override
State<_IconSubmenuButton> createState() => _IconSubmenuButtonState();
@@ -1870,7 +1948,7 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
height: _ToolbarTheme.buttonSize,
);
final button = SizedBox(
width: _ToolbarTheme.buttonSize,
width: widget.width ?? _ToolbarTheme.buttonSize,
height: _ToolbarTheme.buttonSize,
child: SubmenuButton(
menuStyle: widget.menuStyle ?? _ToolbarTheme.defaultMenuStyle,
@@ -2016,6 +2094,7 @@ class _DraggableShowHide extends StatefulWidget {
final RxDouble fractionX;
final RxBool dragging;
final RxBool show;
final BorderRadius borderRadius;
final Function(bool) setFullscreen;
final Function() setMinimize;
@@ -2028,6 +2107,7 @@ class _DraggableShowHide extends StatefulWidget {
required this.show,
required this.setFullscreen,
required this.setMinimize,
required this.borderRadius,
}) : super(key: key);
@override
@@ -2164,9 +2244,11 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
.style
?.backgroundColor
?.resolve(MaterialState.values.toSet()),
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(5),
border: Border.all(
color: _ToolbarTheme.borderColor,
width: 1,
),
borderRadius: widget.borderRadius,
),
child: SizedBox(
height: 20,

View File

@@ -583,32 +583,19 @@ class WindowActionPanelState extends State<WindowActionPanel>
void onWindowClose() async {
mainWindowClose() async => await windowManager.hide();
notMainWindowClose(WindowController controller) async {
if (widget.tabController.length == 0) {
debugPrint("close emtpy multiwindow, hide");
await controller.hide();
await rustDeskWinManager
.call(WindowType.Main, kWindowEventHide, {"id": kWindowId!});
} else {
if (widget.tabController.length != 0) {
debugPrint("close not emtpy multiwindow from taskbar");
if (Platform.isWindows) {
await controller.show();
await controller.focus();
final res = await widget.onClose?.call() ?? true;
if (res) {
Future.delayed(Duration.zero, () async {
// onWindowClose will be called again to hide
await WindowController.fromWindowId(kWindowId!).close();
});
}
} else {
// ubuntu22.04 windowOnTop not work from taskbar
widget.tabController.clear();
Future.delayed(Duration.zero, () async {
// onWindowClose will be called again to hide
await WindowController.fromWindowId(kWindowId!).close();
});
if (!res) return;
}
widget.tabController.clear();
}
await controller.hide();
await rustDeskWinManager
.call(WindowType.Main, kWindowEventHide, {"id": kWindowId!});
}
macOSWindowClose(