Merge branch 'rustdesk:master' into master

This commit is contained in:
botanicvelious 2023-02-05 17:42:49 -07:00 committed by GitHub
commit 41c50c4c51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
96 changed files with 2817 additions and 1214 deletions

41
Cargo.lock generated
View File

@ -1137,7 +1137,7 @@ checksum = "413487ef345ab5cdfbf23e66070741217a701bce70f2f397a54221b4f2b6056a"
dependencies = [
"dconf_rs",
"detect-desktop-environment",
"dirs",
"dirs 4.0.0",
"objc",
"rust-ini",
"web-sys",
@ -1401,6 +1401,16 @@ dependencies = [
"dirs-sys-next",
]
[[package]]
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [
"cfg-if 0.1.10",
"dirs-sys",
]
[[package]]
name = "dirs"
version = "4.0.0"
@ -1873,6 +1883,19 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fruitbasket"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "898289b8e0528c84fb9b88f15ac9d5109bcaf23e0e49bb6f64deee0d86b6a351"
dependencies = [
"dirs 2.0.2",
"objc",
"objc-foundation",
"objc_id",
"time 0.1.45",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@ -3657,6 +3680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
"objc_exception",
]
[[package]]
@ -3670,6 +3694,15 @@ dependencies = [
"objc_id",
]
[[package]]
name = "objc_exception"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
dependencies = [
"cc",
]
[[package]]
name = "objc_id"
version = "0.1.1"
@ -4655,6 +4688,7 @@ dependencies = [
"flexi_logger",
"flutter_rust_bridge",
"flutter_rust_bridge_codegen",
"fruitbasket",
"glib 0.16.5",
"gtk",
"hbb_common",
@ -4673,6 +4707,7 @@ dependencies = [
"mouce",
"num_cpus",
"objc",
"objc_id",
"parity-tokio-ipc",
"rdev",
"repng",
@ -4713,7 +4748,7 @@ name = "rustdesk-portable-packer"
version = "0.1.0"
dependencies = [
"brotli",
"dirs",
"dirs 4.0.0",
"embed-resource",
"md5",
]
@ -6591,7 +6626,7 @@ dependencies = [
"async-trait",
"byteorder",
"derivative",
"dirs",
"dirs 4.0.0",
"enumflags2",
"event-listener",
"futures-core",

View File

@ -106,6 +106,8 @@ core-graphics = "0.22"
include_dir = "0.7.2"
tray-item = "0.7" # looks better than trayicon
dark-light = "0.2"
fruitbasket = "0.10.0"
objc_id = "0.1.1"
[target.'cfg(target_os = "linux")'.dependencies]
psimple = { package = "libpulse-simple-binding", version = "2.25" }

View File

@ -1,6 +1,6 @@
<p dir="rtl" align="center">
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
<a href="#تصاویر-محیط-نرمافزار">تصاویر محیط نرم‌افزار</a>
<a href="#تصاویر-محیط-نرم افزار">تصاویر محیط نرم‌افزار</a>
<a href="#ساختار-پوشه-ها">ساختار</a>
<a href="#نحوه-ساخت-با-داکر">داکر</a>
<a href="#ساخت">ساخت</a>
@ -9,12 +9,12 @@
<p align="center" dir="auto">[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>]</p>
<p dir="rtl" align="center"><b>برای ترجمه این سند (README)، <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang" dir="rtl">رابط کاربری RustDesk</a>، <a href="https://github.com/rustdesk/doc.rustdesk.com" dir="rtl">و مستندات آن</a> به زبان مادری شما به کمکتان نیازمندیم. </b></p>
با ما گپ بزنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV)
با ما گفتگو کنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
راست‌دسک (RustDesk) نرم‌افزاری برای گارکردن با رایانه‌ی رومیزی از راه دور است و با زبان برنامه‌نویسی Rust نوشته شده است. نیاز به تنظیمات چندانی ندارد و شما را قادر می سازد تا بدون نگرانی از امنیت اطلاعات خود بر آن‌ها کنترل کامل داشته باشید.
راست‌دسک (RustDesk) نرم‌افزاری برای کارکردن با رایانه‌ی رومیزی از راه دور است و با زبان برنامه‌نویسی Rust نوشته شده است. نیاز به تنظیمات چندانی ندارد و شما را قادر می سازد تا بدون نگرانی از امنیت اطلاعات خود بر آن‌ها کنترل کامل داشته باشید.
می‌توانید از سرور rendezvous/relay ما استفاده کنید، [سرور خودتان را راه‌اندازی کنید](https://rustdesk.com/server) یا
[ سرورrendezvous/relay خود را بنویسید](https://github.com/rustdesk/rustdesk).
@ -130,7 +130,7 @@ cd rustdesk
docker build -t "rustdesk-builder" .
```
سپس، هر بار که نیاز به ساخت ترم‌افزار داشتید، دستور زیر را اجرا کنید:
سپس، هر بار که نیاز به ساخت نرم‌افزار داشتید، دستور زیر را اجرا کنید:
```sh
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder

View File

@ -3,13 +3,11 @@ import 'dart:convert';
import 'dart:ffi' hide Size;
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:win32/win32.dart' as win32;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -18,14 +16,17 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_hbb/utils/platform_channel.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:uni_links/uni_links.dart';
import 'package:uni_links_desktop/uni_links_desktop.dart';
import 'package:window_manager/window_manager.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:window_size/window_size.dart' as window_size;
import 'package:url_launcher/url_launcher.dart';
import 'package:win32/win32.dart' as win32;
import 'package:window_manager/window_manager.dart';
import 'package:window_size/window_size.dart' as window_size;
import '../consts.dart';
import 'common/widgets/overlay.dart';
import 'mobile/pages/file_manager_page.dart';
import 'mobile/pages/remote_page.dart';
@ -33,8 +34,6 @@ import 'models/input_model.dart';
import 'models/model.dart';
import 'models/platform_model.dart';
import '../consts.dart';
final globalKey = GlobalKey<NavigatorState>();
final navigationBarKey = GlobalKey();
@ -205,6 +204,9 @@ class MyTheme {
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
outlinedButtonTheme: OutlinedButtonThemeData(
style:
OutlinedButton.styleFrom(side: BorderSide(color: Colors.white38))),
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
@ -221,16 +223,18 @@ class MyTheme {
return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme));
}
static void changeDarkMode(ThemeMode mode) {
static void changeDarkMode(ThemeMode mode) async {
Get.changeThemeMode(mode);
if (desktopType == DesktopType.main) {
if (mode == ThemeMode.system) {
bind.mainSetLocalOption(key: kCommConfKeyTheme, value: '');
await bind.mainSetLocalOption(key: kCommConfKeyTheme, value: '');
} else {
bind.mainSetLocalOption(
await bind.mainSetLocalOption(
key: kCommConfKeyTheme, value: mode.toShortString());
}
bind.mainChangeTheme(dark: mode.toShortString());
await bind.mainChangeTheme(dark: mode.toShortString());
// Synchronize the window theme of the system.
updateSystemWindowTheme();
}
}
@ -608,12 +612,12 @@ class CustomAlertDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
FocusNode focusNode = FocusNode();
// request focus if there is no focused FocusNode in the dialog
Future.delayed(Duration.zero, () {
if (!focusNode.hasFocus) focusNode.requestFocus();
});
// request focus
FocusScopeNode scopeNode = FocusScopeNode();
Future.delayed(Duration.zero, () {
if (!scopeNode.hasFocus) scopeNode.requestFocus();
});
const double padding = 16;
return FocusScope(
node: scopeNode,
autofocus: true,
@ -638,17 +642,18 @@ class CustomAlertDialog extends StatelessWidget {
child: AlertDialog(
scrollable: true,
title: title,
contentPadding: EdgeInsets.symmetric(
horizontal: contentPadding ?? 25, vertical: 10),
contentPadding: EdgeInsets.fromLTRB(
contentPadding ?? padding, 25, contentPadding ?? padding, 10),
content: ConstrainedBox(
constraints: contentBoxConstraints,
child: Theme(
data: ThemeData(
constraints: contentBoxConstraints,
child: Theme(
data: Theme.of(context).copyWith(
inputDecorationTheme: InputDecorationTheme(
isDense: true, contentPadding: EdgeInsets.all(15)),
),
child: content)),
isDense: true, contentPadding: EdgeInsets.all(15))),
child: content),
),
actions: actions,
actionsPadding: EdgeInsets.fromLTRB(0, 0, padding, padding),
),
);
}
@ -689,7 +694,6 @@ void msgBox(String id, String type, String title, String text, String link,
buttons.insert(
0, dialogButton('Cancel', onPressed: cancel, isOutline: true));
}
// TODO: test this button
if (type.contains("hasclose")) {
buttons.insert(
0,
@ -702,9 +706,8 @@ void msgBox(String id, String type, String title, String text, String link,
}
dialogManager.show(
(setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title),
content:
SelectableText(translate(text), style: const TextStyle(fontSize: 15)),
title: null,
content: SelectionArea(child: msgboxContent(type, title, text)),
actions: buttons,
onSubmit: hasOk ? submit : null,
onCancel: hasCancel == true ? cancel : null,
@ -713,30 +716,74 @@ void msgBox(String id, String type, String title, String text, String link,
);
}
Widget msgBoxButton(String text, void Function() onPressed) {
return ButtonTheme(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
//limits the touch area to the button area
minWidth: 0,
//wraps child's width
height: 0,
child: TextButton(
style: flatButtonStyle,
onPressed: onPressed,
child:
Text(translate(text), style: TextStyle(color: MyTheme.accent))));
Color? _msgboxColor(String type) {
if (type == "input-password" || type == "custom-os-password") {
return Color(0xFFAD448E);
}
if (type.contains("success")) {
return Color(0xFF32bea6);
}
if (type.contains("error") || type == "re-input-password") {
return Color(0xFFE04F5F);
}
return Color(0xFF2C8CFF);
}
Widget _msgBoxTitle(String title) =>
Text(translate(title), style: TextStyle(fontSize: 21));
Widget msgboxIcon(String type) {
IconData? iconData;
if (type.contains("error") || type == "re-input-password") {
iconData = Icons.cancel;
}
if (type.contains("success")) {
iconData = Icons.check_circle;
}
if (type == "wait-uac" || type == "wait-remote-accept-nook") {
iconData = Icons.hourglass_top;
}
if (type == 'on-uac' || type == 'on-foreground-elevated') {
iconData = Icons.admin_panel_settings;
}
if (type == "info") {
iconData = Icons.info;
}
if (iconData != null) {
return Icon(iconData, size: 50, color: _msgboxColor(type))
.marginOnly(right: 16);
}
return Offstage();
}
// title should be null
Widget msgboxContent(String type, String title, String text) {
return Row(
children: [
msgboxIcon(type),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
translate(title),
style: TextStyle(fontSize: 21),
).marginOnly(bottom: 10),
Text(translate(text), style: const TextStyle(fontSize: 15)),
],
),
),
],
).marginOnly(bottom: 12);
}
void msgBoxCommon(OverlayDialogManager dialogManager, String title,
Widget content, List<Widget> buttons,
{bool hasCancel = true}) {
dialogManager.dismissAll();
dialogManager.show((setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title),
title: Text(
translate(title),
style: TextStyle(fontSize: 21),
),
content: content,
actions: buttons,
onCancel: hasCancel ? close : null,
@ -1223,10 +1270,12 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
/// [Availability]
/// initUniLinks should only be used on macos/windows.
/// we use dbus for linux currently.
Future<void> initUniLinks() async {
if (!Platform.isWindows && !Platform.isMacOS) {
return;
Future<bool> initUniLinks() async {
if (Platform.isLinux) {
return false;
}
// Register uni links for Windows. The required info of url scheme is already
// declared in `Info.plist` for macOS.
if (Platform.isWindows) {
registerProtocol('rustdesk');
}
@ -1234,11 +1283,12 @@ Future<void> initUniLinks() async {
try {
final initialLink = await getInitialLink();
if (initialLink == null) {
return;
return false;
}
parseRustdeskUri(initialLink);
return parseRustdeskUri(initialLink);
} catch (err) {
debugPrintStack(label: "$err");
return false;
}
}
@ -1259,11 +1309,19 @@ StreamSubscription? listenUniLinks() {
return sub;
}
/// Returns true if we successfully handle the startup arguments.
/// Handle command line arguments
///
/// * Returns true if we successfully handle the startup arguments.
bool checkArguments() {
if (kBootArgs.isNotEmpty) {
final ret = parseRustdeskUri(kBootArgs.first);
if (ret) {
return true;
}
}
// bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05]
// check connect args
final connectIndex = kBootArgs.indexOf("--connect");
var connectIndex = kBootArgs.indexOf("--connect");
if (connectIndex == -1) {
return false;
}
@ -1298,7 +1356,7 @@ bool checkArguments() {
bool parseRustdeskUri(String uriPath) {
final uri = Uri.tryParse(uriPath);
if (uri == null) {
print("uri is not valid: $uriPath");
debugPrint("uri is not valid: $uriPath");
return false;
}
return callUniLinksUriHandler(uri);
@ -1317,7 +1375,7 @@ bool callUniLinksUriHandler(Uri uri) {
Future.delayed(Duration.zero, () {
rustDeskWinManager.newRemoteDesktop(peerId, switch_uuid: switch_uuid);
});
return false;
return true;
}
return false;
}
@ -1457,8 +1515,12 @@ Future<void> onActiveWindowChanged() async {
} catch (err) {
debugPrintStack(label: "$err");
} finally {
debugPrint("Start closing RustDesk...");
await windowManager.setPreventClose(false);
await windowManager.close();
if (Platform.isMacOS) {
RdPlatformChannel.instance.terminate();
}
}
}
}
@ -1590,7 +1652,8 @@ class ServerConfig {
Widget dialogButton(String text,
{required VoidCallback? onPressed,
bool isOutline = false,
TextStyle? style}) {
TextStyle? style,
ButtonStyle? buttonStyle}) {
if (isDesktop) {
if (isOutline) {
return OutlinedButton(
@ -1599,7 +1662,7 @@ Widget dialogButton(String text,
);
} else {
return ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
style: ElevatedButton.styleFrom(elevation: 0).merge(buttonStyle),
onPressed: onPressed,
child: Text(translate(text), style: style),
);
@ -1637,3 +1700,16 @@ String getWindowName({WindowType? overrideType}) {
String getWindowNameWithId(String id, {WindowType? overrideType}) {
return "${DesktopTab.labelGetterAlias(id).value} - ${getWindowName(overrideType: overrideType)}";
}
Future<void> updateSystemWindowTheme() async {
// Set system window theme for macOS.
final userPreference = MyTheme.getThemeModePreference();
if (userPreference != ThemeMode.system) {
if (Platform.isMacOS) {
await RdPlatformChannel.instance.changeSystemWindowTheme(
userPreference == ThemeMode.light
? SystemWindowTheme.light
: SystemWindowTheme.dark);
}
}
}

View File

@ -1,5 +1,9 @@
import 'dart:io';
import 'package:flutter_hbb/models/peer_model.dart';
import '../../models/platform_model.dart';
class HttpType {
static const kAuthReqTypeAccount = "account";
static const kAuthReqTypeMobile = "mobile";
@ -48,6 +52,16 @@ class PeerPayload {
}
}
class DeviceInfo {
static Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['os'] = Platform.operatingSystem;
data['type'] = "client";
data['name'] = bind.mainGetHostname();
return data;
}
}
class LoginRequest {
String? username;
String? password;
@ -56,7 +70,7 @@ class LoginRequest {
bool? autoLogin;
String? type;
String? verificationCode;
String? deviceInfo;
Map<String, dynamic> deviceInfo = DeviceInfo.toJson();
LoginRequest(
{this.username,
@ -65,19 +79,7 @@ class LoginRequest {
this.uuid,
this.autoLogin,
this.type,
this.verificationCode,
this.deviceInfo});
LoginRequest.fromJson(Map<String, dynamic> json) {
username = json['username'];
password = json['password'];
id = json['id'];
uuid = json['uuid'];
autoLogin = json['autoLogin'];
type = json['type'];
verificationCode = json['verificationCode'];
deviceInfo = json['deviceInfo'];
}
this.verificationCode});
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
@ -88,7 +90,7 @@ class LoginRequest {
data['autoLogin'] = autoLogin ?? '';
data['type'] = type ?? '';
data['verificationCode'] = verificationCode ?? '';
data['deviceInfo'] = deviceInfo ?? '';
data['deviceInfo'] = deviceInfo;
return data;
}
}

View File

@ -51,7 +51,7 @@ class ChatPage extends StatelessWidget implements PageShape {
return Stack(
children: [
LayoutBuilder(builder: (context, constraints) {
return DashChat(
final chat = DashChat(
onSend: (chatMsg) {
chatModel.send(chatMsg);
chatModel.inputNode.requestFocus();
@ -108,6 +108,7 @@ class ChatPage extends StatelessWidget implements PageShape {
borderBottomLeft: 8,
)),
);
return SelectionArea(child: chat);
}),
desktopType == DesktopType.cm ||
chatModel.currentID == ChatModel.clientModeID

View File

@ -666,6 +666,8 @@ Future<bool?> verificationCodeDialog(UserPayload? user) async {
child: const LinearProgressIndicator()),
],
),
onCancel: close,
onSubmit: onVerify,
actions: [
dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("Verify", onPressed: onVerify),

View File

@ -1,3 +1,4 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:provider/provider.dart';
@ -316,46 +317,52 @@ class _DraggableState extends State<Draggable> {
}
class QualityMonitor extends StatelessWidget {
static const textStyle = TextStyle(color: MyTheme.grayBg);
final QualityMonitorModel qualityMonitorModel;
QualityMonitor(this.qualityMonitorModel);
Widget _row(String info, String? value, {Color? rightColor}) {
return Row(
children: [
Expanded(
flex: 8,
child: AutoSizeText(info,
style: TextStyle(color: MyTheme.darkGray),
textAlign: TextAlign.right,
maxLines: 1)),
Spacer(flex: 1),
Expanded(
flex: 8,
child: AutoSizeText(value ?? '',
style: TextStyle(color: rightColor ?? Colors.white),
maxLines: 1)),
],
);
}
@override
Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: qualityMonitorModel,
child: Consumer<QualityMonitorModel>(
builder: (context, qualityMonitorModel, child) => Positioned(
top: 10,
right: 10,
child: qualityMonitorModel.show
? Container(
padding: const EdgeInsets.all(8),
color: MyTheme.canvasColor.withAlpha(120),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: textStyle,
),
Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: textStyle,
),
Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: textStyle,
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: textStyle,
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: textStyle,
),
],
),
)
: const SizedBox.shrink())));
builder: (context, qualityMonitorModel, child) => qualityMonitorModel
.show
? Container(
constraints: BoxConstraints(maxWidth: 200),
padding: const EdgeInsets.all(8),
color: MyTheme.canvasColor.withAlpha(120),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_row("Speed", qualityMonitorModel.data.speed ?? '-'),
_row("FPS", qualityMonitorModel.data.fps ?? '-'),
_row(
"Delay", "${qualityMonitorModel.data.delay ?? '-'}ms",
rightColor: Colors.green),
_row("Target Bitrate",
"${qualityMonitorModel.data.targetBitrate ?? '-'}kb"),
_row(
"Codec", qualityMonitorModel.data.codecFormat ?? '-'),
],
),
)
: const SizedBox.shrink()));
}

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart';
@ -20,7 +21,9 @@ const int groupTabIndex = 4;
const String defaultGroupTabname = 'Group';
class StatePeerTab {
final RxInt currentTab = 0.obs;
final RxInt currentTab = 0.obs; // index in tabNames
final RxList<int> visibleOrderedTabs = RxList.empty(growable: true);
List<int> tabOrder = List.from([0, 1, 2, 3, 4]); // constant length
final RxInt tabHiddenFlag = 0.obs;
final RxList<String> tabNames = [
'Recent Sessions',
@ -31,53 +34,80 @@ class StatePeerTab {
].obs;
StatePeerTab._() {
// init tabHiddenFlag
tabHiddenFlag.value = (int.tryParse(
bind.getLocalFlutterConfig(k: 'hidden-peer-card'),
radix: 2) ??
0);
var tabs = _notHiddenTabs();
// remove dynamic tabs
tabs.remove(groupTabIndex);
// init tabOrder
try {
final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order');
if (conf.isNotEmpty) {
final json = jsonDecode(conf);
if (json is List) {
final List<int> list =
json.map((e) => int.tryParse(e.toString()) ?? -1).toList();
if (list.length == tabOrder.length &&
tabOrder.every((e) => list.contains(e))) {
tabOrder = list;
}
}
}
} catch (e) {
debugPrintStack(label: '$e');
}
// init visibleOrderedTabs
var tempList = tabOrder.toList();
tempList.removeWhere((e) => !tabs.contains(e));
visibleOrderedTabs.value = tempList;
// init currentTab
currentTab.value =
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0;
if (!tabs.contains(currentTab.value)) {
currentTab.value = 0;
if (tabs.isNotEmpty) {
currentTab.value = tabs[0];
} else {
currentTab.value = 0;
}
}
}
static final StatePeerTab instance = StatePeerTab._();
// check dynamic tabs
check() {
var tabs = _notHiddenTabs();
if (filterGroupCard()) {
if (currentTab.value == groupTabIndex) {
currentTab.value =
tabs.firstWhereOrNull((e) => e != groupTabIndex) ?? 0;
bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: currentTab.value.toString());
}
tabOrder2visibleOrderedTabs();
if (visibleOrderedTabs.contains(groupTabIndex) &&
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ==
groupTabIndex) {
currentTab.value = groupTabIndex;
}
if (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isNotEmpty) {
tabNames[groupTabIndex] = gFFI.userModel.groupName.value;
} else {
if (gFFI.userModel.isAdmin.isFalse &&
gFFI.userModel.groupName.isNotEmpty) {
tabNames[groupTabIndex] = gFFI.userModel.groupName.value;
} else {
tabNames[groupTabIndex] = defaultGroupTabname;
}
if (tabs.contains(groupTabIndex) &&
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ==
groupTabIndex) {
currentTab.value = groupTabIndex;
}
tabNames[groupTabIndex] = defaultGroupTabname;
}
}
List<int> currentTabs() {
var v = List<int>.empty(growable: true);
for (int i = 0; i < tabNames.length; i++) {
if (!_isTabHidden(i) && !_isTabFilter(i)) {
v.add(i);
}
visibleOrderedTabs2TabOrder() {
var tmpTabOrder = visibleOrderedTabs.toList();
var left = tabOrder.where((e) => !tmpTabOrder.contains(e)).toList();
for (var t in left) {
_addTabInOrder(tmpTabOrder, t);
}
return v;
statePeerTab.tabOrder = tmpTabOrder;
bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(tmpTabOrder));
}
tabOrder2visibleOrderedTabs() {
var visible = statePeerTab.visibleTabs();
statePeerTab.visibleOrderedTabs.value =
statePeerTab.tabOrder.where((e) => visible.contains(e)).toList();
}
// return true if hide group card
bool filterGroupCard() {
if (gFFI.groupModel.users.isEmpty ||
(gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) {
@ -87,6 +117,17 @@ class StatePeerTab {
}
}
// return index array of tabNames
List<int> visibleTabs() {
var v = List<int>.empty(growable: true);
for (int i = 0; i < tabNames.length; i++) {
if (!_isTabHidden(i) && !_isTabFilter(i)) {
v.add(i);
}
}
return v;
}
bool _isTabHidden(int tabindex) {
return tabHiddenFlag & (1 << tabindex) != 0;
}
@ -107,6 +148,41 @@ class StatePeerTab {
}
return v;
}
// add tabIndex to list
_addTabInOrder(List<int> list, int tabIndex) {
if (!tabOrder.contains(tabIndex) || list.contains(tabIndex)) {
return;
}
bool sameOrder = true;
int lastIndex = -1;
for (int i = 0; i < list.length; i++) {
var index = tabOrder.lastIndexOf(list[i]);
if (index > lastIndex) {
lastIndex = index;
continue;
} else {
sameOrder = false;
break;
}
}
if (sameOrder) {
var indexInTabOrder = tabOrder.indexOf(tabIndex);
var left = List.empty(growable: true);
for (int i = 0; i < indexInTabOrder; i++) {
left.add(tabOrder[i]);
}
int insertIndex = list.lastIndexWhere((e) => left.contains(e));
if (insertIndex < 0) {
insertIndex = 0;
} else {
insertIndex += 1;
}
list.insert(insertIndex, tabIndex);
} else {
list.add(tabIndex);
}
}
}
final statePeerTab = StatePeerTab.instance;
@ -177,11 +253,6 @@ class _PeerTabPageState extends State<PeerTabPage>
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
@ -213,42 +284,58 @@ class _PeerTabPageState extends State<PeerTabPage>
}
Widget _createSwitchBar(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Obx(() {
var tabs = statePeerTab.currentTabs();
return ListView(
var tabs = statePeerTab.visibleOrderedTabs;
int indexCounter = -1;
return ReorderableListView(
buildDefaultDragHandles: false,
onReorder: (oldIndex, newIndex) {
if (oldIndex < newIndex) {
newIndex -= 1;
}
var list = tabs.toList();
final int item = list.removeAt(oldIndex);
list.insert(newIndex, item);
tabs.value = list;
statePeerTab.visibleOrderedTabs2TabOrder();
},
scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(),
controller: ScrollController(),
scrollController: ScrollController(),
children: tabs.map((t) {
return InkWell(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: statePeerTab.currentTab.value == t
? Theme.of(context).backgroundColor
: null,
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
),
child: Align(
alignment: Alignment.center,
child: Text(
translatedTabname(t),
textAlign: TextAlign.center,
style: TextStyle(
height: 1,
fontSize: 14,
color: statePeerTab.currentTab.value == t
? textColor
: textColor
?..withOpacity(0.5)),
indexCounter++;
return ReorderableDragStartListener(
key: ValueKey(t),
index: indexCounter,
child: InkWell(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: statePeerTab.currentTab.value == t
? Theme.of(context).backgroundColor
: null,
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
),
)),
onTap: () async {
await handleTabSelection(t);
await bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: t.toString());
},
child: Align(
alignment: Alignment.center,
child: Text(
translatedTabname(t),
textAlign: TextAlign.center,
style: TextStyle(
height: 1,
fontSize: 14,
color: statePeerTab.currentTab.value == t
? MyTheme.tabbar(context).selectedTextColor
: MyTheme.tabbar(context).unSelectedTextColor
?..withOpacity(0.5)),
),
)),
onTap: () async {
await handleTabSelection(t);
await bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: t.toString());
},
),
);
}).toList());
});
@ -275,7 +362,7 @@ class _PeerTabPageState extends State<PeerTabPage>
final verticalMargin = isDesktop ? 12.0 : 6.0;
return Expanded(
child: Obx(() {
var tabs = statePeerTab.currentTabs();
var tabs = statePeerTab.visibleOrderedTabs;
if (tabs.isEmpty) {
return visibleContextMenuListener(Center(
child: Text(translate('Right click to select tabs')),
@ -322,7 +409,7 @@ class _PeerTabPageState extends State<PeerTabPage>
}
adjustTab() {
var tabs = statePeerTab.currentTabs();
var tabs = statePeerTab.visibleOrderedTabs;
if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) {
statePeerTab.currentTab.value = tabs[0];
}
@ -349,11 +436,13 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget visibleContextMenu(CancelFunc cancelFunc) {
return Obx(() {
final List<MenuEntryBase> menu = List.empty(growable: true);
final List<int> menuIndex = List.empty(growable: true);
for (int i = 0; i < statePeerTab.tabNames.length; i++) {
if (i == groupTabIndex && statePeerTab.filterGroupCard()) {
continue;
}
int bitMask = 1 << i;
menuIndex.add(i);
menu.add(MenuEntrySwitch(
switchType: SwitchType.scheckbox,
text: translatedTabname(i),
@ -369,12 +458,21 @@ class _PeerTabPageState extends State<PeerTabPage>
await bind.setLocalFlutterConfig(
k: 'hidden-peer-card',
v: statePeerTab.tabHiddenFlag.value.toRadixString(2));
statePeerTab.tabOrder2visibleOrderedTabs();
cancelFunc();
adjustTab();
}));
}
// show in tabOrder
List<MenuEntryBase> menu2 = List.empty(growable: true);
statePeerTab.tabOrder.map((e) {
final index = menuIndex.indexOf(e);
if (index >= 0) {
menu2.add(menu[index]);
}
}).toList();
return mod_menu.PopupMenu(
items: menu
items: menu2
.map((entry) => entry.build(
context,
const MenuConfig(
@ -419,7 +517,12 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
Widget _buildSearchBar() {
RxBool focused = false.obs;
FocusNode focusNode = FocusNode();
focusNode.addListener(() => focused.value = focusNode.hasFocus);
focusNode.addListener(() {
focused.value = focusNode.hasFocus;
peerSearchTextController.selection = TextSelection(
baseOffset: 0,
extentOffset: peerSearchTextController.value.text.length);
});
return Container(
width: 120,
decoration: BoxDecoration(

View File

@ -64,11 +64,9 @@ class RawPointerMouseRegion extends StatelessWidget {
},
onPointerMove: inputModel.onPointMoveImage,
onPointerSignal: inputModel.onPointerSignalImage,
/*
onPointerPanZoomStart: inputModel.onPointerPanZoomStart,
onPointerPanZoomUpdate: inputModel.onPointerPanZoomUpdate,
onPointerPanZoomEnd: inputModel.onPointerPanZoomEnd,
*/
child: MouseRegion(
cursor: cursor ?? MouseCursor.defer,
onEnter: onEnter,

View File

@ -1,17 +1,19 @@
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
const double kDesktopRemoteTabBarHeight = 28.0;
const int kMainWindowId = 0;
const String kPeerPlatformWindows = "Windows";
const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS";
const String kPeerPlatformAndroid = "Android";
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)' , 'Desktop CM Page', "Install Page"
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page"
const String kAppTypeMain = "main";
const String kAppTypeConnectionManager = "cm";
const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer";
const String kAppTypeDesktopPortForward = "port forward";
@ -24,7 +26,6 @@ const String kWindowEventShow = "show";
const String kWindowConnect = "connect";
const String kUniLinksPrefix = "rustdesk://";
const String kActionNewConnection = "connection/new/";
const String kTabLabelHomePage = "Home";
const String kTabLabelSettingPage = "Settings";

View File

@ -8,6 +8,7 @@ import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart';
@ -64,6 +65,8 @@ class _ConnectionPageState extends State<ConnectionPage>
});
_idFocusNode.addListener(() {
_idInputFocused.value = _idFocusNode.hasFocus;
// select all to faciliate removing text, just following the behavior of address input of chrome
_idController.selection = TextSelection(baseOffset: 0, extentOffset: _idController.value.text.length);
});
windowManager.addListener(this);
}
@ -90,6 +93,18 @@ class _ConnectionPageState extends State<ConnectionPage>
}
}
@override
void onWindowEnterFullScreen() {
// Remove edge border by setting the value to zero.
stateGlobal.resizeEdgeSize.value = 0;
}
@override
void onWindowLeaveFullScreen() {
// Restore edge border to default edge size.
stateGlobal.resizeEdgeSize.value = kWindowEdgeSize;
}
@override
void onWindowClose() {
super.onWindowClose();

View File

@ -33,6 +33,7 @@ const double _kContentFontSize = 15;
const Color _accentColor = MyTheme.accent;
const String _kSettingPageControllerTag = 'settingPageController';
const String _kSettingPageIndexTag = 'settingPageIndex';
const int _kPageCount = 6;
class _TabInfo {
late final String label;
@ -51,7 +52,7 @@ class DesktopSettingPage extends StatefulWidget {
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
static void switch2page(int page) {
if (page >= 5) return;
if (page >= _kPageCount) return;
try {
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
DesktopTabPage.onAddSetting(initialPage: page);
@ -75,6 +76,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_TabInfo('Security', Icons.enhanced_encryption_outlined,
Icons.enhanced_encryption),
_TabInfo('Network', Icons.link_outlined, Icons.link),
_TabInfo('Display', Icons.desktop_windows_outlined, Icons.desktop_windows),
_TabInfo('Account', Icons.person_outline, Icons.person),
_TabInfo('About', Icons.info_outline, Icons.info)
];
@ -88,7 +90,8 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
@override
void initState() {
super.initState();
selectedIndex = (widget.initialPage < 5 ? widget.initialPage : 0).obs;
selectedIndex =
(widget.initialPage < _kPageCount ? widget.initialPage : 0).obs;
Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
controller = PageController(initialPage: widget.initialPage);
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
@ -130,6 +133,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_General(),
_Safety(),
_Network(),
_Display(),
_Account(),
_About(),
],
@ -1047,6 +1051,247 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
}
}
class _Display extends StatefulWidget {
const _Display({Key? key}) : super(key: key);
@override
State<_Display> createState() => _DisplayState();
}
class _DisplayState extends State<_Display> {
@override
Widget build(BuildContext context) {
final scrollController = ScrollController();
return DesktopScrollWrapper(
scrollController: scrollController,
child: ListView(
controller: scrollController,
physics: NeverScrollableScrollPhysics(),
children: [
viewStyle(context),
scrollStyle(context),
imageQuality(context),
codec(context),
other(context),
]).marginOnly(bottom: _kListViewBottomMargin));
}
Widget viewStyle(BuildContext context) {
final key = 'view_style';
onChanged(String value) async {
await bind.mainSetUserDefaultOption(key: key, value: value);
setState(() {});
}
final groupValue = bind.mainGetUserDefaultOption(key: key);
return _Card(title: 'Default View Style', children: [
_Radio(context,
value: kRemoteViewStyleOriginal,
groupValue: groupValue,
label: 'Scale original',
onChanged: onChanged),
_Radio(context,
value: kRemoteViewStyleAdaptive,
groupValue: groupValue,
label: 'Scale adaptive',
onChanged: onChanged),
]);
}
Widget scrollStyle(BuildContext context) {
final key = 'scroll_style';
onChanged(String value) async {
await bind.mainSetUserDefaultOption(key: key, value: value);
setState(() {});
}
final groupValue = bind.mainGetUserDefaultOption(key: key);
return _Card(title: 'Default Scroll Style', children: [
_Radio(context,
value: kRemoteScrollStyleAuto,
groupValue: groupValue,
label: 'ScrollAuto',
onChanged: onChanged),
_Radio(context,
value: kRemoteScrollStyleBar,
groupValue: groupValue,
label: 'Scrollbar',
onChanged: onChanged),
]);
}
Widget imageQuality(BuildContext context) {
final key = 'image_quality';
onChanged(String value) async {
await bind.mainSetUserDefaultOption(key: key, value: value);
setState(() {});
}
final groupValue = bind.mainGetUserDefaultOption(key: key);
final qualityKey = 'custom_image_quality';
final qualityValue =
(double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ??
50.0)
.obs;
final fpsKey = 'custom-fps';
final fpsValue =
(double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? 30.0)
.obs;
return _Card(title: 'Default Image Quality', children: [
_Radio(context,
value: kRemoteImageQualityBest,
groupValue: groupValue,
label: 'Good image quality',
onChanged: onChanged),
_Radio(context,
value: kRemoteImageQualityBalanced,
groupValue: groupValue,
label: 'Balanced',
onChanged: onChanged),
_Radio(context,
value: kRemoteImageQualityLow,
groupValue: groupValue,
label: 'Optimize reaction time',
onChanged: onChanged),
_Radio(context,
value: kRemoteImageQualityCustom,
groupValue: groupValue,
label: 'Custom',
onChanged: onChanged),
Offstage(
offstage: groupValue != kRemoteImageQualityCustom,
child: Column(
children: [
Obx(() => Row(
children: [
Slider(
value: qualityValue.value,
min: 10.0,
max: 100.0,
divisions: 18,
onChanged: (double value) async {
qualityValue.value = value;
await bind.mainSetUserDefaultOption(
key: qualityKey, value: value.toString());
},
),
SizedBox(
width: 40,
child: Text(
'${qualityValue.value.round()}%',
style: const TextStyle(fontSize: 15),
)),
SizedBox(
width: 50,
child: Text(
translate('Bitrate'),
style: const TextStyle(fontSize: 15),
))
],
)),
Obx(() => Row(
children: [
Slider(
value: fpsValue.value,
min: 10.0,
max: 120.0,
divisions: 22,
onChanged: (double value) async {
fpsValue.value = value;
await bind.mainSetUserDefaultOption(
key: fpsKey, value: value.toString());
},
),
SizedBox(
width: 40,
child: Text(
'${fpsValue.value.round()}',
style: const TextStyle(fontSize: 15),
)),
SizedBox(
width: 50,
child: Text(
translate('FPS'),
style: const TextStyle(fontSize: 15),
))
],
)),
],
),
)
]);
}
Widget codec(BuildContext context) {
if (!bind.mainHasHwcodec()) {
return Offstage();
}
final key = 'codec-preference';
onChanged(String value) async {
await bind.mainSetUserDefaultOption(key: key, value: value);
setState(() {});
}
final groupValue = bind.mainGetUserDefaultOption(key: key);
return _Card(title: 'Default Codec', children: [
_Radio(context,
value: 'auto',
groupValue: groupValue,
label: 'Auto',
onChanged: onChanged),
_Radio(context,
value: 'vp9',
groupValue: groupValue,
label: 'VP9',
onChanged: onChanged),
_Radio(context,
value: 'h264',
groupValue: groupValue,
label: 'H264',
onChanged: onChanged),
_Radio(context,
value: 'h265',
groupValue: groupValue,
label: 'H265',
onChanged: onChanged),
]);
}
Widget otherRow(String label, String key) {
final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
onChanged(bool b) async {
await bind.mainSetUserDefaultOption(key: key, value: b ? 'Y' : '');
setState(() {});
}
return GestureDetector(
child: Row(
children: [
Checkbox(value: value, onChanged: (_) => onChanged(!value))
.marginOnly(right: 5),
Expanded(
child: Text(translate(label)),
)
],
).marginOnly(left: _kCheckBoxLeftMargin),
onTap: () => onChanged(!value));
}
Widget other(BuildContext context) {
return _Card(title: 'Other Default Options', children: [
otherRow('Show remote cursor', 'show_remote_cursor'),
otherRow('Zoom cursor', 'zoom-cursor'),
otherRow('Show quality monitor', 'show_quality_monitor'),
otherRow('Mute', 'disable_audio'),
otherRow('Allow file copy and paste', 'enable_file_transfer'),
otherRow('Disable clipboard', 'disable_clipboard'),
otherRow('Lock after session end', 'lock_after_session_end'),
otherRow('Privacy mode', 'privacy_mode'),
]);
}
}
class _Account extends StatefulWidget {
const _Account({Key? key}) : super(key: key);
@ -1113,10 +1358,12 @@ class _AboutState extends State<_About> {
const SizedBox(
height: 8.0,
),
Text('${translate('Version')}: $version')
.marginSymmetric(vertical: 4.0),
Text('${translate('Build Date')}: $buildDate')
.marginSymmetric(vertical: 4.0),
SelectionArea(
child: Text('${translate('Version')}: $version')
.marginSymmetric(vertical: 4.0)),
SelectionArea(
child: Text('${translate('Build Date')}: $buildDate')
.marginSymmetric(vertical: 4.0)),
InkWell(
onTap: () {
launchUrlString('https://rustdesk.com/privacy');
@ -1137,7 +1384,8 @@ class _AboutState extends State<_About> {
decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
padding:
const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
child: Row(
child: SelectionArea(
child: Row(
children: [
Expanded(
child: Column(
@ -1157,7 +1405,7 @@ class _AboutState extends State<_About> {
),
),
],
),
)),
).marginSymmetric(vertical: 4.0)
],
).marginOnly(left: _kContentHMargin)

View File

@ -23,7 +23,7 @@ class DesktopTabPage extends StatefulWidget {
DesktopTabController tabController = Get.find();
tabController.add(TabInfo(
key: kTabLabelSettingPage,
label: kTabLabelSettingPage,
label: translate(kTabLabelSettingPage),
selectedIcon: Icons.build_sharp,
unselectedIcon: Icons.build_outlined,
page: DesktopSettingPage(
@ -46,7 +46,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
RemoteCountState.init();
tabController.add(TabInfo(
key: kTabLabelHomePage,
label: kTabLabelHomePage,
label: translate(kTabLabelHomePage),
selectedIcon: Icons.home_sharp,
unselectedIcon: Icons.home_outlined,
closable: false,

View File

@ -3,7 +3,6 @@ import 'dart:io';
import 'dart:ui' as ui;
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_custom_cursor/cursor_manager.dart'
@ -279,6 +278,34 @@ class _RemotePageState extends State<RemotePage>
}
}
Widget _buildRawPointerMouseRegion(
Widget child,
PointerEnterEventListener? onEnter,
PointerExitEventListener? onExit,
) {
return RawPointerMouseRegion(
onEnter: enterView,
onExit: leaveView,
onPointerDown: (event) {
// A double check for blur status.
// Note: If there's an `onPointerDown` event is triggered, `_isWindowBlur` is expected being false.
// Sometimes the system does not send the necessary focus event to flutter. We should manually
// handle this inconsistent status by setting `_isWindowBlur` to false. So we can
// ensure the grab-key thread is running when our users are clicking the remote canvas.
if (_isWindowBlur) {
debugPrint(
"Unexpected status: onPointerDown is triggered while the remote window is in blur status");
_isWindowBlur = false;
}
if (!_rawKeyFocusNode.hasFocus) {
_rawKeyFocusNode.requestFocus();
}
},
inputModel: _ffi.inputModel,
child: child,
);
}
Widget getBodyForDesktop(BuildContext context) {
var paints = <Widget>[
MouseRegion(onEnter: (evt) {
@ -295,27 +322,8 @@ class _RemotePageState extends State<RemotePage>
cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved,
listenerBuilder: (child) => RawPointerMouseRegion(
onEnter: enterView,
onExit: leaveView,
onPointerDown: (event) {
// A double check for blur status.
// Note: If there's an `onPointerDown` event is triggered, `_isWindowBlur` is expected being false.
// Sometimes the system does not send the necessary focus event to flutter. We should manually
// handle this inconsistent status by setting `_isWindowBlur` to false. So we can
// ensure the grab-key thread is running when our users are clicking the remote canvas.
if (_isWindowBlur) {
debugPrint(
"Unexpected status: onPointerDown is triggered while the remote window is in blur status");
_isWindowBlur = false;
}
if (!_rawKeyFocusNode.hasFocus) {
_rawKeyFocusNode.requestFocus();
}
},
inputModel: _ffi.inputModel,
child: child,
),
listenerBuilder: (child) =>
_buildRawPointerMouseRegion(child, enterView, leaveView),
);
}))
];
@ -328,7 +336,14 @@ class _RemotePageState extends State<RemotePage>
zoomCursor: _zoomCursor,
))));
}
paints.add(QualityMonitor(_ffi.qualityMonitorModel));
paints.add(
Positioned(
top: 10,
right: 10,
child: _buildRawPointerMouseRegion(
QualityMonitor(_ffi.qualityMonitorModel), null, null),
),
);
paints.add(RemoteMenubar(
id: widget.id,
ffi: _ffi,
@ -347,10 +362,10 @@ class _RemotePageState extends State<RemotePage>
class ImagePaint extends StatefulWidget {
final String id;
final Rx<bool> zoomCursor;
final Rx<bool> cursorOverImage;
final Rx<bool> keyboardEnabled;
final Rx<bool> remoteCursorMoved;
final RxBool zoomCursor;
final RxBool cursorOverImage;
final RxBool keyboardEnabled;
final RxBool remoteCursorMoved;
final Widget Function(Widget)? listenerBuilder;
ImagePaint(
@ -373,10 +388,10 @@ class _ImagePaintState extends State<ImagePaint> {
final ScrollController _vertical = ScrollController();
String get id => widget.id;
Rx<bool> get zoomCursor => widget.zoomCursor;
Rx<bool> get cursorOverImage => widget.cursorOverImage;
Rx<bool> get keyboardEnabled => widget.keyboardEnabled;
Rx<bool> get remoteCursorMoved => widget.remoteCursorMoved;
RxBool get zoomCursor => widget.zoomCursor;
RxBool get cursorOverImage => widget.cursorOverImage;
RxBool get keyboardEnabled => widget.keyboardEnabled;
RxBool get remoteCursorMoved => widget.remoteCursorMoved;
Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder;
@override
@ -385,27 +400,50 @@ class _ImagePaintState extends State<ImagePaint> {
var c = Provider.of<CanvasModel>(context);
final s = c.scale;
mouseRegion({child}) => Obx(() => MouseRegion(
cursor: cursorOverImage.isTrue
? c.cursorEmbedded
? SystemMouseCursors.none
: keyboardEnabled.isTrue
? (() {
if (remoteCursorMoved.isTrue) {
_lastRemoteCursorMoved = true;
return SystemMouseCursors.none;
} else {
if (_lastRemoteCursorMoved) {
_lastRemoteCursorMoved = false;
_firstEnterImage.value = true;
}
return _buildCustomCursor(context, s);
}
}())
: _buildDisabledCursor(context, s)
: MouseCursor.defer,
onHover: (evt) {},
child: child));
mouseRegion({child}) => Obx(() {
double getCursorScale() {
var c = Provider.of<CanvasModel>(context);
var cursorScale = 1.0;
if (Platform.isWindows) {
// debug win10
final isViewAdaptive =
c.viewStyle.style == kRemoteViewStyleAdaptive;
if (zoomCursor.value && isViewAdaptive) {
cursorScale = s * c.devicePixelRatio;
}
} else {
final isViewOriginal =
c.viewStyle.style == kRemoteViewStyleOriginal;
if (zoomCursor.value || isViewOriginal) {
cursorScale = s;
}
}
return cursorScale;
}
return MouseRegion(
cursor: cursorOverImage.isTrue
? c.cursorEmbedded
? SystemMouseCursors.none
: keyboardEnabled.isTrue
? (() {
if (remoteCursorMoved.isTrue) {
_lastRemoteCursorMoved = true;
return SystemMouseCursors.none;
} else {
if (_lastRemoteCursorMoved) {
_lastRemoteCursorMoved = false;
_firstEnterImage.value = true;
}
return _buildCustomCursor(
context, getCursorScale());
}
}())
: _buildDisabledCursor(context, getCursorScale())
: MouseCursor.defer,
onHover: (evt) {},
child: child);
});
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
final imageWidth = c.getDisplayWidth() * s;
@ -451,7 +489,7 @@ class _ImagePaintState extends State<ImagePaint> {
if (cache == null) {
return MouseCursor.defer;
} else {
final key = cache.updateGetKey(scale, zoomCursor.value);
final key = cache.updateGetKey(scale);
if (!cursor.cachedKeys.contains(key)) {
debugPrint("Register custom cursor with key $key");
// [Safety]
@ -617,7 +655,8 @@ class CursorPaint extends StatelessWidget {
double x = (m.x - hotx) * c.scale + cx;
double y = (m.y - hoty) * c.scale + cy;
double scale = 1.0;
if (zoomCursor.isTrue) {
final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal;
if (zoomCursor.value || isViewOriginal) {
x = m.x - hotx + cx / c.scale;
y = m.y - hoty + cy / c.scale;
scale = c.scale;

View File

@ -38,8 +38,9 @@ class ConnectionTabPage extends StatefulWidget {
}
class _ConnectionTabPageState extends State<ConnectionTabPage> {
final tabController = Get.put(DesktopTabController(
tabType: DesktopTabType.remoteScreen));
final tabController =
Get.put(DesktopTabController(tabType: DesktopTabType.remoteScreen));
final contentKey = UniqueKey();
static const IconData selectedIcon = Icons.desktop_windows_sharp;
static const IconData unselectedIcon = Icons.desktop_windows_outlined;
@ -80,7 +81,6 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
super.initState();
tabController.onRemoved = (_, id) => onRemoveId(id);
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
print(
@ -113,6 +113,9 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
}
_update_remote_count();
});
Future.delayed(Duration.zero, () {
restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId());
});
}
@override
@ -197,11 +200,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
);
return Platform.isMacOS
? tabWidget
: SubWindowDragToResizeArea(
child: tabWidget,
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
windowId: stateGlobal.windowId,
);
: Obx(() => SubWindowDragToResizeArea(
key: contentKey,
child: tabWidget,
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
windowId: stateGlobal.windowId,
));
}
// Note: Some dup code to ../widgets/remote_menubar
@ -269,6 +273,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
menu.add(MenuEntryDivider<String>());
menu.add(() {
final state = ShowRemoteCursorState.find(key);
final optKey = 'show-remote-cursor';
return MenuEntrySwitch2<String>(
switchType: SwitchType.scheckbox,
text: translate('Show remote cursor'),
@ -276,9 +281,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
return state;
},
setter: (bool v) async {
state.value = v;
await bind.sessionToggleOption(
id: key, value: 'show-remote-cursor');
await bind.sessionToggleOption(id: key, value: optKey);
state.value = bind.sessionGetToggleOptionSync(id: key, arg: optKey);
cancelFunc();
},
padding: padding,

View File

@ -790,6 +790,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
_PopupMenuRoute({
required this.position,
required this.items,
this.menuWrapper,
this.initialValue,
this.elevation,
required this.barrierLabel,
@ -802,6 +803,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
final RelativeRect position;
final List<PopupMenuEntry<T>> items;
final MenuWrapper? menuWrapper;
final List<Size?> itemSizes;
final T? initialValue;
final double? elevation;
@ -844,11 +846,14 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
}
}
final Widget menu = _PopupMenu<T>(
Widget menu = _PopupMenu<T>(
route: this,
semanticLabel: semanticLabel,
constraints: constraints,
);
if (this.menuWrapper != null) {
menu = this.menuWrapper!(menu);
}
final MediaQueryData mediaQuery = MediaQuery.of(context);
return MediaQuery.removePadding(
context: context,
@ -1035,6 +1040,7 @@ Future<T?> showMenu<T>({
required BuildContext context,
required RelativeRect position,
required List<PopupMenuEntry<T>> items,
MenuWrapper? menuWrapper,
T? initialValue,
double? elevation,
String? semanticLabel,
@ -1062,6 +1068,7 @@ Future<T?> showMenu<T>({
return navigator.push(_PopupMenuRoute<T>(
position: position,
items: items,
menuWrapper: menuWrapper,
initialValue: initialValue,
elevation: elevation,
semanticLabel: semanticLabel,
@ -1094,6 +1101,8 @@ typedef PopupMenuCanceled = void Function();
typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(
BuildContext context);
typedef MenuWrapper = Widget Function(Widget child);
/// Displays a menu when pressed and calls [onSelected] when the menu is dismissed
/// because an item was selected. The value passed to [onSelected] is the value of
/// the selected menu item.
@ -1124,6 +1133,7 @@ class PopupMenuButton<T> extends StatefulWidget {
const PopupMenuButton({
Key? key,
required this.itemBuilder,
this.menuWrapper,
this.initialValue,
this.onHover,
this.onSelected,
@ -1151,6 +1161,9 @@ class PopupMenuButton<T> extends StatefulWidget {
/// Called when the button is pressed to create the items to show in the menu.
final PopupMenuItemBuilder<T> itemBuilder;
/// Menu wrapper.
final MenuWrapper? menuWrapper;
/// The value of the menu item, if any, that should be highlighted when the menu opens.
final T? initialValue;
@ -1333,6 +1346,7 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
context: context,
elevation: widget.elevation ?? popupMenuTheme.elevation,
items: items,
menuWrapper: widget.menuWrapper,
initialValue: widget.initialValue,
position: position,
shape: widget.shape ?? popupMenuTheme.shape,

View File

@ -109,13 +109,17 @@ class MenuConfig {
this.boxWidth});
}
typedef DismissCallback = Function();
abstract class MenuEntryBase<T> {
bool dismissOnClicked;
DismissCallback? dismissCallback;
RxBool? enabled;
MenuEntryBase({
this.dismissOnClicked = false,
this.enabled,
this.dismissCallback,
});
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
@ -146,12 +150,14 @@ class MenuEntryRadioOption {
String value;
bool dismissOnClicked;
RxBool? enabled;
DismissCallback? dismissCallback;
MenuEntryRadioOption({
required this.text,
required this.value,
this.dismissOnClicked = false,
this.enabled,
this.dismissCallback,
});
}
@ -177,8 +183,13 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
required this.optionSetter,
this.padding,
dismissOnClicked = false,
dismissCallback,
RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
}) : super(
dismissOnClicked: dismissOnClicked,
enabled: enabled,
dismissCallback: dismissCallback,
) {
() async {
_curOption.value = await curOptionGetter();
}();
@ -249,6 +260,9 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
onPressed() {
if (opt.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context);
if (opt.dismissCallback != null) {
opt.dismissCallback!();
}
}
setOption(opt.value);
}
@ -360,6 +374,9 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
onPressed: () {
if (opt.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context);
if (opt.dismissCallback != null) {
opt.dismissCallback!();
}
}
setOption(opt.value);
},
@ -421,7 +438,12 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
this.textStyle,
this.padding,
RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
dismissCallback,
}) : super(
dismissOnClicked: dismissOnClicked,
enabled: enabled,
dismissCallback: dismissCallback,
);
RxBool get curOption;
Future<void> setOption(bool? option);
@ -463,6 +485,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
if (super.dismissCallback != null) {
super.dismissCallback!();
}
}
setOption(v);
},
@ -474,6 +499,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
if (super.dismissCallback != null) {
super.dismissCallback!();
}
}
setOption(v);
},
@ -485,6 +513,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
onPressed: () {
if (super.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context);
if (super.dismissCallback != null) {
super.dismissCallback!();
}
}
setOption(!curOption.value);
},
@ -508,6 +539,7 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
EdgeInsets? padding,
dismissOnClicked = false,
RxBool? enabled,
dismissCallback,
}) : super(
switchType: switchType,
text: text,
@ -515,6 +547,7 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
padding: padding,
dismissOnClicked: dismissOnClicked,
enabled: enabled,
dismissCallback: dismissCallback,
) {
() async {
_curOption.value = await getter();
@ -551,12 +584,15 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
EdgeInsets? padding,
dismissOnClicked = false,
RxBool? enabled,
dismissCallback,
}) : super(
switchType: switchType,
text: text,
textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked);
switchType: switchType,
text: text,
textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked,
dismissCallback: dismissCallback,
);
@override
RxBool get curOption => getter();
@ -627,9 +663,11 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
this.padding,
dismissOnClicked = false,
RxBool? enabled,
dismissCallback,
}) : super(
dismissOnClicked: dismissOnClicked,
enabled: enabled,
dismissCallback: dismissCallback,
);
Widget _buildChild(BuildContext context, MenuConfig conf) {
@ -641,6 +679,9 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
? () {
if (super.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context);
if (super.dismissCallback != null) {
super.dismissCallback!();
}
}
proc();
}

View File

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/main.dart';
import 'package:get/get.dart';
class RefreshWrapper extends StatefulWidget {
final Widget Function(BuildContext context) builder;
const RefreshWrapper({super.key, required this.builder});
@override
@ -30,6 +32,8 @@ class RefreshWrapperState extends State<RefreshWrapper> {
if (Get.context != null) {
(context as Element).visitChildren(_rebuildElement);
}
// Synchronize the window theme of the system.
updateSystemWindowTheme();
setState(() {});
}

View File

@ -221,6 +221,18 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
}
}
Widget _buildPointerTrackWidget(Widget child) {
return Listener(
onPointerHover: (PointerHoverEvent e) =>
widget.ffi.inputModel.lastMousePos = e.position,
child: MouseRegion(
child: child,
),
);
}
_menuDismissCallback() => widget.ffi.inputModel.refreshMousePos();
Widget _buildMenubar(BuildContext context) {
final List<Widget> menubarItems = [];
if (!isWebDesktop) {
@ -362,6 +374,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
),
)),
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
_menuDismissCallback();
}
RxInt display = CurrentDisplayState.find(widget.id);
if (display.value != i) {
bind.sessionSwitchDisplay(id: widget.id, value: i);
@ -376,9 +392,12 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
mod_menu.PopupMenuItem<String>(
height: _MenubarTheme.height,
padding: EdgeInsets.zero,
child: Row(
child: _buildPointerTrackWidget(
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: rowChildren),
children: rowChildren,
),
),
)
];
},
@ -426,6 +445,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
),
tooltip: translate('Display Settings'),
position: mod_menu.PopupMenuPosition.under,
menuWrapper: _buildPointerTrackWidget,
itemBuilder: (BuildContext context) =>
_getDisplayMenu(snapshot.data!, remoteCount)
.map((entry) => entry.build(
@ -534,6 +554,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
_menuDismissCallback();
}
showSetOSPassword(
widget.id, false, widget.ffi.dialogManager);
@ -546,6 +567,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
@ -557,6 +579,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
@ -568,6 +591,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
connect(context, widget.id, isTcpTunneling: true);
},
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
]);
// {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
@ -585,6 +609,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
);
}
@ -601,6 +626,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
}
@ -618,6 +644,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
@ -632,6 +659,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
if (pi.platform == kPeerPlatformWindows) {
@ -650,9 +678,11 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
if (pi.platform != kPeerPlatformAndroid &&
pi.platform != kPeerPlatformMacOS && // unsupport yet
version_cmp(peer_version, '1.2.0') >= 0) {
displayMenu.add(MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
@ -663,6 +693,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
showConfirmSwitchSidesDialog(widget.id, widget.ffi.dialogManager),
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
}
@ -678,6 +709,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
@ -699,6 +731,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
// },
// padding: padding,
// dismissOnClicked: true,
// dismissCallback: _menuDismissCallback,
// ));
// }
}
@ -744,11 +777,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
text: translate('Scale original'),
value: kRemoteViewStyleOriginal,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
MenuEntryRadioOption(
text: translate('Scale adaptive'),
value: kRemoteViewStyleAdaptive,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
],
curOptionGetter: () async {
@ -764,6 +799,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
MenuEntryDivider<String>(),
MenuEntryRadios<String>(
@ -773,21 +809,26 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
text: translate('Good image quality'),
value: kRemoteImageQualityBest,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
MenuEntryRadioOption(
text: translate('Balanced'),
value: kRemoteImageQualityBalanced,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
MenuEntryRadioOption(
text: translate('Optimize reaction time'),
value: kRemoteImageQualityLow,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
MenuEntryRadioOption(
text: translate('Custom'),
value: kRemoteImageQualityCustom,
dismissOnClicked: true),
text: translate('Custom'),
value: kRemoteImageQualityCustom,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
],
curOptionGetter: () async =>
// null means peer id is not found, which there's no need to care about
@ -857,18 +898,24 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
value: qualitySliderValue.value,
min: qualityMinValue,
max: qualityMaxValue,
divisions: 90,
divisions: 18,
onChanged: (double value) {
qualitySliderValue.value = value;
debouncerQuality.value = value;
},
),
SizedBox(
width: 90,
child: Obx(() => Text(
'${qualitySliderValue.value.round()}% Bitrate',
style: const TextStyle(fontSize: 15),
)))
width: 40,
child: Text(
'${qualitySliderValue.value.round()}%',
style: const TextStyle(fontSize: 15),
)),
SizedBox(
width: 50,
child: Text(
translate('Bitrate'),
style: const TextStyle(fontSize: 15),
))
],
));
// fps
@ -909,20 +956,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
))),
SizedBox(
width: 90,
child: Obx(() {
final fps = fpsSliderValue.value.round();
String text;
if (fps < 100) {
text = '$fps FPS';
} else {
text = '$fps FPS';
}
return Text(
text,
style: const TextStyle(fontSize: 15),
);
}))
width: 40,
child: Obx(() => Text(
'${fpsSliderValue.value.round()}',
style: const TextStyle(fontSize: 15),
))),
SizedBox(
width: 50,
child: Text(
translate('FPS'),
style: const TextStyle(fontSize: 15),
))
],
),
);
@ -949,12 +993,14 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
text: translate('ScrollAuto'),
value: kRemoteScrollStyleAuto,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
enabled: widget.ffi.canvasModel.imageOverflow,
),
MenuEntryRadioOption(
text: translate('Scrollbar'),
value: kRemoteScrollStyleBar,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
enabled: widget.ffi.canvasModel.imageOverflow,
),
],
@ -967,6 +1013,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
displayMenu.insert(3, MenuEntryDivider<String>());
@ -1037,6 +1084,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
);
}
@ -1063,11 +1111,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
text: translate('Auto'),
value: 'auto',
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
MenuEntryRadioOption(
text: 'VP9',
value: 'vp9',
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
),
];
if (codecs[0]) {
@ -1075,6 +1125,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
text: 'H264',
value: 'h264',
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
if (codecs[1]) {
@ -1082,6 +1133,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
text: 'H265',
value: 'h265',
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
return list;
@ -1098,14 +1150,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
}
displayMenu.add(MenuEntryDivider());
/// Show remote cursor
if (!widget.ffi.canvasModel.cursorEmbedded) {
displayMenu.add(() {
final state = ShowRemoteCursorState.find(widget.id);
final optKey = 'show-remote-cursor';
return MenuEntrySwitch2<String>(
switchType: SwitchType.scheckbox,
text: translate('Show remote cursor'),
@ -1113,12 +1168,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
return state;
},
setter: (bool v) async {
state.value = v;
await bind.sessionToggleOption(
id: widget.id, value: 'show-remote-cursor');
await bind.sessionToggleOption(id: widget.id, value: optKey);
state.value =
bind.sessionGetToggleOptionSync(id: widget.id, arg: optKey);
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
);
}());
}
@ -1135,11 +1191,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
return state;
},
setter: (bool v) async {
state.value = v;
await bind.sessionToggleOption(id: widget.id, value: opt);
state.value =
bind.sessionGetToggleOptionSync(id: widget.id, arg: opt);
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
);
}());
}
@ -1159,6 +1217,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
final perms = widget.ffi.ffiModel.permissions;
@ -1196,6 +1255,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: true,
dismissCallback: _menuDismissCallback,
));
}
}
@ -1267,6 +1327,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
_menuDismissCallback();
}
showKBLayoutTypeChooser(
localPlatform, widget.ffi.dialogManager);
@ -1279,6 +1340,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {},
padding: EdgeInsets.zero,
dismissOnClicked: false,
dismissCallback: _menuDismissCallback,
),
);
}
@ -1298,6 +1360,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
},
padding: padding,
dismissOnClicked: dismissOnClicked,
dismissCallback: _menuDismissCallback,
);
}
}
@ -1422,12 +1485,8 @@ void showConfirmSwitchSidesDialog(
}
return CustomAlertDialog(
title: Text(translate('Switch Sides')),
content: Column(
children: [
Text(translate('Please confirm if you want to share your desktop?')),
],
),
content: msgboxContent('info', 'Switch Sides',
'Please confirm if you want to share your desktop?'),
actions: [
dialogButton('Cancel', onPressed: close, isOutline: true),
dialogButton('OK', onPressed: submit),

View File

@ -1,23 +1,23 @@
import 'dart:io';
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide TabBarTheme;
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:scroll_pos/scroll_pos.dart';
import 'package:window_manager/window_manager.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:bot_toast/bot_toast.dart';
import '../../utils/multi_window_manager.dart';
@ -527,7 +527,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
void onWindowClose() async {
// hide window on close
if (widget.isMainWindow) {
await rustDeskWinManager.unregisterActiveWindow(0);
if (rustDeskWinManager.getActiveWindows().contains(kMainWindowId)) {
await rustDeskWinManager.unregisterActiveWindow(kMainWindowId);
}
// `hide` must be placed after unregisterActiveWindow, because once all windows are hidden,
// flutter closes the application on macOS. We should ensure the post-run logic has ran successfully.
// e.g.: saving window position.
@ -765,7 +767,7 @@ class _ListView extends StatelessWidget {
tabBuilder: tabBuilder,
tabMenuBuilder: tabMenuBuilder,
maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor: selectedTabBackgroundColor,
selectedTabBackgroundColor: selectedTabBackgroundColor ?? MyTheme.tabbar(context).selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
);
}).toList()));
@ -910,7 +912,7 @@ class _TabState extends State<_Tab> with RestorationMixin {
tabSelected: isSelected,
onClose: () => widget.onClose(),
)))
])).paddingSymmetric(horizontal: 10),
])).paddingOnly(left: 10, right: 5),
Offstage(
offstage: !showDivider,
child: VerticalDivider(
@ -956,7 +958,8 @@ class _CloseButton extends StatelessWidget {
child: Offstage(
offstage: !visible,
child: InkWell(
customBorder: const RoundedRectangleBorder(),
hoverColor: MyTheme.tabbar(context).closeHoverColor,
customBorder: const CircleBorder(),
onTap: () => onClose(),
child: Icon(
Icons.close,
@ -966,7 +969,7 @@ class _CloseButton extends StatelessWidget {
: MyTheme.tabbar(context).unSelectedIconColor,
),
),
)).paddingOnly(left: 5);
)).paddingOnly(left: 10);
}
}
@ -1055,6 +1058,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
final Color? unSelectedIconColor;
final Color? dividerColor;
final Color? hoverColor;
final Color? closeHoverColor;
final Color? selectedTabBackgroundColor;
const TabbarTheme(
{required this.selectedTabIconColor,
@ -1064,27 +1069,33 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
required this.selectedIconColor,
required this.unSelectedIconColor,
required this.dividerColor,
required this.hoverColor});
required this.hoverColor,
required this.closeHoverColor,
required this.selectedTabBackgroundColor});
static const light = TabbarTheme(
selectedTabIconColor: MyTheme.accent,
unSelectedTabIconColor: Color.fromARGB(255, 162, 203, 241),
selectedTextColor: Color.fromARGB(255, 26, 26, 26),
unSelectedTextColor: Color.fromARGB(255, 96, 96, 96),
selectedTextColor: Colors.black,
unSelectedTextColor: Color.fromARGB(255, 112, 112, 112),
selectedIconColor: Color.fromARGB(255, 26, 26, 26),
unSelectedIconColor: Color.fromARGB(255, 96, 96, 96),
dividerColor: Color.fromARGB(255, 238, 238, 238),
hoverColor: Color.fromARGB(51, 158, 158, 158));
hoverColor: Color.fromARGB(51, 158, 158, 158),
closeHoverColor: Color.fromARGB(255, 224, 224, 224),
selectedTabBackgroundColor: Color.fromARGB(255, 240, 240, 240));
static const dark = TabbarTheme(
selectedTabIconColor: MyTheme.accent,
unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98),
selectedTextColor: Color.fromARGB(255, 255, 255, 255),
unSelectedTextColor: Color.fromARGB(255, 207, 207, 207),
selectedIconColor: Color.fromARGB(255, 215, 215, 215),
unSelectedTextColor: Color.fromARGB(255, 192, 192, 192),
selectedIconColor: Color.fromARGB(255, 192, 192, 192),
unSelectedIconColor: Color.fromARGB(255, 255, 255, 255),
dividerColor: Color.fromARGB(255, 64, 64, 64),
hoverColor: Colors.black26);
hoverColor: Colors.black26,
closeHoverColor: Colors.black,
selectedTabBackgroundColor: Colors.black26);
@override
ThemeExtension<TabbarTheme> copyWith({
@ -1096,6 +1107,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
Color? unSelectedIconColor,
Color? dividerColor,
Color? hoverColor,
Color? closeHoverColor,
Color? selectedTabBackgroundColor,
}) {
return TabbarTheme(
selectedTabIconColor: selectedTabIconColor ?? this.selectedTabIconColor,
@ -1107,6 +1120,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
unSelectedIconColor: unSelectedIconColor ?? this.unSelectedIconColor,
dividerColor: dividerColor ?? this.dividerColor,
hoverColor: hoverColor ?? this.hoverColor,
closeHoverColor: closeHoverColor ?? this.closeHoverColor,
selectedTabBackgroundColor: selectedTabBackgroundColor ?? this.selectedTabBackgroundColor,
);
}
@ -1131,6 +1146,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
Color.lerp(unSelectedIconColor, other.unSelectedIconColor, t),
dividerColor: Color.lerp(dividerColor, other.dividerColor, t),
hoverColor: Color.lerp(hoverColor, other.hoverColor, t),
closeHoverColor: Color.lerp(closeHoverColor, other.closeHoverColor, t),
selectedTabBackgroundColor: Color.lerp(selectedTabBackgroundColor, other.selectedTabBackgroundColor, t),
);
}

