mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-16 12:21:28 +03:00
Merge remote-tracking branch 'upstream/master'
# Conflicts: # Cargo.lock # src/server/connection.rs
This commit is contained in:
@@ -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)))
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user