Merge branch 'master' into sort-favorites

This commit is contained in:
RustDesk 2023-03-10 13:05:24 +08:00 committed by GitHub
commit 04bd8e167a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 3197 additions and 3057 deletions

@ -6,7 +6,7 @@ body:
id: desc id: desc
attributes: attributes:
label: Bug Description label: Bug Description
description: A clear and concise description of what the bug is description: A clear and concise description of what the bug is (if it's a keyboard issue, provide the keyboard mode you're using. e.g. legacy, map, translate)
validations: validations:
required: true required: true
- type: textarea - type: textarea

1939
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -45,22 +45,22 @@ lazy_static = "1.4"
sha2 = "0.10" sha2 = "0.10"
repng = "0.2" repng = "0.2"
parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" } parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" }
runas = "0.2" runas = "1.0"
magnum-opus = { git = "https://github.com/rustdesk/magnum-opus" } magnum-opus = { git = "https://github.com/rustdesk/magnum-opus" }
dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpolate"], optional = true } dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpolate"], optional = true }
rubato = { version = "0.12", optional = true } rubato = { version = "0.12", optional = true }
samplerate = { version = "0.2", optional = true } samplerate = { version = "0.2", optional = true }
async-trait = "0.1" async-trait = "0.1"
uuid = { version = "1.0", features = ["v4"] } uuid = { version = "1.0", features = ["v4"] }
clap = "3.0" clap = "4.1"
rpassword = "7.0" rpassword = "7.0"
base64 = "0.13" base64 = "0.21"
num_cpus = "1.13" num_cpus = "1.13"
bytes = { version = "1.2", features = ["serde"] } bytes = { version = "1.2", features = ["serde"] }
default-net = "0.12.0" default-net = "0.12.0"
wol-rs = "0.9.1" wol-rs = "1.0"
flutter_rust_bridge = { version = "1.61.1", optional = true } flutter_rust_bridge = { version = "1.61.1", optional = true }
errno = "0.2.8" errno = "0.3"
rdev = { git = "https://github.com/fufesou/rdev" } rdev = { git = "https://github.com/fufesou/rdev" }
url = { version = "2.1", features = ["serde"] } url = { version = "2.1", features = ["serde"] }
dlopen = "0.1" dlopen = "0.1"
@ -71,7 +71,7 @@ chrono = "0.4.23"
cidr-utils = "0.5.9" cidr-utils = "0.5.9"
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
cpal = "0.13.5" cpal = "0.14"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
machine-uid = "0.2" machine-uid = "0.2"
@ -81,9 +81,9 @@ sys-locale = "0.2"
enigo = { path = "libs/enigo", features = [ "with_serde" ] } enigo = { path = "libs/enigo", features = [ "with_serde" ] }
clipboard = { path = "libs/clipboard" } clipboard = { path = "libs/clipboard" }
ctrlc = "3.2" ctrlc = "3.2"
arboard = "2.0" arboard = "3.2"
#minreq = { version = "2.4", features = ["punycode", "https-native"] } #minreq = { version = "2.4", features = ["punycode", "https-native"] }
system_shutdown = "3.0.0" system_shutdown = "4.0"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
trayicon = { git = "https://github.com/open-trade/trayicon-rs", features = ["winit"] } trayicon = { git = "https://github.com/open-trade/trayicon-rs", features = ["winit"] }
@ -148,6 +148,7 @@ cc = "1.0"
hbb_common = { path = "libs/hbb_common" } hbb_common = { path = "libs/hbb_common" }
simple_rc = { path = "libs/simple_rc", optional = true } simple_rc = { path = "libs/simple_rc", optional = true }
flutter_rust_bridge_codegen = "1.61.1" flutter_rust_bridge_codegen = "1.61.1"
os-version = "0.2"
[dev-dependencies] [dev-dependencies]
hound = "3.5" hound = "3.5"

