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

View File

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

View File

@ -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 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> <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) [![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://rustdesk.com/server) یا
[ سرورrendezvous/relay خود را بنویسید](https://github.com/rustdesk/rustdesk). [ سرورrendezvous/relay خود را بنویسید](https://github.com/rustdesk/rustdesk).
@ -130,7 +130,7 @@ cd rustdesk
docker build -t "rustdesk-builder" . docker build -t "rustdesk-builder" .
``` ```
سپس، هر بار که نیاز به ساخت ترم‌افزار داشتید، دستور زیر را اجرا کنید: سپس، هر بار که نیاز به ساخت نرم‌افزار داشتید، دستور زیر را اجرا کنید:
```sh ```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 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:ffi' hide Size;
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:back_button_interceptor/back_button_interceptor.dart'; import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:win32/win32.dart' as win32;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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/main.dart';
import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.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:get/get.dart';
import 'package:uni_links/uni_links.dart'; import 'package:uni_links/uni_links.dart';
import 'package:uni_links_desktop/uni_links_desktop.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: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 'common/widgets/overlay.dart';
import 'mobile/pages/file_manager_page.dart'; import 'mobile/pages/file_manager_page.dart';
import 'mobile/pages/remote_page.dart'; import 'mobile/pages/remote_page.dart';
@ -33,8 +34,6 @@ import 'models/input_model.dart';
import 'models/model.dart'; import 'models/model.dart';
import 'models/platform_model.dart'; import 'models/platform_model.dart';
import '../consts.dart';
final globalKey = GlobalKey<NavigatorState>(); final globalKey = GlobalKey<NavigatorState>();
final navigationBarKey = GlobalKey(); final navigationBarKey = GlobalKey();
@ -205,6 +204,9 @@ class MyTheme {
splashColor: Colors.transparent, splashColor: Colors.transparent,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null, splashFactory: isDesktop ? NoSplash.splashFactory : null,
outlinedButtonTheme: OutlinedButtonThemeData(
style:
OutlinedButton.styleFrom(side: BorderSide(color: Colors.white38))),
textButtonTheme: isDesktop textButtonTheme: isDesktop
? TextButtonThemeData( ? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory), style: ButtonStyle(splashFactory: NoSplash.splashFactory),
@ -221,16 +223,18 @@ class MyTheme {
return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme)); return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme));
} }
static void changeDarkMode(ThemeMode mode) { static void changeDarkMode(ThemeMode mode) async {
Get.changeThemeMode(mode); Get.changeThemeMode(mode);
if (desktopType == DesktopType.main) { if (desktopType == DesktopType.main) {
if (mode == ThemeMode.system) { if (mode == ThemeMode.system) {
bind.mainSetLocalOption(key: kCommConfKeyTheme, value: ''); await bind.mainSetLocalOption(key: kCommConfKeyTheme, value: '');
} else { } else {
bind.mainSetLocalOption( await bind.mainSetLocalOption(
key: kCommConfKeyTheme, value: mode.toShortString()); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
FocusNode focusNode = FocusNode(); // request focus
// request focus if there is no focused FocusNode in the dialog
Future.delayed(Duration.zero, () {
if (!focusNode.hasFocus) focusNode.requestFocus();
});
FocusScopeNode scopeNode = FocusScopeNode(); FocusScopeNode scopeNode = FocusScopeNode();
Future.delayed(Duration.zero, () {
if (!scopeNode.hasFocus) scopeNode.requestFocus();
});
const double padding = 16;
return FocusScope( return FocusScope(
node: scopeNode, node: scopeNode,
autofocus: true, autofocus: true,
@ -638,17 +642,18 @@ class CustomAlertDialog extends StatelessWidget {
child: AlertDialog( child: AlertDialog(
scrollable: true, scrollable: true,
title: title, title: title,
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.fromLTRB(
horizontal: contentPadding ?? 25, vertical: 10), contentPadding ?? padding, 25, contentPadding ?? padding, 10),
content: ConstrainedBox( content: ConstrainedBox(
constraints: contentBoxConstraints, constraints: contentBoxConstraints,
child: Theme( child: Theme(
data: ThemeData( data: Theme.of(context).copyWith(
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
isDense: true, contentPadding: EdgeInsets.all(15)), isDense: true, contentPadding: EdgeInsets.all(15))),
child: content),
), ),
child: content)),
actions: actions, 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( buttons.insert(
0, dialogButton('Cancel', onPressed: cancel, isOutline: true)); 0, dialogButton('Cancel', onPressed: cancel, isOutline: true));
} }
// TODO: test this button
if (type.contains("hasclose")) { if (type.contains("hasclose")) {
buttons.insert( buttons.insert(
0, 0,
@ -702,9 +706,8 @@ void msgBox(String id, String type, String title, String text, String link,
} }
dialogManager.show( dialogManager.show(
(setState, close) => CustomAlertDialog( (setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title), title: null,
content: content: SelectionArea(child: msgboxContent(type, title, text)),
SelectableText(translate(text), style: const TextStyle(fontSize: 15)),
actions: buttons, actions: buttons,
onSubmit: hasOk ? submit : null, onSubmit: hasOk ? submit : null,
onCancel: hasCancel == true ? cancel : 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) { Color? _msgboxColor(String type) {
return ButtonTheme( if (type == "input-password" || type == "custom-os-password") {
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), return Color(0xFFAD448E);
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, }
//limits the touch area to the button area if (type.contains("success")) {
minWidth: 0, return Color(0xFF32bea6);
//wraps child's width }
height: 0, if (type.contains("error") || type == "re-input-password") {
child: TextButton( return Color(0xFFE04F5F);
style: flatButtonStyle, }
onPressed: onPressed, return Color(0xFF2C8CFF);
child:
Text(translate(text), style: TextStyle(color: MyTheme.accent))));
} }
Widget _msgBoxTitle(String title) => Widget msgboxIcon(String type) {
Text(translate(title), style: TextStyle(fontSize: 21)); 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, void msgBoxCommon(OverlayDialogManager dialogManager, String title,
Widget content, List<Widget> buttons, Widget content, List<Widget> buttons,
{bool hasCancel = true}) { {bool hasCancel = true}) {
dialogManager.dismissAll(); dialogManager.dismissAll();
dialogManager.show((setState, close) => CustomAlertDialog( dialogManager.show((setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title), title: Text(
translate(title),
style: TextStyle(fontSize: 21),
),
content: content, content: content,
actions: buttons, actions: buttons,
onCancel: hasCancel ? close : null, onCancel: hasCancel ? close : null,
@ -1223,10 +1270,12 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
/// [Availability] /// [Availability]
/// initUniLinks should only be used on macos/windows. /// initUniLinks should only be used on macos/windows.
/// we use dbus for linux currently. /// we use dbus for linux currently.
Future<void> initUniLinks() async { Future<bool> initUniLinks() async {
if (!Platform.isWindows && !Platform.isMacOS) { if (Platform.isLinux) {
return; return false;
} }
// Register uni links for Windows. The required info of url scheme is already
// declared in `Info.plist` for macOS.
if (Platform.isWindows) { if (Platform.isWindows) {
registerProtocol('rustdesk'); registerProtocol('rustdesk');
} }
@ -1234,11 +1283,12 @@ Future<void> initUniLinks() async {
try { try {
final initialLink = await getInitialLink(); final initialLink = await getInitialLink();
if (initialLink == null) { if (initialLink == null) {
return; return false;
} }
parseRustdeskUri(initialLink); return parseRustdeskUri(initialLink);
} catch (err) { } catch (err) {
debugPrintStack(label: "$err"); debugPrintStack(label: "$err");
return false;
} }
} }
@ -1259,11 +1309,19 @@ StreamSubscription? listenUniLinks() {
return sub; 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() { bool checkArguments() {
if (kBootArgs.isNotEmpty) {
final ret = parseRustdeskUri(kBootArgs.first);
if (ret) {
return true;
}
}
// bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05] // bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05]
// check connect args // check connect args
final connectIndex = kBootArgs.indexOf("--connect"); var connectIndex = kBootArgs.indexOf("--connect");
if (connectIndex == -1) { if (connectIndex == -1) {
return false; return false;
} }
@ -1298,7 +1356,7 @@ bool checkArguments() {
bool parseRustdeskUri(String uriPath) { bool parseRustdeskUri(String uriPath) {
final uri = Uri.tryParse(uriPath); final uri = Uri.tryParse(uriPath);
if (uri == null) { if (uri == null) {
print("uri is not valid: $uriPath"); debugPrint("uri is not valid: $uriPath");
return false; return false;
} }
return callUniLinksUriHandler(uri); return callUniLinksUriHandler(uri);
@ -1317,7 +1375,7 @@ bool callUniLinksUriHandler(Uri uri) {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
rustDeskWinManager.newRemoteDesktop(peerId, switch_uuid: switch_uuid); rustDeskWinManager.newRemoteDesktop(peerId, switch_uuid: switch_uuid);
}); });
return false; return true;
} }
return false; return false;
} }
@ -1457,8 +1515,12 @@ Future<void> onActiveWindowChanged() async {
} catch (err) { } catch (err) {
debugPrintStack(label: "$err"); debugPrintStack(label: "$err");
} finally { } finally {
debugPrint("Start closing RustDesk...");
await windowManager.setPreventClose(false); await windowManager.setPreventClose(false);
await windowManager.close(); await windowManager.close();
if (Platform.isMacOS) {
RdPlatformChannel.instance.terminate();
}
} }
} }
} }
@ -1590,7 +1652,8 @@ class ServerConfig {
Widget dialogButton(String text, Widget dialogButton(String text,
{required VoidCallback? onPressed, {required VoidCallback? onPressed,
bool isOutline = false, bool isOutline = false,
TextStyle? style}) { TextStyle? style,
ButtonStyle? buttonStyle}) {
if (isDesktop) { if (isDesktop) {
if (isOutline) { if (isOutline) {
return OutlinedButton( return OutlinedButton(
@ -1599,7 +1662,7 @@ Widget dialogButton(String text,
); );
} else { } else {
return ElevatedButton( return ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0), style: ElevatedButton.styleFrom(elevation: 0).merge(buttonStyle),
onPressed: onPressed, onPressed: onPressed,
child: Text(translate(text), style: style), child: Text(translate(text), style: style),
); );
@ -1637,3 +1700,16 @@ String getWindowName({WindowType? overrideType}) {
String getWindowNameWithId(String id, {WindowType? overrideType}) { String getWindowNameWithId(String id, {WindowType? overrideType}) {
return "${DesktopTab.labelGetterAlias(id).value} - ${getWindowName(overrideType: 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 'package:flutter_hbb/models/peer_model.dart';
import '../../models/platform_model.dart';
class HttpType { class HttpType {
static const kAuthReqTypeAccount = "account"; static const kAuthReqTypeAccount = "account";
static const kAuthReqTypeMobile = "mobile"; 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 { class LoginRequest {
String? username; String? username;
String? password; String? password;
@ -56,7 +70,7 @@ class LoginRequest {
bool? autoLogin; bool? autoLogin;
String? type; String? type;
String? verificationCode; String? verificationCode;
String? deviceInfo; Map<String, dynamic> deviceInfo = DeviceInfo.toJson();
LoginRequest( LoginRequest(
{this.username, {this.username,
@ -65,19 +79,7 @@ class LoginRequest {
this.uuid, this.uuid,
this.autoLogin, this.autoLogin,
this.type, this.type,
this.verificationCode, 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'];
}
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};
@ -88,7 +90,7 @@ class LoginRequest {
data['autoLogin'] = autoLogin ?? ''; data['autoLogin'] = autoLogin ?? '';
data['type'] = type ?? ''; data['type'] = type ?? '';
data['verificationCode'] = verificationCode ?? ''; data['verificationCode'] = verificationCode ?? '';
data['deviceInfo'] = deviceInfo ?? ''; data['deviceInfo'] = deviceInfo;
return data; return data;
} }
} }

View File

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

View File

@ -666,6 +666,8 @@ Future<bool?> verificationCodeDialog(UserPayload? user) async {
child: const LinearProgressIndicator()), child: const LinearProgressIndicator()),
], ],
), ),
onCancel: close,
onSubmit: onVerify,
actions: [ actions: [
dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("Verify", onPressed: onVerify), 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/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -316,46 +317,52 @@ class _DraggableState extends State<Draggable> {
} }
class QualityMonitor extends StatelessWidget { class QualityMonitor extends StatelessWidget {
static const textStyle = TextStyle(color: MyTheme.grayBg);
final QualityMonitorModel qualityMonitorModel; final QualityMonitorModel qualityMonitorModel;
QualityMonitor(this.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 @override
Widget build(BuildContext context) => ChangeNotifierProvider.value( Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: qualityMonitorModel, value: qualityMonitorModel,
child: Consumer<QualityMonitorModel>( child: Consumer<QualityMonitorModel>(
builder: (context, qualityMonitorModel, child) => Positioned( builder: (context, qualityMonitorModel, child) => qualityMonitorModel
top: 10, .show
right: 10,
child: qualityMonitorModel.show
? Container( ? Container(
constraints: BoxConstraints(maxWidth: 200),
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
color: MyTheme.canvasColor.withAlpha(120), color: MyTheme.canvasColor.withAlpha(120),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( _row("Speed", qualityMonitorModel.data.speed ?? '-'),
"Speed: ${qualityMonitorModel.data.speed ?? ''}", _row("FPS", qualityMonitorModel.data.fps ?? '-'),
style: textStyle, _row(
), "Delay", "${qualityMonitorModel.data.delay ?? '-'}ms",
Text( rightColor: Colors.green),
"FPS: ${qualityMonitorModel.data.fps ?? ''}", _row("Target Bitrate",
style: textStyle, "${qualityMonitorModel.data.targetBitrate ?? '-'}kb"),
), _row(
Text( "Codec", qualityMonitorModel.data.codecFormat ?? '-'),
"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()))); : const SizedBox.shrink()));
} }

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
@ -20,7 +21,9 @@ const int groupTabIndex = 4;
const String defaultGroupTabname = 'Group'; const String defaultGroupTabname = 'Group';
class StatePeerTab { 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 RxInt tabHiddenFlag = 0.obs;
final RxList<String> tabNames = [ final RxList<String> tabNames = [
'Recent Sessions', 'Recent Sessions',
@ -31,53 +34,80 @@ class StatePeerTab {
].obs; ].obs;
StatePeerTab._() { StatePeerTab._() {
// init tabHiddenFlag
tabHiddenFlag.value = (int.tryParse( tabHiddenFlag.value = (int.tryParse(
bind.getLocalFlutterConfig(k: 'hidden-peer-card'), bind.getLocalFlutterConfig(k: 'hidden-peer-card'),
radix: 2) ?? radix: 2) ??
0); 0);
var tabs = _notHiddenTabs(); 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 = currentTab.value =
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0; int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0;
if (!tabs.contains(currentTab.value)) { if (!tabs.contains(currentTab.value)) {
if (tabs.isNotEmpty) {
currentTab.value = tabs[0];
} else {
currentTab.value = 0; currentTab.value = 0;
} }
} }
}
static final StatePeerTab instance = StatePeerTab._(); static final StatePeerTab instance = StatePeerTab._();
// check dynamic tabs
check() { check() {
var tabs = _notHiddenTabs(); tabOrder2visibleOrderedTabs();
if (filterGroupCard()) { if (visibleOrderedTabs.contains(groupTabIndex) &&
if (currentTab.value == groupTabIndex) {
currentTab.value =
tabs.firstWhereOrNull((e) => e != groupTabIndex) ?? 0;
bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: currentTab.value.toString());
}
} 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')) == int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ==
groupTabIndex) { groupTabIndex) {
currentTab.value = groupTabIndex; currentTab.value = groupTabIndex;
} }
if (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isNotEmpty) {
tabNames[groupTabIndex] = gFFI.userModel.groupName.value;
} else {
tabNames[groupTabIndex] = defaultGroupTabname;
} }
} }
List<int> currentTabs() { visibleOrderedTabs2TabOrder() {
var v = List<int>.empty(growable: true); var tmpTabOrder = visibleOrderedTabs.toList();
for (int i = 0; i < tabNames.length; i++) { var left = tabOrder.where((e) => !tmpTabOrder.contains(e)).toList();
if (!_isTabHidden(i) && !_isTabFilter(i)) { for (var t in left) {
v.add(i); _addTabInOrder(tmpTabOrder, t);
} }
} statePeerTab.tabOrder = tmpTabOrder;
return v; 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() { bool filterGroupCard() {
if (gFFI.groupModel.users.isEmpty || if (gFFI.groupModel.users.isEmpty ||
(gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.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) { bool _isTabHidden(int tabindex) {
return tabHiddenFlag & (1 << tabindex) != 0; return tabHiddenFlag & (1 << tabindex) != 0;
} }
@ -107,6 +148,41 @@ class StatePeerTab {
} }
return v; 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; final statePeerTab = StatePeerTab.instance;
@ -177,11 +253,6 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
} }
@override
void dispose() {
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
@ -213,15 +284,30 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
Widget _createSwitchBar(BuildContext context) { Widget _createSwitchBar(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Obx(() { return Obx(() {
var tabs = statePeerTab.currentTabs(); var tabs = statePeerTab.visibleOrderedTabs;
return ListView( 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, scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
controller: ScrollController(), scrollController: ScrollController(),
children: tabs.map((t) { children: tabs.map((t) {
return InkWell( indexCounter++;
return ReorderableDragStartListener(
key: ValueKey(t),
index: indexCounter,
child: InkWell(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -239,8 +325,8 @@ class _PeerTabPageState extends State<PeerTabPage>
height: 1, height: 1,
fontSize: 14, fontSize: 14,
color: statePeerTab.currentTab.value == t color: statePeerTab.currentTab.value == t
? textColor ? MyTheme.tabbar(context).selectedTextColor
: textColor : MyTheme.tabbar(context).unSelectedTextColor
?..withOpacity(0.5)), ?..withOpacity(0.5)),
), ),
)), )),
@ -249,6 +335,7 @@ class _PeerTabPageState extends State<PeerTabPage>
await bind.setLocalFlutterConfig( await bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: t.toString()); k: 'peer-tab-index', v: t.toString());
}, },
),
); );
}).toList()); }).toList());
}); });
@ -275,7 +362,7 @@ class _PeerTabPageState extends State<PeerTabPage>
final verticalMargin = isDesktop ? 12.0 : 6.0; final verticalMargin = isDesktop ? 12.0 : 6.0;
return Expanded( return Expanded(
child: Obx(() { child: Obx(() {
var tabs = statePeerTab.currentTabs(); var tabs = statePeerTab.visibleOrderedTabs;
if (tabs.isEmpty) { if (tabs.isEmpty) {
return visibleContextMenuListener(Center( return visibleContextMenuListener(Center(
child: Text(translate('Right click to select tabs')), child: Text(translate('Right click to select tabs')),
@ -322,7 +409,7 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
adjustTab() { adjustTab() {
var tabs = statePeerTab.currentTabs(); var tabs = statePeerTab.visibleOrderedTabs;
if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) { if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) {
statePeerTab.currentTab.value = tabs[0]; statePeerTab.currentTab.value = tabs[0];
} }
@ -349,11 +436,13 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget visibleContextMenu(CancelFunc cancelFunc) { Widget visibleContextMenu(CancelFunc cancelFunc) {
return Obx(() { return Obx(() {
final List<MenuEntryBase> menu = List.empty(growable: true); 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++) { for (int i = 0; i < statePeerTab.tabNames.length; i++) {
if (i == groupTabIndex && statePeerTab.filterGroupCard()) { if (i == groupTabIndex && statePeerTab.filterGroupCard()) {
continue; continue;
} }
int bitMask = 1 << i; int bitMask = 1 << i;
menuIndex.add(i);
menu.add(MenuEntrySwitch( menu.add(MenuEntrySwitch(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translatedTabname(i), text: translatedTabname(i),
@ -369,12 +458,21 @@ class _PeerTabPageState extends State<PeerTabPage>
await bind.setLocalFlutterConfig( await bind.setLocalFlutterConfig(
k: 'hidden-peer-card', k: 'hidden-peer-card',
v: statePeerTab.tabHiddenFlag.value.toRadixString(2)); v: statePeerTab.tabHiddenFlag.value.toRadixString(2));
statePeerTab.tabOrder2visibleOrderedTabs();
cancelFunc(); cancelFunc();
adjustTab(); 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( return mod_menu.PopupMenu(
items: menu items: menu2
.map((entry) => entry.build( .map((entry) => entry.build(
context, context,
const MenuConfig( const MenuConfig(
@ -419,7 +517,12 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
Widget _buildSearchBar() { Widget _buildSearchBar() {
RxBool focused = false.obs; RxBool focused = false.obs;
FocusNode focusNode = FocusNode(); 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( return Container(
width: 120, width: 120,
decoration: BoxDecoration( decoration: BoxDecoration(

View File

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

View File

@ -1,17 +1,19 @@
import 'package:flutter/material.dart';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
const double kDesktopRemoteTabBarHeight = 28.0; const double kDesktopRemoteTabBarHeight = 28.0;
const int kMainWindowId = 0;
const String kPeerPlatformWindows = "Windows"; const String kPeerPlatformWindows = "Windows";
const String kPeerPlatformLinux = "Linux"; const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS"; const String kPeerPlatformMacOS = "Mac OS";
const String kPeerPlatformAndroid = "Android"; 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 kAppTypeMain = "main";
const String kAppTypeConnectionManager = "cm";
const String kAppTypeDesktopRemote = "remote"; const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer"; const String kAppTypeDesktopFileTransfer = "file transfer";
const String kAppTypeDesktopPortForward = "port forward"; const String kAppTypeDesktopPortForward = "port forward";
@ -24,7 +26,6 @@ const String kWindowEventShow = "show";
const String kWindowConnect = "connect"; const String kWindowConnect = "connect";
const String kUniLinksPrefix = "rustdesk://"; const String kUniLinksPrefix = "rustdesk://";
const String kActionNewConnection = "connection/new/";
const String kTabLabelHomePage = "Home"; const String kTabLabelHomePage = "Home";
const String kTabLabelSettingPage = "Settings"; 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/material.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.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:get/get.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@ -64,6 +65,8 @@ class _ConnectionPageState extends State<ConnectionPage>
}); });
_idFocusNode.addListener(() { _idFocusNode.addListener(() {
_idInputFocused.value = _idFocusNode.hasFocus; _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); 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 @override
void onWindowClose() { void onWindowClose() {
super.onWindowClose(); super.onWindowClose();

View File

@ -33,6 +33,7 @@ const double _kContentFontSize = 15;
const Color _accentColor = MyTheme.accent; const Color _accentColor = MyTheme.accent;
const String _kSettingPageControllerTag = 'settingPageController'; const String _kSettingPageControllerTag = 'settingPageController';
const String _kSettingPageIndexTag = 'settingPageIndex'; const String _kSettingPageIndexTag = 'settingPageIndex';
const int _kPageCount = 6;
class _TabInfo { class _TabInfo {
late final String label; late final String label;
@ -51,7 +52,7 @@ class DesktopSettingPage extends StatefulWidget {
State<DesktopSettingPage> createState() => _DesktopSettingPageState(); State<DesktopSettingPage> createState() => _DesktopSettingPageState();
static void switch2page(int page) { static void switch2page(int page) {
if (page >= 5) return; if (page >= _kPageCount) return;
try { try {
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) { if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
DesktopTabPage.onAddSetting(initialPage: page); DesktopTabPage.onAddSetting(initialPage: page);
@ -75,6 +76,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_TabInfo('Security', Icons.enhanced_encryption_outlined, _TabInfo('Security', Icons.enhanced_encryption_outlined,
Icons.enhanced_encryption), Icons.enhanced_encryption),
_TabInfo('Network', Icons.link_outlined, Icons.link), _TabInfo('Network', Icons.link_outlined, Icons.link),
_TabInfo('Display', Icons.desktop_windows_outlined, Icons.desktop_windows),
_TabInfo('Account', Icons.person_outline, Icons.person), _TabInfo('Account', Icons.person_outline, Icons.person),
_TabInfo('About', Icons.info_outline, Icons.info) _TabInfo('About', Icons.info_outline, Icons.info)
]; ];
@ -88,7 +90,8 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
@override @override
void initState() { void initState() {
super.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); Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
controller = PageController(initialPage: widget.initialPage); controller = PageController(initialPage: widget.initialPage);
Get.put<PageController>(controller, tag: _kSettingPageControllerTag); Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
@ -130,6 +133,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_General(), _General(),
_Safety(), _Safety(),
_Network(), _Network(),
_Display(),
_Account(), _Account(),
_About(), _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 { class _Account extends StatefulWidget {
const _Account({Key? key}) : super(key: key); const _Account({Key? key}) : super(key: key);
@ -1113,10 +1358,12 @@ class _AboutState extends State<_About> {
const SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Text('${translate('Version')}: $version') SelectionArea(
.marginSymmetric(vertical: 4.0), child: Text('${translate('Version')}: $version')
Text('${translate('Build Date')}: $buildDate') .marginSymmetric(vertical: 4.0)),
.marginSymmetric(vertical: 4.0), SelectionArea(
child: Text('${translate('Build Date')}: $buildDate')
.marginSymmetric(vertical: 4.0)),
InkWell( InkWell(
onTap: () { onTap: () {
launchUrlString('https://rustdesk.com/privacy'); launchUrlString('https://rustdesk.com/privacy');
@ -1137,6 +1384,7 @@ class _AboutState extends State<_About> {
decoration: const BoxDecoration(color: Color(0xFF2c8cff)), decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
padding: padding:
const EdgeInsets.symmetric(vertical: 24, horizontal: 8), const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
child: SelectionArea(
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@ -1157,7 +1405,7 @@ class _AboutState extends State<_About> {
), ),
), ),
], ],
), )),
).marginSymmetric(vertical: 4.0) ).marginSymmetric(vertical: 4.0)
], ],
).marginOnly(left: _kContentHMargin) ).marginOnly(left: _kContentHMargin)

View File

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

View File

@ -3,7 +3,6 @@ import 'dart:io';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_custom_cursor/cursor_manager.dart' import 'package:flutter_custom_cursor/cursor_manager.dart'
@ -279,23 +278,12 @@ class _RemotePageState extends State<RemotePage>
} }
} }
Widget getBodyForDesktop(BuildContext context) { Widget _buildRawPointerMouseRegion(
var paints = <Widget>[ Widget child,
MouseRegion(onEnter: (evt) { PointerEnterEventListener? onEnter,
bind.hostStopSystemKeyPropagate(stopped: false); PointerExitEventListener? onExit,
}, onExit: (evt) { ) {
bind.hostStopSystemKeyPropagate(stopped: true); return RawPointerMouseRegion(
}, child: LayoutBuilder(builder: (context, constraints) {
Future.delayed(Duration.zero, () {
Provider.of<CanvasModel>(context, listen: false).updateViewStyle();
});
return ImagePaint(
id: widget.id,
zoomCursor: _zoomCursor,
cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved,
listenerBuilder: (child) => RawPointerMouseRegion(
onEnter: enterView, onEnter: enterView,
onExit: leaveView, onExit: leaveView,
onPointerDown: (event) { onPointerDown: (event) {
@ -315,7 +303,27 @@ class _RemotePageState extends State<RemotePage>
}, },
inputModel: _ffi.inputModel, inputModel: _ffi.inputModel,
child: child, child: child,
), );
}
Widget getBodyForDesktop(BuildContext context) {
var paints = <Widget>[
MouseRegion(onEnter: (evt) {
bind.hostStopSystemKeyPropagate(stopped: false);
}, onExit: (evt) {
bind.hostStopSystemKeyPropagate(stopped: true);
}, child: LayoutBuilder(builder: (context, constraints) {
Future.delayed(Duration.zero, () {
Provider.of<CanvasModel>(context, listen: false).updateViewStyle();
});
return ImagePaint(
id: widget.id,
zoomCursor: _zoomCursor,
cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved,
listenerBuilder: (child) =>
_buildRawPointerMouseRegion(child, enterView, leaveView),
); );
})) }))
]; ];
@ -328,7 +336,14 @@ class _RemotePageState extends State<RemotePage>
zoomCursor: _zoomCursor, zoomCursor: _zoomCursor,
)))); ))));
} }
paints.add(QualityMonitor(_ffi.qualityMonitorModel)); paints.add(
Positioned(
top: 10,
right: 10,
child: _buildRawPointerMouseRegion(
QualityMonitor(_ffi.qualityMonitorModel), null, null),
),
);
paints.add(RemoteMenubar( paints.add(RemoteMenubar(
id: widget.id, id: widget.id,
ffi: _ffi, ffi: _ffi,
@ -347,10 +362,10 @@ class _RemotePageState extends State<RemotePage>
class ImagePaint extends StatefulWidget { class ImagePaint extends StatefulWidget {
final String id; final String id;
final Rx<bool> zoomCursor; final RxBool zoomCursor;
final Rx<bool> cursorOverImage; final RxBool cursorOverImage;
final Rx<bool> keyboardEnabled; final RxBool keyboardEnabled;
final Rx<bool> remoteCursorMoved; final RxBool remoteCursorMoved;
final Widget Function(Widget)? listenerBuilder; final Widget Function(Widget)? listenerBuilder;
ImagePaint( ImagePaint(
@ -373,10 +388,10 @@ class _ImagePaintState extends State<ImagePaint> {
final ScrollController _vertical = ScrollController(); final ScrollController _vertical = ScrollController();
String get id => widget.id; String get id => widget.id;
Rx<bool> get zoomCursor => widget.zoomCursor; RxBool get zoomCursor => widget.zoomCursor;
Rx<bool> get cursorOverImage => widget.cursorOverImage; RxBool get cursorOverImage => widget.cursorOverImage;
Rx<bool> get keyboardEnabled => widget.keyboardEnabled; RxBool get keyboardEnabled => widget.keyboardEnabled;
Rx<bool> get remoteCursorMoved => widget.remoteCursorMoved; RxBool get remoteCursorMoved => widget.remoteCursorMoved;
Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder; Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder;
@override @override
@ -385,7 +400,28 @@ class _ImagePaintState extends State<ImagePaint> {
var c = Provider.of<CanvasModel>(context); var c = Provider.of<CanvasModel>(context);
final s = c.scale; final s = c.scale;
mouseRegion({child}) => Obx(() => MouseRegion( 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 cursor: cursorOverImage.isTrue
? c.cursorEmbedded ? c.cursorEmbedded
? SystemMouseCursors.none ? SystemMouseCursors.none
@ -399,13 +435,15 @@ class _ImagePaintState extends State<ImagePaint> {
_lastRemoteCursorMoved = false; _lastRemoteCursorMoved = false;
_firstEnterImage.value = true; _firstEnterImage.value = true;
} }
return _buildCustomCursor(context, s); return _buildCustomCursor(
context, getCursorScale());
} }
}()) }())
: _buildDisabledCursor(context, s) : _buildDisabledCursor(context, getCursorScale())
: MouseCursor.defer, : MouseCursor.defer,
onHover: (evt) {}, onHover: (evt) {},
child: child)); child: child);
});
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) { if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
final imageWidth = c.getDisplayWidth() * s; final imageWidth = c.getDisplayWidth() * s;
@ -451,7 +489,7 @@ class _ImagePaintState extends State<ImagePaint> {
if (cache == null) { if (cache == null) {
return MouseCursor.defer; return MouseCursor.defer;
} else { } else {
final key = cache.updateGetKey(scale, zoomCursor.value); final key = cache.updateGetKey(scale);
if (!cursor.cachedKeys.contains(key)) { if (!cursor.cachedKeys.contains(key)) {
debugPrint("Register custom cursor with key $key"); debugPrint("Register custom cursor with key $key");
// [Safety] // [Safety]
@ -617,7 +655,8 @@ class CursorPaint extends StatelessWidget {
double x = (m.x - hotx) * c.scale + cx; double x = (m.x - hotx) * c.scale + cx;
double y = (m.y - hoty) * c.scale + cy; double y = (m.y - hoty) * c.scale + cy;
double scale = 1.0; double scale = 1.0;
if (zoomCursor.isTrue) { final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal;
if (zoomCursor.value || isViewOriginal) {
x = m.x - hotx + cx / c.scale; x = m.x - hotx + cx / c.scale;
y = m.y - hoty + cy / c.scale; y = m.y - hoty + cy / c.scale;
scale = c.scale; scale = c.scale;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,23 @@
import 'dart:io';
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide TabBarTheme; import 'package:flutter/material.dart' hide TabBarTheme;
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/main.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/platform_model.dart';
import 'package:flutter_hbb/models/state_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.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:scroll_pos/scroll_pos.dart'; import 'package:scroll_pos/scroll_pos.dart';
import 'package:window_manager/window_manager.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'; import '../../utils/multi_window_manager.dart';
@ -527,7 +527,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
void onWindowClose() async { void onWindowClose() async {
// hide window on close // hide window on close
if (widget.isMainWindow) { 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, // `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. // flutter closes the application on macOS. We should ensure the post-run logic has ran successfully.
// e.g.: saving window position. // e.g.: saving window position.
@ -765,7 +767,7 @@ class _ListView extends StatelessWidget {
tabBuilder: tabBuilder, tabBuilder: tabBuilder,
tabMenuBuilder: tabMenuBuilder, tabMenuBuilder: tabMenuBuilder,
maxLabelWidth: maxLabelWidth, maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor: selectedTabBackgroundColor, selectedTabBackgroundColor: selectedTabBackgroundColor ?? MyTheme.tabbar(context).selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor, unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
); );
}).toList())); }).toList()));
@ -910,7 +912,7 @@ class _TabState extends State<_Tab> with RestorationMixin {
tabSelected: isSelected, tabSelected: isSelected,
onClose: () => widget.onClose(), onClose: () => widget.onClose(),
))) )))
])).paddingSymmetric(horizontal: 10), ])).paddingOnly(left: 10, right: 5),
Offstage( Offstage(
offstage: !showDivider, offstage: !showDivider,
child: VerticalDivider( child: VerticalDivider(
@ -956,7 +958,8 @@ class _CloseButton extends StatelessWidget {
child: Offstage( child: Offstage(
offstage: !visible, offstage: !visible,
child: InkWell( child: InkWell(
customBorder: const RoundedRectangleBorder(), hoverColor: MyTheme.tabbar(context).closeHoverColor,
customBorder: const CircleBorder(),
onTap: () => onClose(), onTap: () => onClose(),
child: Icon( child: Icon(
Icons.close, Icons.close,
@ -966,7 +969,7 @@ class _CloseButton extends StatelessWidget {
: MyTheme.tabbar(context).unSelectedIconColor, : MyTheme.tabbar(context).unSelectedIconColor,
), ),
), ),
)).paddingOnly(left: 5); )).paddingOnly(left: 10);
} }
} }
@ -1055,6 +1058,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
final Color? unSelectedIconColor; final Color? unSelectedIconColor;
final Color? dividerColor; final Color? dividerColor;
final Color? hoverColor; final Color? hoverColor;
final Color? closeHoverColor;
final Color? selectedTabBackgroundColor;
const TabbarTheme( const TabbarTheme(
{required this.selectedTabIconColor, {required this.selectedTabIconColor,
@ -1064,27 +1069,33 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
required this.selectedIconColor, required this.selectedIconColor,
required this.unSelectedIconColor, required this.unSelectedIconColor,
required this.dividerColor, required this.dividerColor,
required this.hoverColor}); required this.hoverColor,
required this.closeHoverColor,
required this.selectedTabBackgroundColor});
static const light = TabbarTheme( static const light = TabbarTheme(
selectedTabIconColor: MyTheme.accent, selectedTabIconColor: MyTheme.accent,
unSelectedTabIconColor: Color.fromARGB(255, 162, 203, 241), unSelectedTabIconColor: Color.fromARGB(255, 162, 203, 241),
selectedTextColor: Color.fromARGB(255, 26, 26, 26), selectedTextColor: Colors.black,
unSelectedTextColor: Color.fromARGB(255, 96, 96, 96), unSelectedTextColor: Color.fromARGB(255, 112, 112, 112),
selectedIconColor: Color.fromARGB(255, 26, 26, 26), selectedIconColor: Color.fromARGB(255, 26, 26, 26),
unSelectedIconColor: Color.fromARGB(255, 96, 96, 96), unSelectedIconColor: Color.fromARGB(255, 96, 96, 96),
dividerColor: Color.fromARGB(255, 238, 238, 238), 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( static const dark = TabbarTheme(
selectedTabIconColor: MyTheme.accent, selectedTabIconColor: MyTheme.accent,
unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98), unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98),
selectedTextColor: Color.fromARGB(255, 255, 255, 255), selectedTextColor: Color.fromARGB(255, 255, 255, 255),
unSelectedTextColor: Color.fromARGB(255, 207, 207, 207), unSelectedTextColor: Color.fromARGB(255, 192, 192, 192),
selectedIconColor: Color.fromARGB(255, 215, 215, 215), selectedIconColor: Color.fromARGB(255, 192, 192, 192),
unSelectedIconColor: Color.fromARGB(255, 255, 255, 255), unSelectedIconColor: Color.fromARGB(255, 255, 255, 255),
dividerColor: Color.fromARGB(255, 64, 64, 64), dividerColor: Color.fromARGB(255, 64, 64, 64),
hoverColor: Colors.black26); hoverColor: Colors.black26,
closeHoverColor: Colors.black,
selectedTabBackgroundColor: Colors.black26);
@override @override
ThemeExtension<TabbarTheme> copyWith({ ThemeExtension<TabbarTheme> copyWith({
@ -1096,6 +1107,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
Color? unSelectedIconColor, Color? unSelectedIconColor,
Color? dividerColor, Color? dividerColor,
Color? hoverColor, Color? hoverColor,
Color? closeHoverColor,
Color? selectedTabBackgroundColor,
}) { }) {
return TabbarTheme( return TabbarTheme(
selectedTabIconColor: selectedTabIconColor ?? this.selectedTabIconColor, selectedTabIconColor: selectedTabIconColor ?? this.selectedTabIconColor,
@ -1107,6 +1120,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
unSelectedIconColor: unSelectedIconColor ?? this.unSelectedIconColor, unSelectedIconColor: unSelectedIconColor ?? this.unSelectedIconColor,
dividerColor: dividerColor ?? this.dividerColor, dividerColor: dividerColor ?? this.dividerColor,
hoverColor: hoverColor ?? this.hoverColor, 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), Color.lerp(unSelectedIconColor, other.unSelectedIconColor, t),
dividerColor: Color.lerp(dividerColor, other.dividerColor, t), dividerColor: Color.lerp(dividerColor, other.dividerColor, t),
hoverColor: Color.lerp(hoverColor, other.hoverColor, 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:convert';
import 'dart:io'; import 'dart:io';
import 'package:bot_toast/bot_toast.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.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/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/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_file_transfer_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_port_forward_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/screen/desktop_remote_screen.dart';
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.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_hbb/utils/multi_window_manager.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:bot_toast/bot_toast.dart';
// import 'package:window_manager/window_manager.dart'; // import 'package:window_manager/window_manager.dart';
@ -108,11 +108,12 @@ Future<void> initEnv(String appType) async {
await initGlobalFFI(); await initGlobalFFI();
// await Firebase.initializeApp(); // await Firebase.initializeApp();
_registerEventHandler(); _registerEventHandler();
// Update the system theme.
updateSystemWindowTheme();
} }
void runMainApp(bool startService) async { void runMainApp(bool startService) async {
// register uni links // register uni links
initUniLinks();
await initEnv(kAppTypeMain); await initEnv(kAppTypeMain);
// trigger connection status updater // trigger connection status updater
await bind.mainCheckConnectStatus(); await bind.mainCheckConnectStatus();
@ -122,23 +123,27 @@ void runMainApp(bool startService) async {
} }
gFFI.userModel.refreshCurrentUser(); gFFI.userModel.refreshCurrentUser();
runApp(App()); runApp(App());
// restore the location of the main window before window hide or show // 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); await restoreWindowPosition(WindowType.Main);
// check the startup argument, if we successfully handle the argument, we keep the main window hidden. // Check the startup argument, if we successfully handle the argument, we keep the main window hidden.
if (checkArguments()) { final handledByUniLinks = await initUniLinks();
final handledByCli = checkArguments();
debugPrint(
"handled by uni links: $handledByUniLinks, handled by cli: $handledByCli");
if (handledByUniLinks || handledByCli) {
windowManager.hide(); windowManager.hide();
} else { } else {
windowManager.show(); windowManager.show();
windowManager.focus(); windowManager.focus();
// move registration of active main window here to prevent async visible check. // Move registration of active main window here to prevent from async visible check.
rustDeskWinManager.registerActiveWindow(kWindowMainId); rustDeskWinManager.registerActiveWindow(kWindowMainId);
} }
// set window option
WindowOptions windowOptions = getHiddenTitleBarWindowOptions();
windowManager.waitUntilReadyToShow(windowOptions, () async {
windowManager.setOpacity(1); windowManager.setOpacity(1);
});
windowManager.setTitle(getWindowName()); windowManager.setTitle(getWindowName());
});
} }
void runMobileApp() async { void runMobileApp() async {
@ -206,7 +211,7 @@ void runMultiWindow(
} }
void runConnectionManagerScreen(bool hide) async { void runConnectionManagerScreen(bool hide) async {
await initEnv(kAppTypeMain); await initEnv(kAppTypeConnectionManager);
_runApp( _runApp(
'', '',
const DesktopServerPage(), const DesktopServerPage(),
@ -223,6 +228,7 @@ void showCmWindow() {
WindowOptions windowOptions = WindowOptions windowOptions =
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize); getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
windowManager.waitUntilReadyToShow(windowOptions, () async { windowManager.waitUntilReadyToShow(windowOptions, () async {
bind.mainHideDocker();
await windowManager.show(); await windowManager.show();
await Future.wait([windowManager.focus(), windowManager.setOpacity(1)]); await Future.wait([windowManager.focus(), windowManager.setOpacity(1)]);
// ensure initial window size to be changed // ensure initial window size to be changed
@ -236,6 +242,7 @@ void hideCmWindow() {
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize); getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
windowManager.setOpacity(0); windowManager.setOpacity(0);
windowManager.waitUntilReadyToShow(windowOptions, () async { windowManager.waitUntilReadyToShow(windowOptions, () async {
bind.mainHideDocker();
await windowManager.hide(); await windowManager.hide();
}); });
} }
@ -325,6 +332,8 @@ class _AppState extends State<App> {
to = ThemeMode.light; to = ThemeMode.light;
} }
Get.changeThemeMode(to); Get.changeThemeMode(to);
// Synchronize the window theme of the system.
updateSystemWindowTheme();
if (desktopType == DesktopType.main) { if (desktopType == DesktopType.main) {
bind.mainChangeTheme(dark: to.toShortString()); bind.mainChangeTheme(dark: to.toShortString());
} }

View File

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

View File

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

View File

@ -310,7 +310,6 @@ class InputModel {
} }
} }
/*
int _signOrZero(num x) { int _signOrZero(num x) {
if (x == 0) { if (x == 0) {
return 0; return 0;
@ -361,7 +360,6 @@ class InputModel {
trackpadScrollDistance = Offset.zero; trackpadScrollDistance = Offset.zero;
} }
*/
void onPointDownImage(PointerDownEvent e) { void onPointDownImage(PointerDownEvent e) {
debugPrint("onPointDownImage"); 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) { void handleMouse(Map<String, dynamic> evt) {
double x = evt['x']; double x = evt['x'];
double y = max(0.0, evt['y']); double y = max(0.0, evt['y']);

View File

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

View File

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

View File

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

View File

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

View File

@ -43,11 +43,14 @@ class RustDeskMultiWindowManager {
Future<dynamic> newRemoteDesktop(String remoteId, Future<dynamic> newRemoteDesktop(String remoteId,
{String? switch_uuid}) async { {String? switch_uuid}) async {
final msg = jsonEncode({ var params = {
"type": WindowType.RemoteDesktop.index, "type": WindowType.RemoteDesktop.index,
"id": remoteId, "id": remoteId,
"switch_uuid": switch_uuid ?? "" };
}); if (switch_uuid != null) {
params['switch_uuid'] = switch_uuid;
}
final msg = jsonEncode(params);
try { try {
final ids = await DesktopMultiWindow.getAllSubWindowIds(); final ids = await DesktopMultiWindow.getAllSubWindowIds();
@ -157,6 +160,24 @@ class RustDeskMultiWindowManager {
return null; 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( void setMethodHandler(
Future<dynamic> Function(MethodCall call, int fromWindowId)? handler) { Future<dynamic> Function(MethodCall call, int fromWindowId)? handler) {
DesktopMultiWindow.setMethodHandler(handler); DesktopMultiWindow.setMethodHandler(handler);
@ -183,8 +204,11 @@ class RustDeskMultiWindowManager {
} }
await WindowController.fromWindowId(wId).setPreventClose(false); await WindowController.fromWindowId(wId).setPreventClose(false);
await WindowController.fromWindowId(wId).close(); await WindowController.fromWindowId(wId).close();
} on Error { } catch (e) {
debugPrint("$e");
return; 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) - FMDB/standard (2.7.5)
- package_info_plus_macos (0.0.1): - package_info_plus_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- path_provider_macos (0.0.1): - path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS - FlutterMacOS
- screen_retriever (0.0.1): - screen_retriever (0.0.1):
- FlutterMacOS - FlutterMacOS
@ -38,7 +39,7 @@ DEPENDENCIES:
- flutter_custom_cursor (from `Flutter/ephemeral/.symlinks/plugins/flutter_custom_cursor/macos`) - flutter_custom_cursor (from `Flutter/ephemeral/.symlinks/plugins/flutter_custom_cursor/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`) - 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`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
- uni_links_desktop (from `Flutter/ephemeral/.symlinks/plugins/uni_links_desktop/macos`) - uni_links_desktop (from `Flutter/ephemeral/.symlinks/plugins/uni_links_desktop/macos`)
@ -64,8 +65,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral :path: Flutter/ephemeral
package_info_plus_macos: package_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
path_provider_macos: path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos
screen_retriever: screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
sqflite: sqflite:
@ -86,14 +87,14 @@ SPEC CHECKSUMS:
desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486 desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486
device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7 device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7
flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7 flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026 uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9 wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195

View File

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

View File

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

View File

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

View File

@ -27,12 +27,16 @@ class MainFlutterWindow: NSWindow {
let windowFrame = self.frame let windowFrame = self.frame
self.contentViewController = flutterViewController self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true) self.setFrame(windowFrame, display: true)
// register self method handler
let registrar = flutterViewController.registrar(forPlugin: "RustDeskPlugin")
setMethodHandler(registrar: registrar)
RegisterGeneratedPlugins(registry: flutterViewController) RegisterGeneratedPlugins(registry: flutterViewController)
FlutterMultiWindowPlugin.setOnWindowCreatedCallback { controller in FlutterMultiWindowPlugin.setOnWindowCreatedCallback { controller in
// Register the plugin which you want access from other isolate. // Register the plugin which you want access from other isolate.
// DesktopLifecyclePlugin.register(with: controller.registrar(forPlugin: "DesktopLifecyclePlugin")) // DesktopLifecyclePlugin.register(with: controller.registrar(forPlugin: "DesktopLifecyclePlugin"))
self.setMethodHandler(registrar: controller.registrar(forPlugin: "RustDeskPlugin"))
DesktopDropPlugin.register(with: controller.registrar(forPlugin: "DesktopDropPlugin")) DesktopDropPlugin.register(with: controller.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: controller.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: controller.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FlutterCustomCursorPlugin.register(with: controller.registrar(forPlugin: "FlutterCustomCursorPlugin")) FlutterCustomCursorPlugin.register(with: controller.registrar(forPlugin: "FlutterCustomCursorPlugin"))
@ -53,4 +57,33 @@ class MainFlutterWindow: NSWindow {
super.order(place, relativeTo: otherWin) super.order(place, relativeTo: otherWin)
hiddenWindowAtLaunch() 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: desktop_multi_window:
git: git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window url: https://github.com/Kingtous/rustdesk_desktop_multi_window
ref: 057e6eb1bc7dcbcf9dafd1384274a611e4fe7124 ref: bc8604a88e52b2b6e64d2661ae49a71450a47af8
freezed_annotation: ^2.0.3 freezed_annotation: ^2.0.3
flutter_custom_cursor: ^0.0.2 flutter_custom_cursor: ^0.0.4
window_size: window_size:
git: git:
url: https://github.com/google/flutter-desktop-embedding.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)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NetworkType { pub enum NetworkType {
Direct, Direct,
@ -192,26 +212,29 @@ pub struct PeerConfig {
deserialize_with = "PeerConfig::deserialize_image_quality" deserialize_with = "PeerConfig::deserialize_image_quality"
)] )]
pub image_quality: String, 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>, pub custom_image_quality: Vec<i32>,
#[serde(default)] #[serde(flatten)]
pub show_remote_cursor: bool, pub show_remote_cursor: ShowRemoteCursor,
#[serde(default)] #[serde(flatten)]
pub lock_after_session_end: bool, pub lock_after_session_end: LockAfterSessionEnd,
#[serde(default)] #[serde(flatten)]
pub privacy_mode: bool, pub privacy_mode: PrivacyMode,
#[serde(default)] #[serde(default)]
pub port_forwards: Vec<(i32, String, i32)>, pub port_forwards: Vec<(i32, String, i32)>,
#[serde(default)] #[serde(default)]
pub direct_failures: i32, pub direct_failures: i32,
#[serde(default)] #[serde(flatten)]
pub disable_audio: bool, pub disable_audio: DisableAudio,
#[serde(default)] #[serde(flatten)]
pub disable_clipboard: bool, pub disable_clipboard: DisableClipboard,
#[serde(default)] #[serde(flatten)]
pub enable_file_transfer: bool, pub enable_file_transfer: EnableFileTransfer,
#[serde(default)] #[serde(flatten)]
pub show_quality_monitor: bool, pub show_quality_monitor: ShowQualityMonitor,
#[serde(default)] #[serde(default)]
pub keyboard_mode: String, pub keyboard_mode: String,
@ -961,31 +984,88 @@ impl PeerConfig {
serde_field_string!( serde_field_string!(
default_view_style, default_view_style,
deserialize_view_style, deserialize_view_style,
"original".to_owned() UserDefaultConfig::load().get("view_style")
); );
serde_field_string!( serde_field_string!(
default_scroll_style, default_scroll_style,
deserialize_scroll_style, deserialize_scroll_style,
"scrollauto".to_owned() UserDefaultConfig::load().get("scroll_style")
); );
serde_field_string!( serde_field_string!(
default_image_quality, default_image_quality,
deserialize_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> fn deserialize_options<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
where where
D: de::Deserializer<'de>, D: de::Deserializer<'de>,
{ {
let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?; let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?;
if !mp.contains_key("codec-preference") { let mut key = "codec-preference";
mp.insert("codec-preference".to_owned(), "auto".to_owned()); 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) 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)] #[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct LocalConfig { pub struct LocalConfig {
#[serde(default)] #[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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

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

View File

@ -76,7 +76,7 @@ pub fn is_cursor_embedded() -> bool {
if is_x11() { if is_x11() {
x11::IS_CURSOR_EMBEDDED x11::IS_CURSOR_EMBEDDED
} else { } 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 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! { lazy_static::lazy_static! {
static ref MAP_ERR: RwLock<Option<fn(err: String)-> io::Error>> = Default::default(); 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) { pub fn set_map_err(f: fn(err: String) -> io::Error) {
*MAP_ERR.write().unwrap() = Some(f); *MAP_ERR.write().unwrap() = Some(f);
} }
@ -74,7 +95,7 @@ impl Display {
} }
pub fn all() -> io::Result<Vec<Display>> { pub fn all() -> io::Result<Vec<Display>> {
Ok(pipewire::get_capturables(true) Ok(pipewire::get_capturables(is_cursor_embedded())
.map_err(map_err)? .map_err(map_err)?
.drain(..) .drain(..)
.map(|x| Display(x)) .map(|x| Display(x))

View File

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

View File

@ -386,8 +386,8 @@ fn streams_from_response(response: OrgFreedesktopPortalRequestResponse) -> Vec<P
info.size.1 = v[1] as _; info.size.1 = v[1] as _;
} }
} }
let v = attributes if let Some(pos) = attributes.get("position") {
.get("position")? let v = pos
.as_iter()? .as_iter()?
.filter_map(|v| { .filter_map(|v| {
Some( Some(
@ -403,6 +403,7 @@ fn streams_from_response(response: OrgFreedesktopPortalRequestResponse) -> Vec<P
info.position.1 = v[1] as _; info.position.1 = v[1] as _;
} }
} }
}
Some(info) Some(info)
}) })
.collect::<Vec<PwStreamInfo>>(), .collect::<Vec<PwStreamInfo>>(),
@ -415,6 +416,12 @@ static mut INIT: bool = false;
const RESTORE_TOKEN: &str = "restore_token"; const RESTORE_TOKEN: &str = "restore_token";
const RESTORE_TOKEN_CONF_KEY: &str = "wayland-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 // mostly inspired by https://gitlab.gnome.org/snippets/19
fn request_screen_cast( fn request_screen_cast(
capture_cursor: bool, capture_cursor: bool,
@ -473,7 +480,17 @@ fn request_screen_cast(
args.insert("multiple".into(), Variant(Box::new(true))); args.insert("multiple".into(), Variant(Box::new(true)));
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32))); 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")); let plasma = std::env::var("DESKTOP_SESSION").map_or(false, |s| s.contains("plasma"));
if plasma && capture_cursor { if plasma && capture_cursor {
// Warn the user if capturing the cursor is tried on kde as this can crash // 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! \ desktop, see https://bugs.kde.org/show_bug.cgi?id=435042 for details! \
You have been warned."); You have been warned.");
} }
if cursor_mode > 0 {
args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode))); args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode)));
}
let session: dbus::Path = r let session: dbus::Path = r
.results .results
.get("session_handle") .get("session_handle")

View File

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

View File

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

View File

@ -52,7 +52,7 @@ pub fn global_init() -> bool {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
if !*IS_X11 { if !*IS_X11 {
crate::server::wayland::set_wayland_scrap_map_err(); crate::server::wayland::init();
} }
} }
true 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 { pub fn username() -> String {
// fix bug of whoami // fix bug of whoami
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -459,6 +460,14 @@ pub fn username() -> String {
return DEVICE_NAME.lock().unwrap().clone(); 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] #[inline]
pub fn check_port<T: std::string::ToString>(host: T, port: i32) -> String { pub fn check_port<T: std::string::ToString>(host: T, port: i32) -> String {
hbb_common::socket_client::check_port(host, port) 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() { if !s0.is_empty() {
let s = crate::increase_port(&s0, -2); let s = crate::increase_port(&s0, -2);
if s == s0 { if s == s0 {
format!("http://{}:{}", s, config::RENDEZVOUS_PORT - 2); return format!("http://{}:{}", s, config::RENDEZVOUS_PORT - 2);
} else { } else {
format!("http://{}", s); return format!("http://{}", s);
} }
} }
"https://admin.rustdesk.com".to_owned() "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 /// 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()); return core_main_invoke_new_connection(std::env::args());
} }
let click_setup = cfg!(windows) && args.is_empty() && crate::common::is_setup(&arg_exe); 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 { if click_setup {
args.push("--install".to_owned()); args.push("--install".to_owned());
flutter_args.push("--install".to_string()); flutter_args.push("--install".to_string());
@ -70,6 +67,14 @@ pub fn core_main() -> Option<Vec<String>> {
println!("{}", crate::VERSION); println!("{}", crate::VERSION);
return None; 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)] #[cfg(debug_assertions)]
{ {
use hbb_common::env_logger::*; use hbb_common::env_logger::*;
@ -195,7 +200,7 @@ pub fn core_main() -> Option<Vec<String>> {
{ {
std::thread::spawn(move || crate::start_server(true)); std::thread::spawn(move || crate::start_server(true));
crate::platform::macos::hide_dock(); crate::platform::macos::hide_dock();
crate::tray::make_tray(); crate::ui::macos::make_tray();
return None; return None;
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -244,8 +249,6 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
crate::flutter::connection_manager::start_listen_ipc_thread(); crate::flutter::connection_manager::start_listen_ipc_thread();
crate::ui_interface::start_option_status_sync(); crate::ui_interface::start_option_status_sync();
#[cfg(target_os = "macos")]
crate::platform::macos::hide_dock();
} }
} }
//_async_logger_holder.map(|x| x.flush()); //_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>> { fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
args.position(|element| { args.position(|element| {
return element == "--connect"; return element == "--connect";
}) })?;
.unwrap();
let peer_id = args.next().unwrap_or("".to_string()); let peer_id = args.next().unwrap_or("".to_string());
if peer_id.is_empty() { if peer_id.is_empty() {
eprintln!("please provide a valid peer id"); 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(); 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)); let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
let params = vec![switch_uuid].join("&"); param_array.push(switch_uuid);
}
let params = param_array.join("&");
let params_flag = if params.is_empty() { "" } else { "?" }; let params_flag = if params.is_empty() { "" } else { "?" };
#[allow(unused)] #[allow(unused)]
let uni_links = format!( 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()) }; return if res { None } else { Some(Vec::new()) };
} }
#[cfg(target_os = "macos")] #[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::{ use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char, thread};
collections::HashMap, use std::str::FromStr;
ffi::{CStr, CString},
os::raw::c_char,
};
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer}; use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
use serde_json::json; use serde_json::json;
use crate::common::is_keyboard_mode_supported;
use hbb_common::message_proto::KeyboardMode;
use hbb_common::ResultType;
use hbb_common::{ use hbb_common::{
config::{self, LocalConfig, PeerConfig, ONLINE}, config::{self, LocalConfig, ONLINE, PeerConfig},
fs, log, 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::{ use crate::{
client::file_trait::FileManager, client::file_trait::FileManager,
common::make_fd_to_json, common::make_fd_to_json,
flutter::{session_add, session_start_}, 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) { fn initialize(app_dir: &str) {
*config::APP_DIR.write().unwrap() = app_dir.to_owned(); *config::APP_DIR.write().unwrap() = app_dir.to_owned();
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@ -523,6 +520,10 @@ pub fn main_get_sound_inputs() -> Vec<String> {
vec![String::from("")] vec![String::from("")]
} }
pub fn main_get_hostname() -> SyncReturn<String> {
SyncReturn(crate::common::hostname())
}
pub fn main_change_id(new_id: String) { pub fn main_change_id(new_id: String) {
change_id(new_id) change_id(new_id)
} }
@ -787,6 +788,14 @@ pub fn main_default_video_save_directory() -> String {
default_video_save_directory() 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( pub fn session_add_port_forward(
id: String, id: String,
local_port: i32, local_port: i32,
@ -1237,16 +1246,38 @@ pub fn main_is_login_wayland() -> SyncReturn<bool> {
SyncReturn(is_login_wayland()) 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")] #[cfg(target_os = "android")]
pub mod server_side { pub mod server_side {
use hbb_common::log;
use jni::{ use jni::{
JNIEnv,
objects::{JClass, JString}, objects::{JClass, JString},
sys::jstring, sys::jstring,
JNIEnv,
}; };
use hbb_common::log;
use crate::start_server; use crate::start_server;
#[no_mangle] #[no_mangle]

View File

@ -16,10 +16,10 @@ use hbb_common::{
config::{self, Config, Config2}, config::{self, Config, Config2},
futures::StreamExt as _, futures::StreamExt as _,
futures_util::sink::SinkExt, futures_util::sink::SinkExt,
log, password_security as password, timeout, tokio, log, password_security as password, ResultType, timeout,
tokio,
tokio::io::{AsyncRead, AsyncWrite}, tokio::io::{AsyncRead, AsyncWrite},
tokio_util::codec::Framed, tokio_util::codec::Framed,
ResultType,
}; };
use crate::rendezvous_mediator::RendezvousMediator; use crate::rendezvous_mediator::RendezvousMediator;
@ -210,6 +210,7 @@ pub enum Data {
DataPortableService(DataPortableService), DataPortableService(DataPortableService),
SwitchSidesRequest(String), SwitchSidesRequest(String),
SwitchSidesBack, SwitchSidesBack,
UrlLink(String)
} }
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
@ -832,3 +833,9 @@ pub async fn test_rendezvous_server() -> ResultType<()> {
c.send(&Data::TestRendezvousServer).await?; c.send(&Data::TestRendezvousServer).await?;
Ok(()) 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", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Silenciar"), ("Mute", "Silenciar"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Entrada d'àudio"), ("Audio Input", "Entrada d'àudio"),
("Enhancements", "Millores"), ("Enhancements", "Millores"),
("Hardware Codec", "Còdec de hardware"), ("Hardware Codec", "Còdec de hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Sortir"), ("Logout", "Sortir"),
("Tags", ""), ("Tags", ""),
("Search ID", "Cerca ID"), ("Search ID", "Cerca ID"),
("Current Wayland display server is not supported", "El servidor de visualització actual de Wayland no és compatible"),
("whitelist_sep", ""), ("whitelist_sep", ""),
("Add ID", "Afegir ID"), ("Add ID", "Afegir ID"),
("Add Tag", "Afegir tag"), ("Add Tag", "Afegir tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "静音"), ("Mute", "静音"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "音频输入"), ("Audio Input", "音频输入"),
("Enhancements", "增强功能"), ("Enhancements", "增强功能"),
("Hardware Codec", "硬件编解码"), ("Hardware Codec", "硬件编解码"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "登出"), ("Logout", "登出"),
("Tags", "标签"), ("Tags", "标签"),
("Search ID", "查找ID"), ("Search ID", "查找ID"),
("Current Wayland display server is not supported", "不支持 Wayland 显示服务器"),
("whitelist_sep", "可以使用逗号,分号,空格或者换行符作为分隔符"), ("whitelist_sep", "可以使用逗号,分号,空格或者换行符作为分隔符"),
("Add ID", "增加ID"), ("Add ID", "增加ID"),
("Add Tag", "增加标签"), ("Add Tag", "增加标签"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", "反转访问方向"), ("Switch Sides", "反转访问方向"),
("Please confirm if you want to share your desktop?", "请确认要让对方访问你的桌面?"), ("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", "帧率"),
("Auto", "自动"),
("Other Default Options", "其它默认选项"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Ztlumit"), ("Mute", "Ztlumit"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Vstup zvuku"), ("Audio Input", "Vstup zvuku"),
("Enhancements", ""), ("Enhancements", ""),
("Hardware Codec", ""), ("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Odhlásit se"), ("Logout", "Odhlásit se"),
("Tags", "Štítky"), ("Tags", "Štítky"),
("Search ID", "Hledat identifikátor"), ("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"), ("whitelist_sep", "Odělováno čárkou, středníkem, mezerou nebo koncem řádku"),
("Add ID", "Přidat identifikátor"), ("Add ID", "Přidat identifikátor"),
("Add Tag", "Přidat štítek"), ("Add Tag", "Přidat štítek"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Sluk for mikrofonen"), ("Mute", "Sluk for mikrofonen"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Lydindgang"), ("Audio Input", "Lydindgang"),
("Enhancements", ""), ("Enhancements", ""),
("Hardware Codec", ""), ("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "logger af"), ("Logout", "logger af"),
("Tags", "Nøgleord"), ("Tags", "Nøgleord"),
("Search ID", "Søg ID"), ("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"), ("whitelist_sep", "Adskilt af komma, semikolon, rum eller linjepaus"),
("Add ID", "Tilføj ID"), ("Add ID", "Tilføj ID"),
("Add Tag", "Tilføj nøgleord"), ("Add Tag", "Tilføj nøgleord"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].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!"), ("Slogan_tip", "Mit Herzblut programmiert - in einer Welt, die im Chaos versinkt!"),
("Privacy Statement", "Datenschutz"), ("Privacy Statement", "Datenschutz"),
("Mute", "Stummschalten"), ("Mute", "Stummschalten"),
("Build Date", "Erstelldatum"),
("Version", "Version"),
("Home", "Startseite"),
("Audio Input", "Audioeingang"), ("Audio Input", "Audioeingang"),
("Enhancements", "Verbesserungen"), ("Enhancements", "Verbesserungen"),
("Hardware Codec", "Hardware-Codec"), ("Hardware Codec", "Hardware-Codec"),
@ -197,7 +200,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Warning", "Warnung"), ("Warning", "Warnung"),
("Login screen using Wayland is not supported", "Anmeldebildschirm mit Wayland wird nicht unterstützt."), ("Login screen using Wayland is not supported", "Anmeldebildschirm mit Wayland wird nicht unterstützt."),
("Reboot required", "Neustart erforderlich"), ("Reboot required", "Neustart erforderlich"),
("Unsupported display server ", "Nicht unterstützter Display-Server"), ("Unsupported display server ", "Nicht unterstützter Anzeigeserver"),
("x11 expected", "X11 erwartet"), ("x11 expected", "X11 erwartet"),
("Port", "Port"), ("Port", "Port"),
("Settings", "Einstellungen"), ("Settings", "Einstellungen"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Abmelden"), ("Logout", "Abmelden"),
("Tags", "Schlagworte"), ("Tags", "Schlagworte"),
("Search ID", "Suche ID"), ("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"), ("whitelist_sep", "Getrennt durch Komma, Semikolon, Leerzeichen oder Zeilenumbruch"),
("Add ID", "ID hinzufügen"), ("Add ID", "ID hinzufügen"),
("Add Tag", "Stichwort 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"), ("Remote ID", "Entfernte ID"),
("Paste", "Einfügen"), ("Paste", "Einfügen"),
("Paste here?", "Hier 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"), ("Download new version", "Neue Version herunterladen"),
("Touch mode", "Touch-Modus"), ("Touch mode", "Touch-Modus"),
("Mouse mode", "Mausmodus"), ("Mouse mode", "Mausmodus"),
@ -264,8 +266,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Note", "Hinweis"), ("Note", "Hinweis"),
("Connection", "Verbindung"), ("Connection", "Verbindung"),
("Share Screen", "Bildschirm freigeben"), ("Share Screen", "Bildschirm freigeben"),
("CLOSE", "DEAKTIV."), ("CLOSE", "SCHLIEẞEN"),
("OPEN", "AKTIVIER."), ("OPEN", "ÖFFNEN"),
("Chat", "Chat"), ("Chat", "Chat"),
("Total", "Gesamt"), ("Total", "Gesamt"),
("items", "Einträge"), ("items", "Einträge"),
@ -325,7 +327,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Mobile Actions", "Mobile Aktionen"), ("Mobile Actions", "Mobile Aktionen"),
("Select Monitor", "Bildschirm auswählen"), ("Select Monitor", "Bildschirm auswählen"),
("Control Actions", "Aktionen"), ("Control Actions", "Aktionen"),
("Display Settings", "Bildschirmeinstellungen"), ("Display Settings", "Anzeigeeinstellungen"),
("Ratio", "Verhältnis"), ("Ratio", "Verhältnis"),
("Image Quality", "Bildqualität"), ("Image Quality", "Bildqualität"),
("Scroll Style", "Scroll-Stil"), ("Scroll Style", "Scroll-Stil"),
@ -336,7 +338,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Secure Connection", "Sichere Verbindung"), ("Secure Connection", "Sichere Verbindung"),
("Insecure Connection", "Unsichere Verbindung"), ("Insecure Connection", "Unsichere Verbindung"),
("Scale original", "Keine Skalierung"), ("Scale original", "Keine Skalierung"),
("Scale adaptive", "Automatische Skalierung"), ("Scale adaptive", "Anpassbare Skalierung"),
("General", "Allgemein"), ("General", "Allgemein"),
("Security", "Sicherheit"), ("Security", "Sicherheit"),
("Theme", "Farbgebung"), ("Theme", "Farbgebung"),
@ -356,7 +358,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clear", "Zurücksetzen"), ("Clear", "Zurücksetzen"),
("Audio Input Device", "Audioeingabegerät"), ("Audio Input Device", "Audioeingabegerät"),
("Deny remote access", "Fernzugriff verbieten"), ("Deny remote access", "Fernzugriff verbieten"),
("Use IP Whitelisting", "IP-Whitelist benutzen"), ("Use IP Whitelisting", "IP-Whitelist verwenden"),
("Network", "Netzwerk"), ("Network", "Netzwerk"),
("Enable RDP", "RDP aktivieren"), ("Enable RDP", "RDP aktivieren"),
("Pin menubar", "Menüleiste anpinnen"), ("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 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."), ("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"), ("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"), ("Show RustDesk", "RustDesk anzeigen"),
("This PC", "Dieser PC"), ("This PC", "Dieser PC"),
("or", "oder"), ("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"), ("Add to Address Book", "Zum Adressbuch hinzufügen"),
("Group", "Gruppe"), ("Group", "Gruppe"),
("Search", "Suchen"), ("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"), ("Local keyboard type", "Lokaler Tastaturtyp"),
("Select local keyboard type", "Lokalen Tastaturtyp auswählen"), ("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."), ("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"), ("Strong", "Stark"),
("Switch Sides", "Seiten wechseln"), ("Switch Sides", "Seiten wechseln"),
("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, ob Sie Ihren Desktop freigeben möchten."), ("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(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Muta"), ("Mute", "Muta"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Aŭdia enigo"), ("Audio Input", "Aŭdia enigo"),
("Enhancements", ""), ("Enhancements", ""),
("Hardware Codec", ""), ("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Malkonekti"), ("Logout", "Malkonekti"),
("Tags", "Etikedi"), ("Tags", "Etikedi"),
("Search ID", "Serĉi ID"), ("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"), ("whitelist_sep", "Vi povas uzi komon, punktokomon, spacon aŭ linsalton kiel apartigilo"),
("Add ID", "Aldoni identigilo"), ("Add ID", "Aldoni identigilo"),
("Add Tag", "Aldoni etikedo"), ("Add Tag", "Aldoni etikedo"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].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!"), ("Slogan_tip", "Hecho con corazón en este mundo caótico!"),
("Privacy Statement", "Declaración de privacidad"), ("Privacy Statement", "Declaración de privacidad"),
("Mute", "Silenciar"), ("Mute", "Silenciar"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Entrada de audio"), ("Audio Input", "Entrada de audio"),
("Enhancements", "Mejoras"), ("Enhancements", "Mejoras"),
("Hardware Codec", "Códec de hardware"), ("Hardware Codec", "Códec de hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Salir"), ("Logout", "Salir"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Buscar ID"), ("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"), ("whitelist_sep", "Separados por coma, punto y coma, espacio o nueva línea"),
("Add ID", "Agregar ID"), ("Add ID", "Agregar ID"),
("Add Tag", "Agregar tag"), ("Add Tag", "Agregar tag"),
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Fuerte"), ("Strong", "Fuerte"),
("Switch Sides", "Intercambiar lados"), ("Switch Sides", "Intercambiar lados"),
("Please confirm if you want to share your desktop?", "Por favor, confirma si quieres compartir tu escritorio"), ("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(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "بستن صدا"), ("Mute", "بستن صدا"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "ورودی صدا"), ("Audio Input", "ورودی صدا"),
("Enhancements", "بهبودها"), ("Enhancements", "بهبودها"),
("Hardware Codec", "کدک سخت افزاری"), ("Hardware Codec", "کدک سخت افزاری"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "خروج"), ("Logout", "خروج"),
("Tags", "برچسب ها"), ("Tags", "برچسب ها"),
("Search ID", "جستجوی شناسه"), ("Search ID", "جستجوی شناسه"),
("Current Wayland display server is not supported", "پشتیبانی نمی شود Wayland سرور نمایش فعلی"),
("whitelist_sep", "با کاما، نقطه ویرگول، فاصله یا خط جدید از هم جدا می شوند"), ("whitelist_sep", "با کاما، نقطه ویرگول، فاصله یا خط جدید از هم جدا می شوند"),
("Add ID", "افزودن شناسه"), ("Add ID", "افزودن شناسه"),
("Add Tag", "افزودن برچسب"), ("Add Tag", "افزودن برچسب"),
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "قوی"), ("Strong", "قوی"),
("Switch Sides", "طرفین را عوض کنید"), ("Switch Sides", "طرفین را عوض کنید"),
("Please confirm if you want to share your desktop?", "لطفاً تأیید کنید که آیا می خواهید دسکتاپ خود را به اشتراک بگذارید؟"), ("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(); ].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!"), ("Slogan_tip", "Fait avec cœur dans ce monde chaotique!"),
("Privacy Statement", "Déclaration de confidentialité"), ("Privacy Statement", "Déclaration de confidentialité"),
("Mute", "Muet"), ("Mute", "Muet"),
("Build Date", "Date de compilation"),
("Version", "Version"),
("Home", "Accueil"),
("Audio Input", "Entrée audio"), ("Audio Input", "Entrée audio"),
("Enhancements", "Améliorations"), ("Enhancements", "Améliorations"),
("Hardware Codec", "Transcodage matériel"), ("Hardware Codec", "Transcodage matériel"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Déconnexion"), ("Logout", "Déconnexion"),
("Tags", "Étiqueter"), ("Tags", "Étiqueter"),
("Search ID", "Rechercher un ID"), ("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"), ("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 ID", "Ajouter un ID"),
("Add Tag", "Ajouter une balise"), ("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"), ("Switch Sides", "Inverser la prise de contrôle"),
("Please confirm if you want to share your desktop?", "Veuillez confirmer le partager de votre bureau ?"), ("Please confirm if you want to share your desktop?", "Veuillez confirmer le partager de votre bureau ?"),
("Closed as expected", ""), ("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

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

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Némítás"), ("Mute", "Némítás"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Hangátvitel"), ("Audio Input", "Hangátvitel"),
("Enhancements", "Fejlesztések"), ("Enhancements", "Fejlesztések"),
("Hardware Codec", "Hardware kodek"), ("Hardware Codec", "Hardware kodek"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Kilépés"), ("Logout", "Kilépés"),
("Tags", "Tagok"), ("Tags", "Tagok"),
("Search ID", "Azonosító keresése..."), ("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"), ("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 ID", "Azonosító hozzáadása"),
("Add Tag", "Címke 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", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", "Pernyataan Privasi"), ("Privacy Statement", "Pernyataan Privasi"),
("Mute", "Bisukan"), ("Mute", "Bisukan"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Masukkan Audio"), ("Audio Input", "Masukkan Audio"),
("Enhancements", "Peningkatan"), ("Enhancements", "Peningkatan"),
("Hardware Codec", "Codec Perangkat Keras"), ("Hardware Codec", "Codec Perangkat Keras"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Keluar"), ("Logout", "Keluar"),
("Tags", "Tag"), ("Tags", "Tag"),
("Search ID", "Cari ID"), ("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"), ("whitelist_sep", "Dipisahkan dengan koma, titik koma, spasi, atau baris baru"),
("Add ID", "Tambah ID"), ("Add ID", "Tambah ID"),
("Add Tag", "Tambah Tag"), ("Add Tag", "Tambah Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].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!"), ("Slogan_tip", "Fatta con il cuore in questo mondo caotico!"),
("Privacy Statement", "Informativa sulla privacy"), ("Privacy Statement", "Informativa sulla privacy"),
("Mute", "Silenzia"), ("Mute", "Silenzia"),
("Build Date", "Data della build"),
("Version", "Versione"),
("Home", "Home"),
("Audio Input", "Input audio"), ("Audio Input", "Input audio"),
("Enhancements", "Miglioramenti"), ("Enhancements", "Miglioramenti"),
("Hardware Codec", "Codifica Hardware"), ("Hardware Codec", "Codifica Hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Esci"), ("Logout", "Esci"),
("Tags", "Tag"), ("Tags", "Tag"),
("Search ID", "Cerca ID"), ("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"), ("whitelist_sep", "Separati da virgola, punto e virgola, spazio o a capo"),
("Add ID", "Aggiungi ID"), ("Add ID", "Aggiungi ID"),
("Add Tag", "Aggiungi tag"), ("Add Tag", "Aggiungi tag"),
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Forte"), ("Strong", "Forte"),
("Switch Sides", "Cambia lato"), ("Switch Sides", "Cambia lato"),
("Please confirm if you want to share your desktop?", "Vuoi condividere il tuo desktop?"), ("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(); ].iter().cloned().collect();
} }

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[ [
("Status", "Status"), ("Status", "Status"),
("Your Desktop", "Twój pulpit"), ("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"), ("Password", "Hasło"),
("Ready", "Gotowe"), ("Ready", "Gotowe"),
("Established", "Nawiązano"), ("Established", "Nawiązano"),
@ -38,10 +38,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop service", "Zatrzymaj usługę"), ("Stop service", "Zatrzymaj usługę"),
("Change ID", "Zmień ID"), ("Change ID", "Zmień ID"),
("Website", "Strona internetowa"), ("Website", "Strona internetowa"),
("About", "O"), ("About", "O aplikacji"),
("Slogan_tip", ""), ("Slogan_tip", "Tworzone z miłością w tym pełnym chaosu świecie!"),
("Privacy Statement", ""), ("Privacy Statement", "Oświadczenie o ochronie prywatności"),
("Mute", "Wycisz"), ("Mute", "Wycisz"),
("Build Date", "Zbudowano"),
("Version", "Wersja"),
("Home", "Pulpit"),
("Audio Input", "Wejście audio"), ("Audio Input", "Wejście audio"),
("Enhancements", "Ulepszenia"), ("Enhancements", "Ulepszenia"),
("Hardware Codec", "Kodek sprzętowy"), ("Hardware Codec", "Kodek sprzętowy"),
@ -96,7 +99,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Directory", "Pusty katalog"), ("Empty Directory", "Pusty katalog"),
("Not an empty directory", "Katalog nie jest pusty"), ("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 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?"), ("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"), ("Do this for all conflicts", "wykonaj dla wszystkich konfliktów"),
("This is irreversible!", "To jest nieodwracalne!"), ("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"), ("Good image quality", "Dobra jakość obrazu"),
("Balanced", "Zrównoważony"), ("Balanced", "Zrównoważony"),
("Optimize reaction time", "Zoptymalizuj czas reakcji"), ("Optimize reaction time", "Zoptymalizuj czas reakcji"),
("Custom", "Własne"), ("Custom", "Niestandardowe"),
("Show remote cursor", "Pokazuj zdalny kursor"), ("Show remote cursor", "Pokazuj zdalny kursor"),
("Show quality monitor", "Parametry połączenia"), ("Show quality monitor", "Parametry połączenia"),
("Disable clipboard", "Wyłącz schowek"), ("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"), ("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"), ("Set Password", "Ustaw hasło"),
("OS Password", "Hasło systemu operacyjnego"), ("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 upgrade", "Zaktualizuj"),
("Click to download", "Pobierz"), ("Click to download", "Pobierz"),
("Click to update", "Uaktualinij"), ("Click to update", "Uaktualnij"),
("Configure", "Konfiguruj"), ("Configure", "Konfiguruj"),
("config_acc", "Konfiguracja konta"), ("config_acc", "Konfiguracja konta"),
("config_screen", "Konfiguracja ekranu"), ("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"), ("Run without install", "Uruchom bez instalacji"),
("Always connected via relay", "Zawsze połączony pośrednio"), ("Always connected via relay", "Zawsze połączony pośrednio"),
("Always connect via relay", "Zawsze łącz 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"), ("Login", "Zaloguj"),
("Verify", ""), ("Verify", "Zweryfikuj"),
("Remember me", ""), ("Remember me", "Zapamiętaj mnie"),
("Trust this device", ""), ("Trust this device", "Dodaj to urządzenie do zaufanych"),
("Verification code", ""), ("Verification code", "Kod weryfikacyjny"),
("verification_tip", ""), ("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"), ("Logout", "Wyloguj"),
("Tags", "Tagi"), ("Tags", "Tagi"),
("Search ID", "Szukaj ID"), ("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"), ("whitelist_sep", "Oddzielone przecinkiem, średnikiem, spacją lub w nowej linii"),
("Add ID", "Dodaj ID"), ("Add ID", "Dodaj ID"),
("Add Tag", "Dodaj Tag"), ("Add Tag", "Dodaj Tag"),
@ -232,7 +234,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Favorites", "Ulubione"), ("Favorites", "Ulubione"),
("Add to Favorites", "Dodaj do ulubionych"), ("Add to Favorites", "Dodaj do ulubionych"),
("Remove from Favorites", "Usuń z ulubionych"), ("Remove from Favorites", "Usuń z ulubionych"),
("Empty", "Pusty"), ("Empty", "Pusto"),
("Invalid folder name", "Nieprawidłowa nazwa folderu"), ("Invalid folder name", "Nieprawidłowa nazwa folderu"),
("Socks5 Proxy", "Socks5 Proxy"), ("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Nazwa hosta"), ("Hostname", "Nazwa hosta"),
@ -331,7 +333,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Styl przewijania"), ("Scroll Style", "Styl przewijania"),
("Show Menubar", "Pokaż pasek menu"), ("Show Menubar", "Pokaż pasek menu"),
("Hide Menubar", "Ukryj 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ę"), ("Relay Connection", "Połączenie przez bramkę"),
("Secure Connection", "Połączenie szyfrowane"), ("Secure Connection", "Połączenie szyfrowane"),
("Insecure Connection", "Połączenie nieszyfrowane"), ("Insecure Connection", "Połączenie nieszyfrowane"),
@ -344,12 +346,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Dark", "Ciemny"), ("Dark", "Ciemny"),
("Light", "Jasny"), ("Light", "Jasny"),
("Follow System", "Zgodne z systemem"), ("Follow System", "Zgodne z systemem"),
("Enable hardware codec", "Włącz wsparcie sprzętowe dla kodeków"), ("Enable hardware codec", "Włącz akcelerację sprzętową kodeków"),
("Unlock Security Settings", "Odblokuj Ustawienia Zabezpieczeń"), ("Unlock Security Settings", "Odblokuj ustawienia zabezpieczeń"),
("Enable Audio", "Włącz Dźwięk"), ("Enable Audio", "Włącz dźwięk"),
("Unlock Network Settings", "Odblokuj ustawienia Sieciowe"), ("Unlock Network Settings", "Odblokuj ustawienia Sieciowe"),
("Server", "Serwer"), ("Server", "Serwer"),
("Direct IP Access", "Bezpośredni Adres IP"), ("Direct IP Access", "Bezpośredni adres IP"),
("Proxy", "Proxy"), ("Proxy", "Proxy"),
("Apply", "Zastosuj"), ("Apply", "Zastosuj"),
("Disconnect all devices?", "Czy rozłączyć wszystkie urządzenia?"), ("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"), ("Enable RDP", "Włącz RDP"),
("Pin menubar", "Przypnij pasek menu"), ("Pin menubar", "Przypnij pasek menu"),
("Unpin menubar", "Odepnij pasek menu"), ("Unpin menubar", "Odepnij pasek menu"),
("Recording", "Trwa nagrywanie"), ("Recording", "Nagrywanie"),
("Directory", "Katalog"), ("Directory", "Katalog"),
("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"), ("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"),
("Change", "Zmień"), ("Change", "Zmień"),
("Start session recording", "Zacznij nagrywać sesję"), ("Start session recording", "Zacznij nagrywać sesję"),
("Stop session recording", "Zatrzymaj nagrywanie sesji"), ("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"), ("Allow recording session", "Zezwól na nagrywanie sesji"),
("Enable LAN Discovery", "Włącz wykrywanie urządzenia w sieci LAN"), ("Enable LAN Discovery", "Włącz wykrywanie urządzenia w sieci LAN"),
("Deny LAN Discovery", "Zablokuj wykrywanie urządzenia w sieci LAN"), ("Deny LAN Discovery", "Zablokuj wykrywanie urządzenia w sieci LAN"),
("Write a message", "Napisz wiadomość"), ("Write a message", "Napisz wiadomość"),
("Prompt", "Monit"), ("Prompt", "Monit"),
("Please wait for confirmation of UAC...", "Oczekuje potwierdzenia ustawień UAC"), ("Please wait for confirmation of UAC...", "Poczekaj na potwierdzenie uprawnień UAC"),
("elevated_foreground_window_tip", ""), ("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"), ("Disconnected", "Rozłączone"),
("Other", "Inne"), ("Other", "Inne"),
("Confirm before closing multiple tabs", "Potwierdź przed zamknięciem wielu kart"), ("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"), ("Full Access", "Pełny dostęp"),
("Screen Share", "Udostępnianie ekranu"), ("Screen Share", "Udostępnianie ekranu"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland wymaga Ubuntu 21.04 lub nowszego."), ("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"), ("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)."), ("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"), ("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"), ("One-time password length", "Długość hasła jednorazowego"),
("Request access to your device", "Żądanie dostępu do Twojego urządzenia"), ("Request access to your device", "Żądanie dostępu do Twojego urządzenia"),
("Hide connection management window", "Ukryj okno zarządzania połączeniem"), ("Hide connection management window", "Ukryj okno zarządzania połączeniem"),
("hide_cm_tip", ""), ("hide_cm_tip", "Pozwalaj na ukrycie tylko gdy akceptujesz sesje za pośrednictwem hasła i używasz hasła permanentnego"),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", "Wsparcie dla Wayland jest niekompletne, użyj X11 jeżeli chcesz korzystać z dostępu nienadzorowanego"),
("Right click to select tabs", ""), ("Right click to select tabs", "Kliknij prawym przyciskiem myszy by wybrać zakładkę"),
("Skipped", ""), ("Skipped", "Pominięte"),
("Add to Address Book", "Dodaj do Książki Adresowej"), ("Add to Address Book", "Dodaj do Książki Adresowej"),
("Group", "Grypy"), ("Group", "Grypy"),
("Search", "Szukaj"), ("Search", "Szukaj"),
("Closed manually by web console", ""), ("Closed manually by web console", "Zakończone manualnie z konsoli Web"),
("Local keyboard type", ""), ("Local keyboard type", "Lokalny typ klawiatury"),
("Select local keyboard type", ""), ("Select local keyboard type", "Wybierz lokalny typ klawiatury"),
("software_render_tip", ""), ("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", ""), ("Always use software rendering", "Zawsze używaj renderowania programowego"),
("config_input", ""), ("config_input", "By kontrolować zdalne urządzenie przy pomocy klawiatury, musisz udzielić aplikacji RustDesk uprawnień do \"Urządzeń Wejściowych\"."),
("request_elevation_tip", ""), ("request_elevation_tip", "Możesz poprosić o podniesienie uprawnień jeżeli ktoś posiada dostęp do zdalnego urządzenia."),
("Wait", ""), ("Wait", "Czekaj"),
("Elevation Error", ""), ("Elevation Error", "Błąd przy podnoszeniu uprawnień"),
("Ask the remote user for authentication", ""), ("Ask the remote user for authentication", "Poproś użytkownika zdalnego o uwierzytelnienie"),
("Choose this if the remote account is administrator", ""), ("Choose this if the remote account is administrator", "Wybierz to jeżeli zdalne konto jest administratorem"),
("Transmit the username and password of administrator", ""), ("Transmit the username and password of administrator", "Prześlij nazwę użytkownika i hasło administratora"),
("still_click_uac_tip", ""), ("still_click_uac_tip", "Nadal wymaga od zdalnego użytkownika potwierdzenia uprawnień UAC."),
("Request Elevation", ""), ("Request Elevation", "Poproś o podniesienie uprawnień"),
("wait_accept_uac_tip", ""), ("wait_accept_uac_tip", "Prosimy czekać aż zdalny użytkownik potwierdzi uprawnienia UAC."),
("Elevate successfully", ""), ("Elevate successfully", "Pomyślnie podniesiono uprawnienia"),
("uppercase", ""), ("uppercase", "wielkie litery"),
("lowercase", ""), ("lowercase", "małe litery"),
("digit", ""), ("digit", "cyfra"),
("special character", ""), ("special character", "znak specjalny"),
("length>=8", ""), ("length>=8", "długość>=8"),
("Weak", ""), ("Weak", "Słabe"),
("Medium", ""), ("Medium", "Średnie"),
("Strong", ""), ("Strong", "Mocne"),
("Switch Sides", ""), ("Switch Sides", "Zmień Strony"),
("Please confirm if you want to share your desktop?", ""), ("Please confirm if you want to share your desktop?", "Czy na pewno chcesz udostępnić swój ekran?"),
("Closed as expected", ""), ("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(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Silenciar"), ("Mute", "Silenciar"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Entrada de Áudio"), ("Audio Input", "Entrada de Áudio"),
("Enhancements", "Melhorias"), ("Enhancements", "Melhorias"),
("Hardware Codec", ""), ("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Sair"), ("Logout", "Sair"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Procurar ID"), ("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"), ("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
("Add ID", "Adicionar ID"), ("Add ID", "Adicionar ID"),
("Add Tag", "Adicionar Tag"), ("Add Tag", "Adicionar Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Desativar som"), ("Mute", "Desativar som"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Entrada de Áudio"), ("Audio Input", "Entrada de Áudio"),
("Enhancements", "Melhorias"), ("Enhancements", "Melhorias"),
("Hardware Codec", "Codec de hardware"), ("Hardware Codec", "Codec de hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Sair"), ("Logout", "Sair"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Pesquisar ID"), ("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"), ("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
("Add ID", "Adicionar ID"), ("Add ID", "Adicionar ID"),
("Add Tag", "Adicionar Tag"), ("Add Tag", "Adicionar Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Fără sunet"), ("Mute", "Fără sunet"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Intrare audio"), ("Audio Input", "Intrare audio"),
("Enhancements", "Îmbunătățiri"), ("Enhancements", "Îmbunătățiri"),
("Hardware Codec", "Codec hardware"), ("Hardware Codec", "Codec hardware"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Deconectare"), ("Logout", "Deconectare"),
("Tags", "Etichetare"), ("Tags", "Etichetare"),
("Search ID", "Caută după ID"), ("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ă"), ("whitelist_sep", "Poți folosi ca separator virgula, punctul și virgula, spațiul sau linia nouă"),
("Add ID", "Adaugă ID"), ("Add ID", "Adaugă ID"),
("Add Tag", "Adaugă etichetă"), ("Add Tag", "Adaugă etichetă"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

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

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Stíšiť"), ("Mute", "Stíšiť"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Zvukový vstup"), ("Audio Input", "Zvukový vstup"),
("Enhancements", ""), ("Enhancements", ""),
("Hardware Codec", ""), ("Hardware Codec", ""),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Odhlásenie"), ("Logout", "Odhlásenie"),
("Tags", "Štítky"), ("Tags", "Štítky"),
("Search ID", "Hľadať ID"), ("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"), ("whitelist_sep", "Oddelené čiarkou, bodkočiarkou, medzerou alebo koncom riadku"),
("Add ID", "Pridať ID"), ("Add ID", "Pridať ID"),
("Add Tag", "Pridať štítok"), ("Add Tag", "Pridať štítok"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Izklopi zvok"), ("Mute", "Izklopi zvok"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Avdio vhod"), ("Audio Input", "Avdio vhod"),
("Enhancements", "Izboljšave"), ("Enhancements", "Izboljšave"),
("Hardware Codec", "Strojni kodek"), ("Hardware Codec", "Strojni kodek"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Odjavi"), ("Logout", "Odjavi"),
("Tags", "Oznake"), ("Tags", "Oznake"),
("Search ID", "Išči ID"), ("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"), ("whitelist_sep", "Naslovi ločeni z vejico, podpičjem, presledkom ali novo vrstico"),
("Add ID", "Dodaj ID"), ("Add ID", "Dodaj ID"),
("Add Tag", "Dodaj oznako"), ("Add Tag", "Dodaj oznako"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Pa zë"), ("Mute", "Pa zë"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Inputi zërit"), ("Audio Input", "Inputi zërit"),
("Enhancements", "Përmirësimet"), ("Enhancements", "Përmirësimet"),
("Hardware Codec", "Kodeku Harduerik"), ("Hardware Codec", "Kodeku Harduerik"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Dalje"), ("Logout", "Dalje"),
("Tags", "Tage"), ("Tags", "Tage"),
("Search ID", "Kerko ID"), ("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"), ("whitelist_sep", "Të ndara me presje, pikëpresje, hapësira ose rresht të ri"),
("Add ID", "Shto ID"), ("Add ID", "Shto ID"),
("Add Tag", "Shto Tag"), ("Add Tag", "Shto Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Utišaj"), ("Mute", "Utišaj"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Audio ulaz"), ("Audio Input", "Audio ulaz"),
("Enhancements", "Proširenja"), ("Enhancements", "Proširenja"),
("Hardware Codec", "Hardverski kodek"), ("Hardware Codec", "Hardverski kodek"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Odjava"), ("Logout", "Odjava"),
("Tags", "Oznake"), ("Tags", "Oznake"),
("Search ID", "Traži ID"), ("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"), ("whitelist_sep", "Odvojeno zarezima, tačka zarezima, praznim mestima ili novim redovima"),
("Add ID", "Dodaj ID"), ("Add ID", "Dodaj ID"),
("Add Tag", "Dodaj oznaku"), ("Add Tag", "Dodaj oznaku"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Tyst"), ("Mute", "Tyst"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Ljud input"), ("Audio Input", "Ljud input"),
("Enhancements", "Förbättringar"), ("Enhancements", "Förbättringar"),
("Hardware Codec", "Hårdvarucodec"), ("Hardware Codec", "Hårdvarucodec"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Logga ut"), ("Logout", "Logga ut"),
("Tags", "Taggar"), ("Tags", "Taggar"),
("Search ID", "Sök ID"), ("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"), ("whitelist_sep", "Separerat av ett comma, semikolon, mellanslag eller ny linje"),
("Add ID", "Lägg till ID"), ("Add ID", "Lägg till ID"),
("Add Tag", "Lägg till Tagg"), ("Add Tag", "Lägg till Tagg"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

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

View File

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

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Sustur"), ("Mute", "Sustur"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Ses Girişi"), ("Audio Input", "Ses Girişi"),
("Enhancements", "Geliştirmeler"), ("Enhancements", "Geliştirmeler"),
("Hardware Codec", "Donanımsal Codec"), ("Hardware Codec", "Donanımsal Codec"),
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Çıkış yap"), ("Logout", "Çıkış yap"),
("Tags", "Etiketler"), ("Tags", "Etiketler"),
("Search ID", "ID Arama"), ("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ış"), ("whitelist_sep", "Virgül, noktalı virgül, boşluk veya yeni satır ile ayrılmış"),
("Add ID", "ID Ekle"), ("Add ID", "ID Ekle"),
("Add Tag", "Etiket Ekle"), ("Add Tag", "Etiket Ekle"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

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

View File

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

View File

@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Slogan_tip", ""), ("Slogan_tip", ""),
("Privacy Statement", ""), ("Privacy Statement", ""),
("Mute", "Tắt tiếng"), ("Mute", "Tắt tiếng"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Đầu vào âm thanh"), ("Audio Input", "Đầu vào âm thanh"),
("Enhancements", "Các tiện itchs"), ("Enhancements", "Các tiện itchs"),
("Hardware Codec", "Codec phần cứng"), ("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"), ("Logout", "Đăng xuất"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Tìm ID"), ("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"), ("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 ID", "Thêm ID"),
("Add Tag", "Thêm Tag"), ("Add Tag", "Thêm Tag"),
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Switch Sides", ""), ("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""), ("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", ""),
("Auto", ""),
("Other Default Options", ""),
].iter().cloned().collect(); ].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 { pub fn current_is_wayland() -> bool {
let dtype = get_display_server(); let dtype = get_display_server();
return "wayland" == dtype && unsafe { UNMODIFIED }; 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 // to-do: test the other display manager
fn _get_display_manager() -> String { fn _get_display_manager() -> String {
if let Ok(x) = std::fs::read_to_string("/etc/X11/default-display-manager") { 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)> { pub fn get_cursor_pos() -> Option<(i32, i32)> {
unsafe { unsafe {
#[allow(invalid_value)]
let mut out = mem::MaybeUninit::uninit().assume_init(); let mut out = mem::MaybeUninit::uninit().assume_init();
if GetCursorPos(&mut out) == FALSE { if GetCursorPos(&mut out) == FALSE {
return None; return None;
@ -61,6 +62,7 @@ pub fn reset_input_cache() {}
pub fn get_cursor() -> ResultType<Option<u64>> { pub fn get_cursor() -> ResultType<Option<u64>> {
unsafe { unsafe {
#[allow(invalid_value)]
let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init(); let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init();
ci.cbSize = std::mem::size_of::<CURSORINFO>() as _; ci.cbSize = std::mem::size_of::<CURSORINFO>() as _;
if crate::portable_service::client::get_cursor_info(&mut ci) == FALSE { if crate::portable_service::client::get_cursor_info(&mut ci) == FALSE {
@ -79,6 +81,7 @@ struct IconInfo(ICONINFO);
impl IconInfo { impl IconInfo {
fn new(icon: HICON) -> ResultType<Self> { fn new(icon: HICON) -> ResultType<Self> {
unsafe { unsafe {
#[allow(invalid_value)]
let mut ii = mem::MaybeUninit::uninit().assume_init(); let mut ii = mem::MaybeUninit::uninit().assume_init();
if GetIconInfo(icon, &mut ii) == FALSE { if GetIconInfo(icon, &mut ii) == FALSE {
Err(io::Error::last_os_error().into()) 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(()); 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; use bytes::Bytes;
pub use connection::*; pub use connection::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::config::Config2;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
anyhow::{anyhow, Context}, anyhow::{anyhow, Context},
@ -12,19 +17,18 @@ use hbb_common::{
message_proto::*, message_proto::*,
protobuf::{Enum, Message as _}, protobuf::{Enum, Message as _},
rendezvous_proto::*, rendezvous_proto::*,
ResultType,
socket_client, socket_client,
sodiumoxide::crypto::{box_, secretbox, sign}, sodiumoxide::crypto::{box_, secretbox, sign}, Stream, timeout, tokio,
timeout, tokio, ResultType, Stream,
}; };
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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 service::{GenericService, Service, Subscriber};
use std::{ #[cfg(not(any(target_os = "android", target_os = "ios")))]
collections::HashMap, use service::ServiceTmpl;
net::SocketAddr,
sync::{Arc, Mutex, RwLock, Weak}, use crate::ipc::{connect, Data};
time::Duration,
};
pub mod audio_service; pub mod audio_service;
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -55,8 +59,6 @@ mod service;
mod video_qos; mod video_qos;
pub mod video_service; pub mod video_service;
use hbb_common::tcp::new_listener;
pub type Childs = Arc<Mutex<Vec<std::process::Child>>>; pub type Childs = Arc<Mutex<Vec<std::process::Child>>>;
type ConnMap = HashMap<i32, ConnInner>; 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")] #[cfg(target_os = "macos")]
async fn sync_and_watch_config_dir() { async fn sync_and_watch_config_dir() {
if crate::platform::is_root() { if crate::platform::is_root() {

View File

@ -3,6 +3,8 @@ use super::{input_service::*, *};
use crate::clipboard_file::*; use crate::clipboard_file::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::common::update_clipboard; use crate::common::update_clipboard;
#[cfg(windows)]
use crate::portable_service::client as portable_client;
use crate::video_service; use crate::video_service;
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel}; use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
@ -101,8 +103,8 @@ pub struct Connection {
lr: LoginRequest, lr: LoginRequest,
last_recv_time: Arc<Mutex<Instant>>, last_recv_time: Arc<Mutex<Instant>>,
chat_unanswered: bool, chat_unanswered: bool,
#[allow(unused)] #[cfg(windows)]
elevation_requested: bool, portable: PortableState,
from_switch: bool, from_switch: bool,
} }
@ -136,7 +138,6 @@ const MILLI1: Duration = Duration::from_millis(1);
const SEND_TIMEOUT_VIDEO: u64 = 12_000; const SEND_TIMEOUT_VIDEO: u64 = 12_000;
const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10; const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10;
const SESSION_TIMEOUT: Duration = Duration::from_secs(30); const SESSION_TIMEOUT: Duration = Duration::from_secs(30);
const SWITCH_SIDES_TIMEOUT: Duration = Duration::from_secs(10);
impl Connection { impl Connection {
pub async fn start( pub async fn start(
@ -199,7 +200,8 @@ impl Connection {
lr: Default::default(), lr: Default::default(),
last_recv_time: Arc::new(Mutex::new(Instant::now())), last_recv_time: Arc::new(Mutex::new(Instant::now())),
chat_unanswered: false, chat_unanswered: false,
elevation_requested: false, #[cfg(windows)]
portable: Default::default(),
from_switch: false, from_switch: false,
}; };
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -247,14 +249,6 @@ impl Connection {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
std::thread::spawn(move || Self::handle_input(rx_input, tx_cloned)); std::thread::spawn(move || Self::handle_input(rx_input, tx_cloned));
let mut second_timer = time::interval(Duration::from_secs(1)); 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 { loop {
tokio::select! { tokio::select! {
@ -362,8 +356,7 @@ impl Connection {
} }
#[cfg(windows)] #[cfg(windows)]
ipc::Data::DataPortableService(ipc::DataPortableService::RequestStart) => { ipc::Data::DataPortableService(ipc::DataPortableService::RequestStart) => {
use crate::portable_service::client; if let Err(e) = portable_client::start_portable_service(portable_client::StartPara::Direct) {
if let Err(e) = client::start_portable_service(client::StartPara::Direct) {
log::error!("Failed to start portable service from cm:{:?}", e); log::error!("Failed to start portable service from cm:{:?}", e);
} }
} }
@ -458,46 +451,7 @@ impl Connection {
}, },
_ = second_timer.tick() => { _ = second_timer.tick() => {
#[cfg(windows)] #[cfg(windows)]
{ conn.portable_check();
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)));
}
}
} }
_ = test_delay_timer.tick() => { _ = test_delay_timer.tick() => {
if last_recv_time.elapsed() >= SEC30 { if last_recv_time.elapsed() >= SEC30 {
@ -1276,7 +1230,7 @@ impl Connection {
SWITCH_SIDES_UUID SWITCH_SIDES_UUID
.lock() .lock()
.unwrap() .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); 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 Ok(uuid) = uuid::Uuid::from_slice(_s.uuid.to_vec().as_ref()) {
if let Some((_instant, uuid_old)) = uuid_old { if let Some((_instant, uuid_old)) = uuid_old {
@ -1537,15 +1491,14 @@ impl Connection {
#[cfg(windows)] #[cfg(windows)]
{ {
let mut err = "No need to elevate".to_string(); let mut err = "No need to elevate".to_string();
if !crate::platform::is_installed() if !crate::platform::is_installed() && !portable_client::running() {
&& !crate::portable_service::client::running() err = portable_client::start_portable_service(
{ portable_client::StartPara::Direct,
use crate::portable_service::client; )
err = client::start_portable_service(client::StartPara::Direct)
.err() .err()
.map_or("".to_string(), |e| e.to_string()); .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(); let mut misc = Misc::new();
misc.set_elevation_response(err); misc.set_elevation_response(err);
let mut msg = Message::new(); let mut msg = Message::new();
@ -1557,18 +1510,14 @@ impl Connection {
#[cfg(windows)] #[cfg(windows)]
{ {
let mut err = "No need to elevate".to_string(); let mut err = "No need to elevate".to_string();
if !crate::platform::is_installed() if !crate::platform::is_installed() && !portable_client::running() {
&& !crate::portable_service::client::running() err = portable_client::start_portable_service(
{ portable_client::StartPara::Logon(_r.username, _r.password),
use crate::portable_service::client; )
err = client::start_portable_service(client::StartPara::Logon(
_r.username,
_r.password,
))
.err() .err()
.map_or("".to_string(), |e| e.to_string()); .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(); let mut misc = Misc::new();
misc.set_elevation_response(err); misc.set_elevation_response(err);
let mut msg = Message::new(); let mut msg = Message::new();
@ -1588,8 +1537,8 @@ impl Connection {
uuid.to_string().as_ref(), uuid.to_string().as_ref(),
]) ])
.ok(); .ok();
self.send_close_reason_no_retry("Closed as expected"); self.send_close_reason_no_retry("Closed as expected").await;
self.on_close("switch sides", false); self.on_close("switch sides", false).await;
return false; return false;
} }
} }
@ -1810,6 +1759,59 @@ impl Connection {
pub fn alive_conns() -> Vec<i32> { pub fn alive_conns() -> Vec<i32> {
ALIVE_CONNS.lock().unwrap().clone() 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) { pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
@ -1984,3 +1986,25 @@ pub enum FileAuditType {
RemoteSend = 0, RemoteSend = 0,
RemoteReceive = 1, 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::{ use hbb_common::{
allow_err, allow_err,
anyhow::anyhow, anyhow::anyhow,
bail, bail, log,
config::Config,
log,
message_proto::{KeyEvent, MouseEvent}, message_proto::{KeyEvent, MouseEvent},
protobuf::Message, protobuf::Message,
tokio::{self, sync::mpsc}, tokio::{self, sync::mpsc},
@ -15,6 +13,7 @@ use shared_memory::*;
use std::{ use std::{
mem::size_of, mem::size_of,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
time::Duration, time::Duration,
}; };
@ -25,6 +24,7 @@ use winapi::{
use crate::{ use crate::{
ipc::{self, new_listener, Connection, Data, DataPortableService}, ipc::{self, new_listener, Connection, Data, DataPortableService},
platform::set_path_permission,
video_service::get_current_display, video_service::get_current_display,
}; };
@ -72,7 +72,7 @@ impl DerefMut for SharedMemory {
impl SharedMemory { impl SharedMemory {
pub fn create(name: &str, size: usize) -> ResultType<Self> { 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() let shmem = match ShmemConf::new()
.size(size) .size(size)
.flink(&flink) .flink(&flink)
@ -91,12 +91,12 @@ impl SharedMemory {
} }
}; };
log::info!("Create shared memory, size:{}, flink:{}", size, flink); 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 }) Ok(SharedMemory { inner: shmem })
} }
pub fn open_existing(name: &str) -> ResultType<Self> { 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() { let shmem = match ShmemConf::new().flink(&flink).allow_raw(true).open() {
Ok(m) => m, Ok(m) => m,
Err(e) => { Err(e) => {
@ -116,30 +116,29 @@ impl SharedMemory {
} }
} }
fn flink(name: String) -> String { fn flink(name: String) -> ResultType<String> {
let mut shmem_flink = format!("shared_memory{}", name); let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string());
if cfg!(windows) { let mut dir = PathBuf::from(disk);
let df = "C:\\ProgramData"; let dir1 = dir.join("ProgramData");
let df = if std::path::Path::new(df).exists() { let dir2 = std::env::var("TEMP")
df.to_owned() .map(|d| PathBuf::from(d))
.unwrap_or(dir.join("Windows").join("Temp"));
if dir1.exists() {
dir = dir1;
} else if dir2.exists() {
dir = dir2;
} else { } else {
std::env::var("TEMP").unwrap_or("C:\\Windows\\TEMP".to_owned()) bail!("no vaild flink directory");
};
let df = format!("{}\\{}", df, *hbb_common::config::APP_NAME.read().unwrap());
std::fs::create_dir(&df).ok();
shmem_flink = format!("{}\\{}", df, shmem_flink);
} else {
shmem_flink = Config::ipc_path("").replace("ipc", "") + &shmem_flink;
} }
return shmem_flink; dir = dir.join(hbb_common::config::APP_NAME.read().unwrap().clone());
} if !dir.exists() {
std::fs::create_dir(&dir)?;
fn set_all_perm(_p: &str) { set_path_permission(&dir, "F").ok();
#[cfg(not(windows))]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(_p, std::fs::Permissions::from_mode(0o0777)).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. // functions called in main process.
pub mod client { pub mod client {
use hbb_common::anyhow::Context; use hbb_common::anyhow::Context;
use std::path::PathBuf;
use super::*; use super::*;
@ -459,6 +457,7 @@ pub mod client {
static ref RUNNING: Arc<Mutex<bool>> = Default::default(); static ref RUNNING: Arc<Mutex<bool>> = Default::default();
static ref SHMEM: Arc<Mutex<Option<SharedMemory>>> = 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 SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(client::start_ipc_server());
static ref QUICK_SUPPORT: Arc<Mutex<bool>> = Default::default();
} }
pub enum StartPara { pub enum StartPara {
@ -514,7 +513,7 @@ pub mod client {
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
{ {
if let Some(dir) = PathBuf::from(&exe).parent() { 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; *SHMEM.lock().unwrap() = None;
bail!("Failed to set permission of {:?}", dir); bail!("Failed to set permission of {:?}", dir);
} }
@ -532,7 +531,7 @@ pub mod client {
let dst = dir.join("rustdesk.exe"); let dst = dir.join("rustdesk.exe");
if std::fs::copy(&exe, &dst).is_ok() { if std::fs::copy(&exe, &dst).is_ok() {
if dst.exists() { if dst.exists() {
if set_dir_permission(&dir) { if set_path_permission(&dir, "RX").is_ok() {
exe = dst.to_string_lossy().to_string(); exe = dst.to_string_lossy().to_string();
} }
} }
@ -561,16 +560,10 @@ pub mod client {
*SHMEM.lock().unwrap() = None; *SHMEM.lock().unwrap() = None;
} }
fn set_dir_permission(dir: &PathBuf) -> bool { pub fn set_quick_support(v: bool) {
// // give Everyone RX permission *QUICK_SUPPORT.lock().unwrap() = v;
std::process::Command::new("icacls")
.arg(dir.as_os_str())
.arg("/grant")
.arg("Everyone:(OI)(CI)RX")
.arg("/T")
.spawn()
.is_ok()
} }
pub struct CapturerPortable; pub struct CapturerPortable;
impl CapturerPortable { impl CapturerPortable {
@ -685,17 +678,7 @@ pub mod client {
use DataPortableService::*; use DataPortableService::*;
let rx = Arc::new(tokio::sync::Mutex::new(rx)); let rx = Arc::new(tokio::sync::Mutex::new(rx));
let postfix = IPC_SUFFIX; let postfix = IPC_SUFFIX;
#[cfg(feature = "flutter")] let quick_support = QUICK_SUPPORT.lock().unwrap().clone();
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");
match new_listener(postfix).await { match new_listener(postfix).await {
Ok(mut incoming) => loop { Ok(mut incoming) => loop {

View File

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

View File

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

View File

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

View File

@ -755,11 +755,6 @@ class FixWayland: Reactor.Component {
</div>; </div>;
} }
event click $(#fix-wayland) {
handler.fix_login_wayland();
app.update();
}
event click $(#help-me) { event click $(#help-me) {
handler.open_url(translate("doc_fix_wayland")); handler.open_url(translate("doc_fix_wayland"));
} }
@ -769,19 +764,11 @@ class ModifyDefaultLogin: Reactor.Component {
function render() { function render() {
return <div .trust-me> return <div .trust-me>
<div>{translate('Warning')}</div> <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 #help-me .link>{translate('Help')}</div>
</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) { event click $(#help-me) {
handler.open_url(translate("doc_fix_wayland")); handler.open_url(translate("doc_fix_wayland"));
} }

View File

@ -1,3 +1,5 @@
use std::{ffi::c_void, rc::Rc};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use cocoa::{ use cocoa::{
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy::*, NSMenu, NSMenuItem}, appkit::{NSApp, NSApplication, NSApplicationActivationPolicy::*, NSMenu, NSMenuItem},
@ -8,11 +10,16 @@ use objc::{
class, class,
declare::ClassDecl, declare::ClassDecl,
msg_send, msg_send,
runtime::{Object, Sel, BOOL}, runtime::{BOOL, Object, Sel},
sel, sel_impl, sel, sel_impl,
}; };
use sciter::{make_args, Host}; use objc::runtime::Class;
use std::{ffi::c_void, rc::Rc}; 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"; static APP_HANDLER_IVAR: &str = "GoDeskAppHandler";
@ -98,12 +105,21 @@ unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
sel!(handleMenuItem:), sel!(handleMenuItem:),
handle_menu_item as extern "C" fn(&mut Object, Sel, id), 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 decl = decl.register();
let delegate: id = msg_send![decl, alloc]; let delegate: id = msg_send![decl, alloc];
let () = msg_send![delegate, init]; let () = msg_send![delegate, init];
let state = DelegateState { handler }; let state = DelegateState { handler };
let handler_ptr = Box::into_raw(Box::new(state)); let handler_ptr = Box::into_raw(Box::new(state));
(*delegate).set_ivar(APP_HANDLER_IVAR, handler_ptr as *mut c_void); (*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]; 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 { unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object {
let title = NSString::alloc(nil).init_str(title); let title = NSString::alloc(nil).init_str(title);
let action = sel!(handleMenuItem:); let action = sel!(handleMenuItem:);

View File

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

View File

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

View File

@ -614,12 +614,6 @@ pub fn is_login_wayland() -> bool {
return false; return false;
} }
#[inline]
pub fn fix_login_wayland() {
#[cfg(target_os = "linux")]
crate::platform::linux::fix_login_wayland();
}
#[inline] #[inline]
pub fn current_is_wayland() -> bool { pub fn current_is_wayland() -> bool {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -628,14 +622,6 @@ pub fn current_is_wayland() -> bool {
return false; 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] #[inline]
pub fn get_software_update_url() -> String { pub fn get_software_update_url() -> String {
SOFTWARE_UPDATE_URL.lock().unwrap().clone() 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() 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, // notice: avoiding create ipc connection repeatedly,
// because windows named pipe has serious memory leak issue. // because windows named pipe has serious memory leak issue.
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]

View File

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