mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-28 23:59:05 +08:00
Merge branch 'rustdesk:master' into master
This commit is contained in:
commit
41c50c4c51
41
Cargo.lock
generated
41
Cargo.lock
generated
@ -1137,7 +1137,7 @@ checksum = "413487ef345ab5cdfbf23e66070741217a701bce70f2f397a54221b4f2b6056a"
|
||||
dependencies = [
|
||||
"dconf_rs",
|
||||
"detect-desktop-environment",
|
||||
"dirs",
|
||||
"dirs 4.0.0",
|
||||
"objc",
|
||||
"rust-ini",
|
||||
"web-sys",
|
||||
@ -1401,6 +1401,16 @@ dependencies = [
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
@ -1873,6 +1883,19 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fruitbasket"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "898289b8e0528c84fb9b88f15ac9d5109bcaf23e0e49bb6f64deee0d86b6a351"
|
||||
dependencies = [
|
||||
"dirs 2.0.2",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"time 0.1.45",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@ -3657,6 +3680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
"objc_exception",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3670,6 +3694,15 @@ dependencies = [
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_exception"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
@ -4655,6 +4688,7 @@ dependencies = [
|
||||
"flexi_logger",
|
||||
"flutter_rust_bridge",
|
||||
"flutter_rust_bridge_codegen",
|
||||
"fruitbasket",
|
||||
"glib 0.16.5",
|
||||
"gtk",
|
||||
"hbb_common",
|
||||
@ -4673,6 +4707,7 @@ dependencies = [
|
||||
"mouce",
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"objc_id",
|
||||
"parity-tokio-ipc",
|
||||
"rdev",
|
||||
"repng",
|
||||
@ -4713,7 +4748,7 @@ name = "rustdesk-portable-packer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"dirs",
|
||||
"dirs 4.0.0",
|
||||
"embed-resource",
|
||||
"md5",
|
||||
]
|
||||
@ -6591,7 +6626,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"byteorder",
|
||||
"derivative",
|
||||
"dirs",
|
||||
"dirs 4.0.0",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
|
@ -106,6 +106,8 @@ core-graphics = "0.22"
|
||||
include_dir = "0.7.2"
|
||||
tray-item = "0.7" # looks better than trayicon
|
||||
dark-light = "0.2"
|
||||
fruitbasket = "0.10.0"
|
||||
objc_id = "0.1.1"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
psimple = { package = "libpulse-simple-binding", version = "2.25" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
<p dir="rtl" align="center">
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#تصاویر-محیط-نرمافزار">تصاویر محیط نرمافزار</a> •
|
||||
<a href="#تصاویر-محیط-نرم افزار">تصاویر محیط نرمافزار</a> •
|
||||
<a href="#ساختار-پوشه-ها">ساختار</a> •
|
||||
<a href="#نحوه-ساخت-با-داکر">داکر</a> •
|
||||
<a href="#ساخت">ساخت</a> •
|
||||
@ -9,12 +9,12 @@
|
||||
<p align="center" dir="auto">[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>]</p>
|
||||
<p dir="rtl" align="center"><b>برای ترجمه این سند (README)، <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang" dir="rtl">رابط کاربری RustDesk</a>، <a href="https://github.com/rustdesk/doc.rustdesk.com" dir="rtl">و مستندات آن</a> به زبان مادری شما به کمکتان نیازمندیم. </b></p>
|
||||
|
||||
با ما گپ بزنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV)
|
||||
با ما گفتگو کنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV)
|
||||
|
||||
|
||||
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
راستدسک (RustDesk) نرمافزاری برای گارکردن با رایانهی رومیزی از راه دور است و با زبان برنامهنویسی Rust نوشته شده است. نیاز به تنظیمات چندانی ندارد و شما را قادر می سازد تا بدون نگرانی از امنیت اطلاعات خود بر آنها کنترل کامل داشته باشید.
|
||||
راستدسک (RustDesk) نرمافزاری برای کارکردن با رایانهی رومیزی از راه دور است و با زبان برنامهنویسی Rust نوشته شده است. نیاز به تنظیمات چندانی ندارد و شما را قادر می سازد تا بدون نگرانی از امنیت اطلاعات خود بر آنها کنترل کامل داشته باشید.
|
||||
|
||||
میتوانید از سرور rendezvous/relay ما استفاده کنید، [سرور خودتان را راهاندازی کنید](https://rustdesk.com/server) یا
|
||||
[ سرورrendezvous/relay خود را بنویسید](https://github.com/rustdesk/rustdesk).
|
||||
@ -130,7 +130,7 @@ cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
سپس، هر بار که نیاز به ساخت ترمافزار داشتید، دستور زیر را اجرا کنید:
|
||||
سپس، هر بار که نیاز به ساخت نرمافزار داشتید، دستور زیر را اجرا کنید:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
|
@ -3,13 +3,11 @@ import 'dart:convert';
|
||||
import 'dart:ffi' hide Size;
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:back_button_interceptor/back_button_interceptor.dart';
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:win32/win32.dart' as win32;
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -18,14 +16,17 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_hbb/utils/platform_channel.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:uni_links/uni_links.dart';
|
||||
import 'package:uni_links_desktop/uni_links_desktop.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:window_size/window_size.dart' as window_size;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:win32/win32.dart' as win32;
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:window_size/window_size.dart' as window_size;
|
||||
|
||||
import '../consts.dart';
|
||||
import 'common/widgets/overlay.dart';
|
||||
import 'mobile/pages/file_manager_page.dart';
|
||||
import 'mobile/pages/remote_page.dart';
|
||||
@ -33,8 +34,6 @@ import 'models/input_model.dart';
|
||||
import 'models/model.dart';
|
||||
import 'models/platform_model.dart';
|
||||
|
||||
import '../consts.dart';
|
||||
|
||||
final globalKey = GlobalKey<NavigatorState>();
|
||||
final navigationBarKey = GlobalKey();
|
||||
|
||||
@ -205,6 +204,9 @@ class MyTheme {
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style:
|
||||
OutlinedButton.styleFrom(side: BorderSide(color: Colors.white38))),
|
||||
textButtonTheme: isDesktop
|
||||
? TextButtonThemeData(
|
||||
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
|
||||
@ -221,16 +223,18 @@ class MyTheme {
|
||||
return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme));
|
||||
}
|
||||
|
||||
static void changeDarkMode(ThemeMode mode) {
|
||||
static void changeDarkMode(ThemeMode mode) async {
|
||||
Get.changeThemeMode(mode);
|
||||
if (desktopType == DesktopType.main) {
|
||||
if (mode == ThemeMode.system) {
|
||||
bind.mainSetLocalOption(key: kCommConfKeyTheme, value: '');
|
||||
await bind.mainSetLocalOption(key: kCommConfKeyTheme, value: '');
|
||||
} else {
|
||||
bind.mainSetLocalOption(
|
||||
await bind.mainSetLocalOption(
|
||||
key: kCommConfKeyTheme, value: mode.toShortString());
|
||||
}
|
||||
bind.mainChangeTheme(dark: mode.toShortString());
|
||||
await bind.mainChangeTheme(dark: mode.toShortString());
|
||||
// Synchronize the window theme of the system.
|
||||
updateSystemWindowTheme();
|
||||
}
|
||||
}
|
||||
|
||||
@ -608,12 +612,12 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
FocusNode focusNode = FocusNode();
|
||||
// request focus if there is no focused FocusNode in the dialog
|
||||
Future.delayed(Duration.zero, () {
|
||||
if (!focusNode.hasFocus) focusNode.requestFocus();
|
||||
});
|
||||
// request focus
|
||||
FocusScopeNode scopeNode = FocusScopeNode();
|
||||
Future.delayed(Duration.zero, () {
|
||||
if (!scopeNode.hasFocus) scopeNode.requestFocus();
|
||||
});
|
||||
const double padding = 16;
|
||||
return FocusScope(
|
||||
node: scopeNode,
|
||||
autofocus: true,
|
||||
@ -638,17 +642,18 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
child: AlertDialog(
|
||||
scrollable: true,
|
||||
title: title,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: contentPadding ?? 25, vertical: 10),
|
||||
contentPadding: EdgeInsets.fromLTRB(
|
||||
contentPadding ?? padding, 25, contentPadding ?? padding, 10),
|
||||
content: ConstrainedBox(
|
||||
constraints: contentBoxConstraints,
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
constraints: contentBoxConstraints,
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
isDense: true, contentPadding: EdgeInsets.all(15)),
|
||||
),
|
||||
child: content)),
|
||||
isDense: true, contentPadding: EdgeInsets.all(15))),
|
||||
child: content),
|
||||
),
|
||||
actions: actions,
|
||||
actionsPadding: EdgeInsets.fromLTRB(0, 0, padding, padding),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -689,7 +694,6 @@ void msgBox(String id, String type, String title, String text, String link,
|
||||
buttons.insert(
|
||||
0, dialogButton('Cancel', onPressed: cancel, isOutline: true));
|
||||
}
|
||||
// TODO: test this button
|
||||
if (type.contains("hasclose")) {
|
||||
buttons.insert(
|
||||
0,
|
||||
@ -702,9 +706,8 @@ void msgBox(String id, String type, String title, String text, String link,
|
||||
}
|
||||
dialogManager.show(
|
||||
(setState, close) => CustomAlertDialog(
|
||||
title: _msgBoxTitle(title),
|
||||
content:
|
||||
SelectableText(translate(text), style: const TextStyle(fontSize: 15)),
|
||||
title: null,
|
||||
content: SelectionArea(child: msgboxContent(type, title, text)),
|
||||
actions: buttons,
|
||||
onSubmit: hasOk ? submit : null,
|
||||
onCancel: hasCancel == true ? cancel : null,
|
||||
@ -713,30 +716,74 @@ void msgBox(String id, String type, String title, String text, String link,
|
||||
);
|
||||
}
|
||||
|
||||
Widget msgBoxButton(String text, void Function() onPressed) {
|
||||
return ButtonTheme(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
//limits the touch area to the button area
|
||||
minWidth: 0,
|
||||
//wraps child's width
|
||||
height: 0,
|
||||
child: TextButton(
|
||||
style: flatButtonStyle,
|
||||
onPressed: onPressed,
|
||||
child:
|
||||
Text(translate(text), style: TextStyle(color: MyTheme.accent))));
|
||||
Color? _msgboxColor(String type) {
|
||||
if (type == "input-password" || type == "custom-os-password") {
|
||||
return Color(0xFFAD448E);
|
||||
}
|
||||
if (type.contains("success")) {
|
||||
return Color(0xFF32bea6);
|
||||
}
|
||||
if (type.contains("error") || type == "re-input-password") {
|
||||
return Color(0xFFE04F5F);
|
||||
}
|
||||
return Color(0xFF2C8CFF);
|
||||
}
|
||||
|
||||
Widget _msgBoxTitle(String title) =>
|
||||
Text(translate(title), style: TextStyle(fontSize: 21));
|
||||
Widget msgboxIcon(String type) {
|
||||
IconData? iconData;
|
||||
if (type.contains("error") || type == "re-input-password") {
|
||||
iconData = Icons.cancel;
|
||||
}
|
||||
if (type.contains("success")) {
|
||||
iconData = Icons.check_circle;
|
||||
}
|
||||
if (type == "wait-uac" || type == "wait-remote-accept-nook") {
|
||||
iconData = Icons.hourglass_top;
|
||||
}
|
||||
if (type == 'on-uac' || type == 'on-foreground-elevated') {
|
||||
iconData = Icons.admin_panel_settings;
|
||||
}
|
||||
if (type == "info") {
|
||||
iconData = Icons.info;
|
||||
}
|
||||
if (iconData != null) {
|
||||
return Icon(iconData, size: 50, color: _msgboxColor(type))
|
||||
.marginOnly(right: 16);
|
||||
}
|
||||
|
||||
return Offstage();
|
||||
}
|
||||
|
||||
// title should be null
|
||||
Widget msgboxContent(String type, String title, String text) {
|
||||
return Row(
|
||||
children: [
|
||||
msgboxIcon(type),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
translate(title),
|
||||
style: TextStyle(fontSize: 21),
|
||||
).marginOnly(bottom: 10),
|
||||
Text(translate(text), style: const TextStyle(fontSize: 15)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).marginOnly(bottom: 12);
|
||||
}
|
||||
|
||||
void msgBoxCommon(OverlayDialogManager dialogManager, String title,
|
||||
Widget content, List<Widget> buttons,
|
||||
{bool hasCancel = true}) {
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show((setState, close) => CustomAlertDialog(
|
||||
title: _msgBoxTitle(title),
|
||||
title: Text(
|
||||
translate(title),
|
||||
style: TextStyle(fontSize: 21),
|
||||
),
|
||||
content: content,
|
||||
actions: buttons,
|
||||
onCancel: hasCancel ? close : null,
|
||||
@ -1223,10 +1270,12 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
|
||||
/// [Availability]
|
||||
/// initUniLinks should only be used on macos/windows.
|
||||
/// we use dbus for linux currently.
|
||||
Future<void> initUniLinks() async {
|
||||
if (!Platform.isWindows && !Platform.isMacOS) {
|
||||
return;
|
||||
Future<bool> initUniLinks() async {
|
||||
if (Platform.isLinux) {
|
||||
return false;
|
||||
}
|
||||
// Register uni links for Windows. The required info of url scheme is already
|
||||
// declared in `Info.plist` for macOS.
|
||||
if (Platform.isWindows) {
|
||||
registerProtocol('rustdesk');
|
||||
}
|
||||
@ -1234,11 +1283,12 @@ Future<void> initUniLinks() async {
|
||||
try {
|
||||
final initialLink = await getInitialLink();
|
||||
if (initialLink == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
parseRustdeskUri(initialLink);
|
||||
return parseRustdeskUri(initialLink);
|
||||
} catch (err) {
|
||||
debugPrintStack(label: "$err");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1259,11 +1309,19 @@ StreamSubscription? listenUniLinks() {
|
||||
return sub;
|
||||
}
|
||||
|
||||
/// Returns true if we successfully handle the startup arguments.
|
||||
/// Handle command line arguments
|
||||
///
|
||||
/// * Returns true if we successfully handle the startup arguments.
|
||||
bool checkArguments() {
|
||||
if (kBootArgs.isNotEmpty) {
|
||||
final ret = parseRustdeskUri(kBootArgs.first);
|
||||
if (ret) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05]
|
||||
// check connect args
|
||||
final connectIndex = kBootArgs.indexOf("--connect");
|
||||
var connectIndex = kBootArgs.indexOf("--connect");
|
||||
if (connectIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
@ -1298,7 +1356,7 @@ bool checkArguments() {
|
||||
bool parseRustdeskUri(String uriPath) {
|
||||
final uri = Uri.tryParse(uriPath);
|
||||
if (uri == null) {
|
||||
print("uri is not valid: $uriPath");
|
||||
debugPrint("uri is not valid: $uriPath");
|
||||
return false;
|
||||
}
|
||||
return callUniLinksUriHandler(uri);
|
||||
@ -1317,7 +1375,7 @@ bool callUniLinksUriHandler(Uri uri) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
rustDeskWinManager.newRemoteDesktop(peerId, switch_uuid: switch_uuid);
|
||||
});
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1457,8 +1515,12 @@ Future<void> onActiveWindowChanged() async {
|
||||
} catch (err) {
|
||||
debugPrintStack(label: "$err");
|
||||
} finally {
|
||||
debugPrint("Start closing RustDesk...");
|
||||
await windowManager.setPreventClose(false);
|
||||
await windowManager.close();
|
||||
if (Platform.isMacOS) {
|
||||
RdPlatformChannel.instance.terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1590,7 +1652,8 @@ class ServerConfig {
|
||||
Widget dialogButton(String text,
|
||||
{required VoidCallback? onPressed,
|
||||
bool isOutline = false,
|
||||
TextStyle? style}) {
|
||||
TextStyle? style,
|
||||
ButtonStyle? buttonStyle}) {
|
||||
if (isDesktop) {
|
||||
if (isOutline) {
|
||||
return OutlinedButton(
|
||||
@ -1599,7 +1662,7 @@ Widget dialogButton(String text,
|
||||
);
|
||||
} else {
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(elevation: 0),
|
||||
style: ElevatedButton.styleFrom(elevation: 0).merge(buttonStyle),
|
||||
onPressed: onPressed,
|
||||
child: Text(translate(text), style: style),
|
||||
);
|
||||
@ -1637,3 +1700,16 @@ String getWindowName({WindowType? overrideType}) {
|
||||
String getWindowNameWithId(String id, {WindowType? overrideType}) {
|
||||
return "${DesktopTab.labelGetterAlias(id).value} - ${getWindowName(overrideType: overrideType)}";
|
||||
}
|
||||
|
||||
Future<void> updateSystemWindowTheme() async {
|
||||
// Set system window theme for macOS.
|
||||
final userPreference = MyTheme.getThemeModePreference();
|
||||
if (userPreference != ThemeMode.system) {
|
||||
if (Platform.isMacOS) {
|
||||
await RdPlatformChannel.instance.changeSystemWindowTheme(
|
||||
userPreference == ThemeMode.light
|
||||
? SystemWindowTheme.light
|
||||
: SystemWindowTheme.dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
|
||||
import '../../models/platform_model.dart';
|
||||
|
||||
class HttpType {
|
||||
static const kAuthReqTypeAccount = "account";
|
||||
static const kAuthReqTypeMobile = "mobile";
|
||||
@ -48,6 +52,16 @@ class PeerPayload {
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceInfo {
|
||||
static Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['os'] = Platform.operatingSystem;
|
||||
data['type'] = "client";
|
||||
data['name'] = bind.mainGetHostname();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class LoginRequest {
|
||||
String? username;
|
||||
String? password;
|
||||
@ -56,7 +70,7 @@ class LoginRequest {
|
||||
bool? autoLogin;
|
||||
String? type;
|
||||
String? verificationCode;
|
||||
String? deviceInfo;
|
||||
Map<String, dynamic> deviceInfo = DeviceInfo.toJson();
|
||||
|
||||
LoginRequest(
|
||||
{this.username,
|
||||
@ -65,19 +79,7 @@ class LoginRequest {
|
||||
this.uuid,
|
||||
this.autoLogin,
|
||||
this.type,
|
||||
this.verificationCode,
|
||||
this.deviceInfo});
|
||||
|
||||
LoginRequest.fromJson(Map<String, dynamic> json) {
|
||||
username = json['username'];
|
||||
password = json['password'];
|
||||
id = json['id'];
|
||||
uuid = json['uuid'];
|
||||
autoLogin = json['autoLogin'];
|
||||
type = json['type'];
|
||||
verificationCode = json['verificationCode'];
|
||||
deviceInfo = json['deviceInfo'];
|
||||
}
|
||||
this.verificationCode});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
@ -88,7 +90,7 @@ class LoginRequest {
|
||||
data['autoLogin'] = autoLogin ?? '';
|
||||
data['type'] = type ?? '';
|
||||
data['verificationCode'] = verificationCode ?? '';
|
||||
data['deviceInfo'] = deviceInfo ?? '';
|
||||
data['deviceInfo'] = deviceInfo;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
return Stack(
|
||||
children: [
|
||||
LayoutBuilder(builder: (context, constraints) {
|
||||
return DashChat(
|
||||
final chat = DashChat(
|
||||
onSend: (chatMsg) {
|
||||
chatModel.send(chatMsg);
|
||||
chatModel.inputNode.requestFocus();
|
||||
@ -108,6 +108,7 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
borderBottomLeft: 8,
|
||||
)),
|
||||
);
|
||||
return SelectionArea(child: chat);
|
||||
}),
|
||||
desktopType == DesktopType.cm ||
|
||||
chatModel.currentID == ChatModel.clientModeID
|
||||
|
@ -666,6 +666,8 @@ Future<bool?> verificationCodeDialog(UserPayload? user) async {
|
||||
child: const LinearProgressIndicator()),
|
||||
],
|
||||
),
|
||||
onCancel: close,
|
||||
onSubmit: onVerify,
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("Verify", onPressed: onVerify),
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -316,46 +317,52 @@ class _DraggableState extends State<Draggable> {
|
||||
}
|
||||
|
||||
class QualityMonitor extends StatelessWidget {
|
||||
static const textStyle = TextStyle(color: MyTheme.grayBg);
|
||||
final QualityMonitorModel qualityMonitorModel;
|
||||
QualityMonitor(this.qualityMonitorModel);
|
||||
|
||||
Widget _row(String info, String? value, {Color? rightColor}) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: AutoSizeText(info,
|
||||
style: TextStyle(color: MyTheme.darkGray),
|
||||
textAlign: TextAlign.right,
|
||||
maxLines: 1)),
|
||||
Spacer(flex: 1),
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: AutoSizeText(value ?? '',
|
||||
style: TextStyle(color: rightColor ?? Colors.white),
|
||||
maxLines: 1)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ChangeNotifierProvider.value(
|
||||
value: qualityMonitorModel,
|
||||
child: Consumer<QualityMonitorModel>(
|
||||
builder: (context, qualityMonitorModel, child) => Positioned(
|
||||
top: 10,
|
||||
right: 10,
|
||||
child: qualityMonitorModel.show
|
||||
? Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: MyTheme.canvasColor.withAlpha(120),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
|
||||
style: textStyle,
|
||||
),
|
||||
Text(
|
||||
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
|
||||
style: textStyle,
|
||||
),
|
||||
Text(
|
||||
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
|
||||
style: textStyle,
|
||||
),
|
||||
Text(
|
||||
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
|
||||
style: textStyle,
|
||||
),
|
||||
Text(
|
||||
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
|
||||
style: textStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink())));
|
||||
builder: (context, qualityMonitorModel, child) => qualityMonitorModel
|
||||
.show
|
||||
? Container(
|
||||
constraints: BoxConstraints(maxWidth: 200),
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: MyTheme.canvasColor.withAlpha(120),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_row("Speed", qualityMonitorModel.data.speed ?? '-'),
|
||||
_row("FPS", qualityMonitorModel.data.fps ?? '-'),
|
||||
_row(
|
||||
"Delay", "${qualityMonitorModel.data.delay ?? '-'}ms",
|
||||
rightColor: Colors.green),
|
||||
_row("Target Bitrate",
|
||||
"${qualityMonitorModel.data.targetBitrate ?? '-'}kb"),
|
||||
_row(
|
||||
"Codec", qualityMonitorModel.data.codecFormat ?? '-'),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()));
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
@ -20,7 +21,9 @@ const int groupTabIndex = 4;
|
||||
const String defaultGroupTabname = 'Group';
|
||||
|
||||
class StatePeerTab {
|
||||
final RxInt currentTab = 0.obs;
|
||||
final RxInt currentTab = 0.obs; // index in tabNames
|
||||
final RxList<int> visibleOrderedTabs = RxList.empty(growable: true);
|
||||
List<int> tabOrder = List.from([0, 1, 2, 3, 4]); // constant length
|
||||
final RxInt tabHiddenFlag = 0.obs;
|
||||
final RxList<String> tabNames = [
|
||||
'Recent Sessions',
|
||||
@ -31,53 +34,80 @@ class StatePeerTab {
|
||||
].obs;
|
||||
|
||||
StatePeerTab._() {
|
||||
// init tabHiddenFlag
|
||||
tabHiddenFlag.value = (int.tryParse(
|
||||
bind.getLocalFlutterConfig(k: 'hidden-peer-card'),
|
||||
radix: 2) ??
|
||||
0);
|
||||
var tabs = _notHiddenTabs();
|
||||
// remove dynamic tabs
|
||||
tabs.remove(groupTabIndex);
|
||||
// init tabOrder
|
||||
try {
|
||||
final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order');
|
||||
if (conf.isNotEmpty) {
|
||||
final json = jsonDecode(conf);
|
||||
if (json is List) {
|
||||
final List<int> list =
|
||||
json.map((e) => int.tryParse(e.toString()) ?? -1).toList();
|
||||
if (list.length == tabOrder.length &&
|
||||
tabOrder.every((e) => list.contains(e))) {
|
||||
tabOrder = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrintStack(label: '$e');
|
||||
}
|
||||
// init visibleOrderedTabs
|
||||
var tempList = tabOrder.toList();
|
||||
tempList.removeWhere((e) => !tabs.contains(e));
|
||||
visibleOrderedTabs.value = tempList;
|
||||
// init currentTab
|
||||
currentTab.value =
|
||||
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0;
|
||||
if (!tabs.contains(currentTab.value)) {
|
||||
currentTab.value = 0;
|
||||
if (tabs.isNotEmpty) {
|
||||
currentTab.value = tabs[0];
|
||||
} else {
|
||||
currentTab.value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
static final StatePeerTab instance = StatePeerTab._();
|
||||
|
||||
// check dynamic tabs
|
||||
check() {
|
||||
var tabs = _notHiddenTabs();
|
||||
if (filterGroupCard()) {
|
||||
if (currentTab.value == groupTabIndex) {
|
||||
currentTab.value =
|
||||
tabs.firstWhereOrNull((e) => e != groupTabIndex) ?? 0;
|
||||
bind.setLocalFlutterConfig(
|
||||
k: 'peer-tab-index', v: currentTab.value.toString());
|
||||
}
|
||||
tabOrder2visibleOrderedTabs();
|
||||
if (visibleOrderedTabs.contains(groupTabIndex) &&
|
||||
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ==
|
||||
groupTabIndex) {
|
||||
currentTab.value = groupTabIndex;
|
||||
}
|
||||
if (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isNotEmpty) {
|
||||
tabNames[groupTabIndex] = gFFI.userModel.groupName.value;
|
||||
} else {
|
||||
if (gFFI.userModel.isAdmin.isFalse &&
|
||||
gFFI.userModel.groupName.isNotEmpty) {
|
||||
tabNames[groupTabIndex] = gFFI.userModel.groupName.value;
|
||||
} else {
|
||||
tabNames[groupTabIndex] = defaultGroupTabname;
|
||||
}
|
||||
if (tabs.contains(groupTabIndex) &&
|
||||
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ==
|
||||
groupTabIndex) {
|
||||
currentTab.value = groupTabIndex;
|
||||
}
|
||||
tabNames[groupTabIndex] = defaultGroupTabname;
|
||||
}
|
||||
}
|
||||
|
||||
List<int> currentTabs() {
|
||||
var v = List<int>.empty(growable: true);
|
||||
for (int i = 0; i < tabNames.length; i++) {
|
||||
if (!_isTabHidden(i) && !_isTabFilter(i)) {
|
||||
v.add(i);
|
||||
}
|
||||
visibleOrderedTabs2TabOrder() {
|
||||
var tmpTabOrder = visibleOrderedTabs.toList();
|
||||
var left = tabOrder.where((e) => !tmpTabOrder.contains(e)).toList();
|
||||
for (var t in left) {
|
||||
_addTabInOrder(tmpTabOrder, t);
|
||||
}
|
||||
return v;
|
||||
statePeerTab.tabOrder = tmpTabOrder;
|
||||
bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(tmpTabOrder));
|
||||
}
|
||||
|
||||
tabOrder2visibleOrderedTabs() {
|
||||
var visible = statePeerTab.visibleTabs();
|
||||
statePeerTab.visibleOrderedTabs.value =
|
||||
statePeerTab.tabOrder.where((e) => visible.contains(e)).toList();
|
||||
}
|
||||
|
||||
// return true if hide group card
|
||||
bool filterGroupCard() {
|
||||
if (gFFI.groupModel.users.isEmpty ||
|
||||
(gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) {
|
||||
@ -87,6 +117,17 @@ class StatePeerTab {
|
||||
}
|
||||
}
|
||||
|
||||
// return index array of tabNames
|
||||
List<int> visibleTabs() {
|
||||
var v = List<int>.empty(growable: true);
|
||||
for (int i = 0; i < tabNames.length; i++) {
|
||||
if (!_isTabHidden(i) && !_isTabFilter(i)) {
|
||||
v.add(i);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
bool _isTabHidden(int tabindex) {
|
||||
return tabHiddenFlag & (1 << tabindex) != 0;
|
||||
}
|
||||
@ -107,6 +148,41 @@ class StatePeerTab {
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// add tabIndex to list
|
||||
_addTabInOrder(List<int> list, int tabIndex) {
|
||||
if (!tabOrder.contains(tabIndex) || list.contains(tabIndex)) {
|
||||
return;
|
||||
}
|
||||
bool sameOrder = true;
|
||||
int lastIndex = -1;
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
var index = tabOrder.lastIndexOf(list[i]);
|
||||
if (index > lastIndex) {
|
||||
lastIndex = index;
|
||||
continue;
|
||||
} else {
|
||||
sameOrder = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sameOrder) {
|
||||
var indexInTabOrder = tabOrder.indexOf(tabIndex);
|
||||
var left = List.empty(growable: true);
|
||||
for (int i = 0; i < indexInTabOrder; i++) {
|
||||
left.add(tabOrder[i]);
|
||||
}
|
||||
int insertIndex = list.lastIndexWhere((e) => left.contains(e));
|
||||
if (insertIndex < 0) {
|
||||
insertIndex = 0;
|
||||
} else {
|
||||
insertIndex += 1;
|
||||
}
|
||||
list.insert(insertIndex, tabIndex);
|
||||
} else {
|
||||
list.add(tabIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final statePeerTab = StatePeerTab.instance;
|
||||
@ -177,11 +253,6 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
@ -213,42 +284,58 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
}
|
||||
|
||||
Widget _createSwitchBar(BuildContext context) {
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return Obx(() {
|
||||
var tabs = statePeerTab.currentTabs();
|
||||
return ListView(
|
||||
var tabs = statePeerTab.visibleOrderedTabs;
|
||||
int indexCounter = -1;
|
||||
return ReorderableListView(
|
||||
buildDefaultDragHandles: false,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
var list = tabs.toList();
|
||||
final int item = list.removeAt(oldIndex);
|
||||
list.insert(newIndex, item);
|
||||
tabs.value = list;
|
||||
statePeerTab.visibleOrderedTabs2TabOrder();
|
||||
},
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: ScrollController(),
|
||||
scrollController: ScrollController(),
|
||||
children: tabs.map((t) {
|
||||
return InkWell(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: statePeerTab.currentTab.value == t
|
||||
? Theme.of(context).backgroundColor
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
translatedTabname(t),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: statePeerTab.currentTab.value == t
|
||||
? textColor
|
||||
: textColor
|
||||
?..withOpacity(0.5)),
|
||||
indexCounter++;
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey(t),
|
||||
index: indexCounter,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: statePeerTab.currentTab.value == t
|
||||
? Theme.of(context).backgroundColor
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
|
||||
),
|
||||
)),
|
||||
onTap: () async {
|
||||
await handleTabSelection(t);
|
||||
await bind.setLocalFlutterConfig(
|
||||
k: 'peer-tab-index', v: t.toString());
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
translatedTabname(t),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: statePeerTab.currentTab.value == t
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
: MyTheme.tabbar(context).unSelectedTextColor
|
||||
?..withOpacity(0.5)),
|
||||
),
|
||||
)),
|
||||
onTap: () async {
|
||||
await handleTabSelection(t);
|
||||
await bind.setLocalFlutterConfig(
|
||||
k: 'peer-tab-index', v: t.toString());
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList());
|
||||
});
|
||||
@ -275,7 +362,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
final verticalMargin = isDesktop ? 12.0 : 6.0;
|
||||
return Expanded(
|
||||
child: Obx(() {
|
||||
var tabs = statePeerTab.currentTabs();
|
||||
var tabs = statePeerTab.visibleOrderedTabs;
|
||||
if (tabs.isEmpty) {
|
||||
return visibleContextMenuListener(Center(
|
||||
child: Text(translate('Right click to select tabs')),
|
||||
@ -322,7 +409,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
}
|
||||
|
||||
adjustTab() {
|
||||
var tabs = statePeerTab.currentTabs();
|
||||
var tabs = statePeerTab.visibleOrderedTabs;
|
||||
if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) {
|
||||
statePeerTab.currentTab.value = tabs[0];
|
||||
}
|
||||
@ -349,11 +436,13 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
||||
return Obx(() {
|
||||
final List<MenuEntryBase> menu = List.empty(growable: true);
|
||||
final List<int> menuIndex = List.empty(growable: true);
|
||||
for (int i = 0; i < statePeerTab.tabNames.length; i++) {
|
||||
if (i == groupTabIndex && statePeerTab.filterGroupCard()) {
|
||||
continue;
|
||||
}
|
||||
int bitMask = 1 << i;
|
||||
menuIndex.add(i);
|
||||
menu.add(MenuEntrySwitch(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translatedTabname(i),
|
||||
@ -369,12 +458,21 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
await bind.setLocalFlutterConfig(
|
||||
k: 'hidden-peer-card',
|
||||
v: statePeerTab.tabHiddenFlag.value.toRadixString(2));
|
||||
statePeerTab.tabOrder2visibleOrderedTabs();
|
||||
cancelFunc();
|
||||
adjustTab();
|
||||
}));
|
||||
}
|
||||
// show in tabOrder
|
||||
List<MenuEntryBase> menu2 = List.empty(growable: true);
|
||||
statePeerTab.tabOrder.map((e) {
|
||||
final index = menuIndex.indexOf(e);
|
||||
if (index >= 0) {
|
||||
menu2.add(menu[index]);
|
||||
}
|
||||
}).toList();
|
||||
return mod_menu.PopupMenu(
|
||||
items: menu
|
||||
items: menu2
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
@ -419,7 +517,12 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
||||
Widget _buildSearchBar() {
|
||||
RxBool focused = false.obs;
|
||||
FocusNode focusNode = FocusNode();
|
||||
focusNode.addListener(() => focused.value = focusNode.hasFocus);
|
||||
focusNode.addListener(() {
|
||||
focused.value = focusNode.hasFocus;
|
||||
peerSearchTextController.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: peerSearchTextController.value.text.length);
|
||||
});
|
||||
return Container(
|
||||
width: 120,
|
||||
decoration: BoxDecoration(
|
||||
|
@ -64,11 +64,9 @@ class RawPointerMouseRegion extends StatelessWidget {
|
||||
},
|
||||
onPointerMove: inputModel.onPointMoveImage,
|
||||
onPointerSignal: inputModel.onPointerSignalImage,
|
||||
/*
|
||||
onPointerPanZoomStart: inputModel.onPointerPanZoomStart,
|
||||
onPointerPanZoomUpdate: inputModel.onPointerPanZoomUpdate,
|
||||
onPointerPanZoomEnd: inputModel.onPointerPanZoomEnd,
|
||||
*/
|
||||
child: MouseRegion(
|
||||
cursor: cursor ?? MouseCursor.defer,
|
||||
onEnter: onEnter,
|
||||
|
@ -1,17 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
|
||||
const double kDesktopRemoteTabBarHeight = 28.0;
|
||||
const int kMainWindowId = 0;
|
||||
|
||||
const String kPeerPlatformWindows = "Windows";
|
||||
const String kPeerPlatformLinux = "Linux";
|
||||
const String kPeerPlatformMacOS = "Mac OS";
|
||||
const String kPeerPlatformAndroid = "Android";
|
||||
|
||||
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)' , 'Desktop CM Page', "Install Page"
|
||||
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page"
|
||||
const String kAppTypeMain = "main";
|
||||
const String kAppTypeConnectionManager = "cm";
|
||||
const String kAppTypeDesktopRemote = "remote";
|
||||
const String kAppTypeDesktopFileTransfer = "file transfer";
|
||||
const String kAppTypeDesktopPortForward = "port forward";
|
||||
@ -24,7 +26,6 @@ const String kWindowEventShow = "show";
|
||||
const String kWindowConnect = "connect";
|
||||
|
||||
const String kUniLinksPrefix = "rustdesk://";
|
||||
const String kActionNewConnection = "connection/new/";
|
||||
|
||||
const String kTabLabelHomePage = "Home";
|
||||
const String kTabLabelSettingPage = "Settings";
|
||||
|
@ -8,6 +8,7 @@ import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
@ -64,6 +65,8 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
});
|
||||
_idFocusNode.addListener(() {
|
||||
_idInputFocused.value = _idFocusNode.hasFocus;
|
||||
// select all to faciliate removing text, just following the behavior of address input of chrome
|
||||
_idController.selection = TextSelection(baseOffset: 0, extentOffset: _idController.value.text.length);
|
||||
});
|
||||
windowManager.addListener(this);
|
||||
}
|
||||
@ -90,6 +93,18 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowEnterFullScreen() {
|
||||
// Remove edge border by setting the value to zero.
|
||||
stateGlobal.resizeEdgeSize.value = 0;
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowLeaveFullScreen() {
|
||||
// Restore edge border to default edge size.
|
||||
stateGlobal.resizeEdgeSize.value = kWindowEdgeSize;
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowClose() {
|
||||
super.onWindowClose();
|
||||
|
@ -33,6 +33,7 @@ const double _kContentFontSize = 15;
|
||||
const Color _accentColor = MyTheme.accent;
|
||||
const String _kSettingPageControllerTag = 'settingPageController';
|
||||
const String _kSettingPageIndexTag = 'settingPageIndex';
|
||||
const int _kPageCount = 6;
|
||||
|
||||
class _TabInfo {
|
||||
late final String label;
|
||||
@ -51,7 +52,7 @@ class DesktopSettingPage extends StatefulWidget {
|
||||
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
|
||||
|
||||
static void switch2page(int page) {
|
||||
if (page >= 5) return;
|
||||
if (page >= _kPageCount) return;
|
||||
try {
|
||||
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
|
||||
DesktopTabPage.onAddSetting(initialPage: page);
|
||||
@ -75,6 +76,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
_TabInfo('Security', Icons.enhanced_encryption_outlined,
|
||||
Icons.enhanced_encryption),
|
||||
_TabInfo('Network', Icons.link_outlined, Icons.link),
|
||||
_TabInfo('Display', Icons.desktop_windows_outlined, Icons.desktop_windows),
|
||||
_TabInfo('Account', Icons.person_outline, Icons.person),
|
||||
_TabInfo('About', Icons.info_outline, Icons.info)
|
||||
];
|
||||
@ -88,7 +90,8 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedIndex = (widget.initialPage < 5 ? widget.initialPage : 0).obs;
|
||||
selectedIndex =
|
||||
(widget.initialPage < _kPageCount ? widget.initialPage : 0).obs;
|
||||
Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
|
||||
controller = PageController(initialPage: widget.initialPage);
|
||||
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
|
||||
@ -130,6 +133,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
_General(),
|
||||
_Safety(),
|
||||
_Network(),
|
||||
_Display(),
|
||||
_Account(),
|
||||
_About(),
|
||||
],
|
||||
@ -1047,6 +1051,247 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
}
|
||||
|
||||
class _Display extends StatefulWidget {
|
||||
const _Display({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_Display> createState() => _DisplayState();
|
||||
}
|
||||
|
||||
class _DisplayState extends State<_Display> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scrollController = ScrollController();
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
controller: scrollController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
viewStyle(context),
|
||||
scrollStyle(context),
|
||||
imageQuality(context),
|
||||
codec(context),
|
||||
other(context),
|
||||
]).marginOnly(bottom: _kListViewBottomMargin));
|
||||
}
|
||||
|
||||
Widget viewStyle(BuildContext context) {
|
||||
final key = 'view_style';
|
||||
onChanged(String value) async {
|
||||
await bind.mainSetUserDefaultOption(key: key, value: value);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
||||
return _Card(title: 'Default View Style', children: [
|
||||
_Radio(context,
|
||||
value: kRemoteViewStyleOriginal,
|
||||
groupValue: groupValue,
|
||||
label: 'Scale original',
|
||||
onChanged: onChanged),
|
||||
_Radio(context,
|
||||
value: kRemoteViewStyleAdaptive,
|
||||
groupValue: groupValue,
|
||||
label: 'Scale adaptive',
|
||||
onChanged: onChanged),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget scrollStyle(BuildContext context) {
|
||||
final key = 'scroll_style';
|
||||
onChanged(String value) async {
|
||||
await bind.mainSetUserDefaultOption(key: key, value: value);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
||||
return _Card(title: 'Default Scroll Style', children: [
|
||||
_Radio(context,
|
||||
value: kRemoteScrollStyleAuto,
|
||||
groupValue: groupValue,
|
||||
label: 'ScrollAuto',
|
||||
onChanged: onChanged),
|
||||
_Radio(context,
|
||||
value: kRemoteScrollStyleBar,
|
||||
groupValue: groupValue,
|
||||
label: 'Scrollbar',
|
||||
onChanged: onChanged),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget imageQuality(BuildContext context) {
|
||||
final key = 'image_quality';
|
||||
onChanged(String value) async {
|
||||
await bind.mainSetUserDefaultOption(key: key, value: value);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
||||
final qualityKey = 'custom_image_quality';
|
||||
final qualityValue =
|
||||
(double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ??
|
||||
50.0)
|
||||
.obs;
|
||||
final fpsKey = 'custom-fps';
|
||||
final fpsValue =
|
||||
(double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? 30.0)
|
||||
.obs;
|
||||
return _Card(title: 'Default Image Quality', children: [
|
||||
_Radio(context,
|
||||
value: kRemoteImageQualityBest,
|
||||
groupValue: groupValue,
|
||||
label: 'Good image quality',
|
||||
onChanged: onChanged),
|
||||
_Radio(context,
|
||||
value: kRemoteImageQualityBalanced,
|
||||
groupValue: groupValue,
|
||||
label: 'Balanced',
|
||||
onChanged: onChanged),
|
||||
_Radio(context,
|
||||
value: kRemoteImageQualityLow,
|
||||
groupValue: groupValue,
|
||||
label: 'Optimize reaction time',
|
||||
onChanged: onChanged),
|
||||
_Radio(context,
|
||||
value: kRemoteImageQualityCustom,
|
||||
groupValue: groupValue,
|
||||
label: 'Custom',
|
||||
onChanged: onChanged),
|
||||
Offstage(
|
||||
offstage: groupValue != kRemoteImageQualityCustom,
|
||||
child: Column(
|
||||
children: [
|
||||
Obx(() => Row(
|
||||
children: [
|
||||
Slider(
|
||||
value: qualityValue.value,
|
||||
min: 10.0,
|
||||
max: 100.0,
|
||||
divisions: 18,
|
||||
onChanged: (double value) async {
|
||||
qualityValue.value = value;
|
||||
await bind.mainSetUserDefaultOption(
|
||||
key: qualityKey, value: value.toString());
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: Text(
|
||||
'${qualityValue.value.round()}%',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)),
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: Text(
|
||||
translate('Bitrate'),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
))
|
||||
],
|
||||
)),
|
||||
Obx(() => Row(
|
||||
children: [
|
||||
Slider(
|
||||
value: fpsValue.value,
|
||||
min: 10.0,
|
||||
max: 120.0,
|
||||
divisions: 22,
|
||||
onChanged: (double value) async {
|
||||
fpsValue.value = value;
|
||||
await bind.mainSetUserDefaultOption(
|
||||
key: fpsKey, value: value.toString());
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: Text(
|
||||
'${fpsValue.value.round()}',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)),
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: Text(
|
||||
translate('FPS'),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
))
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
Widget codec(BuildContext context) {
|
||||
if (!bind.mainHasHwcodec()) {
|
||||
return Offstage();
|
||||
}
|
||||
final key = 'codec-preference';
|
||||
onChanged(String value) async {
|
||||
await bind.mainSetUserDefaultOption(key: key, value: value);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
||||
|
||||
return _Card(title: 'Default Codec', children: [
|
||||
_Radio(context,
|
||||
value: 'auto',
|
||||
groupValue: groupValue,
|
||||
label: 'Auto',
|
||||
onChanged: onChanged),
|
||||
_Radio(context,
|
||||
value: 'vp9',
|
||||
groupValue: groupValue,
|
||||
label: 'VP9',
|
||||
onChanged: onChanged),
|
||||
_Radio(context,
|
||||
value: 'h264',
|
||||
groupValue: groupValue,
|
||||
label: 'H264',
|
||||
onChanged: onChanged),
|
||||
_Radio(context,
|
||||
value: 'h265',
|
||||
groupValue: groupValue,
|
||||
label: 'H265',
|
||||
onChanged: onChanged),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget otherRow(String label, String key) {
|
||||
final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
|
||||
onChanged(bool b) async {
|
||||
await bind.mainSetUserDefaultOption(key: key, value: b ? 'Y' : '');
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(value: value, onChanged: (_) => onChanged(!value))
|
||||
.marginOnly(right: 5),
|
||||
Expanded(
|
||||
child: Text(translate(label)),
|
||||
)
|
||||
],
|
||||
).marginOnly(left: _kCheckBoxLeftMargin),
|
||||
onTap: () => onChanged(!value));
|
||||
}
|
||||
|
||||
Widget other(BuildContext context) {
|
||||
return _Card(title: 'Other Default Options', children: [
|
||||
otherRow('Show remote cursor', 'show_remote_cursor'),
|
||||
otherRow('Zoom cursor', 'zoom-cursor'),
|
||||
otherRow('Show quality monitor', 'show_quality_monitor'),
|
||||
otherRow('Mute', 'disable_audio'),
|
||||
otherRow('Allow file copy and paste', 'enable_file_transfer'),
|
||||
otherRow('Disable clipboard', 'disable_clipboard'),
|
||||
otherRow('Lock after session end', 'lock_after_session_end'),
|
||||
otherRow('Privacy mode', 'privacy_mode'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class _Account extends StatefulWidget {
|
||||
const _Account({Key? key}) : super(key: key);
|
||||
|
||||
@ -1113,10 +1358,12 @@ class _AboutState extends State<_About> {
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Text('${translate('Version')}: $version')
|
||||
.marginSymmetric(vertical: 4.0),
|
||||
Text('${translate('Build Date')}: $buildDate')
|
||||
.marginSymmetric(vertical: 4.0),
|
||||
SelectionArea(
|
||||
child: Text('${translate('Version')}: $version')
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
SelectionArea(
|
||||
child: Text('${translate('Build Date')}: $buildDate')
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
launchUrlString('https://rustdesk.com/privacy');
|
||||
@ -1137,7 +1384,8 @@ class _AboutState extends State<_About> {
|
||||
decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
|
||||
child: Row(
|
||||
child: SelectionArea(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
@ -1157,7 +1405,7 @@ class _AboutState extends State<_About> {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
).marginSymmetric(vertical: 4.0)
|
||||
],
|
||||
).marginOnly(left: _kContentHMargin)
|
||||
|
@ -23,7 +23,7 @@ class DesktopTabPage extends StatefulWidget {
|
||||
DesktopTabController tabController = Get.find();
|
||||
tabController.add(TabInfo(
|
||||
key: kTabLabelSettingPage,
|
||||
label: kTabLabelSettingPage,
|
||||
label: translate(kTabLabelSettingPage),
|
||||
selectedIcon: Icons.build_sharp,
|
||||
unselectedIcon: Icons.build_outlined,
|
||||
page: DesktopSettingPage(
|
||||
@ -46,7 +46,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
RemoteCountState.init();
|
||||
tabController.add(TabInfo(
|
||||
key: kTabLabelHomePage,
|
||||
label: kTabLabelHomePage,
|
||||
label: translate(kTabLabelHomePage),
|
||||
selectedIcon: Icons.home_sharp,
|
||||
unselectedIcon: Icons.home_outlined,
|
||||
closable: false,
|
||||
|
@ -3,7 +3,6 @@ import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_custom_cursor/cursor_manager.dart'
|
||||
@ -279,6 +278,34 @@ class _RemotePageState extends State<RemotePage>
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildRawPointerMouseRegion(
|
||||
Widget child,
|
||||
PointerEnterEventListener? onEnter,
|
||||
PointerExitEventListener? onExit,
|
||||
) {
|
||||
return RawPointerMouseRegion(
|
||||
onEnter: enterView,
|
||||
onExit: leaveView,
|
||||
onPointerDown: (event) {
|
||||
// A double check for blur status.
|
||||
// Note: If there's an `onPointerDown` event is triggered, `_isWindowBlur` is expected being false.
|
||||
// Sometimes the system does not send the necessary focus event to flutter. We should manually
|
||||
// handle this inconsistent status by setting `_isWindowBlur` to false. So we can
|
||||
// ensure the grab-key thread is running when our users are clicking the remote canvas.
|
||||
if (_isWindowBlur) {
|
||||
debugPrint(
|
||||
"Unexpected status: onPointerDown is triggered while the remote window is in blur status");
|
||||
_isWindowBlur = false;
|
||||
}
|
||||
if (!_rawKeyFocusNode.hasFocus) {
|
||||
_rawKeyFocusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
inputModel: _ffi.inputModel,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget getBodyForDesktop(BuildContext context) {
|
||||
var paints = <Widget>[
|
||||
MouseRegion(onEnter: (evt) {
|
||||
@ -295,27 +322,8 @@ class _RemotePageState extends State<RemotePage>
|
||||
cursorOverImage: _cursorOverImage,
|
||||
keyboardEnabled: _keyboardEnabled,
|
||||
remoteCursorMoved: _remoteCursorMoved,
|
||||
listenerBuilder: (child) => RawPointerMouseRegion(
|
||||
onEnter: enterView,
|
||||
onExit: leaveView,
|
||||
onPointerDown: (event) {
|
||||
// A double check for blur status.
|
||||
// Note: If there's an `onPointerDown` event is triggered, `_isWindowBlur` is expected being false.
|
||||
// Sometimes the system does not send the necessary focus event to flutter. We should manually
|
||||
// handle this inconsistent status by setting `_isWindowBlur` to false. So we can
|
||||
// ensure the grab-key thread is running when our users are clicking the remote canvas.
|
||||
if (_isWindowBlur) {
|
||||
debugPrint(
|
||||
"Unexpected status: onPointerDown is triggered while the remote window is in blur status");
|
||||
_isWindowBlur = false;
|
||||
}
|
||||
if (!_rawKeyFocusNode.hasFocus) {
|
||||
_rawKeyFocusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
inputModel: _ffi.inputModel,
|
||||
child: child,
|
||||
),
|
||||
listenerBuilder: (child) =>
|
||||
_buildRawPointerMouseRegion(child, enterView, leaveView),
|
||||
);
|
||||
}))
|
||||
];
|
||||
@ -328,7 +336,14 @@ class _RemotePageState extends State<RemotePage>
|
||||
zoomCursor: _zoomCursor,
|
||||
))));
|
||||
}
|
||||
paints.add(QualityMonitor(_ffi.qualityMonitorModel));
|
||||
paints.add(
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 10,
|
||||
child: _buildRawPointerMouseRegion(
|
||||
QualityMonitor(_ffi.qualityMonitorModel), null, null),
|
||||
),
|
||||
);
|
||||
paints.add(RemoteMenubar(
|
||||
id: widget.id,
|
||||
ffi: _ffi,
|
||||
@ -347,10 +362,10 @@ class _RemotePageState extends State<RemotePage>
|
||||
|
||||
class ImagePaint extends StatefulWidget {
|
||||
final String id;
|
||||
final Rx<bool> zoomCursor;
|
||||
final Rx<bool> cursorOverImage;
|
||||
final Rx<bool> keyboardEnabled;
|
||||
final Rx<bool> remoteCursorMoved;
|
||||
final RxBool zoomCursor;
|
||||
final RxBool cursorOverImage;
|
||||
final RxBool keyboardEnabled;
|
||||
final RxBool remoteCursorMoved;
|
||||
final Widget Function(Widget)? listenerBuilder;
|
||||
|
||||
ImagePaint(
|
||||
@ -373,10 +388,10 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
final ScrollController _vertical = ScrollController();
|
||||
|
||||
String get id => widget.id;
|
||||
Rx<bool> get zoomCursor => widget.zoomCursor;
|
||||
Rx<bool> get cursorOverImage => widget.cursorOverImage;
|
||||
Rx<bool> get keyboardEnabled => widget.keyboardEnabled;
|
||||
Rx<bool> get remoteCursorMoved => widget.remoteCursorMoved;
|
||||
RxBool get zoomCursor => widget.zoomCursor;
|
||||
RxBool get cursorOverImage => widget.cursorOverImage;
|
||||
RxBool get keyboardEnabled => widget.keyboardEnabled;
|
||||
RxBool get remoteCursorMoved => widget.remoteCursorMoved;
|
||||
Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder;
|
||||
|
||||
@override
|
||||
@ -385,27 +400,50 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
var c = Provider.of<CanvasModel>(context);
|
||||
final s = c.scale;
|
||||
|
||||
mouseRegion({child}) => Obx(() => MouseRegion(
|
||||
cursor: cursorOverImage.isTrue
|
||||
? c.cursorEmbedded
|
||||
? SystemMouseCursors.none
|
||||
: keyboardEnabled.isTrue
|
||||
? (() {
|
||||
if (remoteCursorMoved.isTrue) {
|
||||
_lastRemoteCursorMoved = true;
|
||||
return SystemMouseCursors.none;
|
||||
} else {
|
||||
if (_lastRemoteCursorMoved) {
|
||||
_lastRemoteCursorMoved = false;
|
||||
_firstEnterImage.value = true;
|
||||
}
|
||||
return _buildCustomCursor(context, s);
|
||||
}
|
||||
}())
|
||||
: _buildDisabledCursor(context, s)
|
||||
: MouseCursor.defer,
|
||||
onHover: (evt) {},
|
||||
child: child));
|
||||
mouseRegion({child}) => Obx(() {
|
||||
double getCursorScale() {
|
||||
var c = Provider.of<CanvasModel>(context);
|
||||
var cursorScale = 1.0;
|
||||
if (Platform.isWindows) {
|
||||
// debug win10
|
||||
final isViewAdaptive =
|
||||
c.viewStyle.style == kRemoteViewStyleAdaptive;
|
||||
if (zoomCursor.value && isViewAdaptive) {
|
||||
cursorScale = s * c.devicePixelRatio;
|
||||
}
|
||||
} else {
|
||||
final isViewOriginal =
|
||||
c.viewStyle.style == kRemoteViewStyleOriginal;
|
||||
if (zoomCursor.value || isViewOriginal) {
|
||||
cursorScale = s;
|
||||
}
|
||||
}
|
||||
return cursorScale;
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: cursorOverImage.isTrue
|
||||
? c.cursorEmbedded
|
||||
? SystemMouseCursors.none
|
||||
: keyboardEnabled.isTrue
|
||||
? (() {
|
||||
if (remoteCursorMoved.isTrue) {
|
||||
_lastRemoteCursorMoved = true;
|
||||
return SystemMouseCursors.none;
|
||||
} else {
|
||||
if (_lastRemoteCursorMoved) {
|
||||
_lastRemoteCursorMoved = false;
|
||||
_firstEnterImage.value = true;
|
||||
}
|
||||
return _buildCustomCursor(
|
||||
context, getCursorScale());
|
||||
}
|
||||
}())
|
||||
: _buildDisabledCursor(context, getCursorScale())
|
||||
: MouseCursor.defer,
|
||||
onHover: (evt) {},
|
||||
child: child);
|
||||
});
|
||||
|
||||
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
|
||||
final imageWidth = c.getDisplayWidth() * s;
|
||||
@ -451,7 +489,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
if (cache == null) {
|
||||
return MouseCursor.defer;
|
||||
} else {
|
||||
final key = cache.updateGetKey(scale, zoomCursor.value);
|
||||
final key = cache.updateGetKey(scale);
|
||||
if (!cursor.cachedKeys.contains(key)) {
|
||||
debugPrint("Register custom cursor with key $key");
|
||||
// [Safety]
|
||||
@ -617,7 +655,8 @@ class CursorPaint extends StatelessWidget {
|
||||
double x = (m.x - hotx) * c.scale + cx;
|
||||
double y = (m.y - hoty) * c.scale + cy;
|
||||
double scale = 1.0;
|
||||
if (zoomCursor.isTrue) {
|
||||
final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal;
|
||||
if (zoomCursor.value || isViewOriginal) {
|
||||
x = m.x - hotx + cx / c.scale;
|
||||
y = m.y - hoty + cy / c.scale;
|
||||
scale = c.scale;
|
||||
|
@ -38,8 +38,9 @@ class ConnectionTabPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
final tabController = Get.put(DesktopTabController(
|
||||
tabType: DesktopTabType.remoteScreen));
|
||||
final tabController =
|
||||
Get.put(DesktopTabController(tabType: DesktopTabType.remoteScreen));
|
||||
final contentKey = UniqueKey();
|
||||
static const IconData selectedIcon = Icons.desktop_windows_sharp;
|
||||
static const IconData unselectedIcon = Icons.desktop_windows_outlined;
|
||||
|
||||
@ -80,7 +81,6 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
super.initState();
|
||||
|
||||
tabController.onRemoved = (_, id) => onRemoveId(id);
|
||||
|
||||
|
||||
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
|
||||
print(
|
||||
@ -113,6 +113,9 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
}
|
||||
_update_remote_count();
|
||||
});
|
||||
Future.delayed(Duration.zero, () {
|
||||
restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId());
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -197,11 +200,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
);
|
||||
return Platform.isMacOS
|
||||
? tabWidget
|
||||
: SubWindowDragToResizeArea(
|
||||
child: tabWidget,
|
||||
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
||||
windowId: stateGlobal.windowId,
|
||||
);
|
||||
: Obx(() => SubWindowDragToResizeArea(
|
||||
key: contentKey,
|
||||
child: tabWidget,
|
||||
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
||||
windowId: stateGlobal.windowId,
|
||||
));
|
||||
}
|
||||
|
||||
// Note: Some dup code to ../widgets/remote_menubar
|
||||
@ -269,6 +273,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
menu.add(MenuEntryDivider<String>());
|
||||
menu.add(() {
|
||||
final state = ShowRemoteCursorState.find(key);
|
||||
final optKey = 'show-remote-cursor';
|
||||
return MenuEntrySwitch2<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Show remote cursor'),
|
||||
@ -276,9 +281,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
state.value = v;
|
||||
await bind.sessionToggleOption(
|
||||
id: key, value: 'show-remote-cursor');
|
||||
await bind.sessionToggleOption(id: key, value: optKey);
|
||||
state.value = bind.sessionGetToggleOptionSync(id: key, arg: optKey);
|
||||
cancelFunc();
|
||||
},
|
||||
padding: padding,
|
||||
|
@ -790,6 +790,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
_PopupMenuRoute({
|
||||
required this.position,
|
||||
required this.items,
|
||||
this.menuWrapper,
|
||||
this.initialValue,
|
||||
this.elevation,
|
||||
required this.barrierLabel,
|
||||
@ -802,6 +803,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
|
||||
final RelativeRect position;
|
||||
final List<PopupMenuEntry<T>> items;
|
||||
final MenuWrapper? menuWrapper;
|
||||
final List<Size?> itemSizes;
|
||||
final T? initialValue;
|
||||
final double? elevation;
|
||||
@ -844,11 +846,14 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
}
|
||||
}
|
||||
|
||||
final Widget menu = _PopupMenu<T>(
|
||||
Widget menu = _PopupMenu<T>(
|
||||
route: this,
|
||||
semanticLabel: semanticLabel,
|
||||
constraints: constraints,
|
||||
);
|
||||
if (this.menuWrapper != null) {
|
||||
menu = this.menuWrapper!(menu);
|
||||
}
|
||||
final MediaQueryData mediaQuery = MediaQuery.of(context);
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
@ -1035,6 +1040,7 @@ Future<T?> showMenu<T>({
|
||||
required BuildContext context,
|
||||
required RelativeRect position,
|
||||
required List<PopupMenuEntry<T>> items,
|
||||
MenuWrapper? menuWrapper,
|
||||
T? initialValue,
|
||||
double? elevation,
|
||||
String? semanticLabel,
|
||||
@ -1062,6 +1068,7 @@ Future<T?> showMenu<T>({
|
||||
return navigator.push(_PopupMenuRoute<T>(
|
||||
position: position,
|
||||
items: items,
|
||||
menuWrapper: menuWrapper,
|
||||
initialValue: initialValue,
|
||||
elevation: elevation,
|
||||
semanticLabel: semanticLabel,
|
||||
@ -1094,6 +1101,8 @@ typedef PopupMenuCanceled = void Function();
|
||||
typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(
|
||||
BuildContext context);
|
||||
|
||||
typedef MenuWrapper = Widget Function(Widget child);
|
||||
|
||||
/// Displays a menu when pressed and calls [onSelected] when the menu is dismissed
|
||||
/// because an item was selected. The value passed to [onSelected] is the value of
|
||||
/// the selected menu item.
|
||||
@ -1124,6 +1133,7 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
const PopupMenuButton({
|
||||
Key? key,
|
||||
required this.itemBuilder,
|
||||
this.menuWrapper,
|
||||
this.initialValue,
|
||||
this.onHover,
|
||||
this.onSelected,
|
||||
@ -1151,6 +1161,9 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
/// Called when the button is pressed to create the items to show in the menu.
|
||||
final PopupMenuItemBuilder<T> itemBuilder;
|
||||
|
||||
/// Menu wrapper.
|
||||
final MenuWrapper? menuWrapper;
|
||||
|
||||
/// The value of the menu item, if any, that should be highlighted when the menu opens.
|
||||
final T? initialValue;
|
||||
|
||||
@ -1333,6 +1346,7 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
context: context,
|
||||
elevation: widget.elevation ?? popupMenuTheme.elevation,
|
||||
items: items,
|
||||
menuWrapper: widget.menuWrapper,
|
||||
initialValue: widget.initialValue,
|
||||
position: position,
|
||||
shape: widget.shape ?? popupMenuTheme.shape,
|
||||
|
@ -109,13 +109,17 @@ class MenuConfig {
|
||||
this.boxWidth});
|
||||
}
|
||||
|
||||
typedef DismissCallback = Function();
|
||||
|
||||
abstract class MenuEntryBase<T> {
|
||||
bool dismissOnClicked;
|
||||
DismissCallback? dismissCallback;
|
||||
RxBool? enabled;
|
||||
|
||||
MenuEntryBase({
|
||||
this.dismissOnClicked = false,
|
||||
this.enabled,
|
||||
this.dismissCallback,
|
||||
});
|
||||
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
|
||||
|
||||
@ -146,12 +150,14 @@ class MenuEntryRadioOption {
|
||||
String value;
|
||||
bool dismissOnClicked;
|
||||
RxBool? enabled;
|
||||
DismissCallback? dismissCallback;
|
||||
|
||||
MenuEntryRadioOption({
|
||||
required this.text,
|
||||
required this.value,
|
||||
this.dismissOnClicked = false,
|
||||
this.enabled,
|
||||
this.dismissCallback,
|
||||
});
|
||||
}
|
||||
|
||||
@ -177,8 +183,13 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
required this.optionSetter,
|
||||
this.padding,
|
||||
dismissOnClicked = false,
|
||||
dismissCallback,
|
||||
RxBool? enabled,
|
||||
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
|
||||
}) : super(
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
) {
|
||||
() async {
|
||||
_curOption.value = await curOptionGetter();
|
||||
}();
|
||||
@ -249,6 +260,9 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
onPressed() {
|
||||
if (opt.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (opt.dismissCallback != null) {
|
||||
opt.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(opt.value);
|
||||
}
|
||||
@ -360,6 +374,9 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
onPressed: () {
|
||||
if (opt.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (opt.dismissCallback != null) {
|
||||
opt.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(opt.value);
|
||||
},
|
||||
@ -421,7 +438,12 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
this.textStyle,
|
||||
this.padding,
|
||||
RxBool? enabled,
|
||||
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
|
||||
RxBool get curOption;
|
||||
Future<void> setOption(bool? option);
|
||||
@ -463,6 +485,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
if (super.dismissOnClicked &&
|
||||
Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (super.dismissCallback != null) {
|
||||
super.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(v);
|
||||
},
|
||||
@ -474,6 +499,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
if (super.dismissOnClicked &&
|
||||
Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (super.dismissCallback != null) {
|
||||
super.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(v);
|
||||
},
|
||||
@ -485,6 +513,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
onPressed: () {
|
||||
if (super.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (super.dismissCallback != null) {
|
||||
super.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(!curOption.value);
|
||||
},
|
||||
@ -508,6 +539,7 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
EdgeInsets? padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
@ -515,6 +547,7 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
) {
|
||||
() async {
|
||||
_curOption.value = await getter();
|
||||
@ -551,12 +584,15 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
|
||||
EdgeInsets? padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
textStyle: textStyle,
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked);
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
textStyle: textStyle,
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
|
||||
@override
|
||||
RxBool get curOption => getter();
|
||||
@ -627,9 +663,11 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
this.padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
|
||||
Widget _buildChild(BuildContext context, MenuConfig conf) {
|
||||
@ -641,6 +679,9 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
? () {
|
||||
if (super.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (super.dismissCallback != null) {
|
||||
super.dismissCallback!();
|
||||
}
|
||||
}
|
||||
proc();
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class RefreshWrapper extends StatefulWidget {
|
||||
final Widget Function(BuildContext context) builder;
|
||||
|
||||
const RefreshWrapper({super.key, required this.builder});
|
||||
|
||||
@override
|
||||
@ -30,6 +32,8 @@ class RefreshWrapperState extends State<RefreshWrapper> {
|
||||
if (Get.context != null) {
|
||||
(context as Element).visitChildren(_rebuildElement);
|
||||
}
|
||||
// Synchronize the window theme of the system.
|
||||
updateSystemWindowTheme();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
|
@ -221,6 +221,18 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildPointerTrackWidget(Widget child) {
|
||||
return Listener(
|
||||
onPointerHover: (PointerHoverEvent e) =>
|
||||
widget.ffi.inputModel.lastMousePos = e.position,
|
||||
child: MouseRegion(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_menuDismissCallback() => widget.ffi.inputModel.refreshMousePos();
|
||||
|
||||
Widget _buildMenubar(BuildContext context) {
|
||||
final List<Widget> menubarItems = [];
|
||||
if (!isWebDesktop) {
|
||||
@ -362,6 +374,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
),
|
||||
)),
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
_menuDismissCallback();
|
||||
}
|
||||
RxInt display = CurrentDisplayState.find(widget.id);
|
||||
if (display.value != i) {
|
||||
bind.sessionSwitchDisplay(id: widget.id, value: i);
|
||||
@ -376,9 +392,12 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
mod_menu.PopupMenuItem<String>(
|
||||
height: _MenubarTheme.height,
|
||||
padding: EdgeInsets.zero,
|
||||
child: Row(
|
||||
child: _buildPointerTrackWidget(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: rowChildren),
|
||||
children: rowChildren,
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
},
|
||||
@ -426,6 +445,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
),
|
||||
tooltip: translate('Display Settings'),
|
||||
position: mod_menu.PopupMenuPosition.under,
|
||||
menuWrapper: _buildPointerTrackWidget,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
_getDisplayMenu(snapshot.data!, remoteCount)
|
||||
.map((entry) => entry.build(
|
||||
@ -534,6 +554,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
_menuDismissCallback();
|
||||
}
|
||||
showSetOSPassword(
|
||||
widget.id, false, widget.ffi.dialogManager);
|
||||
@ -546,6 +567,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
@ -557,6 +579,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
@ -568,6 +591,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
connect(context, widget.id, isTcpTunneling: true);
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
]);
|
||||
// {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
|
||||
@ -585,6 +609,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -601,6 +626,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -618,6 +644,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
|
||||
@ -632,6 +659,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
|
||||
if (pi.platform == kPeerPlatformWindows) {
|
||||
@ -650,9 +678,11 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
if (pi.platform != kPeerPlatformAndroid &&
|
||||
pi.platform != kPeerPlatformMacOS && // unsupport yet
|
||||
version_cmp(peer_version, '1.2.0') >= 0) {
|
||||
displayMenu.add(MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
@ -663,6 +693,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
showConfirmSwitchSidesDialog(widget.id, widget.ffi.dialogManager),
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -678,6 +709,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
|
||||
@ -699,6 +731,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
// },
|
||||
// padding: padding,
|
||||
// dismissOnClicked: true,
|
||||
// dismissCallback: _menuDismissCallback,
|
||||
// ));
|
||||
// }
|
||||
}
|
||||
@ -744,11 +777,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('Scale original'),
|
||||
value: kRemoteViewStyleOriginal,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale adaptive'),
|
||||
value: kRemoteViewStyleAdaptive,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
@ -764,6 +799,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryDivider<String>(),
|
||||
MenuEntryRadios<String>(
|
||||
@ -773,21 +809,26 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('Good image quality'),
|
||||
value: kRemoteImageQualityBest,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Balanced'),
|
||||
value: kRemoteImageQualityBalanced,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Optimize reaction time'),
|
||||
value: kRemoteImageQualityLow,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Custom'),
|
||||
value: kRemoteImageQualityCustom,
|
||||
dismissOnClicked: true),
|
||||
text: translate('Custom'),
|
||||
value: kRemoteImageQualityCustom,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async =>
|
||||
// null means peer id is not found, which there's no need to care about
|
||||
@ -857,18 +898,24 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
value: qualitySliderValue.value,
|
||||
min: qualityMinValue,
|
||||
max: qualityMaxValue,
|
||||
divisions: 90,
|
||||
divisions: 18,
|
||||
onChanged: (double value) {
|
||||
qualitySliderValue.value = value;
|
||||
debouncerQuality.value = value;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Obx(() => Text(
|
||||
'${qualitySliderValue.value.round()}% Bitrate',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)))
|
||||
width: 40,
|
||||
child: Text(
|
||||
'${qualitySliderValue.value.round()}%',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)),
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: Text(
|
||||
translate('Bitrate'),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
))
|
||||
],
|
||||
));
|
||||
// fps
|
||||
@ -909,20 +956,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
))),
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Obx(() {
|
||||
final fps = fpsSliderValue.value.round();
|
||||
String text;
|
||||
if (fps < 100) {
|
||||
text = '$fps FPS';
|
||||
} else {
|
||||
text = '$fps FPS';
|
||||
}
|
||||
return Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
);
|
||||
}))
|
||||
width: 40,
|
||||
child: Obx(() => Text(
|
||||
'${fpsSliderValue.value.round()}',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
))),
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: Text(
|
||||
translate('FPS'),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -949,12 +993,14 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('ScrollAuto'),
|
||||
value: kRemoteScrollStyleAuto,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
enabled: widget.ffi.canvasModel.imageOverflow,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scrollbar'),
|
||||
value: kRemoteScrollStyleBar,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
enabled: widget.ffi.canvasModel.imageOverflow,
|
||||
),
|
||||
],
|
||||
@ -967,6 +1013,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
displayMenu.insert(3, MenuEntryDivider<String>());
|
||||
|
||||
@ -1037,6 +1084,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1063,11 +1111,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('Auto'),
|
||||
value: 'auto',
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: 'VP9',
|
||||
value: 'vp9',
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
];
|
||||
if (codecs[0]) {
|
||||
@ -1075,6 +1125,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: 'H264',
|
||||
value: 'h264',
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
if (codecs[1]) {
|
||||
@ -1082,6 +1133,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: 'H265',
|
||||
value: 'h265',
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
return list;
|
||||
@ -1098,14 +1150,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
}
|
||||
displayMenu.add(MenuEntryDivider());
|
||||
|
||||
/// Show remote cursor
|
||||
if (!widget.ffi.canvasModel.cursorEmbedded) {
|
||||
displayMenu.add(() {
|
||||
final state = ShowRemoteCursorState.find(widget.id);
|
||||
final optKey = 'show-remote-cursor';
|
||||
return MenuEntrySwitch2<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Show remote cursor'),
|
||||
@ -1113,12 +1168,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
state.value = v;
|
||||
await bind.sessionToggleOption(
|
||||
id: widget.id, value: 'show-remote-cursor');
|
||||
await bind.sessionToggleOption(id: widget.id, value: optKey);
|
||||
state.value =
|
||||
bind.sessionGetToggleOptionSync(id: widget.id, arg: optKey);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
);
|
||||
}());
|
||||
}
|
||||
@ -1135,11 +1191,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
state.value = v;
|
||||
await bind.sessionToggleOption(id: widget.id, value: opt);
|
||||
state.value =
|
||||
bind.sessionGetToggleOptionSync(id: widget.id, arg: opt);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
);
|
||||
}());
|
||||
}
|
||||
@ -1159,6 +1217,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
|
||||
final perms = widget.ffi.ffiModel.permissions;
|
||||
@ -1196,6 +1255,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -1267,6 +1327,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
_menuDismissCallback();
|
||||
}
|
||||
showKBLayoutTypeChooser(
|
||||
localPlatform, widget.ffi.dialogManager);
|
||||
@ -1279,6 +1340,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {},
|
||||
padding: EdgeInsets.zero,
|
||||
dismissOnClicked: false,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1298,6 +1360,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1422,12 +1485,8 @@ void showConfirmSwitchSidesDialog(
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('Switch Sides')),
|
||||
content: Column(
|
||||
children: [
|
||||
Text(translate('Please confirm if you want to share your desktop?')),
|
||||
],
|
||||
),
|
||||
content: msgboxContent('info', 'Switch Sides',
|
||||
'Please confirm if you want to share your desktop?'),
|
||||
actions: [
|
||||
dialogButton('Cancel', onPressed: close, isOutline: true),
|
||||
dialogButton('OK', onPressed: submit),
|
||||
|
@ -1,23 +1,23 @@
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' hide TabBarTheme;
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
||||
import 'package:scroll_pos/scroll_pos.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
|
||||
import '../../utils/multi_window_manager.dart';
|
||||
|
||||
@ -527,7 +527,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
void onWindowClose() async {
|
||||
// hide window on close
|
||||
if (widget.isMainWindow) {
|
||||
await rustDeskWinManager.unregisterActiveWindow(0);
|
||||
if (rustDeskWinManager.getActiveWindows().contains(kMainWindowId)) {
|
||||
await rustDeskWinManager.unregisterActiveWindow(kMainWindowId);
|
||||
}
|
||||
// `hide` must be placed after unregisterActiveWindow, because once all windows are hidden,
|
||||
// flutter closes the application on macOS. We should ensure the post-run logic has ran successfully.
|
||||
// e.g.: saving window position.
|
||||
@ -765,7 +767,7 @@ class _ListView extends StatelessWidget {
|
||||
tabBuilder: tabBuilder,
|
||||
tabMenuBuilder: tabMenuBuilder,
|
||||
maxLabelWidth: maxLabelWidth,
|
||||
selectedTabBackgroundColor: selectedTabBackgroundColor,
|
||||
selectedTabBackgroundColor: selectedTabBackgroundColor ?? MyTheme.tabbar(context).selectedTabBackgroundColor,
|
||||
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
|
||||
);
|
||||
}).toList()));
|
||||
@ -910,7 +912,7 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
tabSelected: isSelected,
|
||||
onClose: () => widget.onClose(),
|
||||
)))
|
||||
])).paddingSymmetric(horizontal: 10),
|
||||
])).paddingOnly(left: 10, right: 5),
|
||||
Offstage(
|
||||
offstage: !showDivider,
|
||||
child: VerticalDivider(
|
||||
@ -956,7 +958,8 @@ class _CloseButton extends StatelessWidget {
|
||||
child: Offstage(
|
||||
offstage: !visible,
|
||||
child: InkWell(
|
||||
customBorder: const RoundedRectangleBorder(),
|
||||
hoverColor: MyTheme.tabbar(context).closeHoverColor,
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () => onClose(),
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
@ -966,7 +969,7 @@ class _CloseButton extends StatelessWidget {
|
||||
: MyTheme.tabbar(context).unSelectedIconColor,
|
||||
),
|
||||
),
|
||||
)).paddingOnly(left: 5);
|
||||
)).paddingOnly(left: 10);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1055,6 +1058,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
final Color? unSelectedIconColor;
|
||||
final Color? dividerColor;
|
||||
final Color? hoverColor;
|
||||
final Color? closeHoverColor;
|
||||
final Color? selectedTabBackgroundColor;
|
||||
|
||||
const TabbarTheme(
|
||||
{required this.selectedTabIconColor,
|
||||
@ -1064,27 +1069,33 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
required this.selectedIconColor,
|
||||
required this.unSelectedIconColor,
|
||||
required this.dividerColor,
|
||||
required this.hoverColor});
|
||||
required this.hoverColor,
|
||||
required this.closeHoverColor,
|
||||
required this.selectedTabBackgroundColor});
|
||||
|
||||
static const light = TabbarTheme(
|
||||
selectedTabIconColor: MyTheme.accent,
|
||||
unSelectedTabIconColor: Color.fromARGB(255, 162, 203, 241),
|
||||
selectedTextColor: Color.fromARGB(255, 26, 26, 26),
|
||||
unSelectedTextColor: Color.fromARGB(255, 96, 96, 96),
|
||||
selectedTextColor: Colors.black,
|
||||
unSelectedTextColor: Color.fromARGB(255, 112, 112, 112),
|
||||
selectedIconColor: Color.fromARGB(255, 26, 26, 26),
|
||||
unSelectedIconColor: Color.fromARGB(255, 96, 96, 96),
|
||||
dividerColor: Color.fromARGB(255, 238, 238, 238),
|
||||
hoverColor: Color.fromARGB(51, 158, 158, 158));
|
||||
hoverColor: Color.fromARGB(51, 158, 158, 158),
|
||||
closeHoverColor: Color.fromARGB(255, 224, 224, 224),
|
||||
selectedTabBackgroundColor: Color.fromARGB(255, 240, 240, 240));
|
||||
|
||||
static const dark = TabbarTheme(
|
||||
selectedTabIconColor: MyTheme.accent,
|
||||
unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98),
|
||||
selectedTextColor: Color.fromARGB(255, 255, 255, 255),
|
||||
unSelectedTextColor: Color.fromARGB(255, 207, 207, 207),
|
||||
selectedIconColor: Color.fromARGB(255, 215, 215, 215),
|
||||
unSelectedTextColor: Color.fromARGB(255, 192, 192, 192),
|
||||
selectedIconColor: Color.fromARGB(255, 192, 192, 192),
|
||||
unSelectedIconColor: Color.fromARGB(255, 255, 255, 255),
|
||||
dividerColor: Color.fromARGB(255, 64, 64, 64),
|
||||
hoverColor: Colors.black26);
|
||||
hoverColor: Colors.black26,
|
||||
closeHoverColor: Colors.black,
|
||||
selectedTabBackgroundColor: Colors.black26);
|
||||
|
||||
@override
|
||||
ThemeExtension<TabbarTheme> copyWith({
|
||||
@ -1096,6 +1107,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
Color? unSelectedIconColor,
|
||||
Color? dividerColor,
|
||||
Color? hoverColor,
|
||||
Color? closeHoverColor,
|
||||
Color? selectedTabBackgroundColor,
|
||||
}) {
|
||||
return TabbarTheme(
|
||||
selectedTabIconColor: selectedTabIconColor ?? this.selectedTabIconColor,
|
||||
@ -1107,6 +1120,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
unSelectedIconColor: unSelectedIconColor ?? this.unSelectedIconColor,
|
||||
dividerColor: dividerColor ?? this.dividerColor,
|
||||
hoverColor: hoverColor ?? this.hoverColor,
|
||||
closeHoverColor: closeHoverColor ?? this.closeHoverColor,
|
||||
selectedTabBackgroundColor: selectedTabBackgroundColor ?? this.selectedTabBackgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1131,6 +1146,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
Color.lerp(unSelectedIconColor, other.unSelectedIconColor, t),
|
||||
dividerColor: Color.lerp(dividerColor, other.dividerColor, t),
|
||||
hoverColor: Color.lerp(hoverColor, other.hoverColor, t),
|
||||
closeHoverColor: Color.lerp(closeHoverColor, other.closeHoverColor, t),
|
||||
selectedTabBackgroundColor: Color.lerp(selectedTabBackgroundColor, other.selectedTabBackgroundColor, t),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,22 +1,22 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/server_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/install_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/server_page.dart';
|
||||
import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart';
|
||||
import 'package:flutter_hbb/desktop/screen/desktop_port_forward_screen.dart';
|
||||
import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
|
||||
// import 'package:window_manager/window_manager.dart';
|
||||
|
||||
@ -108,11 +108,12 @@ Future<void> initEnv(String appType) async {
|
||||
await initGlobalFFI();
|
||||
// await Firebase.initializeApp();
|
||||
_registerEventHandler();
|
||||
// Update the system theme.
|
||||
updateSystemWindowTheme();
|
||||
}
|
||||
|
||||
void runMainApp(bool startService) async {
|
||||
// register uni links
|
||||
initUniLinks();
|
||||
await initEnv(kAppTypeMain);
|
||||
// trigger connection status updater
|
||||
await bind.mainCheckConnectStatus();
|
||||
@ -122,23 +123,27 @@ void runMainApp(bool startService) async {
|
||||
}
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
runApp(App());
|
||||
// restore the location of the main window before window hide or show
|
||||
await restoreWindowPosition(WindowType.Main);
|
||||
// check the startup argument, if we successfully handle the argument, we keep the main window hidden.
|
||||
if (checkArguments()) {
|
||||
windowManager.hide();
|
||||
} else {
|
||||
windowManager.show();
|
||||
windowManager.focus();
|
||||
// move registration of active main window here to prevent async visible check.
|
||||
rustDeskWinManager.registerActiveWindow(kWindowMainId);
|
||||
}
|
||||
// set window option
|
||||
// Set window option.
|
||||
WindowOptions windowOptions = getHiddenTitleBarWindowOptions();
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
// Restore the location of the main window before window hide or show.
|
||||
await restoreWindowPosition(WindowType.Main);
|
||||
// Check the startup argument, if we successfully handle the argument, we keep the main window hidden.
|
||||
final handledByUniLinks = await initUniLinks();
|
||||
final handledByCli = checkArguments();
|
||||
debugPrint(
|
||||
"handled by uni links: $handledByUniLinks, handled by cli: $handledByCli");
|
||||
if (handledByUniLinks || handledByCli) {
|
||||
windowManager.hide();
|
||||
} else {
|
||||
windowManager.show();
|
||||
windowManager.focus();
|
||||
// Move registration of active main window here to prevent from async visible check.
|
||||
rustDeskWinManager.registerActiveWindow(kWindowMainId);
|
||||
}
|
||||
windowManager.setOpacity(1);
|
||||
windowManager.setTitle(getWindowName());
|
||||
});
|
||||
windowManager.setTitle(getWindowName());
|
||||
}
|
||||
|
||||
void runMobileApp() async {
|
||||
@ -206,7 +211,7 @@ void runMultiWindow(
|
||||
}
|
||||
|
||||
void runConnectionManagerScreen(bool hide) async {
|
||||
await initEnv(kAppTypeMain);
|
||||
await initEnv(kAppTypeConnectionManager);
|
||||
_runApp(
|
||||
'',
|
||||
const DesktopServerPage(),
|
||||
@ -223,6 +228,7 @@ void showCmWindow() {
|
||||
WindowOptions windowOptions =
|
||||
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
bind.mainHideDocker();
|
||||
await windowManager.show();
|
||||
await Future.wait([windowManager.focus(), windowManager.setOpacity(1)]);
|
||||
// ensure initial window size to be changed
|
||||
@ -236,6 +242,7 @@ void hideCmWindow() {
|
||||
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
|
||||
windowManager.setOpacity(0);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
bind.mainHideDocker();
|
||||
await windowManager.hide();
|
||||
});
|
||||
}
|
||||
@ -325,6 +332,8 @@ class _AppState extends State<App> {
|
||||
to = ThemeMode.light;
|
||||
}
|
||||
Get.changeThemeMode(to);
|
||||
// Synchronize the window theme of the system.
|
||||
updateSystemWindowTheme();
|
||||
if (desktopType == DesktopType.main) {
|
||||
bind.mainChangeTheme(dark: to.toShortString());
|
||||
}
|
||||
|
@ -497,7 +497,11 @@ class _RemotePageState extends State<RemotePage> {
|
||||
child: Stack(children: () {
|
||||
final paints = [
|
||||
ImagePaint(),
|
||||
QualityMonitor(gFFI.qualityMonitorModel),
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 10,
|
||||
child: QualityMonitor(gFFI.qualityMonitorModel),
|
||||
),
|
||||
getHelpTools(),
|
||||
SizedBox(
|
||||
width: 0,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/button.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
@ -9,7 +8,7 @@ import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
|
||||
void clientClose(String id, OverlayDialogManager dialogManager) {
|
||||
msgBox(id, '', 'Close', 'Are you sure to close the connection?', '',
|
||||
msgBox(id, 'info', 'Close', 'Are you sure to close the connection?', '',
|
||||
dialogManager);
|
||||
}
|
||||
|
||||
@ -33,8 +32,10 @@ void showRestartRemoteDevice(
|
||||
]),
|
||||
content: Text(
|
||||
"${translate('Are you sure you want to restart')} \n${pi.username}@${pi.hostname}($id) ?"),
|
||||
onCancel: close,
|
||||
onSubmit: () => close(true),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: () => close(), isOutline: true),
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: () => close(true)),
|
||||
],
|
||||
));
|
||||
@ -48,6 +49,18 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
|
||||
var validateLength = false;
|
||||
var validateSame = false;
|
||||
dialogManager.show((setState, close) {
|
||||
submit() async {
|
||||
close();
|
||||
dialogManager.showLoading(translate("Waiting"));
|
||||
if (await gFFI.serverModel.setPermanentPassword(p0.text)) {
|
||||
dialogManager.dismissAll();
|
||||
showSuccess();
|
||||
} else {
|
||||
dialogManager.dismissAll();
|
||||
showError();
|
||||
}
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('Set your own password')),
|
||||
content: Form(
|
||||
@ -94,29 +107,17 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
|
||||
},
|
||||
),
|
||||
])),
|
||||
onCancel: close,
|
||||
onSubmit: (validateLength && validateSame) ? submit : null,
|
||||
actions: [
|
||||
dialogButton(
|
||||
'Cancel',
|
||||
onPressed: () {
|
||||
close();
|
||||
},
|
||||
onPressed: close,
|
||||
isOutline: true,
|
||||
),
|
||||
dialogButton(
|
||||
'OK',
|
||||
onPressed: (validateLength && validateSame)
|
||||
? () async {
|
||||
close();
|
||||
dialogManager.showLoading(translate("Waiting"));
|
||||
if (await gFFI.serverModel.setPermanentPassword(p0.text)) {
|
||||
dialogManager.dismissAll();
|
||||
showSuccess();
|
||||
} else {
|
||||
dialogManager.dismissAll();
|
||||
showError();
|
||||
}
|
||||
}
|
||||
: null,
|
||||
onPressed: (validateLength && validateSame) ? submit : null,
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -205,26 +206,36 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async {
|
||||
});
|
||||
}
|
||||
|
||||
void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) {
|
||||
dialogManager.show((setState, close) => CustomAlertDialog(
|
||||
title: Text(translate('Wrong Password')),
|
||||
content: Text(translate('Do you want to enter again?')),
|
||||
actions: [
|
||||
dialogButton(
|
||||
'Cancel',
|
||||
onPressed: () {
|
||||
close();
|
||||
closeConnection();
|
||||
},
|
||||
isOutline: true,
|
||||
),
|
||||
dialogButton(
|
||||
'Retry',
|
||||
onPressed: () {
|
||||
enterPasswordDialog(id, dialogManager);
|
||||
},
|
||||
),
|
||||
]));
|
||||
void wrongPasswordDialog(
|
||||
String id, OverlayDialogManager dialogManager, type, title, text) {
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show((setState, close) {
|
||||
cancel() {
|
||||
close();
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
submit() {
|
||||
enterPasswordDialog(id, dialogManager);
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: null,
|
||||
content: msgboxContent(type, title, text),
|
||||
onSubmit: submit,
|
||||
onCancel: cancel,
|
||||
actions: [
|
||||
dialogButton(
|
||||
'Cancel',
|
||||
onPressed: cancel,
|
||||
isOutline: true,
|
||||
),
|
||||
dialogButton(
|
||||
'Retry',
|
||||
onPressed: submit,
|
||||
),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
void showServerSettingsWithValue(
|
||||
@ -352,13 +363,14 @@ void showServerSettingsWithValue(
|
||||
});
|
||||
}
|
||||
|
||||
void showWaitUacDialog(String id, OverlayDialogManager dialogManager) {
|
||||
void showWaitUacDialog(
|
||||
String id, OverlayDialogManager dialogManager, String type) {
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show(
|
||||
tag: '$id-wait-uac',
|
||||
(setState, close) => CustomAlertDialog(
|
||||
title: Text(translate('Wait')),
|
||||
content: Text(translate('wait_accept_uac_tip')).marginAll(10),
|
||||
title: null,
|
||||
content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'),
|
||||
));
|
||||
}
|
||||
|
||||
@ -516,16 +528,6 @@ void showOnBlockDialog(
|
||||
dialogManager.existing('$id-request-elevation')) {
|
||||
return;
|
||||
}
|
||||
var content = Column(children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
"${translate(text)}${type.contains('uac') ? '\n' : '\n\n'}${translate('request_elevation_tip')}",
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(fontWeight: FontWeight.w400),
|
||||
).marginSymmetric(vertical: 15),
|
||||
),
|
||||
]);
|
||||
dialogManager.show(tag: '$id-$type', (setState, close) {
|
||||
void submit() {
|
||||
close();
|
||||
@ -533,12 +535,11 @@ void showOnBlockDialog(
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate(title)),
|
||||
content: content,
|
||||
title: null,
|
||||
content: msgboxContent(type, title,
|
||||
"${translate(text)}${type.contains('uac') ? '\n' : '\n\n'}${translate('request_elevation_tip')}"),
|
||||
actions: [
|
||||
dialogButton('Wait', onPressed: () {
|
||||
close();
|
||||
}, isOutline: true),
|
||||
dialogButton('Wait', onPressed: close, isOutline: true),
|
||||
dialogButton('Request Elevation', onPressed: submit),
|
||||
],
|
||||
onSubmit: submit,
|
||||
@ -556,8 +557,8 @@ void showElevationError(String id, String type, String title, String text,
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate(title)),
|
||||
content: Text(translate(text)),
|
||||
title: null,
|
||||
content: msgboxContent(type, title, text),
|
||||
actions: [
|
||||
dialogButton('Cancel', onPressed: () {
|
||||
close();
|
||||
@ -570,6 +571,25 @@ void showElevationError(String id, String type, String title, String text,
|
||||
});
|
||||
}
|
||||
|
||||
void showWaitAcceptDialog(String id, String type, String title, String text,
|
||||
OverlayDialogManager dialogManager) {
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show((setState, close) {
|
||||
onCancel() {
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: null,
|
||||
content: msgboxContent(type, title, text),
|
||||
actions: [
|
||||
dialogButton('Cancel', onPressed: onCancel, isOutline: true),
|
||||
],
|
||||
onCancel: onCancel,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<String?> validateAsync(String value) async {
|
||||
value = value.trim();
|
||||
if (value.isEmpty) {
|
||||
@ -625,7 +645,6 @@ class _PasswordWidgetState extends State<PasswordWidget> {
|
||||
icon: Icon(
|
||||
// Based on passwordVisible state choose the icon
|
||||
_passwordVisible ? Icons.visibility : Icons.visibility_off,
|
||||
color: Theme.of(context).primaryColorDark,
|
||||
),
|
||||
onPressed: () {
|
||||
// Update the state i.e. toggle the state of passwordVisible variable
|
||||
|
@ -310,7 +310,6 @@ class InputModel {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
int _signOrZero(num x) {
|
||||
if (x == 0) {
|
||||
return 0;
|
||||
@ -361,7 +360,6 @@ class InputModel {
|
||||
|
||||
trackpadScrollDistance = Offset.zero;
|
||||
}
|
||||
*/
|
||||
|
||||
void onPointDownImage(PointerDownEvent e) {
|
||||
debugPrint("onPointDownImage");
|
||||
@ -408,6 +406,13 @@ class InputModel {
|
||||
}
|
||||
}
|
||||
|
||||
void refreshMousePos() => handleMouse({
|
||||
'x': lastMousePos.dx,
|
||||
'y': lastMousePos.dy,
|
||||
'buttons': 0,
|
||||
'type': _kMouseEventMove,
|
||||
});
|
||||
|
||||
void handleMouse(Map<String, dynamic> evt) {
|
||||
double x = evt['x'];
|
||||
double y = max(0.0, evt['y']);
|
||||
|
@ -199,6 +199,9 @@ class FfiModel with ChangeNotifier {
|
||||
final peer_id = evt['peer_id'].toString();
|
||||
await bind.sessionSwitchSides(id: peer_id);
|
||||
closeConnection(id: peer_id);
|
||||
} else if (name == "on_url_scheme_received") {
|
||||
final url = evt['url'].toString();
|
||||
parseRustdeskUri(url);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -263,19 +266,18 @@ class FfiModel with ChangeNotifier {
|
||||
final text = evt['text'];
|
||||
final link = evt['link'];
|
||||
if (type == 're-input-password') {
|
||||
wrongPasswordDialog(id, dialogManager);
|
||||
wrongPasswordDialog(id, dialogManager, type, title, text);
|
||||
} else if (type == 'input-password') {
|
||||
enterPasswordDialog(id, dialogManager);
|
||||
} else if (type == 'restarting') {
|
||||
showMsgBox(id, type, title, text, link, false, dialogManager,
|
||||
hasCancel: false);
|
||||
} else if (type == 'wait-remote-accept-nook') {
|
||||
msgBoxCommon(dialogManager, title, Text(translate(text)),
|
||||
[dialogButton("Cancel", onPressed: closeConnection)]);
|
||||
showWaitAcceptDialog(id, type, title, text, dialogManager);
|
||||
} else if (type == 'on-uac' || type == 'on-foreground-elevated') {
|
||||
showOnBlockDialog(id, type, title, text, dialogManager);
|
||||
} else if (type == 'wait-uac') {
|
||||
showWaitUacDialog(id, dialogManager);
|
||||
showWaitUacDialog(id, dialogManager, type);
|
||||
} else if (type == 'elevation-error') {
|
||||
showElevationError(id, type, title, text, dialogManager);
|
||||
} else {
|
||||
@ -540,6 +542,7 @@ class CanvasModel with ChangeNotifier {
|
||||
double _y = 0;
|
||||
// image scale
|
||||
double _scale = 1.0;
|
||||
double _devicePixelRatio = 1.0;
|
||||
Size _size = Size.zero;
|
||||
// the tabbar over the image
|
||||
// double tabBarHeight = 0.0;
|
||||
@ -563,6 +566,7 @@ class CanvasModel with ChangeNotifier {
|
||||
double get x => _x;
|
||||
double get y => _y;
|
||||
double get scale => _scale;
|
||||
double get devicePixelRatio => _devicePixelRatio;
|
||||
Size get size => _size;
|
||||
ScrollStyle get scrollStyle => _scrollStyle;
|
||||
ViewStyle get viewStyle => _lastViewStyle;
|
||||
@ -611,13 +615,15 @@ class CanvasModel with ChangeNotifier {
|
||||
_lastViewStyle = viewStyle;
|
||||
_scale = viewStyle.scale;
|
||||
|
||||
_devicePixelRatio = ui.window.devicePixelRatio;
|
||||
if (kIgnoreDpi && style == kRemoteViewStyleOriginal) {
|
||||
_scale = 1.0 / ui.window.devicePixelRatio;
|
||||
_scale = 1.0 / _devicePixelRatio;
|
||||
}
|
||||
_x = (size.width - displayWidth * _scale) / 2;
|
||||
_y = (size.height - displayHeight * _scale) / 2;
|
||||
_imageOverflow.value = _x < 0 || y < 0;
|
||||
notifyListeners();
|
||||
parent.target?.inputModel.refreshMousePos();
|
||||
}
|
||||
|
||||
updateScrollStyle() async {
|
||||
@ -747,7 +753,7 @@ class CanvasModel with ChangeNotifier {
|
||||
class CursorData {
|
||||
final String peerId;
|
||||
final int id;
|
||||
final img2.Image? image;
|
||||
final img2.Image image;
|
||||
double scale;
|
||||
Uint8List? data;
|
||||
final double hotxOrigin;
|
||||
@ -772,33 +778,40 @@ class CursorData {
|
||||
|
||||
int _doubleToInt(double v) => (v * 10e6).round().toInt();
|
||||
|
||||
double _checkUpdateScale(double scale, bool shouldScale) {
|
||||
double _checkUpdateScale(double scale) {
|
||||
double oldScale = this.scale;
|
||||
if (!shouldScale) {
|
||||
scale = 1.0;
|
||||
} else {
|
||||
if (scale != 1.0) {
|
||||
// Update data if scale changed.
|
||||
if (Platform.isWindows) {
|
||||
final tgtWidth = (width * scale).toInt();
|
||||
final tgtHeight = (width * scale).toInt();
|
||||
if (tgtWidth < kMinCursorSize || tgtHeight < kMinCursorSize) {
|
||||
double sw = kMinCursorSize.toDouble() / width;
|
||||
double sh = kMinCursorSize.toDouble() / height;
|
||||
scale = sw < sh ? sh : sw;
|
||||
}
|
||||
final tgtWidth = (width * scale).toInt();
|
||||
final tgtHeight = (width * scale).toInt();
|
||||
if (tgtWidth < kMinCursorSize || tgtHeight < kMinCursorSize) {
|
||||
double sw = kMinCursorSize.toDouble() / width;
|
||||
double sh = kMinCursorSize.toDouble() / height;
|
||||
scale = sw < sh ? sh : sw;
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform.isWindows) {
|
||||
if (_doubleToInt(oldScale) != _doubleToInt(scale)) {
|
||||
if (_doubleToInt(oldScale) != _doubleToInt(scale)) {
|
||||
if (Platform.isWindows) {
|
||||
data = img2
|
||||
.copyResize(
|
||||
image!,
|
||||
image,
|
||||
width: (width * scale).toInt(),
|
||||
height: (height * scale).toInt(),
|
||||
interpolation: img2.Interpolation.average,
|
||||
)
|
||||
.getBytes(format: img2.Format.bgra);
|
||||
} else {
|
||||
data = Uint8List.fromList(
|
||||
img2.encodePng(
|
||||
img2.copyResize(
|
||||
image,
|
||||
width: (width * scale).toInt(),
|
||||
height: (height * scale).toInt(),
|
||||
interpolation: img2.Interpolation.average,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -808,8 +821,8 @@ class CursorData {
|
||||
return scale;
|
||||
}
|
||||
|
||||
String updateGetKey(double scale, bool shouldScale) {
|
||||
scale = _checkUpdateScale(scale, shouldScale);
|
||||
String updateGetKey(double scale) {
|
||||
scale = _checkUpdateScale(scale);
|
||||
return '${peerId}_${id}_${_doubleToInt(width * scale)}_${_doubleToInt(height * scale)}';
|
||||
}
|
||||
}
|
||||
@ -867,7 +880,7 @@ class PredefinedCursor {
|
||||
_cache = CursorData(
|
||||
peerId: '',
|
||||
id: id,
|
||||
image: _image2?.clone(),
|
||||
image: _image2!.clone(),
|
||||
scale: scale,
|
||||
data: data,
|
||||
hotxOrigin:
|
||||
@ -894,9 +907,10 @@ class CursorModel with ChangeNotifier {
|
||||
double _hoty = 0;
|
||||
double _displayOriginX = 0;
|
||||
double _displayOriginY = 0;
|
||||
DateTime? _firstUpdateMouseTime;
|
||||
bool gotMouseControl = true;
|
||||
DateTime _lastPeerMouse = DateTime.now()
|
||||
.subtract(Duration(milliseconds: 2 * kMouseControlTimeoutMSec));
|
||||
.subtract(Duration(milliseconds: 3000 * kMouseControlTimeoutMSec));
|
||||
String id = '';
|
||||
WeakReference<FFI> parent;
|
||||
|
||||
@ -915,6 +929,15 @@ class CursorModel with ChangeNotifier {
|
||||
DateTime.now().difference(_lastPeerMouse).inMilliseconds <
|
||||
kMouseControlTimeoutMSec;
|
||||
|
||||
bool isConnIn2Secs() {
|
||||
if (_firstUpdateMouseTime == null) {
|
||||
_firstUpdateMouseTime = DateTime.now();
|
||||
return true;
|
||||
} else {
|
||||
return DateTime.now().difference(_firstUpdateMouseTime!).inSeconds < 2;
|
||||
}
|
||||
}
|
||||
|
||||
CursorModel(this.parent);
|
||||
|
||||
Set<String> get cachedKeys => _cacheKeys;
|
||||
@ -1067,9 +1090,9 @@ class CursorModel with ChangeNotifier {
|
||||
Future<bool> _updateCache(
|
||||
Uint8List rgba, ui.Image image, int id, int w, int h) async {
|
||||
Uint8List? data;
|
||||
img2.Image? imgOrigin;
|
||||
img2.Image imgOrigin =
|
||||
img2.Image.fromBytes(w, h, rgba, format: img2.Format.rgba);
|
||||
if (Platform.isWindows) {
|
||||
imgOrigin = img2.Image.fromBytes(w, h, rgba, format: img2.Format.rgba);
|
||||
data = imgOrigin.getBytes(format: img2.Format.bgra);
|
||||
} else {
|
||||
ByteData? imgBytes =
|
||||
@ -1111,8 +1134,10 @@ class CursorModel with ChangeNotifier {
|
||||
|
||||
/// Update the cursor position.
|
||||
updateCursorPosition(Map<String, dynamic> evt, String id) async {
|
||||
gotMouseControl = false;
|
||||
_lastPeerMouse = DateTime.now();
|
||||
if (!isConnIn2Secs()) {
|
||||
gotMouseControl = false;
|
||||
_lastPeerMouse = DateTime.now();
|
||||
}
|
||||
_x = double.parse(evt['x']);
|
||||
_y = double.parse(evt['y']);
|
||||
try {
|
||||
|
@ -8,6 +8,7 @@ import 'package:external_path/external_path.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:win32/win32.dart' as win32;
|
||||
@ -46,6 +47,8 @@ class PlatformFFI {
|
||||
|
||||
static get localeName => Platform.localeName;
|
||||
|
||||
static get isMain => instance._appType == kAppTypeMain;
|
||||
|
||||
static Future<String> getVersion() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
return packageInfo.version;
|
||||
@ -112,8 +115,11 @@ class PlatformFFI {
|
||||
}
|
||||
_ffiBind = RustdeskImpl(dylib);
|
||||
if (Platform.isLinux) {
|
||||
// start dbus service, no need to await
|
||||
await _ffiBind.mainStartDbusServer();
|
||||
// Start a dbus service, no need to await
|
||||
_ffiBind.mainStartDbusServer();
|
||||
} else if (Platform.isMacOS) {
|
||||
// Start an ipc server for handling url schemes.
|
||||
_ffiBind.mainStartIpcUrlServer();
|
||||
}
|
||||
_startListenEvent(_ffiBind); // global event
|
||||
try {
|
||||
|
@ -28,7 +28,7 @@ class ServerModel with ChangeNotifier {
|
||||
bool _inputOk = false;
|
||||
bool _audioOk = false;
|
||||
bool _fileOk = false;
|
||||
bool _showElevation = true;
|
||||
bool _showElevation = false;
|
||||
bool _hideCm = false;
|
||||
int _connectStatus = 0; // Rendezvous Server status
|
||||
String _verificationMethod = "";
|
||||
|
@ -80,13 +80,15 @@ class UserModel {
|
||||
final tag = gFFI.dialogManager.showLoading(translate('Waiting'));
|
||||
try {
|
||||
final url = await bind.mainGetApiServer();
|
||||
final authHeaders = getHttpHeaders();
|
||||
authHeaders['Content-Type'] = "application/json";
|
||||
await http
|
||||
.post(Uri.parse('$url/api/logout'),
|
||||
body: {
|
||||
body: jsonEncode({
|
||||
'id': await bind.mainGetMyId(),
|
||||
'uuid': await bind.mainGetUuid(),
|
||||
},
|
||||
headers: getHttpHeaders())
|
||||
}),
|
||||
headers: authHeaders)
|
||||
.timeout(Duration(seconds: 2));
|
||||
} catch (e) {
|
||||
print("request /api/logout failed: err=$e");
|
||||
|
@ -43,11 +43,14 @@ class RustDeskMultiWindowManager {
|
||||
|
||||
Future<dynamic> newRemoteDesktop(String remoteId,
|
||||
{String? switch_uuid}) async {
|
||||
final msg = jsonEncode({
|
||||
var params = {
|
||||
"type": WindowType.RemoteDesktop.index,
|
||||
"id": remoteId,
|
||||
"switch_uuid": switch_uuid ?? ""
|
||||
});
|
||||
};
|
||||
if (switch_uuid != null) {
|
||||
params['switch_uuid'] = switch_uuid;
|
||||
}
|
||||
final msg = jsonEncode(params);
|
||||
|
||||
try {
|
||||
final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
||||
@ -157,6 +160,24 @@ class RustDeskMultiWindowManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
void clearWindowType(WindowType type) {
|
||||
switch (type) {
|
||||
case WindowType.Main:
|
||||
return;
|
||||
case WindowType.RemoteDesktop:
|
||||
_remoteDesktopWindowId = null;
|
||||
break;
|
||||
case WindowType.FileTransfer:
|
||||
_fileTransferWindowId = null;
|
||||
break;
|
||||
case WindowType.PortForward:
|
||||
_portForwardWindowId = null;
|
||||
break;
|
||||
case WindowType.Unknown:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setMethodHandler(
|
||||
Future<dynamic> Function(MethodCall call, int fromWindowId)? handler) {
|
||||
DesktopMultiWindow.setMethodHandler(handler);
|
||||
@ -183,8 +204,11 @@ class RustDeskMultiWindowManager {
|
||||
}
|
||||
await WindowController.fromWindowId(wId).setPreventClose(false);
|
||||
await WindowController.fromWindowId(wId).close();
|
||||
} on Error {
|
||||
} catch (e) {
|
||||
debugPrint("$e");
|
||||
return;
|
||||
} finally {
|
||||
clearWindowType(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
flutter/lib/utils/platform_channel.dart
Normal file
40
flutter/lib/utils/platform_channel.dart
Normal 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");
|
||||
}
|
||||
}
|
@ -13,7 +13,8 @@ PODS:
|
||||
- FMDB/standard (2.7.5)
|
||||
- package_info_plus_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- path_provider_macos (0.0.1):
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- screen_retriever (0.0.1):
|
||||
- FlutterMacOS
|
||||
@ -38,7 +39,7 @@ DEPENDENCIES:
|
||||
- flutter_custom_cursor (from `Flutter/ephemeral/.symlinks/plugins/flutter_custom_cursor/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
|
||||
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`)
|
||||
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
|
||||
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
|
||||
- uni_links_desktop (from `Flutter/ephemeral/.symlinks/plugins/uni_links_desktop/macos`)
|
||||
@ -64,8 +65,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral
|
||||
package_info_plus_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
|
||||
path_provider_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos
|
||||
screen_retriever:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
|
||||
sqflite:
|
||||
@ -86,14 +87,14 @@ SPEC CHECKSUMS:
|
||||
desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486
|
||||
device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7
|
||||
flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7
|
||||
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
|
||||
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
|
||||
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
|
||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
|
||||
uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026
|
||||
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
|
||||
url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2
|
||||
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
|
||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
||||
|
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
@ -227,7 +227,7 @@
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1100;
|
||||
LastSwiftMigration = 1420;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
@ -279,6 +279,7 @@
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
@ -429,7 +430,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
@ -462,6 +463,7 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Profile;
|
||||
@ -606,6 +608,7 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@ -642,6 +645,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
@ -3,21 +3,22 @@ import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
var lauched = false;
|
||||
var launched = false;
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
dummy_method_to_enforce_bundling()
|
||||
return true
|
||||
// https://github.com/leanflutter/window_manager/issues/214
|
||||
return false
|
||||
}
|
||||
|
||||
override func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
|
||||
if (lauched) {
|
||||
if (launched) {
|
||||
handle_applicationShouldOpenUntitledFile();
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
lauched = true;
|
||||
launched = true;
|
||||
NSApplication.shared.activate(ignoringOtherApps: true);
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,10 @@
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<key>CFBundleURLIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.carriez.rustdesk</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>rustdesk</string>
|
||||
@ -35,13 +37,13 @@
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>$(PRODUCT_COPYRIGHT)</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -27,12 +27,16 @@ class MainFlutterWindow: NSWindow {
|
||||
let windowFrame = self.frame
|
||||
self.contentViewController = flutterViewController
|
||||
self.setFrame(windowFrame, display: true)
|
||||
// register self method handler
|
||||
let registrar = flutterViewController.registrar(forPlugin: "RustDeskPlugin")
|
||||
setMethodHandler(registrar: registrar)
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
|
||||
FlutterMultiWindowPlugin.setOnWindowCreatedCallback { controller in
|
||||
// Register the plugin which you want access from other isolate.
|
||||
// DesktopLifecyclePlugin.register(with: controller.registrar(forPlugin: "DesktopLifecyclePlugin"))
|
||||
self.setMethodHandler(registrar: controller.registrar(forPlugin: "RustDeskPlugin"))
|
||||
DesktopDropPlugin.register(with: controller.registrar(forPlugin: "DesktopDropPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: controller.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FlutterCustomCursorPlugin.register(with: controller.registrar(forPlugin: "FlutterCustomCursorPlugin"))
|
||||
@ -53,4 +57,33 @@ class MainFlutterWindow: NSWindow {
|
||||
super.order(place, relativeTo: otherWin)
|
||||
hiddenWindowAtLaunch()
|
||||
}
|
||||
|
||||
/// Override window theme.
|
||||
public func setWindowInterfaceMode(window: NSWindow, themeName: String) {
|
||||
window.appearance = NSAppearance(named: themeName == "light" ? .aqua : .darkAqua)
|
||||
}
|
||||
|
||||
public func setMethodHandler(registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "org.rustdesk.rustdesk/macos", binaryMessenger: registrar.messenger)
|
||||
channel.setMethodCallHandler({
|
||||
(call, result) -> Void in
|
||||
switch call.method {
|
||||
case "setWindowTheme":
|
||||
let arg = call.arguments as! [String: Any]
|
||||
let themeName = arg["themeName"] as? String
|
||||
guard let window = registrar.view?.window else {
|
||||
result(nil)
|
||||
return
|
||||
}
|
||||
self.setWindowInterfaceMode(window: window,themeName: themeName ?? "light")
|
||||
result(nil)
|
||||
break;
|
||||
case "terminate":
|
||||
NSApplication.shared.terminate(self)
|
||||
result(nil)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -59,9 +59,9 @@ dependencies:
|
||||
desktop_multi_window:
|
||||
git:
|
||||
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
|
||||
ref: 057e6eb1bc7dcbcf9dafd1384274a611e4fe7124
|
||||
ref: bc8604a88e52b2b6e64d2661ae49a71450a47af8
|
||||
freezed_annotation: ^2.0.3
|
||||
flutter_custom_cursor: ^0.0.2
|
||||
flutter_custom_cursor: ^0.0.4
|
||||
window_size:
|
||||
git:
|
||||
url: https://github.com/google/flutter-desktop-embedding.git
|
||||
|
@ -115,6 +115,26 @@ macro_rules! serde_field_string {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! serde_field_bool {
|
||||
($struct_name: ident, $field_name: literal, $func: ident) => {
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct $struct_name {
|
||||
#[serde(rename = $field_name)]
|
||||
pub v: bool,
|
||||
}
|
||||
impl Default for $struct_name {
|
||||
fn default() -> Self {
|
||||
Self { v: Self::$func() }
|
||||
}
|
||||
}
|
||||
impl $struct_name {
|
||||
pub fn $func() -> bool {
|
||||
UserDefaultConfig::load().get($field_name) == "Y"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum NetworkType {
|
||||
Direct,
|
||||
@ -192,26 +212,29 @@ pub struct PeerConfig {
|
||||
deserialize_with = "PeerConfig::deserialize_image_quality"
|
||||
)]
|
||||
pub image_quality: String,
|
||||
#[serde(default)]
|
||||
#[serde(
|
||||
default = "PeerConfig::default_custom_image_quality",
|
||||
deserialize_with = "PeerConfig::deserialize_custom_image_quality"
|
||||
)]
|
||||
pub custom_image_quality: Vec<i32>,
|
||||
#[serde(default)]
|
||||
pub show_remote_cursor: bool,
|
||||
#[serde(default)]
|
||||
pub lock_after_session_end: bool,
|
||||
#[serde(default)]
|
||||
pub privacy_mode: bool,
|
||||
#[serde(flatten)]
|
||||
pub show_remote_cursor: ShowRemoteCursor,
|
||||
#[serde(flatten)]
|
||||
pub lock_after_session_end: LockAfterSessionEnd,
|
||||
#[serde(flatten)]
|
||||
pub privacy_mode: PrivacyMode,
|
||||
#[serde(default)]
|
||||
pub port_forwards: Vec<(i32, String, i32)>,
|
||||
#[serde(default)]
|
||||
pub direct_failures: i32,
|
||||
#[serde(default)]
|
||||
pub disable_audio: bool,
|
||||
#[serde(default)]
|
||||
pub disable_clipboard: bool,
|
||||
#[serde(default)]
|
||||
pub enable_file_transfer: bool,
|
||||
#[serde(default)]
|
||||
pub show_quality_monitor: bool,
|
||||
#[serde(flatten)]
|
||||
pub disable_audio: DisableAudio,
|
||||
#[serde(flatten)]
|
||||
pub disable_clipboard: DisableClipboard,
|
||||
#[serde(flatten)]
|
||||
pub enable_file_transfer: EnableFileTransfer,
|
||||
#[serde(flatten)]
|
||||
pub show_quality_monitor: ShowQualityMonitor,
|
||||
#[serde(default)]
|
||||
pub keyboard_mode: String,
|
||||
|
||||
@ -961,31 +984,88 @@ impl PeerConfig {
|
||||
serde_field_string!(
|
||||
default_view_style,
|
||||
deserialize_view_style,
|
||||
"original".to_owned()
|
||||
UserDefaultConfig::load().get("view_style")
|
||||
);
|
||||
serde_field_string!(
|
||||
default_scroll_style,
|
||||
deserialize_scroll_style,
|
||||
"scrollauto".to_owned()
|
||||
UserDefaultConfig::load().get("scroll_style")
|
||||
);
|
||||
serde_field_string!(
|
||||
default_image_quality,
|
||||
deserialize_image_quality,
|
||||
"balanced".to_owned()
|
||||
UserDefaultConfig::load().get("image_quality")
|
||||
);
|
||||
|
||||
fn default_custom_image_quality() -> Vec<i32> {
|
||||
let f: f64 = UserDefaultConfig::load()
|
||||
.get("custom_image_quality")
|
||||
.parse()
|
||||
.unwrap_or(50.0);
|
||||
vec![f as _]
|
||||
}
|
||||
|
||||
fn deserialize_custom_image_quality<'de, D>(deserializer: D) -> Result<Vec<i32>, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let v: Vec<i32> = de::Deserialize::deserialize(deserializer)?;
|
||||
if v.len() == 1 && v[0] >= 10 && v[0] <= 100 {
|
||||
Ok(v)
|
||||
} else {
|
||||
Ok(Self::default_custom_image_quality())
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_options<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?;
|
||||
if !mp.contains_key("codec-preference") {
|
||||
mp.insert("codec-preference".to_owned(), "auto".to_owned());
|
||||
let mut key = "codec-preference";
|
||||
if !mp.contains_key(key) {
|
||||
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key));
|
||||
}
|
||||
key = "custom-fps";
|
||||
if !mp.contains_key(key) {
|
||||
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key));
|
||||
}
|
||||
key = "zoom-cursor";
|
||||
if !mp.contains_key(key) {
|
||||
mp.insert(key.to_owned(), UserDefaultConfig::load().get(key));
|
||||
}
|
||||
Ok(mp)
|
||||
}
|
||||
}
|
||||
|
||||
serde_field_bool!(
|
||||
ShowRemoteCursor,
|
||||
"show_remote_cursor",
|
||||
default_show_remote_cursor
|
||||
);
|
||||
serde_field_bool!(
|
||||
ShowQualityMonitor,
|
||||
"show_quality_monitor",
|
||||
default_show_quality_monitor
|
||||
);
|
||||
serde_field_bool!(DisableAudio, "disable_audio", default_disable_audio);
|
||||
serde_field_bool!(
|
||||
EnableFileTransfer,
|
||||
"enable_file_transfer",
|
||||
default_enable_file_transfer
|
||||
);
|
||||
serde_field_bool!(
|
||||
DisableClipboard,
|
||||
"disable_clipboard",
|
||||
default_disable_clipboard
|
||||
);
|
||||
serde_field_bool!(
|
||||
LockAfterSessionEnd,
|
||||
"lock_after_session_end",
|
||||
default_lock_after_session_end
|
||||
);
|
||||
serde_field_bool!(PrivacyMode, "privacy_mode", default_privacy_mode);
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct LocalConfig {
|
||||
#[serde(default)]
|
||||
@ -1192,6 +1272,73 @@ impl HwCodecConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct UserDefaultConfig {
|
||||
#[serde(default)]
|
||||
options: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl UserDefaultConfig {
|
||||
pub fn load() -> UserDefaultConfig {
|
||||
Config::load_::<UserDefaultConfig>("_default")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn store(&self) {
|
||||
Config::store_(self, "_default");
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> String {
|
||||
match key {
|
||||
"view_style" => self.get_string(key, "original", vec!["adaptive"]),
|
||||
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]),
|
||||
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]),
|
||||
"codec-preference" => self.get_string(key, "auto", vec!["vp9", "h264", "h265"]),
|
||||
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
|
||||
"custom-fps" => self.get_double_string(key, 30.0, 10.0, 120.0),
|
||||
_ => self
|
||||
.options
|
||||
.get(key)
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, key: String, value: String) {
|
||||
self.options.insert(key, value);
|
||||
self.store();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_string(&self, key: &str, default: &str, others: Vec<&str>) -> String {
|
||||
match self.options.get(key) {
|
||||
Some(option) => {
|
||||
if others.contains(&option.as_str()) {
|
||||
option.to_owned()
|
||||
} else {
|
||||
default.to_owned()
|
||||
}
|
||||
}
|
||||
None => default.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_double_string(&self, key: &str, default: f64, min: f64, max: f64) -> String {
|
||||
match self.options.get(key) {
|
||||
Some(option) => {
|
||||
let v: f64 = option.parse().unwrap_or(default);
|
||||
if v >= min && v <= max {
|
||||
v.to_string()
|
||||
} else {
|
||||
default.to_string()
|
||||
}
|
||||
}
|
||||
None => default.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -197,9 +197,6 @@ pub fn get_version_from_url(url: &str) -> String {
|
||||
}
|
||||
|
||||
pub fn gen_version() {
|
||||
if Ok("release".to_owned()) != std::env::var("PROFILE") {
|
||||
return;
|
||||
}
|
||||
println!("cargo:rerun-if-changed=Cargo.toml");
|
||||
use std::io::prelude::*;
|
||||
let mut file = File::create("./src/version.rs").unwrap();
|
||||
|
@ -76,7 +76,7 @@ pub fn is_cursor_embedded() -> bool {
|
||||
if is_x11() {
|
||||
x11::IS_CURSOR_EMBEDDED
|
||||
} else {
|
||||
wayland::IS_CURSOR_EMBEDDED
|
||||
wayland::is_cursor_embedded()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,33 @@ use std::{io, sync::RwLock, time::Duration};
|
||||
|
||||
pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>);
|
||||
|
||||
pub const IS_CURSOR_EMBEDDED: bool = true;
|
||||
static mut IS_CURSOR_EMBEDDED: Option<bool> = None;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref MAP_ERR: RwLock<Option<fn(err: String)-> io::Error>> = Default::default();
|
||||
}
|
||||
|
||||
pub fn is_cursor_embedded() -> bool {
|
||||
unsafe {
|
||||
if IS_CURSOR_EMBEDDED.is_none() {
|
||||
init_cursor_embedded();
|
||||
}
|
||||
IS_CURSOR_EMBEDDED.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init_cursor_embedded() {
|
||||
use crate::common::wayland::pipewire::get_available_cursor_modes;
|
||||
match get_available_cursor_modes() {
|
||||
Ok(modes) => {
|
||||
IS_CURSOR_EMBEDDED = Some((modes & 0x02) > 0);
|
||||
}
|
||||
Err(..) => {
|
||||
IS_CURSOR_EMBEDDED = Some(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_map_err(f: fn(err: String) -> io::Error) {
|
||||
*MAP_ERR.write().unwrap() = Some(f);
|
||||
}
|
||||
@ -74,7 +95,7 @@ impl Display {
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Display>> {
|
||||
Ok(pipewire::get_capturables(true)
|
||||
Ok(pipewire::get_capturables(is_cursor_embedded())
|
||||
.map_err(map_err)?
|
||||
.drain(..)
|
||||
.map(|x| Display(x))
|
||||
|
@ -58,6 +58,7 @@ impl Capturer {
|
||||
let mut device = ptr::null_mut();
|
||||
let mut context = ptr::null_mut();
|
||||
let mut duplication = ptr::null_mut();
|
||||
#[allow(invalid_value)]
|
||||
let mut desc = unsafe { mem::MaybeUninit::uninit().assume_init() };
|
||||
let mut gdi_capturer = None;
|
||||
|
||||
@ -176,6 +177,7 @@ impl Capturer {
|
||||
|
||||
unsafe fn load_frame(&mut self, timeout: UINT) -> io::Result<(*const u8, i32)> {
|
||||
let mut frame = ptr::null_mut();
|
||||
#[allow(invalid_value)]
|
||||
let mut info = mem::MaybeUninit::uninit().assume_init();
|
||||
|
||||
wrap_hresult((*self.duplication.0).AcquireNextFrame(timeout, &mut info, &mut frame))?;
|
||||
@ -185,6 +187,7 @@ impl Capturer {
|
||||
return Err(std::io::ErrorKind::WouldBlock.into());
|
||||
}
|
||||
|
||||
#[allow(invalid_value)]
|
||||
let mut rect = mem::MaybeUninit::uninit().assume_init();
|
||||
if self.fastlane {
|
||||
wrap_hresult((*self.duplication.0).MapDesktopSurface(&mut rect))?;
|
||||
@ -204,6 +207,7 @@ impl Capturer {
|
||||
);
|
||||
let texture = ComPtr(texture);
|
||||
|
||||
#[allow(invalid_value)]
|
||||
let mut texture_desc = mem::MaybeUninit::uninit().assume_init();
|
||||
(*texture.0).GetDesc(&mut texture_desc);
|
||||
|
||||
@ -362,6 +366,7 @@ impl Displays {
|
||||
let mut all = Vec::new();
|
||||
let mut i: DWORD = 0;
|
||||
loop {
|
||||
#[allow(invalid_value)]
|
||||
let mut d: DISPLAY_DEVICEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
|
||||
d.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as _;
|
||||
let ok = unsafe { EnumDisplayDevicesW(std::ptr::null(), i, &mut d as _, 0) };
|
||||
@ -382,6 +387,7 @@ impl Displays {
|
||||
gdi: true,
|
||||
};
|
||||
disp.desc.DeviceName = d.DeviceName;
|
||||
#[allow(invalid_value)]
|
||||
let mut m: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
|
||||
m.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
m.dmDriverExtra = 0;
|
||||
@ -441,6 +447,7 @@ impl Displays {
|
||||
// We get the display's details.
|
||||
|
||||
let desc = unsafe {
|
||||
#[allow(invalid_value)]
|
||||
let mut desc = mem::MaybeUninit::uninit().assume_init();
|
||||
(*output.0).GetDesc(&mut desc);
|
||||
desc
|
||||
|
@ -386,21 +386,22 @@ fn streams_from_response(response: OrgFreedesktopPortalRequestResponse) -> Vec<P
|
||||
info.size.1 = v[1] as _;
|
||||
}
|
||||
}
|
||||
let v = attributes
|
||||
.get("position")?
|
||||
.as_iter()?
|
||||
.filter_map(|v| {
|
||||
Some(
|
||||
v.as_iter()?
|
||||
.map(|x| x.as_i64().unwrap_or(0))
|
||||
.collect::<Vec<i64>>(),
|
||||
)
|
||||
})
|
||||
.next();
|
||||
if let Some(v) = v {
|
||||
if v.len() == 2 {
|
||||
info.position.0 = v[0] as _;
|
||||
info.position.1 = v[1] as _;
|
||||
if let Some(pos) = attributes.get("position") {
|
||||
let v = pos
|
||||
.as_iter()?
|
||||
.filter_map(|v| {
|
||||
Some(
|
||||
v.as_iter()?
|
||||
.map(|x| x.as_i64().unwrap_or(0))
|
||||
.collect::<Vec<i64>>(),
|
||||
)
|
||||
})
|
||||
.next();
|
||||
if let Some(v) = v {
|
||||
if v.len() == 2 {
|
||||
info.position.0 = v[0] as _;
|
||||
info.position.1 = v[1] as _;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(info)
|
||||
@ -415,6 +416,12 @@ static mut INIT: bool = false;
|
||||
const RESTORE_TOKEN: &str = "restore_token";
|
||||
const RESTORE_TOKEN_CONF_KEY: &str = "wayland-restore-token";
|
||||
|
||||
pub fn get_available_cursor_modes() -> Result<u32, dbus::Error> {
|
||||
let conn = SyncConnection::new_session()?;
|
||||
let portal = get_portal(&conn);
|
||||
portal.available_cursor_modes()
|
||||
}
|
||||
|
||||
// mostly inspired by https://gitlab.gnome.org/snippets/19
|
||||
fn request_screen_cast(
|
||||
capture_cursor: bool,
|
||||
@ -473,7 +480,17 @@ fn request_screen_cast(
|
||||
args.insert("multiple".into(), Variant(Box::new(true)));
|
||||
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
|
||||
|
||||
let cursor_mode = if capture_cursor { 2u32 } else { 1u32 };
|
||||
let mut cursor_mode = 0u32;
|
||||
let mut available_cursor_modes = 0u32;
|
||||
if let Ok(modes) = portal.available_cursor_modes() {
|
||||
available_cursor_modes = modes;
|
||||
}
|
||||
if capture_cursor {
|
||||
cursor_mode = 2u32 & available_cursor_modes;
|
||||
}
|
||||
if cursor_mode == 0 {
|
||||
cursor_mode = 1u32 & available_cursor_modes;
|
||||
}
|
||||
let plasma = std::env::var("DESKTOP_SESSION").map_or(false, |s| s.contains("plasma"));
|
||||
if plasma && capture_cursor {
|
||||
// Warn the user if capturing the cursor is tried on kde as this can crash
|
||||
@ -483,7 +500,9 @@ fn request_screen_cast(
|
||||
desktop, see https://bugs.kde.org/show_bug.cgi?id=435042 for details! \
|
||||
You have been warned.");
|
||||
}
|
||||
args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode)));
|
||||
if cursor_mode > 0 {
|
||||
args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode)));
|
||||
}
|
||||
let session: dbus::Path = r
|
||||
.results
|
||||
.get("session_handle")
|
||||
|
@ -956,7 +956,7 @@ impl LoginConfigHandler {
|
||||
/// Check if the client should auto login.
|
||||
/// Return password if the client should auto login, otherwise return empty string.
|
||||
pub fn should_auto_login(&self) -> String {
|
||||
let l = self.lock_after_session_end;
|
||||
let l = self.lock_after_session_end.v;
|
||||
let a = !self.get_option("auto-login").is_empty();
|
||||
let p = self.get_option("os-password");
|
||||
if !p.is_empty() && l && a {
|
||||
@ -1063,32 +1063,32 @@ impl LoginConfigHandler {
|
||||
let mut option = OptionMessage::default();
|
||||
let mut config = self.load_config();
|
||||
if name == "show-remote-cursor" {
|
||||
config.show_remote_cursor = !config.show_remote_cursor;
|
||||
option.show_remote_cursor = (if config.show_remote_cursor {
|
||||
config.show_remote_cursor.v = !config.show_remote_cursor.v;
|
||||
option.show_remote_cursor = (if config.show_remote_cursor.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "disable-audio" {
|
||||
config.disable_audio = !config.disable_audio;
|
||||
option.disable_audio = (if config.disable_audio {
|
||||
config.disable_audio.v = !config.disable_audio.v;
|
||||
option.disable_audio = (if config.disable_audio.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "disable-clipboard" {
|
||||
config.disable_clipboard = !config.disable_clipboard;
|
||||
option.disable_clipboard = (if config.disable_clipboard {
|
||||
config.disable_clipboard.v = !config.disable_clipboard.v;
|
||||
option.disable_clipboard = (if config.disable_clipboard.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "lock-after-session-end" {
|
||||
config.lock_after_session_end = !config.lock_after_session_end;
|
||||
option.lock_after_session_end = (if config.lock_after_session_end {
|
||||
config.lock_after_session_end.v = !config.lock_after_session_end.v;
|
||||
option.lock_after_session_end = (if config.lock_after_session_end.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
@ -1096,15 +1096,15 @@ impl LoginConfigHandler {
|
||||
.into();
|
||||
} else if name == "privacy-mode" {
|
||||
// try toggle privacy mode
|
||||
option.privacy_mode = (if config.privacy_mode {
|
||||
option.privacy_mode = (if config.privacy_mode.v {
|
||||
BoolOption::No
|
||||
} else {
|
||||
BoolOption::Yes
|
||||
})
|
||||
.into();
|
||||
} else if name == "enable-file-transfer" {
|
||||
config.enable_file_transfer = !config.enable_file_transfer;
|
||||
option.enable_file_transfer = (if config.enable_file_transfer {
|
||||
config.enable_file_transfer.v = !config.enable_file_transfer.v;
|
||||
option.enable_file_transfer = (if config.enable_file_transfer.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
@ -1115,10 +1115,14 @@ impl LoginConfigHandler {
|
||||
} else if name == "unblock-input" {
|
||||
option.block_input = BoolOption::No.into();
|
||||
} else if name == "show-quality-monitor" {
|
||||
config.show_quality_monitor = !config.show_quality_monitor;
|
||||
config.show_quality_monitor.v = !config.show_quality_monitor.v;
|
||||
} else {
|
||||
let v = self.options.get(&name).is_some();
|
||||
if v {
|
||||
let is_set = self
|
||||
.options
|
||||
.get(&name)
|
||||
.map(|o| !o.is_empty())
|
||||
.unwrap_or(false);
|
||||
if is_set {
|
||||
self.config.options.remove(&name);
|
||||
} else {
|
||||
self.config.options.insert(name, "Y".to_owned());
|
||||
@ -1252,19 +1256,19 @@ impl LoginConfigHandler {
|
||||
/// * `name` - The name of the toggle option.
|
||||
pub fn get_toggle_option(&self, name: &str) -> bool {
|
||||
if name == "show-remote-cursor" {
|
||||
self.config.show_remote_cursor
|
||||
self.config.show_remote_cursor.v
|
||||
} else if name == "lock-after-session-end" {
|
||||
self.config.lock_after_session_end
|
||||
self.config.lock_after_session_end.v
|
||||
} else if name == "privacy-mode" {
|
||||
self.config.privacy_mode
|
||||
self.config.privacy_mode.v
|
||||
} else if name == "enable-file-transfer" {
|
||||
self.config.enable_file_transfer
|
||||
self.config.enable_file_transfer.v
|
||||
} else if name == "disable-audio" {
|
||||
self.config.disable_audio
|
||||
self.config.disable_audio.v
|
||||
} else if name == "disable-clipboard" {
|
||||
self.config.disable_clipboard
|
||||
self.config.disable_clipboard.v
|
||||
} else if name == "show-quality-monitor" {
|
||||
self.config.show_quality_monitor
|
||||
self.config.show_quality_monitor.v
|
||||
} else {
|
||||
!self.get_option(name).is_empty()
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
if !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
|| lc.read().unwrap().disable_clipboard
|
||||
|| lc.read().unwrap().disable_clipboard.v
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -778,7 +778,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
|| self.handler.is_port_forward()
|
||||
|| !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
|| self.handler.lc.read().unwrap().disable_clipboard)
|
||||
|| self.handler.lc.read().unwrap().disable_clipboard.v)
|
||||
{
|
||||
let txt = self.old_clipboard.lock().unwrap().clone();
|
||||
if !txt.is_empty() {
|
||||
@ -808,7 +808,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.set_cursor_position(cp);
|
||||
}
|
||||
Some(message::Union::Clipboard(cb)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard.v {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
update_clipboard(cb, Some(&self.old_clipboard));
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
@ -1104,7 +1104,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
Some(misc::Union::PortableServiceRunning(b)) => {
|
||||
if b {
|
||||
self.handler.msgbox(
|
||||
"custom-nocancel",
|
||||
"custom-nocancel-success",
|
||||
"Successful",
|
||||
"Elevate successfully",
|
||||
"",
|
||||
@ -1121,7 +1121,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.handle_test_delay(t, peer).await;
|
||||
}
|
||||
Some(message::Union::AudioFrame(frame)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_audio {
|
||||
if !self.handler.lc.read().unwrap().disable_audio.v {
|
||||
self.audio_sender.send(MediaData::AudioFrame(frame)).ok();
|
||||
}
|
||||
}
|
||||
@ -1204,7 +1204,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
#[inline(always)]
|
||||
fn update_privacy_mode(&mut self, on: bool) {
|
||||
let mut config = self.handler.load_config();
|
||||
config.privacy_mode = on;
|
||||
config.privacy_mode.v = on;
|
||||
self.handler.save_config(config);
|
||||
|
||||
self.handler.update_privacy_mode();
|
||||
@ -1278,14 +1278,14 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let enabled = SERVER_FILE_TRANSFER_ENABLED.load(Ordering::SeqCst)
|
||||
&& self.handler.lc.read().unwrap().enable_file_transfer;
|
||||
&& self.handler.lc.read().unwrap().enable_file_transfer.v;
|
||||
ContextSend::enable(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn handle_cliprdr_msg(&self, clip: hbb_common::message_proto::Cliprdr) {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard.v {
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
|
||||
if self.client_conn_id
|
||||
|
@ -52,7 +52,7 @@ pub fn global_init() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !*IS_X11 {
|
||||
crate::server::wayland::set_wayland_scrap_map_err();
|
||||
crate::server::wayland::init();
|
||||
}
|
||||
}
|
||||
true
|
||||
@ -451,6 +451,7 @@ pub fn run_me<T: AsRef<std::ffi::OsStr>>(args: Vec<T>) -> std::io::Result<std::p
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn username() -> String {
|
||||
// fix bug of whoami
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@ -459,6 +460,14 @@ pub fn username() -> String {
|
||||
return DEVICE_NAME.lock().unwrap().clone();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hostname() -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return whoami::hostname();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return DEVICE_NAME.lock().unwrap().clone();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_port<T: std::string::ToString>(host: T, port: i32) -> String {
|
||||
hbb_common::socket_client::check_port(host, port)
|
||||
@ -581,9 +590,9 @@ pub fn get_api_server(api: String, custom: String) -> String {
|
||||
if !s0.is_empty() {
|
||||
let s = crate::increase_port(&s0, -2);
|
||||
if s == s0 {
|
||||
format!("http://{}:{}", s, config::RENDEZVOUS_PORT - 2);
|
||||
return format!("http://{}:{}", s, config::RENDEZVOUS_PORT - 2);
|
||||
} else {
|
||||
format!("http://{}", s);
|
||||
return format!("http://{}", s);
|
||||
}
|
||||
}
|
||||
"https://admin.rustdesk.com".to_owned()
|
||||
|
@ -1,4 +1,6 @@
|
||||
use hbb_common::log;
|
||||
use std::future::Future;
|
||||
|
||||
use hbb_common::{log, ResultType};
|
||||
|
||||
/// shared by flutter and sciter main function
|
||||
///
|
||||
@ -54,11 +56,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
return core_main_invoke_new_connection(std::env::args());
|
||||
}
|
||||
let click_setup = cfg!(windows) && args.is_empty() && crate::common::is_setup(&arg_exe);
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
{
|
||||
_is_quick_support =
|
||||
cfg!(windows) && args.is_empty() && arg_exe.to_lowercase().ends_with("qs.exe");
|
||||
}
|
||||
if click_setup {
|
||||
args.push("--install".to_owned());
|
||||
flutter_args.push("--install".to_string());
|
||||
@ -70,6 +67,14 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
println!("{}", crate::VERSION);
|
||||
return None;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
_is_quick_support |= !crate::platform::is_installed()
|
||||
&& args.is_empty()
|
||||
&& (arg_exe.to_lowercase().ends_with("qs.exe")
|
||||
|| (!click_setup && crate::platform::is_elevated(None).unwrap_or(false)));
|
||||
crate::portable_service::client::set_quick_support(_is_quick_support);
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use hbb_common::env_logger::*;
|
||||
@ -195,7 +200,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
{
|
||||
std::thread::spawn(move || crate::start_server(true));
|
||||
crate::platform::macos::hide_dock();
|
||||
crate::tray::make_tray();
|
||||
crate::ui::macos::make_tray();
|
||||
return None;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
@ -244,8 +249,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
#[cfg(feature = "flutter")]
|
||||
crate::flutter::connection_manager::start_listen_ipc_thread();
|
||||
crate::ui_interface::start_option_status_sync();
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::platform::macos::hide_dock();
|
||||
}
|
||||
}
|
||||
//_async_logger_holder.map(|x| x.flush());
|
||||
@ -291,8 +294,7 @@ fn import_config(path: &str) {
|
||||
fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
|
||||
args.position(|element| {
|
||||
return element == "--connect";
|
||||
})
|
||||
.unwrap();
|
||||
})?;
|
||||
let peer_id = args.next().unwrap_or("".to_string());
|
||||
if peer_id.is_empty() {
|
||||
eprintln!("please provide a valid peer id");
|
||||
@ -304,9 +306,13 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
|
||||
switch_uuid = args.next();
|
||||
}
|
||||
}
|
||||
let mut param_array = vec![];
|
||||
if switch_uuid.is_some() {
|
||||
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
|
||||
param_array.push(switch_uuid);
|
||||
}
|
||||
|
||||
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
|
||||
let params = vec![switch_uuid].join("&");
|
||||
let params = param_array.join("&");
|
||||
let params_flag = if params.is_empty() { "" } else { "?" };
|
||||
#[allow(unused)]
|
||||
let uni_links = format!(
|
||||
@ -342,5 +348,11 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
|
||||
return if res { None } else { Some(Vec::new()) };
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
return Some(Vec::new());
|
||||
{
|
||||
return if let Err(_) = crate::ipc::send_url_scheme(uni_links) {
|
||||
Some(Vec::new())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,27 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
};
|
||||
use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char, thread};
|
||||
use std::str::FromStr;
|
||||
|
||||
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::is_keyboard_mode_supported;
|
||||
use hbb_common::message_proto::KeyboardMode;
|
||||
use hbb_common::ResultType;
|
||||
use hbb_common::{
|
||||
config::{self, LocalConfig, PeerConfig, ONLINE},
|
||||
config::{self, LocalConfig, ONLINE, PeerConfig},
|
||||
fs, log,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use hbb_common::message_proto::KeyboardMode;
|
||||
use hbb_common::ResultType;
|
||||
|
||||
// use crate::hbbs_http::account::AuthResult;
|
||||
|
||||
use crate::flutter::{self, SESSIONS};
|
||||
use crate::ui_interface::{self, *};
|
||||
use crate::{
|
||||
client::file_trait::FileManager,
|
||||
common::make_fd_to_json,
|
||||
flutter::{session_add, session_start_},
|
||||
};
|
||||
use crate::common::is_keyboard_mode_supported;
|
||||
use crate::flutter::{self, SESSIONS};
|
||||
use crate::ui_interface::{self, *};
|
||||
|
||||
// use crate::hbbs_http::account::AuthResult;
|
||||
|
||||
fn initialize(app_dir: &str) {
|
||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||
#[cfg(target_os = "android")]
|
||||
@ -523,6 +520,10 @@ pub fn main_get_sound_inputs() -> Vec<String> {
|
||||
vec![String::from("")]
|
||||
}
|
||||
|
||||
pub fn main_get_hostname() -> SyncReturn<String> {
|
||||
SyncReturn(crate::common::hostname())
|
||||
}
|
||||
|
||||
pub fn main_change_id(new_id: String) {
|
||||
change_id(new_id)
|
||||
}
|
||||
@ -787,6 +788,14 @@ pub fn main_default_video_save_directory() -> String {
|
||||
default_video_save_directory()
|
||||
}
|
||||
|
||||
pub fn main_set_user_default_option(key: String, value: String) {
|
||||
set_user_default_option(key, value);
|
||||
}
|
||||
|
||||
pub fn main_get_user_default_option(key: String) -> SyncReturn<String> {
|
||||
SyncReturn(get_user_default_option(key))
|
||||
}
|
||||
|
||||
pub fn session_add_port_forward(
|
||||
id: String,
|
||||
local_port: i32,
|
||||
@ -1237,16 +1246,38 @@ pub fn main_is_login_wayland() -> SyncReturn<bool> {
|
||||
SyncReturn(is_login_wayland())
|
||||
}
|
||||
|
||||
pub fn main_hide_docker() -> SyncReturn<bool> {
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::platform::macos::hide_dock();
|
||||
SyncReturn(true)
|
||||
}
|
||||
|
||||
/// Start an ipc server for receiving the url scheme.
|
||||
///
|
||||
/// * Should only be called in the main flutter window.
|
||||
/// * macOS only
|
||||
pub fn main_start_ipc_url_server() {
|
||||
#[cfg(target_os = "macos")]
|
||||
thread::spawn(move || crate::server::start_ipc_url_server());
|
||||
}
|
||||
|
||||
/// Send a url scheme throught the ipc.
|
||||
///
|
||||
/// * macOS only
|
||||
pub fn send_url_scheme(url: String) {
|
||||
#[cfg(target_os = "macos")]
|
||||
thread::spawn(move || crate::ui::macos::handle_url_scheme(url));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::log;
|
||||
use jni::{
|
||||
JNIEnv,
|
||||
objects::{JClass, JString},
|
||||
sys::jstring,
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
use hbb_common::log;
|
||||
|
||||
use crate::start_server;
|
||||
|
||||
#[no_mangle]
|
||||
|
11
src/ipc.rs
11
src/ipc.rs
@ -16,10 +16,10 @@ use hbb_common::{
|
||||
config::{self, Config, Config2},
|
||||
futures::StreamExt as _,
|
||||
futures_util::sink::SinkExt,
|
||||
log, password_security as password, timeout, tokio,
|
||||
log, password_security as password, ResultType, timeout,
|
||||
tokio,
|
||||
tokio::io::{AsyncRead, AsyncWrite},
|
||||
tokio_util::codec::Framed,
|
||||
ResultType,
|
||||
};
|
||||
|
||||
use crate::rendezvous_mediator::RendezvousMediator;
|
||||
@ -210,6 +210,7 @@ pub enum Data {
|
||||
DataPortableService(DataPortableService),
|
||||
SwitchSidesRequest(String),
|
||||
SwitchSidesBack,
|
||||
UrlLink(String)
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
@ -832,3 +833,9 @@ pub async fn test_rendezvous_server() -> ResultType<()> {
|
||||
c.send(&Data::TestRendezvousServer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn send_url_scheme(url: String) -> ResultType<()> {
|
||||
connect(1_000, "_url").await?.send(&Data::UrlLink(url)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Silenciar"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Entrada d'àudio"),
|
||||
("Enhancements", "Millores"),
|
||||
("Hardware Codec", "Còdec de hardware"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Sortir"),
|
||||
("Tags", ""),
|
||||
("Search ID", "Cerca ID"),
|
||||
("Current Wayland display server is not supported", "El servidor de visualització actual de Wayland no és compatible"),
|
||||
("whitelist_sep", ""),
|
||||
("Add ID", "Afegir ID"),
|
||||
("Add Tag", "Afegir tag"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "静音"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "音频输入"),
|
||||
("Enhancements", "增强功能"),
|
||||
("Hardware Codec", "硬件编解码"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "登出"),
|
||||
("Tags", "标签"),
|
||||
("Search ID", "查找ID"),
|
||||
("Current Wayland display server is not supported", "不支持 Wayland 显示服务器"),
|
||||
("whitelist_sep", "可以使用逗号,分号,空格或者换行符作为分隔符"),
|
||||
("Add ID", "增加ID"),
|
||||
("Add Tag", "增加标签"),
|
||||
@ -278,12 +280,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you accept?", "是否接受?"),
|
||||
("Open System Setting", "打开系统设置"),
|
||||
("How to get Android input permission?", "如何获取安卓的输入权限?"),
|
||||
("android_input_permission_tip1", "为了让远程设备通过鼠标或触屏控制您的安卓设备,你需要允許RustDesk使用\"无障碍\"服务。"),
|
||||
("android_input_permission_tip1", "为了让远程设备通过鼠标或触屏控制您的安卓设备,你需要允許 RustDesk 使用\"无障碍\"服务。"),
|
||||
("android_input_permission_tip2", "请在接下来的系统设置页面里,找到并进入 [已安装的服务] 页面,将 [RustDesk Input] 服务开启。"),
|
||||
("android_new_connection_tip", "收到新的连接控制请求,对方想要控制你当前的设备。"),
|
||||
("android_service_will_start_tip", "开启录屏权限将自动开启服务,允许其他设备向此设备请求建立连接。"),
|
||||
("android_stop_service_tip", "关闭服务将自动关闭所有已建立的连接。"),
|
||||
("android_version_audio_tip", "当前安卓版本不支持音频录制,请升级至安卓10或更高。"),
|
||||
("android_version_audio_tip", "当前安卓版本不支持音频录制,请升级至安卓 10 或更高。"),
|
||||
("android_start_service_tip", "点击 [启动服务] 或打开 [屏幕录制] 权限开启手机屏幕共享服务。"),
|
||||
("Account", "账户"),
|
||||
("Overwrite", "覆盖"),
|
||||
@ -373,7 +375,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Deny LAN Discovery", "拒绝局域网发现"),
|
||||
("Write a message", "输入聊天消息"),
|
||||
("Prompt", "提示"),
|
||||
("Please wait for confirmation of UAC...", "请等待对方确认UAC..."),
|
||||
("Please wait for confirmation of UAC...", "请等待对方确认 UAC ..."),
|
||||
("elevated_foreground_window_tip", "远端桌面的当前窗口需要更高的权限才能操作, 暂时无法使用鼠标键盘, 可以请求对方最小化当前窗口, 或者在连接管理窗口点击提升。为避免这个问题,建议在远端设备上安装本软件。"),
|
||||
("Disconnected", "会话已结束"),
|
||||
("Other", "其他"),
|
||||
@ -401,16 +403,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Request access to your device", "请求访问你的设备"),
|
||||
("Hide connection management window", "隐藏连接管理窗口"),
|
||||
("hide_cm_tip", "在只允许密码连接并且只用固定密码的情况下才允许隐藏"),
|
||||
("wayland_experiment_tip", "Wayland支持处于实验阶段,如果你需要使用无人值守访问,请使用X11。"),
|
||||
("wayland_experiment_tip", "Wayland 支持处于实验阶段,如果你需要使用无人值守访问,请使用X11。"),
|
||||
("Right click to select tabs", "右键选择选项卡"),
|
||||
("Skipped", "已跳过"),
|
||||
("Add to Address Book", "添加到地址簿"),
|
||||
("Group", "小组"),
|
||||
("Search", "搜索"),
|
||||
("Closed manually by web console", "被web控制台手动关闭"),
|
||||
("Closed manually by web console", "被 web 控制台手动关闭"),
|
||||
("Local keyboard type", "本地键盘类型"),
|
||||
("Select local keyboard type", "请选择本地键盘类型"),
|
||||
("software_render_tip", "如果你使用英伟达显卡, 并且远程窗口在会话建立后会立刻关闭, 那么安装nouveau驱动并且选择使用软件渲染可能会有帮助。重启软件后生效。"),
|
||||
("software_render_tip", "如果你使用英伟达显卡, 并且远程窗口在会话建立后会立刻关闭, 那么安装 nouveau 驱动并且选择使用软件渲染可能会有帮助。重启软件后生效。"),
|
||||
("Always use software rendering", "使用软件渲染"),
|
||||
("config_input", "为了能够通过键盘控制远程桌面, 请给予 RustDesk \"输入监控\" 权限。"),
|
||||
("request_elevation_tip", "如果对面有人, 也可以请求提升权限。"),
|
||||
@ -419,9 +421,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Ask the remote user for authentication", "请求远端用户授权"),
|
||||
("Choose this if the remote account is administrator", "当对面电脑是管理员账号时选择该选项"),
|
||||
("Transmit the username and password of administrator", "发送管理员账号的用户名密码"),
|
||||
("still_click_uac_tip", "依然需要被控端用戶在運行RustDesk的UAC窗口點擊確認。"),
|
||||
("still_click_uac_tip", "依然需要被控端用戶在運行 RustDesk 的 UAC 窗口點擊確認。"),
|
||||
("Request Elevation", "请求提权"),
|
||||
("wait_accept_uac_tip", "请等待远端用户确认UAC对话框。"),
|
||||
("wait_accept_uac_tip", "请等待远端用户确认 UAC 对话框。"),
|
||||
("Elevate successfully", "提权成功"),
|
||||
("uppercase", "大写字母"),
|
||||
("lowercase", "小写字母"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", "反转访问方向"),
|
||||
("Please confirm if you want to share your desktop?", "请确认要让对方访问你的桌面?"),
|
||||
("Closed as expected", "正常关闭"),
|
||||
("Display", "显示"),
|
||||
("Default View Style", "默认显示方式"),
|
||||
("Default Scroll Style", "默认滚动方式"),
|
||||
("Default Image Quality", "默认图像质量"),
|
||||
("Default Codec", "默认编解码"),
|
||||
("Bitrate", "波特率"),
|
||||
("FPS", "帧率"),
|
||||
("Auto", "自动"),
|
||||
("Other Default Options", "其它默认选项"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Ztlumit"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Vstup zvuku"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Odhlásit se"),
|
||||
("Tags", "Štítky"),
|
||||
("Search ID", "Hledat identifikátor"),
|
||||
("Current Wayland display server is not supported", "Zobrazovací server Wayland zatím není podporován"),
|
||||
("whitelist_sep", "Odělováno čárkou, středníkem, mezerou nebo koncem řádku"),
|
||||
("Add ID", "Přidat identifikátor"),
|
||||
("Add Tag", "Přidat štítek"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Sluk for mikrofonen"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Lydindgang"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "logger af"),
|
||||
("Tags", "Nøgleord"),
|
||||
("Search ID", "Søg ID"),
|
||||
("Current Wayland display server is not supported", "Den aktuelle Wayland-Anzege-server understøttes ikke"),
|
||||
("whitelist_sep", "Adskilt af komma, semikolon, rum eller linjepaus"),
|
||||
("Add ID", "Tilføj ID"),
|
||||
("Add Tag", "Tilføj nøgleord"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "Mit Herzblut programmiert - in einer Welt, die im Chaos versinkt!"),
|
||||
("Privacy Statement", "Datenschutz"),
|
||||
("Mute", "Stummschalten"),
|
||||
("Build Date", "Erstelldatum"),
|
||||
("Version", "Version"),
|
||||
("Home", "Startseite"),
|
||||
("Audio Input", "Audioeingang"),
|
||||
("Enhancements", "Verbesserungen"),
|
||||
("Hardware Codec", "Hardware-Codec"),
|
||||
@ -197,7 +200,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Warning", "Warnung"),
|
||||
("Login screen using Wayland is not supported", "Anmeldebildschirm mit Wayland wird nicht unterstützt."),
|
||||
("Reboot required", "Neustart erforderlich"),
|
||||
("Unsupported display server ", "Nicht unterstützter Display-Server"),
|
||||
("Unsupported display server ", "Nicht unterstützter Anzeigeserver"),
|
||||
("x11 expected", "X11 erwartet"),
|
||||
("Port", "Port"),
|
||||
("Settings", "Einstellungen"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Abmelden"),
|
||||
("Tags", "Schlagworte"),
|
||||
("Search ID", "Suche ID"),
|
||||
("Current Wayland display server is not supported", "Der aktuelle Wayland-Anzeigeserver wird nicht unterstützt."),
|
||||
("whitelist_sep", "Getrennt durch Komma, Semikolon, Leerzeichen oder Zeilenumbruch"),
|
||||
("Add ID", "ID hinzufügen"),
|
||||
("Add Tag", "Stichwort hinzufügen"),
|
||||
@ -241,7 +243,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remote ID", "Entfernte ID"),
|
||||
("Paste", "Einfügen"),
|
||||
("Paste here?", "Hier einfügen?"),
|
||||
("Are you sure to close the connection?", "Möchten Sie diese Verbindung wirklich trennen?"),
|
||||
("Are you sure to close the connection?", "Möchten Sie diese Verbindung wirklich schließen?"),
|
||||
("Download new version", "Neue Version herunterladen"),
|
||||
("Touch mode", "Touch-Modus"),
|
||||
("Mouse mode", "Mausmodus"),
|
||||
@ -264,8 +266,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Note", "Hinweis"),
|
||||
("Connection", "Verbindung"),
|
||||
("Share Screen", "Bildschirm freigeben"),
|
||||
("CLOSE", "DEAKTIV."),
|
||||
("OPEN", "AKTIVIER."),
|
||||
("CLOSE", "SCHLIEẞEN"),
|
||||
("OPEN", "ÖFFNEN"),
|
||||
("Chat", "Chat"),
|
||||
("Total", "Gesamt"),
|
||||
("items", "Einträge"),
|
||||
@ -325,7 +327,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Mobile Actions", "Mobile Aktionen"),
|
||||
("Select Monitor", "Bildschirm auswählen"),
|
||||
("Control Actions", "Aktionen"),
|
||||
("Display Settings", "Bildschirmeinstellungen"),
|
||||
("Display Settings", "Anzeigeeinstellungen"),
|
||||
("Ratio", "Verhältnis"),
|
||||
("Image Quality", "Bildqualität"),
|
||||
("Scroll Style", "Scroll-Stil"),
|
||||
@ -336,7 +338,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Secure Connection", "Sichere Verbindung"),
|
||||
("Insecure Connection", "Unsichere Verbindung"),
|
||||
("Scale original", "Keine Skalierung"),
|
||||
("Scale adaptive", "Automatische Skalierung"),
|
||||
("Scale adaptive", "Anpassbare Skalierung"),
|
||||
("General", "Allgemein"),
|
||||
("Security", "Sicherheit"),
|
||||
("Theme", "Farbgebung"),
|
||||
@ -356,7 +358,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clear", "Zurücksetzen"),
|
||||
("Audio Input Device", "Audioeingabegerät"),
|
||||
("Deny remote access", "Fernzugriff verbieten"),
|
||||
("Use IP Whitelisting", "IP-Whitelist benutzen"),
|
||||
("Use IP Whitelisting", "IP-Whitelist verwenden"),
|
||||
("Network", "Netzwerk"),
|
||||
("Enable RDP", "RDP aktivieren"),
|
||||
("Pin menubar", "Menüleiste anpinnen"),
|
||||
@ -384,7 +386,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland erfordert Ubuntu 21.04 oder eine höhere Version."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland erfordert eine höhere Version der Linux-Distribution. Bitte versuchen Sie den X11-Desktop oder ändern Sie Ihr Betriebssystem."),
|
||||
("JumpLink", "View"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den Bildschirm aus, der freigegeben werden soll (auf der Peer-Seite arbeiten)."),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den freizugebenden Bildschirm aus (Bedienung auf der Peer-Seite)."),
|
||||
("Show RustDesk", "RustDesk anzeigen"),
|
||||
("This PC", "Dieser PC"),
|
||||
("or", "oder"),
|
||||
@ -407,7 +409,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "Zum Adressbuch hinzufügen"),
|
||||
("Group", "Gruppe"),
|
||||
("Search", "Suchen"),
|
||||
("Closed manually by web console", "Manuell über die Webkonsole beendet"),
|
||||
("Closed manually by web console", "Manuell über die Webkonsole geschlossen"),
|
||||
("Local keyboard type", "Lokaler Tastaturtyp"),
|
||||
("Select local keyboard type", "Lokalen Tastaturtyp auswählen"),
|
||||
("software_render_tip", "Wenn Sie eine Nvidia-Grafikkarte haben und sich das entfernte Fenster sofort nach dem Herstellen der Verbindung schließt, kann es helfen, den Nouveau-Treiber zu installieren und Software-Rendering zu verwenden. Ein Neustart der Software ist erforderlich."),
|
||||
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Strong", "Stark"),
|
||||
("Switch Sides", "Seiten wechseln"),
|
||||
("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, ob Sie Ihren Desktop freigeben möchten."),
|
||||
("Closed as expected", ""),
|
||||
("Closed as expected", "Wie erwartet geschlossen"),
|
||||
("Display", "Anzeige"),
|
||||
("Default View Style", "Standard-Ansichtsstil"),
|
||||
("Default Scroll Style", "Standard-Scroll-Stil"),
|
||||
("Default Image Quality", "Standard-Bildqualität"),
|
||||
("Default Codec", "Standard-Codec"),
|
||||
("Bitrate", "Bitrate"),
|
||||
("FPS", "fps"),
|
||||
("Auto", "Automatisch"),
|
||||
("Other Default Options", "Weitere Standardoptionen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Muta"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Aŭdia enigo"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Malkonekti"),
|
||||
("Tags", "Etikedi"),
|
||||
("Search ID", "Serĉi ID"),
|
||||
("Current Wayland display server is not supported", "La aktuala bilda servilo Wayland ne estas subtenita"),
|
||||
("whitelist_sep", "Vi povas uzi komon, punktokomon, spacon aŭ linsalton kiel apartigilo"),
|
||||
("Add ID", "Aldoni identigilo"),
|
||||
("Add Tag", "Aldoni etikedo"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "Hecho con corazón en este mundo caótico!"),
|
||||
("Privacy Statement", "Declaración de privacidad"),
|
||||
("Mute", "Silenciar"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Entrada de audio"),
|
||||
("Enhancements", "Mejoras"),
|
||||
("Hardware Codec", "Códec de hardware"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Salir"),
|
||||
("Tags", "Tags"),
|
||||
("Search ID", "Buscar ID"),
|
||||
("Current Wayland display server is not supported", "El servidor de visualización actual de Wayland no es compatible"),
|
||||
("whitelist_sep", "Separados por coma, punto y coma, espacio o nueva línea"),
|
||||
("Add ID", "Agregar ID"),
|
||||
("Add Tag", "Agregar tag"),
|
||||
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Strong", "Fuerte"),
|
||||
("Switch Sides", "Intercambiar lados"),
|
||||
("Please confirm if you want to share your desktop?", "Por favor, confirma si quieres compartir tu escritorio"),
|
||||
("Closed as expected", ""),
|
||||
("Closed as expected", "Cerrado como se esperaba"),
|
||||
("Display", "Pantalla"),
|
||||
("Default View Style", "Estilo de vista predeterminado"),
|
||||
("Default Scroll Style", "Estilo de desplazamiento predeterminado"),
|
||||
("Default Image Quality", "Calidad de imagen predeterminada"),
|
||||
("Default Codec", "Códec predeterminado"),
|
||||
("Bitrate", "Tasa de bits"),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", "Otras opciones predeterminadas"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "بستن صدا"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "ورودی صدا"),
|
||||
("Enhancements", "بهبودها"),
|
||||
("Hardware Codec", "کدک سخت افزاری"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "خروج"),
|
||||
("Tags", "برچسب ها"),
|
||||
("Search ID", "جستجوی شناسه"),
|
||||
("Current Wayland display server is not supported", "پشتیبانی نمی شود Wayland سرور نمایش فعلی"),
|
||||
("whitelist_sep", "با کاما، نقطه ویرگول، فاصله یا خط جدید از هم جدا می شوند"),
|
||||
("Add ID", "افزودن شناسه"),
|
||||
("Add Tag", "افزودن برچسب"),
|
||||
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Strong", "قوی"),
|
||||
("Switch Sides", "طرفین را عوض کنید"),
|
||||
("Please confirm if you want to share your desktop?", "لطفاً تأیید کنید که آیا می خواهید دسکتاپ خود را به اشتراک بگذارید؟"),
|
||||
("Closed as expected", ""),
|
||||
("Closed as expected", "طبق انتظار بسته شد"),
|
||||
("Display", "نمایش دادن"),
|
||||
("Default View Style", "سبک نمایش پیش فرض"),
|
||||
("Default Scroll Style", "سبک پیشفرض اسکرول"),
|
||||
("Default Image Quality", "کیفیت تصویر پیش فرض"),
|
||||
("Default Codec", "کدک پیش فرض"),
|
||||
("Bitrate", "میزان بیت صفحه نمایش"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "خودکار"),
|
||||
("Other Default Options", "سایر گزینه های پیش فرض"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "Fait avec cœur dans ce monde chaotique!"),
|
||||
("Privacy Statement", "Déclaration de confidentialité"),
|
||||
("Mute", "Muet"),
|
||||
("Build Date", "Date de compilation"),
|
||||
("Version", "Version"),
|
||||
("Home", "Accueil"),
|
||||
("Audio Input", "Entrée audio"),
|
||||
("Enhancements", "Améliorations"),
|
||||
("Hardware Codec", "Transcodage matériel"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Déconnexion"),
|
||||
("Tags", "Étiqueter"),
|
||||
("Search ID", "Rechercher un ID"),
|
||||
("Current Wayland display server is not supported", "Le serveur d'affichage Wayland n'est pas pris en charge"),
|
||||
("whitelist_sep", "Vous pouvez utiliser une virgule, un point-virgule, un espace ou une nouvelle ligne comme séparateur"),
|
||||
("Add ID", "Ajouter un ID"),
|
||||
("Add Tag", "Ajouter une balise"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", "Inverser la prise de contrôle"),
|
||||
("Please confirm if you want to share your desktop?", "Veuillez confirmer le partager de votre bureau ?"),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Σίγαση"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Είσοδος ήχου"),
|
||||
("Enhancements", "Βελτιώσεις"),
|
||||
("Hardware Codec", "Κωδικοποιητής υλικού"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Αποσύνδεση"),
|
||||
("Tags", "Ετικέτες"),
|
||||
("Search ID", "Αναζήτηση ID"),
|
||||
("Current Wayland display server is not supported", "Ο τρέχων διακομιστής εμφάνισης Wayland δεν υποστηρίζεται"),
|
||||
("whitelist_sep", "Διαχωρίζονται με κόμμα, ερωτηματικό, διάστημα ή νέα γραμμή"),
|
||||
("Add ID", "Προσθήκη αναγνωριστικού ID"),
|
||||
("Add Tag", "Προσθήκη ετικέτας"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Némítás"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Hangátvitel"),
|
||||
("Enhancements", "Fejlesztések"),
|
||||
("Hardware Codec", "Hardware kodek"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Kilépés"),
|
||||
("Tags", "Tagok"),
|
||||
("Search ID", "Azonosító keresése..."),
|
||||
("Current Wayland display server is not supported", "A Wayland display szerver nem támogatott"),
|
||||
("whitelist_sep", "A címeket veszővel, pontosvesszővel, szóközzel, vagy új sorral válassza el"),
|
||||
("Add ID", "Azonosító hozzáadása"),
|
||||
("Add Tag", "Címke hozzáadása"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", "Pernyataan Privasi"),
|
||||
("Mute", "Bisukan"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Masukkan Audio"),
|
||||
("Enhancements", "Peningkatan"),
|
||||
("Hardware Codec", "Codec Perangkat Keras"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Keluar"),
|
||||
("Tags", "Tag"),
|
||||
("Search ID", "Cari ID"),
|
||||
("Current Wayland display server is not supported", "Server tampilan Wayland saat ini tidak didukung"),
|
||||
("whitelist_sep", "Dipisahkan dengan koma, titik koma, spasi, atau baris baru"),
|
||||
("Add ID", "Tambah ID"),
|
||||
("Add Tag", "Tambah Tag"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "Fatta con il cuore in questo mondo caotico!"),
|
||||
("Privacy Statement", "Informativa sulla privacy"),
|
||||
("Mute", "Silenzia"),
|
||||
("Build Date", "Data della build"),
|
||||
("Version", "Versione"),
|
||||
("Home", "Home"),
|
||||
("Audio Input", "Input audio"),
|
||||
("Enhancements", "Miglioramenti"),
|
||||
("Hardware Codec", "Codifica Hardware"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Esci"),
|
||||
("Tags", "Tag"),
|
||||
("Search ID", "Cerca ID"),
|
||||
("Current Wayland display server is not supported", "Questo display server Wayland non è supportato"),
|
||||
("whitelist_sep", "Separati da virgola, punto e virgola, spazio o a capo"),
|
||||
("Add ID", "Aggiungi ID"),
|
||||
("Add Tag", "Aggiungi tag"),
|
||||
@ -433,6 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Strong", "Forte"),
|
||||
("Switch Sides", "Cambia lato"),
|
||||
("Please confirm if you want to share your desktop?", "Vuoi condividere il tuo desktop?"),
|
||||
("Closed as expected", ""),
|
||||
("Closed as expected", "Chiuso come previsto"),
|
||||
("Display", "Visualizzazione"),
|
||||
("Default View Style", "Stile Visualizzazione Predefinito"),
|
||||
("Default Scroll Style", "Stile Scorrimento Predefinito"),
|
||||
("Default Image Quality", "Qualità Immagine Predefinita"),
|
||||
("Default Codec", "Codec Predefinito"),
|
||||
("Bitrate", "Bitrate"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "Auto"),
|
||||
("Other Default Options", "Altre Opzioni Predefinite"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "ミュート"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "音声入力デバイス"),
|
||||
("Enhancements", "追加機能"),
|
||||
("Hardware Codec", "ハードウェア コーデック"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "ログアウト"),
|
||||
("Tags", "タグ"),
|
||||
("Search ID", "IDを検索"),
|
||||
("Current Wayland display server is not supported", "現在のWaylandディスプレイサーバーはサポートされていません"),
|
||||
("whitelist_sep", "カンマやセミコロン、空白、改行で区切ってください"),
|
||||
("Add ID", "IDを追加"),
|
||||
("Add Tag", "タグを追加"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "음소거"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "오디오 입력"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", "하드웨어 코덱"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "로그아웃"),
|
||||
("Tags", "태그"),
|
||||
("Search ID", "ID 검색"),
|
||||
("Current Wayland display server is not supported", "현재 Wayland 디스플레이 서버가 지원되지 않습니다"),
|
||||
("whitelist_sep", "다음 글자로 구분합니다. ',(콤마) ;(세미콜론) 띄어쓰기 혹은 줄바꿈'"),
|
||||
("Add ID", "ID 추가"),
|
||||
("Add Tag", "태그 추가"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Дыбыссыздандыру"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Аудио Еңгізу"),
|
||||
("Enhancements", "Жақсартулар"),
|
||||
("Hardware Codec", "Hardware Codec"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Шығу"),
|
||||
("Tags", "Тақтар"),
|
||||
("Search ID", "ID Іздеу"),
|
||||
("Current Wayland display server is not supported", "Ағымдағы Wayland дисплей серберіне қолдау көрсетілмейді"),
|
||||
("whitelist_sep", "Үтір, нүктелі үтір, бос орын және жаңа жолал арқылы бөлінеді"),
|
||||
("Add ID", "ID Қосу"),
|
||||
("Add Tag", "Тақ Қосу"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
125
src/lang/pl.rs
125
src/lang/pl.rs
@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Status"),
|
||||
("Your Desktop", "Twój pulpit"),
|
||||
("desk_tip", "W celu zestawienia połączenia z tym urządzeniem należy poniższego ID i hasła."),
|
||||
("desk_tip", "W celu połączenia się z tym urządzeniem należy użyć poniższego ID i hasła"),
|
||||
("Password", "Hasło"),
|
||||
("Ready", "Gotowe"),
|
||||
("Established", "Nawiązano"),
|
||||
@ -38,10 +38,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop service", "Zatrzymaj usługę"),
|
||||
("Change ID", "Zmień ID"),
|
||||
("Website", "Strona internetowa"),
|
||||
("About", "O"),
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("About", "O aplikacji"),
|
||||
("Slogan_tip", "Tworzone z miłością w tym pełnym chaosu świecie!"),
|
||||
("Privacy Statement", "Oświadczenie o ochronie prywatności"),
|
||||
("Mute", "Wycisz"),
|
||||
("Build Date", "Zbudowano"),
|
||||
("Version", "Wersja"),
|
||||
("Home", "Pulpit"),
|
||||
("Audio Input", "Wejście audio"),
|
||||
("Enhancements", "Ulepszenia"),
|
||||
("Hardware Codec", "Kodek sprzętowy"),
|
||||
@ -96,7 +99,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Directory", "Pusty katalog"),
|
||||
("Not an empty directory", "Katalog nie jest pusty"),
|
||||
("Are you sure you want to delete this file?", "Czy na pewno chcesz usunąć ten plik?"),
|
||||
("Are you sure you want to delete this empty directory?", "Czy na pewno chcesz usunać ten pusty katalog?"),
|
||||
("Are you sure you want to delete this empty directory?", "Czy na pewno chcesz usunąć ten pusty katalog?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Czy na pewno chcesz usunąć pliki z tego katalogu?"),
|
||||
("Do this for all conflicts", "wykonaj dla wszystkich konfliktów"),
|
||||
("This is irreversible!", "To jest nieodwracalne!"),
|
||||
@ -118,7 +121,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Good image quality", "Dobra jakość obrazu"),
|
||||
("Balanced", "Zrównoważony"),
|
||||
("Optimize reaction time", "Zoptymalizuj czas reakcji"),
|
||||
("Custom", "Własne"),
|
||||
("Custom", "Niestandardowe"),
|
||||
("Show remote cursor", "Pokazuj zdalny kursor"),
|
||||
("Show quality monitor", "Parametry połączenia"),
|
||||
("Disable clipboard", "Wyłącz schowek"),
|
||||
@ -138,10 +141,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to make direct connection to remote desktop", "Nie udało się nawiązać bezpośredniego połączenia z pulpitem zdalnym"),
|
||||
("Set Password", "Ustaw hasło"),
|
||||
("OS Password", "Hasło systemu operacyjnego"),
|
||||
("install_tip", "RustDesk może nie działać poprawnie na maszynie zdalnej z przyczyn związanych z UAC. W celu uniknięcią problemów z UAC, kliknij poniższy przycisk by zainstalować RustDesk w swoim systemie."),
|
||||
("install_tip", "RustDesk może nie działać poprawnie na maszynie zdalnej z przyczyn związanych z UAC. W celu uniknięcia problemów z UAC, kliknij poniższy przycisk by zainstalować RustDesk w swoim systemie."),
|
||||
("Click to upgrade", "Zaktualizuj"),
|
||||
("Click to download", "Pobierz"),
|
||||
("Click to update", "Uaktualinij"),
|
||||
("Click to update", "Uaktualnij"),
|
||||
("Configure", "Konfiguruj"),
|
||||
("config_acc", "Konfiguracja konta"),
|
||||
("config_screen", "Konfiguracja ekranu"),
|
||||
@ -208,17 +211,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Run without install", "Uruchom bez instalacji"),
|
||||
("Always connected via relay", "Zawsze połączony pośrednio"),
|
||||
("Always connect via relay", "Zawsze łącz pośrednio"),
|
||||
("whitelist_tip", "Zezwlaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
|
||||
("whitelist_tip", "Zezwalaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
|
||||
("Login", "Zaloguj"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "Zweryfikuj"),
|
||||
("Remember me", "Zapamiętaj mnie"),
|
||||
("Trust this device", "Dodaj to urządzenie do zaufanych"),
|
||||
("Verification code", "Kod weryfikacyjny"),
|
||||
("verification_tip", "Nastąpiło logowanie z nowego urządzenia, kod weryfikacyjny został wysłany na podany adres email, wprowadź kod by kontynuować proces logowania"),
|
||||
("Logout", "Wyloguj"),
|
||||
("Tags", "Tagi"),
|
||||
("Search ID", "Szukaj ID"),
|
||||
("Current Wayland display server is not supported", "Obecny serwer wyświetlania Wayland nie jest obsługiwany"),
|
||||
("whitelist_sep", "Oddzielone przecinkiem, średnikiem, spacją lub w nowej linii"),
|
||||
("Add ID", "Dodaj ID"),
|
||||
("Add Tag", "Dodaj Tag"),
|
||||
@ -232,7 +234,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Favorites", "Ulubione"),
|
||||
("Add to Favorites", "Dodaj do ulubionych"),
|
||||
("Remove from Favorites", "Usuń z ulubionych"),
|
||||
("Empty", "Pusty"),
|
||||
("Empty", "Pusto"),
|
||||
("Invalid folder name", "Nieprawidłowa nazwa folderu"),
|
||||
("Socks5 Proxy", "Socks5 Proxy"),
|
||||
("Hostname", "Nazwa hosta"),
|
||||
@ -331,7 +333,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scroll Style", "Styl przewijania"),
|
||||
("Show Menubar", "Pokaż pasek menu"),
|
||||
("Hide Menubar", "Ukryj pasek menu"),
|
||||
("Direct Connection", "Połącznie bezpośrednie"),
|
||||
("Direct Connection", "Połączenie bezpośrednie"),
|
||||
("Relay Connection", "Połączenie przez bramkę"),
|
||||
("Secure Connection", "Połączenie szyfrowane"),
|
||||
("Insecure Connection", "Połączenie nieszyfrowane"),
|
||||
@ -344,12 +346,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Dark", "Ciemny"),
|
||||
("Light", "Jasny"),
|
||||
("Follow System", "Zgodne z systemem"),
|
||||
("Enable hardware codec", "Włącz wsparcie sprzętowe dla kodeków"),
|
||||
("Unlock Security Settings", "Odblokuj Ustawienia Zabezpieczeń"),
|
||||
("Enable Audio", "Włącz Dźwięk"),
|
||||
("Enable hardware codec", "Włącz akcelerację sprzętową kodeków"),
|
||||
("Unlock Security Settings", "Odblokuj ustawienia zabezpieczeń"),
|
||||
("Enable Audio", "Włącz dźwięk"),
|
||||
("Unlock Network Settings", "Odblokuj ustawienia Sieciowe"),
|
||||
("Server", "Serwer"),
|
||||
("Direct IP Access", "Bezpośredni Adres IP"),
|
||||
("Direct IP Access", "Bezpośredni adres IP"),
|
||||
("Proxy", "Proxy"),
|
||||
("Apply", "Zastosuj"),
|
||||
("Disconnect all devices?", "Czy rozłączyć wszystkie urządzenia?"),
|
||||
@ -361,20 +363,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable RDP", "Włącz RDP"),
|
||||
("Pin menubar", "Przypnij pasek menu"),
|
||||
("Unpin menubar", "Odepnij pasek menu"),
|
||||
("Recording", "Trwa nagrywanie"),
|
||||
("Recording", "Nagrywanie"),
|
||||
("Directory", "Katalog"),
|
||||
("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"),
|
||||
("Change", "Zmień"),
|
||||
("Start session recording", "Zacznij nagrywać sesję"),
|
||||
("Stop session recording", "Zatrzymaj nagrywanie sesji"),
|
||||
("Enable Recording Session", "Włącz Nagrywanie Sesji"),
|
||||
("Enable Recording Session", "Włącz nagrywanie Sesji"),
|
||||
("Allow recording session", "Zezwól na nagrywanie sesji"),
|
||||
("Enable LAN Discovery", "Włącz wykrywanie urządzenia w sieci LAN"),
|
||||
("Deny LAN Discovery", "Zablokuj wykrywanie urządzenia w sieci LAN"),
|
||||
("Write a message", "Napisz wiadomość"),
|
||||
("Prompt", "Monit"),
|
||||
("Please wait for confirmation of UAC...", "Oczekuje potwierdzenia ustawień UAC"),
|
||||
("elevated_foreground_window_tip", ""),
|
||||
("Please wait for confirmation of UAC...", "Poczekaj na potwierdzenie uprawnień UAC"),
|
||||
("elevated_foreground_window_tip", "Aktualne okno zdalnego urządzenia wymaga wyższych uprawnień by poprawnie działać, chwilowo niemożliwym jest korzystanie z myszy i klawiatury. Możesz poprosić zdalnego użytkownika o minimalizację okna, lub nacisnąć przycisk podniesienia uprawnień w oknie zarządzania połączeniami. By uniknąć tego problemu, rekomendujemy instalację oprogramowania na urządzeniu zdalnym."),
|
||||
("Disconnected", "Rozłączone"),
|
||||
("Other", "Inne"),
|
||||
("Confirm before closing multiple tabs", "Potwierdź przed zamknięciem wielu kart"),
|
||||
@ -382,7 +384,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Full Access", "Pełny dostęp"),
|
||||
("Screen Share", "Udostępnianie ekranu"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland wymaga Ubuntu 21.04 lub nowszego."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland wymaga wyższej wersji dystrybucji Linuksa. Wypróbuj pulpit X11 lub zmień system operacyjny."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland wymaga nowszej dystrybucji Linuksa. Wypróbuj pulpit X11 lub zmień system operacyjny."),
|
||||
("JumpLink", "View"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po stronie równorzędnej)."),
|
||||
("Show RustDesk", "Pokaż RustDesk"),
|
||||
@ -400,39 +402,48 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("One-time password length", "Długość hasła jednorazowego"),
|
||||
("Request access to your device", "Żądanie dostępu do Twojego urządzenia"),
|
||||
("Hide connection management window", "Ukryj okno zarządzania połączeniem"),
|
||||
("hide_cm_tip", ""),
|
||||
("wayland_experiment_tip", ""),
|
||||
("Right click to select tabs", ""),
|
||||
("Skipped", ""),
|
||||
("hide_cm_tip", "Pozwalaj na ukrycie tylko gdy akceptujesz sesje za pośrednictwem hasła i używasz hasła permanentnego"),
|
||||
("wayland_experiment_tip", "Wsparcie dla Wayland jest niekompletne, użyj X11 jeżeli chcesz korzystać z dostępu nienadzorowanego"),
|
||||
("Right click to select tabs", "Kliknij prawym przyciskiem myszy by wybrać zakładkę"),
|
||||
("Skipped", "Pominięte"),
|
||||
("Add to Address Book", "Dodaj do Książki Adresowej"),
|
||||
("Group", "Grypy"),
|
||||
("Search", "Szukaj"),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Closed manually by web console", "Zakończone manualnie z konsoli Web"),
|
||||
("Local keyboard type", "Lokalny typ klawiatury"),
|
||||
("Select local keyboard type", "Wybierz lokalny typ klawiatury"),
|
||||
("software_render_tip", "Jeżeli posiadasz kartę graficzną Nvidia i okno zamyka się natychmiast po nawiązaniu połączenia, instalacja sterownika nouveau i wybór renderowania programowego mogą pomóc. Restart aplikacji jest wymagany."),
|
||||
("Always use software rendering", "Zawsze używaj renderowania programowego"),
|
||||
("config_input", "By kontrolować zdalne urządzenie przy pomocy klawiatury, musisz udzielić aplikacji RustDesk uprawnień do \"Urządzeń Wejściowych\"."),
|
||||
("request_elevation_tip", "Możesz poprosić o podniesienie uprawnień jeżeli ktoś posiada dostęp do zdalnego urządzenia."),
|
||||
("Wait", "Czekaj"),
|
||||
("Elevation Error", "Błąd przy podnoszeniu uprawnień"),
|
||||
("Ask the remote user for authentication", "Poproś użytkownika zdalnego o uwierzytelnienie"),
|
||||
("Choose this if the remote account is administrator", "Wybierz to jeżeli zdalne konto jest administratorem"),
|
||||
("Transmit the username and password of administrator", "Prześlij nazwę użytkownika i hasło administratora"),
|
||||
("still_click_uac_tip", "Nadal wymaga od zdalnego użytkownika potwierdzenia uprawnień UAC."),
|
||||
("Request Elevation", "Poproś o podniesienie uprawnień"),
|
||||
("wait_accept_uac_tip", "Prosimy czekać aż zdalny użytkownik potwierdzi uprawnienia UAC."),
|
||||
("Elevate successfully", "Pomyślnie podniesiono uprawnienia"),
|
||||
("uppercase", "wielkie litery"),
|
||||
("lowercase", "małe litery"),
|
||||
("digit", "cyfra"),
|
||||
("special character", "znak specjalny"),
|
||||
("length>=8", "długość>=8"),
|
||||
("Weak", "Słabe"),
|
||||
("Medium", "Średnie"),
|
||||
("Strong", "Mocne"),
|
||||
("Switch Sides", "Zmień Strony"),
|
||||
("Please confirm if you want to share your desktop?", "Czy na pewno chcesz udostępnić swój ekran?"),
|
||||
("Closed as expected", "Zamknięto pomyślnie"),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Silenciar"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Entrada de Áudio"),
|
||||
("Enhancements", "Melhorias"),
|
||||
("Hardware Codec", ""),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Sair"),
|
||||
("Tags", "Tags"),
|
||||
("Search ID", "Procurar ID"),
|
||||
("Current Wayland display server is not supported", "Servidor de display Wayland atual não é suportado"),
|
||||
("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
|
||||
("Add ID", "Adicionar ID"),
|
||||
("Add Tag", "Adicionar Tag"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Desativar som"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Entrada de Áudio"),
|
||||
("Enhancements", "Melhorias"),
|
||||
("Hardware Codec", "Codec de hardware"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Sair"),
|
||||
("Tags", "Tags"),
|
||||
("Search ID", "Pesquisar ID"),
|
||||
("Current Wayland display server is not supported", "Servidor de display Wayland atual não é suportado"),
|
||||
("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
|
||||
("Add ID", "Adicionar ID"),
|
||||
("Add Tag", "Adicionar Tag"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Fără sunet"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Intrare audio"),
|
||||
("Enhancements", "Îmbunătățiri"),
|
||||
("Hardware Codec", "Codec hardware"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Deconectare"),
|
||||
("Tags", "Etichetare"),
|
||||
("Search ID", "Caută după ID"),
|
||||
("Current Wayland display server is not supported", "Serverul de afișaj Wayland nu este acceptat"),
|
||||
("whitelist_sep", "Poți folosi ca separator virgula, punctul și virgula, spațiul sau linia nouă"),
|
||||
("Add ID", "Adaugă ID"),
|
||||
("Add Tag", "Adaugă etichetă"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "Сделано с душой в этом безумном мире!"),
|
||||
("Privacy Statement", "Заявление о конфиденциальности"),
|
||||
("Mute", "Отключить звук"),
|
||||
("Build Date", "Дата сборки"),
|
||||
("Version", "Версия"),
|
||||
("Home", "Главная"),
|
||||
("Audio Input", "Аудиовход"),
|
||||
("Enhancements", "Улучшения"),
|
||||
("Hardware Codec", "Аппаратный кодек"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Выйти"),
|
||||
("Tags", "Метки"),
|
||||
("Search ID", "Поиск по ID"),
|
||||
("Current Wayland display server is not supported", "Текущий сервер отображения Wayland не поддерживается"),
|
||||
("whitelist_sep", "Раздельно запятой, точкой с запятой, пробелом или новой строкой"),
|
||||
("Add ID", "Добавить ID"),
|
||||
("Add Tag", "Добавить ключевое слово"),
|
||||
@ -432,7 +434,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Medium", "Средний"),
|
||||
("Strong", "Стойкий"),
|
||||
("Switch Sides", "Переключить стороны"),
|
||||
("Please confirm if you want to share your desktop?", "Подтвердите, что хотите поделиться своим рабочим столом?"),
|
||||
("Closed as expected", ""),
|
||||
("Please confirm if you want to share your desktop?", "Подтверждаете, что хотите поделиться своим рабочим столом?"),
|
||||
("Closed as expected", "Закрыто по ожиданию"),
|
||||
("Display", "Отображение"),
|
||||
("Default View Style", "Стиль отображения по умолчанию"),
|
||||
("Default Scroll Style", "Стиль прокрутки по умолчанию"),
|
||||
("Default Image Quality", "Качество изображения по умолчанию"),
|
||||
("Default Codec", "Кодек по умолчанию"),
|
||||
("Bitrate", "Битрейт"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "Авто"),
|
||||
("Other Default Options", "Другие параметры по умолчанию"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Stíšiť"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Zvukový vstup"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Odhlásenie"),
|
||||
("Tags", "Štítky"),
|
||||
("Search ID", "Hľadať ID"),
|
||||
("Current Wayland display server is not supported", "Zobrazovací (display) server Wayland nie je podporovaný"),
|
||||
("whitelist_sep", "Oddelené čiarkou, bodkočiarkou, medzerou alebo koncom riadku"),
|
||||
("Add ID", "Pridať ID"),
|
||||
("Add Tag", "Pridať štítok"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Izklopi zvok"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Avdio vhod"),
|
||||
("Enhancements", "Izboljšave"),
|
||||
("Hardware Codec", "Strojni kodek"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Odjavi"),
|
||||
("Tags", "Oznake"),
|
||||
("Search ID", "Išči ID"),
|
||||
("Current Wayland display server is not supported", "Trenutni Wayland zaslonski strežnik ni podprt"),
|
||||
("whitelist_sep", "Naslovi ločeni z vejico, podpičjem, presledkom ali novo vrstico"),
|
||||
("Add ID", "Dodaj ID"),
|
||||
("Add Tag", "Dodaj oznako"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Pa zë"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Inputi zërit"),
|
||||
("Enhancements", "Përmirësimet"),
|
||||
("Hardware Codec", "Kodeku Harduerik"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Dalje"),
|
||||
("Tags", "Tage"),
|
||||
("Search ID", "Kerko ID"),
|
||||
("Current Wayland display server is not supported", "Serveri aktual i ekranit Wayland nuk mbështetet"),
|
||||
("whitelist_sep", "Të ndara me presje, pikëpresje, hapësira ose rresht të ri"),
|
||||
("Add ID", "Shto ID"),
|
||||
("Add Tag", "Shto Tag"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Utišaj"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Audio ulaz"),
|
||||
("Enhancements", "Proširenja"),
|
||||
("Hardware Codec", "Hardverski kodek"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Odjava"),
|
||||
("Tags", "Oznake"),
|
||||
("Search ID", "Traži ID"),
|
||||
("Current Wayland display server is not supported", "Tekući Wazland server za prikaz nije podržan"),
|
||||
("whitelist_sep", "Odvojeno zarezima, tačka zarezima, praznim mestima ili novim redovima"),
|
||||
("Add ID", "Dodaj ID"),
|
||||
("Add Tag", "Dodaj oznaku"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Tyst"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Ljud input"),
|
||||
("Enhancements", "Förbättringar"),
|
||||
("Hardware Codec", "Hårdvarucodec"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Logga ut"),
|
||||
("Tags", "Taggar"),
|
||||
("Search ID", "Sök ID"),
|
||||
("Current Wayland display server is not supported", "Nuvarande Wayland displayserver stöds inte"),
|
||||
("whitelist_sep", "Separerat av ett comma, semikolon, mellanslag eller ny linje"),
|
||||
("Add ID", "Lägg till ID"),
|
||||
("Add Tag", "Lägg till Tagg"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", ""),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", ""),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", ""),
|
||||
("Tags", ""),
|
||||
("Search ID", ""),
|
||||
("Current Wayland display server is not supported", ""),
|
||||
("whitelist_sep", ""),
|
||||
("Add ID", ""),
|
||||
("Add Tag", ""),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"),
|
||||
("Privacy Statement", "คำแถลงเกี่ยวกับความเป็นส่วนตัว"),
|
||||
("Mute", "ปิดเสียง"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "ออดิโออินพุท"),
|
||||
("Enhancements", "การปรับปรุง"),
|
||||
("Hardware Codec", "ฮาร์ดแวร์ codec"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "ออกจากระบบ"),
|
||||
("Tags", "แท็ก"),
|
||||
("Search ID", "ค้นหา ID"),
|
||||
("Current Wayland display server is not supported", "เซิร์ฟเวอร์การแสดงผล Wayland ปัจจุบันไม่รองรับ"),
|
||||
("whitelist_sep", "คั่นโดยเครื่องหมาย comma semicolon เว้นวรรค หรือ ขึ้นบรรทัดใหม่"),
|
||||
("Add ID", "เพิ่ม ID"),
|
||||
("Add Tag", "เพิ่มแท็ก"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Sustur"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Ses Girişi"),
|
||||
("Enhancements", "Geliştirmeler"),
|
||||
("Hardware Codec", "Donanımsal Codec"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Çıkış yap"),
|
||||
("Tags", "Etiketler"),
|
||||
("Search ID", "ID Arama"),
|
||||
("Current Wayland display server is not supported", "Mevcut Wayland görüntüleme sunucusu desteklenmiyor"),
|
||||
("whitelist_sep", "Virgül, noktalı virgül, boşluk veya yeni satır ile ayrılmış"),
|
||||
("Add ID", "ID Ekle"),
|
||||
("Add Tag", "Etiket Ekle"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "靜音"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "音訊輸入"),
|
||||
("Enhancements", "增強功能"),
|
||||
("Hardware Codec", "硬件編解碼"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "登出"),
|
||||
("Tags", "標籤"),
|
||||
("Search ID", "搜尋 ID"),
|
||||
("Current Wayland display server is not supported", "目前不支援 Wayland 顯示伺服器"),
|
||||
("whitelist_sep", "使用逗號、分號、空白,或是換行來分隔"),
|
||||
("Add ID", "新增 ID"),
|
||||
("Add Tag", "新增標籤"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", "正常關閉"),
|
||||
("Display", "顯示"),
|
||||
("Default View Style", "默認顯示方式"),
|
||||
("Default Scroll Style", "默認滾動方式"),
|
||||
("Default Image Quality", "默認圖像質量"),
|
||||
("Default Codec", "默認編解碼"),
|
||||
("Bitrate", "波特率"),
|
||||
("FPS", "幀率"),
|
||||
("Auto", "自動"),
|
||||
("Other Default Options", "其它默認選項"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "Створено з душею в цьому хаотичному світі!"),
|
||||
("Privacy Statement", "Декларація про конфіденційність"),
|
||||
("Mute", "Вимкнути звук"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Аудіовхід"),
|
||||
("Enhancements", "Покращення"),
|
||||
("Hardware Codec", "Апаратний кодек"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Вийти"),
|
||||
("Tags", "Ключові слова"),
|
||||
("Search ID", "Пошук за ID"),
|
||||
("Current Wayland display server is not supported", "Поточний графічний сервер Wayland не підтримується"),
|
||||
("whitelist_sep", "Розділені комою, крапкою з комою, пробілом або новим рядком"),
|
||||
("Add ID", "Додати ID"),
|
||||
("Add Tag", "Додати ключове слово"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Tắt tiếng"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Đầu vào âm thanh"),
|
||||
("Enhancements", "Các tiện itchs"),
|
||||
("Hardware Codec", "Codec phần cứng"),
|
||||
@ -218,7 +221,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Đăng xuất"),
|
||||
("Tags", "Tags"),
|
||||
("Search ID", "Tìm ID"),
|
||||
("Current Wayland display server is not supported", "Máy chủ hình ảnh Wayland hiện không đuợc hỗ trợ"),
|
||||
("whitelist_sep", "Đuợc cách nhau bởi dấu phẩy, dấu chấm phẩy, dấu cách hay dòng mới"),
|
||||
("Add ID", "Thêm ID"),
|
||||
("Add Tag", "Thêm Tag"),
|
||||
@ -434,5 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Closed as expected", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -426,104 +426,11 @@ pub fn is_login_wayland() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fix_login_wayland() {
|
||||
let mut file = "/etc/gdm3/custom.conf".to_owned();
|
||||
if !std::path::Path::new(&file).exists() {
|
||||
file = "/etc/gdm/custom.conf".to_owned();
|
||||
}
|
||||
match std::process::Command::new("pkexec")
|
||||
.args(vec![
|
||||
"sed",
|
||||
"-i",
|
||||
"s/#WaylandEnable=false/WaylandEnable=false/g",
|
||||
&file,
|
||||
])
|
||||
.output()
|
||||
{
|
||||
Ok(x) => {
|
||||
let x = String::from_utf8_lossy(&x.stderr);
|
||||
if !x.is_empty() {
|
||||
log::error!("fix_login_wayland failed: {}", x);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("fix_login_wayland failed: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_is_wayland() -> bool {
|
||||
let dtype = get_display_server();
|
||||
return "wayland" == dtype && unsafe { UNMODIFIED };
|
||||
}
|
||||
|
||||
pub fn modify_default_login() -> String {
|
||||
let dsession = std::env::var("DESKTOP_SESSION").unwrap();
|
||||
let user_name = std::env::var("USERNAME").unwrap();
|
||||
if let Ok(x) = run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION}-xorg.desktop".to_owned()) {
|
||||
if x.trim_end().to_string() != "" {
|
||||
match std::process::Command::new("pkexec")
|
||||
.args(vec![
|
||||
"sed",
|
||||
"-i",
|
||||
&format!("s/={0}$/={0}-xorg/g", &dsession),
|
||||
&format!("/var/lib/AccountsService/users/{}", &user_name),
|
||||
])
|
||||
.output()
|
||||
{
|
||||
Ok(x) => {
|
||||
let x = String::from_utf8_lossy(&x.stderr);
|
||||
if !x.is_empty() {
|
||||
log::error!("modify_default_login failed: {}", x);
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
} else {
|
||||
unsafe {
|
||||
UNMODIFIED = false;
|
||||
}
|
||||
return "".to_owned();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("modify_default_login failed: {}", err);
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
}
|
||||
}
|
||||
} else if let Ok(z) =
|
||||
run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION:0:-8}.desktop".to_owned())
|
||||
{
|
||||
if z.trim_end().to_string() != "" {
|
||||
match std::process::Command::new("pkexec")
|
||||
.args(vec![
|
||||
"sed",
|
||||
"-i",
|
||||
&format!("s/={}$/={}/g", &dsession, &dsession[..dsession.len() - 8]),
|
||||
&format!("/var/lib/AccountsService/users/{}", &user_name),
|
||||
])
|
||||
.output()
|
||||
{
|
||||
Ok(x) => {
|
||||
let x = String::from_utf8_lossy(&x.stderr);
|
||||
if !x.is_empty() {
|
||||
log::error!("modify_default_login failed: {}", x);
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
} else {
|
||||
unsafe {
|
||||
UNMODIFIED = false;
|
||||
}
|
||||
return "".to_owned();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("modify_default_login failed: {}", err);
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
}
|
||||
|
||||
// to-do: test the other display manager
|
||||
fn _get_display_manager() -> String {
|
||||
if let Ok(x) = std::fs::read_to_string("/etc/X11/default-display-manager") {
|
||||
|
@ -49,6 +49,7 @@ use winreg::RegKey;
|
||||
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
let mut out = mem::MaybeUninit::uninit().assume_init();
|
||||
if GetCursorPos(&mut out) == FALSE {
|
||||
return None;
|
||||
@ -61,6 +62,7 @@ pub fn reset_input_cache() {}
|
||||
|
||||
pub fn get_cursor() -> ResultType<Option<u64>> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init();
|
||||
ci.cbSize = std::mem::size_of::<CURSORINFO>() as _;
|
||||
if crate::portable_service::client::get_cursor_info(&mut ci) == FALSE {
|
||||
@ -79,6 +81,7 @@ struct IconInfo(ICONINFO);
|
||||
impl IconInfo {
|
||||
fn new(icon: HICON) -> ResultType<Self> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
let mut ii = mem::MaybeUninit::uninit().assume_init();
|
||||
if GetIconInfo(icon, &mut ii) == FALSE {
|
||||
Err(io::Error::last_os_error().into())
|
||||
@ -1742,3 +1745,13 @@ pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) ->
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> {
|
||||
std::process::Command::new("icacls")
|
||||
.arg(dir.as_os_str())
|
||||
.arg("/grant")
|
||||
.arg(format!("Everyone:(OI)(CI){}", permission))
|
||||
.arg("/T")
|
||||
.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
use crate::ipc::Data;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex, RwLock, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
pub use connection::*;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::config::Config2;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::{anyhow, Context},
|
||||
@ -12,19 +17,18 @@ use hbb_common::{
|
||||
message_proto::*,
|
||||
protobuf::{Enum, Message as _},
|
||||
rendezvous_proto::*,
|
||||
ResultType,
|
||||
socket_client,
|
||||
sodiumoxide::crypto::{box_, secretbox, sign},
|
||||
timeout, tokio, ResultType, Stream,
|
||||
sodiumoxide::crypto::{box_, secretbox, sign}, Stream, timeout, tokio,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use service::ServiceTmpl;
|
||||
use hbb_common::config::Config2;
|
||||
use hbb_common::tcp::new_listener;
|
||||
use service::{GenericService, Service, Subscriber};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex, RwLock, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use service::ServiceTmpl;
|
||||
|
||||
use crate::ipc::{connect, Data};
|
||||
|
||||
pub mod audio_service;
|
||||
cfg_if::cfg_if! {
|
||||
@ -55,8 +59,6 @@ mod service;
|
||||
mod video_qos;
|
||||
pub mod video_service;
|
||||
|
||||
use hbb_common::tcp::new_listener;
|
||||
|
||||
pub type Childs = Arc<Mutex<Vec<std::process::Child>>>;
|
||||
type ConnMap = HashMap<i32, ConnInner>;
|
||||
|
||||
@ -425,6 +427,50 @@ pub async fn start_server(is_server: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_ipc_url_server() {
|
||||
log::debug!("Start an ipc server for listening to url schemes");
|
||||
match crate::ipc::new_listener("_url").await {
|
||||
Ok(mut incoming) => {
|
||||
while let Some(Ok(conn)) = incoming.next().await {
|
||||
let mut conn = crate::ipc::Connection::new(conn);
|
||||
match conn.next_timeout(1000).await {
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::UrlLink(url) => {
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(
|
||||
crate::flutter::APP_TYPE_MAIN
|
||||
) {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", "on_url_scheme_received");
|
||||
m.insert("url", url.as_str());
|
||||
stream.add(serde_json::to_string(&m).unwrap());
|
||||
} else {
|
||||
log::warn!("No main window app found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::warn!("An unexpected data was sent to the ipc url server.")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("{}", err);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn sync_and_watch_config_dir() {
|
||||
if crate::platform::is_root() {
|
||||
|
@ -3,6 +3,8 @@ use super::{input_service::*, *};
|
||||
use crate::clipboard_file::*;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::update_clipboard;
|
||||
#[cfg(windows)]
|
||||
use crate::portable_service::client as portable_client;
|
||||
use crate::video_service;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
||||
@ -101,8 +103,8 @@ pub struct Connection {
|
||||
lr: LoginRequest,
|
||||
last_recv_time: Arc<Mutex<Instant>>,
|
||||
chat_unanswered: bool,
|
||||
#[allow(unused)]
|
||||
elevation_requested: bool,
|
||||
#[cfg(windows)]
|
||||
portable: PortableState,
|
||||
from_switch: bool,
|
||||
}
|
||||
|
||||
@ -136,7 +138,6 @@ const MILLI1: Duration = Duration::from_millis(1);
|
||||
const SEND_TIMEOUT_VIDEO: u64 = 12_000;
|
||||
const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10;
|
||||
const SESSION_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const SWITCH_SIDES_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
impl Connection {
|
||||
pub async fn start(
|
||||
@ -199,7 +200,8 @@ impl Connection {
|
||||
lr: Default::default(),
|
||||
last_recv_time: Arc::new(Mutex::new(Instant::now())),
|
||||
chat_unanswered: false,
|
||||
elevation_requested: false,
|
||||
#[cfg(windows)]
|
||||
portable: Default::default(),
|
||||
from_switch: false,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@ -247,14 +249,6 @@ impl Connection {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
std::thread::spawn(move || Self::handle_input(rx_input, tx_cloned));
|
||||
let mut second_timer = time::interval(Duration::from_secs(1));
|
||||
#[cfg(windows)]
|
||||
let mut last_uac = false;
|
||||
#[cfg(windows)]
|
||||
let mut last_foreground_window_elevated = false;
|
||||
#[cfg(windows)]
|
||||
let mut last_portable_service_running = false;
|
||||
#[cfg(windows)]
|
||||
let is_installed = crate::platform::is_installed();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@ -362,8 +356,7 @@ impl Connection {
|
||||
}
|
||||
#[cfg(windows)]
|
||||
ipc::Data::DataPortableService(ipc::DataPortableService::RequestStart) => {
|
||||
use crate::portable_service::client;
|
||||
if let Err(e) = client::start_portable_service(client::StartPara::Direct) {
|
||||
if let Err(e) = portable_client::start_portable_service(portable_client::StartPara::Direct) {
|
||||
log::error!("Failed to start portable service from cm:{:?}", e);
|
||||
}
|
||||
}
|
||||
@ -458,46 +451,7 @@ impl Connection {
|
||||
},
|
||||
_ = second_timer.tick() => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if !is_installed && conn.file_transfer.is_none() && conn.port_forward_socket.is_none(){
|
||||
let portable_service_running = crate::portable_service::client::running();
|
||||
if portable_service_running != last_portable_service_running {
|
||||
last_portable_service_running = portable_service_running;
|
||||
if portable_service_running && conn.elevation_requested {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_portable_service_running(portable_service_running);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
conn.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone();
|
||||
if last_uac != uac {
|
||||
last_uac = uac;
|
||||
if !uac || !portable_service_running{
|
||||
let mut misc = Misc::new();
|
||||
misc.set_uac(uac);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
conn.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap().clone();
|
||||
if last_foreground_window_elevated != foreground_window_elevated {
|
||||
last_foreground_window_elevated = foreground_window_elevated;
|
||||
if !foreground_window_elevated || !portable_service_running {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_foreground_window_elevated(foreground_window_elevated);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
conn.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
let show_elevation = !portable_service_running;
|
||||
conn.send_to_cm(ipc::Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show_elevation)));
|
||||
|
||||
}
|
||||
}
|
||||
conn.portable_check();
|
||||
}
|
||||
_ = test_delay_timer.tick() => {
|
||||
if last_recv_time.elapsed() >= SEC30 {
|
||||
@ -1276,7 +1230,7 @@ impl Connection {
|
||||
SWITCH_SIDES_UUID
|
||||
.lock()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.0.elapsed() < SWITCH_SIDES_TIMEOUT);
|
||||
.retain(|_, v| v.0.elapsed() < Duration::from_secs(10));
|
||||
let uuid_old = SWITCH_SIDES_UUID.lock().unwrap().remove(&lr.my_id);
|
||||
if let Ok(uuid) = uuid::Uuid::from_slice(_s.uuid.to_vec().as_ref()) {
|
||||
if let Some((_instant, uuid_old)) = uuid_old {
|
||||
@ -1537,15 +1491,14 @@ impl Connection {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut err = "No need to elevate".to_string();
|
||||
if !crate::platform::is_installed()
|
||||
&& !crate::portable_service::client::running()
|
||||
{
|
||||
use crate::portable_service::client;
|
||||
err = client::start_portable_service(client::StartPara::Direct)
|
||||
.err()
|
||||
.map_or("".to_string(), |e| e.to_string());
|
||||
if !crate::platform::is_installed() && !portable_client::running() {
|
||||
err = portable_client::start_portable_service(
|
||||
portable_client::StartPara::Direct,
|
||||
)
|
||||
.err()
|
||||
.map_or("".to_string(), |e| e.to_string());
|
||||
}
|
||||
self.elevation_requested = err.is_empty();
|
||||
self.portable.elevation_requested = err.is_empty();
|
||||
let mut misc = Misc::new();
|
||||
misc.set_elevation_response(err);
|
||||
let mut msg = Message::new();
|
||||
@ -1557,18 +1510,14 @@ impl Connection {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut err = "No need to elevate".to_string();
|
||||
if !crate::platform::is_installed()
|
||||
&& !crate::portable_service::client::running()
|
||||
{
|
||||
use crate::portable_service::client;
|
||||
err = client::start_portable_service(client::StartPara::Logon(
|
||||
_r.username,
|
||||
_r.password,
|
||||
))
|
||||
if !crate::platform::is_installed() && !portable_client::running() {
|
||||
err = portable_client::start_portable_service(
|
||||
portable_client::StartPara::Logon(_r.username, _r.password),
|
||||
)
|
||||
.err()
|
||||
.map_or("".to_string(), |e| e.to_string());
|
||||
}
|
||||
self.elevation_requested = err.is_empty();
|
||||
self.portable.elevation_requested = err.is_empty();
|
||||
let mut misc = Misc::new();
|
||||
misc.set_elevation_response(err);
|
||||
let mut msg = Message::new();
|
||||
@ -1588,8 +1537,8 @@ impl Connection {
|
||||
uuid.to_string().as_ref(),
|
||||
])
|
||||
.ok();
|
||||
self.send_close_reason_no_retry("Closed as expected");
|
||||
self.on_close("switch sides", false);
|
||||
self.send_close_reason_no_retry("Closed as expected").await;
|
||||
self.on_close("switch sides", false).await;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1810,6 +1759,59 @@ impl Connection {
|
||||
pub fn alive_conns() -> Vec<i32> {
|
||||
ALIVE_CONNS.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn portable_check(&mut self) {
|
||||
if self.portable.is_installed
|
||||
|| self.file_transfer.is_some()
|
||||
|| self.port_forward_socket.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let running = portable_client::running();
|
||||
let show_elevation = !running;
|
||||
self.send_to_cm(ipc::Data::DataPortableService(
|
||||
ipc::DataPortableService::CmShowElevation(show_elevation),
|
||||
));
|
||||
if self.authorized {
|
||||
let p = &mut self.portable;
|
||||
if running != p.last_running {
|
||||
p.last_running = running;
|
||||
if running && p.elevation_requested {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_portable_service_running(running);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone();
|
||||
if p.last_uac != uac {
|
||||
p.last_uac = uac;
|
||||
if !uac || !running {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_uac(uac);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone();
|
||||
if p.last_foreground_window_elevated != foreground_window_elevated {
|
||||
p.last_foreground_window_elevated = foreground_window_elevated;
|
||||
if !foreground_window_elevated || !running {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_foreground_window_elevated(foreground_window_elevated);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
@ -1984,3 +1986,25 @@ pub enum FileAuditType {
|
||||
RemoteSend = 0,
|
||||
RemoteReceive = 1,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub struct PortableState {
|
||||
pub last_uac: bool,
|
||||
pub last_foreground_window_elevated: bool,
|
||||
pub last_running: bool,
|
||||
pub is_installed: bool,
|
||||
pub elevation_requested: bool,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Default for PortableState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_installed: crate::platform::is_installed(),
|
||||
last_uac: Default::default(),
|
||||
last_foreground_window_elevated: Default::default(),
|
||||
last_running: Default::default(),
|
||||
elevation_requested: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,7 @@ use core::slice;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::anyhow,
|
||||
bail,
|
||||
config::Config,
|
||||
log,
|
||||
bail, log,
|
||||
message_proto::{KeyEvent, MouseEvent},
|
||||
protobuf::Message,
|
||||
tokio::{self, sync::mpsc},
|
||||
@ -15,6 +13,7 @@ use shared_memory::*;
|
||||
use std::{
|
||||
mem::size_of,
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
@ -25,6 +24,7 @@ use winapi::{
|
||||
|
||||
use crate::{
|
||||
ipc::{self, new_listener, Connection, Data, DataPortableService},
|
||||
platform::set_path_permission,
|
||||
video_service::get_current_display,
|
||||
};
|
||||
|
||||
@ -72,7 +72,7 @@ impl DerefMut for SharedMemory {
|
||||
|
||||
impl SharedMemory {
|
||||
pub fn create(name: &str, size: usize) -> ResultType<Self> {
|
||||
let flink = Self::flink(name.to_string());
|
||||
let flink = Self::flink(name.to_string())?;
|
||||
let shmem = match ShmemConf::new()
|
||||
.size(size)
|
||||
.flink(&flink)
|
||||
@ -91,12 +91,12 @@ impl SharedMemory {
|
||||
}
|
||||
};
|
||||
log::info!("Create shared memory, size:{}, flink:{}", size, flink);
|
||||
Self::set_all_perm(&flink);
|
||||
set_path_permission(&PathBuf::from(flink), "F").ok();
|
||||
Ok(SharedMemory { inner: shmem })
|
||||
}
|
||||
|
||||
pub fn open_existing(name: &str) -> ResultType<Self> {
|
||||
let flink = Self::flink(name.to_string());
|
||||
let flink = Self::flink(name.to_string())?;
|
||||
let shmem = match ShmemConf::new().flink(&flink).allow_raw(true).open() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
@ -116,30 +116,29 @@ impl SharedMemory {
|
||||
}
|
||||
}
|
||||
|
||||
fn flink(name: String) -> String {
|
||||
let mut shmem_flink = format!("shared_memory{}", name);
|
||||
if cfg!(windows) {
|
||||
let df = "C:\\ProgramData";
|
||||
let df = if std::path::Path::new(df).exists() {
|
||||
df.to_owned()
|
||||
} else {
|
||||
std::env::var("TEMP").unwrap_or("C:\\Windows\\TEMP".to_owned())
|
||||
};
|
||||
let df = format!("{}\\{}", df, *hbb_common::config::APP_NAME.read().unwrap());
|
||||
std::fs::create_dir(&df).ok();
|
||||
shmem_flink = format!("{}\\{}", df, shmem_flink);
|
||||
fn flink(name: String) -> ResultType<String> {
|
||||
let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string());
|
||||
let mut dir = PathBuf::from(disk);
|
||||
let dir1 = dir.join("ProgramData");
|
||||
let dir2 = std::env::var("TEMP")
|
||||
.map(|d| PathBuf::from(d))
|
||||
.unwrap_or(dir.join("Windows").join("Temp"));
|
||||
if dir1.exists() {
|
||||
dir = dir1;
|
||||
} else if dir2.exists() {
|
||||
dir = dir2;
|
||||
} else {
|
||||
shmem_flink = Config::ipc_path("").replace("ipc", "") + &shmem_flink;
|
||||
bail!("no vaild flink directory");
|
||||
}
|
||||
return shmem_flink;
|
||||
}
|
||||
|
||||
fn set_all_perm(_p: &str) {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(_p, std::fs::Permissions::from_mode(0o0777)).ok();
|
||||
dir = dir.join(hbb_common::config::APP_NAME.read().unwrap().clone());
|
||||
if !dir.exists() {
|
||||
std::fs::create_dir(&dir)?;
|
||||
set_path_permission(&dir, "F").ok();
|
||||
}
|
||||
Ok(dir
|
||||
.join(format!("shared_memory{}", name))
|
||||
.to_string_lossy()
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,7 +450,6 @@ pub mod server {
|
||||
// functions called in main process.
|
||||
pub mod client {
|
||||
use hbb_common::anyhow::Context;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -459,6 +457,7 @@ pub mod client {
|
||||
static ref RUNNING: Arc<Mutex<bool>> = Default::default();
|
||||
static ref SHMEM: Arc<Mutex<Option<SharedMemory>>> = Default::default();
|
||||
static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(client::start_ipc_server());
|
||||
static ref QUICK_SUPPORT: Arc<Mutex<bool>> = Default::default();
|
||||
}
|
||||
|
||||
pub enum StartPara {
|
||||
@ -514,7 +513,7 @@ pub mod client {
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
if let Some(dir) = PathBuf::from(&exe).parent() {
|
||||
if !set_dir_permission(&PathBuf::from(dir)) {
|
||||
if set_path_permission(&PathBuf::from(dir), "RX").is_err() {
|
||||
*SHMEM.lock().unwrap() = None;
|
||||
bail!("Failed to set permission of {:?}", dir);
|
||||
}
|
||||
@ -532,7 +531,7 @@ pub mod client {
|
||||
let dst = dir.join("rustdesk.exe");
|
||||
if std::fs::copy(&exe, &dst).is_ok() {
|
||||
if dst.exists() {
|
||||
if set_dir_permission(&dir) {
|
||||
if set_path_permission(&dir, "RX").is_ok() {
|
||||
exe = dst.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
@ -561,16 +560,10 @@ pub mod client {
|
||||
*SHMEM.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
fn set_dir_permission(dir: &PathBuf) -> bool {
|
||||
// // give Everyone RX permission
|
||||
std::process::Command::new("icacls")
|
||||
.arg(dir.as_os_str())
|
||||
.arg("/grant")
|
||||
.arg("Everyone:(OI)(CI)RX")
|
||||
.arg("/T")
|
||||
.spawn()
|
||||
.is_ok()
|
||||
pub fn set_quick_support(v: bool) {
|
||||
*QUICK_SUPPORT.lock().unwrap() = v;
|
||||
}
|
||||
|
||||
pub struct CapturerPortable;
|
||||
|
||||
impl CapturerPortable {
|
||||
@ -685,17 +678,7 @@ pub mod client {
|
||||
use DataPortableService::*;
|
||||
let rx = Arc::new(tokio::sync::Mutex::new(rx));
|
||||
let postfix = IPC_SUFFIX;
|
||||
#[cfg(feature = "flutter")]
|
||||
let quick_support = {
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
args.contains(&"--quick_support".to_string())
|
||||
};
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
let quick_support = std::env::current_exe()
|
||||
.unwrap_or("".into())
|
||||
.to_string_lossy()
|
||||
.to_lowercase()
|
||||
.ends_with("qs.exe");
|
||||
let quick_support = QUICK_SUPPORT.lock().unwrap().clone();
|
||||
|
||||
match new_listener(postfix).await {
|
||||
Ok(mut incoming) => loop {
|
||||
|
@ -498,7 +498,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
video_qos.target_bitrate,
|
||||
video_qos.fps
|
||||
);
|
||||
encoder.set_bitrate(video_qos.target_bitrate).unwrap();
|
||||
allow_err!(encoder.set_bitrate(video_qos.target_bitrate));
|
||||
spf = video_qos.spf();
|
||||
}
|
||||
drop(video_qos);
|
||||
@ -956,15 +956,17 @@ fn start_uac_elevation_check() {
|
||||
START.call_once(|| {
|
||||
if !crate::platform::is_installed()
|
||||
&& !crate::platform::is_root()
|
||||
&& !crate::platform::is_elevated(None).map_or(false, |b| b)
|
||||
&& !crate::portable_service::client::running()
|
||||
{
|
||||
std::thread::spawn(|| loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(uac) = crate::ui::win_privacy::is_process_consent_running() {
|
||||
*IS_UAC_RUNNING.lock().unwrap() = uac;
|
||||
}
|
||||
if let Ok(elevated) = crate::platform::is_foreground_window_elevated() {
|
||||
*IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap() = elevated;
|
||||
if !crate::platform::is_elevated(None).unwrap_or(false) {
|
||||
if let Ok(elevated) = crate::platform::is_foreground_window_elevated() {
|
||||
*IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap() = elevated;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use hbb_common::{allow_err, platform::linux::DISTRO};
|
||||
use scrap::{set_map_err, Capturer, Display, Frame, TraitCapturer};
|
||||
use scrap::{is_cursor_embedded, set_map_err, Capturer, Display, Frame, TraitCapturer};
|
||||
use std::io;
|
||||
|
||||
use super::video_service::{
|
||||
@ -12,7 +12,7 @@ lazy_static::lazy_static! {
|
||||
static ref LOG_SCRAP_COUNT: Mutex<u32> = Mutex::new(0);
|
||||
}
|
||||
|
||||
pub fn set_wayland_scrap_map_err() {
|
||||
pub fn init() {
|
||||
set_map_err(map_err_scrap);
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ pub(super) async fn check_init() -> ResultType<()> {
|
||||
let num = all.len();
|
||||
let (primary, mut displays) = super::video_service::get_displays_2(&all);
|
||||
for display in displays.iter_mut() {
|
||||
display.cursor_embedded = true;
|
||||
display.cursor_embedded = is_cursor_embedded();
|
||||
}
|
||||
|
||||
let mut rects: Vec<((i32, i32), usize, usize)> = Vec::new();
|
||||
|
12
src/ui.rs
12
src/ui.rs
@ -21,7 +21,7 @@ mod cm;
|
||||
#[cfg(feature = "inline")]
|
||||
pub mod inline;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
pub mod macos;
|
||||
pub mod remote;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod win_privacy;
|
||||
@ -434,18 +434,10 @@ impl UI {
|
||||
is_login_wayland()
|
||||
}
|
||||
|
||||
fn fix_login_wayland(&mut self) {
|
||||
fix_login_wayland()
|
||||
}
|
||||
|
||||
fn current_is_wayland(&mut self) -> bool {
|
||||
current_is_wayland()
|
||||
}
|
||||
|
||||
fn modify_default_login(&mut self) -> String {
|
||||
modify_default_login()
|
||||
}
|
||||
|
||||
fn get_software_update_url(&self) -> String {
|
||||
get_software_update_url()
|
||||
}
|
||||
@ -590,9 +582,7 @@ impl sciter::EventHandler for UI {
|
||||
fn is_installed_daemon(bool);
|
||||
fn get_error();
|
||||
fn is_login_wayland();
|
||||
fn fix_login_wayland();
|
||||
fn current_is_wayland();
|
||||
fn modify_default_login();
|
||||
fn get_options();
|
||||
fn get_option(String);
|
||||
fn get_local_option(String);
|
||||
|
@ -755,11 +755,6 @@ class FixWayland: Reactor.Component {
|
||||
</div>;
|
||||
}
|
||||
|
||||
event click $(#fix-wayland) {
|
||||
handler.fix_login_wayland();
|
||||
app.update();
|
||||
}
|
||||
|
||||
event click $(#help-me) {
|
||||
handler.open_url(translate("doc_fix_wayland"));
|
||||
}
|
||||
@ -769,19 +764,11 @@ class ModifyDefaultLogin: Reactor.Component {
|
||||
function render() {
|
||||
return <div .trust-me>
|
||||
<div>{translate('Warning')}</div>
|
||||
<div>{translate('Current Wayland display server is not supported')}</div>
|
||||
<div>{translate('wayland_experiment_tip')}</div>
|
||||
<div #help-me .link>{translate('Help')}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
event click $(#modify-default-login) {
|
||||
if (var r = handler.modify_default_login()) {
|
||||
// without handler, will fail, fucking stupid sciter
|
||||
handler.msgbox("custom-error", "Error", r);
|
||||
}
|
||||
app.update();
|
||||
}
|
||||
|
||||
event click $(#help-me) {
|
||||
handler.open_url(translate("doc_fix_wayland"));
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::{ffi::c_void, rc::Rc};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy::*, NSMenu, NSMenuItem},
|
||||
@ -8,11 +10,16 @@ use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
runtime::{Object, Sel, BOOL},
|
||||
runtime::{BOOL, Object, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use sciter::{make_args, Host};
|
||||
use std::{ffi::c_void, rc::Rc};
|
||||
use objc::runtime::Class;
|
||||
use objc_id::WeakId;
|
||||
use sciter::{Host, make_args};
|
||||
|
||||
use hbb_common::{log, tokio};
|
||||
|
||||
use crate::ui_cm_interface::start_ipc;
|
||||
|
||||
static APP_HANDLER_IVAR: &str = "GoDeskAppHandler";
|
||||
|
||||
@ -98,12 +105,21 @@ unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
|
||||
sel!(handleMenuItem:),
|
||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(sel!(handleEvent:withReplyEvent:), handle_apple_event as extern fn(&Object, Sel, u64, u64));
|
||||
let decl = decl.register();
|
||||
let delegate: id = msg_send![decl, alloc];
|
||||
let () = msg_send![delegate, init];
|
||||
let state = DelegateState { handler };
|
||||
let handler_ptr = Box::into_raw(Box::new(state));
|
||||
(*delegate).set_ivar(APP_HANDLER_IVAR, handler_ptr as *mut c_void);
|
||||
// Set the url scheme handler
|
||||
let cls = Class::get("NSAppleEventManager").unwrap();
|
||||
let manager: *mut Object = msg_send![cls, sharedAppleEventManager];
|
||||
let _: () = msg_send![manager,
|
||||
setEventHandler: delegate
|
||||
andSelector: sel!(handleEvent:withReplyEvent:)
|
||||
forEventClass: fruitbasket::kInternetEventClass
|
||||
andEventID: fruitbasket::kAEGetURL];
|
||||
let () = msg_send![NSApp(), setDelegate: delegate];
|
||||
}
|
||||
|
||||
@ -167,6 +183,24 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The function to handle the url scheme sent by the system.
|
||||
///
|
||||
/// 1. Try to send the url scheme from ipc.
|
||||
/// 2. If failed to send the url scheme, we open a new main window to handle this url scheme.
|
||||
pub fn handle_url_scheme(url: String) {
|
||||
if let Err(err) = crate::ipc::send_url_scheme(url.clone()) {
|
||||
log::debug!("Send the url to the existing flutter process failed, {}. Let's open a new program to handle this.", err);
|
||||
let _ = crate::run_me(vec![url]);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn handle_apple_event(_this: &Object, _cmd: Sel, event: u64, _reply: u64) {
|
||||
let event = event as *mut Object;
|
||||
let url = fruitbasket::parse_url_event(event);
|
||||
log::debug!("an event was received: {}", url);
|
||||
std::thread::spawn(move || handle_url_scheme(url));
|
||||
}
|
||||
|
||||
unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object {
|
||||
let title = NSString::alloc(nil).init_str(title);
|
||||
let action = sel!(handleMenuItem:);
|
||||
|
@ -16,7 +16,7 @@ div#quality-monitor {
|
||||
padding: 5px;
|
||||
min-width: 150px;
|
||||
color: azure;
|
||||
border: solid azure;
|
||||
border: 0.5px solid azure;
|
||||
}
|
||||
|
||||
video#handler {
|
||||
|
@ -789,9 +789,7 @@ fn cm_inner_send(id: i32, data: Data) {
|
||||
|
||||
pub fn can_elevate() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
return !crate::platform::is_installed() && !crate::portable_service::client::running();
|
||||
}
|
||||
return !crate::platform::is_installed();
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
}
|
||||
|
@ -614,12 +614,6 @@ pub fn is_login_wayland() -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fix_login_wayland() {
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::platform::linux::fix_login_wayland();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_is_wayland() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
@ -628,14 +622,6 @@ pub fn current_is_wayland() -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn modify_default_login() -> String {
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::modify_default_login();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_software_update_url() -> String {
|
||||
SOFTWARE_UPDATE_URL.lock().unwrap().clone()
|
||||
@ -931,6 +917,18 @@ pub fn account_auth_result() -> String {
|
||||
serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
pub fn set_user_default_option(key: String, value: String) {
|
||||
use hbb_common::config::UserDefaultConfig;
|
||||
UserDefaultConfig::load().set(key, value);
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
pub fn get_user_default_option(key: String) -> String {
|
||||
use hbb_common::config::UserDefaultConfig;
|
||||
UserDefaultConfig::load().get(&key)
|
||||
}
|
||||
|
||||
// notice: avoiding create ipc connection repeatedly,
|
||||
// because windows named pipe has serious memory leak issue.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
|
@ -6,7 +6,6 @@ use crate::client::{
|
||||
};
|
||||
use crate::common::{self, GrabState};
|
||||
use crate::keyboard;
|
||||
use crate::ui_interface::using_public_server;
|
||||
use crate::{client::Data, client::Interface};
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
|
Loading…
Reference in New Issue
Block a user