@ -9,7 +9,14 @@ fn build_windows() {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn build_mac() { fn build_mac() {
let file = "src/platform/macos.mm"; let file = "src/platform/macos.mm";
cc::Build::new().file(file).compile("macos"); let mut b = cc::Build::new();
if let Ok(os_version::OsVersion::MacOS(v)) = os_version::detect() {
let v = v.version;
if v.contains("10.14") {
b.flag("-DNO_InputMonitoringAuthStatus=1");
}
}
b.file(file).compile("macos");
println!("cargo:rerun-if-changed={}", file); println!("cargo:rerun-if-changed={}", file);
} }

@ -37,9 +37,9 @@
| Σεούλ | AWS lightsail | 1 vCPU / 0.5GB RAM | | Σεούλ | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Γερμανία | Hetzner | 2 vCPU / 4GB RAM | | Γερμανία | Hetzner | 2 vCPU / 4GB RAM |
| Γερμανία | Codext | 4 vCPU / 8GB RAM | | Γερμανία | Codext | 4 vCPU / 8GB RAM |
| Φινλανδία (Ελσίνκι) | 0x101 Cyber Security | 4 vCPU / 8GB RAM | | Φινλανδία (Ελσίνκι) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
| ΗΠΑ (Άσμπερν) | 0x101 Cyber Security | 4 vCPU / 8GB RAM | | ΗΠΑ (Άσμπερν) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
| Ουκρανία (Κίεβο) | dc.volia (2VM) | 2 vCPU / 4GB RAM | | Ουκρανία (Κίεβο) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Dev Container ## Dev Container

@ -430,7 +430,7 @@ final ButtonStyle flatButtonStyle = TextButton.styleFrom(
); );
List<Locale> supportedLocales = const [ List<Locale> supportedLocales = const [
// specify CN/TW to fix CJK issue in flutter Locale('en', 'US'),
Locale('zh', 'CN'), Locale('zh', 'CN'),
Locale('zh', 'TW'), Locale('zh', 'TW'),
Locale('zh', 'SG'), Locale('zh', 'SG'),
@ -452,7 +452,7 @@ List<Locale> supportedLocales = const [
Locale('vi'), Locale('vi'),
Locale('pl'), Locale('pl'),
Locale('kz'), Locale('kz'),
Locale('en', 'US'), Locale('es'),
]; ];
String formatDurationToTime(Duration duration) { String formatDurationToTime(Duration duration) {

@ -68,7 +68,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
var _lastChangeTime = DateTime.now(); var _lastChangeTime = DateTime.now();
var _lastQueryPeers = <String>{}; var _lastQueryPeers = <String>{};
var _lastQueryTime = DateTime.now().subtract(const Duration(hours: 1)); var _lastQueryTime = DateTime.now().subtract(const Duration(hours: 1));
var _queryCoun = 0; var _queryCount = 0;
var _exit = false; var _exit = false;
late final mobileWidth = () { late final mobileWidth = () {
@ -101,12 +101,12 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
@override @override
void onWindowFocus() { void onWindowFocus() {
_queryCoun = 0; _queryCount = 0;
} }
@override @override
void onWindowMinimize() { void onWindowMinimize() {
_queryCoun = _maxQueryCount; _queryCount = _maxQueryCount;
} }
@override @override
@ -123,6 +123,19 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
); );
} }
onVisibilityChanged(VisibilityInfo info) {
final peerId = _peerId((info.key as ValueKey).value);
if (info.visibleFraction > 0.00001) {
_curPeers.add(peerId);
} else {
_curPeers.remove(peerId);
}
_lastChangeTime = DateTime.now();
}
String _cardId(String id) => widget.peers.name + id;
String _peerId(String cardId) => cardId.replaceAll(widget.peers.name, '');
Widget _buildPeersView(Peers peers) { Widget _buildPeersView(Peers peers) {
final body = ObxValue<RxList>((filters) { final body = ObxValue<RxList>((filters) {
return FutureBuilder<List<Peer>>( return FutureBuilder<List<Peer>>(
@ -132,16 +145,8 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
final cards = <Widget>[]; final cards = <Widget>[];
for (final peer in peers) { for (final peer in peers) {
final visibilityChild = VisibilityDetector( final visibilityChild = VisibilityDetector(
key: ValueKey(peer.id), key: ValueKey(_cardId(peer.id)),
onVisibilityChanged: (info) { onVisibilityChanged: onVisibilityChanged,
final peerId = (info.key as ValueKey).value;
if (info.visibleFraction > 0.00001) {
_curPeers.add(peerId);
} else {
_curPeers.remove(peerId);
}
_lastChangeTime = DateTime.now();
},
child: widget.peerCardBuilder(peer), child: widget.peerCardBuilder(peer),
); );
cards.add(isDesktop cards.add(isDesktop
@ -172,6 +177,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
// ignore: todo // ignore: todo
// TODO: variables walk through async tasks? // TODO: variables walk through async tasks?
void _startCheckOnlines() { void _startCheckOnlines() {
final queryInterval = const Duration(seconds: 20);
() async { () async {
while (!_exit) { while (!_exit) {
final now = DateTime.now(); final now = DateTime.now();
@ -181,18 +187,18 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
platformFFI.ffiBind platformFFI.ffiBind
.queryOnlines(ids: _curPeers.toList(growable: false)); .queryOnlines(ids: _curPeers.toList(growable: false));
_lastQueryPeers = {..._curPeers}; _lastQueryPeers = {..._curPeers};
_lastQueryTime = DateTime.now(); _lastQueryTime = DateTime.now().subtract(queryInterval);
_queryCoun = 0; _queryCount = 0;
} }
} }
} else { } else {
if (_queryCoun < _maxQueryCount) { if (_queryCount < _maxQueryCount) {
if (now.difference(_lastQueryTime) > const Duration(seconds: 20)) { if (now.difference(_lastQueryTime) >= queryInterval) {
if (_curPeers.isNotEmpty) { if (_curPeers.isNotEmpty) {
platformFFI.ffiBind platformFFI.ffiBind
.queryOnlines(ids: _curPeers.toList(growable: false)); .queryOnlines(ids: _curPeers.toList(growable: false));
_lastQueryTime = DateTime.now(); _lastQueryTime = DateTime.now();
_queryCoun += 1; _queryCount += 1;
} }
} }
} }

File diff suppressed because it is too large Load Diff

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/file_model.dart';
import 'package:provider/provider.dart'; import 'package:get/get.dart';
import 'package:toggle_switch/toggle_switch.dart'; import 'package:toggle_switch/toggle_switch.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
@ -18,10 +18,51 @@ class FileManagerPage extends StatefulWidget {
State<StatefulWidget> createState() => _FileManagerPageState(); State<StatefulWidget> createState() => _FileManagerPageState();
} }
enum SelectMode { local, remote, none }
extension SelectModeEq on SelectMode {
bool eq(bool? currentIsLocal) {
if (currentIsLocal == null) {
return false;
}
if (currentIsLocal) {
return this == SelectMode.local;
} else {
return this == SelectMode.remote;
}
}
}
extension SelectModeExt on Rx<SelectMode> {
void toggle(bool currentIsLocal) {
switch (value) {
case SelectMode.local:
value = SelectMode.none;
break;
case SelectMode.remote:
value = SelectMode.none;
break;
case SelectMode.none:
if (currentIsLocal) {
value = SelectMode.local;
} else {
value = SelectMode.remote;
}
break;
}
}
}
class _FileManagerPageState extends State<FileManagerPage> { class _FileManagerPageState extends State<FileManagerPage> {
final model = gFFI.fileModel; final model = gFFI.fileModel;
final _selectedItems = SelectedItems(); final selectMode = SelectMode.none.obs;
final _breadCrumbScroller = ScrollController();
var showLocal = true;
FileController get currentFileController =>
showLocal ? model.localController : model.remoteController;
FileDirectory get currentDir => currentFileController.directory.value;
DirectoryOptions get currentOptions => currentFileController.options.value;
@override @override
void initState() { void initState() {
@ -32,13 +73,12 @@ class _FileManagerPageState extends State<FileManagerPage> {
.showLoading(translate('Connecting...'), onCancel: closeConnection); .showLoading(translate('Connecting...'), onCancel: closeConnection);
}); });
gFFI.ffiModel.updateEventListener(widget.id); gFFI.ffiModel.updateEventListener(widget.id);
model.onDirChanged = (_) => breadCrumbScrollToEnd();
Wakelock.enable(); Wakelock.enable();
} }
@override @override
void dispose() { void dispose() {
model.onClose().whenComplete(() { model.close().whenComplete(() {
gFFI.close(); gFFI.close();
gFFI.dialogManager.dismissAll(); gFFI.dialogManager.dismissAll();
Wakelock.disable(); Wakelock.disable();
@ -47,15 +87,13 @@ class _FileManagerPageState extends State<FileManagerPage> {
} }
@override @override
Widget build(BuildContext context) => ChangeNotifierProvider.value( Widget build(BuildContext context) => WillPopScope(
value: model,
child: Consumer<FileModel>(builder: (_context, _model, _child) {
return WillPopScope(
onWillPop: () async { onWillPop: () async {
if (model.selectMode) { if (selectMode.value != SelectMode.none) {
model.toggleSelectMode(); selectMode.value = SelectMode.none;
setState(() {});
} else { } else {
model.goBack(); currentFileController.goBack();
} }
return false; return false;
}, },
@ -65,19 +103,16 @@ class _FileManagerPageState extends State<FileManagerPage> {
leading: Row(children: [ leading: Row(children: [
IconButton( IconButton(
icon: Icon(Icons.close), icon: Icon(Icons.close),
onPressed: () => onPressed: () => clientClose(widget.id, gFFI.dialogManager)),
clientClose(widget.id, gFFI.dialogManager)),
]), ]),
centerTitle: true, centerTitle: true,
title: ToggleSwitch( title: ToggleSwitch(
initialLabelIndex: model.isLocal ? 0 : 1, initialLabelIndex: showLocal ? 0 : 1,
activeBgColor: [MyTheme.idColor], activeBgColor: [MyTheme.idColor],
inactiveBgColor: inactiveBgColor: Theme.of(context).brightness == Brightness.light
Theme.of(context).brightness == Brightness.light
? MyTheme.grayBg ? MyTheme.grayBg
: null, : null,
inactiveFgColor: inactiveFgColor: Theme.of(context).brightness == Brightness.light
Theme.of(context).brightness == Brightness.light
? Colors.black54 ? Colors.black54
: null, : null,
totalSwitches: 2, totalSwitches: 2,
@ -87,9 +122,9 @@ class _FileManagerPageState extends State<FileManagerPage> {
labels: [translate("Local"), translate("Remote")], labels: [translate("Local"), translate("Remote")],
icons: [Icons.phone_android_sharp, Icons.screen_share], icons: [Icons.phone_android_sharp, Icons.screen_share],
onToggle: (index) { onToggle: (index) {
final current = model.isLocal ? 0 : 1; final current = showLocal ? 0 : 1;
if (index != current) { if (index != current) {
model.togglePage(); setState(() => showLocal = !showLocal);
} }
}, },
), ),
@ -110,7 +145,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
value: "refresh", value: "refresh",
), ),
PopupMenuItem( PopupMenuItem(
enabled: model.currentDir.path != "/", enabled: currentDir.path != "/",
child: Row( child: Row(
children: [ children: [
Icon(Icons.check, Icon(Icons.check,
@ -122,7 +157,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
value: "select", value: "select",
), ),
PopupMenuItem( PopupMenuItem(
enabled: model.currentDir.path != "/", enabled: currentDir.path != "/",
child: Row( child: Row(
children: [ children: [
Icon(Icons.folder_outlined, Icon(Icons.folder_outlined,
@ -134,11 +169,11 @@ class _FileManagerPageState extends State<FileManagerPage> {
value: "folder", value: "folder",
), ),
PopupMenuItem( PopupMenuItem(
enabled: model.currentDir.path != "/", enabled: currentDir.path != "/",
child: Row( child: Row(
children: [ children: [
Icon( Icon(
model.getCurrentShowHidden() currentOptions.showHidden
? Icons.check_box_outlined ? Icons.check_box_outlined
: Icons.check_box_outline_blank, : Icons.check_box_outline_blank,
color: Theme.of(context).iconTheme.color), color: Theme.of(context).iconTheme.color),
@ -152,10 +187,12 @@ class _FileManagerPageState extends State<FileManagerPage> {
}, },
onSelected: (v) { onSelected: (v) {
if (v == "refresh") { if (v == "refresh") {
model.refresh(); currentFileController.refresh();
} else if (v == "select") { } else if (v == "select") {
_selectedItems.clear(); model.localController.selectedItems.clear();
model.toggleSelectMode(); model.remoteController.selectedItems.clear();
selectMode.toggle(showLocal);
setState(() {});
} else if (v == "folder") { } else if (v == "folder") {
final name = TextEditingController(); final name = TextEditingController();
gFFI.dialogManager gFFI.dialogManager
@ -179,54 +216,222 @@ class _FileManagerPageState extends State<FileManagerPage> {
isOutline: true), isOutline: true),
dialogButton("OK", onPressed: () { dialogButton("OK", onPressed: () {
if (name.value.text.isNotEmpty) { if (name.value.text.isNotEmpty) {
model.createDir(PathUtil.join( currentFileController.createDir(
model.currentDir.path, PathUtil.join(
currentDir.path,
name.value.text, name.value.text,
model.getCurrentIsWindows())); currentOptions.isWindows));
close(); close();
} }
}) })
])); ]));
} else if (v == "hidden") { } else if (v == "hidden") {
model.toggleShowHidden(); currentFileController.toggleShowHidden();
} }
}), }),
], ],
), ),
body: body(), body: showLocal
? FileManagerView(
controller: model.localController,
selectMode: selectMode,
)
: FileManagerView(
controller: model.remoteController,
selectMode: selectMode,
),
bottomSheet: bottomSheet(), bottomSheet: bottomSheet(),
)); ));
}));
bool showCheckBox() { Widget? bottomSheet() {
if (!model.selectMode) { return Obx(() {
return false; final selectedItems = getActiveSelectedItems();
final jobTable = model.jobController.jobTable;
final localLabel = selectedItems?.isLocal == null
? ""
: " [${selectedItems!.isLocal ? translate("Local") : translate("Remote")}]";
if (!(selectMode.value == SelectMode.none)) {
final selectedItemsLen =
"${selectedItems?.items.length ?? 0} ${translate("items")}";
if (selectedItems == null ||
selectedItems.items.isEmpty ||
selectMode.value.eq(showLocal)) {
return BottomSheetBody(
leading: Icon(Icons.check),
title: translate("Selected"),
text: selectedItemsLen + localLabel,
onCanceled: () {
selectedItems?.items.clear();
selectMode.value = SelectMode.none;
setState(() {});
},
actions: [
IconButton(
icon: Icon(Icons.compare_arrows),
onPressed: () => setState(() => showLocal = !showLocal),
),
IconButton(
icon: Icon(Icons.delete_forever),
onPressed: selectedItems != null
? () async {
if (selectedItems.items.isNotEmpty) {
await currentFileController
.removeAction(selectedItems);
selectedItems.items.clear();
selectMode.value = SelectMode.none;
}
}
: null,
)
]);
} else {
return BottomSheetBody(
leading: Icon(Icons.input),
title: translate("Paste here?"),
text: selectedItemsLen + localLabel,
onCanceled: () {
selectedItems.items.clear();
selectMode.value = SelectMode.none;
setState(() {});
},
actions: [
IconButton(
icon: Icon(Icons.compare_arrows),
onPressed: () => setState(() => showLocal = !showLocal),
),
IconButton(
icon: Icon(Icons.paste),
onPressed: () {
selectMode.value = SelectMode.none;
final otherSide = showLocal
? model.remoteController
: model.localController;
final thisSideData =
DirectoryData(currentDir, currentOptions);
otherSide.sendFiles(selectedItems, thisSideData);
selectedItems.items.clear();
selectMode.value = SelectMode.none;
},
)
]);
} }
return !_selectedItems.isOtherPage(model.isLocal);
} }
Widget body() { if (jobTable.isEmpty) {
final isLocal = model.isLocal; return Offstage();
final fd = model.currentDir; }
final entries = fd.entries;
switch (jobTable.last.state) {
case JobState.inProgress:
return BottomSheetBody(
leading: CircularProgressIndicator(),
title: translate("Waiting"),
text:
"${translate("Speed")}: ${readableFileSize(jobTable.last.speed)}/s",
onCanceled: () {
model.jobController.cancelJob(jobTable.last.id);
jobTable.clear();
},
);
case JobState.done:
return BottomSheetBody(
leading: Icon(Icons.check),
title: "${translate("Successful")}!",
text: jobTable.last.display(),
onCanceled: () => jobTable.clear(),
);
case JobState.error:
return BottomSheetBody(
leading: Icon(Icons.error),
title: "${translate("Error")}!",
text: "",
onCanceled: () => jobTable.clear(),
);
case JobState.none:
break;
case JobState.paused:
// TODO: Handle this case.
break;
}
return Offstage();
});
}
SelectedItems? getActiveSelectedItems() {
final localSelectedItems = model.localController.selectedItems;
final remoteSelectedItems = model.remoteController.selectedItems;
if (localSelectedItems.items.isNotEmpty &&
remoteSelectedItems.items.isNotEmpty) {
// assert unreachable
debugPrint("Wrong SelectedItems state, reset");
localSelectedItems.clear();
remoteSelectedItems.clear();
}
if (localSelectedItems.items.isEmpty && remoteSelectedItems.items.isEmpty) {
return null;
}
if (localSelectedItems.items.length > remoteSelectedItems.items.length) {
return localSelectedItems;
} else {
return remoteSelectedItems;
}
}
}
class FileManagerView extends StatefulWidget {
final FileController controller;
final Rx<SelectMode> selectMode;
FileManagerView({required this.controller, required this.selectMode});
@override
State<StatefulWidget> createState() => _FileManagerViewState();
}
class _FileManagerViewState extends State<FileManagerView> {
final _listScrollController = ScrollController();
final _breadCrumbScroller = ScrollController();
bool get isLocal => widget.controller.isLocal;
FileController get controller => widget.controller;
SelectedItems get _selectedItems => widget.controller.selectedItems;
@override
void initState() {
super.initState();
controller.directory.listen((e) => breadCrumbScrollToEnd());
}
@override
Widget build(BuildContext context) {
return Column(children: [ return Column(children: [
headTools(), headTools(),
Expanded( Expanded(child: Obx(() {
child: ListView.builder( final entries = controller.directory.value.entries;
controller: ScrollController(), return ListView.builder(
controller: _listScrollController,
itemCount: entries.length + 1, itemCount: entries.length + 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index >= entries.length) { if (index >= entries.length) {
return listTail(); return listTail();
} }
var selected = false; var selected = false;
if (model.selectMode) { if (widget.selectMode.value != SelectMode.none) {
selected = _selectedItems.contains(entries[index]); selected = _selectedItems.items.contains(entries[index]);
} }
final sizeStr = entries[index].isFile final sizeStr = entries[index].isFile
? readableFileSize(entries[index].size.toDouble()) ? readableFileSize(entries[index].size.toDouble())
: ""; : "";
final showCheckBox = () {
return widget.selectMode.value != SelectMode.none &&
widget.selectMode.value.eq(controller.selectedItems.isLocal);
}();
return Card( return Card(
child: ListTile( child: ListTile(
leading: entries[index].isDrive leading: entries[index].isDrive
@ -254,13 +459,13 @@ class _FileManagerPageState extends State<FileManagerPage> {
), ),
trailing: entries[index].isDrive trailing: entries[index].isDrive
? null ? null
: showCheckBox() : showCheckBox
? Checkbox( ? Checkbox(
value: selected, value: selected,
onChanged: (v) { onChanged: (v) {
if (v == null) return; if (v == null) return;
if (v && !selected) { if (v && !selected) {
_selectedItems.add(isLocal, entries[index]); _selectedItems.add(entries[index]);
} else if (!v && selected) { } else if (!v && selected) {
_selectedItems.remove(entries[index]); _selectedItems.remove(entries[index]);
} }
@ -287,26 +492,27 @@ class _FileManagerPageState extends State<FileManagerPage> {
}, },
onSelected: (v) { onSelected: (v) {
if (v == "delete") { if (v == "delete") {
final items = SelectedItems(); final items = SelectedItems(isLocal: isLocal);
items.add(isLocal, entries[index]); items.add(entries[index]);
model.removeAction(items); controller.removeAction(items);
} else if (v == "multi_select") { } else if (v == "multi_select") {
_selectedItems.clear(); _selectedItems.clear();
model.toggleSelectMode(); widget.selectMode.toggle(isLocal);
setState(() {});
} }
}), }),
onTap: () { onTap: () {
if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) { if (showCheckBox) {
if (selected) { if (selected) {
_selectedItems.remove(entries[index]); _selectedItems.remove(entries[index]);
} else { } else {
_selectedItems.add(isLocal, entries[index]); _selectedItems.add(entries[index]);
} }
setState(() {}); setState(() {});
return; return;
} }
if (entries[index].isDirectory || entries[index].isDrive) { if (entries[index].isDirectory || entries[index].isDrive) {
model.openDirectory(entries[index].path); controller.openDirectory(entries[index].path);
} else { } else {
// Perform file-related tasks. // Perform file-related tasks.
} }
@ -315,20 +521,21 @@ class _FileManagerPageState extends State<FileManagerPage> {
? null ? null
: () { : () {
_selectedItems.clear(); _selectedItems.clear();
model.toggleSelectMode(); widget.selectMode.toggle(isLocal);
if (model.selectMode) { if (widget.selectMode.value != SelectMode.none) {
_selectedItems.add(isLocal, entries[index]); _selectedItems.add(entries[index]);
} }
setState(() {}); setState(() {});
}, },
), ),
); );
}, },
)) );
}))
]); ]);
} }
breadCrumbScrollToEnd() { void breadCrumbScrollToEnd() {
Future.delayed(Duration(milliseconds: 200), () { Future.delayed(Duration(milliseconds: 200), () {
if (_breadCrumbScroller.hasClients) { if (_breadCrumbScroller.hasClients) {
_breadCrumbScroller.animateTo( _breadCrumbScroller.animateTo(
@ -342,35 +549,39 @@ class _FileManagerPageState extends State<FileManagerPage> {
Widget headTools() => Container( Widget headTools() => Container(
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(child: Obx(() {
child: BreadCrumb( final home = controller.options.value.home;
items: getPathBreadCrumbItems(() => model.goHome(), (list) { final isWindows = controller.options.value.isWindows;
return BreadCrumb(
items: getPathBreadCrumbItems(controller.shortPath, isWindows,
() => controller.goToHomeDirectory(), (list) {
var path = ""; var path = "";
if (model.currentHome.startsWith(list[0])) { if (home.startsWith(list[0])) {
// absolute path // absolute path
for (var item in list) { for (var item in list) {
path = PathUtil.join(path, item, model.getCurrentIsWindows()); path = PathUtil.join(path, item, isWindows);
} }
} else { } else {
path += model.currentHome; path += home;
for (var item in list) { for (var item in list) {
path = PathUtil.join(path, item, model.getCurrentIsWindows()); path = PathUtil.join(path, item, isWindows);
} }
} }
model.openDirectory(path); controller.openDirectory(path);
}), }),
divider: Icon(Icons.chevron_right), divider: Icon(Icons.chevron_right),
overflow: ScrollableOverflow(controller: _breadCrumbScroller), overflow: ScrollableOverflow(controller: _breadCrumbScroller),
)), );
})),
Row( Row(
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.arrow_back), icon: Icon(Icons.arrow_back),
onPressed: model.goBack, onPressed: controller.goBack,
), ),
IconButton( IconButton(
icon: Icon(Icons.arrow_upward), icon: Icon(Icons.arrow_upward),
onPressed: model.goToParentDirectory, onPressed: controller.goToParentDirectory,
), ),
PopupMenuButton<SortBy>( PopupMenuButton<SortBy>(
icon: Icon(Icons.sort), icon: Icon(Icons.sort),
@ -382,123 +593,37 @@ class _FileManagerPageState extends State<FileManagerPage> {
)) ))
.toList(); .toList();
}, },
onSelected: model.changeSortStyle), onSelected: controller.changeSortStyle),
], ],
) )
], ],
)); ));
Widget listTail() { Widget listTail() => Obx(() => Container(
return Container(
height: 100, height: 100,
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: EdgeInsets.fromLTRB(30, 5, 30, 0), padding: EdgeInsets.fromLTRB(30, 5, 30, 0),
child: Text( child: Text(
model.currentDir.path, controller.directory.value.path,
style: TextStyle(color: MyTheme.darkGray), style: TextStyle(color: MyTheme.darkGray),
), ),
), ),
Padding( Padding(
padding: EdgeInsets.all(2), padding: EdgeInsets.all(2),
child: Text( child: Text(
"${translate("Total")}: ${model.currentDir.entries.length} ${translate("items")}", "${translate("Total")}: ${controller.directory.value.entries.length} ${translate("items")}",
style: TextStyle(color: MyTheme.darkGray), style: TextStyle(color: MyTheme.darkGray),
), ),
) )
], ],
), ),
); ));
}
Widget? bottomSheet() { List<BreadCrumbItem> getPathBreadCrumbItems(String shortPath, bool isWindows,
final state = model.jobState;
final isOtherPage = _selectedItems.isOtherPage(model.isLocal);
final selectedItemsLen = "${_selectedItems.length} ${translate("items")}";
final local = _selectedItems.isLocal == null
? ""
: " [${_selectedItems.isLocal! ? translate("Local") : translate("Remote")}]";
if (model.selectMode) {
if (_selectedItems.length == 0 || !isOtherPage) {
return BottomSheetBody(
leading: Icon(Icons.check),
title: translate("Selected"),
text: selectedItemsLen + local,
onCanceled: () => model.toggleSelectMode(),
actions: [
IconButton(
icon: Icon(Icons.compare_arrows),
onPressed: model.togglePage,
),
IconButton(
icon: Icon(Icons.delete_forever),
onPressed: () {
if (_selectedItems.length > 0) {
model.removeAction(_selectedItems);
}
},
)
]);
} else {
return BottomSheetBody(
leading: Icon(Icons.input),
title: translate("Paste here?"),
text: selectedItemsLen + local,
onCanceled: () => model.toggleSelectMode(),
actions: [
IconButton(
icon: Icon(Icons.compare_arrows),
onPressed: model.togglePage,
),
IconButton(
icon: Icon(Icons.paste),
onPressed: () {
model.toggleSelectMode();
model.sendFiles(_selectedItems);
},
)
]);
}
}
switch (state) {
case JobState.inProgress:
return BottomSheetBody(
leading: CircularProgressIndicator(),
title: translate("Waiting"),
text:
"${translate("Speed")}: ${readableFileSize(model.jobProgress.speed)}/s",
onCanceled: () => model.cancelJob(model.jobProgress.id),
);
case JobState.done:
return BottomSheetBody(
leading: Icon(Icons.check),
title: "${translate("Successful")}!",
text: model.jobProgress.display(),
onCanceled: () => model.jobReset(),
);
case JobState.error:
return BottomSheetBody(
leading: Icon(Icons.error),
title: "${translate("Error")}!",
text: "",
onCanceled: () => model.jobReset(),
);
case JobState.none:
break;
case JobState.paused:
// TODO: Handle this case.
break;
}
return null;
}
List<BreadCrumbItem> getPathBreadCrumbItems(
void Function() onHome, void Function(List<String>) onPressed) { void Function() onHome, void Function(List<String>) onPressed) {
final path = model.currentShortPath; final list = PathUtil.split(shortPath, isWindows);
final list = PathUtil.split(path, model.getCurrentIsWindows());
final breadCrumbList = [ final breadCrumbList = [
BreadCrumbItem( BreadCrumbItem(
content: IconButton( content: IconButton(

File diff suppressed because it is too large Load Diff

@ -166,17 +166,18 @@ class FfiModel with ChangeNotifier {
} else if (name == 'file_dir') { } else if (name == 'file_dir') {
parent.target?.fileModel.receiveFileDir(evt); parent.target?.fileModel.receiveFileDir(evt);
} else if (name == 'job_progress') { } else if (name == 'job_progress') {
parent.target?.fileModel.tryUpdateJobProgress(evt); parent.target?.fileModel.jobController.tryUpdateJobProgress(evt);
} else if (name == 'job_done') { } else if (name == 'job_done') {
parent.target?.fileModel.jobDone(evt); parent.target?.fileModel.jobController.jobDone(evt);
parent.target?.fileModel.refreshAll();
} else if (name == 'job_error') { } else if (name == 'job_error') {
parent.target?.fileModel.jobError(evt); parent.target?.fileModel.jobController.jobError(evt);
} else if (name == 'override_file_confirm') { } else if (name == 'override_file_confirm') {
parent.target?.fileModel.overrideFileConfirm(evt); parent.target?.fileModel.overrideFileConfirm(evt);
} else if (name == 'load_last_job') { } else if (name == 'load_last_job') {
parent.target?.fileModel.loadLastJob(evt); parent.target?.fileModel.jobController.loadLastJob(evt);
} else if (name == 'update_folder_files') { } else if (name == 'update_folder_files') {
parent.target?.fileModel.updateFolderFiles(evt); parent.target?.fileModel.jobController.updateFolderFiles(evt);
} else if (name == 'add_connection') { } else if (name == 'add_connection') {
parent.target?.serverModel.addConnection(evt); parent.target?.serverModel.addConnection(evt);
} else if (name == 'on_client_remove') { } else if (name == 'on_client_remove') {
@ -371,8 +372,12 @@ class FfiModel with ChangeNotifier {
_updateSessionWidthHeight(String id) { _updateSessionWidthHeight(String id) {
parent.target?.canvasModel.updateViewStyle(); parent.target?.canvasModel.updateViewStyle();
if (display.width <= 0 || display.height <= 0) {
debugPrintStack(label: 'invalid display size (${display.width},${display.height})');
} else {
bind.sessionSetSize(id: id, width: display.width, height: display.height); bind.sessionSetSize(id: id, width: display.width, height: display.height);
} }
}
/// Handle the peer info event based on [evt]. /// Handle the peer info event based on [evt].
handlePeerInfo(Map<String, dynamic> evt, String peerId) async { handlePeerInfo(Map<String, dynamic> evt, String peerId) async {
@ -1571,9 +1576,6 @@ class FFI {
}(); }();
// every instance will bind a stream // every instance will bind a stream
this.id = id; this.id = id;
if (isFileTransfer) {
fileModel.initFileFetcher();
}
} }
/// Login with [password], choose if the client should [remember] it. /// Login with [password], choose if the client should [remember] it.

@ -28,10 +28,9 @@ class StateGlobal {
setWindowId(int id) => _windowId = id; setWindowId(int id) => _windowId = id;
setMaximize(bool v) { setMaximize(bool v) {
if (_maximize != v) { if (_maximize != v && !_fullscreen) {
_maximize = v; _maximize = v;
_resizeEdgeSize.value = _resizeEdgeSize.value = _maximize ? kMaximizeEdgeSize : kWindowEdgeSize;
_maximize ? kMaximizeEdgeSize : kWindowEdgeSize;
} }
} }
setFullscreen(bool v) { setFullscreen(bool v) {
@ -39,7 +38,13 @@ class StateGlobal {
_fullscreen = v; _fullscreen = v;
_showTabBar.value = !_fullscreen; _showTabBar.value = !_fullscreen;
_resizeEdgeSize.value = _resizeEdgeSize.value =
fullscreen ? kFullScreenEdgeSize : kWindowEdgeSize; fullscreen
? kFullScreenEdgeSize
: _maximize
? kMaximizeEdgeSize
: kWindowEdgeSize;
print(
"fullscreen: ${fullscreen}, resizeEdgeSize: ${_resizeEdgeSize.value}");
_windowBorderWidth.value = fullscreen ? 0 : kWindowBorderWidth; _windowBorderWidth.value = fullscreen ? 0 : kWindowBorderWidth;
WindowController.fromWindowId(windowId) WindowController.fromWindowId(windowId)
.setFullscreen(_fullscreen) .setFullscreen(_fullscreen)

@ -59,7 +59,7 @@ dependencies:
desktop_multi_window: desktop_multi_window:
git: git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window url: https://github.com/Kingtous/rustdesk_desktop_multi_window
ref: 3e2655677c54f421f9e378680d8171b95a211e0f ref: e3947d4b4f8edaa655de63cd47f2a59a6e024218
freezed_annotation: ^2.0.3 freezed_annotation: ^2.0.3
flutter_custom_cursor: ^0.0.4 flutter_custom_cursor: ^0.0.4
window_size: window_size:

@ -7,17 +7,17 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
flexi_logger = { version = "0.22", features = ["async", "use_chrono_for_offset"] } flexi_logger = { version = "0.25", features = ["async"] }
protobuf = { version = "3.1", features = ["with-bytes"] } protobuf = { version = "3.1", features = ["with-bytes"] }
tokio = { version = "1.20", features = ["full"] } tokio = { version = "1.20", features = ["full"] }
tokio-util = { version = "0.7", features = ["full"] } tokio-util = { version = "0.7", features = ["full"] }
futures = "0.3" futures = "0.3"
bytes = { version = "1.2", features = ["serde"] } bytes = { version = "1.2", features = ["serde"] }
log = "0.4" log = "0.4"
env_logger = "0.9" env_logger = "0.10"
socket2 = { version = "0.3", features = ["reuseport"] } socket2 = { version = "0.3", features = ["reuseport"] }
zstd = "0.9" zstd = "0.9"
quinn = {version = "0.8", optional = true } quinn = {version = "0.9", optional = true }
anyhow = "1.0" anyhow = "1.0"
futures-util = "0.3" futures-util = "0.3"
directories-next = "2.0" directories-next = "2.0"
@ -34,7 +34,7 @@ tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
chrono = "0.4" chrono = "0.4"
backtrace = "0.3" backtrace = "0.3"
libc = "0.2" libc = "0.2"
sysinfo = "0.24" sysinfo = "0.26"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
mac_address = "1.1" mac_address = "1.1"
@ -54,5 +54,5 @@ winapi = { version = "0.3", features = ["winuser"] }
osascript = "0.3.0" osascript = "0.3.0"
[dev-dependencies] [dev-dependencies]
toml = "0.5" toml = "0.7"
serde_json = "1.0" serde_json = "1.0"

@ -4,7 +4,7 @@ use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{Arc, Mutex, RwLock}, sync::{Arc, Mutex, RwLock},
time::SystemTime, time::{Duration, Instant, SystemTime},
}; };
use anyhow::Result; use anyhow::Result;
@ -51,6 +51,7 @@ lazy_static::lazy_static! {
pub static ref APP_NAME: Arc<RwLock<String>> = Arc::new(RwLock::new("RustDesk".to_owned())); pub static ref APP_NAME: Arc<RwLock<String>> = Arc::new(RwLock::new("RustDesk".to_owned()));
static ref KEY_PAIR: Arc<Mutex<Option<KeyPair>>> = Default::default(); static ref KEY_PAIR: Arc<Mutex<Option<KeyPair>>> = Default::default();
static ref HW_CODEC_CONFIG: Arc<RwLock<HwCodecConfig>> = Arc::new(RwLock::new(HwCodecConfig::load())); static ref HW_CODEC_CONFIG: Arc<RwLock<HwCodecConfig>> = Arc::new(RwLock::new(HwCodecConfig::load()));
static ref USER_DEFAULT_CONFIG: Arc<RwLock<(UserDefaultConfig, Instant)>> = Arc::new(RwLock::new((UserDefaultConfig::load(), Instant::now())));
} }
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -123,7 +124,7 @@ macro_rules! serde_field_bool {
} }
impl $struct_name { impl $struct_name {
pub fn $func() -> bool { pub fn $func() -> bool {
UserDefaultConfig::load().get($field_name) == "Y" UserDefaultConfig::read().get($field_name) == "Y"
} }
} }
}; };
@ -980,21 +981,21 @@ impl PeerConfig {
serde_field_string!( serde_field_string!(
default_view_style, default_view_style,
deserialize_view_style, deserialize_view_style,
UserDefaultConfig::load().get("view_style") UserDefaultConfig::read().get("view_style")
); );
serde_field_string!( serde_field_string!(
default_scroll_style, default_scroll_style,
deserialize_scroll_style, deserialize_scroll_style,
UserDefaultConfig::load().get("scroll_style") UserDefaultConfig::read().get("scroll_style")
); );
serde_field_string!( serde_field_string!(
default_image_quality, default_image_quality,
deserialize_image_quality, deserialize_image_quality,
UserDefaultConfig::load().get("image_quality") UserDefaultConfig::read().get("image_quality")
); );
fn default_custom_image_quality() -> Vec<i32> { fn default_custom_image_quality() -> Vec<i32> {
let f: f64 = UserDefaultConfig::load() let f: f64 = UserDefaultConfig::read()
.get("custom_image_quality") .get("custom_image_quality")
.parse() .parse()
.unwrap_or(50.0); .unwrap_or(50.0);
@ -1020,15 +1021,15 @@ impl PeerConfig {
let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?; let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?;
let mut key = "codec-preference"; let mut key = "codec-preference";
if !mp.contains_key(key) { if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
} }
key = "custom-fps"; key = "custom-fps";
if !mp.contains_key(key) { if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
} }
key = "zoom-cursor"; key = "zoom-cursor";
if !mp.contains_key(key) { if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
} }
Ok(mp) Ok(mp)
} }
@ -1046,7 +1047,12 @@ serde_field_bool!(
default_show_quality_monitor, default_show_quality_monitor,
"ShowQualityMonitor::default_show_quality_monitor" "ShowQualityMonitor::default_show_quality_monitor"
); );
serde_field_bool!(DisableAudio, "disable_audio", default_disable_audio, "DisableAudio::default_disable_audio"); serde_field_bool!(
DisableAudio,
"disable_audio",
default_disable_audio,
"DisableAudio::default_disable_audio"
);
serde_field_bool!( serde_field_bool!(
EnableFileTransfer, EnableFileTransfer,
"enable_file_transfer", "enable_file_transfer",
@ -1065,9 +1071,19 @@ serde_field_bool!(
default_lock_after_session_end, default_lock_after_session_end,
"LockAfterSessionEnd::default_lock_after_session_end" "LockAfterSessionEnd::default_lock_after_session_end"
); );
serde_field_bool!(PrivacyMode, "privacy_mode", default_privacy_mode, "PrivacyMode::default_privacy_mode"); serde_field_bool!(
PrivacyMode,
"privacy_mode",
default_privacy_mode,
"PrivacyMode::default_privacy_mode"
);
serde_field_bool!(AllowSwapKey, "allow_swap_key", default_allow_swap_key, "AllowSwapKey::default_allow_swap_key"); serde_field_bool!(
AllowSwapKey,
"allow_swap_key",
default_allow_swap_key,
"AllowSwapKey::default_allow_swap_key"
);
#[derive(Debug, Default, Serialize, Deserialize, Clone)] #[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct LocalConfig { pub struct LocalConfig {
@ -1282,6 +1298,14 @@ pub struct UserDefaultConfig {
} }
impl UserDefaultConfig { impl UserDefaultConfig {
pub fn read() -> UserDefaultConfig {
let mut cfg = USER_DEFAULT_CONFIG.write().unwrap();
if cfg.1.elapsed() > Duration::from_secs(1) {
*cfg = (Self::load(), Instant::now());
}
cfg.0.clone()
}
pub fn load() -> UserDefaultConfig { pub fn load() -> UserDefaultConfig {
Config::load_::<UserDefaultConfig>("_default") Config::load_::<UserDefaultConfig>("_default")
} }

@ -42,7 +42,7 @@ quest = "0.3"
[build-dependencies] [build-dependencies]
target_build_utils = "0.3" target_build_utils = "0.3"
bindgen = "0.59" bindgen = "0.64"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
dbus = { version = "0.9", optional = true } dbus = { version = "0.9", optional = true }

@ -83,7 +83,7 @@ impl crate::TraitCapturer for Capturer {
} }
} }
pub struct Frame<'a>(quartz::Frame, PhantomData<&'a [u8]>); pub struct Frame<'a>(pub quartz::Frame, PhantomData<&'a [u8]>);
impl<'a> ops::Deref for Frame<'a> { impl<'a> ops::Deref for Frame<'a> {
type Target = [u8]; type Target = [u8];

@ -708,6 +708,7 @@ pub struct AudioHandler {
audio_stream: Option<Box<dyn StreamTrait>>, audio_stream: Option<Box<dyn StreamTrait>>,
channels: u16, channels: u16,
latency_controller: Arc<Mutex<LatencyController>>, latency_controller: Arc<Mutex<LatencyController>>,
ignore_count: i32,
} }
impl AudioHandler { impl AudioHandler {
@ -810,7 +811,11 @@ impl AudioHandler {
.check_audio(frame.timestamp) .check_audio(frame.timestamp)
.not() .not()
{ {
log::debug!("audio frame {} is ignored", frame.timestamp); self.ignore_count += 1;
if self.ignore_count == 100 {
self.ignore_count = 0;
log::debug!("100 audio frames are ignored");
}
return; return;
} }
} }
@ -2233,7 +2238,7 @@ fn get_pk(pk: &[u8]) -> Option<[u8; 32]> {
#[inline] #[inline]
fn get_rs_pk(str_base64: &str) -> Option<sign::PublicKey> { fn get_rs_pk(str_base64: &str) -> Option<sign::PublicKey> {
if let Ok(pk) = base64::decode(str_base64) { if let Ok(pk) = crate::decode64(str_base64) {
get_pk(&pk).map(|x| sign::PublicKey(x)) get_pk(&pk).map(|x| sign::PublicKey(x))
} else { } else {
None None

@ -787,3 +787,15 @@ pub fn handle_url_scheme(url: String) {
let _ = crate::run_me(vec![url]); let _ = crate::run_me(vec![url]);
} }
} }
#[inline]
pub fn encode64<T: AsRef<[u8]>>(input: T) -> String {
#[allow(deprecated)]
base64::encode(input)
}
#[inline]
pub fn decode64<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, base64::DecodeError> {
#[allow(deprecated)]
base64::decode(input)
}

@ -461,9 +461,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Ανάλυση"), ("Resolution", "Ανάλυση"),
("No transfers in progress", "Δεν υπάρχει μεταφορά σε εξέλιξη"), ("No transfers in progress", "Δεν υπάρχει μεταφορά σε εξέλιξη"),
("Set one-time password length", "Μέγεθος κωδικού μιας χρήσης"), ("Set one-time password length", "Μέγεθος κωδικού μιας χρήσης"),
("idd_driver_tip", ""), ("idd_driver_tip", "Εγκαταστήστε το πρόγραμμα οδήγησης εικονικής οθόνης που χρησιμοποιείται όταν δεν έχετε φυσικές οθόνες."),
("confirm_idd_driver_tip", ""), ("confirm_idd_driver_tip", "Είναι ενεργοποιημένη η επιλογή εγκατάστασης του προγράμματος οδήγησης εικονικής οθόνης. Λάβετε υπόψη ότι θα εγκατασταθεί ένα δοκιμαστικό πιστοποιητικό για το πρόγραμμα οδήγησης εικονικής οθόνης. Αυτό το πιστοποιητικό θα χρησιμοποιηθεί μόνο για την πιστοποίηση των προγραμμάτων οδήγησης του Rustdesk."),
("RDP Settings", ""), ("RDP Settings", "Ρυθμίσεις RDP"),
("Sort by", ""), ("Sort by", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -461,9 +461,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Resolución"), ("Resolution", "Resolución"),
("No transfers in progress", "No hay transferencias en curso"), ("No transfers in progress", "No hay transferencias en curso"),
("Set one-time password length", "Establecer contraseña de un solo uso"), ("Set one-time password length", "Establecer contraseña de un solo uso"),
("idd_driver_tip", ""), ("idd_driver_tip", "Instalar controlador virtual de pantalla a usar cuando no hay pantalla física."),
("confirm_idd_driver_tip", ""), ("confirm_idd_driver_tip", "La opción de instalar el controlador de pantalla virtual está marcada. Hay que tener en cuenta que se instalará un certificado de prueba para confirar en el controlador de pantalla. Este certificado solo se usará para confiar en controladores Rustdesk."),
("RDP Settings", ""), ("RDP Settings", "Ajustes RDP"),
("Sort by", ""), ("Sort by", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -461,9 +461,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Разрешение"), ("Resolution", "Разрешение"),
("No transfers in progress", "Передача не осуществляется"), ("No transfers in progress", "Передача не осуществляется"),
("Set one-time password length", "Установить длину одноразового пароля"), ("Set one-time password length", "Установить длину одноразового пароля"),
("idd_driver_tip", ""), ("idd_driver_tip", "Установите драйвер виртуального дисплея, который используется при отсутствии физических дисплеев."),
("confirm_idd_driver_tip", ""), ("confirm_idd_driver_tip", "Включена функция установки драйвера виртуального дисплея. Обратите внимание, что для доверия к драйверу будет установлен тестовый сертификат. Этот сертификат будет использоваться только для подтверждения доверия драйверам Rustdesk."),
("RDP Settings", ""), ("RDP Settings", "Настройки RDP"),
("Sort by", ""), ("Sort by", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -1,3 +1,4 @@
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use hbb_common::{bail, sodiumoxide::crypto::sign, ResultType}; use hbb_common::{bail, sodiumoxide::crypto::sign, ResultType};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -18,7 +19,7 @@ fn get_license_from_string_(s: &str) -> ResultType<License> {
12, 46, 129, 83, 17, 84, 193, 119, 197, 130, 103, 12, 46, 129, 83, 17, 84, 193, 119, 197, 130, 103,
]; ];
let pk = sign::PublicKey(*PK); let pk = sign::PublicKey(*PK);
let data = base64::decode_config(tmp, base64::URL_SAFE_NO_PAD)?; let data = URL_SAFE_NO_PAD.decode(tmp)?;
if let Ok(lic) = serde_json::from_slice::<License>(&data) { if let Ok(lic) = serde_json::from_slice::<License>(&data) {
return Ok(lic); return Ok(lic);
} }

@ -1,10 +1,11 @@
mod license; mod license;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use hbb_common::ResultType; use hbb_common::ResultType;
use license::*; use license::*;
fn gen_name(lic: &License) -> ResultType<String> { fn gen_name(lic: &License) -> ResultType<String> {
let tmp = serde_json::to_vec::<License>(lic)?; let tmp = serde_json::to_vec::<License>(lic)?;
let tmp = base64::encode_config(tmp, base64::URL_SAFE_NO_PAD); let tmp = URL_SAFE_NO_PAD.encode(&tmp);
let tmp: String = tmp.chars().rev().collect(); let tmp: String = tmp.chars().rev().collect();
Ok(tmp) Ok(tmp)
} }

@ -8,6 +8,9 @@
// https://github.com/codebytere/node-mac-permissions/blob/main/permissions.mm // https://github.com/codebytere/node-mac-permissions/blob/main/permissions.mm
extern "C" bool InputMonitoringAuthStatus(bool prompt) { extern "C" bool InputMonitoringAuthStatus(bool prompt) {
#ifdef NO_InputMonitoringAuthStatus
return true;
#else
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_15) { if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_15) {
IOHIDAccessType theType = IOHIDCheckAccess(kIOHIDRequestTypeListenEvent); IOHIDAccessType theType = IOHIDCheckAccess(kIOHIDRequestTypeListenEvent);
NSLog(@"IOHIDCheckAccess = %d, kIOHIDAccessTypeGranted = %d", theType, kIOHIDAccessTypeGranted); NSLog(@"IOHIDCheckAccess = %d, kIOHIDAccessTypeGranted = %d", theType, kIOHIDAccessTypeGranted);
@ -36,6 +39,7 @@ extern "C" bool InputMonitoringAuthStatus(bool prompt) {
return true; return true;
} }
return false; return false;
#endif
} }
extern "C" bool MacCheckAdminAuthorization() { extern "C" bool MacCheckAdminAuthorization() {

@ -735,7 +735,7 @@ impl Connection {
let url = self.server_audit_conn.clone(); let url = self.server_audit_conn.clone();
let mut v = v; let mut v = v;
v["id"] = json!(Config::get_id()); v["id"] = json!(Config::get_id());
v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); v["uuid"] = json!(crate::encode64(hbb_common::get_uuid()));
v["conn_id"] = json!(self.inner.id); v["conn_id"] = json!(self.inner.id);
tokio::spawn(async move { tokio::spawn(async move {
allow_err!(Self::post_audit_async(url, v).await); allow_err!(Self::post_audit_async(url, v).await);
@ -765,7 +765,7 @@ impl Connection {
info["files"] = json!(files); info["files"] = json!(files);
let v = json!({ let v = json!({
"id":json!(Config::get_id()), "id":json!(Config::get_id()),
"uuid":json!(base64::encode(hbb_common::get_uuid())), "uuid":json!(crate::encode64(hbb_common::get_uuid())),
"peer_id":json!(self.lr.my_id), "peer_id":json!(self.lr.my_id),
"type": r#type as i8, "type": r#type as i8,
"path":path, "path":path,
@ -788,7 +788,7 @@ impl Connection {
} }
let mut v = Value::default(); let mut v = Value::default();
v["id"] = json!(Config::get_id()); v["id"] = json!(Config::get_id());
v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); v["uuid"] = json!(crate::encode64(hbb_common::get_uuid()));
v["typ"] = json!(typ as i8); v["typ"] = json!(typ as i8);
v["from_remote"] = json!(from_remote); v["from_remote"] = json!(from_remote);
v["info"] = serde_json::Value::String(info.to_string()); v["info"] = serde_json::Value::String(info.to_string());

@ -848,7 +848,7 @@ pub fn elevate_portable(_id: i32) {
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline] #[inline]
pub fn handle_incoming_voice_call(id: i32, accept: bool) { pub fn handle_incoming_voice_call(id: i32, accept: bool) {
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { if let Some(client) = CLIENTS.read().unwrap().get(&id) {
allow_err!(client.tx.send(Data::VoiceCallResponse(accept))); allow_err!(client.tx.send(Data::VoiceCallResponse(accept)));
}; };
} }
@ -856,7 +856,7 @@ pub fn handle_incoming_voice_call(id: i32, accept: bool) {
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline] #[inline]
pub fn close_voice_call(id: i32) { pub fn close_voice_call(id: i32) {
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { if let Some(client) = CLIENTS.read().unwrap().get(&id) {
allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned()))); allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned())));
}; };
} }

@ -605,7 +605,7 @@ pub fn remove_discovered(id: String) {
#[inline] #[inline]
pub fn get_uuid() -> String { pub fn get_uuid() -> String {
base64::encode(hbb_common::get_uuid()) crate::encode64(hbb_common::get_uuid())
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
@ -876,7 +876,7 @@ pub async fn change_id_shared(id: String, old_id: String) -> &'static str {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
let uuid = machine_uid::get().unwrap_or("".to_owned()); let uuid = machine_uid::get().unwrap_or("".to_owned());
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
let uuid = base64::encode(hbb_common::get_uuid()); let uuid = crate::encode64(hbb_common::get_uuid());
if uuid.is_empty() { if uuid.is_empty() {
log::error!("Failed to change id, uuid is_empty"); log::error!("Failed to change id, uuid is_empty");