View File

@ -1,22 +1,22 @@
import 'dart:convert';
import 'dart:io';
import 'package:bot_toast/bot_toast.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/desktop/pages/server_page.dart';
import 'package:flutter_hbb/desktop/pages/install_page.dart';
import 'package:flutter_hbb/desktop/pages/server_page.dart';
import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_port_forward_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart';
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:window_manager/window_manager.dart';
import 'package:bot_toast/bot_toast.dart';
// import 'package:window_manager/window_manager.dart';
@ -108,11 +108,12 @@ Future<void> initEnv(String appType) async {
await initGlobalFFI();
// await Firebase.initializeApp();
_registerEventHandler();
// Update the system theme.
updateSystemWindowTheme();
}
void runMainApp(bool startService) async {
// register uni links
initUniLinks();
await initEnv(kAppTypeMain);
// trigger connection status updater
await bind.mainCheckConnectStatus();
@ -122,23 +123,27 @@ void runMainApp(bool startService) async {
}
gFFI.userModel.refreshCurrentUser();
runApp(App());
// restore the location of the main window before window hide or show
await restoreWindowPosition(WindowType.Main);
// check the startup argument, if we successfully handle the argument, we keep the main window hidden.
if (checkArguments()) {
windowManager.hide();
} else {
windowManager.show();
windowManager.focus();
// move registration of active main window here to prevent async visible check.
rustDeskWinManager.registerActiveWindow(kWindowMainId);
}
// set window option
// Set window option.
WindowOptions windowOptions = getHiddenTitleBarWindowOptions();
windowManager.waitUntilReadyToShow(windowOptions, () async {
// Restore the location of the main window before window hide or show.
await restoreWindowPosition(WindowType.Main);
// Check the startup argument, if we successfully handle the argument, we keep the main window hidden.
final handledByUniLinks = await initUniLinks();
final handledByCli = checkArguments();
debugPrint(
"handled by uni links: $handledByUniLinks, handled by cli: $handledByCli");
if (handledByUniLinks || handledByCli) {
windowManager.hide();
} else {
windowManager.show();
windowManager.focus();
// Move registration of active main window here to prevent from async visible check.
rustDeskWinManager.registerActiveWindow(kWindowMainId);
}
windowManager.setOpacity(1);
windowManager.setTitle(getWindowName());
});
windowManager.setTitle(getWindowName());
}
void runMobileApp() async {
@ -206,7 +211,7 @@ void runMultiWindow(
}
void runConnectionManagerScreen(bool hide) async {
await initEnv(kAppTypeMain);
await initEnv(kAppTypeConnectionManager);
_runApp(
'',
const DesktopServerPage(),
@ -223,6 +228,7 @@ void showCmWindow() {
WindowOptions windowOptions =
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
windowManager.waitUntilReadyToShow(windowOptions, () async {
bind.mainHideDocker();
await windowManager.show();
await Future.wait([windowManager.focus(), windowManager.setOpacity(1)]);
// ensure initial window size to be changed
@ -236,6 +242,7 @@ void hideCmWindow() {
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
windowManager.setOpacity(0);
windowManager.waitUntilReadyToShow(windowOptions, () async {
bind.mainHideDocker();
await windowManager.hide();
});
}
@ -325,6 +332,8 @@ class _AppState extends State<App> {
to = ThemeMode.light;
}
Get.changeThemeMode(to);
// Synchronize the window theme of the system.
updateSystemWindowTheme();
if (desktopType == DesktopType.main) {
bind.mainChangeTheme(dark: to.toShortString());
}

View File

@ -497,7 +497,11 @@ class _RemotePageState extends State<RemotePage> {
child: Stack(children: () {
final paints = [
ImagePaint(),
QualityMonitor(gFFI.qualityMonitorModel),
Positioned(
top: 10,
right: 10,
child: QualityMonitor(gFFI.qualityMonitorModel),
),
getHelpTools(),
SizedBox(
width: 0,

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/widgets/button.dart';
import 'package:get/get.dart';
import '../../common.dart';
@ -9,7 +8,7 @@ import '../../models/model.dart';
import '../../models/platform_model.dart';
void clientClose(String id, OverlayDialogManager dialogManager) {
msgBox(id, '', 'Close', 'Are you sure to close the connection?', '',
msgBox(id, 'info', 'Close', 'Are you sure to close the connection?', '',
dialogManager);
}
@ -33,8 +32,10 @@ void showRestartRemoteDevice(
]),
content: Text(
"${translate('Are you sure you want to restart')} \n${pi.username}@${pi.hostname}($id) ?"),
onCancel: close,
onSubmit: () => close(true),
actions: [
dialogButton("Cancel", onPressed: () => close(), isOutline: true),
dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("OK", onPressed: () => close(true)),
],
));
@ -48,6 +49,18 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
var validateLength = false;
var validateSame = false;
dialogManager.show((setState, close) {
submit() async {
close();
dialogManager.showLoading(translate("Waiting"));
if (await gFFI.serverModel.setPermanentPassword(p0.text)) {
dialogManager.dismissAll();
showSuccess();
} else {
dialogManager.dismissAll();
showError();
}
}
return CustomAlertDialog(
title: Text(translate('Set your own password')),
content: Form(
@ -94,29 +107,17 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
},
),
])),
onCancel: close,
onSubmit: (validateLength && validateSame) ? submit : null,
actions: [
dialogButton(
'Cancel',
onPressed: () {
close();
},
onPressed: close,
isOutline: true,
),
dialogButton(
'OK',
onPressed: (validateLength && validateSame)
? () async {
close();
dialogManager.showLoading(translate("Waiting"));
if (await gFFI.serverModel.setPermanentPassword(p0.text)) {
dialogManager.dismissAll();
showSuccess();
} else {
dialogManager.dismissAll();
showError();
}
}
: null,
onPressed: (validateLength && validateSame) ? submit : null,
),
],
);
@ -205,26 +206,36 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async {
});
}
void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) {
dialogManager.show((setState, close) => CustomAlertDialog(
title: Text(translate('Wrong Password')),
content: Text(translate('Do you want to enter again?')),
actions: [
dialogButton(
'Cancel',
onPressed: () {
close();
closeConnection();
},
isOutline: true,
),
dialogButton(
'Retry',
onPressed: () {
enterPasswordDialog(id, dialogManager);
},
),
]));
void wrongPasswordDialog(
String id, OverlayDialogManager dialogManager, type, title, text) {
dialogManager.dismissAll();
dialogManager.show((setState, close) {
cancel() {
close();
closeConnection();
}
submit() {
enterPasswordDialog(id, dialogManager);
}
return CustomAlertDialog(
title: null,
content: msgboxContent(type, title, text),
onSubmit: submit,
onCancel: cancel,
actions: [
dialogButton(
'Cancel',
onPressed: cancel,
isOutline: true,
),
dialogButton(
'Retry',
onPressed: submit,
),
]);
});
}
void showServerSettingsWithValue(
@ -352,13 +363,14 @@ void showServerSettingsWithValue(
});
}
void showWaitUacDialog(String id, OverlayDialogManager dialogManager) {
void showWaitUacDialog(
String id, OverlayDialogManager dialogManager, String type) {
dialogManager.dismissAll();
dialogManager.show(
tag: '$id-wait-uac',
(setState, close) => CustomAlertDialog(
title: Text(translate('Wait')),
content: Text(translate('wait_accept_uac_tip')).marginAll(10),
title: null,
content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'),
));
}
@ -516,16 +528,6 @@ void showOnBlockDialog(
dialogManager.existing('$id-request-elevation')) {
return;
}
var content = Column(children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"${translate(text)}${type.contains('uac') ? '\n' : '\n\n'}${translate('request_elevation_tip')}",
textAlign: TextAlign.left,
style: TextStyle(fontWeight: FontWeight.w400),
).marginSymmetric(vertical: 15),
),
]);
dialogManager.show(tag: '$id-$type', (setState, close) {
void submit() {
close();
@ -533,12 +535,11 @@ void showOnBlockDialog(
}
return CustomAlertDialog(
title: Text(translate(title)),
content: content,
title: null,
content: msgboxContent(type, title,
"${translate(text)}${type.contains('uac') ? '\n' : '\n\n'}${translate('request_elevation_tip')}"),
actions: [
dialogButton('Wait', onPressed: () {
close();
}, isOutline: true),
dialogButton('Wait', onPressed: close, isOutline: true),
dialogButton('Request Elevation', onPressed: submit),
],
onSubmit: submit,
@ -556,8 +557,8 @@ void showElevationError(String id, String type, String title, String text,
}
return CustomAlertDialog(
title: Text(translate(title)),
content: Text(translate(text)),
title: null,
content: msgboxContent(type, title, text),
actions: [
dialogButton('Cancel', onPressed: () {
close();
@ -570,6 +571,25 @@ void showElevationError(String id, String type, String title, String text,
});
}
void showWaitAcceptDialog(String id, String type, String title, String text,
OverlayDialogManager dialogManager) {
dialogManager.dismissAll();
dialogManager.show((setState, close) {
onCancel() {
closeConnection();
}
return CustomAlertDialog(
title: null,
content: msgboxContent(type, title, text),
actions: [
dialogButton('Cancel', onPressed: onCancel, isOutline: true),
],
onCancel: onCancel,
);
});
}
Future<String?> validateAsync(String value) async {
value = value.trim();
if (value.isEmpty) {
@ -625,7 +645,6 @@ class _PasswordWidgetState extends State<PasswordWidget> {
icon: Icon(
// Based on passwordVisible state choose the icon
_passwordVisible ? Icons.visibility : Icons.visibility_off,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
// Update the state i.e. toggle the state of passwordVisible variable

View File

@ -310,7 +310,6 @@ class InputModel {
}
}
/*
int _signOrZero(num x) {
if (x == 0) {
return 0;
@ -361,7 +360,6 @@ class InputModel {
trackpadScrollDistance = Offset.zero;
}
*/
void onPointDownImage(PointerDownEvent e) {
debugPrint("onPointDownImage");
@ -408,6 +406,13 @@ class InputModel {
}
}
void refreshMousePos() => handleMouse({
'x': lastMousePos.dx,
'y': lastMousePos.dy,
'buttons': 0,
'type': _kMouseEventMove,
});
void handleMouse(Map<String, dynamic> evt) {
double x = evt['x'];
double y = max(0.0, evt['y']);

View File

@ -199,6 +199,9 @@ class FfiModel with ChangeNotifier {
final peer_id = evt['peer_id'].toString();
await bind.sessionSwitchSides(id: peer_id);
closeConnection(id: peer_id);
} else if (name == "on_url_scheme_received") {
final url = evt['url'].toString();
parseRustdeskUri(url);
}
};
}
@ -263,19 +266,18 @@ class FfiModel with ChangeNotifier {
final text = evt['text'];
final link = evt['link'];
if (type == 're-input-password') {
wrongPasswordDialog(id, dialogManager);
wrongPasswordDialog(id, dialogManager, type, title, text);
} else if (type == 'input-password') {
enterPasswordDialog(id, dialogManager);
} else if (type == 'restarting') {
showMsgBox(id, type, title, text, link, false, dialogManager,
hasCancel: false);
} else if (type == 'wait-remote-accept-nook') {
msgBoxCommon(dialogManager, title, Text(translate(text)),
[dialogButton("Cancel", onPressed: closeConnection)]);
showWaitAcceptDialog(id, type, title, text, dialogManager);
} else if (type == 'on-uac' || type == 'on-foreground-elevated') {
showOnBlockDialog(id, type, title, text, dialogManager);
} else if (type == 'wait-uac') {
showWaitUacDialog(id, dialogManager);
showWaitUacDialog(id, dialogManager, type);
} else if (type == 'elevation-error') {
showElevationError(id, type, title, text, dialogManager);
} else {
@ -540,6 +542,7 @@ class CanvasModel with ChangeNotifier {
double _y = 0;
// image scale
double _scale = 1.0;
double _devicePixelRatio = 1.0;
Size _size = Size.zero;
// the tabbar over the image
// double tabBarHeight = 0.0;
@ -563,6 +566,7 @@ class CanvasModel with ChangeNotifier {
double get x => _x;
double get y => _y;
double get scale => _scale;
double get devicePixelRatio => _devicePixelRatio;
Size get size => _size;
ScrollStyle get scrollStyle => _scrollStyle;
ViewStyle get viewStyle => _lastViewStyle;
@ -611,13 +615,15 @@ class CanvasModel with ChangeNotifier {
_lastViewStyle = viewStyle;
_scale = viewStyle.scale;
_devicePixelRatio = ui.window.devicePixelRatio;
if (kIgnoreDpi && style == kRemoteViewStyleOriginal) {
_scale = 1.0 / ui.window.devicePixelRatio;
_scale = 1.0 / _devicePixelRatio;
}
_x = (size.width - displayWidth * _scale) / 2;
_y = (size.height - displayHeight * _scale) / 2;
_imageOverflow.value = _x < 0 || y < 0;
notifyListeners();
parent.target?.inputModel.refreshMousePos();
}
updateScrollStyle() async {
@ -747,7 +753,7 @@ class CanvasModel with ChangeNotifier {
class CursorData {
final String peerId;
final int id;
final img2.Image? image;
final img2.Image image;
double scale;
Uint8List? data;
final double hotxOrigin;
@ -772,33 +778,40 @@ class CursorData {
int _doubleToInt(double v) => (v * 10e6).round().toInt();
double _checkUpdateScale(double scale, bool shouldScale) {
double _checkUpdateScale(double scale) {
double oldScale = this.scale;
if (!shouldScale) {
scale = 1.0;
} else {
if (scale != 1.0) {
// Update data if scale changed.
if (Platform.isWindows) {
final tgtWidth = (width * scale).toInt();
final tgtHeight = (width * scale).toInt();
if (tgtWidth < kMinCursorSize || tgtHeight < kMinCursorSize) {
double sw = kMinCursorSize.toDouble() / width;
double sh = kMinCursorSize.toDouble() / height;
scale = sw < sh ? sh : sw;
}
final tgtWidth = (width * scale).toInt();
final tgtHeight = (width * scale).toInt();
if (tgtWidth < kMinCursorSize || tgtHeight < kMinCursorSize) {
double sw = kMinCursorSize.toDouble() / width;
double sh = kMinCursorSize.toDouble() / height;
scale = sw < sh ? sh : sw;
}
}
if (Platform.isWindows) {
if (_doubleToInt(oldScale) != _doubleToInt(scale)) {
if (_doubleToInt(oldScale) != _doubleToInt(scale)) {
if (Platform.isWindows) {
data = img2
.copyResize(
image!,
image,
width: (width * scale).toInt(),
height: (height * scale).toInt(),
interpolation: img2.Interpolation.average,
)
.getBytes(format: img2.Format.bgra);
} else {
data = Uint8List.fromList(
img2.encodePng(
img2.copyResize(
image,
width: (width * scale).toInt(),
height: (height * scale).toInt(),
interpolation: img2.Interpolation.average,
),
),
);
}
}
@ -808,8 +821,8 @@ class CursorData {
return scale;
}
String updateGetKey(double scale, bool shouldScale) {
scale = _checkUpdateScale(scale, shouldScale);
String updateGetKey(double scale) {
scale = _checkUpdateScale(scale);
return '${peerId}_${id}_${_doubleToInt(width * scale)}_${_doubleToInt(height * scale)}';
}
}
@ -867,7 +880,7 @@ class PredefinedCursor {
_cache = CursorData(
peerId: '',
id: id,
image: _image2?.clone(),
image: _image2!.clone(),
scale: scale,
data: data,
hotxOrigin:
@ -894,9 +907,10 @@ class CursorModel with ChangeNotifier {
double _hoty = 0;
double _displayOriginX = 0;
double _displayOriginY = 0;
DateTime? _firstUpdateMouseTime;
bool gotMouseControl = true;
DateTime _lastPeerMouse = DateTime.now()
.subtract(Duration(milliseconds: 2 * kMouseControlTimeoutMSec));
.subtract(Duration(milliseconds: 3000 * kMouseControlTimeoutMSec));
String id = '';
WeakReference<FFI> parent;
@ -915,6 +929,15 @@ class CursorModel with ChangeNotifier {
DateTime.now().difference(_lastPeerMouse).inMilliseconds <
kMouseControlTimeoutMSec;
bool isConnIn2Secs() {
if (_firstUpdateMouseTime == null) {
_firstUpdateMouseTime = DateTime.now();
return true;
} else {
return DateTime.now().difference(_firstUpdateMouseTime!).inSeconds < 2;
}
}
CursorModel(this.parent);
Set<String> get cachedKeys => _cacheKeys;
@ -1067,9 +1090,9 @@ class CursorModel with ChangeNotifier {
Future<bool> _updateCache(
Uint8List rgba, ui.Image image, int id, int w, int h) async {
Uint8List? data;
img2.Image? imgOrigin;
img2.Image imgOrigin =
img2.Image.fromBytes(w, h, rgba, format: img2.Format.rgba);
if (Platform.isWindows) {
imgOrigin = img2.Image.fromBytes(w, h, rgba, format: img2.Format.rgba);
data = imgOrigin.getBytes(format: img2.Format.bgra);
} else {
ByteData? imgBytes =
@ -1111,8 +1134,10 @@ class CursorModel with ChangeNotifier {
/// Update the cursor position.
updateCursorPosition(Map<String, dynamic> evt, String id) async {
gotMouseControl = false;
_lastPeerMouse = DateTime.now();
if (!isConnIn2Secs()) {
gotMouseControl = false;
_lastPeerMouse = DateTime.now();
}
_x = double.parse(evt['x']);
_y = double.parse(evt['y']);
try {

View File

@ -8,6 +8,7 @@ import 'package:external_path/external_path.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:win32/win32.dart' as win32;
@ -46,6 +47,8 @@ class PlatformFFI {
static get localeName => Platform.localeName;
static get isMain => instance._appType == kAppTypeMain;
static Future<String> getVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo.version;
@ -112,8 +115,11 @@ class PlatformFFI {
}
_ffiBind = RustdeskImpl(dylib);
if (Platform.isLinux) {
// start dbus service, no need to await
await _ffiBind.mainStartDbusServer();
// Start a dbus service, no need to await
_ffiBind.mainStartDbusServer();
} else if (Platform.isMacOS) {
// Start an ipc server for handling url schemes.
_ffiBind.mainStartIpcUrlServer();
}
_startListenEvent(_ffiBind); // global event
try {

View File

@ -28,7 +28,7 @@ class ServerModel with ChangeNotifier {
bool _inputOk = false;
bool _audioOk = false;
bool _fileOk = false;
bool _showElevation = true;
bool _showElevation = false;
bool _hideCm = false;
int _connectStatus = 0; // Rendezvous Server status
String _verificationMethod = "";

View File

@ -80,13 +80,15 @@ class UserModel {
final tag = gFFI.dialogManager.showLoading(translate('Waiting'));
try {
final url = await bind.mainGetApiServer();
final authHeaders = getHttpHeaders();
authHeaders['Content-Type'] = "application/json";
await http
.post(Uri.parse('$url/api/logout'),
body: {
body: jsonEncode({
'id': await bind.mainGetMyId(),
'uuid': await bind.mainGetUuid(),
},
headers: getHttpHeaders())
}),
headers: authHeaders)
.timeout(Duration(seconds: 2));
} catch (e) {
print("request /api/logout failed: err=$e");

View File

@ -43,11 +43,14 @@ class RustDeskMultiWindowManager {
Future<dynamic> newRemoteDesktop(String remoteId,
{String? switch_uuid}) async {
final msg = jsonEncode({
var params = {
"type": WindowType.RemoteDesktop.index,
"id": remoteId,
"switch_uuid": switch_uuid ?? ""
});
};
if (switch_uuid != null) {
params['switch_uuid'] = switch_uuid;
}
final msg = jsonEncode(params);
try {
final ids = await DesktopMultiWindow.getAllSubWindowIds();
@ -157,6 +160,24 @@ class RustDeskMultiWindowManager {
return null;
}
void clearWindowType(WindowType type) {
switch (type) {
case WindowType.Main:
return;
case WindowType.RemoteDesktop:
_remoteDesktopWindowId = null;
break;
case WindowType.FileTransfer:
_fileTransferWindowId = null;
break;
case WindowType.PortForward:
_portForwardWindowId = null;
break;
case WindowType.Unknown:
break;
}
}
void setMethodHandler(
Future<dynamic> Function(MethodCall call, int fromWindowId)? handler) {
DesktopMultiWindow.setMethodHandler(handler);
@ -183,8 +204,11 @@ class RustDeskMultiWindowManager {
}
await WindowController.fromWindowId(wId).setPreventClose(false);
await WindowController.fromWindowId(wId).close();
} on Error {
} catch (e) {
debugPrint("$e");
return;
} finally {
clearWindowType(type);
}
}
}

View File

@ -0,0 +1,40 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/main.dart';
enum SystemWindowTheme { light, dark }
/// The platform channel for RustDesk.
class RdPlatformChannel {
RdPlatformChannel._();
static final RdPlatformChannel _windowUtil = RdPlatformChannel._();
static RdPlatformChannel get instance => _windowUtil;
final MethodChannel _osxMethodChannel =
MethodChannel("org.rustdesk.rustdesk/macos");
final MethodChannel _winMethodChannel =
MethodChannel("org.rustdesk.rustdesk/windows");
final MethodChannel _linuxMethodChannel =
MethodChannel("org.rustdesk.rustdesk/linux");
/// Change the theme of the system window
Future<void> changeSystemWindowTheme(SystemWindowTheme theme) {
assert(Platform.isMacOS);
if (kDebugMode) {
print(
"[Window ${kWindowId ?? 'Main'}] change system window theme to ${theme.name}");
}
return _osxMethodChannel
.invokeMethod("setWindowTheme", {"themeName": theme.name});
}
/// Terminate .app manually.
Future<void> terminate() {
assert(Platform.isMacOS);
return _osxMethodChannel.invokeMethod("terminate");
}
}

View File

@ -13,7 +13,8 @@ PODS:
- FMDB/standard (2.7.5)
- package_info_plus_macos (0.0.1):
- FlutterMacOS
- path_provider_macos (0.0.1):
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- screen_retriever (0.0.1):
- FlutterMacOS
@ -38,7 +39,7 @@ DEPENDENCIES:
- flutter_custom_cursor (from `Flutter/ephemeral/.symlinks/plugins/flutter_custom_cursor/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
- uni_links_desktop (from `Flutter/ephemeral/.symlinks/plugins/uni_links_desktop/macos`)
@ -64,8 +65,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral
package_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos
screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
sqflite:
@ -86,14 +87,14 @@ SPEC CHECKSUMS:
desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486
device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7
flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
@ -227,7 +227,7 @@
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
LastSwiftMigration = 1420;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
@ -279,6 +279,7 @@
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -429,7 +430,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@ -462,6 +463,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Profile;
@ -606,6 +608,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -642,6 +645,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
SWIFT_VERSION = 5.0;
};

View File

@ -3,21 +3,22 @@ import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
var lauched = false;
var launched = false;
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
dummy_method_to_enforce_bundling()
return true
// https://github.com/leanflutter/window_manager/issues/214
return false
}
override func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
if (lauched) {
if (launched) {
handle_applicationShouldOpenUntitledFile();
}
return true
}
override func applicationDidFinishLaunching(_ aNotification: Notification) {
lauched = true;
launched = true;
NSApplication.shared.activate(ignoringOtherApps: true);
}
}

View File

@ -23,8 +23,10 @@
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<key>CFBundleURLIconFile</key>
<string></string>
<key>CFBundleURLName</key>
<string>com.carriez.rustdesk</string>
<key>CFBundleURLSchemes</key>
<array>
<string>rustdesk</string>
@ -35,13 +37,13 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSUIElement</key>
<string>1</string>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -27,12 +27,16 @@ class MainFlutterWindow: NSWindow {
let windowFrame = self.frame
self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
// register self method handler
let registrar = flutterViewController.registrar(forPlugin: "RustDeskPlugin")
setMethodHandler(registrar: registrar)
RegisterGeneratedPlugins(registry: flutterViewController)
FlutterMultiWindowPlugin.setOnWindowCreatedCallback { controller in
// Register the plugin which you want access from other isolate.
// DesktopLifecyclePlugin.register(with: controller.registrar(forPlugin: "DesktopLifecyclePlugin"))
self.setMethodHandler(registrar: controller.registrar(forPlugin: "RustDeskPlugin"))
DesktopDropPlugin.register(with: controller.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: controller.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FlutterCustomCursorPlugin.register(with: controller.registrar(forPlugin: "FlutterCustomCursorPlugin"))
@ -53,4 +57,33 @@ class MainFlutterWindow: NSWindow {
super.order(place, relativeTo: otherWin)
hiddenWindowAtLaunch()
}
/// Override window theme.
public func setWindowInterfaceMode(window: NSWindow, themeName: String) {
window.appearance = NSAppearance(named: themeName == "light" ? .aqua : .darkAqua)
}
public func setMethodHandler(registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "org.rustdesk.rustdesk/macos", binaryMessenger: registrar.messenger)
channel.setMethodCallHandler({
(call, result) -> Void in
switch call.method {
case "setWindowTheme":
let arg = call.arguments as! [String: Any]
let themeName = arg["themeName"] as? String
guard let window = registrar.view?.window else {
result(nil)
return
}
self.setWindowInterfaceMode(window: window,themeName: themeName ?? "light")
result(nil)
break;
case "terminate":
NSApplication.shared.terminate(self)
result(nil)
default:
result(FlutterMethodNotImplemented)
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -59,9 +59,9 @@ dependencies:
desktop_multi_window:
git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
ref: 057e6eb1bc7dcbcf9dafd1384274a611e4fe7124
ref: bc8604a88e52b2b6e64d2661ae49a71450a47af8
freezed_annotation: ^2.0.3
flutter_custom_cursor: ^0.0.2
flutter_custom_cursor: ^0.0.4
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding.git

View File

@ -115,6 +115,26 @@ macro_rules! serde_field_string {
};
}
macro_rules! serde_field_bool {
($struct_name: ident, $field_name: literal, $func: ident) => {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct $struct_name {
#[serde(rename = $field_name)]
pub v: bool,
}
impl Default for $struct_name {
fn default() -> Self {
Self { v: Self::$func() }
}
}
impl $struct_name {
pub fn $func() -> bool {
UserDefaultConfig::load().get($field_name) == "Y"
}
}
};
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NetworkType {
Direct,
@ -192,26 +212,29 @@ pub struct PeerConfig {
deserialize_with = "PeerConfig::deserialize_image_quality"
)]
pub image_quality: String,
#[serde(default)]
#[serde(
default = "PeerConfig::default_custom_image_quality",
deserialize_with = "PeerConfig::deserialize_custom_image_quality"
)]
pub custom_image_quality: Vec<i32>,
#[serde(default)]
pub show_remote_cursor: bool,
#[serde(default)]
pub lock_after_session_end: bool,
#[serde(default)]
pub privacy_mode: bool,
#[serde(flatten)]
pub show_remote_cursor: ShowRemoteCursor,
#[serde(flatten)]
pub lock_after_session_end: LockAfterSessionEnd,
#[serde(flatten)]
pub privacy_mode: PrivacyMode,
#[serde(default)]
pub port_forwards: Vec<(i32, String, i32)>,
#[serde(default)]
pub direct_failures: i32,
#[serde(default)]
pub disable_audio: bool,
#[serde(default)]
pub disable_clipboard: bool,
#[serde(default)]
pub enable_file_transfer: bool,
#[serde(default)]
pub show_quality_monitor: bool,
#[serde(flatten)]
pub disable_audio: DisableAudio,
#[serde(flatten)]
pub disable_clipboard: DisableClipboard,
#[serde(flatten)]
pub enable_file_transfer: EnableFileTransfer,
#[serde(flatten)]
pub show_quality_monitor: ShowQualityMonitor,
#[serde(default)]
pub keyboard_mode: String,
@ -961,31 +984,88 @@ impl PeerConfig {
serde_field_string!(
default_view_style,
deserialize_view_style,
"original".to_owned()
UserDefaultConfig::load().get("view_style")
);
serde_field_string!(
default_scroll_style,
deserialize_scroll_style,
"scrollauto".to_owned()
UserDefaultConfig::load().get("scroll_style")
);
serde_field_string!(
default_image_quality,
deserialize_image_quality,
"balanced".to_owned()
UserDefaultConfig::load().get("image_quality")
);
fn default_custom_image_quality() -> Vec<i32> {
let f: f64 = UserDefaultConfig::load()
.get("custom_image_quality")
.parse()
.unwrap_or(50.0);
vec![f as _]
}
fn deserialize_custom_image_quality<'de, D>(deserializer: D) -> Result<Vec<i32>, D::Error>
where
D: de::Deserializer<'de>,
{
let v: Vec<i32> = de::Deserialize::deserialize(deserializer)?;
if v.len() == 1 && v[0] >= 10 && v[0] <= 100 {
Ok(v)
} else {
Ok(Self::default_custom_image_quality())
}
}
fn deserialize_options<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
where
D: de::Deserializer<'de>,
{
let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?;
if !mp.contains_key("codec-preference") {
mp.insert("codec-preference".to_owned(), "auto".to_owned());
let mut key = "codec-preference";
if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key));
}
key = "custom-fps";
if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key));
}
key = "zoom-cursor";
if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key));
}
Ok(mp)
}
}
serde_field_bool!(
ShowRemoteCursor,
"show_remote_cursor",
default_show_remote_cursor
);
serde_field_bool!(
ShowQualityMonitor,
"show_quality_monitor",
default_show_quality_monitor
);
serde_field_bool!(DisableAudio, "disable_audio", default_disable_audio);
serde_field_bool!(
EnableFileTransfer,
"enable_file_transfer",
default_enable_file_transfer
);
serde_field_bool!(
DisableClipboard,
"disable_clipboard",
default_disable_clipboard
);
serde_field_bool!(
LockAfterSessionEnd,
"lock_after_session_end",
default_lock_after_session_end
);
serde_field_bool!(PrivacyMode, "privacy_mode", default_privacy_mode);
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct LocalConfig {
#[serde(default)]
@ -1192,6 +1272,73 @@ impl HwCodecConfig {
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct UserDefaultConfig {
#[serde(default)]
options: HashMap<String, String>,
}
impl UserDefaultConfig {
pub fn load() -> UserDefaultConfig {
Config::load_::<UserDefaultConfig>("_default")
}
#[inline]
fn store(&self) {
Config::store_(self, "_default");
}
pub fn get(&self, key: &str) -> String {
match key {
"view_style" => self.get_string(key, "original", vec!["adaptive"]),
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]),
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]),
"codec-preference" => self.get_string(key, "auto", vec!["vp9", "h264", "h265"]),
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
"custom-fps" => self.get_double_string(key, 30.0, 10.0, 120.0),
_ => self
.options
.get(key)
.map(|v| v.to_string())
.unwrap_or_default(),
}
}
pub fn set(&mut self, key: String, value: String) {
self.options.insert(key, value);
self.store();
}
#[inline]
fn get_string(&self, key: &str, default: &str, others: Vec<&str>) -> String {
match self.options.get(key) {
Some(option) => {
if others.contains(&option.as_str()) {
option.to_owned()
} else {
default.to_owned()
}
}
None => default.to_owned(),
}
}
#[inline]
fn get_double_string(&self, key: &str, default: f64, min: f64, max: f64) -> String {
match self.options.get(key) {
Some(option) => {
let v: f64 = option.parse().unwrap_or(default);
if v >= min && v <= max {
v.to_string()
} else {
default.to_string()
}
}
None => default.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -197,9 +197,6 @@ pub fn get_version_from_url(url: &str) -> String {
}
pub fn gen_version() {
if Ok("release".to_owned()) != std::env::var("PROFILE") {
return;
}
println!("cargo:rerun-if-changed=Cargo.toml");
use std::io::prelude::*;
let mut file = File::create("./src/version.rs").unwrap();

View File

@ -76,7 +76,7 @@ pub fn is_cursor_embedded() -> bool {
if is_x11() {
x11::IS_CURSOR_EMBEDDED
} else {
wayland::IS_CURSOR_EMBEDDED
wayland::is_cursor_embedded()
}
}

View File

@ -4,12 +4,33 @@ use std::{io, sync::RwLock, time::Duration};
pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>);
pub const IS_CURSOR_EMBEDDED: bool = true;
static mut IS_CURSOR_EMBEDDED: Option<bool> = None;
lazy_static::lazy_static! {
static ref MAP_ERR: RwLock<Option<fn(err: String)-> io::Error>> = Default::default();
}
pub fn is_cursor_embedded() -> bool {
unsafe {
if IS_CURSOR_EMBEDDED.is_none() {
init_cursor_embedded();
}
IS_CURSOR_EMBEDDED.unwrap_or(false)
}
}
unsafe fn init_cursor_embedded() {
use crate::common::wayland::pipewire::get_available_cursor_modes;
match get_available_cursor_modes() {
Ok(modes) => {
IS_CURSOR_EMBEDDED = Some((modes & 0x02) > 0);
}
Err(..) => {
IS_CURSOR_EMBEDDED = Some(false);
}
}
}
pub fn set_map_err(f: fn(err: String) -> io::Error) {
*MAP_ERR.write().unwrap() = Some(f);
}
@ -74,7 +95,7 @@ impl Display {
}
pub fn all() -> io::Result<Vec<Display>> {
Ok(pipewire::get_capturables(true)
Ok(pipewire::get_capturables(is_cursor_embedded())
.map_err(map_err)?
.drain(..)
.map(|x| Display(x))

View File

@ -58,6 +58,7 @@ impl Capturer {
let mut device = ptr::null_mut();
let mut context = ptr::null_mut();
let mut duplication = ptr::null_mut();
#[allow(invalid_value)]
let mut desc = unsafe { mem::MaybeUninit::uninit().assume_init() };
let mut gdi_capturer = None;
@ -176,6 +177,7 @@ impl Capturer {
unsafe fn load_frame(&mut self, timeout: UINT) -> io::Result<(*const u8, i32)> {
let mut frame = ptr::null_mut();
#[allow(invalid_value)]
let mut info = mem::MaybeUninit::uninit().assume_init();
wrap_hresult((*self.duplication.0).AcquireNextFrame(timeout, &mut info, &mut frame))?;
@ -185,6 +187,7 @@ impl Capturer {
return Err(std::io::ErrorKind::WouldBlock.into());
}
#[allow(invalid_value)]
let mut rect = mem::MaybeUninit::uninit().assume_init();
if self.fastlane {
wrap_hresult((*self.duplication.0).MapDesktopSurface(&mut rect))?;
@ -204,6 +207,7 @@ impl Capturer {
);
let texture = ComPtr(texture);
#[allow(invalid_value)]
let mut texture_desc = mem::MaybeUninit::uninit().assume_init();
(*texture.0).GetDesc(&mut texture_desc);
@ -362,6 +366,7 @@ impl Displays {
let mut all = Vec::new();
let mut i: DWORD = 0;
loop {
#[allow(invalid_value)]
let mut d: DISPLAY_DEVICEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
d.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as _;
let ok = unsafe { EnumDisplayDevicesW(std::ptr::null(), i, &mut d as _, 0) };
@ -382,6 +387,7 @@ impl Displays {
gdi: true,
};
disp.desc.DeviceName = d.DeviceName;
#[allow(invalid_value)]
let mut m: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
m.dmSize = std::mem::size_of::<DEVMODEW>() as _;
m.dmDriverExtra = 0;
@ -441,6 +447,7 @@ impl Displays {
// We get the display's details.
let desc = unsafe {
#[allow(invalid_value)]
let mut desc = mem::MaybeUninit::uninit().assume_init();
(*output.0).GetDesc(&mut desc);
desc

View File

@ -386,21 +386,22 @@ fn streams_from_response(response: OrgFreedesktopPortalRequestResponse) -> Vec<P
info.size.1 = v[1] as _;
}
}
let v = attributes
.get("position")?
.as_iter()?
.filter_map(|v| {
Some(
v.as_iter()?
.map(|x| x.as_i64().unwrap_or(0))
.collect::<Vec<i64>>(),
)
})
.next();
if let Some(v) = v {
if v.len() == 2 {
info.position.0 = v[0] as _;
info.position.1 = v[1] as _;
if let Some(pos) = attributes.get("position") {
let v = pos
.as_iter()?
.filter_map(|v| {
Some(
v.as_iter()?
.map(|x| x.as_i64().unwrap_or(0))
.collect::<Vec<i64>>(),
)
})
.next();
if let Some(v) = v {
if v.len() == 2 {
info.position.0 = v[0] as _;
info.position.1 = v[1] as _;
}
}
}
Some(info)
@ -415,6 +416,12 @@ static mut INIT: bool = false;
const RESTORE_TOKEN: &str = "restore_token";
const RESTORE_TOKEN_CONF_KEY: &str = "wayland-restore-token";
pub fn get_available_cursor_modes() -> Result<u32, dbus::Error> {
let conn = SyncConnection::new_session()?;
let portal = get_portal(&conn);
portal.available_cursor_modes()
}
// mostly inspired by https://gitlab.gnome.org/snippets/19
fn request_screen_cast(
capture_cursor: bool,
@ -473,7 +480,17 @@ fn request_screen_cast(
args.insert("multiple".into(), Variant(Box::new(true)));
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
let cursor_mode = if capture_cursor { 2u32 } else { 1u32 };
let mut cursor_mode = 0u32;
let mut available_cursor_modes = 0u32;
if let Ok(modes) = portal.available_cursor_modes() {
available_cursor_modes = modes;
}
if capture_cursor {
cursor_mode = 2u32 & available_cursor_modes;
}
if cursor_mode == 0 {
cursor_mode = 1u32 & available_cursor_modes;
}
let plasma = std::env::var("DESKTOP_SESSION").map_or(false, |s| s.contains("plasma"));
if plasma && capture_cursor {
// Warn the user if capturing the cursor is tried on kde as this can crash
@ -483,7 +500,9 @@ fn request_screen_cast(
desktop, see https://bugs.kde.org/show_bug.cgi?id=435042 for details! \
You have been warned.");
}
args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode)));
if cursor_mode > 0 {
args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode)));
}
let session: dbus::Path = r
.results
.get("session_handle")

View File

@ -956,7 +956,7 @@ impl LoginConfigHandler {
/// Check if the client should auto login.
/// Return password if the client should auto login, otherwise return empty string.
pub fn should_auto_login(&self) -> String {
let l = self.lock_after_session_end;
let l = self.lock_after_session_end.v;
let a = !self.get_option("auto-login").is_empty();
let p = self.get_option("os-password");
if !p.is_empty() && l && a {
@ -1063,32 +1063,32 @@ impl LoginConfigHandler {
let mut option = OptionMessage::default();
let mut config = self.load_config();
if name == "show-remote-cursor" {
config.show_remote_cursor = !config.show_remote_cursor;
option.show_remote_cursor = (if config.show_remote_cursor {
config.show_remote_cursor.v = !config.show_remote_cursor.v;
option.show_remote_cursor = (if config.show_remote_cursor.v {
BoolOption::Yes
} else {
BoolOption::No
})
.into();
} else if name == "disable-audio" {
config.disable_audio = !config.disable_audio;
option.disable_audio = (if config.disable_audio {
config.disable_audio.v = !config.disable_audio.v;
option.disable_audio = (if config.disable_audio.v {
BoolOption::Yes
} else {
BoolOption::No
})
.into();
} else if name == "disable-clipboard" {
config.disable_clipboard = !config.disable_clipboard;
option.disable_clipboard = (if config.disable_clipboard {
config.disable_clipboard.v = !config.disable_clipboard.v;
option.disable_clipboard = (if config.disable_clipboard.v {
BoolOption::Yes
} else {
BoolOption::No
})
.into();
} else if name == "lock-after-session-end" {
config.lock_after_session_end = !config.lock_after_session_end;
option.lock_after_session_end = (if config.lock_after_session_end {
config.lock_after_session_end.v = !config.lock_after_session_end.v;
option.lock_after_session_end = (if config.lock_after_session_end.v {
BoolOption::Yes
} else {
BoolOption::No
@ -1096,15 +1096,15 @@ impl LoginConfigHandler {
.into();
} else if name == "privacy-mode" {
// try toggle privacy mode
option.privacy_mode = (if config.privacy_mode {
option.privacy_mode = (if config.privacy_mode.v {
BoolOption::No
} else {
BoolOption::Yes
})
.into();
} else if name == "enable-file-transfer" {
config.enable_file_transfer = !config.enable_file_transfer;
option.enable_file_transfer = (if config.enable_file_transfer {
config.enable_file_transfer.v = !config.enable_file_transfer.v;
option.enable_file_transfer = (if config.enable_file_transfer.v {
BoolOption::Yes
} else {
BoolOption::No
@ -1115,10 +1115,14 @@ impl LoginConfigHandler {
} else if name == "unblock-input" {
option.block_input = BoolOption::No.into();
} else if name == "show-quality-monitor" {
config.show_quality_monitor = !config.show_quality_monitor;
config.show_quality_monitor.v = !config.show_quality_monitor.v;
} else {
let v = self.options.get(&name).is_some();
if v {
let is_set = self
.options
.get(&name)
.map(|o| !o.is_empty())
.unwrap_or(false);
if is_set {
self.config.options.remove(&name);
} else {
self.config.options.insert(name, "Y".to_owned());
@ -1252,19 +1256,19 @@ impl LoginConfigHandler {
/// * `name` - The name of the toggle option.
pub fn get_toggle_option(&self, name: &str) -> bool {
if name == "show-remote-cursor" {
self.config.show_remote_cursor
self.config.show_remote_cursor.v
} else if name == "lock-after-session-end" {
self.config.lock_after_session_end
self.config.lock_after_session_end.v
} else if name == "privacy-mode" {
self.config.privacy_mode
self.config.privacy_mode.v
} else if name == "enable-file-transfer" {
self.config.enable_file_transfer
self.config.enable_file_transfer.v
} else if name == "disable-audio" {
self.config.disable_audio
self.config.disable_audio.v
} else if name == "disable-clipboard" {
self.config.disable_clipboard
self.config.disable_clipboard.v
} else if name == "show-quality-monitor" {
self.config.show_quality_monitor
self.config.show_quality_monitor.v
} else {
!self.get_option(name).is_empty()
}

View File

@ -277,7 +277,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
if !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|| lc.read().unwrap().disable_clipboard
|| lc.read().unwrap().disable_clipboard.v
{
continue;
}
@ -778,7 +778,7 @@ impl<T: InvokeUiSession> Remote<T> {
|| self.handler.is_port_forward()
|| !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|| self.handler.lc.read().unwrap().disable_clipboard)
|| self.handler.lc.read().unwrap().disable_clipboard.v)
{
let txt = self.old_clipboard.lock().unwrap().clone();
if !txt.is_empty() {
@ -808,7 +808,7 @@ impl<T: InvokeUiSession> Remote<T> {
self.handler.set_cursor_position(cp);
}
Some(message::Union::Clipboard(cb)) => {
if !self.handler.lc.read().unwrap().disable_clipboard {
if !self.handler.lc.read().unwrap().disable_clipboard.v {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
update_clipboard(cb, Some(&self.old_clipboard));
#[cfg(any(target_os = "android", target_os = "ios"))]
@ -1104,7 +1104,7 @@ impl<T: InvokeUiSession> Remote<T> {
Some(misc::Union::PortableServiceRunning(b)) => {
if b {
self.handler.msgbox(
"custom-nocancel",
"custom-nocancel-success",
"Successful",
"Elevate successfully",
"",
@ -1121,7 +1121,7 @@ impl<T: InvokeUiSession> Remote<T> {
self.handler.handle_test_delay(t, peer).await;
}
Some(message::Union::AudioFrame(frame)) => {
if !self.handler.lc.read().unwrap().disable_audio {
if !self.handler.lc.read().unwrap().disable_audio.v {
self.audio_sender.send(MediaData::AudioFrame(frame)).ok();
}
}
@ -1204,7 +1204,7 @@ impl<T: InvokeUiSession> Remote<T> {
#[inline(always)]
fn update_privacy_mode(&mut self, on: bool) {
let mut config = self.handler.load_config();
config.privacy_mode = on;
config.privacy_mode.v = on;
self.handler.save_config(config);
self.handler.update_privacy_mode();
@ -1278,14 +1278,14 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(windows)]
{
let enabled = SERVER_FILE_TRANSFER_ENABLED.load(Ordering::SeqCst)
&& self.handler.lc.read().unwrap().enable_file_transfer;
&& self.handler.lc.read().unwrap().enable_file_transfer.v;
ContextSend::enable(enabled);
}
}
#[cfg(windows)]
fn handle_cliprdr_msg(&self, clip: hbb_common::message_proto::Cliprdr) {
if !self.handler.lc.read().unwrap().disable_clipboard {
if !self.handler.lc.read().unwrap().disable_clipboard.v {
#[cfg(feature = "flutter")]
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
if self.client_conn_id

View File

@ -52,7 +52,7 @@ pub fn global_init() -> bool {
#[cfg(target_os = "linux")]
{
if !*IS_X11 {
crate::server::wayland::set_wayland_scrap_map_err();
crate::server::wayland::init();
}
}
true
@ -451,6 +451,7 @@ pub fn run_me<T: AsRef<std::ffi::OsStr>>(args: Vec<T>) -> std::io::Result<std::p
}
}
#[inline]
pub fn username() -> String {
// fix bug of whoami
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -459,6 +460,14 @@ pub fn username() -> String {
return DEVICE_NAME.lock().unwrap().clone();
}
#[inline]
pub fn hostname() -> String {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return whoami::hostname();
#[cfg(any(target_os = "android", target_os = "ios"))]
return DEVICE_NAME.lock().unwrap().clone();
}
#[inline]
pub fn check_port<T: std::string::ToString>(host: T, port: i32) -> String {
hbb_common::socket_client::check_port(host, port)
@ -581,9 +590,9 @@ pub fn get_api_server(api: String, custom: String) -> String {
if !s0.is_empty() {
let s = crate::increase_port(&s0, -2);
if s == s0 {
format!("http://{}:{}", s, config::RENDEZVOUS_PORT - 2);
return format!("http://{}:{}", s, config::RENDEZVOUS_PORT - 2);
} else {
format!("http://{}", s);
return format!("http://{}", s);
}
}
"https://admin.rustdesk.com".to_owned()

View File

@ -1,4 +1,6 @@
use hbb_common::log;
use std::future::Future;
use hbb_common::{log, ResultType};
/// shared by flutter and sciter main function
///
@ -54,11 +56,6 @@ pub fn core_main() -> Option<Vec<String>> {
return core_main_invoke_new_connection(std::env::args());
}
let click_setup = cfg!(windows) && args.is_empty() && crate::common::is_setup(&arg_exe);
#[cfg(not(feature = "flutter"))]
{
_is_quick_support =
cfg!(windows) && args.is_empty() && arg_exe.to_lowercase().ends_with("qs.exe");
}
if click_setup {
args.push("--install".to_owned());
flutter_args.push("--install".to_string());
@ -70,6 +67,14 @@ pub fn core_main() -> Option<Vec<String>> {
println!("{}", crate::VERSION);
return None;
}
#[cfg(windows)]
{
_is_quick_support |= !crate::platform::is_installed()
&& args.is_empty()
&& (arg_exe.to_lowercase().ends_with("qs.exe")
|| (!click_setup && crate::platform::is_elevated(None).unwrap_or(false)));
crate::portable_service::client::set_quick_support(_is_quick_support);
}
#[cfg(debug_assertions)]
{
use hbb_common::env_logger::*;
@ -195,7 +200,7 @@ pub fn core_main() -> Option<Vec<String>> {
{
std::thread::spawn(move || crate::start_server(true));
crate::platform::macos::hide_dock();
crate::tray::make_tray();
crate::ui::macos::make_tray();
return None;
}
#[cfg(target_os = "linux")]
@ -244,8 +249,6 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(feature = "flutter")]
crate::flutter::connection_manager::start_listen_ipc_thread();
crate::ui_interface::start_option_status_sync();
#[cfg(target_os = "macos")]
crate::platform::macos::hide_dock();
}
}
//_async_logger_holder.map(|x| x.flush());
@ -291,8 +294,7 @@ fn import_config(path: &str) {
fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
args.position(|element| {
return element == "--connect";
})
.unwrap();
})?;
let peer_id = args.next().unwrap_or("".to_string());
if peer_id.is_empty() {
eprintln!("please provide a valid peer id");
@ -304,9 +306,13 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
switch_uuid = args.next();
}
}
let mut param_array = vec![];
if switch_uuid.is_some() {
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
param_array.push(switch_uuid);
}
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
let params = vec![switch_uuid].join("&");
let params = param_array.join("&");
let params_flag = if params.is_empty() { "" } else { "?" };
#[allow(unused)]
let uni_links = format!(
@ -342,5 +348,11 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
return if res { None } else { Some(Vec::new()) };
}
#[cfg(target_os = "macos")]
return Some(Vec::new());
{
return if let Err(_) = crate::ipc::send_url_scheme(uni_links) {
Some(Vec::new())
} else {
None
}
}
}

View File

@ -1,30 +1,27 @@
use std::{
collections::HashMap,
ffi::{CStr, CString},
os::raw::c_char,
};
use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char, thread};
use std::str::FromStr;
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
use serde_json::json;
use crate::common::is_keyboard_mode_supported;
use hbb_common::message_proto::KeyboardMode;
use hbb_common::ResultType;
use hbb_common::{
config::{self, LocalConfig, PeerConfig, ONLINE},
config::{self, LocalConfig, ONLINE, PeerConfig},
fs, log,
};
use std::str::FromStr;
use hbb_common::message_proto::KeyboardMode;
use hbb_common::ResultType;
// use crate::hbbs_http::account::AuthResult;
use crate::flutter::{self, SESSIONS};
use crate::ui_interface::{self, *};
use crate::{
client::file_trait::FileManager,
common::make_fd_to_json,
flutter::{session_add, session_start_},
};
use crate::common::is_keyboard_mode_supported;
use crate::flutter::{self, SESSIONS};
use crate::ui_interface::{self, *};
// use crate::hbbs_http::account::AuthResult;
fn initialize(app_dir: &str) {
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
#[cfg(target_os = "android")]
@ -523,6 +520,10 @@ pub fn main_get_sound_inputs() -> Vec<String> {
vec![String::from("")]
}
pub fn main_get_hostname() -> SyncReturn<String> {
SyncReturn(crate::common::hostname())
}
pub fn main_change_id(new_id: String) {
change_id(new_id)
}
@ -787,6 +788,14 @@ pub fn main_default_video_save_directory() -> String {
default_video_save_directory()
}
pub fn main_set_user_default_option(key: String, value: String) {
set_user_default_option(key, value);
}
pub fn main_get_user_default_option(key: String) -> SyncReturn<String> {
SyncReturn(get_user_default_option(key))
}
pub fn session_add_port_forward(
id: String,
local_port: i32,
@ -1237,16 +1246,38 @@ pub fn main_is_login_wayland() -> SyncReturn<bool> {
SyncReturn(is_login_wayland())
}
pub fn main_hide_docker() -> SyncReturn<bool> {
#[cfg(target_os = "macos")]
crate::platform::macos::hide_dock();
SyncReturn(true)
}
/// Start an ipc server for receiving the url scheme.
///
/// * Should only be called in the main flutter window.
/// * macOS only
pub fn main_start_ipc_url_server() {
#[cfg(target_os = "macos")]
thread::spawn(move || crate::server::start_ipc_url_server());
}
/// Send a url scheme throught the ipc.
///
/// * macOS only
pub fn send_url_scheme(url: String) {
#[cfg(target_os = "macos")]
thread::spawn(move || crate::ui::macos::handle_url_scheme(url));
}
#[cfg(target_os = "android")]
pub mod server_side {
use hbb_common::log;
use jni::{
JNIEnv,
objects::{JClass, JString},
sys::jstring,
JNIEnv,
};
use hbb_common::log;
use crate::start_server;
#[no_mangle]

View File

@ -16,10 +16,10 @@ use hbb_common::{
config::{self, Config, Config2},
futures::StreamExt as _,
futures_util::sink::SinkExt,
log, password_security as password, timeout, tokio,
log, password_security as password, ResultType, timeout,
tokio,
tokio::io::{AsyncRead, AsyncWrite},
tokio_util::codec::Framed,
ResultType,
};
use crate::rendezvous_mediator::RendezvousMediator;
@ -210,6 +210,7 @@ pub enum Data {
DataPortableService(DataPortableService),
SwitchSidesRequest(String),
SwitchSidesBack,
UrlLink(String)
}
#[tokio::main(flavor = "current_thread")]
@ -832,3 +833,9 @@ pub async fn test_rendezvous_server() -> ResultType<()> {
c.send(&Data::TestRendezvousServer).await?;
Ok(())
}
#[tokio::main(flavor = "current_thread")]
pub async fn send_url_scheme(url: String) -> ResultType<()> {
connect(1_000, "_url").await?.send(&Data::UrlLink(url)).await?;
Ok(())
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Silenciar"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Entrada d'àudio"),
("Enhancements", "Millores"),
("Hardware Codec", "Còdec de hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Sortir"),
("Tags", ""),
("Search ID", "Cerca ID"),
("Current Wayland display server is not supported", "El servidor de visualització actual de Wayland no és compatible"),
("whitelist_sep", ""),
("Add ID", "Afegir ID"),
("Add Tag", "Afegir tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "静音"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "音频输入"),
("Enhancements", "增强功能"),
("Hardware Codec", "硬件编解码"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "登出"),
("Tags", "标签"),
("Search ID", "查找ID"),
("Current Wayland display server is not supported", "不支持 Wayland 显示服务器"),
("whitelist_sep", "可以使用逗号,分号,空格或者换行符作为分隔符"),
("Add ID", "增加ID"),
("Add Tag", "增加标签"),
@ -278,12 +280,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Do you accept?", "是否接受?"),
("Open System Setting", "打开系统设置"),
("How to get Android input permission?", "如何获取安卓的输入权限?"),
("android_input_permission_tip1", "为了让远程设备通过鼠标或触屏控制您的安卓设备,你需要允許RustDesk使用\"无障碍\"服务。"),
("android_input_permission_tip1", "为了让远程设备通过鼠标或触屏控制您的安卓设备,你需要允許 RustDesk 使用\"无障碍\"服务。"),
("android_input_permission_tip2", "请在接下来的系统设置页面里,找到并进入 [已安装的服务] 页面,将 [RustDesk Input] 服务开启。"),
("android_new_connection_tip", "收到新的连接控制请求,对方想要控制你当前的设备。"),
("android_service_will_start_tip", "开启录屏权限将自动开启服务,允许其他设备向此设备请求建立连接。"),
("android_stop_service_tip", "关闭服务将自动关闭所有已建立的连接。"),
("android_version_audio_tip", "当前安卓版本不支持音频录制,请升级至安卓10或更高。"),
("android_version_audio_tip", "当前安卓版本不支持音频录制,请升级至安卓 10 或更高。"),
("android_start_service_tip", "点击 [启动服务] 或打开 [屏幕录制] 权限开启手机屏幕共享服务。"),
("Account", "账户"),
("Overwrite", "覆盖"),
@ -373,7 +375,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "拒绝局域网发现"),
("Write a message", "输入聊天消息"),
("Prompt", "提示"),
("Please wait for confirmation of UAC...", "请等待对方确认UAC..."),
("Please wait for confirmation of UAC...", "请等待对方确认 UAC ..."),
("elevated_foreground_window_tip", "远端桌面的当前窗口需要更高的权限才能操作, 暂时无法使用鼠标键盘, 可以请求对方最小化当前窗口, 或者在连接管理窗口点击提升。为避免这个问题,建议在远端设备上安装本软件。"),
("Disconnected", "会话已结束"),
("Other", "其他"),
@ -401,16 +403,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Request access to your device", "请求访问你的设备"),
("Hide connection management window", "隐藏连接管理窗口"),
("hide_cm_tip", "在只允许密码连接并且只用固定密码的情况下才允许隐藏"),
("wayland_experiment_tip", "Wayland支持处于实验阶段如果你需要使用无人值守访问请使用X11。"),
("wayland_experiment_tip", "Wayland 支持处于实验阶段如果你需要使用无人值守访问请使用X11。"),
("Right click to select tabs", "右键选择选项卡"),
("Skipped", "已跳过"),
("Add to Address Book", "添加到地址簿"),
("Group", "小组"),
("Search", "搜索"),
("Closed manually by web console", "web控制台手动关闭"),
("Closed manually by web console", " web 控制台手动关闭"),
("Local keyboard type", "本地键盘类型"),
("Select local keyboard type", "请选择本地键盘类型"),
("software_render_tip", "如果你使用英伟达显卡, 并且远程窗口在会话建立后会立刻关闭, 那么安装nouveau驱动并且选择使用软件渲染可能会有帮助。重启软件后生效。"),
("software_render_tip", "如果你使用英伟达显卡, 并且远程窗口在会话建立后会立刻关闭, 那么安装 nouveau 驱动并且选择使用软件渲染可能会有帮助。重启软件后生效。"),
("Always use software rendering", "使用软件渲染"),
("config_input", "为了能够通过键盘控制远程桌面, 请给予 RustDesk \"输入监控\" 权限。"),
("request_elevation_tip", "如果对面有人, 也可以请求提升权限。"),
@ -419,9 +421,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ask the remote user for authentication", "请求远端用户授权"),
("Choose this if the remote account is administrator", "当对面电脑是管理员账号时选择该选项"),
("Transmit the username and password of administrator", "发送管理员账号的用户名密码"),
("still_click_uac_tip", "依然需要被控端用戶在運行RustDesk的UAC窗口點擊確認。"),
("still_click_uac_tip", "依然需要被控端用戶在運行 RustDesk UAC 窗口點擊確認。"),
("Request Elevation", "请求提权"),
("wait_accept_uac_tip", "请等待远端用户确认UAC对话框。"),
("wait_accept_uac_tip", "请等待远端用户确认 UAC 对话框。"),
("Elevate successfully", "提权成功"),
("uppercase", "大写字母"),
("lowercase", "小写字母"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", "反转访问方向"),
("Please confirm if you want to share your desktop?", "请确认要让对方访问你的桌面?"),
("Closed as expected", "正常关闭"),
("Display", "显示"),
("Default View Style", "默认显示方式"),
("Default Scroll Style", "默认滚动方式"),
("Default Image Quality", "默认图像质量"),
("Default Codec", "默认编解码"),
("Bitrate", "波特率"),
("FPS", "帧率"),
("Auto", "自动"),
("Other Default Options", "其它默认选项"),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Ztlumit"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Vstup zvuku"),
("Enhancements", ""),
("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Odhlásit se"),
("Tags", "Štítky"),
("Search ID", "Hledat identifikátor"),
("Current Wayland display server is not supported", "Zobrazovací server Wayland zatím není podporován"),
("whitelist_sep", "Odělováno čárkou, středníkem, mezerou nebo koncem řádku"),
("Add ID", "Přidat identifikátor"),
("Add Tag", "Přidat štítek"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Sluk for mikrofonen"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Lydindgang"),
("Enhancements", ""),
("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "logger af"),
("Tags", "Nøgleord"),
("Search ID", "Søg ID"),
("Current Wayland display server is not supported", "Den aktuelle Wayland-Anzege-server understøttes ikke"),
("whitelist_sep", "Adskilt af komma, semikolon, rum eller linjepaus"),
("Add ID", "Tilføj ID"),
("Add Tag", "Tilføj nøgleord"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", "Mit Herzblut programmiert - in einer Welt, die im Chaos versinkt!"),
("Privacy Statement", "Datenschutz"),
("Mute", "Stummschalten"),
("Build Date", "Erstelldatum"),
("Version", "Version"),
("Home", "Startseite"),
("Audio Input", "Audioeingang"),
("Enhancements", "Verbesserungen"),
("Hardware Codec", "Hardware-Codec"),
@ -197,7 +200,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Warning", "Warnung"),
("Login screen using Wayland is not supported", "Anmeldebildschirm mit Wayland wird nicht unterstützt."),
("Reboot required", "Neustart erforderlich"),
("Unsupported display server ", "Nicht unterstützter Display-Server"),
("Unsupported display server ", "Nicht unterstützter Anzeigeserver"),
("x11 expected", "X11 erwartet"),
("Port", "Port"),
("Settings", "Einstellungen"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Abmelden"),
("Tags", "Schlagworte"),
("Search ID", "Suche ID"),
("Current Wayland display server is not supported", "Der aktuelle Wayland-Anzeigeserver wird nicht unterstützt."),
("whitelist_sep", "Getrennt durch Komma, Semikolon, Leerzeichen oder Zeilenumbruch"),
("Add ID", "ID hinzufügen"),
("Add Tag", "Stichwort hinzufügen"),
@ -241,7 +243,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Remote ID", "Entfernte ID"),
("Paste", "Einfügen"),
("Paste here?", "Hier einfügen?"),
("Are you sure to close the connection?", "Möchten Sie diese Verbindung wirklich trennen?"),
("Are you sure to close the connection?", "Möchten Sie diese Verbindung wirklich schließen?"),
("Download new version", "Neue Version herunterladen"),
("Touch mode", "Touch-Modus"),
("Mouse mode", "Mausmodus"),
@ -264,8 +266,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Note", "Hinweis"),
("Connection", "Verbindung"),
("Share Screen", "Bildschirm freigeben"),
("CLOSE", "DEAKTIV."),
("OPEN", "AKTIVIER."),
("CLOSE", "SCHLIEẞEN"),
("OPEN", "ÖFFNEN"),
("Chat", "Chat"),
("Total", "Gesamt"),
("items", "Einträge"),
@ -325,7 +327,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Mobile Actions", "Mobile Aktionen"),
("Select Monitor", "Bildschirm auswählen"),
("Control Actions", "Aktionen"),
("Display Settings", "Bildschirmeinstellungen"),
("Display Settings", "Anzeigeeinstellungen"),
("Ratio", "Verhältnis"),
("Image Quality", "Bildqualität"),
("Scroll Style", "Scroll-Stil"),
@ -336,7 +338,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Secure Connection", "Sichere Verbindung"),
("Insecure Connection", "Unsichere Verbindung"),
("Scale original", "Keine Skalierung"),
("Scale adaptive", "Automatische Skalierung"),
("Scale adaptive", "Anpassbare Skalierung"),
("General", "Allgemein"),
("Security", "Sicherheit"),
("Theme", "Farbgebung"),
@ -356,7 +358,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clear", "Zurücksetzen"),
("Audio Input Device", "Audioeingabegerät"),
("Deny remote access", "Fernzugriff verbieten"),
("Use IP Whitelisting", "IP-Whitelist benutzen"),
("Use IP Whitelisting", "IP-Whitelist verwenden"),
("Network", "Netzwerk"),
("Enable RDP", "RDP aktivieren"),
("Pin menubar", "Menüleiste anpinnen"),
@ -384,7 +386,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland erfordert Ubuntu 21.04 oder eine höhere Version."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland erfordert eine höhere Version der Linux-Distribution. Bitte versuchen Sie den X11-Desktop oder ändern Sie Ihr Betriebssystem."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den Bildschirm aus, der freigegeben werden soll (auf der Peer-Seite arbeiten)."),
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den freizugebenden Bildschirm aus (Bedienung auf der Peer-Seite)."),
("Show RustDesk", "RustDesk anzeigen"),
("This PC", "Dieser PC"),
("or", "oder"),
@ -407,7 +409,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Zum Adressbuch hinzufügen"),
("Group", "Gruppe"),
("Search", "Suchen"),
("Closed manually by web console", "Manuell über die Webkonsole beendet"),
("Closed manually by web console", "Manuell über die Webkonsole geschlossen"),
("Local keyboard type", "Lokaler Tastaturtyp"),
("Select local keyboard type", "Lokalen Tastaturtyp auswählen"),
("software_render_tip", "Wenn Sie eine Nvidia-Grafikkarte haben und sich das entfernte Fenster sofort nach dem Herstellen der Verbindung schließt, kann es helfen, den Nouveau-Treiber zu installieren und Software-Rendering zu verwenden. Ein Neustart der Software ist erforderlich."),
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Stark"),
("Switch Sides", "Seiten wechseln"),
("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, ob Sie Ihren Desktop freigeben möchten."),
("Closed as expected", ""),
("Closed as expected", "Wie erwartet geschlossen"),
("Display", "Anzeige"),
("Default View Style", "Standard-Ansichtsstil"),
("Default Scroll Style", "Standard-Scroll-Stil"),
("Default Image Quality", "Standard-Bildqualität"),
("Default Codec", "Standard-Codec"),
("Bitrate", "Bitrate"),
("FPS", "fps"),
("Auto", "Automatisch"),
("Other Default Options", "Weitere Standardoptionen"),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Muta"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Aŭdia enigo"),
("Enhancements", ""),
("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Malkonekti"),
("Tags", "Etikedi"),
("Search ID", "Serĉi ID"),
("Current Wayland display server is not supported", "La aktuala bilda servilo Wayland ne estas subtenita"),
("whitelist_sep", "Vi povas uzi komon, punktokomon, spacon aŭ linsalton kiel apartigilo"),
("Add ID", "Aldoni identigilo"),
("Add Tag", "Aldoni etikedo"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", "Hecho con corazón en este mundo caótico!"),
("Privacy Statement", "Declaración de privacidad"),
("Mute", "Silenciar"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Entrada de audio"),
("Enhancements", "Mejoras"),
("Hardware Codec", "Códec de hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Salir"),
("Tags", "Tags"),
("Search ID", "Buscar ID"),
("Current Wayland display server is not supported", "El servidor de visualización actual de Wayland no es compatible"),
("whitelist_sep", "Separados por coma, punto y coma, espacio o nueva línea"),
("Add ID", "Agregar ID"),
("Add Tag", "Agregar tag"),
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Fuerte"),
("Switch Sides", "Intercambiar lados"),
("Please confirm if you want to share your desktop?", "Por favor, confirma si quieres compartir tu escritorio"),
("Closed as expected", ""),
("Closed as expected", "Cerrado como se esperaba"),
("Display", "Pantalla"),
("Default View Style", "Estilo de vista predeterminado"),
("Default Scroll Style", "Estilo de desplazamiento predeterminado"),
("Default Image Quality", "Calidad de imagen predeterminada"),
("Default Codec", "Códec predeterminado"),
("Bitrate", "Tasa de bits"),
("FPS", ""),
("Auto", ""),
("Other Default Options", "Otras opciones predeterminadas"),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "بستن صدا"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "ورودی صدا"),
("Enhancements", "بهبودها"),
("Hardware Codec", "کدک سخت افزاری"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "خروج"),
("Tags", "برچسب ها"),
("Search ID", "جستجوی شناسه"),
("Current Wayland display server is not supported", "پشتیبانی نمی شود Wayland سرور نمایش فعلی"),
("whitelist_sep", "با کاما، نقطه ویرگول، فاصله یا خط جدید از هم جدا می شوند"),
("Add ID", "افزودن شناسه"),
("Add Tag", "افزودن برچسب"),
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "قوی"),
("Switch Sides", "طرفین را عوض کنید"),
("Please confirm if you want to share your desktop?", "لطفاً تأیید کنید که آیا می خواهید دسکتاپ خود را به اشتراک بگذارید؟"),
("Closed as expected", ""),
("Closed as expected", "طبق انتظار بسته شد"),
("Display", "نمایش دادن"),
("Default View Style", "سبک نمایش پیش فرض"),
("Default Scroll Style", "سبک پیش‌فرض اسکرول"),
("Default Image Quality", "کیفیت تصویر پیش فرض"),
("Default Codec", "کدک پیش فرض"),
("Bitrate", "میزان بیت صفحه نمایش"),
("FPS", "FPS"),
("Auto", "خودکار"),
("Other Default Options", "سایر گزینه های پیش فرض"),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", "Fait avec cœur dans ce monde chaotique!"),
("Privacy Statement", "Déclaration de confidentialité"),
("Mute", "Muet"),
("Build Date", "Date de compilation"),
("Version", "Version"),
("Home", "Accueil"),
("Audio Input", "Entrée audio"),
("Enhancements", "Améliorations"),
("Hardware Codec", "Transcodage matériel"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Déconnexion"),
("Tags", "Étiqueter"),
("Search ID", "Rechercher un ID"),
("Current Wayland display server is not supported", "Le serveur d'affichage Wayland n'est pas pris en charge"),
("whitelist_sep", "Vous pouvez utiliser une virgule, un point-virgule, un espace ou une nouvelle ligne comme séparateur"),
("Add ID", "Ajouter un ID"),
("Add Tag", "Ajouter une balise"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", "Inverser la prise de contrôle"),
("Please confirm if you want to share your desktop?", "Veuillez confirmer le partager de votre bureau ?"),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Σίγαση"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Είσοδος ήχου"),
("Enhancements", "Βελτιώσεις"),
("Hardware Codec", "Κωδικοποιητής υλικού"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Αποσύνδεση"),
("Tags", "Ετικέτες"),
("Search ID", "Αναζήτηση ID"),
("Current Wayland display server is not supported", "Ο τρέχων διακομιστής εμφάνισης Wayland δεν υποστηρίζεται"),
("whitelist_sep", "Διαχωρίζονται με κόμμα, ερωτηματικό, διάστημα ή νέα γραμμή"),
("Add ID", "Προσθήκη αναγνωριστικού ID"),
("Add Tag", "Προσθήκη ετικέτας"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Némítás"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Hangátvitel"),
("Enhancements", "Fejlesztések"),
("Hardware Codec", "Hardware kodek"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Kilépés"),
("Tags", "Tagok"),
("Search ID", "Azonosító keresése..."),
("Current Wayland display server is not supported", "A Wayland display szerver nem támogatott"),
("whitelist_sep", "A címeket veszővel, pontosvesszővel, szóközzel, vagy új sorral válassza el"),
("Add ID", "Azonosító hozzáadása"),
("Add Tag", "Címke hozzáadása"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", "Pernyataan Privasi"),
("Mute", "Bisukan"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Masukkan Audio"),
("Enhancements", "Peningkatan"),
("Hardware Codec", "Codec Perangkat Keras"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Keluar"),
("Tags", "Tag"),
("Search ID", "Cari ID"),
("Current Wayland display server is not supported", "Server tampilan Wayland saat ini tidak didukung"),
("whitelist_sep", "Dipisahkan dengan koma, titik koma, spasi, atau baris baru"),
("Add ID", "Tambah ID"),
("Add Tag", "Tambah Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", "Fatta con il cuore in questo mondo caotico!"),
("Privacy Statement", "Informativa sulla privacy"),
("Mute", "Silenzia"),
("Build Date", "Data della build"),
("Version", "Versione"),
("Home", "Home"),
("Audio Input", "Input audio"),
("Enhancements", "Miglioramenti"),
("Hardware Codec", "Codifica Hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Esci"),
("Tags", "Tag"),
("Search ID", "Cerca ID"),
("Current Wayland display server is not supported", "Questo display server Wayland non è supportato"),
("whitelist_sep", "Separati da virgola, punto e virgola, spazio o a capo"),
("Add ID", "Aggiungi ID"),
("Add Tag", "Aggiungi tag"),
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Forte"),
("Switch Sides", "Cambia lato"),
("Please confirm if you want to share your desktop?", "Vuoi condividere il tuo desktop?"),
("Closed as expected", ""),
("Closed as expected", "Chiuso come previsto"),
("Display", "Visualizzazione"),
("Default View Style", "Stile Visualizzazione Predefinito"),
("Default Scroll Style", "Stile Scorrimento Predefinito"),
("Default Image Quality", "Qualità Immagine Predefinita"),
("Default Codec", "Codec Predefinito"),
("Bitrate", "Bitrate"),
("FPS", "FPS"),
("Auto", "Auto"),
("Other Default Options", "Altre Opzioni Predefinite"),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "ミュート"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "音声入力デバイス"),
("Enhancements", "追加機能"),
("Hardware Codec", "ハードウェア コーデック"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "ログアウト"),
("Tags", "タグ"),
("Search ID", "IDを検索"),
("Current Wayland display server is not supported", "現在のWaylandディスプレイサーバーはサポートされていません"),
("whitelist_sep", "カンマやセミコロン、空白、改行で区切ってください"),
("Add ID", "IDを追加"),
("Add Tag", "タグを追加"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "음소거"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "오디오 입력"),
("Enhancements", ""),
("Hardware Codec", "하드웨어 코덱"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "로그아웃"),
("Tags", "태그"),
("Search ID", "ID 검색"),
("Current Wayland display server is not supported", "현재 Wayland 디스플레이 서버가 지원되지 않습니다"),
("whitelist_sep", "다음 글자로 구분합니다. ',(콤마) ;(세미콜론) 띄어쓰기 혹은 줄바꿈'"),
("Add ID", "ID 추가"),
("Add Tag", "태그 추가"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Дыбыссыздандыру"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Аудио Еңгізу"),
("Enhancements", "Жақсартулар"),
("Hardware Codec", "Hardware Codec"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Шығу"),
("Tags", "Тақтар"),
("Search ID", "ID Іздеу"),
("Current Wayland display server is not supported", "Ағымдағы Wayland дисплей серберіне қолдау көрсетілмейді"),
("whitelist_sep", "Үтір, нүктелі үтір, бос орын және жаңа жолал арқылы бөлінеді"),
("Add ID", "ID Қосу"),
("Add Tag", "Тақ Қосу"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Status"),
("Your Desktop", "Twój pulpit"),
("desk_tip", "W celu zestawienia połączenia z tym urządzeniem należy poniższego ID i hasła."),
("desk_tip", "W celu połączenia się z tym urządzeniem należy użyć poniższego ID i hasła"),
("Password", "Hasło"),
("Ready", "Gotowe"),
("Established", "Nawiązano"),
@ -38,10 +38,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop service", "Zatrzymaj usługę"),
("Change ID", "Zmień ID"),
("Website", "Strona internetowa"),
("About", "O"),
("Slogan_tip", ""),
("Privacy Statement", ""),
("About", "O aplikacji"),
("Slogan_tip", "Tworzone z miłością w tym pełnym chaosu świecie!"),
("Privacy Statement", "Oświadczenie o ochronie prywatności"),
("Mute", "Wycisz"),
("Build Date", "Zbudowano"),
("Version", "Wersja"),
("Home", "Pulpit"),
("Audio Input", "Wejście audio"),
("Enhancements", "Ulepszenia"),
("Hardware Codec", "Kodek sprzętowy"),
@ -96,7 +99,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Directory", "Pusty katalog"),
("Not an empty directory", "Katalog nie jest pusty"),
("Are you sure you want to delete this file?", "Czy na pewno chcesz usunąć ten plik?"),
("Are you sure you want to delete this empty directory?", "Czy na pewno chcesz usunać ten pusty katalog?"),
("Are you sure you want to delete this empty directory?", "Czy na pewno chcesz usunąć ten pusty katalog?"),
("Are you sure you want to delete the file of this directory?", "Czy na pewno chcesz usunąć pliki z tego katalogu?"),
("Do this for all conflicts", "wykonaj dla wszystkich konfliktów"),
("This is irreversible!", "To jest nieodwracalne!"),
@ -118,7 +121,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Good image quality", "Dobra jakość obrazu"),
("Balanced", "Zrównoważony"),
("Optimize reaction time", "Zoptymalizuj czas reakcji"),
("Custom", "Własne"),
("Custom", "Niestandardowe"),
("Show remote cursor", "Pokazuj zdalny kursor"),
("Show quality monitor", "Parametry połączenia"),
("Disable clipboard", "Wyłącz schowek"),
@ -138,10 +141,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Failed to make direct connection to remote desktop", "Nie udało się nawiązać bezpośredniego połączenia z pulpitem zdalnym"),
("Set Password", "Ustaw hasło"),
("OS Password", "Hasło systemu operacyjnego"),
("install_tip", "RustDesk może nie działać poprawnie na maszynie zdalnej z przyczyn związanych z UAC. W celu uniknięcią problemów z UAC, kliknij poniższy przycisk by zainstalować RustDesk w swoim systemie."),
("install_tip", "RustDesk może nie działać poprawnie na maszynie zdalnej z przyczyn związanych z UAC. W celu uniknięcia problemów z UAC, kliknij poniższy przycisk by zainstalować RustDesk w swoim systemie."),
("Click to upgrade", "Zaktualizuj"),
("Click to download", "Pobierz"),
("Click to update", "Uaktualinij"),
("Click to update", "Uaktualnij"),
("Configure", "Konfiguruj"),
("config_acc", "Konfiguracja konta"),
("config_screen", "Konfiguracja ekranu"),
@ -208,17 +211,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Run without install", "Uruchom bez instalacji"),
("Always connected via relay", "Zawsze połączony pośrednio"),
("Always connect via relay", "Zawsze łącz pośrednio"),
("whitelist_tip", "Zezwlaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
("whitelist_tip", "Zezwalaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
("Login", "Zaloguj"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Verify", "Zweryfikuj"),
("Remember me", "Zapamiętaj mnie"),
("Trust this device", "Dodaj to urządzenie do zaufanych"),
("Verification code", "Kod weryfikacyjny"),
("verification_tip", "Nastąpiło logowanie z nowego urządzenia, kod weryfikacyjny został wysłany na podany adres email, wprowadź kod by kontynuować proces logowania"),
("Logout", "Wyloguj"),
("Tags", "Tagi"),
("Search ID", "Szukaj ID"),
("Current Wayland display server is not supported", "Obecny serwer wyświetlania Wayland nie jest obsługiwany"),
("whitelist_sep", "Oddzielone przecinkiem, średnikiem, spacją lub w nowej linii"),
("Add ID", "Dodaj ID"),
("Add Tag", "Dodaj Tag"),
@ -232,7 +234,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Favorites", "Ulubione"),
("Add to Favorites", "Dodaj do ulubionych"),
("Remove from Favorites", "Usuń z ulubionych"),
("Empty", "Pusty"),
("Empty", "Pusto"),
("Invalid folder name", "Nieprawidłowa nazwa folderu"),
("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Nazwa hosta"),
@ -331,7 +333,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Styl przewijania"),
("Show Menubar", "Pokaż pasek menu"),
("Hide Menubar", "Ukryj pasek menu"),
("Direct Connection", "Połącznie bezpośrednie"),
("Direct Connection", "Połączenie bezpośrednie"),
("Relay Connection", "Połączenie przez bramkę"),
("Secure Connection", "Połączenie szyfrowane"),
("Insecure Connection", "Połączenie nieszyfrowane"),
@ -344,12 +346,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Dark", "Ciemny"),
("Light", "Jasny"),
("Follow System", "Zgodne z systemem"),
("Enable hardware codec", "Włącz wsparcie sprzętowe dla kodeków"),
("Unlock Security Settings", "Odblokuj Ustawienia Zabezpieczeń"),
("Enable Audio", "Włącz Dźwięk"),
("Enable hardware codec", "Włącz akcelerację sprzętową kodeków"),
("Unlock Security Settings", "Odblokuj ustawienia zabezpieczeń"),
("Enable Audio", "Włącz dźwięk"),
("Unlock Network Settings", "Odblokuj ustawienia Sieciowe"),
("Server", "Serwer"),
("Direct IP Access", "Bezpośredni Adres IP"),
("Direct IP Access", "Bezpośredni adres IP"),
("Proxy", "Proxy"),
("Apply", "Zastosuj"),
("Disconnect all devices?", "Czy rozłączyć wszystkie urządzenia?"),
@ -361,20 +363,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", "Włącz RDP"),
("Pin menubar", "Przypnij pasek menu"),
("Unpin menubar", "Odepnij pasek menu"),
("Recording", "Trwa nagrywanie"),
("Recording", "Nagrywanie"),
("Directory", "Katalog"),
("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"),
("Change", "Zmień"),
("Start session recording", "Zacznij nagrywać sesję"),
("Stop session recording", "Zatrzymaj nagrywanie sesji"),
("Enable Recording Session", "Włącz Nagrywanie Sesji"),
("Enable Recording Session", "Włącz nagrywanie Sesji"),
("Allow recording session", "Zezwól na nagrywanie sesji"),
("Enable LAN Discovery", "Włącz wykrywanie urządzenia w sieci LAN"),
("Deny LAN Discovery", "Zablokuj wykrywanie urządzenia w sieci LAN"),
("Write a message", "Napisz wiadomość"),
("Prompt", "Monit"),
("Please wait for confirmation of UAC...", "Oczekuje potwierdzenia ustawień UAC"),
("elevated_foreground_window_tip", ""),
("Please wait for confirmation of UAC...", "Poczekaj na potwierdzenie uprawnień UAC"),
("elevated_foreground_window_tip", "Aktualne okno zdalnego urządzenia wymaga wyższych uprawnień by poprawnie działać, chwilowo niemożliwym jest korzystanie z myszy i klawiatury. Możesz poprosić zdalnego użytkownika o minimalizację okna, lub nacisnąć przycisk podniesienia uprawnień w oknie zarządzania połączeniami. By uniknąć tego problemu, rekomendujemy instalację oprogramowania na urządzeniu zdalnym."),
("Disconnected", "Rozłączone"),
("Other", "Inne"),
("Confirm before closing multiple tabs", "Potwierdź przed zamknięciem wielu kart"),
@ -382,7 +384,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Full Access", "Pełny dostęp"),
("Screen Share", "Udostępnianie ekranu"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland wymaga Ubuntu 21.04 lub nowszego."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland wymaga wszej wersji dystrybucji Linuksa. Wypróbuj pulpit X11 lub zmień system operacyjny."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland wymaga nowszej dystrybucji Linuksa. Wypróbuj pulpit X11 lub zmień system operacyjny."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po stronie równorzędnej)."),
("Show RustDesk", "Pokaż RustDesk"),
@ -400,39 +402,48 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("One-time password length", "Długość hasła jednorazowego"),
("Request access to your device", "Żądanie dostępu do Twojego urządzenia"),
("Hide connection management window", "Ukryj okno zarządzania połączeniem"),
("hide_cm_tip", ""),
("wayland_experiment_tip", ""),
("Right click to select tabs", ""),
("Skipped", ""),
("hide_cm_tip", "Pozwalaj na ukrycie tylko gdy akceptujesz sesje za pośrednictwem hasła i używasz hasła permanentnego"),
("wayland_experiment_tip", "Wsparcie dla Wayland jest niekompletne, użyj X11 jeżeli chcesz korzystać z dostępu nienadzorowanego"),
("Right click to select tabs", "Kliknij prawym przyciskiem myszy by wybrać zakładkę"),
("Skipped", "Pominięte"),
("Add to Address Book", "Dodaj do Książki Adresowej"),
("Group", "Grypy"),
("Search", "Szukaj"),
("Closed manually by web console", ""),
("Local keyboard type", ""),
("Select local keyboard type", ""),
("software_render_tip", ""),
("Always use software rendering", ""),
("config_input", ""),
("request_elevation_tip", ""),
("Wait", ""),
("Elevation Error", ""),
("Ask the remote user for authentication", ""),
("Choose this if the remote account is administrator", ""),
("Transmit the username and password of administrator", ""),
("still_click_uac_tip", ""),
("Request Elevation", ""),
("wait_accept_uac_tip", ""),
("Elevate successfully", ""),
("uppercase", ""),
("lowercase", ""),
("digit", ""),
("special character", ""),
("length>=8", ""),
("Weak", ""),
("Medium", ""),
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Closed manually by web console", "Zakończone manualnie z konsoli Web"),
("Local keyboard type", "Lokalny typ klawiatury"),
("Select local keyboard type", "Wybierz lokalny typ klawiatury"),
("software_render_tip", "Jeżeli posiadasz kartę graficzną Nvidia i okno zamyka się natychmiast po nawiązaniu połączenia, instalacja sterownika nouveau i wybór renderowania programowego mogą pomóc. Restart aplikacji jest wymagany."),
("Always use software rendering", "Zawsze używaj renderowania programowego"),
("config_input", "By kontrolować zdalne urządzenie przy pomocy klawiatury, musisz udzielić aplikacji RustDesk uprawnień do \"Urządzeń Wejściowych\"."),
("request_elevation_tip", "Możesz poprosić o podniesienie uprawnień jeżeli ktoś posiada dostęp do zdalnego urządzenia."),
("Wait", "Czekaj"),
("Elevation Error", "Błąd przy podnoszeniu uprawnień"),
("Ask the remote user for authentication", "Poproś użytkownika zdalnego o uwierzytelnienie"),
("Choose this if the remote account is administrator", "Wybierz to jeżeli zdalne konto jest administratorem"),
("Transmit the username and password of administrator", "Prześlij nazwę użytkownika i hasło administratora"),
("still_click_uac_tip", "Nadal wymaga od zdalnego użytkownika potwierdzenia uprawnień UAC."),
("Request Elevation", "Poproś o podniesienie uprawnień"),
("wait_accept_uac_tip", "Prosimy czekać aż zdalny użytkownik potwierdzi uprawnienia UAC."),
("Elevate successfully", "Pomyślnie podniesiono uprawnienia"),
("uppercase", "wielkie litery"),
("lowercase", "małe litery"),
("digit", "cyfra"),
("special character", "znak specjalny"),
("length>=8", "długość>=8"),
("Weak", "Słabe"),
("Medium", "Średnie"),
("Strong", "Mocne"),
("Switch Sides", "Zmień Strony"),
("Please confirm if you want to share your desktop?", "Czy na pewno chcesz udostępnić swój ekran?"),
("Closed as expected", "Zamknięto pomyślnie"),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Silenciar"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Entrada de Áudio"),
("Enhancements", "Melhorias"),
("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Sair"),
("Tags", "Tags"),
("Search ID", "Procurar ID"),
("Current Wayland display server is not supported", "Servidor de display Wayland atual não é suportado"),
("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
("Add ID", "Adicionar ID"),
("Add Tag", "Adicionar Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Desativar som"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Entrada de Áudio"),
("Enhancements", "Melhorias"),
("Hardware Codec", "Codec de hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Sair"),
("Tags", "Tags"),
("Search ID", "Pesquisar ID"),
("Current Wayland display server is not supported", "Servidor de display Wayland atual não é suportado"),
("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
("Add ID", "Adicionar ID"),
("Add Tag", "Adicionar Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Fără sunet"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Intrare audio"),
("Enhancements", "Îmbunătățiri"),
("Hardware Codec", "Codec hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Deconectare"),
("Tags", "Etichetare"),
("Search ID", "Caută după ID"),
("Current Wayland display server is not supported", "Serverul de afișaj Wayland nu este acceptat"),
("whitelist_sep", "Poți folosi ca separator virgula, punctul și virgula, spațiul sau linia nouă"),
("Add ID", "Adaugă ID"),
("Add Tag", "Adaugă etichetă"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", "Сделано с душой в этом безумном мире!"),
("Privacy Statement", "Заявление о конфиденциальности"),
("Mute", "Отключить звук"),
("Build Date", "Дата сборки"),
("Version", "Версия"),
("Home", "Главная"),
("Audio Input", "Аудиовход"),
("Enhancements", "Улучшения"),
("Hardware Codec", "Аппаратный кодек"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Выйти"),
("Tags", "Метки"),
("Search ID", "Поиск по ID"),
("Current Wayland display server is not supported", "Текущий сервер отображения Wayland не поддерживается"),
("whitelist_sep", "Раздельно запятой, точкой с запятой, пробелом или новой строкой"),
("Add ID", "Добавить ID"),
("Add Tag", "Добавить ключевое слово"),
@ -432,7 +434,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Medium", "Средний"),
("Strong", "Стойкий"),
("Switch Sides", "Переключить стороны"),
("Please confirm if you want to share your desktop?", "Подтвердите, что хотите поделиться своим рабочим столом?"),
("Closed as expected", ""),
("Please confirm if you want to share your desktop?", "Подтверждаете, что хотите поделиться своим рабочим столом?"),
("Closed as expected", "Закрыто по ожиданию"),
("Display", "Отображение"),
("Default View Style", "Стиль отображения по умолчанию"),
("Default Scroll Style", "Стиль прокрутки по умолчанию"),
("Default Image Quality", "Качество изображения по умолчанию"),
("Default Codec", "Кодек по умолчанию"),
("Bitrate", "Битрейт"),
("FPS", "FPS"),
("Auto", "Авто"),
("Other Default Options", "Другие параметры по умолчанию"),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Stíšiť"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Zvukový vstup"),
("Enhancements", ""),
("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Odhlásenie"),
("Tags", "Štítky"),
("Search ID", "Hľadať ID"),
("Current Wayland display server is not supported", "Zobrazovací (display) server Wayland nie je podporovaný"),
("whitelist_sep", "Oddelené čiarkou, bodkočiarkou, medzerou alebo koncom riadku"),
("Add ID", "Pridať ID"),
("Add Tag", "Pridať štítok"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Izklopi zvok"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Avdio vhod"),
("Enhancements", "Izboljšave"),
("Hardware Codec", "Strojni kodek"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Odjavi"),
("Tags", "Oznake"),
("Search ID", "Išči ID"),
("Current Wayland display server is not supported", "Trenutni Wayland zaslonski strežnik ni podprt"),
("whitelist_sep", "Naslovi ločeni z vejico, podpičjem, presledkom ali novo vrstico"),
("Add ID", "Dodaj ID"),
("Add Tag", "Dodaj oznako"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Pa zë"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Inputi zërit"),
("Enhancements", "Përmirësimet"),
("Hardware Codec", "Kodeku Harduerik"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Dalje"),
("Tags", "Tage"),
("Search ID", "Kerko ID"),
("Current Wayland display server is not supported", "Serveri aktual i ekranit Wayland nuk mbështetet"),
("whitelist_sep", "Të ndara me presje, pikëpresje, hapësira ose rresht të ri"),
("Add ID", "Shto ID"),
("Add Tag", "Shto Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Utišaj"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Audio ulaz"),
("Enhancements", "Proširenja"),
("Hardware Codec", "Hardverski kodek"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Odjava"),
("Tags", "Oznake"),
("Search ID", "Traži ID"),
("Current Wayland display server is not supported", "Tekući Wazland server za prikaz nije podržan"),
("whitelist_sep", "Odvojeno zarezima, tačka zarezima, praznim mestima ili novim redovima"),
("Add ID", "Dodaj ID"),
("Add Tag", "Dodaj oznaku"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Tyst"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Ljud input"),
("Enhancements", "Förbättringar"),
("Hardware Codec", "Hårdvarucodec"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Logga ut"),
("Tags", "Taggar"),
("Search ID", "Sök ID"),
("Current Wayland display server is not supported", "Nuvarande Wayland displayserver stöds inte"),
("whitelist_sep", "Separerat av ett comma, semikolon, mellanslag eller ny linje"),
("Add ID", "Lägg till ID"),
("Add Tag", "Lägg till Tagg"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", ""),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", ""),
("Enhancements", ""),
("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", ""),
("Tags", ""),
("Search ID", ""),
("Current Wayland display server is not supported", ""),
("whitelist_sep", ""),
("Add ID", ""),
("Add Tag", ""),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"),
("Privacy Statement", "คำแถลงเกี่ยวกับความเป็นส่วนตัว"),
("Mute", "ปิดเสียง"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "ออดิโออินพุท"),
("Enhancements", "การปรับปรุง"),
("Hardware Codec", "ฮาร์ดแวร์ codec"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "ออกจากระบบ"),
("Tags", "แท็ก"),
("Search ID", "ค้นหา ID"),
("Current Wayland display server is not supported", "เซิร์ฟเวอร์การแสดงผล Wayland ปัจจุบันไม่รองรับ"),
("whitelist_sep", "คั่นโดยเครื่องหมาย comma semicolon เว้นวรรค หรือ ขึ้นบรรทัดใหม่"),
("Add ID", "เพิ่ม ID"),
("Add Tag", "เพิ่มแท็ก"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Sustur"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Ses Girişi"),
("Enhancements", "Geliştirmeler"),
("Hardware Codec", "Donanımsal Codec"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Çıkış yap"),
("Tags", "Etiketler"),
("Search ID", "ID Arama"),
("Current Wayland display server is not supported", "Mevcut Wayland görüntüleme sunucusu desteklenmiyor"),
("whitelist_sep", "Virgül, noktalı virgül, boşluk veya yeni satır ile ayrılmış"),
("Add ID", "ID Ekle"),
("Add Tag", "Etiket Ekle"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "靜音"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "音訊輸入"),
("Enhancements", "增強功能"),
("Hardware Codec", "硬件編解碼"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "登出"),
("Tags", "標籤"),
("Search ID", "搜尋 ID"),
("Current Wayland display server is not supported", "目前不支援 Wayland 顯示伺服器"),
("whitelist_sep", "使用逗號、分號、空白,或是換行來分隔"),
("Add ID", "新增 ID"),
("Add Tag", "新增標籤"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", "正常關閉"),
("Display", "顯示"),
("Default View Style", "默認顯示方式"),
("Default Scroll Style", "默認滾動方式"),
("Default Image Quality", "默認圖像質量"),
("Default Codec", "默認編解碼"),
("Bitrate", "波特率"),
("FPS", "幀率"),
("Auto", "自動"),
("Other Default Options", "其它默認選項"),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", "Створено з душею в цьому хаотичному світі!"),
("Privacy Statement", "Декларація про конфіденційність"),
("Mute", "Вимкнути звук"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Аудіовхід"),
("Enhancements", "Покращення"),
("Hardware Codec", "Апаратний кодек"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Вийти"),
("Tags", "Ключові слова"),
("Search ID", "Пошук за ID"),
("Current Wayland display server is not supported", "Поточний графічний сервер Wayland не підтримується"),
("whitelist_sep", "Розділені комою, крапкою з комою, пробілом або новим рядком"),
("Add ID", "Додати ID"),
("Add Tag", "Додати ключове слово"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Tắt tiếng"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Đầu vào âm thanh"),
("Enhancements", "Các tiện itchs"),
("Hardware Codec", "Codec phần cứng"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Đăng xuất"),
("Tags", "Tags"),
("Search ID", "Tìm ID"),
("Current Wayland display server is not supported", "Máy chủ hình ảnh Wayland hiện không đuợc hỗ trợ"),
("whitelist_sep", "Đuợc cách nhau bởi dấu phẩy, dấu chấm phẩy, dấu cách hay dòng mới"),
("Add ID", "Thêm ID"),
("Add Tag", "Thêm Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect();
}

View File

@ -426,104 +426,11 @@ pub fn is_login_wayland() -> bool {
}
}
pub fn fix_login_wayland() {
let mut file = "/etc/gdm3/custom.conf".to_owned();
if !std::path::Path::new(&file).exists() {
file = "/etc/gdm/custom.conf".to_owned();
}
match std::process::Command::new("pkexec")
.args(vec![
"sed",
"-i",
"s/#WaylandEnable=false/WaylandEnable=false/g",
&file,
])
.output()
{
Ok(x) => {
let x = String::from_utf8_lossy(&x.stderr);
if !x.is_empty() {
log::error!("fix_login_wayland failed: {}", x);
}
}
Err(err) => {
log::error!("fix_login_wayland failed: {}", err);
}
}
}
pub fn current_is_wayland() -> bool {
let dtype = get_display_server();
return "wayland" == dtype && unsafe { UNMODIFIED };
}
pub fn modify_default_login() -> String {
let dsession = std::env::var("DESKTOP_SESSION").unwrap();
let user_name = std::env::var("USERNAME").unwrap();
if let Ok(x) = run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION}-xorg.desktop".to_owned()) {
if x.trim_end().to_string() != "" {
match std::process::Command::new("pkexec")
.args(vec![
"sed",
"-i",
&format!("s/={0}$/={0}-xorg/g", &dsession),
&format!("/var/lib/AccountsService/users/{}", &user_name),
])
.output()
{
Ok(x) => {
let x = String::from_utf8_lossy(&x.stderr);
if !x.is_empty() {
log::error!("modify_default_login failed: {}", x);
return "Fix failed! Please re-login with X server manually".to_owned();
} else {
unsafe {
UNMODIFIED = false;
}
return "".to_owned();
}
}
Err(err) => {
log::error!("modify_default_login failed: {}", err);
return "Fix failed! Please re-login with X server manually".to_owned();
}
}
} else if let Ok(z) =
run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION:0:-8}.desktop".to_owned())
{
if z.trim_end().to_string() != "" {
match std::process::Command::new("pkexec")
.args(vec![
"sed",
"-i",
&format!("s/={}$/={}/g", &dsession, &dsession[..dsession.len() - 8]),
&format!("/var/lib/AccountsService/users/{}", &user_name),
])
.output()
{
Ok(x) => {
let x = String::from_utf8_lossy(&x.stderr);
if !x.is_empty() {
log::error!("modify_default_login failed: {}", x);
return "Fix failed! Please re-login with X server manually".to_owned();
} else {
unsafe {
UNMODIFIED = false;
}
return "".to_owned();
}
}
Err(err) => {
log::error!("modify_default_login failed: {}", err);
return "Fix failed! Please re-login with X server manually".to_owned();
}
}
}
}
}
return "Fix failed! Please re-login with X server manually".to_owned();
}
// to-do: test the other display manager
fn _get_display_manager() -> String {
if let Ok(x) = std::fs::read_to_string("/etc/X11/default-display-manager") {

View File

@ -49,6 +49,7 @@ use winreg::RegKey;
pub fn get_cursor_pos() -> Option<(i32, i32)> {
unsafe {
#[allow(invalid_value)]
let mut out = mem::MaybeUninit::uninit().assume_init();
if GetCursorPos(&mut out) == FALSE {
return None;
@ -61,6 +62,7 @@ pub fn reset_input_cache() {}
pub fn get_cursor() -> ResultType<Option<u64>> {
unsafe {
#[allow(invalid_value)]
let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init();
ci.cbSize = std::mem::size_of::<CURSORINFO>() as _;
if crate::portable_service::client::get_cursor_info(&mut ci) == FALSE {
@ -79,6 +81,7 @@ struct IconInfo(ICONINFO);
impl IconInfo {
fn new(icon: HICON) -> ResultType<Self> {
unsafe {
#[allow(invalid_value)]
let mut ii = mem::MaybeUninit::uninit().assume_init();
if GetIconInfo(icon, &mut ii) == FALSE {
Err(io::Error::last_os_error().into())
@ -1742,3 +1745,13 @@ pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) ->
}
return Ok(());
}
pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> {
std::process::Command::new("icacls")
.arg(dir.as_os_str())
.arg("/grant")
.arg(format!("Everyone:(OI)(CI){}", permission))
.arg("/T")
.spawn()?;
Ok(())
}

View File

@ -1,8 +1,13 @@
use crate::ipc::Data;
use std::{
collections::HashMap,
net::SocketAddr,
sync::{Arc, Mutex, RwLock, Weak},
time::Duration,
};
use bytes::Bytes;
pub use connection::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::config::Config2;
use hbb_common::{
allow_err,
anyhow::{anyhow, Context},
@ -12,19 +17,18 @@ use hbb_common::{
message_proto::*,
protobuf::{Enum, Message as _},
rendezvous_proto::*,
ResultType,
socket_client,
sodiumoxide::crypto::{box_, secretbox, sign},
timeout, tokio, ResultType, Stream,
sodiumoxide::crypto::{box_, secretbox, sign}, Stream, timeout, tokio,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use service::ServiceTmpl;
use hbb_common::config::Config2;
use hbb_common::tcp::new_listener;
use service::{GenericService, Service, Subscriber};
use std::{
collections::HashMap,
net::SocketAddr,
sync::{Arc, Mutex, RwLock, Weak},
time::Duration,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use service::ServiceTmpl;
use crate::ipc::{connect, Data};
pub mod audio_service;
cfg_if::cfg_if! {
@ -55,8 +59,6 @@ mod service;
mod video_qos;
pub mod video_service;
use hbb_common::tcp::new_listener;
pub type Childs = Arc<Mutex<Vec<std::process::Child>>>;
type ConnMap = HashMap<i32, ConnInner>;
@ -425,6 +427,50 @@ pub async fn start_server(is_server: bool) {
}
}
#[cfg(target_os = "macos")]
#[tokio::main(flavor = "current_thread")]
pub async fn start_ipc_url_server() {
log::debug!("Start an ipc server for listening to url schemes");
match crate::ipc::new_listener("_url").await {
Ok(mut incoming) => {
while let Some(Ok(conn)) = incoming.next().await {
let mut conn = crate::ipc::Connection::new(conn);
match conn.next_timeout(1000).await {
Ok(Some(data)) => {
match data {
Data::UrlLink(url) => {
#[cfg(feature = "flutter")]
{
if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(
crate::flutter::APP_TYPE_MAIN
) {
let mut m = HashMap::new();
m.insert("name", "on_url_scheme_received");
m.insert("url", url.as_str());
stream.add(serde_json::to_string(&m).unwrap());
} else {
log::warn!("No main window app found!");
}
}
}
_ => {
log::warn!("An unexpected data was sent to the ipc url server.")
}
}
}
Err(err) => {
log::error!("{}", err);
}
_ => {}
}
}
}
Err(err) => {
log::error!("{}", err);
}
}
}
#[cfg(target_os = "macos")]
async fn sync_and_watch_config_dir() {
if crate::platform::is_root() {

View File

@ -3,6 +3,8 @@ use super::{input_service::*, *};
use crate::clipboard_file::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::common::update_clipboard;
#[cfg(windows)]
use crate::portable_service::client as portable_client;
use crate::video_service;
#[cfg(any(target_os = "android", target_os = "ios"))]
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
@ -101,8 +103,8 @@ pub struct Connection {
lr: LoginRequest,
last_recv_time: Arc<Mutex<Instant>>,
chat_unanswered: bool,
#[allow(unused)]
elevation_requested: bool,
#[cfg(windows)]
portable: PortableState,
from_switch: bool,
}
@ -136,7 +138,6 @@ const MILLI1: Duration = Duration::from_millis(1);
const SEND_TIMEOUT_VIDEO: u64 = 12_000;
const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10;
const SESSION_TIMEOUT: Duration = Duration::from_secs(30);
const SWITCH_SIDES_TIMEOUT: Duration = Duration::from_secs(10);
impl Connection {
pub async fn start(
@ -199,7 +200,8 @@ impl Connection {
lr: Default::default(),
last_recv_time: Arc::new(Mutex::new(Instant::now())),
chat_unanswered: false,
elevation_requested: false,
#[cfg(windows)]
portable: Default::default(),
from_switch: false,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -247,14 +249,6 @@ impl Connection {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
std::thread::spawn(move || Self::handle_input(rx_input, tx_cloned));
let mut second_timer = time::interval(Duration::from_secs(1));
#[cfg(windows)]
let mut last_uac = false;
#[cfg(windows)]
let mut last_foreground_window_elevated = false;
#[cfg(windows)]
let mut last_portable_service_running = false;
#[cfg(windows)]
let is_installed = crate::platform::is_installed();
loop {
tokio::select! {
@ -362,8 +356,7 @@ impl Connection {
}
#[cfg(windows)]
ipc::Data::DataPortableService(ipc::DataPortableService::RequestStart) => {
use crate::portable_service::client;
if let Err(e) = client::start_portable_service(client::StartPara::Direct) {
if let Err(e) = portable_client::start_portable_service(portable_client::StartPara::Direct) {
log::error!("Failed to start portable service from cm:{:?}", e);
}
}
@ -458,46 +451,7 @@ impl Connection {
},
_ = second_timer.tick() => {
#[cfg(windows)]
{
if !is_installed && conn.file_transfer.is_none() && conn.port_forward_socket.is_none(){
let portable_service_running = crate::portable_service::client::running();
if portable_service_running != last_portable_service_running {
last_portable_service_running = portable_service_running;
if portable_service_running && conn.elevation_requested {
let mut misc = Misc::new();
misc.set_portable_service_running(portable_service_running);
let mut msg = Message::new();
msg.set_misc(misc);
conn.inner.send(msg.into());
}
}
let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone();
if last_uac != uac {
last_uac = uac;
if !uac || !portable_service_running{
let mut misc = Misc::new();
misc.set_uac(uac);
let mut msg = Message::new();
msg.set_misc(misc);
conn.inner.send(msg.into());
}
}
let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap().clone();
if last_foreground_window_elevated != foreground_window_elevated {
last_foreground_window_elevated = foreground_window_elevated;
if !foreground_window_elevated || !portable_service_running {
let mut misc = Misc::new();
misc.set_foreground_window_elevated(foreground_window_elevated);
let mut msg = Message::new();
msg.set_misc(misc);
conn.inner.send(msg.into());
}
}
let show_elevation = !portable_service_running;
conn.send_to_cm(ipc::Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show_elevation)));
}
}
conn.portable_check();
}
_ = test_delay_timer.tick() => {
if last_recv_time.elapsed() >= SEC30 {
@ -1276,7 +1230,7 @@ impl Connection {
SWITCH_SIDES_UUID
.lock()
.unwrap()
.retain(|_, v| v.0.elapsed() < SWITCH_SIDES_TIMEOUT);
.retain(|_, v| v.0.elapsed() < Duration::from_secs(10));
let uuid_old = SWITCH_SIDES_UUID.lock().unwrap().remove(&lr.my_id);
if let Ok(uuid) = uuid::Uuid::from_slice(_s.uuid.to_vec().as_ref()) {
if let Some((_instant, uuid_old)) = uuid_old {
@ -1537,15 +1491,14 @@ impl Connection {
#[cfg(windows)]
{
let mut err = "No need to elevate".to_string();
if !crate::platform::is_installed()
&& !crate::portable_service::client::running()
{
use crate::portable_service::client;
err = client::start_portable_service(client::StartPara::Direct)
.err()
.map_or("".to_string(), |e| e.to_string());
if !crate::platform::is_installed() && !portable_client::running() {
err = portable_client::start_portable_service(
portable_client::StartPara::Direct,
)
.err()
.map_or("".to_string(), |e| e.to_string());
}
self.elevation_requested = err.is_empty();
self.portable.elevation_requested = err.is_empty();
let mut misc = Misc::new();
misc.set_elevation_response(err);
let mut msg = Message::new();
@ -1557,18 +1510,14 @@ impl Connection {
#[cfg(windows)]
{
let mut err = "No need to elevate".to_string();
if !crate::platform::is_installed()
&& !crate::portable_service::client::running()
{
use crate::portable_service::client;
err = client::start_portable_service(client::StartPara::Logon(
_r.username,
_r.password,
))
if !crate::platform::is_installed() && !portable_client::running() {
err = portable_client::start_portable_service(
portable_client::StartPara::Logon(_r.username, _r.password),
)
.err()
.map_or("".to_string(), |e| e.to_string());
}
self.elevation_requested = err.is_empty();
self.portable.elevation_requested = err.is_empty();
let mut misc = Misc::new();
misc.set_elevation_response(err);
let mut msg = Message::new();
@ -1588,8 +1537,8 @@ impl Connection {
uuid.to_string().as_ref(),
])
.ok();
self.send_close_reason_no_retry("Closed as expected");
self.on_close("switch sides", false);
self.send_close_reason_no_retry("Closed as expected").await;
self.on_close("switch sides", false).await;
return false;
}
}
@ -1810,6 +1759,59 @@ impl Connection {
pub fn alive_conns() -> Vec<i32> {
ALIVE_CONNS.lock().unwrap().clone()
}
#[cfg(windows)]
fn portable_check(&mut self) {
if self.portable.is_installed
|| self.file_transfer.is_some()
|| self.port_forward_socket.is_some()
{
return;
}
let running = portable_client::running();
let show_elevation = !running;
self.send_to_cm(ipc::Data::DataPortableService(
ipc::DataPortableService::CmShowElevation(show_elevation),
));
if self.authorized {
let p = &mut self.portable;
if running != p.last_running {
p.last_running = running;
if running && p.elevation_requested {
let mut misc = Misc::new();
misc.set_portable_service_running(running);
let mut msg = Message::new();
msg.set_misc(misc);
self.inner.send(msg.into());
}
}
let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone();
if p.last_uac != uac {
p.last_uac = uac;
if !uac || !running {
let mut misc = Misc::new();
misc.set_uac(uac);
let mut msg = Message::new();
msg.set_misc(misc);
self.inner.send(msg.into());
}
}
let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED
.lock()
.unwrap()
.clone();
if p.last_foreground_window_elevated != foreground_window_elevated {
p.last_foreground_window_elevated = foreground_window_elevated;
if !foreground_window_elevated || !running {
let mut misc = Misc::new();
misc.set_foreground_window_elevated(foreground_window_elevated);
let mut msg = Message::new();
msg.set_misc(misc);
self.inner.send(msg.into());
}
}
}
}
}
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
@ -1984,3 +1986,25 @@ pub enum FileAuditType {
RemoteSend = 0,
RemoteReceive = 1,
}
#[cfg(windows)]
pub struct PortableState {
pub last_uac: bool,
pub last_foreground_window_elevated: bool,
pub last_running: bool,
pub is_installed: bool,
pub elevation_requested: bool,
}
#[cfg(windows)]
impl Default for PortableState {
fn default() -> Self {
Self {
is_installed: crate::platform::is_installed(),
last_uac: Default::default(),
last_foreground_window_elevated: Default::default(),
last_running: Default::default(),
elevation_requested: Default::default(),
}
}
}

View File

@ -2,9 +2,7 @@ use core::slice;
use hbb_common::{
allow_err,
anyhow::anyhow,
bail,
config::Config,
log,
bail, log,
message_proto::{KeyEvent, MouseEvent},
protobuf::Message,
tokio::{self, sync::mpsc},
@ -15,6 +13,7 @@ use shared_memory::*;
use std::{
mem::size_of,
ops::{Deref, DerefMut},
path::PathBuf,
sync::{Arc, Mutex},
time::Duration,
};
@ -25,6 +24,7 @@ use winapi::{
use crate::{
ipc::{self, new_listener, Connection, Data, DataPortableService},
platform::set_path_permission,
video_service::get_current_display,
};
@ -72,7 +72,7 @@ impl DerefMut for SharedMemory {
impl SharedMemory {
pub fn create(name: &str, size: usize) -> ResultType<Self> {
let flink = Self::flink(name.to_string());
let flink = Self::flink(name.to_string())?;
let shmem = match ShmemConf::new()
.size(size)
.flink(&flink)
@ -91,12 +91,12 @@ impl SharedMemory {
}
};
log::info!("Create shared memory, size:{}, flink:{}", size, flink);
Self::set_all_perm(&flink);
set_path_permission(&PathBuf::from(flink), "F").ok();
Ok(SharedMemory { inner: shmem })
}
pub fn open_existing(name: &str) -> ResultType<Self> {
let flink = Self::flink(name.to_string());
let flink = Self::flink(name.to_string())?;
let shmem = match ShmemConf::new().flink(&flink).allow_raw(true).open() {
Ok(m) => m,
Err(e) => {
@ -116,30 +116,29 @@ impl SharedMemory {
}
}
fn flink(name: String) -> String {
let mut shmem_flink = format!("shared_memory{}", name);
if cfg!(windows) {
let df = "C:\\ProgramData";
let df = if std::path::Path::new(df).exists() {
df.to_owned()
} else {
std::env::var("TEMP").unwrap_or("C:\\Windows\\TEMP".to_owned())
};
let df = format!("{}\\{}", df, *hbb_common::config::APP_NAME.read().unwrap());
std::fs::create_dir(&df).ok();
shmem_flink = format!("{}\\{}", df, shmem_flink);
fn flink(name: String) -> ResultType<String> {
let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string());
let mut dir = PathBuf::from(disk);
let dir1 = dir.join("ProgramData");
let dir2 = std::env::var("TEMP")
.map(|d| PathBuf::from(d))
.unwrap_or(dir.join("Windows").join("Temp"));
if dir1.exists() {
dir = dir1;
} else if dir2.exists() {
dir = dir2;
} else {
shmem_flink = Config::ipc_path("").replace("ipc", "") + &shmem_flink;
bail!("no vaild flink directory");
}
return shmem_flink;
}
fn set_all_perm(_p: &str) {
#[cfg(not(windows))]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(_p, std::fs::Permissions::from_mode(0o0777)).ok();
dir = dir.join(hbb_common::config::APP_NAME.read().unwrap().clone());
if !dir.exists() {
std::fs::create_dir(&dir)?;
set_path_permission(&dir, "F").ok();
}
Ok(dir
.join(format!("shared_memory{}", name))
.to_string_lossy()
.to_string())
}
}
@ -451,7 +450,6 @@ pub mod server {
// functions called in main process.
pub mod client {
use hbb_common::anyhow::Context;
use std::path::PathBuf;
use super::*;
@ -459,6 +457,7 @@ pub mod client {
static ref RUNNING: Arc<Mutex<bool>> = Default::default();
static ref SHMEM: Arc<Mutex<Option<SharedMemory>>> = Default::default();
static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(client::start_ipc_server());
static ref QUICK_SUPPORT: Arc<Mutex<bool>> = Default::default();
}
pub enum StartPara {
@ -514,7 +513,7 @@ pub mod client {
#[cfg(feature = "flutter")]
{
if let Some(dir) = PathBuf::from(&exe).parent() {
if !set_dir_permission(&PathBuf::from(dir)) {
if set_path_permission(&PathBuf::from(dir), "RX").is_err() {
*SHMEM.lock().unwrap() = None;
bail!("Failed to set permission of {:?}", dir);
}
@ -532,7 +531,7 @@ pub mod client {
let dst = dir.join("rustdesk.exe");
if std::fs::copy(&exe, &dst).is_ok() {
if dst.exists() {
if set_dir_permission(&dir) {
if set_path_permission(&dir, "RX").is_ok() {
exe = dst.to_string_lossy().to_string();
}
}
@ -561,16 +560,10 @@ pub mod client {
*SHMEM.lock().unwrap() = None;
}
fn set_dir_permission(dir: &PathBuf) -> bool {
// // give Everyone RX permission
std::process::Command::new("icacls")
.arg(dir.as_os_str())
.arg("/grant")
.arg("Everyone:(OI)(CI)RX")
.arg("/T")
.spawn()
.is_ok()
pub fn set_quick_support(v: bool) {
*QUICK_SUPPORT.lock().unwrap() = v;
}
pub struct CapturerPortable;
impl CapturerPortable {
@ -685,17 +678,7 @@ pub mod client {
use DataPortableService::*;
let rx = Arc::new(tokio::sync::Mutex::new(rx));
let postfix = IPC_SUFFIX;
#[cfg(feature = "flutter")]
let quick_support = {
let args: Vec<_> = std::env::args().collect();
args.contains(&"--quick_support".to_string())
};
#[cfg(not(feature = "flutter"))]
let quick_support = std::env::current_exe()
.unwrap_or("".into())
.to_string_lossy()
.to_lowercase()
.ends_with("qs.exe");
let quick_support = QUICK_SUPPORT.lock().unwrap().clone();
match new_listener(postfix).await {
Ok(mut incoming) => loop {

View File

@ -498,7 +498,7 @@ fn run(sp: GenericService) -> ResultType<()> {
video_qos.target_bitrate,
video_qos.fps
);
encoder.set_bitrate(video_qos.target_bitrate).unwrap();
allow_err!(encoder.set_bitrate(video_qos.target_bitrate));
spf = video_qos.spf();
}
drop(video_qos);
@ -956,15 +956,17 @@ fn start_uac_elevation_check() {
START.call_once(|| {
if !crate::platform::is_installed()
&& !crate::platform::is_root()
&& !crate::platform::is_elevated(None).map_or(false, |b| b)
&& !crate::portable_service::client::running()
{
std::thread::spawn(|| loop {
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(uac) = crate::ui::win_privacy::is_process_consent_running() {
*IS_UAC_RUNNING.lock().unwrap() = uac;
}
if let Ok(elevated) = crate::platform::is_foreground_window_elevated() {
*IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap() = elevated;
if !crate::platform::is_elevated(None).unwrap_or(false) {
if let Ok(elevated) = crate::platform::is_foreground_window_elevated() {
*IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap() = elevated;
}
}
});
}

View File

@ -1,6 +1,6 @@
use super::*;
use hbb_common::{allow_err, platform::linux::DISTRO};
use scrap::{set_map_err, Capturer, Display, Frame, TraitCapturer};
use scrap::{is_cursor_embedded, set_map_err, Capturer, Display, Frame, TraitCapturer};
use std::io;
use super::video_service::{
@ -12,7 +12,7 @@ lazy_static::lazy_static! {
static ref LOG_SCRAP_COUNT: Mutex<u32> = Mutex::new(0);
}
pub fn set_wayland_scrap_map_err() {
pub fn init() {
set_map_err(map_err_scrap);
}
@ -129,7 +129,7 @@ pub(super) async fn check_init() -> ResultType<()> {
let num = all.len();
let (primary, mut displays) = super::video_service::get_displays_2(&all);
for display in displays.iter_mut() {
display.cursor_embedded = true;
display.cursor_embedded = is_cursor_embedded();
}
let mut rects: Vec<((i32, i32), usize, usize)> = Vec::new();

View File

@ -21,7 +21,7 @@ mod cm;
#[cfg(feature = "inline")]
pub mod inline;
#[cfg(target_os = "macos")]
mod macos;
pub mod macos;
pub mod remote;
#[cfg(target_os = "windows")]
pub mod win_privacy;
@ -434,18 +434,10 @@ impl UI {
is_login_wayland()
}
fn fix_login_wayland(&mut self) {
fix_login_wayland()
}
fn current_is_wayland(&mut self) -> bool {
current_is_wayland()
}
fn modify_default_login(&mut self) -> String {
modify_default_login()
}
fn get_software_update_url(&self) -> String {
get_software_update_url()
}
@ -590,9 +582,7 @@ impl sciter::EventHandler for UI {
fn is_installed_daemon(bool);
fn get_error();
fn is_login_wayland();
fn fix_login_wayland();
fn current_is_wayland();
fn modify_default_login();
fn get_options();
fn get_option(String);
fn get_local_option(String);

View File

@ -755,11 +755,6 @@ class FixWayland: Reactor.Component {
</div>;
}
event click $(#fix-wayland) {
handler.fix_login_wayland();
app.update();
}
event click $(#help-me) {
handler.open_url(translate("doc_fix_wayland"));
}
@ -769,19 +764,11 @@ class ModifyDefaultLogin: Reactor.Component {
function render() {
return <div .trust-me>
<div>{translate('Warning')}</div>
<div>{translate('Current Wayland display server is not supported')}</div>
<div>{translate('wayland_experiment_tip')}</div>
<div #help-me .link>{translate('Help')}</div>
</div>;
}
event click $(#modify-default-login) {
if (var r = handler.modify_default_login()) {
// without handler, will fail, fucking stupid sciter
handler.msgbox("custom-error", "Error", r);
}
app.update();
}
event click $(#help-me) {
handler.open_url(translate("doc_fix_wayland"));
}

View File

@ -1,3 +1,5 @@
use std::{ffi::c_void, rc::Rc};
#[cfg(target_os = "macos")]
use cocoa::{
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy::*, NSMenu, NSMenuItem},
@ -8,11 +10,16 @@ use objc::{
class,
declare::ClassDecl,
msg_send,
runtime::{Object, Sel, BOOL},
runtime::{BOOL, Object, Sel},
sel, sel_impl,
};
use sciter::{make_args, Host};
use std::{ffi::c_void, rc::Rc};
use objc::runtime::Class;
use objc_id::WeakId;
use sciter::{Host, make_args};
use hbb_common::{log, tokio};
use crate::ui_cm_interface::start_ipc;
static APP_HANDLER_IVAR: &str = "GoDeskAppHandler";
@ -98,12 +105,21 @@ unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
sel!(handleMenuItem:),
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(sel!(handleEvent:withReplyEvent:), handle_apple_event as extern fn(&Object, Sel, u64, u64));
let decl = decl.register();
let delegate: id = msg_send![decl, alloc];
let () = msg_send![delegate, init];
let state = DelegateState { handler };
let handler_ptr = Box::into_raw(Box::new(state));
(*delegate).set_ivar(APP_HANDLER_IVAR, handler_ptr as *mut c_void);
// Set the url scheme handler
let cls = Class::get("NSAppleEventManager").unwrap();
let manager: *mut Object = msg_send![cls, sharedAppleEventManager];
let _: () = msg_send![manager,
setEventHandler: delegate
andSelector: sel!(handleEvent:withReplyEvent:)
forEventClass: fruitbasket::kInternetEventClass
andEventID: fruitbasket::kAEGetURL];
let () = msg_send![NSApp(), setDelegate: delegate];
}
@ -167,6 +183,24 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
}
}
/// The function to handle the url scheme sent by the system.
///
/// 1. Try to send the url scheme from ipc.
/// 2. If failed to send the url scheme, we open a new main window to handle this url scheme.
pub fn handle_url_scheme(url: String) {
if let Err(err) = crate::ipc::send_url_scheme(url.clone()) {
log::debug!("Send the url to the existing flutter process failed, {}. Let's open a new program to handle this.", err);
let _ = crate::run_me(vec![url]);
}
}
extern fn handle_apple_event(_this: &Object, _cmd: Sel, event: u64, _reply: u64) {
let event = event as *mut Object;
let url = fruitbasket::parse_url_event(event);
log::debug!("an event was received: {}", url);
std::thread::spawn(move || handle_url_scheme(url));
}
unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object {
let title = NSString::alloc(nil).init_str(title);
let action = sel!(handleMenuItem:);

View File

@ -16,7 +16,7 @@ div#quality-monitor {
padding: 5px;
min-width: 150px;
color: azure;
border: solid azure;
border: 0.5px solid azure;
}
video#handler {

View File

@ -789,9 +789,7 @@ fn cm_inner_send(id: i32, data: Data) {
pub fn can_elevate() -> bool {
#[cfg(windows)]
{
return !crate::platform::is_installed() && !crate::portable_service::client::running();
}
return !crate::platform::is_installed();
#[cfg(not(windows))]
return false;
}

View File

@ -614,12 +614,6 @@ pub fn is_login_wayland() -> bool {
return false;
}
#[inline]
pub fn fix_login_wayland() {
#[cfg(target_os = "linux")]
crate::platform::linux::fix_login_wayland();
}
#[inline]
pub fn current_is_wayland() -> bool {
#[cfg(target_os = "linux")]
@ -628,14 +622,6 @@ pub fn current_is_wayland() -> bool {
return false;
}
#[inline]
pub fn modify_default_login() -> String {
#[cfg(target_os = "linux")]
return crate::platform::linux::modify_default_login();
#[cfg(not(target_os = "linux"))]
return "".to_owned();
}
#[inline]
pub fn get_software_update_url() -> String {
SOFTWARE_UPDATE_URL.lock().unwrap().clone()
@ -931,6 +917,18 @@ pub fn account_auth_result() -> String {
serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default()
}
#[cfg(feature = "flutter")]
pub fn set_user_default_option(key: String, value: String) {
use hbb_common::config::UserDefaultConfig;
UserDefaultConfig::load().set(key, value);
}
#[cfg(feature = "flutter")]
pub fn get_user_default_option(key: String) -> String {
use hbb_common::config::UserDefaultConfig;
UserDefaultConfig::load().get(&key)
}
// notice: avoiding create ipc connection repeatedly,
// because windows named pipe has serious memory leak issue.
#[tokio::main(flavor = "current_thread")]

View File

@ -6,7 +6,6 @@ use crate::client::{
};
use crate::common::{self, GrabState};
use crate::keyboard;
use crate::ui_interface::using_public_server;
use crate::{client::Data, client::Interface};
use async_trait::async_trait;
use bytes::Bytes;