Merge branch 'master' into dev

This commit is contained in:
xxrl 2022-11-20 13:27:39 +08:00
commit edfb22d9c2
107 changed files with 5470 additions and 1631 deletions

View File

@ -38,6 +38,14 @@ jobs:
with:
channel: 'stable'
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Replace engine with rustdesk custom flutter engine
run: |
flutter doctor -v
flutter precache --windows
Invoke-WebRequest -Uri https://github.com/Kingtous/engine/releases/download/v3.0.5-rustdesk/windows-x64-release-flutter.zip -OutFile windows-x64-release-flutter.zip
Expand-Archive windows-x64-release-flutter.zip -DestinationPath engine
mv -Force engine/* C:/hostedtoolcache/windows/flutter/stable-3.0.5-x64/bin/cache/artifacts/engine/windows-x64-release/
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@ -112,6 +120,13 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v3
- name: Get build target triple
uses: jungwinter/split@v2
id: build-target-triple
with:
separator: '-'
msg: ${{ matrix.job.target }}
- name: Install prerequisites
run: |
case ${{ matrix.job.target }} in
@ -242,19 +257,25 @@ jobs:
files: |
res/rustdesk*.zst
# - name: build RPM package
# id: rpm
# uses: Kingtous/rustdesk-rpmbuild@master
# with:
# spec_file: "res/rpm-flutter.spec"
- name: Make RPM package
shell: bash
if: ${{ matrix.job.extra-build-args == '' }}
run: |
sudo apt install -y rpm
HBB=`pwd` rpmbuild ./res/rpm-flutter.spec -bb
pushd ~/rpmbuild/RPMS/${{ steps.build-target-triple.outputs._0 }}
for name in rustdesk*??.rpm; do
mv "$name" "${name%%.rpm}-fedora28-centos8.rpm"
done
# - name: Publish fedora28/centos8 package
# uses: softprops/action-gh-release@v1
# with:
# prerelease: true
# tag_name: ${{ env.TAG_NAME }}
# files: |
# ${{ steps.rpm.outputs.rpm_dir_path }}/*
- name: Publish fedora28/centos8 package
if: ${{ matrix.job.extra-build-args == '' }}
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
/home/runner/rpmbuild/RPMS/${{ steps.build-target-triple.outputs._0 }}/*.rpm
build-flatpak:
name: Build Flatpak

99
Cargo.lock generated
View File

@ -620,9 +620,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.22"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
@ -1253,7 +1253,7 @@ dependencies = [
"libc",
"memalloc",
"system-configuration",
"windows",
"windows 0.30.0",
]
[[package]]
@ -1392,6 +1392,18 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "dylib_virtual_display"
version = "0.1.0"
dependencies = [
"cc",
"hbb_common",
"lazy_static",
"serde 1.0.144",
"serde_derive",
"thiserror",
]
[[package]]
name = "ed25519"
version = "1.5.2"
@ -2463,7 +2475,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hwcodec"
version = "0.1.0"
source = "git+https://github.com/21pages/hwcodec#bf73e8e650abca3e004e96a245086b3647b9d84a"
source = "git+https://github.com/21pages/hwcodec#f54d69b35251ade110373403ddefcb8b49c87305"
dependencies = [
"bindgen",
"cc",
@ -4362,6 +4374,7 @@ dependencies = [
"bytes",
"cc",
"cfg-if 1.0.0",
"chrono",
"clap 3.2.17",
"clipboard",
"cocoa",
@ -4413,6 +4426,8 @@ dependencies = [
"serde_derive",
"serde_json 1.0.85",
"sha2",
"shared_memory",
"shutdown_hooks",
"simple_rc",
"sys-locale",
"sysinfo",
@ -4761,12 +4776,31 @@ dependencies = [
"digest",
]
[[package]]
name = "shared_memory"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba8593196da75d9dc4f69349682bd4c2099f8cde114257d1ef7ef1b33d1aba54"
dependencies = [
"cfg-if 1.0.0",
"libc",
"nix 0.23.1",
"rand 0.8.5",
"win-sys",
]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "shutdown_hooks"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6057adedbec913419c92996f395ba69931acbd50b7d56955394cd3f7bedbfa45"
[[package]]
name = "signal-hook"
version = "0.3.14"
@ -5533,12 +5567,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "virtual_display"
version = "0.1.0"
dependencies = [
"cc",
"hbb_common",
"lazy_static",
"serde 1.0.144",
"serde_derive",
"thiserror",
"libloading",
]
[[package]]
@ -5844,6 +5875,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]]
name = "win-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b7b128a98c1cfa201b09eb49ba285887deb3cbe7466a98850eb1adabb452be5"
dependencies = [
"windows 0.34.0",
]
[[package]]
name = "winapi"
version = "0.2.8"
@ -5909,6 +5949,19 @@ dependencies = [
"windows_x86_64_msvc 0.30.0",
]
[[package]]
name = "windows"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f"
dependencies = [
"windows_aarch64_msvc 0.34.0",
"windows_i686_gnu 0.34.0",
"windows_i686_msvc 0.34.0",
"windows_x86_64_gnu 0.34.0",
"windows_x86_64_msvc 0.34.0",
]
[[package]]
name = "windows-service"
version = "0.4.0"
@ -5959,6 +6012,12 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca"
[[package]]
name = "windows_aarch64_msvc"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d"
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
@ -5977,6 +6036,12 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8"
[[package]]
name = "windows_i686_gnu"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
@ -5995,6 +6060,12 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6"
[[package]]
name = "windows_i686_msvc"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
@ -6013,6 +6084,12 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
@ -6031,6 +6108,12 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"

View File

@ -67,6 +67,7 @@ rdev = { git = "https://github.com/asur4s/rdev" }
url = { version = "2.1", features = ["serde"] }
reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
chrono = "0.4.23"
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
cpal = "0.13.5"
@ -92,6 +93,8 @@ winreg = "0.10"
windows-service = "0.4"
virtual_display = { path = "libs/virtual_display" }
impersonate_system = { git = "https://github.com/21pages/impersonate-system" }
shared_memory = "0.12.4"
shutdown_hooks = "0.1.0"
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
@ -123,7 +126,7 @@ jni = "0.19"
flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge" }
[workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/simple_rc", "libs/portable"]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/simple_rc", "libs/portable"]
[package.metadata.winres]
LegalCopyright = "Copyright © 2022 Purslane, Inc."

View File

@ -281,6 +281,7 @@ def build_flutter_windows(version, features):
os.chdir('flutter')
os.system('flutter build windows --release')
os.chdir('..')
shutil.copy2('target/release/deps/dylib_virtual_display.dll', flutter_win_target_dir)
os.chdir('libs/portable')
os.system('pip3 install -r requirements.txt')
os.system(
@ -316,6 +317,11 @@ def main():
os.system('python3 res/inline-sciter.py')
portable = args.portable
if windows:
# build virtual display dynamic library
os.chdir('libs/virtual_display/dylib')
os.system('cargo build --release')
os.chdir('../../..')
if flutter:
build_flutter_windows(version, features)
return

View File

@ -15,7 +15,6 @@ 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:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uni_links/uni_links.dart';
import 'package:uni_links_desktop/uni_links_desktop.dart';
import 'package:window_manager/window_manager.dart';
@ -205,18 +204,17 @@ class MyTheme {
);
static ThemeMode getThemeModePreference() {
return themeModeFromString(
Get.find<SharedPreferences>().getString("themeMode") ?? "");
return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme));
}
static void changeDarkMode(ThemeMode mode) {
final preference = getThemeModePreference();
if (preference != mode) {
if (mode == ThemeMode.system) {
Get.find<SharedPreferences>().setString("themeMode", "");
bind.mainSetLocalOption(key: kCommConfKeyTheme, value: '');
} else {
Get.find<SharedPreferences>()
.setString("themeMode", mode.toShortString());
bind.mainSetLocalOption(
key: kCommConfKeyTheme, value: mode.toShortString());
}
Get.changeThemeMode(mode);
if (desktopType == DesktopType.main) {
@ -629,7 +627,7 @@ class CustomAlertDialog extends StatelessWidget {
}
}
void msgBox(String type, String title, String text, String link,
void msgBox(String id, String type, String title, String text, String link,
OverlayDialogManager dialogManager,
{bool? hasCancel}) {
dialogManager.dismissAll();
@ -674,14 +672,17 @@ void msgBox(String type, String title, String text, String link,
if (link.isNotEmpty) {
buttons.insert(0, msgBoxButton(translate('JumpLink'), jumplink));
}
dialogManager.show((setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title),
content: SelectableText(translate(text),
style: const TextStyle(fontSize: 15)),
actions: buttons,
onSubmit: hasOk ? submit : null,
onCancel: hasCancel == true ? cancel : null,
));
dialogManager.show(
(setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title),
content:
SelectableText(translate(text), style: const TextStyle(fontSize: 15)),
actions: buttons,
onSubmit: hasOk ? submit : null,
onCancel: hasCancel == true ? cancel : null,
),
tag: '$id-$type-$title-$text-$link',
);
}
Widget msgBoxButton(String text, void Function() onPressed) {
@ -1026,8 +1027,8 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
final isMaximized = await windowManager.isMaximized();
final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized);
await Get.find<SharedPreferences>()
.setString(kWindowPrefix + type.name, pos.toString());
await bind.setLocalFlutterConfig(
k: kWindowPrefix + type.name, v: pos.toString());
break;
default:
final wc = WindowController.fromWindowId(windowId!);
@ -1037,9 +1038,10 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
final isMaximized = await wc.isMaximized();
final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized);
debugPrint("saving frame: ${windowId}: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}");
await Get.find<SharedPreferences>()
.setString(kWindowPrefix + type.name, pos.toString());
debugPrint(
"saving frame: $windowId: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}");
await bind.setLocalFlutterConfig(
k: kWindowPrefix + type.name, v: pos.toString());
break;
}
}
@ -1109,7 +1111,7 @@ Future<Offset?> _adjustRestoreMainWindowOffset(
.toDouble();
if (isDesktop || isWebDesktop) {
for(final screen in await window_size.getScreenList()) {
for (final screen in await window_size.getScreenList()) {
frameLeft = min(screen.visibleFrame.left, frameLeft);
frameTop = min(screen.visibleFrame.top, frameTop);
frameRight = max(screen.visibleFrame.right, frameRight);
@ -1136,13 +1138,7 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
debugPrint(
"Error: windowId cannot be null when saving positions for sub window");
}
final pos =
Get.find<SharedPreferences>().getString(kWindowPrefix + type.name);
if (pos == null) {
debugPrint("no window position saved, ignore restore");
return false;
}
final pos = bind.getLocalFlutterConfig(k: kWindowPrefix + type.name);
var lpos = LastWindowPosition.loadFromString(pos);
if (lpos == null) {
debugPrint("window position saved, but cannot be parsed");
@ -1175,7 +1171,8 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
final offset = await _adjustRestoreMainWindowOffset(
lpos.offsetWidth, lpos.offsetHeight);
debugPrint("restore lpos: ${size.width}/${size.height}, offset:${offset?.dx}/${offset?.dy}");
debugPrint(
"restore lpos: ${size.width}/${size.height}, offset:${offset?.dx}/${offset?.dy}");
if (offset == null) {
await wc.center();
} else {
@ -1327,8 +1324,7 @@ void connect(BuildContext context, String id,
Future<Map<String, String>> getHttpHeaders() async {
return {
'Authorization':
'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}'
'Authorization': 'Bearer ${bind.mainGetLocalOption(key: 'access_token')}'
};
}

View File

@ -201,3 +201,47 @@ class RemoteCountState {
static RxInt find() => Get.find<RxInt>(tag: tag());
}
class PeerBoolOption {
static String tag(String id, String opt) => 'peer_{$opt}_$id';
static void init(String id, String opt, bool Function() init_getter) {
final key = tag(id, opt);
if (!Get.isRegistered(tag: key)) {
final RxBool value = RxBool(init_getter());
Get.put(value, tag: key);
}
}
static void delete(String id, String opt) {
final key = tag(id, opt);
if (Get.isRegistered(tag: key)) {
Get.delete(tag: key);
}
}
static RxBool find(String id, String opt) =>
Get.find<RxBool>(tag: tag(id, opt));
}
class PeerStringOption {
static String tag(String id, String opt) => 'peer_{$opt}_$id';
static void init(String id, String opt, String Function() init_getter) {
final key = tag(id, opt);
if (!Get.isRegistered(tag: key)) {
final RxString value = RxString(init_getter());
Get.put(value, tag: key);
}
}
static void delete(String id, String opt) {
final key = tag(id, opt);
if (Get.isRegistered(tag: key)) {
Get.delete(tag: key);
}
}
static RxString find(String id, String opt) =>
Get.find<RxString>(tag: tag(id, opt));
}

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common/widgets/address_book.dart';
@ -446,6 +448,22 @@ abstract class BasePeerCard extends StatelessWidget {
);
}
/// Only avaliable on Windows.
@protected
MenuEntryBase<String> _createShortCutAction(String id) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Create Desktop Shortcut'),
style: style,
),
proc: () {
bind.mainCreateShortcut(id: id);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@protected
Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async {
const option = 'force-always-relay';
@ -584,7 +602,7 @@ abstract class BasePeerCard extends StatelessWidget {
submit() async {
isInProgress.value = true;
name = controller.text;
await bind.mainSetPeerOption(id: id, key: 'alias', value: name);
await bind.mainSetPeerAlias(id: id, alias: name);
if (isAddressBook) {
gFFI.abModel.setPeerAlias(id, name);
await gFFI.abModel.pushAb();
@ -649,6 +667,9 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
if (Platform.isWindows) {
menuItems.add(_createShortCutAction(peer.id));
}
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_removeAction(peer.id, () async {
@ -681,6 +702,9 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
if (Platform.isWindows) {
menuItems.add(_createShortCutAction(peer.id));
}
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_removeAction(peer.id, () async {
@ -715,6 +739,9 @@ class DiscoveredPeerCard extends BasePeerCard {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
if (Platform.isWindows) {
menuItems.add(_createShortCutAction(peer.id));
}
menuItems.add(MenuEntryDivider());
menuItems.add(_removeAction(peer.id, () async {}));
return menuItems;
@ -740,6 +767,9 @@ class AddressBookPeerCard extends BasePeerCard {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
if (Platform.isWindows) {
menuItems.add(_createShortCutAction(peer.id));
}
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_removeAction(peer.id, () async {}));

View File

@ -22,27 +22,28 @@ class _PeerTabPageState extends State<PeerTabPage>
@override
void initState() {
() async {
await bind.mainGetLocalOption(key: 'peer-tab-index').then((value) {
if (value == '') return;
final tab = int.parse(value);
_tabIndex.value = tab;
});
await bind.mainGetLocalOption(key: 'peer-card-ui-type').then((value) {
if (value == '') return;
final tab = int.parse(value);
peerCardUiType.value =
tab == PeerUiType.list.index ? PeerUiType.list : PeerUiType.grid;
});
}();
setPeer();
super.initState();
}
setPeer() {
final index = bind.getLocalFlutterConfig(k: 'peer-tab-index');
if (index != '') {
_tabIndex.value = int.parse(index);
}
final uiType = bind.getLocalFlutterConfig(k: 'peer-card-ui-type');
if (uiType != '') {
peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index
? PeerUiType.list
: PeerUiType.grid;
}
}
// hard code for now
Future<void> _handleTabSelection(int index) async {
_tabIndex.value = index;
await bind.mainSetLocalOption(
key: 'peer-tab-index', value: index.toString());
await bind.setLocalFlutterConfig(k: 'peer-tab-index', v: index.toString());
switch (index) {
case 0:
bind.mainLoadRecentPeers();
@ -148,9 +149,8 @@ class _PeerTabPageState extends State<PeerTabPage>
decoration: peerCardUiType.value == type ? activeDeco : null,
child: InkWell(
onTap: () async {
await bind.mainSetLocalOption(
key: 'peer-card-ui-type',
value: type.index.toString());
await bind.setLocalFlutterConfig(
k: 'peer-card-ui-type', v: type.index.toString());
peerCardUiType.value = type;
},
child: Icon(

View File

@ -45,7 +45,7 @@ const double kEmptyMarginTop = 50;
const double kDesktopIconButtonSplashRadius = 20;
/// [kMinCursorSize] indicates min cursor (w, h)
const int kMinCursorSize = 24;
const int kMinCursorSize = 12;
/// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse
const kDefaultScrollAmountMultiplier = 5.0;
@ -56,7 +56,11 @@ var kWindowEdgeSize = Platform.isWindows ? 1.0 : 5.0;
const kWindowBorderWidth = 1.0;
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
const kInvalidValueStr = "InvalidValueStr";
const kInvalidValueStr = 'InvalidValueStr';
// Config key shared by flutter and other ui.
const kCommConfKeyTheme = 'theme';
const kCommConfKeyLang = 'lang';
const kMobilePageConstraints = BoxConstraints(maxWidth: 600);

View File

@ -93,6 +93,12 @@ class _ConnectionPageState extends State<ConnectionPage>
}
}
@override
void onWindowClose() {
super.onWindowClose();
bind.mainOnMainWindowClose();
}
@override
Widget build(BuildContext context) {
return Column(

View File

@ -5,6 +5,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/desktop/widgets/login.dart';
@ -30,8 +31,8 @@ const double _kListViewBottomMargin = 15;
const double _kTitleFontSize = 20;
const double _kContentFontSize = 15;
const Color _accentColor = MyTheme.accent;
const String _kSettingPageControllerTag = "settingPageController";
const String _kSettingPageIndexTag = "settingPageIndex";
const String _kSettingPageControllerTag = 'settingPageController';
const String _kSettingPageIndexTag = 'settingPageIndex';
class _TabInfo {
late final String label;
@ -250,19 +251,19 @@ class _GeneralState extends State<_General> {
return _Card(title: 'Theme', children: [
_Radio<String>(context,
value: "light",
value: 'light',
groupValue: current,
label: "Light",
label: 'Light',
onChanged: onChanged),
_Radio<String>(context,
value: "dark",
value: 'dark',
groupValue: current,
label: "Dark",
label: 'Dark',
onChanged: onChanged),
_Radio<String>(context,
value: "system",
value: 'system',
groupValue: current,
label: "Follow System",
label: 'Follow System',
onChanged: onChanged),
]);
}
@ -286,8 +287,8 @@ class _GeneralState extends State<_General> {
Widget audio(BuildContext context) {
String getDefault() {
if (Platform.isWindows) return "System Sound";
return "";
if (Platform.isWindows) return 'System Sound';
return '';
}
Future<String> getValue() async {
@ -300,7 +301,7 @@ class _GeneralState extends State<_General> {
}
setDevice(String device) {
if (device == getDefault()) device = "";
if (device == getDefault()) device = '';
bind.mainSetOption(key: 'audio-input', value: device);
}
@ -353,7 +354,7 @@ class _GeneralState extends State<_General> {
'allow-auto-record-incoming'),
Row(
children: [
Text('${translate('Directory')}:'),
Text('${translate("Directory")}:'),
Expanded(
child: GestureDetector(
onTap: canlaunch ? () => launchUrl(Uri.file(dir)) : null,
@ -386,26 +387,26 @@ class _GeneralState extends State<_General> {
Widget language() {
return _futureBuilder(future: () async {
String langs = await bind.mainGetLangs();
String lang = await bind.mainGetLocalOption(key: "lang");
return {"langs": langs, "lang": lang};
String lang = bind.mainGetLocalOption(key: kCommConfKeyLang);
return {'langs': langs, 'lang': lang};
}(), hasData: (res) {
Map<String, String> data = res as Map<String, String>;
List<dynamic> langsList = jsonDecode(data["langs"]!);
List<dynamic> langsList = jsonDecode(data['langs']!);
Map<String, String> langsMap = {for (var v in langsList) v[0]: v[1]};
List<String> keys = langsMap.keys.toList();
List<String> values = langsMap.values.toList();
keys.insert(0, "");
values.insert(0, "Default");
String currentKey = data["lang"]!;
keys.insert(0, '');
values.insert(0, 'Default');
String currentKey = data['lang']!;
if (!keys.contains(currentKey)) {
currentKey = "";
currentKey = '';
}
return _ComboBox(
keys: keys,
values: values,
initialKey: currentKey,
onChanged: (key) async {
await bind.mainSetLocalOption(key: "lang", value: key);
await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key);
reloadAllWindows();
bind.mainChangeLanguage(lang: key);
},
@ -585,9 +586,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
kUseBothPasswords,
];
List<String> values = [
translate("Use temporary password"),
translate("Use permanent password"),
translate("Use both passwords"),
translate('Use temporary password'),
translate('Use permanent password'),
translate('Use both passwords'),
];
bool tmpEnabled = model.verificationMethod != kUsePermanentPassword;
bool permEnabled = model.verificationMethod != kUseTemporaryPassword;
@ -830,12 +831,12 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
// Setting page is not modal, oldOptions should only be used when getting options, never when setting.
Map<String, dynamic> oldOptions = jsonDecode(data! as String);
old(String key) {
return (oldOptions[key] ?? "").trim();
return (oldOptions[key] ?? '').trim();
}
RxString idErrMsg = "".obs;
RxString relayErrMsg = "".obs;
RxString apiErrMsg = "".obs;
RxString idErrMsg = ''.obs;
RxString relayErrMsg = ''.obs;
RxString apiErrMsg = ''.obs;
var idController =
TextEditingController(text: old('custom-rendezvous-server'));
var relayController = TextEditingController(text: old('relay-server'));
@ -863,10 +864,10 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
}
}
if (apiServer.isNotEmpty) {
if (!apiServer.startsWith('http://') ||
!apiServer.startsWith("https://")) {
if (!apiServer.startsWith('http://') &&
!apiServer.startsWith('https://')) {
apiErrMsg.value =
"${translate("API Server")}: ${translate("invalid_http")}";
'${translate("API Server")}: ${translate("invalid_http")}';
return false;
}
}
@ -893,7 +894,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
import() {
Clipboard.getData(Clipboard.kTextPlain).then((value) {
TextEditingController mytext = TextEditingController();
String? aNullableString = "";
String? aNullableString = '';
aNullableString = value?.text;
mytext.text = aNullableString.toString();
if (mytext.text.isNotEmpty) {
@ -918,13 +919,13 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
}
});
} else {
showToast(translate("Invalid server configuration"));
showToast(translate('Invalid server configuration'));
}
} catch (e) {
showToast(translate("Invalid server configuration"));
showToast(translate('Invalid server configuration'));
}
} else {
showToast(translate("Clipboard is empty"));
showToast(translate('Clipboard is empty'));
}
});
}
@ -936,7 +937,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
config['ApiServer'] = apiController.text.trim();
config['Key'] = keyController.text.trim();
Clipboard.setData(ClipboardData(text: jsonEncode(config)));
showToast(translate("Export server configuration successfully"));
showToast(translate('Export server configuration successfully'));
}
bool secure = !enabled;
@ -962,7 +963,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
Obx(() => _LabeledTextField(context, 'API Server', apiController,
apiErrMsg.value, enabled, secure)),
_LabeledTextField(
context, 'Key', keyController, "", enabled, secure),
context, 'Key', keyController, '', enabled, secure),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [_Button('Apply', submit, enabled: enabled)],
@ -1028,10 +1029,12 @@ class _AboutState extends State<_About> {
return _futureBuilder(future: () async {
final license = await bind.mainGetLicense();
final version = await bind.mainGetVersion();
return {'license': license, 'version': version};
final buildDate = await bind.mainGetBuildDate();
return {'license': license, 'version': version, 'buildDate': buildDate};
}(), hasData: (data) {
final license = data['license'].toString();
final version = data['version'].toString();
final buildDate = data['buildDate'].toString();
const linkStyle = TextStyle(decoration: TextDecoration.underline);
final scrollController = ScrollController();
return DesktopScrollWrapper(
@ -1039,28 +1042,29 @@ class _AboutState extends State<_About> {
child: SingleChildScrollView(
controller: scrollController,
physics: NeverScrollableScrollPhysics(),
child: _Card(title: "About RustDesk", children: [
child: _Card(title: 'About RustDesk', children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Text("Version: $version").marginSymmetric(vertical: 4.0),
Text('Version: $version').marginSymmetric(vertical: 4.0),
Text('Build Date: $buildDate').marginSymmetric(vertical: 4.0),
InkWell(
onTap: () {
launchUrlString("https://rustdesk.com/privacy");
launchUrlString('https://rustdesk.com/privacy');
},
child: const Text(
"Privacy Statement",
'Privacy Statement',
style: linkStyle,
).marginSymmetric(vertical: 4.0)),
InkWell(
onTap: () {
launchUrlString("https://rustdesk.com");
launchUrlString('https://rustdesk.com');
},
child: const Text(
"Website",
'Website',
style: linkStyle,
).marginSymmetric(vertical: 4.0)),
Container(
@ -1074,11 +1078,11 @@ class _AboutState extends State<_About> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Copyright &copy; 2022 Purslane Ltd.\n$license",
'Copyright © 2022 Purslane Ltd.\n$license',
style: const TextStyle(color: Colors.white),
),
const Text(
"Made with heart in this chaotic world!",
'Made with heart in this chaotic world!',
style: TextStyle(
fontWeight: FontWeight.w800,
color: Colors.white),
@ -1472,10 +1476,10 @@ class _ComboBox extends StatelessWidget {
void changeSocks5Proxy() async {
var socks = await bind.mainGetSocks();
String proxy = "";
String proxyMsg = "";
String username = "";
String password = "";
String proxy = '';
String proxyMsg = '';
String username = '';
String password = '';
if (socks.length == 3) {
proxy = socks[0];
username = socks[1];
@ -1489,7 +1493,7 @@ void changeSocks5Proxy() async {
gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
proxyMsg = "";
proxyMsg = '';
isInProgress = true;
});
cancel() {
@ -1517,7 +1521,7 @@ void changeSocks5Proxy() async {
}
return CustomAlertDialog(
title: Text(translate("Socks5 Proxy")),
title: Text(translate('Socks5 Proxy')),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
@ -1530,7 +1534,7 @@ void changeSocks5Proxy() async {
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Hostname')}:")
child: Text('${translate("Hostname")}:')
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
@ -1553,7 +1557,7 @@ void changeSocks5Proxy() async {
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Username')}:")
child: Text('${translate("Username")}:')
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
@ -1575,7 +1579,7 @@ void changeSocks5Proxy() async {
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:")
child: Text('${translate("Password")}:')
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
@ -1599,8 +1603,8 @@ void changeSocks5Proxy() async {
),
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, child: Text(translate("OK"))),
TextButton(onPressed: close, child: Text(translate('Cancel'))),
TextButton(onPressed: submit, child: Text(translate('OK'))),
],
onSubmit: submit,
onCancel: close,

View File

@ -83,6 +83,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
controller: tabController,
onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10),
labelGetter: DesktopTab.labelGetterAlias,
)),
);
return Platform.isMacOS

View File

@ -94,6 +94,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
return true;
},
tail: AddButton().paddingOnly(left: 10),
labelGetter: DesktopTab.labelGetterAlias,
)),
);
return Platform.isMacOS

View File

@ -28,14 +28,14 @@ class RemotePage extends StatefulWidget {
RemotePage({
Key? key,
required this.id,
required this.menubarState,
}) : super(key: key);
final String id;
final MenubarState menubarState;
final SimpleWrapper<State<RemotePage>?> _lastState = SimpleWrapper(null);
FFI get ffi => (_lastState.value! as _RemotePageState)._ffi;
RxBool get showMenubar =>
(_lastState.value! as _RemotePageState)._showMenubar;
@override
State<RemotePage> createState() {
@ -50,8 +50,8 @@ class _RemotePageState extends State<RemotePage>
Timer? _timer;
String keyboardMode = "legacy";
final _cursorOverImage = false.obs;
final _showMenubar = false.obs;
late RxBool _showRemoteCursor;
late RxBool _zoomCursor;
late RxBool _remoteCursorMoved;
late RxBool _keyboardEnabled;
@ -69,6 +69,10 @@ class _RemotePageState extends State<RemotePage>
KeyboardEnabledState.init(id);
ShowRemoteCursorState.init(id);
RemoteCursorMovedState.init(id);
final optZoomCursor = 'zoom-cursor';
PeerBoolOption.init(id, optZoomCursor,
() => bind.sessionGetToggleOptionSync(id: id, arg: optZoomCursor));
_zoomCursor = PeerBoolOption.find(id, optZoomCursor);
_showRemoteCursor = ShowRemoteCursorState.find(id);
_keyboardEnabled = KeyboardEnabledState.find(id);
_remoteCursorMoved = RemoteCursorMovedState.find(id);
@ -164,7 +168,7 @@ class _RemotePageState extends State<RemotePage>
super.build(context);
return WillPopScope(
onWillPop: () async {
clientClose(_ffi.dialogManager);
clientClose(widget.id, _ffi.dialogManager);
return false;
},
child: MultiProvider(providers: [
@ -217,6 +221,7 @@ class _RemotePageState extends State<RemotePage>
});
return ImagePaint(
id: widget.id,
zoomCursor: _zoomCursor,
cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved,
@ -234,12 +239,13 @@ class _RemotePageState extends State<RemotePage>
visible: _showRemoteCursor.isTrue && _remoteCursorMoved.isTrue,
child: CursorPaint(
id: widget.id,
zoomCursor: _zoomCursor,
))));
paints.add(QualityMonitor(_ffi.qualityMonitorModel));
paints.add(RemoteMenubar(
id: widget.id,
ffi: _ffi,
show: _showMenubar,
state: widget.menubarState,
onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func,
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null,
));
@ -252,24 +258,40 @@ class _RemotePageState extends State<RemotePage>
bool get wantKeepAlive => true;
}
class ImagePaint extends StatelessWidget {
class ImagePaint extends StatefulWidget {
final String id;
final Rx<bool> zoomCursor;
final Rx<bool> cursorOverImage;
final Rx<bool> keyboardEnabled;
final Rx<bool> remoteCursorMoved;
final Widget Function(Widget)? listenerBuilder;
final ScrollController _horizontal = ScrollController();
final ScrollController _vertical = ScrollController();
ImagePaint(
{Key? key,
required this.id,
required this.zoomCursor,
required this.cursorOverImage,
required this.keyboardEnabled,
required this.remoteCursorMoved,
this.listenerBuilder})
: super(key: key);
@override
State<StatefulWidget> createState() => _ImagePaintState();
}
class _ImagePaintState extends State<ImagePaint> {
bool _lastRemoteCursorMoved = false;
final ScrollController _horizontal = ScrollController();
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;
Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder;
@override
Widget build(BuildContext context) {
final m = Provider.of<ImageModel>(context);
@ -279,9 +301,18 @@ class ImagePaint extends StatelessWidget {
mouseRegion({child}) => Obx(() => MouseRegion(
cursor: cursorOverImage.isTrue
? keyboardEnabled.isTrue
? (remoteCursorMoved.isTrue
? SystemMouseCursors.none
: _buildCustomCursor(context, s))
? (() {
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) {},
@ -290,12 +321,10 @@ class ImagePaint extends StatelessWidget {
if (c.scrollStyle == ScrollStyle.scrollbar) {
final imageWidth = c.getDisplayWidth() * s;
final imageHeight = c.getDisplayHeight() * s;
final imageWidget = SizedBox(
width: imageWidth,
height: imageHeight,
child: CustomPaint(
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
));
final imageWidget = CustomPaint(
size: Size(imageWidth, imageHeight),
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
);
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
@ -319,13 +348,10 @@ class ImagePaint extends StatelessWidget {
Size(imageWidth, imageHeight))),
);
} else {
final imageWidget = SizedBox(
width: c.size.width,
height: c.size.height,
child: CustomPaint(
painter:
ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
));
final imageWidget = CustomPaint(
size: Size(c.size.width, c.size.height),
painter: ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
);
return mouseRegion(child: _buildListener(imageWidget));
}
}
@ -336,15 +362,13 @@ class ImagePaint extends StatelessWidget {
if (cache == null) {
return MouseCursor.defer;
} else {
final key = cache.updateGetKey(scale);
final key = cache.updateGetKey(scale, zoomCursor.value);
cursor.addKey(key);
return FlutterCustomMemoryImageCursor(
pixbuf: cache.data,
key: key,
// hotx: cache.hotx,
// hoty: cache.hoty,
hotx: 0,
hoty: 0,
hotx: cache.hotx,
hoty: cache.hoty,
imageWidth: (cache.width * cache.scale).toInt(),
imageHeight: (cache.height * cache.scale).toInt(),
);
@ -481,21 +505,42 @@ class ImagePaint extends StatelessWidget {
class CursorPaint extends StatelessWidget {
final String id;
final RxBool zoomCursor;
const CursorPaint({Key? key, required this.id}) : super(key: key);
const CursorPaint({
Key? key,
required this.id,
required this.zoomCursor,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final m = Provider.of<CursorModel>(context);
final c = Provider.of<CanvasModel>(context);
// final adjust = m.adjustForKeyboard();
return CustomPaint(
painter: ImagePainter(
image: m.image,
x: m.x - m.hotx + c.x / c.scale,
y: m.y - m.hoty + c.y / c.scale,
scale: c.scale),
);
double hotx = m.hotx;
double hoty = m.hoty;
if (m.image == null) {
if (m.defaultCache != null) {
hotx = m.defaultImage!.width / 2;
hoty = m.defaultImage!.height / 2;
}
}
return zoomCursor.isTrue
? CustomPaint(
painter: ImagePainter(
image: m.image ?? m.defaultImage,
x: m.x - hotx + c.x / c.scale,
y: m.y - hoty + c.y / c.scale,
scale: c.scale),
)
: CustomPaint(
painter: ImagePainter(
image: m.image ?? m.defaultImage,
x: (m.x - hotx) * c.scale + c.x,
y: (m.y - hoty) * c.scale + c.y,
scale: 1.0),
);
}
}
@ -520,9 +565,11 @@ class ImagePainter extends CustomPainter {
// https://github.com/flutter/flutter/issues/76187#issuecomment-784628161
// https://api.flutter-io.cn/flutter/dart-ui/FilterQuality.html
var paint = Paint();
paint.filterQuality = FilterQuality.medium;
if (scale > 10.00000) {
paint.filterQuality = FilterQuality.high;
if ((scale - 1.0).abs() > 0.001) {
paint.filterQuality = FilterQuality.medium;
if (scale > 10.00000) {
paint.filterQuality = FilterQuality.high;
}
}
canvas.drawImage(image!, Offset(x, y), paint);
}

View File

@ -9,6 +9,7 @@ import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
import 'package:flutter_hbb/desktop/widgets/remote_menubar.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
as mod_menu;
@ -43,9 +44,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
static const IconData selectedIcon = Icons.desktop_windows_sharp;
static const IconData unselectedIcon = Icons.desktop_windows_outlined;
late MenubarState _menubarState;
var connectionMap = RxList<Widget>.empty(growable: true);
_ConnectionTabPageState(Map<String, dynamic> params) {
_menubarState = MenubarState();
RemoteCountState.init();
final peerId = params['id'];
if (peerId != null) {
@ -59,6 +63,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
page: RemotePage(
key: ValueKey(peerId),
id: peerId,
menubarState: _menubarState,
),
));
_update_remote_count();
@ -88,7 +93,11 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon,
onTabCloseButton: () => tabController.closeBy(id),
page: RemotePage(key: ValueKey(id), id: id),
page: RemotePage(
key: ValueKey(id),
id: id,
menubarState: _menubarState,
),
));
} else if (call.method == "onDestroy") {
tabController.clear();
@ -99,20 +108,29 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
});
}
@override
void dispose() {
super.dispose();
_menubarState.save();
}
@override
Widget build(BuildContext context) {
final tabWidget = Container(
decoration: BoxDecoration(
final tabWidget = Obx(
() => Container(
decoration: BoxDecoration(
border: Border.all(
color: MyTheme.color(context).border!,
width: kWindowBorderWidth)),
child: Scaffold(
width: stateGlobal.windowBorderWidth.value),
),
child: Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10),
pageViewBuilder: (pageView) => pageView,
labelGetter: DesktopTab.labelGetterAlias,
tabBuilder: (key, icon, label, themeConf) => Obx(() {
final connectionType = ConnectionTypeState.find(key);
if (!connectionType.isValid()) {
@ -166,7 +184,9 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
);
}
}),
)),
),
),
),
);
return Platform.isMacOS
? tabWidget
@ -177,7 +197,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
);
}
// to-do: some dup code to ../widgets/remote_menubar
// Note: Some dup code to ../widgets/remote_menubar
Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) {
final List<MenuEntryBase<String>> menu = [];
const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0);
@ -187,7 +207,6 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
final ffi = remotePage.ffi;
final pi = ffi.ffiModel.pi;
final perms = ffi.ffiModel.permissions;
final showMenuBar = remotePage.showMenubar;
menu.addAll([
MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
@ -202,11 +221,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
),
MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Obx(() => Text(
translate(showMenuBar.isTrue ? 'Hide Menubar' : 'Show Menubar'),
translate(
_menubarState.show.isTrue ? 'Hide Menubar' : 'Show Menubar'),
style: style,
)),
proc: () {
showMenuBar.value = !showMenuBar.value;
_menubarState.switchShow();
cancelFunc();
},
padding: padding,
@ -226,13 +246,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
dismissOnClicked: true,
),
],
curOptionGetter: () async {
return await bind.sessionGetOption(id: key, arg: 'view-style') ??
'adaptive';
},
curOptionGetter: () async =>
// null means peer id is not found, which there's no need to care about
await bind.sessionGetViewStyle(id: key) ?? '',
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: key, name: "view-style", value: newValue);
await bind.sessionSetViewStyle(
id: key, value: newValue);
ffi.canvasModel.updateViewStyle();
cancelFunc();
},

View File

@ -76,7 +76,6 @@ class _DesktopServerPageState extends State<DesktopServerPage>
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(child: ConnectionManager()),
SizedBox.fromSize(size: Size(0, 15.0)),
],
),
),
@ -486,6 +485,8 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
}
}
const double bigMargin = 15;
class _CmControlPanel extends StatelessWidget {
final Client client;
@ -501,108 +502,141 @@ class _CmControlPanel extends StatelessWidget {
}
buildAuthorized(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
final bool canElevate = bind.cmCanElevate();
final model = Provider.of<ServerModel>(context);
final showElevation = canElevate && model.showElevation;
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Ink(
width: 200,
height: 40,
decoration: BoxDecoration(
color: Colors.redAccent, borderRadius: BorderRadius.circular(10)),
child: InkWell(
onTap: () =>
checkClickTime(client.id, () => handleDisconnect(context)),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
translate("Disconnect"),
style: TextStyle(color: Colors.white),
),
],
)),
Offstage(
offstage: !showElevation,
child: buildButton(context, color: Colors.green[700], onClick: () {
handleElevate(context);
windowManager.minimize();
},
icon: Icon(
Icons.security_sharp,
color: Colors.white,
),
text: 'Elevate',
textColor: Colors.white),
),
Row(
children: [
Expanded(
child: buildButton(context,
color: Colors.redAccent,
onClick: handleDisconnect,
text: 'Disconnect',
textColor: Colors.white)),
],
)
],
);
)
.marginOnly(bottom: showElevation ? 0 : bigMargin)
.marginSymmetric(horizontal: showElevation ? 0 : bigMargin);
}
buildDisconnected(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Ink(
width: 200,
height: 40,
decoration: BoxDecoration(
color: MyTheme.accent, borderRadius: BorderRadius.circular(10)),
child: InkWell(
onTap: () => handleClose(context),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
translate("Close"),
style: TextStyle(color: Colors.white),
),
],
)),
)
Expanded(
child: buildButton(context,
color: MyTheme.accent,
onClick: handleClose,
text: 'Close',
textColor: Colors.white)),
],
);
).marginOnly(bottom: 15).marginSymmetric(horizontal: bigMargin);
}
buildUnAuthorized(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
final bool canElevate = bind.cmCanElevate();
final model = Provider.of<ServerModel>(context);
final showElevation = canElevate && model.showElevation;
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Ink(
width: 100,
height: 40,
decoration: BoxDecoration(
color: MyTheme.accent, borderRadius: BorderRadius.circular(10)),
child: InkWell(
onTap: () => checkClickTime(client.id, () {
handleAccept(context);
windowManager.minimize();
}),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
translate("Accept"),
style: TextStyle(color: Colors.white),
),
],
)),
Offstage(
offstage: !showElevation,
child: buildButton(context, color: Colors.green[700], onClick: () {
handleAccept(context);
handleElevate(context);
windowManager.minimize();
},
text: 'Accept',
icon: Icon(
Icons.security_sharp,
color: Colors.white,
),
textColor: Colors.white),
),
SizedBox(
width: 30,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: buildButton(context, color: MyTheme.accent, onClick: () {
handleAccept(context);
windowManager.minimize();
}, text: 'Accept', textColor: Colors.white)),
Expanded(
child: buildButton(context,
color: Colors.transparent,
border: Border.all(color: Colors.grey),
onClick: handleDisconnect,
text: 'Cancel',
textColor: null)),
],
),
Ink(
width: 100,
height: 40,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey)),
child: InkWell(
onTap: () =>
checkClickTime(client.id, () => handleDisconnect(context)),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
translate("Cancel"),
style: TextStyle(),
),
],
)),
)
],
);
)
.marginOnly(bottom: showElevation ? 0 : bigMargin)
.marginSymmetric(horizontal: showElevation ? 0 : bigMargin);
}
void handleDisconnect(BuildContext context) {
buildButton(
BuildContext context, {
required Color? color,
required Function() onClick,
Icon? icon,
BoxBorder? border,
required String text,
required Color? textColor,
}) {
Widget textWidget;
if (icon != null) {
textWidget = Text(
translate(text),
style: TextStyle(color: textColor),
textAlign: TextAlign.center,
);
} else {
textWidget = Expanded(
child: Text(
translate(text),
style: TextStyle(color: textColor),
textAlign: TextAlign.center,
),
);
}
return Container(
height: 35,
decoration: BoxDecoration(
color: color, borderRadius: BorderRadius.circular(4), border: border),
child: InkWell(
onTap: () => checkClickTime(client.id, onClick),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Offstage(offstage: icon == null, child: icon),
textWidget,
],
)),
).marginAll(4);
}
void handleDisconnect() {
bind.cmCloseConnection(connId: client.id);
}
@ -611,7 +645,13 @@ class _CmControlPanel extends StatelessWidget {
model.sendLoginResponse(client, true);
}
void handleClose(BuildContext context) async {
void handleElevate(BuildContext context) {
final model = Provider.of<ServerModel>(context, listen: false);
model.setShowElevation(false);
bind.cmElevatePortable(connId: client.id);
}
void handleClose() async {
await bind.cmRemoveDisconnectedConnection(connId: client.id);
if (await bind.cmGetClientsLength() == 0) {
windowManager.close();

View File

@ -21,6 +21,69 @@ import '../../common/shared_state.dart';
import './popup_menu.dart';
import './material_mod_popup_menu.dart' as mod_menu;
class MenubarState {
final kStoreKey = 'remoteMenubarState';
late RxBool show;
late RxBool _pin;
MenubarState() {
final s = bind.getLocalFlutterConfig(k: kStoreKey);
if (s.isEmpty) {
_initSet(false, false);
return;
}
try {
final m = jsonDecode(s);
if (m == null) {
_initSet(false, false);
} else {
_initSet(m['pin'] ?? false, m['pin'] ?? false);
}
} catch (e) {
debugPrint('Failed to decode menubar state ${e.toString()}');
_initSet(false, false);
}
}
_initSet(bool s, bool p) {
// Show remubar when connection is established.
show = RxBool(true);
_pin = RxBool(p);
}
bool get pin => _pin.value;
switchShow() async {
show.value = !show.value;
}
setShow(bool v) async {
if (show.value != v) {
show.value = v;
}
}
switchPin() async {
_pin.value = !_pin.value;
// Save everytime changed, as this func will not be called frequently
await save();
}
setPin(bool v) async {
if (_pin.value != v) {
_pin.value = v;
// Save everytime changed, as this func will not be called frequently
await save();
}
}
save() async {
bind.setLocalFlutterConfig(
k: kStoreKey, v: jsonEncode({'pin': _pin.value}));
}
}
class _MenubarTheme {
static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension
@ -31,7 +94,7 @@ class _MenubarTheme {
class RemoteMenubar extends StatefulWidget {
final String id;
final FFI ffi;
final RxBool show;
final MenubarState state;
final Function(Function(bool)) onEnterOrLeaveImageSetter;
final Function() onEnterOrLeaveImageCleaner;
@ -39,7 +102,7 @@ class RemoteMenubar extends StatefulWidget {
Key? key,
required this.id,
required this.ffi,
required this.show,
required this.state,
required this.onEnterOrLeaveImageSetter,
required this.onEnterOrLeaveImageCleaner,
}) : super(key: key);
@ -51,7 +114,6 @@ class RemoteMenubar extends StatefulWidget {
class _RemoteMenubarState extends State<RemoteMenubar> {
final Rx<Color> _hideColor = Colors.white12.obs;
final _rxHideReplay = rxdart.ReplaySubject<int>();
final _pinMenubar = false.obs;
bool _isCursorOverImage = false;
window_size.Screen? _screen;
@ -63,7 +125,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
setState(() {});
}
RxBool get show => widget.show;
RxBool get show => widget.state.show;
bool get pin => widget.state.pin;
@override
initState() {
@ -82,7 +145,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
.throttleTime(const Duration(milliseconds: 5000),
trailing: true, leading: false)
.listen((int v) {
if (_pinMenubar.isFalse && show.isTrue && _isCursorOverImage) {
if (!pin && show.isTrue && _isCursorOverImage) {
show.value = false;
}
});
@ -132,7 +195,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
}
_updateScreen() async {
final v = await DesktopMultiWindow.invokeMethod(0, "get_window_info", "");
final v = await DesktopMultiWindow.invokeMethod(0, 'get_window_info', '');
final String valueStr = v;
if (valueStr.isEmpty) {
_screen = null;
@ -196,18 +259,15 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
Widget _buildPinMenubar(BuildContext context) {
return Obx(() => IconButton(
tooltip:
translate(_pinMenubar.isTrue ? 'Unpin menubar' : 'Pin menubar'),
tooltip: translate(pin ? 'Unpin menubar' : 'Pin menubar'),
onPressed: () {
_pinMenubar.value = !_pinMenubar.value;
widget.state.switchPin();
},
icon: Obx(() => Transform.rotate(
angle: _pinMenubar.isTrue ? math.pi / 4 : 0,
angle: pin ? math.pi / 4 : 0,
child: Icon(
Icons.push_pin,
color: _pinMenubar.isTrue
? _MenubarTheme.commonColor
: Colors.grey,
color: pin ? _MenubarTheme.commonColor : Colors.grey,
))),
));
}
@ -262,7 +322,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
child: Obx(() {
RxInt display = CurrentDisplayState.find(widget.id);
return Text(
"${display.value + 1}/${pi.displays.length}",
'${display.value + 1}/${pi.displays.length}',
style: const TextStyle(
color: _MenubarTheme.commonColor, fontSize: 8),
);
@ -430,7 +490,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
return IconButton(
tooltip: translate('Close'),
onPressed: () {
clientClose(widget.ffi.dialogManager);
clientClose(widget.id, widget.ffi.dialogManager);
},
icon: const Icon(
Icons.close,
@ -535,10 +595,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
));
}
}
if (perms["restart"] != false &&
(pi.platform == "Linux" ||
pi.platform == "Windows" ||
pi.platform == "Mac OS")) {
if (perms['restart'] != false &&
(pi.platform == 'Linux' ||
pi.platform == 'Windows' ||
pi.platform == 'Mac OS')) {
displayMenu.add(MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Restart Remote Device'),
@ -569,14 +629,14 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
displayMenu.add(MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Obx(() => Text(
translate(
'${BlockInputState.find(widget.id).value ? "Unb" : "B"}lock user input'),
'${BlockInputState.find(widget.id).value ? 'Unb' : 'B'}lock user input'),
style: style,
)),
proc: () {
RxBool blockInput = BlockInputState.find(widget.id);
bind.sessionToggleOption(
id: widget.id,
value: '${blockInput.value ? "un" : ""}block-input');
value: '${blockInput.value ? 'un' : ''}block-input');
blockInput.value = !blockInput.value;
},
padding: padding,
@ -611,7 +671,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
// ClipboardData? data =
// await Clipboard.getData(Clipboard.kTextPlain);
// if (data != null && data.text != null) {
// bind.sessionInputString(id: widget.id, value: data.text ?? "");
// bind.sessionInputString(id: widget.id, value: data.text ?? '');
// }
// }();
// },
@ -619,18 +679,6 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
// dismissOnClicked: true,
// ));
// }
displayMenu.add(MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Reset canvas'),
style: style,
),
proc: () {
widget.ffi.cursorModel.reset();
},
padding: padding,
dismissOnClicked: true,
));
}
return displayMenu;
@ -680,14 +728,11 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
dismissOnClicked: true,
),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'view-style') ??
'adaptive';
},
curOptionGetter: () async =>
// null means peer id is not found, which there's no need to care about
await bind.sessionGetViewStyle(id: widget.id) ?? '',
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "view-style", value: newValue);
await bind.sessionSetViewStyle(id: widget.id, value: newValue);
widget.ffi.canvasModel.updateViewStyle();
},
padding: padding,
@ -708,14 +753,11 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
dismissOnClicked: true,
),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'scroll-style') ??
'';
},
curOptionGetter: () async =>
// null means peer id is not found, which there's no need to care about
await bind.sessionGetScrollStyle(id: widget.id) ?? '',
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "scroll-style", value: newValue);
await bind.sessionSetScrollStyle(id: widget.id, value: newValue);
widget.ffi.canvasModel.updateScrollStyle();
},
padding: padding,
@ -745,12 +787,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
value: 'custom',
dismissOnClicked: true),
],
curOptionGetter: () async {
String quality =
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced';
if (quality == '') quality = 'balanced';
return quality;
},
curOptionGetter: () async =>
// null means peer id is not found, which there's no need to care about
await bind.sessionGetImageQuality(id: widget.id) ?? '',
optionSetter: (String oldValue, String newValue) async {
if (oldValue != newValue) {
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
@ -1015,14 +1054,14 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
}
return list;
},
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'codec-preference') ??
'auto';
},
curOptionGetter: () async =>
// null means peer id is not found, which there's no need to care about
await bind.sessionGetOption(
id: widget.id, arg: 'codec-preference') ??
'',
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "codec-preference", value: newValue);
id: widget.id, name: 'codec-preference', value: newValue);
bind.sessionChangePreferCodec(id: widget.id);
},
padding: padding,
@ -1050,6 +1089,25 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
);
}());
/// Show remote cursor
displayMenu.add(() {
final opt = 'zoom-cursor';
final state = PeerBoolOption.find(widget.id, opt);
return MenuEntrySwitch2<String>(
switchType: SwitchType.scheckbox,
text: translate('Zoom cursor'),
getter: () {
return state;
},
setter: (bool v) async {
state.value = v;
await bind.sessionToggleOption(id: widget.id, value: opt);
},
padding: padding,
dismissOnClicked: true,
);
}());
/// Show quality monitor
displayMenu.add(MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
@ -1116,9 +1174,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
],
curOptionGetter: () async {
return await bind.sessionGetKeyboardName(id: widget.id);
},
curOptionGetter: () async =>
await bind.sessionGetKeyboardName(id: widget.id),
optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetKeyboardMode(
id: widget.id, keyboardMode: newValue);
@ -1150,16 +1207,16 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
void showSetOSPassword(
String id, bool login, OverlayDialogManager dialogManager) async {
final controller = TextEditingController();
var password = await bind.sessionGetOption(id: id, arg: "os-password") ?? "";
var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != "";
var password = await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
var autoLogin = await bind.sessionGetOption(id: id, arg: 'auto-login') != '';
controller.text = password;
dialogManager.show((setState, close) {
submit() {
var text = controller.text.trim();
bind.sessionPeerOption(id: id, name: "os-password", value: text);
bind.sessionPeerOption(id: id, name: 'os-password', value: text);
bind.sessionPeerOption(
id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
if (text != "" && login) {
id: id, name: 'auto-login', value: autoLogin ? 'Y' : '');
if (text != '' && login) {
bind.sessionInputOsPassword(id: id, value: text);
}
close();
@ -1206,7 +1263,7 @@ void showAuditDialog(String id, dialogManager) async {
dialogManager.show((setState, close) {
submit() {
var text = controller.text.trim();
if (text != "") {
if (text != '') {
bind.sessionSendNote(id: id, note: text);
}
close();
@ -1245,11 +1302,11 @@ void showAuditDialog(String id, dialogManager) async {
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.newline,
decoration: const InputDecoration.collapsed(
hintText: "input note here",
hintText: 'input note here',
),
// inputFormatters: [
// LengthLimitingTextInputFormatter(16),
// // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
// // FilteringTextInputFormatter(RegExp(r'[a-zA-z][a-zA-z0-9\_]*'), allow: true)
// ],
maxLines: null,
maxLength: 256,

View File

@ -9,6 +9,7 @@ import 'package:flutter/material.dart' hide TabBarTheme;
import 'package:flutter_hbb/common.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:get/get.dart';
@ -252,6 +253,15 @@ class DesktopTab extends StatelessWidget {
tabType == DesktopTabType.main || tabType == DesktopTabType.cm;
}
static RxString labelGetterAlias(String peerId) {
final opt = 'alias';
PeerStringOption.init(peerId, opt, () {
final alias = bind.mainGetPeerOptionSync(id: peerId, key: opt);
return alias.isEmpty ? peerId : alias;
});
return PeerStringOption.find(peerId, opt);
}
@override
Widget build(BuildContext context) {
return Column(children: [

View File

@ -15,7 +15,6 @@ 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:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';
import 'package:bot_toast/bot_toast.dart';
@ -97,7 +96,6 @@ Future<void> main(List<String> args) async {
Future<void> initEnv(String appType) async {
// global shared preference
await Get.putAsync(() => SharedPreferences.getInstance());
await platformFFI.init(appType);
// global FFI, use this **ONLY** for global configuration
// for convenience, use global FFI on mobile platform

View File

@ -63,7 +63,8 @@ class _FileManagerPageState extends State<FileManagerPage> {
leading: Row(children: [
IconButton(
icon: Icon(Icons.close),
onPressed: () => clientClose(gFFI.dialogManager)),
onPressed: () =>
clientClose(widget.id, gFFI.dialogManager)),
]),
centerTitle: true,
title: ToggleSwitch(

View File

@ -223,7 +223,7 @@ class _RemotePageState extends State<RemotePage> {
return WillPopScope(
onWillPop: () async {
clientClose(gFFI.dialogManager);
clientClose(widget.id, gFFI.dialogManager);
return false;
},
child: getRawPointerAndKeyBody(Scaffold(
@ -304,7 +304,7 @@ class _RemotePageState extends State<RemotePage> {
color: Colors.white,
icon: Icon(Icons.clear),
onPressed: () {
clientClose(gFFI.dialogManager);
clientClose(widget.id, gFFI.dialogManager);
},
)
] +
@ -474,7 +474,7 @@ class _RemotePageState extends State<RemotePage> {
},
onTwoFingerScaleEnd: (d) {
_scale = 1;
bind.sessionPeerOption(id: widget.id, name: "view-style", value: "");
bind.sessionSetViewStyle(id: widget.id, value: "");
},
onThreeFingerVerticalDragUpdate: gFFI.ffiModel.isPeerAndroid
? null
@ -862,11 +862,19 @@ class CursorPaint extends StatelessWidget {
final c = Provider.of<CanvasModel>(context);
final adjust = gFFI.cursorModel.adjustForKeyboard();
var s = c.scale;
double hotx = m.hotx;
double hoty = m.hoty;
if (m.image == null) {
if (m.defaultCache != null) {
hotx = m.defaultImage!.width / 2;
hoty = m.defaultImage!.height / 2;
}
}
return CustomPaint(
painter: ImagePainter(
image: m.image,
x: m.x * s - m.hotx + c.x,
y: m.y * s - m.hoty + c.y - adjust,
image: m.image ?? m.defaultImage,
x: m.x * s - hotx * s + c.x,
y: m.y * s - hoty * s + c.y - adjust,
scale: 1),
);
}
@ -993,7 +1001,7 @@ void showOptions(
setState(() {
viewStyle = value;
bind
.sessionPeerOption(id: id, name: "view-style", value: value)
.sessionSetViewStyle(id: id, value: value)
.then((_) => gFFI.canvasModel.updateViewStyle());
});
}

View File

@ -5,9 +5,9 @@ import '../../common.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
void clientClose(OverlayDialogManager dialogManager) {
msgBox(
'', 'Close', 'Are you sure to close the connection?', '', dialogManager);
void clientClose(String id, OverlayDialogManager dialogManager) {
msgBox(id, '', 'Close', 'Are you sure to close the connection?', '',
dialogManager);
}
void showSuccess() {

View File

@ -42,7 +42,7 @@ class InputModel {
// mouse
final isPhysicalMouse = false.obs;
int _lastMouseDownButtons = 0;
Offset last_mouse_pos = Offset.zero;
Offset lastMousePos = Offset.zero;
get id => parent.target?.id ?? "";
@ -308,23 +308,23 @@ class InputModel {
double y = max(0.0, evt['y']);
final cursorModel = parent.target!.cursorModel;
if (cursorModel.is_peer_control_protected) {
last_mouse_pos = ui.Offset(x, y);
if (cursorModel.isPeerControlProtected) {
lastMousePos = ui.Offset(x, y);
return;
}
if (!cursorModel.got_mouse_control) {
bool self_get_control =
(x - last_mouse_pos.dx).abs() > kMouseControlDistance ||
(y - last_mouse_pos.dy).abs() > kMouseControlDistance;
if (self_get_control) {
cursorModel.got_mouse_control = true;
if (!cursorModel.gotMouseControl) {
bool selfGetControl =
(x - lastMousePos.dx).abs() > kMouseControlDistance ||
(y - lastMousePos.dy).abs() > kMouseControlDistance;
if (selfGetControl) {
cursorModel.gotMouseControl = true;
} else {
last_mouse_pos = ui.Offset(x, y);
lastMousePos = ui.Offset(x, y);
return;
}
}
last_mouse_pos = ui.Offset(x, y);
lastMousePos = ui.Offset(x, y);
var type = '';
var isMove = false;

View File

@ -15,8 +15,8 @@ import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/user_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tuple/tuple.dart';
import 'package:image/image.dart' as img2;
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
@ -190,6 +190,13 @@ class FfiModel with ChangeNotifier {
rustDeskWinManager.newRemoteDesktop(arg);
});
}
} else if (name == 'alias') {
handleAliasChanged(evt);
} else if (name == 'show_elevation') {
final show = evt['show'].toString() == 'true';
parent.target?.serverModel.setShowElevation(show);
} else if (name == 'cancel_msgbox') {
cancelMsgBox(evt, peerId);
}
};
}
@ -199,6 +206,13 @@ class FfiModel with ChangeNotifier {
platformFFI.setEventCallback(startEventListener(peerId));
}
handleAliasChanged(Map<String, dynamic> evt) {
final rxAlias = PeerStringOption.find(evt['id'], 'alias');
if (rxAlias.value != evt['alias']) {
rxAlias.value = evt['alias'];
}
}
handleSwitchDisplay(Map<String, dynamic> evt) {
final oldOrientation = _display.width > _display.height;
var old = _pi.currentDisplay;
@ -219,6 +233,13 @@ class FfiModel with ChangeNotifier {
notifyListeners();
}
cancelMsgBox(Map<String, dynamic> evt, String id) {
if (parent.target == null) return;
final dialogManager = parent.target!.dialogManager;
final tag = '$id-${evt['tag']}';
dialogManager.dismissByTag(tag);
}
/// Handle the message box event based on [evt] and [id].
handleMsgBox(Map<String, dynamic> evt, String id) {
if (parent.target == null) return;
@ -244,7 +265,7 @@ class FfiModel with ChangeNotifier {
showMsgBox(String id, String type, String title, String text, String link,
bool hasRetry, OverlayDialogManager dialogManager,
{bool? hasCancel}) {
msgBox(type, title, text, link, dialogManager, hasCancel: hasCancel);
msgBox(id, type, title, text, link, dialogManager, hasCancel: hasCancel);
_timer?.cancel();
if (hasRetry) {
_timer = Timer(Duration(seconds: _reconnects), () {
@ -392,7 +413,7 @@ class ImageModel with ChangeNotifier {
await initializeCursorAndCanvas(parent.target!);
}
if (parent.target?.ffiModel.isPeerAndroid ?? false) {
bind.sessionPeerOption(id: id, name: 'view-style', value: 'adaptive');
bind.sessionSetViewStyle(id: id, value: 'adaptive');
parent.target?.canvasModel.updateViewStyle();
}
}
@ -514,7 +535,7 @@ class CanvasModel with ChangeNotifier {
double get scrollY => _scrollY;
updateViewStyle() async {
final style = await bind.sessionGetOption(id: id, arg: 'view-style');
final style = await bind.sessionGetViewStyle(id: id);
if (style == null) {
return;
}
@ -540,7 +561,7 @@ class CanvasModel with ChangeNotifier {
}
updateScrollStyle() async {
final style = await bind.sessionGetOption(id: id, arg: 'scroll-style');
final style = await bind.sessionGetScrollStyle(id: id);
if (style == 'scrollbar') {
_scrollStyle = ScrollStyle.scrollbar;
_scrollX = 0.0;
@ -572,7 +593,7 @@ class CanvasModel with ChangeNotifier {
return parent.target?.ffiModel.display.height ?? defaultHeight;
}
double get windowBorderWidth => stateGlobal.windowBorderWidth;
double get windowBorderWidth => stateGlobal.windowBorderWidth.value;
double get tabBarHeight => stateGlobal.tabBarHeight;
Size get size {
@ -675,6 +696,8 @@ class CursorData {
final img2.Image? image;
double scale;
Uint8List? data;
final double hotxOrigin;
final double hotyOrigin;
double hotx;
double hoty;
final int width;
@ -686,48 +709,60 @@ class CursorData {
required this.image,
required this.scale,
required this.data,
required this.hotx,
required this.hoty,
required this.hotxOrigin,
required this.hotyOrigin,
required this.width,
required this.height,
});
}) : hotx = hotxOrigin * scale,
hoty = hotxOrigin * scale;
int _doubleToInt(double v) => (v * 10e6).round().toInt();
double _checkUpdateScale(double scale) {
// 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;
double _checkUpdateScale(double scale, bool shouldScale) {
double oldScale = this.scale;
if (!shouldScale) {
scale = 1.0;
} else {
// 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;
}
}
if (_doubleToInt(this.scale) != _doubleToInt(scale)) {
}
if (Platform.isWindows) {
if (_doubleToInt(oldScale) != _doubleToInt(scale)) {
data = img2
.copyResize(
image!,
width: (width * scale).toInt(),
height: (height * scale).toInt(),
interpolation: img2.Interpolation.average,
)
.getBytes(format: img2.Format.bgra);
hotx = (width * scale) / 2;
hoty = (height * scale) / 2;
}
}
this.scale = scale;
hotx = hotxOrigin * scale;
hoty = hotyOrigin * scale;
return scale;
}
String updateGetKey(double scale) {
scale = _checkUpdateScale(scale);
String updateGetKey(double scale, bool shouldScale) {
scale = _checkUpdateScale(scale, shouldScale);
return '${peerId}_${id}_${_doubleToInt(width * scale)}_${_doubleToInt(height * scale)}';
}
}
class CursorModel with ChangeNotifier {
ui.Image? _image;
ui.Image? _defaultImage;
final _images = <int, Tuple3<ui.Image, double, double>>{};
CursorData? _cache;
final _defaultCacheId = -1;
@ -740,13 +775,14 @@ class CursorModel with ChangeNotifier {
double _hoty = 0;
double _displayOriginX = 0;
double _displayOriginY = 0;
bool got_mouse_control = true;
DateTime _last_peer_mouse = DateTime.now()
bool gotMouseControl = true;
DateTime _lastPeerMouse = DateTime.now()
.subtract(Duration(milliseconds: 2 * kMouseControlTimeoutMSec));
String id = '';
WeakReference<FFI> parent;
ui.Image? get image => _image;
ui.Image? get defaultImage => _defaultImage;
CursorData? get cache => _cache;
CursorData? get defaultCache => _getDefaultCache();
@ -758,34 +794,50 @@ class CursorModel with ChangeNotifier {
double get hotx => _hotx;
double get hoty => _hoty;
bool get is_peer_control_protected =>
DateTime.now().difference(_last_peer_mouse).inMilliseconds <
bool get isPeerControlProtected =>
DateTime.now().difference(_lastPeerMouse).inMilliseconds <
kMouseControlTimeoutMSec;
CursorModel(this.parent);
CursorModel(this.parent) {
_getDefaultImage();
_getDefaultCache();
}
Set<String> get cachedKeys => _cacheKeys;
addKey(String key) => _cacheKeys.add(key);
Future<ui.Image?> _getDefaultImage() async {
if (_defaultImage == null) {
final defaultImg = defaultCursorImage!;
// This function is called only one time, no need to care about the performance.
Uint8List data = defaultImg.getBytes(format: img2.Format.rgba);
_defaultImage = await img.decodeImageFromPixels(
data, defaultImg.width, defaultImg.height, ui.PixelFormat.rgba8888);
}
return _defaultImage;
}
CursorData? _getDefaultCache() {
if (_defaultCache == null) {
Uint8List data;
double scale = 1.0;
if (Platform.isWindows) {
Uint8List data = defaultCursorImage!.getBytes(format: img2.Format.bgra);
_hotx = defaultCursorImage!.width / 2;
_hoty = defaultCursorImage!.height / 2;
_defaultCache = CursorData(
peerId: id,
id: _defaultCacheId,
image: defaultCursorImage?.clone(),
scale: 1.0,
data: data,
hotx: _hotx,
hoty: _hoty,
width: defaultCursorImage!.width,
height: defaultCursorImage!.height,
);
data = defaultCursorImage!.getBytes(format: img2.Format.bgra);
} else {
data = Uint8List.fromList(img2.encodePng(defaultCursorImage!));
}
_defaultCache = CursorData(
peerId: id,
id: _defaultCacheId,
image: defaultCursorImage?.clone(),
scale: scale,
data: data,
hotxOrigin: defaultCursorImage!.width / 2,
hotyOrigin: defaultCursorImage!.height / 2,
width: defaultCursorImage!.width,
height: defaultCursorImage!.height,
);
}
return _defaultCache;
}
@ -917,59 +969,48 @@ class CursorModel with ChangeNotifier {
var height = int.parse(evt['height']);
List<dynamic> colors = json.decode(evt['colors']);
final rgba = Uint8List.fromList(colors.map((s) => s as int).toList());
var pid = parent.target?.id;
final image = await img.decodeImageFromPixels(
rgba, width, height, ui.PixelFormat.rgba8888);
if (parent.target?.id != pid) return;
_image = image;
_images[id] = Tuple3(image, _hotx, _hoty);
await _updateCache(image, id, width, height);
if (await _updateCache(image, id, width, height)) {
_images[id] = Tuple3(image, _hotx, _hoty);
} else {
_hotx = 0;
_hoty = 0;
}
try {
// my throw exception, because the listener maybe already dispose
notifyListeners();
} catch (e) {
debugPrint('notify cursor: $e');
debugPrint('WARNING: updateCursorId $id, without notifyListeners(). $e');
}
}
_updateCache(ui.Image image, int id, int w, int h) async {
Uint8List? data;
img2.Image? image2;
Future<bool> _updateCache(ui.Image image, int id, int w, int h) async {
ui.ImageByteFormat imgFormat = ui.ImageByteFormat.png;
if (Platform.isWindows) {
ByteData? data2 =
await image.toByteData(format: ui.ImageByteFormat.rawRgba);
if (data2 != null) {
data = data2.buffer.asUint8List();
image2 = img2.Image.fromBytes(w, h, data);
} else {
data = defaultCursorImage?.getBytes(format: img2.Format.bgra);
image2 = defaultCursorImage?.clone();
_hotx = defaultCursorImage!.width / 2;
_hoty = defaultCursorImage!.height / 2;
}
} else {
ByteData? data2 = await image.toByteData(format: ui.ImageByteFormat.png);
if (data2 != null) {
data = data2.buffer.asUint8List();
} else {
data = Uint8List.fromList(img2.encodePng(defaultCursorImage!));
_hotx = defaultCursorImage!.width / 2;
_hoty = defaultCursorImage!.height / 2;
}
imgFormat = ui.ImageByteFormat.rawRgba;
}
ByteData? imgBytes = await image.toByteData(format: imgFormat);
if (imgBytes == null) {
return false;
}
Uint8List? data = imgBytes.buffer.asUint8List();
_cache = CursorData(
peerId: this.id,
id: id,
image: image2,
image: Platform.isWindows ? img2.Image.fromBytes(w, h, data) : null,
scale: 1.0,
data: data,
hotx: _hotx,
hoty: _hoty,
hotxOrigin: _hotx,
hotyOrigin: _hoty,
width: w,
height: h,
);
_cacheMap[id] = _cache!;
return true;
}
updateCursorId(Map<String, dynamic> evt) async {
@ -981,13 +1022,16 @@ class CursorModel with ChangeNotifier {
_hotx = tmp.item2;
_hoty = tmp.item3;
notifyListeners();
} else {
debugPrint(
'WARNING: updateCursorId $id, cache is ${_cache == null ? "null" : "not null"}. without notifyListeners()');
}
}
/// Update the cursor position.
updateCursorPosition(Map<String, dynamic> evt, String id) async {
got_mouse_control = false;
_last_peer_mouse = DateTime.now();
gotMouseControl = false;
_lastPeerMouse = DateTime.now();
_x = double.parse(evt['x']);
_y = double.parse(evt['y']);
try {
@ -1219,7 +1263,7 @@ class FFI {
Future<void> close() async {
chatModel.close();
if (imageModel.image != null && !isWebDesktop) {
await savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x,
await setCanvasConfig(id, cursorModel.x, cursorModel.y, canvasModel.x,
canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
}
bind.sessionClose(id: id);
@ -1267,9 +1311,10 @@ class PeerInfo {
List<Display> displays = [];
}
Future<void> savePreference(String id, double xCursor, double yCursor,
const canvasKey = 'canvas';
Future<void> setCanvasConfig(String id, double xCursor, double yCursor,
double xCanvas, double yCanvas, double scale, int currentDisplay) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final p = <String, dynamic>{};
p['xCursor'] = xCursor;
p['yCursor'] = yCursor;
@ -1277,25 +1322,27 @@ Future<void> savePreference(String id, double xCursor, double yCursor,
p['yCanvas'] = yCanvas;
p['scale'] = scale;
p['currentDisplay'] = currentDisplay;
prefs.setString('peer$id', json.encode(p));
await bind.sessionSetFlutterConfig(id: id, k: canvasKey, v: jsonEncode(p));
}
Future<Map<String, dynamic>?> getPreference(String id) async {
Future<Map<String, dynamic>?> getCanvasConfig(String id) async {
if (!isWebDesktop) return null;
SharedPreferences prefs = await SharedPreferences.getInstance();
var p = prefs.getString('peer$id');
if (p == null) return null;
Map<String, dynamic> m = json.decode(p);
return m;
var p = await bind.sessionGetFlutterConfig(id: id, k: canvasKey);
if (p == null || p.isEmpty) return null;
try {
Map<String, dynamic> m = json.decode(p);
return m;
} catch (e) {
return null;
}
}
void removePreference(String id) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove('peer$id');
await bind.sessionSetFlutterConfig(id: id, k: canvasKey, v: '');
}
Future<void> initializeCursorAndCanvas(FFI ffi) async {
var p = await getPreference(ffi.id);
var p = await getCanvasConfig(ffi.id);
int currentDisplay = 0;
if (p != null) {
currentDisplay = p['currentDisplay'];

View File

@ -27,6 +27,7 @@ class ServerModel with ChangeNotifier {
bool _inputOk = false;
bool _audioOk = false;
bool _fileOk = false;
bool _showElevation = true;
int _connectStatus = 0; // Rendezvous Server status
String _verificationMethod = "";
String _temporaryPasswordLength = "";
@ -51,6 +52,8 @@ class ServerModel with ChangeNotifier {
bool get fileOk => _fileOk;
bool get showElevation => _showElevation;
int get connectStatus => _connectStatus;
String get verificationMethod {
@ -530,6 +533,13 @@ class ServerModel with ChangeNotifier {
final index = _clients.indexWhere((client) => client.id == id);
tabController.jumpTo(index);
}
void setShowElevation(bool show) {
if (_showElevation != show) {
_showElevation = show;
notifyListeners();
}
}
}
class Client {

View File

@ -1,4 +1,7 @@
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../consts.dart';
@ -7,14 +10,16 @@ class StateGlobal {
int _windowId = -1;
bool _fullscreen = false;
final RxBool _showTabBar = true.obs;
final RxDouble _resizeEdgeSize = 8.0.obs;
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
final RxBool showRemoteMenuBar = false.obs;
int get windowId => _windowId;
bool get fullscreen => _fullscreen;
double get tabBarHeight => fullscreen ? 0 : kDesktopRemoteTabBarHeight;
double get windowBorderWidth => fullscreen ? 0 : kWindowBorderWidth;
RxBool get showTabBar => _showTabBar;
RxDouble get resizeEdgeSize => _resizeEdgeSize;
RxDouble get windowBorderWidth => _windowBorderWidth;
setWindowId(int id) => _windowId = id;
setFullscreen(bool v) {
@ -23,7 +28,21 @@ class StateGlobal {
_showTabBar.value = !_fullscreen;
_resizeEdgeSize.value =
fullscreen ? kFullScreenEdgeSize : kWindowEdgeSize;
WindowController.fromWindowId(windowId).setFullscreen(_fullscreen);
_windowBorderWidth.value = fullscreen ? 0 : kWindowBorderWidth;
WindowController.fromWindowId(windowId)
.setFullscreen(_fullscreen)
.then((_) {
// https://github.com/leanflutter/window_manager/issues/131#issuecomment-1111587982
if (Platform.isWindows && !v) {
Future.delayed(Duration.zero, () async {
final frame =
await WindowController.fromWindowId(windowId).getFrame();
final newRect = Rect.fromLTWH(
frame.left, frame.top, frame.width + 1, frame.height + 1);
await WindowController.fromWindowId(windowId).setFrame(newRect);
});
}
});
}
}

View File

@ -19,7 +19,7 @@ class UserModel {
void refreshCurrentUser() async {
await getUserName();
final token = await bind.mainGetLocalOption(key: 'access_token');
final token = bind.mainGetLocalOption(key: 'access_token');
if (token == '') return;
final url = await bind.mainGetApiServer();
final body = {
@ -73,7 +73,7 @@ class UserModel {
if (userName.isNotEmpty) {
return userName.value;
}
final userInfo = await bind.mainGetLocalOption(key: 'user_info');
final userInfo = bind.mainGetLocalOption(key: 'user_info');
if (userInfo.trim().isEmpty) {
return '';
}

View File

@ -40,7 +40,6 @@ dependencies:
#firebase_analytics: ^9.1.5
package_info_plus: ^1.4.2
url_launcher: ^6.0.9
shared_preferences: ^2.0.6
toggle_switch: ^1.4.0
dash_chat_2: ^0.0.14
draggable_float_widget: ^0.0.2
@ -60,11 +59,11 @@ dependencies:
window_manager:
git:
url: https://github.com/Kingtous/rustdesk_window_manager
ref: d1e4b40f4a1ffeb8630696be6883dd31bf307998
ref: 32b24c66151b72bba033ef8b954486aa9351d97b
desktop_multi_window:
git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
ref: 8d9db30df28eddfdb11ded4aaba6f0fe96547960
ref: 8ee8eb59cabf6ac83a13fe002de7d4a231263a58
freezed_annotation: ^2.0.3
tray_manager:
git:
@ -73,7 +72,7 @@ dependencies:
flutter_custom_cursor:
git:
url: https://github.com/Kingtous/rustdesk_flutter_custom_cursor
ref: dec2166e881c47d922e1edc484d10d2cd5c2103b
ref: bfb19c84a8244771488bc05cc5f9c9b5e0324cfd
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding.git

View File

@ -1,6 +1,6 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(flutter_hbb LANGUAGES CXX)
project(rustdesk LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
@ -28,6 +28,18 @@ set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Replace /MD flags to /MT to use static vcruntine
set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
@ -41,6 +53,8 @@ function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
# Disable VC140_1
target_compile_options(${TARGET} PRIVATE /d2FH4-)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()

View File

@ -18,7 +18,7 @@ fn build_c_impl() {
if build.get_compiler().is_like_msvc() {
build.define("WIN32", "");
// build.define("_AMD64_", "");
build.flag("-Zi");
build.flag("-Z7");
build.flag("-GR-");
// build.flag("-std:c++11");
} else {

View File

@ -8,8 +8,8 @@ use std::{
};
use anyhow::Result;
use directories_next::ProjectDirs;
use rand::Rng;
use serde as de;
use serde_derive::{Deserialize, Serialize};
use sodiumoxide::crypto::sign;
@ -80,6 +80,26 @@ pub const RS_PUB_KEY: &'static str = "OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmB
pub const RENDEZVOUS_PORT: i32 = 21116;
pub const RELAY_PORT: i32 = 21117;
macro_rules! serde_field_string {
($default_func:ident, $de_func:ident, $default_expr:expr) => {
fn $default_func() -> String {
$default_expr
}
fn $de_func<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: de::Deserializer<'de>,
{
let s: &str = de::Deserialize::deserialize(deserializer)?;
Ok(if s.is_empty() {
Self::$default_func()
} else {
s.to_owned()
})
}
};
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NetworkType {
Direct,
@ -142,9 +162,20 @@ pub struct PeerConfig {
pub size_ft: Size,
#[serde(default)]
pub size_pf: Size,
#[serde(default)]
pub view_style: String, // original (default), scale
#[serde(default)]
#[serde(
default = "PeerConfig::default_view_style",
deserialize_with = "PeerConfig::deserialize_view_style"
)]
pub view_style: String,
#[serde(
default = "PeerConfig::default_scroll_style",
deserialize_with = "PeerConfig::deserialize_scroll_style"
)]
pub scroll_style: String,
#[serde(
default = "PeerConfig::default_image_quality",
deserialize_with = "PeerConfig::deserialize_image_quality"
)]
pub image_quality: String,
#[serde(default)]
pub custom_image_quality: Vec<i32>,
@ -167,9 +198,15 @@ pub struct PeerConfig {
#[serde(default)]
pub show_quality_monitor: bool,
// the other scalar value must before this
#[serde(default)]
// The other scalar value must before this
#[serde(
default,
deserialize_with = "PeerConfig::deserialize_options"
)]
pub options: HashMap<String, String>,
// Various data for flutter ui
#[serde(default)]
pub ui_flutter: HashMap<String, String>,
#[serde(default)]
pub info: PeerInfoSerde,
#[serde(default)]
@ -372,12 +409,15 @@ impl Config {
pub fn get_home() -> PathBuf {
#[cfg(any(target_os = "android", target_os = "ios"))]
return Self::path(APP_HOME_DIR.read().unwrap().as_str());
if let Some(path) = dirs_next::home_dir() {
patch(path)
} else if let Ok(path) = std::env::current_dir() {
path
} else {
std::env::temp_dir()
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
if let Some(path) = dirs_next::home_dir() {
patch(path)
} else if let Ok(path) = std::env::current_dir() {
path
} else {
std::env::temp_dir()
}
}
}
@ -388,17 +428,22 @@ impl Config {
path.push(p);
return path;
}
#[cfg(not(target_os = "macos"))]
let org = "";
#[cfg(target_os = "macos")]
let org = ORG.read().unwrap().clone();
// /var/root for root
if let Some(project) = ProjectDirs::from("", &org, &*APP_NAME.read().unwrap()) {
let mut path = patch(project.config_dir().to_path_buf());
path.push(p);
return path;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
#[cfg(not(target_os = "macos"))]
let org = "";
#[cfg(target_os = "macos")]
let org = ORG.read().unwrap().clone();
// /var/root for root
if let Some(project) =
directories_next::ProjectDirs::from("", &org, &*APP_NAME.read().unwrap())
{
let mut path = patch(project.config_dir().to_path_buf());
path.push(p);
return path;
}
return "".into();
}
return "".into();
}
#[allow(unreachable_code)]
@ -577,16 +622,19 @@ impl Config {
.to_string(),
);
}
let mut id = 0u32;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Ok(Some(ma)) = mac_address::get_mac_address() {
for x in &ma.bytes()[2..] {
id = (id << 8) | (*x as u32);
{
let mut id = 0u32;
if let Ok(Some(ma)) = mac_address::get_mac_address() {
for x in &ma.bytes()[2..] {
id = (id << 8) | (*x as u32);
}
id = id & 0x1FFFFFFF;
Some(id.to_string())
} else {
None
}
id = id & 0x1FFFFFFF;
Some(id.to_string())
} else {
None
}
}
@ -885,6 +933,21 @@ impl PeerConfig {
}
Default::default()
}
serde_field_string!(default_view_style, deserialize_view_style, "original".to_owned());
serde_field_string!(default_scroll_style, deserialize_scroll_style, "scrollauto".to_owned());
serde_field_string!(default_image_quality, deserialize_image_quality, "balanced".to_owned());
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());
}
Ok(mp)
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
@ -897,6 +960,9 @@ pub struct LocalConfig {
pub fav: Vec<String>,
#[serde(default)]
options: HashMap<String, String>,
// Various data for flutter ui
#[serde(default)]
ui_flutter: HashMap<String, String>,
}
impl LocalConfig {
@ -968,6 +1034,27 @@ impl LocalConfig {
config.store();
}
}
pub fn get_flutter_config(k: &str) -> String {
if let Some(v) = LOCAL_CONFIG.read().unwrap().ui_flutter.get(k) {
v.clone()
} else {
"".to_owned()
}
}
pub fn set_flutter_config(k: String, v: String) {
let mut config = LOCAL_CONFIG.write().unwrap();
let v2 = if v.is_empty() { None } else { Some(&v) };
if v2 != config.ui_flutter.get(&k) {
if v2.is_none() {
config.ui_flutter.remove(&k);
} else {
config.ui_flutter.insert(k, v);
}
config.store();
}
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]

View File

@ -380,7 +380,7 @@ impl TransferJob {
}
}
pub async fn write(&mut self, block: FileTransferBlock, raw: Option<&[u8]>) -> ResultType<()> {
pub async fn write(&mut self, block: FileTransferBlock) -> ResultType<()> {
if block.id != self.id {
bail!("Wrong id");
}
@ -402,20 +402,15 @@ impl TransferJob {
let path = format!("{}.download", get_string(&path));
self.file = Some(File::create(&path).await?);
}
let data = if let Some(data) = raw {
data
} else {
&block.data
};
if block.compressed {
let tmp = decompress(data);
let tmp = decompress(&block.data);
self.file.as_mut().unwrap().write_all(&tmp).await?;
self.finished_size += tmp.len() as u64;
} else {
self.file.as_mut().unwrap().write_all(data).await?;
self.finished_size += data.len() as u64;
self.file.as_mut().unwrap().write_all(&block.data).await?;
self.finished_size += block.data.len() as u64;
}
self.transferred += data.len() as u64;
self.transferred += block.data.len() as u64;
Ok(())
}

View File

@ -161,19 +161,23 @@ pub fn get_version_from_url(url: &str) -> String {
}
pub fn gen_version() {
use std::io::prelude::*;
let mut file = File::create("./src/version.rs").unwrap();
for line in read_lines("Cargo.toml").unwrap() {
if let Ok(line) = line {
let ab: Vec<&str> = line.split("=").map(|x| x.trim()).collect();
if ab.len() == 2 && ab[0] == "version" {
use std::io::prelude::*;
file.write_all(format!("pub const VERSION: &str = {};", ab[1]).as_bytes())
file.write_all(format!("pub const VERSION: &str = {};\n", ab[1]).as_bytes())
.ok();
file.sync_all().ok();
break;
}
}
}
// generate build date
let build_date = format!("{}", chrono::Local::now().format("%Y-%m-%d %H:%M"));
file.write_all(format!("pub const BUILD_DATE: &str = \"{}\";", build_date).as_bytes())
.ok();
file.sync_all().ok();
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>

View File

@ -51,24 +51,32 @@ fn execute(path: PathBuf, args: Vec<String>) {
.expect(&format!("failed to execute {:?}", exe_name));
}
fn is_setup(name: &str) -> bool {
name.to_lowercase().ends_with("install.exe") || name.to_lowercase().ends_with("安装.exe")
}
fn main() {
let is_setup = is_setup(
&std::env::current_exe()
.unwrap()
.to_string_lossy()
.to_string(),
);
let reader = BinaryReader::default();
if let Some(exe) = setup(reader, None, is_setup) {
let args = if is_setup {
vec!["--install".to_owned()]
let mut args = Vec::new();
let mut arg_exe = Default::default();
let mut i = 0;
for arg in std::env::args() {
if i == 0 {
arg_exe = arg.clone();
} else {
vec![]
};
args.push(arg);
}
i += 1;
}
let click_setup = args.is_empty() && arg_exe.to_lowercase().ends_with("install.exe");
let quick_support = args.is_empty() && arg_exe.to_lowercase().ends_with("qs.exe");
let reader = BinaryReader::default();
if let Some(exe) = setup(
reader,
None,
click_setup || args.contains(&"--silent-install".to_owned()),
) {
if click_setup {
args = vec!["--install".to_owned()];
} else if quick_support {
args = vec!["--quick_support".to_owned()];
}
execute(exe, args);
}
}

View File

@ -1,6 +1,3 @@
pub mod ffi;
use std::sync::RwLock;
pub use ffi::*;
use lazy_static::lazy_static;

View File

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

View File

@ -4,7 +4,7 @@
use hbb_common::anyhow::{anyhow, Context};
use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame};
use hbb_common::{ResultType, get_time};
use hbb_common::{get_time, ResultType};
use crate::codec::EncoderApi;
use crate::STRIDE_ALIGN;
@ -233,7 +233,9 @@ impl EncoderApi for VpxEncoder {
impl VpxEncoder {
pub fn encode(&mut self, pts: i64, data: &[u8], stride_align: usize) -> Result<EncodeFrames> {
assert!(2 * data.len() >= 3 * self.width * self.height);
if 2 * data.len() < 3 * self.width * self.height {
return Err(Error::FailedCall("len not enough".to_string()));
}
let mut image = Default::default();
call_vpx_ptr!(vpx_img_wrap(

1358
libs/virtual_display/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
cc = "1.0"
[dependencies]
thiserror = "1.0.30"
lazy_static = "1.4"
serde = "1.0"
serde_derive = "1.0"
libloading = "0.7"
hbb_common = { path = "../hbb_common" }

View File

@ -1,32 +1,3 @@
# virtual display
Virtual display may be used on computers that do not have a monitor.
[Development reference](https://github.com/pavlobu/deskreen/discussions/86)
## windows
### win10
Win10 provides [Indirect Display Driver Model](https://msdn.microsoft.com/en-us/library/windows/hardware/mt761968(v=vs.85).aspx).
This lib uses [this project](https://github.com/fufesou/RustDeskIddDriver) as the driver.
**NOTE**: Versions before Win10 1607. Try follow [this method](https://github.com/fanxiushu/xdisp_virt/tree/master/indirect_display).
#### tested platforms
- [x] 19041
- [x] 19043
### win7
TODO
[WDDM](https://docs.microsoft.com/en-us/windows-hardware/drivers/display/windows-vista-display-driver-model-design-guide).
## X11
## OSX
[doc](./dylib/README.md)

View File

@ -0,0 +1,19 @@
[package]
name = "dylib_virtual_display"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "staticlib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
cc = "1.0"
[dependencies]
thiserror = "1.0.30"
lazy_static = "1.4"
serde = "1.0"
serde_derive = "1.0"
hbb_common = { path = "../../hbb_common" }

View File

@ -0,0 +1,32 @@
# virtual display
Virtual display may be used on computers that do not have a monitor.
[Development reference](https://github.com/pavlobu/deskreen/discussions/86)
## windows
### win10
Win10 provides [Indirect Display Driver Model](https://msdn.microsoft.com/en-us/library/windows/hardware/mt761968(v=vs.85).aspx).
This lib uses [this project](https://github.com/fufesou/RustDeskIddDriver) as the driver.
**NOTE**: Versions before Win10 1607. Try follow [this method](https://github.com/fanxiushu/xdisp_virt/tree/master/indirect_display).
#### tested platforms
- [x] 19041
- [x] 19043
### win7
TODO
[WDDM](https://docs.microsoft.com/en-us/windows-hardware/drivers/display/windows-vista-display-driver-model-design-guide).
## X11
## OSX

View File

@ -13,7 +13,7 @@ fn build_c_impl() {
if build.get_compiler().is_like_msvc() {
build.define("WIN32", "");
build.flag("-Zi");
build.flag("-Z7");
build.flag("-GR-");
// build.flag("-std:c++11");
} else {
@ -24,7 +24,7 @@ fn build_c_impl() {
}
#[cfg(target_os = "windows")]
build.compile("xxx");
build.compile("win_virtual_display");
#[cfg(target_os = "windows")]
println!("cargo:rerun-if-changed=src/win10/IddController.c");

View File

@ -1,5 +1,5 @@
#[cfg(windows)]
use virtual_display::win10::{idd, DRIVER_INSTALL_PATH};
use dylib_virtual_display::win10::{idd, DRIVER_INSTALL_PATH};
#[cfg(windows)]
use std::{

View File

@ -0,0 +1,207 @@
#[cfg(windows)]
pub mod win10;
use hbb_common::{bail, lazy_static, ResultType};
use std::{path::Path, sync::Mutex};
lazy_static::lazy_static! {
// If device is uninstalled though "Device Manager" Window.
// Rustdesk is unable to handle device any more...
static ref H_SW_DEVICE: Mutex<u64> = Mutex::new(0);
static ref MONITOR_PLUGIN: Mutex<Vec<u32>> = Mutex::new(Vec::new());
}
#[no_mangle]
#[cfg(windows)]
pub fn get_dirver_install_path() -> &'static str {
win10::DRIVER_INSTALL_PATH
}
#[no_mangle]
pub fn download_driver() -> ResultType<()> {
#[cfg(windows)]
let _download_url = win10::DRIVER_DOWNLOAD_URL;
#[cfg(target_os = "linux")]
let _download_url = "";
// process download and report progress
Ok(())
}
#[no_mangle]
pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
#[cfg(windows)]
let install_path = win10::DRIVER_INSTALL_PATH;
#[cfg(not(windows))]
let install_path = "";
let abs_path = Path::new(install_path).canonicalize()?;
if !abs_path.exists() {
bail!("{} not exists", install_path)
}
#[cfg(windows)]
unsafe {
{
// Device must be created before install driver.
// https://github.com/fufesou/RustDeskIddDriver/issues/1
if let Err(e) = create_device() {
bail!("{}", e);
}
let full_install_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()
.encode_utf16()
.chain(Some(0).into_iter())
.collect();
let mut reboot_required_tmp = win10::idd::FALSE;
if win10::idd::InstallUpdate(full_install_path.as_ptr() as _, &mut reboot_required_tmp)
== win10::idd::FALSE
{
bail!("{}", win10::get_last_msg()?);
}
*_reboot_required = reboot_required_tmp == win10::idd::TRUE;
}
}
Ok(())
}
#[no_mangle]
pub fn uninstall_driver(_reboot_required: &mut bool) -> ResultType<()> {
#[cfg(windows)]
let install_path = win10::DRIVER_INSTALL_PATH;
#[cfg(not(windows))]
let install_path = "";
let abs_path = Path::new(install_path).canonicalize()?;
if !abs_path.exists() {
bail!("{} not exists", install_path)
}
#[cfg(windows)]
unsafe {
{
let full_install_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()
.encode_utf16()
.chain(Some(0).into_iter())
.collect();
let mut reboot_required_tmp = win10::idd::FALSE;
if win10::idd::Uninstall(full_install_path.as_ptr() as _, &mut reboot_required_tmp)
== win10::idd::FALSE
{
bail!("{}", win10::get_last_msg()?);
}
*_reboot_required = reboot_required_tmp == win10::idd::TRUE;
}
}
Ok(())
}
#[no_mangle]
pub fn is_device_created() -> bool {
#[cfg(windows)]
return *H_SW_DEVICE.lock().unwrap() != 0;
#[cfg(not(windows))]
return false;
}
#[no_mangle]
pub fn create_device() -> ResultType<()> {
if is_device_created() {
return Ok(());
}
#[cfg(windows)]
unsafe {
let mut lock_device = H_SW_DEVICE.lock().unwrap();
let mut h_sw_device = *lock_device as win10::idd::HSWDEVICE;
if win10::idd::DeviceCreate(&mut h_sw_device) == win10::idd::FALSE {
bail!("{}", win10::get_last_msg()?);
} else {
*lock_device = h_sw_device as u64;
}
}
Ok(())
}
#[no_mangle]
pub fn close_device() {
#[cfg(windows)]
unsafe {
win10::idd::DeviceClose(*H_SW_DEVICE.lock().unwrap() as win10::idd::HSWDEVICE);
*H_SW_DEVICE.lock().unwrap() = 0;
MONITOR_PLUGIN.lock().unwrap().clear();
}
}
#[no_mangle]
pub fn plug_in_monitor() -> ResultType<()> {
#[cfg(windows)]
unsafe {
let monitor_index = 0 as u32;
let mut plug_in_monitors = MONITOR_PLUGIN.lock().unwrap();
for i in 0..plug_in_monitors.len() {
if let Some(d) = plug_in_monitors.get(i) {
if *d == monitor_index {
return Ok(());
}
};
}
if win10::idd::MonitorPlugIn(monitor_index, 0, 30) == win10::idd::FALSE {
bail!("{}", win10::get_last_msg()?);
}
(*plug_in_monitors).push(monitor_index);
}
Ok(())
}
#[no_mangle]
pub fn plug_out_monitor() -> ResultType<()> {
#[cfg(windows)]
unsafe {
let monitor_index = 0 as u32;
if win10::idd::MonitorPlugOut(monitor_index) == win10::idd::FALSE {
bail!("{}", win10::get_last_msg()?);
}
let mut plug_in_monitors = MONITOR_PLUGIN.lock().unwrap();
for i in 0..plug_in_monitors.len() {
if let Some(d) = plug_in_monitors.get(i) {
if *d == monitor_index {
plug_in_monitors.remove(i);
break;
}
};
}
}
Ok(())
}
#[no_mangle]
pub fn update_monitor_modes() -> ResultType<()> {
#[cfg(windows)]
unsafe {
let monitor_index = 0 as u32;
let mut modes = vec![win10::idd::MonitorMode {
width: 1920,
height: 1080,
sync: 60,
}];
if win10::idd::FALSE
== win10::idd::MonitorModesUpdate(
monitor_index as win10::idd::UINT,
modes.len() as win10::idd::UINT,
modes.as_mut_ptr(),
)
{
bail!("{}", win10::get_last_msg()?);
}
}
Ok(())
}

View File

@ -1,192 +1,125 @@
#[cfg(windows)]
pub mod win10;
use hbb_common::{bail, ResultType};
use std::sync::{Arc, Mutex};
use hbb_common::{bail, lazy_static, ResultType};
use std::{path::Path, sync::Mutex};
const LIB_NAME_VIRTUAL_DISPLAY: &str = "dylib_virtual_display";
lazy_static::lazy_static! {
// If device is uninstalled though "Device Manager" Window.
// Rustdesk is unable to handle device any more...
static ref H_SW_DEVICE: Mutex<u64> = Mutex::new(0);
static ref MONITOR_PLUGIN: Mutex<Vec<u32>> = Mutex::new(Vec::new());
static ref LIB_VIRTUAL_DISPLAY: Arc<Mutex<Result<libloading::Library, libloading::Error>>> = {
Arc::new(Mutex::new(unsafe { libloading::Library::new(get_lib_name()) }))
};
}
pub fn download_driver() -> ResultType<()> {
#[cfg(windows)]
let _download_url = win10::DRIVER_DOWNLOAD_URL;
#[cfg(target_os = "linux")]
let _download_url = "";
// process download and report progress
Ok(())
#[cfg(target_os = "windows")]
fn get_lib_name() -> String {
format!("{}.dll", LIB_NAME_VIRTUAL_DISPLAY)
}
pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
#[cfg(windows)]
let install_path = win10::DRIVER_INSTALL_PATH;
#[cfg(not(windows))]
let install_path = "";
let abs_path = Path::new(install_path).canonicalize()?;
if !abs_path.exists() {
bail!("{} not exists", install_path)
}
#[cfg(windows)]
unsafe {
{
// Device must be created before install driver.
// https://github.com/fufesou/RustDeskIddDriver/issues/1
if let Err(e) = create_device() {
bail!("{}", e);
}
let full_install_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()
.encode_utf16()
.chain(Some(0).into_iter())
.collect();
let mut reboot_required_tmp = win10::idd::FALSE;
if win10::idd::InstallUpdate(full_install_path.as_ptr() as _, &mut reboot_required_tmp)
== win10::idd::FALSE
{
bail!("{}", win10::get_last_msg()?);
}
*_reboot_required = reboot_required_tmp == win10::idd::TRUE;
}
}
Ok(())
#[cfg(target_os = "linux")]
fn get_lib_name() -> String {
format!("lib{}.so", LIB_NAME_VIRTUAL_DISPLAY)
}
pub fn uninstall_driver(_reboot_required: &mut bool) -> ResultType<()> {
#[cfg(windows)]
let install_path = win10::DRIVER_INSTALL_PATH;
#[cfg(not(windows))]
let install_path = "";
#[cfg(target_os = "macos")]
fn get_lib_name() -> String {
format!("lib{}.dylib", LIB_NAME_VIRTUAL_DISPLAY)
}
let abs_path = Path::new(install_path).canonicalize()?;
if !abs_path.exists() {
bail!("{} not exists", install_path)
fn try_reload_lib() {
let mut lock = LIB_VIRTUAL_DISPLAY.lock().unwrap();
if lock.is_err() {
*lock = unsafe { libloading::Library::new(get_lib_name()) };
}
}
#[cfg(windows)]
unsafe {
{
let full_install_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()
.encode_utf16()
.chain(Some(0).into_iter())
.collect();
let mut reboot_required_tmp = win10::idd::FALSE;
if win10::idd::Uninstall(full_install_path.as_ptr() as _, &mut reboot_required_tmp)
== win10::idd::FALSE
{
bail!("{}", win10::get_last_msg()?);
#[cfg(windows)]
pub fn get_dirver_install_path() -> ResultType<&'static str> {
try_reload_lib();
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
Ok(lib) => unsafe {
match lib.get::<libloading::Symbol<fn() -> &'static str>>(b"get_dirver_install_path") {
Ok(func) => Ok(func()),
Err(e) => bail!("Failed to load func get_dirver_install_path, {}", e),
}
*_reboot_required = reboot_required_tmp == win10::idd::TRUE;
}
},
Err(e) => bail!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e),
}
Ok(())
}
pub fn is_device_created() -> bool {
#[cfg(windows)]
return *H_SW_DEVICE.lock().unwrap() != 0;
#[cfg(not(windows))]
return false;
}
pub fn create_device() -> ResultType<()> {
if is_device_created() {
return Ok(());
try_reload_lib();
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
Ok(lib) => unsafe {
match lib.get::<libloading::Symbol<fn() -> bool>>(b"is_device_created") {
Ok(func) => func(),
Err(..) => false,
}
},
Err(..) => false,
}
#[cfg(windows)]
unsafe {
let mut lock_device = H_SW_DEVICE.lock().unwrap();
let mut h_sw_device = *lock_device as win10::idd::HSWDEVICE;
if win10::idd::DeviceCreate(&mut h_sw_device) == win10::idd::FALSE {
bail!("{}", win10::get_last_msg()?);
} else {
*lock_device = h_sw_device as u64;
}
}
Ok(())
}
pub fn close_device() {
#[cfg(windows)]
unsafe {
win10::idd::DeviceClose(*H_SW_DEVICE.lock().unwrap() as win10::idd::HSWDEVICE);
*H_SW_DEVICE.lock().unwrap() = 0;
MONITOR_PLUGIN.lock().unwrap().clear();
try_reload_lib();
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
Ok(lib) => unsafe {
match lib.get::<libloading::Symbol<fn()>>(b"close_device") {
Ok(func) => func(),
Err(..) => {}
}
},
Err(..) => {}
}
}
pub fn plug_in_monitor() -> ResultType<()> {
#[cfg(windows)]
unsafe {
let monitor_index = 0 as u32;
let mut plug_in_monitors = MONITOR_PLUGIN.lock().unwrap();
for i in 0..plug_in_monitors.len() {
if let Some(d) = plug_in_monitors.get(i) {
if *d == monitor_index {
return Ok(());
}
};
macro_rules! def_func_result {
($func:ident, $name: tt) => {
pub fn $func() -> ResultType<()> {
try_reload_lib();
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
Ok(lib) => unsafe {
match lib.get::<libloading::Symbol<fn() -> ResultType<()>>>($name.as_bytes()) {
Ok(func) => func(),
Err(e) => bail!("Failed to load func {}, {}", $name, e),
}
},
Err(e) => bail!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e),
}
}
if win10::idd::MonitorPlugIn(monitor_index, 0, 30) == win10::idd::FALSE {
bail!("{}", win10::get_last_msg()?);
}
(*plug_in_monitors).push(monitor_index);
}
Ok(())
};
}
pub fn plug_out_monitor() -> ResultType<()> {
#[cfg(windows)]
unsafe {
let monitor_index = 0 as u32;
if win10::idd::MonitorPlugOut(monitor_index) == win10::idd::FALSE {
bail!("{}", win10::get_last_msg()?);
}
let mut plug_in_monitors = MONITOR_PLUGIN.lock().unwrap();
for i in 0..plug_in_monitors.len() {
if let Some(d) = plug_in_monitors.get(i) {
if *d == monitor_index {
plug_in_monitors.remove(i);
break;
}
};
}
pub fn install_update_driver(reboot_required: &mut bool) -> ResultType<()> {
try_reload_lib();
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
Ok(lib) => unsafe {
match lib.get::<libloading::Symbol<fn(&mut bool) -> ResultType<()>>>(
b"install_update_driver",
) {
Ok(func) => func(reboot_required),
Err(e) => bail!("Failed to load func install_update_driver, {}", e),
}
},
Err(e) => bail!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e),
}
Ok(())
}
pub fn update_monitor_modes() -> ResultType<()> {
#[cfg(windows)]
unsafe {
let monitor_index = 0 as u32;
let mut modes = vec![win10::idd::MonitorMode {
width: 1920,
height: 1080,
sync: 60,
}];
if win10::idd::FALSE
== win10::idd::MonitorModesUpdate(
monitor_index as win10::idd::UINT,
modes.len() as win10::idd::UINT,
modes.as_mut_ptr(),
)
{
bail!("{}", win10::get_last_msg()?);
}
pub fn uninstall_driver(reboot_required: &mut bool) -> ResultType<()> {
try_reload_lib();
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
Ok(lib) => unsafe {
match lib
.get::<libloading::Symbol<fn(&mut bool) -> ResultType<()>>>(b"uninstall_driver")
{
Ok(func) => func(reboot_required),
Err(e) => bail!("Failed to load func uninstall_driver, {}", e),
}
},
Err(e) => bail!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e),
}
Ok(())
}
def_func_result!(download_driver, "download_driver");
def_func_result!(create_device, "create_device");
def_func_result!(plug_in_monitor, "plug_in_monitor");
def_func_result!(plug_out_monitor, "plug_out_monitor");
def_func_result!(update_monitor_modes, "update_monitor_modes");

View File

@ -3,8 +3,8 @@ Version: 1.2.0
Release: 0
Summary: RPM package
License: GPL-3.0
Requires: gtk3 libxcb libxdo libXfixes pipewire alsa-lib curl libappindicator libvdpau1 libva2
Requires: gtk3 libxcb libxdo libXfixes pipewire alsa-lib curl libappindicator-gtk3 libvdpau libva
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit)
%description
The best open-source remote desktop client software, written in Rust.
@ -19,17 +19,14 @@ The best open-source remote desktop client software, written in Rust.
%install
mkdir -p "${buildroot}/usr/lib/rustdesk" && cp -r ${HBB}/flutter/build/linux/x64/release/bundle/* -t "${buildroot}/usr/lib/rustdesk"
mkdir -p "${buildroot}/usr/bin"
pushd ${buildroot} && ln -s /usr/lib/rustdesk/rustdesk usr/bin/rustdesk && popd
install -Dm 644 $HBB/res/rustdesk.service -t "${buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/rustdesk.desktop -t "${buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "${buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/128x128@2x.png "${buildroot}/usr/share/rustdesk/files/rustdesk.png"
mkdir -p "%{buildroot}/usr/lib/rustdesk" && cp -r ${HBB}/flutter/build/linux/x64/release/bundle/* -t "%{buildroot}/usr/lib/rustdesk"
mkdir -p "%{buildroot}/usr/bin"
install -Dm 644 $HBB/res/rustdesk.service -t "%{buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/rustdesk.desktop -t "%{buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "%{buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/128x128@2x.png "%{buildroot}/usr/share/rustdesk/files/rustdesk.png"
%files
/usr/bin/rustdesk
/usr/lib/rustdesk/*
/usr/share/rustdesk/files/rustdesk.service
/usr/share/rustdesk/files/rustdesk.png
@ -56,6 +53,7 @@ esac
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/
ln -s /usr/lib/rustdesk/rustdesk /usr/bin/rustdesk
systemctl daemon-reload
systemctl enable rustdesk
systemctl start rustdesk
@ -80,6 +78,7 @@ case "$1" in
# for uninstall
rm /usr/share/applications/rustdesk.desktop || true
rm /usr/share/applications/rustdesk-link.desktop || true
rm /usr/bin/rustdesk || true
update-desktop-database
;;
1)

View File

@ -6,6 +6,7 @@ use cpal::{
};
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
use sha2::{Digest, Sha256};
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
use std::sync::atomic::Ordering;
use std::{
collections::HashMap,
@ -973,6 +974,8 @@ impl LoginConfigHandler {
self.save_config(config);
}
//to-do: too many dup code below.
/// Save view style to the current config.
///
/// # Arguments
@ -984,6 +987,43 @@ impl LoginConfigHandler {
self.save_config(config);
}
/// Save scroll style to the current config.
///
/// # Arguments
///
/// * `value` - The view style to be saved.
pub fn save_scroll_style(&mut self, value: String) {
let mut config = self.load_config();
config.scroll_style = value;
self.save_config(config);
}
/// Set a ui config of flutter for handler's [`PeerConfig`].
///
/// # Arguments
///
/// * `k` - key of option
/// * `v` - value of option
pub fn save_ui_flutter(&mut self, k: String, v: String) {
let mut config = self.load_config();
config.ui_flutter.insert(k, v);
self.save_config(config);
}
/// Get a ui config of flutter for handler's [`PeerConfig`].
/// Return String if the option is found, otherwise return "".
///
/// # Arguments
///
/// * `k` - key of option
pub fn get_ui_flutter(&self, k: &str) -> String {
if let Some(v) = self.config.ui_flutter.get(k) {
v.clone()
} else {
"".to_owned()
}
}
/// Toggle an option in the handler.
///
/// # Arguments
@ -1100,6 +1140,9 @@ impl LoginConfigHandler {
msg.custom_image_quality = quality << 8;
n += 1;
}
if let Some(custom_fps) = self.options.get("custom-fps") {
msg.custom_fps = custom_fps.parse().unwrap_or(30);
}
if self.get_toggle_option("show-remote-cursor") {
msg.show_remote_cursor = BoolOption::Yes.into();
n += 1;
@ -1970,6 +2013,7 @@ fn decode_id_pk(signed: &[u8], key: &sign::PublicKey) -> ResultType<(String, [u8
}
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
pub fn disable_keyboard_listening() {
crate::ui_session_interface::KEYBOARD_HOOKED.store(true, Ordering::SeqCst);
}

View File

@ -20,17 +20,14 @@ use hbb_common::fs::{
use hbb_common::message_proto::permission_info::Permission;
use hbb_common::protobuf::Message as _;
use hbb_common::rendezvous_proto::ConnType;
#[cfg(windows)]
use hbb_common::tokio::sync::Mutex as TokioMutex;
use hbb_common::tokio::{
self,
sync::mpsc,
sync::Mutex as TokioMutex,
time::{self, Duration, Instant, Interval},
};
use hbb_common::{
allow_err,
message_proto::*,
sleep,
};
use hbb_common::{allow_err, message_proto::*, sleep};
use hbb_common::{fs, log, Stream};
use std::collections::HashMap;
@ -918,13 +915,8 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
Some(file_response::Union::Block(block)) => {
log::info!(
"file response block, file id:{}, file num: {}",
block.id,
block.file_num
);
if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) {
if let Err(_err) = job.write(block, None).await {
if let Err(_err) = job.write(block).await {
// to-do: add "skip" for writing job
}
self.update_jobs_status();
@ -997,23 +989,31 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
Some(misc::Union::Uac(uac)) => {
let msgtype = "custom-uac-nocancel";
let title = "Prompt";
let text = "Please wait for confirmation of UAC...";
let link = "";
if uac {
self.handler.msgbox(
"custom-uac-nocancel",
"Warning",
"uac_warning",
"",
);
self.handler.msgbox(msgtype, title, text, link);
} else {
self.handler
.cancel_msgbox(
&format!("{}-{}-{}-{}", msgtype, title, text, link,),
);
}
}
Some(misc::Union::ForegroundWindowElevated(elevated)) => {
let msgtype = "custom-elevated-foreground-nocancel";
let title = "Prompt";
let text = "elevated_foreground_window_tip";
let link = "";
if elevated {
self.handler.msgbox(
"custom-elevated-foreground-nocancel",
"Warning",
"elevated_foreground_window_warning",
"",
);
self.handler.msgbox(msgtype, title, text, link);
} else {
self.handler
.cancel_msgbox(
&format!("{}-{}-{}-{}", msgtype, title, text, link,),
);
}
}
_ => {}
@ -1187,7 +1187,7 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(windows)]
fn handle_cliprdr_msg(&self, clip: hbb_common::message_proto::Cliprdr) {
if !self.handler.lc.read().unwrap().disable_clipboard {
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[cfg(feature = "flutter")]
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
if self.client_conn_id
!= clipboard::get_client_conn_id(&crate::flutter::get_cur_session_id())

View File

@ -6,10 +6,12 @@ use std::{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use arboard::Clipboard as ClipboardContext;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::compress::decompress;
use hbb_common::{
allow_err,
anyhow::bail,
compress::{compress as compress_func, decompress},
compress::{compress as compress_func},
config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT},
get_version_number, log,
message_proto::*,
@ -26,7 +28,7 @@ pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future<Out
pub const CLIPBOARD_NAME: &'static str = "clipboard";
pub const CLIPBOARD_INTERVAL: u64 = 333;
// the executable name of the portable version
// the executable name of the portable version
pub const PORTABLE_APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME";
lazy_static::lazy_static! {
@ -564,7 +566,7 @@ pub fn is_ip(id: &str) -> bool {
}
pub fn is_setup(name: &str) -> bool {
name.to_lowercase().ends_with("install.exe") || name.to_lowercase().ends_with("安装.exe")
name.to_lowercase().ends_with("install.exe")
}
pub fn get_custom_rendezvous_server(custom: String) -> String {

View File

@ -1,8 +1,10 @@
use std::env::Args;
use hbb_common::log;
// shared by flutter and sciter main function
/// shared by flutter and sciter main function
///
/// [Note]
/// If it returns [`None`], then the process will terminate, and flutter gui will not be started.
/// If it returns [`Some`], then the process will continue, and flutter gui will be started.
pub fn core_main() -> Option<Vec<String>> {
// https://docs.rs/flexi_logger/latest/flexi_logger/error_info/index.html#write
// though async logger more efficient, but it also causes more problems, disable it for now
@ -10,14 +12,15 @@ pub fn core_main() -> Option<Vec<String>> {
let mut args = Vec::new();
let mut flutter_args = Vec::new();
let mut i = 0;
let mut is_setup = false;
let mut _is_elevate = false;
let mut _is_run_as_system = false;
let mut _is_quick_support = false;
let mut _is_flutter_connect = false;
let mut arg_exe = Default::default();
for arg in std::env::args() {
// to-do: how to pass to flutter?
if i == 0 && crate::common::is_setup(&arg) {
is_setup = true;
if i == 0 {
arg_exe = arg;
} else if i > 0 {
#[cfg(feature = "flutter")]
if arg == "--connect" {
@ -27,27 +30,27 @@ pub fn core_main() -> Option<Vec<String>> {
_is_elevate = true;
} else if arg == "--run-as-system" {
_is_run_as_system = true;
} else if arg == "--quick_support" {
_is_quick_support = true;
} else {
args.push(arg);
}
}
i += 1;
}
if args.contains(&"--install".to_string()) {
is_setup = true;
}
#[cfg(feature = "flutter")]
if _is_flutter_connect {
return core_main_invoke_new_connection(std::env::args());
}
if args.contains(&"--install".to_string()) {
is_setup = true;
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 is_setup {
if args.is_empty() {
args.push("--install".to_owned());
flutter_args.push("--install".to_string());
}
if click_setup {
args.push("--install".to_owned());
flutter_args.push("--install".to_string());
}
if args.contains(&"--noinstall".to_string()) {
args.clear();
@ -85,6 +88,22 @@ pub fn core_main() -> Option<Vec<String>> {
.ok();
}
}
#[cfg(windows)]
if !crate::platform::is_installed()
&& args.is_empty()
&& _is_quick_support
&& !_is_elevate
&& !_is_run_as_system
{
if let Err(e) = crate::portable_service::client::start_portable_service() {
log::error!("Failed to start portable service:{:?}", e);
}
}
#[cfg(windows)]
if !crate::platform::is_installed() && (_is_elevate || _is_run_as_system) {
crate::platform::elevate_or_run_as_system(click_setup, _is_elevate, _is_run_as_system);
return None;
}
if args.is_empty() {
std::thread::spawn(move || crate::start_server(false));
} else {
@ -131,7 +150,14 @@ pub fn core_main() -> Option<Vec<String>> {
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
return None;
} else if args[0] == "--tray" {
crate::tray::start_tray(crate::ui_interface::OPTIONS.clone());
crate::tray::start_tray();
return None;
} else if args[0] == "--portable-service" {
crate::platform::elevate_or_run_as_system(
click_setup,
_is_elevate,
_is_run_as_system,
);
return None;
}
}
@ -156,14 +182,21 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(target_os = "macos")]
{
std::thread::spawn(move || crate::start_server(true));
// to-do: for flutter, starting tray not ready yet, or we can reuse sciter's tray implementation.
crate::tray::make_tray();
return None;
}
#[cfg(all(target_os = "linux"))]
#[cfg(target_os = "linux")]
{
let handler = std::thread::spawn(move || crate::start_server(true));
crate::tray::start_tray(crate::ui_interface::OPTIONS.clone());
// revent server exit when encountering errors from tray
handler.join();
// Show the tray in linux only when current user is a normal user
// [Note]
// As for GNOME, the tray cannot be shown in user's status bar.
// As for KDE, the tray can be shown without user's theme.
if !crate::platform::is_root() {
crate::tray::start_tray();
}
// prevent server exit when encountering errors from tray
hbb_common::allow_err!(handler.join());
}
} else if args[0] == "--import-config" {
if args.len() == 2 {
@ -232,8 +265,10 @@ fn import_config(path: &str) {
///
/// [Note]
/// this is for invoke new connection from dbus.
/// If it returns [`None`], then the process will terminate, and flutter gui will not be started.
/// If it returns [`Some`], then the process will continue, and flutter gui will be started.
#[cfg(feature = "flutter")]
fn core_main_invoke_new_connection(mut args: Args) -> Option<Vec<String>> {
fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
args.position(|element| {
return element == "--connect";
})
@ -258,5 +293,19 @@ fn core_main_invoke_new_connection(mut args: Args) -> Option<Vec<String>> {
}
}
}
return None;
#[cfg(windows)]
{
use winapi::um::winuser::WM_USER;
let uni_links = format!("rustdesk://connection/new/{}", peer_id);
let res = crate::platform::send_message_to_hnwd(
"FLUTTER_RUNNER_WIN32_WINDOW",
"RustDesk",
(WM_USER + 2) as _, // refered from unilinks desktop pub
uni_links.as_str(),
true,
);
return if res { None } else { Some(Vec::new()) };
}
#[cfg(target_os = "macos")]
return Some(Vec::new());
}

View File

@ -54,6 +54,7 @@ pub extern "C" fn rustdesk_core_main(args_len: *mut c_int) -> *mut *mut c_char {
}
// https://gist.github.com/iskakaushik/1c5b8aa75c77479c33c4320913eebef6
#[cfg(windows)]
fn rust_args_to_c_args(args: Vec<String>, outlen: *mut c_int) -> *mut *mut c_char {
let mut v = vec![];
@ -227,7 +228,7 @@ impl InvokeUiSession for FlutterHandler {
id: i32,
entries: &Vec<FileEntry>,
path: String,
is_local: bool,
#[allow(unused_variables)] is_local: bool,
only_count: bool,
) {
// TODO opt
@ -325,6 +326,10 @@ impl InvokeUiSession for FlutterHandler {
);
}
fn cancel_msgbox(&self, tag: &str) {
self.push_event("cancel_msgbox", vec![("tag", tag)]);
}
fn new_message(&self, msg: String) {
self.push_event("chat_client_mode", vec![("text", &msg)]);
}
@ -404,7 +409,7 @@ pub fn session_start_(id: &str, event_stream: StreamSink<EventToUI>) -> ResultTy
*session.event_stream.write().unwrap() = Some(event_stream);
let session = session.clone();
std::thread::spawn(move || {
// if flutter : disable keyboard listen
// if flutter : disable keyboard listen
crate::client::disable_keyboard_listening();
io_loop(session);
});
@ -467,6 +472,10 @@ pub mod connection_manager {
fn change_language(&self) {
self.push_event("language", vec![]);
}
fn show_elevation(&self, show: bool) {
self.push_event("show_elevation", vec![("show", &show.to_string())]);
}
}
impl FlutterHandler {

View File

@ -16,15 +16,13 @@ use hbb_common::{
// use crate::hbbs_http::account::AuthResult;
use crate::flutter::{self, SESSIONS};
#[cfg(target_os = "android")]
use crate::start_server;
use crate::ui_interface::{self, *};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_session_interface::CUR_SESSION;
use crate::{
client::file_trait::FileManager,
flutter::{make_fd_to_json, session_add, session_start_},
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_session_interface::CUR_SESSION;
fn initialize(app_dir: &str) {
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
#[cfg(target_os = "android")]
@ -162,6 +160,56 @@ pub fn session_toggle_option(id: String, value: String) {
}
}
pub fn session_get_flutter_config(id: String, k: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_flutter_config(k))
} else {
None
}
}
pub fn session_set_flutter_config(id: String, k: String, v: String) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
session.save_flutter_config(k, v);
}
}
pub fn get_local_flutter_config(k: String) -> SyncReturn<String> {
SyncReturn(ui_interface::get_local_flutter_config(k))
}
pub fn set_local_flutter_config(k: String, v: String) {
ui_interface::set_local_flutter_config(k, v);
}
pub fn session_get_view_style(id: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_view_style())
} else {
None
}
}
pub fn session_set_view_style(id: String, value: String) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
session.save_view_style(value);
}
}
pub fn session_get_scroll_style(id: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_scroll_style())
} else {
None
}
}
pub fn session_set_scroll_style(id: String, value: String) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
session.save_scroll_style(value);
}
}
pub fn session_get_image_quality(id: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_image_quality())
@ -418,7 +466,7 @@ pub fn session_resume_job(id: String, act_id: i32, is_remote: bool) {
pub fn main_get_sound_inputs() -> Vec<String> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return get_sound_inputs();
#[cfg(any(target_os = "android", target_os = "linux"))]
#[cfg(any(target_os = "android", target_os = "ios"))]
vec![String::from("")]
}
@ -537,8 +585,8 @@ pub fn main_post_request(url: String, body: String, header: String) {
post_request(url, body, header)
}
pub fn main_get_local_option(key: String) -> String {
get_local_option(key)
pub fn main_get_local_option(key: String) -> SyncReturn<String> {
SyncReturn(get_local_option(key))
}
pub fn main_set_local_option(key: String, value: String) {
@ -570,6 +618,15 @@ pub fn main_set_peer_option_sync(id: String, key: String, value: String) -> Sync
SyncReturn(true)
}
pub fn main_set_peer_alias(id: String, alias: String) {
main_broadcast_message(&HashMap::from([
("name", "alias"),
("id", &id),
("alias", &alias),
]));
set_peer_option(id, "alias".to_owned(), alias)
}
pub fn main_forget_password(id: String) {
forget_password(id)
}
@ -811,7 +868,12 @@ pub fn main_is_root() -> bool {
}
pub fn get_double_click_time() -> SyncReturn<i32> {
SyncReturn(crate::platform::get_double_click_time() as _)
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
return SyncReturn(crate::platform::get_double_click_time() as _);
}
#[cfg(any(target_os = "android", target_os = "ios"))]
SyncReturn(500i32)
}
pub fn main_start_dbus_server() {
@ -943,6 +1005,11 @@ pub fn main_wol(id: String) {
crate::lan::send_wol(id)
}
pub fn main_create_shortcut(_id: String) {
#[cfg(windows)]
create_shortcut(_id);
}
pub fn cm_send_chat(conn_id: i32, msg: String) {
crate::ui_cm_interface::send_chat(conn_id, msg);
}
@ -975,6 +1042,14 @@ pub fn cm_switch_permission(conn_id: i32, name: String, enabled: bool) {
crate::ui_cm_interface::switch_permission(conn_id, name, enabled)
}
pub fn cm_can_elevate() -> SyncReturn<bool> {
SyncReturn(crate::ui_cm_interface::can_elevate())
}
pub fn cm_elevate_portable(conn_id: i32) {
crate::ui_cm_interface::elevate_portable(conn_id);
}
pub fn main_get_icon() -> String {
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
return ui_interface::get_icon();
@ -982,6 +1057,10 @@ pub fn main_get_icon() -> String {
return String::new();
}
pub fn main_get_build_date() -> String {
crate::BUILD_DATE.to_string()
}
#[no_mangle]
unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char {
let name = CStr::from_ptr(name);
@ -1021,7 +1100,7 @@ pub fn main_is_installed() -> SyncReturn<bool> {
SyncReturn(is_installed())
}
pub fn main_start_grab_keyboard(){
pub fn main_start_grab_keyboard() {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::ui_session_interface::global_grab_keyboard();
}
@ -1098,6 +1177,12 @@ pub fn main_account_auth_result() -> String {
account_auth_result()
}
pub fn main_on_main_window_close() {
// may called more than one times
#[cfg(windows)]
crate::portable_service::client::drop_portable_service_shared_memory();
}
#[cfg(target_os = "android")]
pub mod server_side {
use jni::{
@ -1106,7 +1191,7 @@ pub mod server_side {
JNIEnv,
};
use hbb_common::{config::Config, log};
use hbb_common::log;
use crate::start_server;

View File

@ -1,7 +1,7 @@
use super::HbbHttpResponse;
use hbb_common::{
config::{Config, LocalConfig},
log, sleep, tokio, ResultType,
log, ResultType,
};
use reqwest::blocking::Client;
use serde_derive::{Deserialize, Serialize};

View File

@ -130,6 +130,19 @@ pub enum DataControl {
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum DataPortableService {
Ping,
Pong,
ConnCount(Option<usize>),
Mouse(Vec<u8>),
Key(Vec<u8>),
RequestStart,
WillClose,
CmShowElevation(bool),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum Data {
@ -187,6 +200,7 @@ pub enum Data {
Language(String),
Empty,
Disconnected,
DataPortableService(DataPortableService),
}
#[tokio::main(flavor = "current_thread")]

View File

@ -24,6 +24,7 @@ mod vn;
mod kz;
mod ua;
mod fa;
mod ca;
lazy_static::lazy_static! {
pub static ref LANGS: Value =
@ -51,6 +52,7 @@ lazy_static::lazy_static! {
("kz", "Қазақ"),
("ua", "Українська"),
("fa", "فارسی"),
("ca", "Català"),
]);
}
@ -102,6 +104,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"kz" => kz::T.deref(),
"ua" => ua::T.deref(),
"fa" => fa::T.deref(),
"ca" => ca::T.deref(),
_ => en::T.deref(),
};
if let Some(v) = m.get(&name as &str) {

393
src/lang/ca.rs Normal file
View File

@ -0,0 +1,393 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Estat"),
("Your Desktop", "EL teu escriptori"),
("desk_tip", "Pots accedir al teu escriptori amb aquest ID i contrasenya."),
("Password", "Contrasenya"),
("Ready", "Llest"),
("Established", "Establert"),
("connecting_status", "Connexió a la xarxa RustDesk en progrés..."),
("Enable Service", "Habilitar Servei"),
("Start Service", "Iniciar Servei"),
("Service is running", "El servei s'està executant"),
("Service is not running", "El servei no s'està executant"),
("not_ready_status", "No està llest. Comprova la teva connexió"),
("Control Remote Desktop", "Controlar escriptori remot"),
("Transfer File", "Transferir arxiu"),
("Connect", "Connectar"),
("Recent Sessions", "Sessions recents"),
("Address Book", "Directori"),
("Confirmation", "Confirmació"),
("TCP Tunneling", "Túnel TCP"),
("Remove", "Eliminar"),
("Refresh random password", "Actualitzar contrasenya aleatòria"),
("Set your own password", "Estableix la teva pròpia contrasenya"),
("Enable Keyboard/Mouse", "Habilitar teclat/ratolí"),
("Enable Clipboard", "Habilitar portapapers"),
("Enable File Transfer", "Habilitar transferència d'arxius"),
("Enable TCP Tunneling", "Habilitar túnel TCP"),
("IP Whitelisting", "Direccions IP admeses"),
("ID/Relay Server", "Servidor ID/Relay"),
("Import Server Config", "Importar configuració de servidor"),
("Export Server Config", "Exportar configuració del servidor"),
("Import server configuration successfully", "Configuració de servidor importada amb èxit"),
("Export server configuration successfully", "Configuració de servidor exportada con èxit"),
("Invalid server configuration", "Configuració de servidor incorrecta"),
("Clipboard is empty", "El portapapers està buit"),
("Stop service", "Aturar servei"),
("Change ID", "Canviar ID"),
("Website", "Lloc web"),
("About", "Sobre"),
("Mute", "Silenciar"),
("Audio Input", "Entrada d'àudio"),
("Enhancements", "Millores"),
("Hardware Codec", "Còdec de hardware"),
("Adaptive Bitrate", "Tasa de bits adaptativa"),
("ID Server", "Servidor de IDs"),
("Relay Server", "Servidor Relay"),
("API Server", "Servidor API"),
("invalid_http", "ha de començar amb http:// o https://"),
("Invalid IP", "IP incorrecta"),
("id_change_tip", "Només pots utilitzar caràcters a-z, A-Z, 0-9 e _ (guionet baix). El primer caràcter ha de ser a-z o A-Z. La longitut ha d'estar entre 6 i 16 caràcters."),
("Invalid format", "Format incorrecte"),
("server_not_support", "Encara no és compatible amb el servidor"),
("Not available", "No disponible"),
("Too frequent", "Massa comú"),
("Cancel", "Cancel·lar"),
("Skip", "Saltar"),
("Close", "Tancar"),
("Retry", "Reintentar"),
("OK", ""),
("Password Required", "Es necessita la contrasenya"),
("Please enter your password", "Si us plau, introdueixi la seva contrasenya"),
("Remember password", "Recordar contrasenya"),
("Wrong Password", "Contrasenya incorrecta"),
("Do you want to enter again?", "Vol tornar a entrar?"),
("Connection Error", "Error de connexió"),
("Error", ""),
("Reset by the peer", "Reestablert pel peer"),
("Connecting...", "Connectant..."),
("Connection in progress. Please wait.", "Connexió en procés. Esperi."),
("Please try 1 minute later", "Torni a provar-ho d'aquí un minut"),
("Login Error", "Error d'inicio de sessió"),
("Successful", "Exitós"),
("Connected, waiting for image...", "Connectant, esperant imatge..."),
("Name", "Nom"),
("Type", "Tipus"),
("Modified", "Modificat"),
("Size", "Grandària"),
("Show Hidden Files", "Mostrar arxius ocults"),
("Receive", "Rebre"),
("Send", "Enviar"),
("Refresh File", "Actualitzar arxiu"),
("Local", ""),
("Remote", "Remot"),
("Remote Computer", "Ordinador remot"),
("Local Computer", "Ordinador local"),
("Confirm Delete", "Confirma eliminació"),
("Delete", "Eliminar"),
("Properties", "Propietats"),
("Multi Select", "Selecció múltiple"),
("Select All", "Selecciona-ho Tot"),
("Unselect All", "Deselecciona-ho Tot"),
("Empty Directory", "Directori buit"),
("Not an empty directory", "No és un directori buit"),
("Are you sure you want to delete this file?", "Estàs segur que vols eliminar aquest arxiu?"),
("Are you sure you want to delete this empty directory?", "Estàs segur que vols eliminar aquest directori buit?"),
("Are you sure you want to delete the file of this directory?", "Estàs segur que vols eliminar aquest arxiu d'aquest directori?"),
("Do this for all conflicts", "Fes això per a tots els conflictes"),
("This is irreversible!", "Això és irreversible!"),
("Deleting", "Eliminant"),
("files", "arxius"),
("Waiting", "Esperant"),
("Finished", "Acabat"),
("Speed", "Velocitat"),
("Custom Image Quality", "Qualitat d'imatge personalitzada"),
("Privacy mode", "Mode privat"),
("Block user input", "Bloquejar entrada d'usuari"),
("Unblock user input", "Desbloquejar entrada d'usuari"),
("Adjust Window", "Ajustar finestra"),
("Original", "Original"),
("Shrink", "Reduir"),
("Stretch", "Estirar"),
("Scrollbar", "Barra de desplaçament"),
("ScrollAuto", "Desplaçament automàtico"),
("Good image quality", "Bona qualitat d'imatge"),
("Balanced", "Equilibrat"),
("Optimize reaction time", "Optimitzar el temps de reacció"),
("Custom", "Personalitzat"),
("Show remote cursor", "Mostrar cursor remot"),
("Show quality monitor", "Mostrar qualitat del monitor"),
("Disable clipboard", "Deshabilitar portapapers"),
("Lock after session end", "Bloquejar després del final de la sessió"),
("Insert", "Inserir"),
("Insert Lock", "Inserir bloqueig"),
("Refresh", "Actualitzar"),
("ID does not exist", "L'ID no existeix"),
("Failed to connect to rendezvous server", "No es pot connectar al servidor rendezvous"),
("Please try later", "Siusplau provi-ho més tard"),
("Remote desktop is offline", "L'escriptori remot està desconecctat"),
("Key mismatch", "La clau no coincideix"),
("Timeout", "Temps esgotat"),
("Failed to connect to relay server", "No es pot connectar al servidor de relay"),
("Failed to connect via rendezvous server", "No es pot connectar a través del servidor de rendezvous"),
("Failed to connect via relay server", "No es pot connectar a través del servidor de relay"),
("Failed to make direct connection to remote desktop", "No s'ha pogut establir una connexió directa amb l'escriptori remot"),
("Set Password", "Configurar la contrasenya"),
("OS Password", "contrasenya del sistema operatiu"),
("install_tip", ""),
("Click to upgrade", "Clicar per actualitzar"),
("Click to download", "Clicar per descarregar"),
("Click to update", "Clicar per refrescar"),
("Configure", "Configurar"),
("config_acc", ""),
("config_screen", ""),
("Installing ...", "Instal·lant ..."),
("Install", "Instal·lar"),
("Installation", "Instal·lació"),
("Installation Path", "Ruta d'instal·lació"),
("Create start menu shortcuts", "Crear accessos directes al menú d'inici"),
("Create desktop icon", "Crear icona d'escriptori"),
("agreement_tip", ""),
("Accept and Install", "Acceptar i instal·lar"),
("End-user license agreement", "Acord de llicència d'usuario final"),
("Generating ...", "Generant ..."),
("Your installation is lower version.", "La seva instal·lació és una versión inferior."),
("not_close_tcp_tip", ""),
("Listening ...", "Escoltant..."),
("Remote Host", "Hoste remot"),
("Remote Port", "Port remot"),
("Action", "Acció"),
("Add", "Afegirr"),
("Local Port", "Port local"),
("Local Address", "Adreça Local"),
("Change Local Port", "Canviar Port Local"),
("setup_server_tip", ""),
("Too short, at least 6 characters.", "Massa curt, almenys 6 caràcters."),
("The confirmation is not identical.", "La confirmación no coincideix."),
("Permissions", "Permisos"),
("Accept", "Acceptar"),
("Dismiss", "Cancel·lar"),
("Disconnect", "Desconnectar"),
("Allow using keyboard and mouse", "Permetre l'ús del teclat i ratolí"),
("Allow using clipboard", "Permetre usar portapapers"),
("Allow hearing sound", "Permetre escoltar so"),
("Allow file copy and paste", "Permetre copiar i enganxar arxius"),
("Connected", "Connectat"),
("Direct and encrypted connection", "Connexió directa i xifrada"),
("Relayed and encrypted connection", "connexió retransmesa i xifrada"),
("Direct and unencrypted connection", "connexió directa i sense xifrar"),
("Relayed and unencrypted connection", "connexió retransmesa i sense xifrar"),
("Enter Remote ID", "Introduixi l'ID remot"),
("Enter your password", "Introdueixi la seva contrasenya"),
("Logging in...", "Iniciant sessió..."),
("Enable RDP session sharing", "Habilitar l'ús compartit de sessions RDP"),
("Auto Login", "Inici de sessió automàtic"),
("Enable Direct IP Access", "Habilitar accés IP directe"),
("Rename", "Renombrar"),
("Space", "Espai"),
("Create Desktop Shortcut", "Crear accés directe a l'escriptori"),
("Change Path", "Cnviar ruta"),
("Create Folder", "Crear carpeta"),
("Please enter the folder name", "Indiqui el nom de la carpeta"),
("Fix it", "Soluciona-ho"),
("Warning", "Avís"),
("Login screen using Wayland is not supported", "La pantalla d'inici de sessió amb Wayland no és compatible"),
("Reboot required", "Cal reiniciar"),
("Unsupported display server ", "Servidor de visualització no compatible"),
("x11 expected", "x11 necessari"),
("Port", ""),
("Settings", "Ajustaments"),
("Username", " Nom d'usuari"),
("Invalid port", "Port incorrecte"),
("Closed manually by the peer", "Tancat manualment pel peer"),
("Enable remote configuration modification", "Habilitar modificació remota de configuració"),
("Run without install", "Executar sense instal·lar"),
("Always connected via relay", "Connectat sempre a través de relay"),
("Always connect via relay", "Connecta sempre a través de relay"),
("whitelist_tip", ""),
("Login", "Inicia sessió"),
("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"),
("Unselect all tags", "Deseleccionar tots els tags"),
("Network error", "Error de xarxa"),
("Username missed", "Nom d'usuari oblidat"),
("Password missed", "Contrasenya oblidada"),
("Wrong credentials", "Credencials incorrectes"),
("Edit Tag", "Editar tag"),
("Unremember Password", "Contrasenya oblidada"),
("Favorites", "Preferits"),
("Add to Favorites", "Afegir a preferits"),
("Remove from Favorites", "Treure de preferits"),
("Empty", "Buit"),
("Invalid folder name", "Nom de carpeta incorrecte"),
("Socks5 Proxy", "Proxy Socks5"),
("Hostname", ""),
("Discovered", "Descobert"),
("install_daemon_tip", ""),
("Remote ID", "ID remot"),
("Paste", "Enganxar"),
("Paste here?", "Enganxar aquí?"),
("Are you sure to close the connection?", "Estàs segur que vols tancar la connexió?"),
("Download new version", "Descarregar nova versió"),
("Touch mode", "Mode tàctil"),
("Mouse mode", "Mode ratolí"),
("One-Finger Tap", "Toqui amb un dit"),
("Left Mouse", "Ratolí esquerra"),
("One-Long Tap", "Toc llarg"),
("Two-Finger Tap", "Toqui amb dos dits"),
("Right Mouse", "Botó dret"),
("One-Finger Move", "Moviment amb un dir"),
("Double Tap & Move", "Toqui dos cops i mogui"),
("Mouse Drag", "Arrastri amb el ratolí"),
("Three-Finger vertically", "Tres dits verticalment"),
("Mouse Wheel", "Roda del ratolí"),
("Two-Finger Move", "Moviment amb dos dits"),
("Canvas Move", "Moviment del llenç"),
("Pinch to Zoom", "Pessiga per fer zoom"),
("Canvas Zoom", "Ampliar llenç"),
("Reset canvas", "Reestablir llenç"),
("No permission of file transfer", "No tens permís de transferència de fitxers"),
("Note", "Nota"),
("Connection", "connexió"),
("Share Screen", "Compartir pantalla"),
("CLOSE", "TANCAR"),
("OPEN", "OBRIR"),
("Chat", "Xat"),
("Total", "Total"),
("items", "ítems"),
("Selected", "Seleccionat"),
("Screen Capture", "Captura de pantalla"),
("Input Control", "Control d'entrada"),
("Audio Capture", "Captura d'àudio"),
("File Connection", "connexió d'arxius"),
("Screen Connection", "connexió de pantalla"),
("Do you accept?", "Acceptes?"),
("Open System Setting", "Configuració del sistema obert"),
("How to get Android input permission?", "Com obtenir el permís d'entrada d'Android?"),
("android_input_permission_tip1", "Per a que un dispositiu remot controli el seu dispositiu Android amb el ratolí o tocs, cal permetre que RustDesk utilitzi el servei d' \"Accesibilitat\"."),
("android_input_permission_tip2", "Vagi a la pàgina de [Serveis instal·lats], activi el servici [RustDesk Input]."),
("android_new_connection_tip", "S'ha rebut una nova sol·licitud de control per al dispositiu actual."),
("android_service_will_start_tip", "Habilitar la captura de pantalla iniciarà el servei automàticament, i permetrà que altres dispositius sol·licitin una connexió des d'aquest dispositiu."),
("android_stop_service_tip", "Tancar el servei tancarà totes les connexions establertes."),
("android_version_audio_tip", "La versión actual de Android no admet la captura d'àudio, actualizi a Android 10 o superior."),
("android_start_service_tip", "Toqui el permís [Iniciar servei] o OBRIR [Captura de pantalla] per iniciar el servei d'ús compartit de pantalla."),
("Account", "Compte"),
("Overwrite", "Sobreescriure"),
("This file exists, skip or overwrite this file?", "Aquest arxiu ja existeix, ometre o sobreescriure l'arxiu?"),
("Quit", "Sortir"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Ajuda"),
("Failed", "Ha fallat"),
("Succeeded", "Aconseguit"),
("Someone turns on privacy mode, exit", "Algú ha activat el mode de privacitat, surti"),
("Unsupported", "No suportat"),
("Peer denied", "Peer denegat"),
("Please install plugins", "Instal·li complements"),
("Peer exit", "El peer ha sortit"),
("Failed to turn off", "Error en apagar"),
("Turned off", "Apagat"),
("In privacy mode", "En mode de privacitat"),
("Out privacy mode", "Fora del mode de privacitat"),
("Language", "Idioma"),
("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"),
("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", "Connexió no disponible"),
("Legacy mode", "Mode heretat"),
("Map mode", "Mode mapa"),
("Translate mode", "Mode traduit"),
("Use temporary password", "Utilitzar contrasenya temporal"),
("Use permanent password", "Utilitzar contrasenya permament"),
("Use both passwords", "Utilitzar ambdues contrasenyas"),
("Set permanent password", "Establir contrasenya permament"),
("Set temporary password length", "Establir llargada de la contrasenya temporal"),
("Enable Remote Restart", "Activar reinici remot"),
("Allow remote restart", "Permetre reinici remot"),
("Restart Remote Device", "Reiniciar dispositiu"),
("Are you sure you want to restart", "Està segur que vol reiniciar?"),
("Restarting Remote Device", "Reiniciant dispositiu remot"),
("remote_restarting_tip", "Dispositiu remot reiniciant, tanqui aquest missatge i tornis a connectar amb la contrasenya."),
("Copied", "Copiat"),
("Exit Fullscreen", "Sortir de la pantalla completa"),
("Fullscreen", "Pantalla completa"),
("Mobile Actions", "Accions mòbils"),
("Select Monitor", "Seleccionar monitor"),
("Control Actions", "Accions de control"),
("Display Settings", "Configuració de pantalla"),
("Ratio", "Relació"),
("Image Quality", "Qualitat d'imatge"),
("Scroll Style", "Estil de desplaçament"),
("Show Menubar", "Mostra barra de menú"),
("Hide Menubar", "Amaga barra de menú"),
("Direct Connection", "Connexió directa"),
("Relay Connection", "Connexió Relay"),
("Secure Connection", "Connexió segura"),
("Insecure Connection", "Connexió insegura"),
("Scale original", "Escala original"),
("Scale adaptive", "Escala adaptativa"),
("General", ""),
("Security", "Seguritat"),
("Account", "Compte"),
("Theme", "Tema"),
("Dark Theme", "Tema Fosc"),
("Dark", "Fosc"),
("Light", "Clar"),
("Follow System", "Tema del sistema"),
("Enable hardware codec", "Habilitar còdec per hardware"),
("Unlock Security Settings", "Desbloquejar ajustaments de seguritat"),
("Enable Audio", "Habilitar àudio"),
("Temporary Password Length", "Longitut de Contrasenya Temporal"),
("Unlock Network Settings", "Desbloquejar Ajustaments de Xarxa"),
("Server", "Servidor"),
("Direct IP Access", "Accés IP Directe"),
("Proxy", ""),
("Port", ""),
("Apply", "Aplicar"),
("Disconnect all devices?", "Desconnectar tots els dispositius?"),
("Clear", "Netejar"),
("Audio Input Device", "Dispositiu d'entrada d'àudio"),
("Deny remote access", "Denegar accés remot"),
("Use IP Whitelisting", "Utilitza llista de IPs admeses"),
("Network", "Xarxa"),
("Enable RDP", "Habilitar RDP"),
("Pin menubar", "Bloqueja barra de menú"),
("Unpin menubar", "Desbloquejar barra de menú"),
("Recording", "Gravant"),
("Directory", "Directori"),
("Automatically record incoming sessions", "Gravació automàtica de sessions entrants"),
("Change", "Canviar"),
("Start session recording", "Començar gravació de sessió"),
("Stop session recording", "Aturar gravació de sessió"),
("Enable Recording Session", "Habilitar gravació de sessió"),
("Allow recording session", "Permetre gravació de sessió"),
("Enable LAN Discovery", "Habilitar descobriment de LAN"),
("Deny LAN Discovery", "Denegar descobriment de LAN"),
("Write a message", "Escriure un missatge"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Disconnected", "Desconnectat"),
("Other", "Altre"),
("Confirm before closing multiple tabs", "Confirmar abans de tancar múltiples pestanyes"),
("Keyboard Settings", "Ajustaments de teclat"),
("Custom", "Personalitzat"),
("Full Access", "Acces complet"),
("Screen Share", "Compartir pantalla"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland requereix Ubuntu 21.04 o una versió superior."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland requereix una versió superior de la distribución de Linux. Provi l'escriptori X11 o canvïi el seu sistema operatiu."),
("JumpLink", "Veure"),
("Please Select the screen to be shared(Operate on the peer side).", "Seleccioni la pantalla que es compartirà (Operar al costat del peer)."),
("Show RustDesk", "Mostrar RustDesk"),
("This PC", "Aquest PC"),
("or", "o"),
("Continue with", "Continuar amb"),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "拒绝局域网发现"),
("Write a message", "输入聊天消息"),
("Prompt", "提示"),
("elevation_prompt", "以当前用户权限运行软件,可能导致远端在访问本机时,没有足够的权限来操作部分窗口。"),
("uac_warning", "暂时无法访问远端设备因为远端设备正在请求用户账户权限请等待对方关闭UAC窗口。为避免这个问题建议在远端设备上安装或者以管理员权限运行本软件。"),
("elevated_foreground_window_warning", "暂时无法使用鼠标键盘,因为远端桌面的当前窗口需要更高的权限才能操作, 可以请求对方最小化当前窗口。为避免这个问题,建议在远端设备上安装或者以管理员权限运行本软件。"),
("Please wait for confirmation of UAC...", "请等待对方确认UAC..."),
("elevated_foreground_window_tip", "远端桌面的当前窗口需要更高的权限才能操作, 暂时无法使用鼠标键盘, 可以请求对方最小化当前窗口, 或者在连接管理窗口点击提升。为避免这个问题,建议在远端设备上安装本软件。"),
("Disconnected", "会话已结束"),
("Other", "其他"),
("Confirm before closing multiple tabs", "关闭多个标签页时向您确认"),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", "此电脑"),
("or", ""),
("Continue with", "使用"),
("Elevate", "提权"),
("Zoom cursor", "缩放鼠标"),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Afvis LAN Discovery"),
("Write a message", "Skriv en besked"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "Afbrudt"),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "LAN-Erkennung verbieten"),
("Write a message", "Nachricht schreiben"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -30,9 +30,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_open_battery_optimizations_tip", "If you want to disable this feature, please go to the next RustDesk application settings page, find and enter [Battery], Uncheck [Unrestricted]"),
("remote_restarting_tip", "Remote device is restarting, please close this message box and reconnect with permanent password after a while"),
("Are you sure to close the connection?", "Are you sure you want to close the connection?"),
("elevation_prompt", "Running software without privilege elevation may cause problems when remote users operate certain windows."),
("uac_warning", "Temporarily denied access due to elevation request, please wait for the remote user to accept the UAC dialog. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."),
("elevated_foreground_window_warning", "Temporarily unable to use the mouse and keyboard, because the current window of the remote desktop requires higher privilege to operate, you can request the remote user to minimize the current window. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."),
("elevated_foreground_window_tip", "The current window of the remote desktop requires higher privilege to operate, so it's unable to use the mouse and keyboard temporarily. You can request the remote user to minimize the current window, or click elevation button on the connection management window. To avoid this problem, it is recommended to install the software on the remote device."),
("JumpLink", "View"),
("Stop service", "Stop Service"),
("or", ""),

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -305,7 +305,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Translate mode", "Modo traducido"),
("Use temporary password", "Usar contraseña temporal"),
("Use permanent password", "Usar contraseña permamente"),
("Use both passwords", "Usar ambas comtraseñas"),
("Use both passwords", "Usar ambas contraseñas"),
("Set permanent password", "Establecer contraseña permamente"),
("Set temporary password length", "Establecer largo de contraseña temporal"),
("Enable Remote Restart", "Activar reinicio remoto"),
@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Denegar descubrimiento de LAN"),
("Write a message", "Escribir un mensaje"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "Desconectado"),
("Other", "Otro"),
("Confirm before closing multiple tabs", "Confirmar antes de cerrar múltiples pestañas"),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", "Este PC"),
("or", "o"),
("Continue with", "Continuar con"),
("Elevate", "Elevar"),
("Zoom cursor", "Ampliar cursor"),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "غیر فعالسازی جستجو در شبکه"),
("Write a message", "یک پیام بنویسید"),
("Prompt", ""),
("elevation_prompt", "اجرای نرم‌افزار بدون افزایش امتیاز می‌تواند باعث ایجاد مشکلاتی در هنگام کار کردن کاربران راه دور با ویندوزهای خاص شود"),
("uac_warning", "به دلیل درخواست دسترسی سطح بالا، به طور موقت از دسترسی رد شد. منتظر بمانید تا کاربر راه دور گفتگوی UAC را بپذیرد. برای جلوگیری از این مشکل، توصیه می شود نرم افزار را روی دستگاه از راه دور نصب کنید یا آن را با دسترسی مدیر اجرا کنید."),
("elevated_foreground_window_warning", "به طور موقت استفاده از ماوس و صفحه کلید امکان پذیر نیست زیرا پنجره دسکتاپ از راه دور فعلی برای کار کردن به دسترسی های بالاتر نیاز دارد، می توانید از کاربر راه دور بخواهید که پنجره فعلی را به حداقل برساند. برای جلوگیری از این مشکل، توصیه می شود نرم افزار را روی یک دستگاه راه دور نصب کنید یا آن را با دسترسی مدیر اجرا کنید"),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "قطع ارتباط"),
("Other", "دیگر"),
("Confirm before closing multiple tabs", "بستن چندین برگه را تأیید کنید"),
@ -389,5 +388,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", "This PC"),
("or", "یا"),
("Continue with", "ادامه با"),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -19,20 +19,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Recent Sessions", "Sessions récentes"),
("Address Book", "Carnet d'adresses"),
("Confirmation", "Confirmation"),
("TCP Tunneling", "Tunneling TCP"),
("TCP Tunneling", "Tunnel TCP"),
("Remove", "Supprimer"),
("Refresh random password", "Actualiser le mot de passe aléatoire"),
("Set your own password", "Définir votre propre mot de passe"),
("Enable Keyboard/Mouse", "Activer le contrôle clavier/souris"),
("Enable Clipboard", "Activer la synchronisation du presse-papier"),
("Enable File Transfer", "Activer le transfert de fichiers"),
("Enable TCP Tunneling", "Activer le tunneling TCP"),
("Enable TCP Tunneling", "Activer le tunnel TCP"),
("IP Whitelisting", "Liste blanche IP"),
("ID/Relay Server", "ID/Serveur Relais"),
("Import Server Config", "Importer la configuration du serveur"),
("Export Server Config", ""),
("Export Server Config", "Exporter la configuration du serveur"),
("Import server configuration successfully", "Configuration du serveur importée avec succès"),
("Export server configuration successfully", ""),
("Export server configuration successfully", "Configuration du serveur exportée avec succès"),
("Invalid server configuration", "Configuration du serveur non valide"),
("Clipboard is empty", "Presse-papier vide"),
("Stop service", "Arrêter le service"),
@ -41,9 +41,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("About", "À propos de"),
("Mute", "Muet"),
("Audio Input", "Entrée audio"),
("Enhancements", ""),
("Hardware Codec", ""),
("Adaptive Bitrate", ""),
("Enhancements", "Améliorations"),
("Hardware Codec", "Transcodage matériel"),
("Adaptive Bitrate", "Débit adaptatif"),
("ID Server", "Serveur ID"),
("Relay Server", "Serveur relais"),
("API Server", "Serveur API"),
@ -74,7 +74,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Successful", "Succès"),
("Connected, waiting for image...", "Connecté, en attente de transmission d'image..."),
("Name", "Nom"),
("Type", "Taper"),
("Type", "Type"),
("Modified", "Modifié"),
("Size", "Taille"),
("Show Hidden Files", "Afficher les fichiers cachés"),
@ -89,8 +89,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Delete", "Supprimer"),
("Properties", "Propriétés"),
("Multi Select", "Choix multiple"),
("Select All", ""),
("Unselect All", ""),
("Select All", "Tout sélectionner"),
("Unselect All", "Tout déselectionner"),
("Empty Directory", "Répertoire vide"),
("Not an empty directory", "Pas un répertoire vide"),
("Are you sure you want to delete this file?", "Voulez-vous vraiment supprimer ce fichier?"),
@ -116,9 +116,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Good image quality", "Bonne qualité d'image"),
("Balanced", "Qualité d'image normale"),
("Optimize reaction time", "Optimiser le temps de réaction"),
("Custom", ""),
("Custom", "Personnalisé"),
("Show remote cursor", "Afficher le curseur distant"),
("Show quality monitor", ""),
("Show quality monitor", "Afficher le moniteur de qualité"),
("Disable clipboard", "Désactiver le presse-papier"),
("Lock after session end", "Verrouiller l'ordinateur distant après la déconnexion"),
("Insert", "Insérer"),
@ -137,9 +137,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Set Password", "Définir le mot de passe"),
("OS Password", "Mot de passe du système d'exploitation"),
("install_tip", "Vous utilisez une version non installée. En raison des restrictions UAC, en tant que terminal contrôlé, dans certains cas, il ne sera pas en mesure de contrôler la souris et le clavier ou d'enregistrer l'écran. Veuillez cliquer sur le bouton ci-dessous pour installer RustDesk au système pour éviter la question ci-dessus."),
("Click to upgrade", "Cliquez pour mettre à niveau"),
("Click to download", "Cliquez pour télécharger"),
("Click to update", "Cliquez pour mettre à jour"),
("Click to upgrade", "Cliquer pour mettre à niveau"),
("Click to download", "Cliquer pour télécharger"),
("Click to update", "Cliquer pour mettre à jour"),
("Configure", "Configurer"),
("config_acc", "Afin de pouvoir contrôler votre bureau à distance, veuillez donner l'autorisation \"accessibilité\" à RustDesk."),
("config_screen", "Afin de pouvoir accéder à votre bureau à distance, veuillez donner à RustDesk l'autorisation \"enregistrement d'écran\"."),
@ -161,8 +161,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Action", "Action"),
("Add", "Ajouter"),
("Local Port", "Port local"),
("Local Address", ""),
("Change Local Port", ""),
("Local Address", "Adresse locale"),
("Change Local Port", "Changer le port local"),
("setup_server_tip", "Si vous avez besoin d'une vitesse de connexion plus rapide, vous pouvez choisir de créer votre propre serveur"),
("Too short, at least 6 characters.", "Trop court, au moins 6 caractères."),
("The confirmation is not identical.", "Les deux entrées ne correspondent pas"),
@ -179,8 +179,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relayed and encrypted connection", "Connexion relais chiffrée"),
("Direct and unencrypted connection", "Connexion directe non chiffrée"),
("Relayed and unencrypted connection", "Connexion relais non chiffrée"),
("Enter Remote ID", "Entrez l'ID de l'appareil à distance"),
("Enter your password", "Entrez votre mot de passe"),
("Enter Remote ID", "Entrer l'ID de l'appareil à distance"),
("Enter your password", "Entrer votre mot de passe"),
("Logging in...", "Se connecter..."),
("Enable RDP session sharing", "Activer le partage de session RDP"),
("Auto Login", "Connexion automatique (le verrouillage ne sera effectif qu'après la désactivation du premier paramètre)"),
@ -191,12 +191,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Change Path", "Changer de chemin"),
("Create Folder", "Créer un dossier"),
("Please enter the folder name", "Veuillez saisir le nom du dossier"),
("Fix it", "Réparez-le"),
("Fix it", "Réparer"),
("Warning", "Avertissement"),
("Login screen using Wayland is not supported", "L'écran de connexion utilisant Wayland n'est pas pris en charge"),
("Reboot required", "Redémarrage pour prendre effet"),
("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"),
("x11 expected", "Veuillez passer à x11"),
("x11 expected", "x11 requis"),
("Port", "Port"),
("Settings", "Paramètres"),
("Username", " Nom d'utilisateur"),
@ -230,7 +230,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Socks5 Proxy", "Socks5 Agents"),
("Hostname", "Nom d'hôte"),
("Discovered", "Découvert"),
("install_daemon_tip", "Pour démarrer au démarrage, vous devez installer le service système."),
("install_daemon_tip", "Pour une exécution au démarrage du système, vous devez installer le service système."),
("Remote ID", "ID de l'appareil à distance"),
("Paste", "Coller"),
("Paste here?", "Coller ici ?"),
@ -239,8 +239,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Touch mode", "Mode tactile"),
("Mouse mode", "Mode souris"),
("One-Finger Tap", "Tapez d'un doigt"),
("Left Mouse", "Souris gauche"),
("One-Long Tap", "Un long robinet"),
("Left Mouse", "Bouton gauche de la souris"),
("One-Long Tap", "Un touché long"),
("Two-Finger Tap", "Tapez à deux doigts"),
("Right Mouse", "Bouton droit de la souris"),
("One-Finger Move", "Mouvement à un doigt"),
@ -249,10 +249,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Three-Finger vertically", "Trois doigts verticalement"),
("Mouse Wheel", "Roulette de la souris"),
("Two-Finger Move", "Mouvement à deux doigts"),
("Canvas Move", "Déplacement de la toile"),
("Canvas Move", "Déplacer la vue"),
("Pinch to Zoom", "Pincer pour zoomer"),
("Canvas Zoom", "Zoom sur la toile"),
("Reset canvas", "Réinitialiser le canevas"),
("Canvas Zoom", "Zoom sur la vue"),
("Reset canvas", "Réinitialiser la vue"),
("No permission of file transfer", "Aucune autorisation de transfert de fichiers"),
("Note", "Noter"),
("Connection", "Connexion"),
@ -303,11 +303,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Legacy mode", "Mode hérité"),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Utiliser mot de passe temporaire"),
("Use permanent password", "Utiliser mot de passe permanent"),
("Use both passwords", "Utiliser tous les mots de passe"),
("Set permanent password", "Définir mot de passe permanent"),
("Set temporary password length", "Définir la longueur mot de passe temporaire"),
("Use temporary password", "Utiliser un mot de passe temporaire"),
("Use permanent password", "Utiliser un mot de passe permanent"),
("Use both passwords", "Utiliser les mots de passe temporaire et permanent"),
("Set permanent password", "Définir le mot de passe permanent"),
("Set temporary password length", "Définir la longueur du mot de passe temporaire"),
("Enable Remote Restart", "Activer le redémarrage à distance"),
("Allow remote restart", "Autoriser le redémarrage à distance"),
("Restart Remote Device", "Redémarrer l'appareil à distance"),
@ -340,7 +340,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Dark", "Sombre"),
("Light", "Clair"),
("Follow System", "Suivi système"),
("Enable hardware codec", "Activer le codec matériel"),
("Enable hardware codec", "Activer le transcodage matériel"),
("Unlock Security Settings", "Déverrouiller les configurations de sécurité"),
("Enable Audio", "Activer l'audio"),
("Temporary Password Length", "Longueur mot de passe temporaire"),
@ -359,7 +359,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", "Activer RDP"),
("Pin menubar", "Épingler la barre de menus"),
("Unpin menubar", "Détacher la barre de menu"),
("Recording", "Enregistrement en cours"),
("Recording", "Enregistrement"),
("Directory", "Répertoire"),
("Automatically record incoming sessions", "Enregistrement automatique des session entrantes"),
("Change", "Modifier"),
@ -371,23 +371,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Interdir la découverte réseau local"),
("Write a message", "Ecrire un message"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
("Keyboard Settings", ""),
("Custom", ""),
("Full Access", ""),
("Screen Share", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "Déconnecté"),
("Other", "Divers"),
("Confirm before closing multiple tabs", "Confirmer avant de fermer plusieurs onglets"),
("Keyboard Settings", "Configuration clavier"),
("Custom", "Personnalisé"),
("Full Access", "Accès total"),
("Screen Share", "Partage d'écran"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland nécessite Ubuntu 21.04 ou une version supérieure."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland nécessite une version supérieure de la distribution Linux. Veuillez essayer le bureau X11 ou changer votre système d'exploitation."),
("JumpLink", "View"),
("JumpLink", "Afficher"),
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (opérer du côté pair)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
("Show RustDesk", "Afficher RustDesk"),
("This PC", "Ce PC"),
("or", "ou"),
("Continue with", "Continuer avec"),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -2,392 +2,393 @@ lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Státusz"),
("Your Desktop", "A te asztalod"),
("desk_tip", "Az asztalod ezzel az ID-vel, és jelszóval érhető el."),
("Your Desktop", "Saját azonosító"),
("desk_tip", "A számítógép ezzel a jelszóval és azonosítóval érhető el távolról."),
("Password", "Jelszó"),
("Ready", "Kész"),
("Established", "Létrejött"),
("connecting_status", "Kapcsolódás a RustDesk hálózatához..."),
("Enable Service", "A szolgáltatás bekapcsolása"),
("Start Service", "Szolgáltatás Elindítása"),
("Service is running", "A szolgáltatás fut"),
("Service is not running", "A szolgáltatás nem fut"),
("not_ready_status", "A RustDesk nem áll készen. Kérlek nézd meg a hálózati beállításaidat."),
("Control Remote Desktop", "Távoli Asztal Kontrollálása"),
("Transfer File", "Fájl Transzfer"),
("Connect", "Kapcsolódás"),
("Recent Sessions", "Korábbi Sessionök"),
("Address Book", "Címköny"),
("Confirmation", "Megerősít"),
("connecting_status", "Csatlakozás folyamatban..."),
("Enable Service", "Szolgáltatás engedélyezése"),
("Start Service", "Szolgáltatás indítása"),
("Service is running", "Szolgáltatás aktív"),
("Service is not running", "Szolgáltatás inaktív"),
("not_ready_status", "Kapcsolódási hiba. Kérlek ellenőrizze a hálózati beállításokat."),
("Control Remote Desktop", "Távoli számítógép vezérlése"),
("Transfer File", "Fájlátvitel"),
("Connect", "Csatlakozás"),
("Recent Sessions", "Legutóbbi munkamanetek"),
("Address Book", "Címjegyzék"),
("Confirmation", "Megerősítés"),
("TCP Tunneling", "TCP Tunneling"),
("Remove", "Eltávolít"),
("Refresh random password", "Véletlenszerű jelszó frissítése"),
("Refresh random password", "Új véletlenszerű jelszó"),
("Set your own password", "Saját jelszó beállítása"),
("Enable Keyboard/Mouse", "Billentyűzet/Egér bekapcsolása"),
("Enable Clipboard", "Megosztott vágólap bekapcsolása"),
("Enable File Transfer", "Fájl transzer bekapcsolása"),
("Enable TCP Tunneling", "TCP Tunneling bekapcsolása"),
("IP Whitelisting", "IP Fehérlista"),
("ID/Relay Server", "ID/Relay Szerver"),
("Import Server Config", "Szerver Konfiguráció Importálása"),
("Export Server Config", ""),
("Enable Keyboard/Mouse", "Billentyűzet/egér engedélyezése"),
("Enable Clipboard", "Megosztott vágólap engedélyezése"),
("Enable File Transfer", "Fájlátvitel engedélyezése"),
("Enable TCP Tunneling", "TCP Tunneling engedélyezése"),
("IP Whitelisting", "IP engedélyezési lista"),
("ID/Relay Server", "Kiszolgáló szerver"),
("Import Server Config", "Szerver konfiguráció importálása"),
("Export Server Config", "Szerver konfiguráció exportálása"),
("Import server configuration successfully", "Szerver konfiguráció sikeresen importálva"),
("Export server configuration successfully", ""),
("Export server configuration successfully", "Szerver konfiguráció sikeresen exportálva"),
("Invalid server configuration", "Érvénytelen szerver konfiguráció"),
("Clipboard is empty", "A vágólap üres"),
("Stop service", "Szolgáltatás Kikapcsolása"),
("Change ID", "ID Megváltoztatása"),
("Stop service", "Szolgáltatás leállítása"),
("Change ID", "Azonosító megváltoztatása"),
("Website", "Weboldal"),
("About", "Rólunk: "),
("About", "Rólunk"),
("Mute", "Némítás"),
("Audio Input", "Audo Bemenet"),
("Enhancements", "Javítások"),
("Hardware Codec", "Hardware Kodek"),
("Adaptive Bitrate", "Adaptív Bitrate"),
("ID Server", "ID Szerver"),
("Relay Server", "Relay Szerver"),
("API Server", "API Szerver"),
("Audio Input", "Hangátvitel"),
("Enhancements", "Fejlesztések"),
("Hardware Codec", "Hardware kodek"),
("Adaptive Bitrate", "Adaptív bitráta"),
("ID Server", "Szerver azonosító/domain"),
("Relay Server", "Kiszolgáló szerver"),
("API Server", "API szerver"),
("invalid_http", "A címnek mindenképpen http(s)://-el kell kezdődnie."),
("Invalid IP", "A megadott íp cím helytelen."),
("id_change_tip", "Csak a-z, A-Z, 0-9 csoportokba tartozó karakterek, illetve a _ karakter van engedélyezve. Az első karakternek mindenképpen a-z, A-Z csoportokba kell esnie. Az ID hosszúsága 6-tól, 16 karakter."),
("Invalid IP", "A megadott IP cím helytelen."),
("id_change_tip", "Csak a-z, A-Z, 0-9 csoportokba tartozó karakterek, illetve a _ karakter van engedélyezve. Az első karakternek mindenképpen a-z, A-Z csoportokba kell esnie. Az azonosító hosszúsága 6-tól, 16 karakter."),
("Invalid format", "Érvénytelen formátum"),
("server_not_support", "Még nem támogatott a szerver által"),
("Not available", "Nem érhető el"),
("server_not_support", "Nem támogatott a szerver által"),
("Not available", "Nem elérhető"),
("Too frequent", "Túl gyakori"),
("Cancel", "Mégsem"),
("Skip", "Kihagy"),
("Close", "Bezár"),
("Retry", "Újrapróbálkozás"),
("Skip", "Kihagyás"),
("Close", "Bezárás"),
("Retry", "Újra"),
("OK", "OK"),
("Password Required", "A jelszó megadása kötelező"),
("Please enter your password", "Kérlek írd be a jelszavad"),
("Remember password", "Kérlek emlékezz a jelszóra"),
("Password Required", "Jelszó megadása kötelező"),
("Please enter your password", "Kérem írja be a jelszavát"),
("Remember password", "Jelszó megjegyzése"),
("Wrong Password", "Hibás jelszó"),
("Do you want to enter again?", "Újra szeretnéd próbálni?"),
("Connection Error", "Kapcsolódási Hiba"),
("Do you want to enter again?", "Szeretne újra belépni?"),
("Connection Error", "Csatlakozási hiba"),
("Error", "Hiba"),
("Reset by the peer", "A kapcsolatot alaphelyzetbe állt"),
("Connecting...", "Kapcsolódás..."),
("Connection in progress. Please wait.", "A kapcsolódás folyamatban van. Kérlek várj."),
("Please try 1 minute later", "Kérlek próbáld újra 1 perc múlva."),
("Login Error", "Belépési Hiba"),
("Connecting...", "Csatlakozás..."),
("Connection in progress. Please wait.", "Csatlakozás folyamatban. Kérem várjon."),
("Please try 1 minute later", "Kérem próbálja meg 1 perc múlva"),
("Login Error", "Bejelentkezési hiba"),
("Successful", "Sikeres"),
("Connected, waiting for image...", "Kapcsolódva, várakozás a képre..."),
("Connected, waiting for image...", "Csatlakozva, várakozás a kép adatokra..."),
("Name", "Név"),
("Type", "Fajta"),
("Type", "Típus"),
("Modified", "Módosított"),
("Size", "Méret"),
("Show Hidden Files", "Rejtett Fájlok Mutatása"),
("Receive", "Kapni"),
("Send", "Küldeni"),
("Refresh File", "Fájlok Frissítése"),
("Local", "Lokális"),
("Show Hidden Files", "Rejtett fájlok mutatása"),
("Receive", "Fogad"),
("Send", "Küld"),
("Refresh File", "Fájl frissítése"),
("Local", "Helyi"),
("Remote", "Távoli"),
("Remote Computer", "Távoli Számítógép"),
("Local Computer", "Lokális Számítógép"),
("Confirm Delete", "Törlés Megerősítése"),
("Remote Computer", "Távoli számítógép"),
("Local Computer", "Helyi számítógép"),
("Confirm Delete", "Törlés megerősítése"),
("Delete", "Törlés"),
("Properties", "Tulajdonságok"),
("Multi Select", "Több fájl kiválasztása"),
("Select All", ""),
("Unselect All", ""),
("Empty Directory", "Üres Könyvtár"),
("Multi Select", "Többszörös kijelölés"),
("Select All", "Összes kijelölése"),
("Unselect All", "Kijelölések megszűntetése"),
("Empty Directory", "Üres könyvtár"),
("Not an empty directory", "Nem egy üres könyvtár"),
("Are you sure you want to delete this file?", "Biztosan törölni szeretnéd ezt a fájlt?"),
("Are you sure you want to delete this empty directory?", "Biztosan törölni szeretnéd ezt az üres könyvtárat?"),
("Are you sure you want to delete the file of this directory?", "Biztosan törölni szeretnéd a fájlokat ebben a könyvtárban?"),
("Do this for all conflicts", "Ezt tedd az összes konfliktussal"),
("Are you sure you want to delete this file?", "Biztosan törli ezt a fájlt?"),
("Are you sure you want to delete this empty directory?", "Biztosan törli ezt az üres könyvtárat?"),
("Are you sure you want to delete the file of this directory?", "Biztos benne, hogy törölni szeretné a könyvtár tartalmát?"),
("Do this for all conflicts", "Tegye ezt minden ütközéskor"),
("This is irreversible!", "Ez a folyamat visszafordíthatatlan!"),
("Deleting", "A törlés folyamatban"),
("Deleting", "Törlés folyamatban"),
("files", "fájlok"),
("Waiting", "Várunk"),
("Finished", "Végzett"),
("Speed", "Gyorsaság"),
("Custom Image Quality", "Egyedi Képminőség"),
("Waiting", "Várakozás"),
("Finished", "Befejezve"),
("Speed", "Sebesség"),
("Custom Image Quality", "Egyedi képminőség"),
("Privacy mode", "Inkognító mód"),
("Block user input", "Felhasználói input blokkokolása"),
("Unblock user input", "Felhasználói input blokkolásának feloldása"),
("Block user input", "Felhasználói bevitel letiltása"),
("Unblock user input", "Felhasználói bevitel engedélyezése"),
("Adjust Window", "Ablakméret beállítása"),
("Original", "Eredeti"),
("Shrink", "Zsugorított"),
("Stretch", "Nyújtott"),
("Original", "Eredeti méret"),
("Shrink", "Kicsinyítés"),
("Stretch", "Nyújtás"),
("Scrollbar", "Görgetősáv"),
("ScrollAuto", "Görgessen Auto"),
("Good image quality", "Jó képminőség"),
("Balanced", "Balanszolt"),
("Optimize reaction time", "Válaszidő optimializálása"),
("Custom", ""),
("Show remote cursor", "Távoli kurzor mutatása"),
("Show quality monitor", "Minőségi monitor mutatása"),
("Disable clipboard", "Vágólap Kikapcsolása"),
("Lock after session end", "Lezárás a session végén"),
("Insert", "Beszúrás"),
("Insert Lock", "Beszúrási Zároló"),
("ScrollAuto", "Automatikus görgetés"),
("Good image quality", "Eredetihez hű"),
("Balanced", "Kiegyensúlyozott"),
("Optimize reaction time", "Gyorsan reagáló"),
("Custom", "Egyedi"),
("Show remote cursor", "Távoli kurzor megjelenítése"),
("Show quality monitor", ""),
("Disable clipboard", "Közös vágólap kikapcsolása"),
("Lock after session end", "Távoli fiók zárolása a munkamenet végén"),
("Insert", ""),
("Insert Lock", "Távoli fiók zárolása"),
("Refresh", "Frissítés"),
("ID does not exist", "Ez az ID nem létezik"),
("Failed to connect to rendezvous server", "A randevú szerverhez való kapcsolódás sikertelen"),
("Please try later", "Kérlek próbád később"),
("Remote desktop is offline", "A távoli asztal offline"),
("ID does not exist", "Az azonosító nem létezik"),
("Failed to connect to rendezvous server", "Nem sikerült csatlakozni a kiszolgáló szerverhez"),
("Please try later", "Kérjük, próbálja később"),
("Remote desktop is offline", "A távoli számítógép offline állapotban van"),
("Key mismatch", "Eltérés a kulcsokban"),
("Timeout", "Időtúllépés"),
("Failed to connect to relay server", "A relay szerverhez való kapcsolódás sikertelen"),
("Failed to connect via rendezvous server", "A randevú szerverrel való kapcsolódás sikertelen"),
("Failed to connect via relay server", "A relay szerverrel való kapcsolódás sikertelen"),
("Failed to make direct connection to remote desktop", "A távoli asztalhoz való direkt kapcsolódás sikertelen"),
("Failed to connect to relay server", "Nem sikerült csatlakozni a közvetítő szerverhez"),
("Failed to connect via rendezvous server", "Nem sikerült csatlakozni a kiszolgáló szerveren keresztül"),
("Failed to connect via relay server", "Nem sikerült csatlakozni a közvetítő szerveren keresztül"),
("Failed to make direct connection to remote desktop", "Nem sikerült közvetlen kapcsolatot létesíteni a távoli számítógéppel"),
("Set Password", "Jelszó Beállítása"),
("OS Password", "Operációs Rendszer Jelszavának Beállítása"),
("install_tip", "Az UAC (Felhasználói Fiók Felügyelet) miatt, a RustDesk nem fog rendesen funkcionálni mint távoli oldal néhány esetben. Hogy ezt kikerüld, vagy kikapcsold, kérlek nyomj rá a gombra ezalatt az üzenet alatt, hogy feltelepítsd a RustDesket a rendszerre."),
("Click to upgrade", "Kattints a frissítés telepítéséhez"),
("Click to download", "Kattints a letöltéshez"),
("Click to update", "Kattints a frissítés letöltéséhez"),
("OS Password", "Operációs rendszer jelszavának beállítása"),
("install_tip", "Előfordul, hogy bizonyos esetekben hiba léphet fel a Portable verzió használata során. A megfelelő működés érdekében, kérem telepítse a RustDesk alkalmazást a számítógépre."),
("Click to upgrade", "Kattintson ide a frissítés telepítéséhez"),
("Click to download", "Kattintson ide a letöltéshez"),
("Click to update", "Kattintson ide a frissítés letöltéséhez"),
("Configure", "Beállítás"),
("config_acc", "Ahhoz hogy a RustDesket távolról irányítani tudd, \"Elérhetőségi\" jogokat kell adnod a RustDesk-nek."),
("config_screen", "Ahhoz hogy a RustDesket távolról irányítani tudd, \"Képernyőfelvételi\" jogokat kell adnod a RustDesk-nek."),
("config_acc", "A távoli vezérléshez a RustDesk-nek \"Kisegítő lehetőség\" engedélyre van szüksége"),
("config_screen", "A távoli vezérléshez szükséges a \"Képernyőfelvétel\" engedély megadása"),
("Installing ...", "Telepítés..."),
("Install", "Telepítés"),
("Installation", "Telepítés"),
("Installation Path", "Telepítési útvonal"),
("Create start menu shortcuts", "Start menu parancsikon létrehozása"),
("Create desktop icon", "Asztali icon létrehozása"),
("agreement_tip", "Azzal hogy elindítod a telepítést, elfogadod a licenszszerződést."),
("Accept and Install", "Elfogadás és Telepítés"),
("End-user license agreement", "Felhasználói licencszerződés"),
("Generating ...", "Generálás..."),
("Your installation is lower version.", "A jelenleg feltelepített verzió régebbi."),
("not_close_tcp_tip", "Ne zárd be ezt az ablakot miközben a tunnelt használod"),
("Listening ...", "Halgazózás..."),
("Remote Host", "Távoli Host"),
("Remote Port", "Távoli Port"),
("Action", "Akció"),
("Add", "Add"),
("Local Port", "Lokális Port"),
("Local Address", ""),
("Change Local Port", ""),
("setup_server_tip", "Egy gyorsabb kapcsolatért, kérlek hostolj egy saját szervert"),
("Too short, at least 6 characters.", "Túl rövid, legalább 6 karakter"),
("Create start menu shortcuts", "Start menü parancsikonok létrehozása"),
("Create desktop icon", "Ikon létrehozása az asztalon"),
("agreement_tip", "A telepítés folytatásával automatikusan elfogadásra kerül a licensz szerződés."),
("Accept and Install", "Elfogadás és telepítés"),
("End-user license agreement", "Felhasználói licensz szerződés"),
("Generating ...", "Létrehozás..."),
("Your installation is lower version.", "A telepített verzió alacsonyabb."),
("not_close_tcp_tip", "Ne zárja be ezt az ablakot miközben a tunnelt használja"),
("Listening ...", "Keresés..."),
("Remote Host", "Távoli kiszolgáló"),
("Remote Port", "Távoli port"),
("Action", "Indítás"),
("Add", "Hozzáadás"),
("Local Port", "Helyi port"),
("Local Address", "Helyi cím"),
("Change Local Port", "Helyi port megváltoztatása"),
("setup_server_tip", "Gyorsabb kapcsolat érdekében, hozzon létre saját szervert"),
("Too short, at least 6 characters.", "Túl rövid, legalább 6 karakter."),
("The confirmation is not identical.", "A megerősítés nem volt azonos"),
("Permissions", "Jogok"),
("Accept", "Elfogad"),
("Dismiss", "Elutasít"),
("Disconnect", "Szétkapcsolás"),
("Permissions", "Engedélyek"),
("Accept", "Elfogadás"),
("Dismiss", "Elutasítás"),
("Disconnect", "Kapcsolat bontása"),
("Allow using keyboard and mouse", "Billentyűzet és egér használatának engedélyezése"),
("Allow using clipboard", "Vágólap használatának engedélyezése"),
("Allow hearing sound", "Hang átvitelének engedélyezése"),
("Allow file copy and paste", "Fájlok másolásának és beillesztésének engedélyezése"),
("Connected", "Kapcsolódva"),
("Direct and encrypted connection", "Direkt, és titkosított kapcsolat"),
("Relayed and encrypted connection", "Relayelt, és titkosított kapcsolat"),
("Direct and unencrypted connection", "Direkt, és nem titkosított kapcsolat"),
("Relayed and unencrypted connection", "Rekayelt, és nem titkosított kapcsolat"),
("Enter Remote ID", "Kérlek írd be a távoli ID-t"),
("Enter your password", "Kérlek írd be a jelszavadat"),
("Connected", "Csatlakozva"),
("Direct and encrypted connection", "Közvetlen, és titkosított kapcsolat"),
("Relayed and encrypted connection", "Továbbított, és titkosított kapcsolat"),
("Direct and unencrypted connection", "Közvetlen, és nem titkosított kapcsolat"),
("Relayed and unencrypted connection", "Továbbított, és nem titkosított kapcsolat"),
("Enter Remote ID", "Távoli számítógép azonosítója"),
("Enter your password", "Írja be a jelszavát"),
("Logging in...", "A belépés folyamatban..."),
("Enable RDP session sharing", "Az RDP session megosztás engedélyezése"),
("Auto Login", "Automatikus Login"),
("Enable Direct IP Access", "Direkt IP elérés engedélyezése"),
("Enable RDP session sharing", "RDP-munkamenet-megosztás engedélyezése"),
("Auto Login", "Automatikus bejelentkezés"),
("Enable Direct IP Access", "Közvetlen IP-elérés engedélyezése"),
("Rename", "Átnevezés"),
("Space", "Hely"),
("Create Desktop Shortcut", "Asztali Parancsikon Lértehozása"),
("Change Path", "Útvonal Megváltoztatása"),
("Create Folder", "Mappa Készítése"),
("Please enter the folder name", "Kérlek írd be a mappa nevét"),
("Fix it", "Kérlek javísd meg"),
("Warning", "Figyelem"),
("Login screen using Wayland is not supported", "A belépési kijelzővel a Wayland használata nem támogatott"),
("Space", ""),
("Create Desktop Shortcut", "Asztali parancsikon létrehozása"),
("Change Path", "Elérési út módosítása"),
("Create Folder", "Mappa létrehozás"),
("Please enter the folder name", "Kérjük, adja meg a mappa nevét"),
("Fix it", "Javítás"),
("Warning", "Figyelmeztetés"),
("Login screen using Wayland is not supported", "Bejelentkezéskori Wayland használata nem támogatott"),
("Reboot required", "Újraindítás szükséges"),
("Unsupported display server ", "Nem támogatott kijelző szerver"),
("Unsupported display server ", "Nem támogatott megjelenítő szerver"),
("x11 expected", "x11-re számítottt"),
("Port", ""),
("Port", "Port"),
("Settings", "Beállítások"),
("Username", "Felhasználónév"),
("Invalid port", "Érvénytelen port"),
("Closed manually by the peer", "A kapcsolat manuálisan be lett zárva a másik fél álltal"),
("Closed manually by the peer", "A kapcsolatot a másik fél manuálisan bezárta"),
("Enable remote configuration modification", "Távoli konfiguráció módosítás engedélyezése"),
("Run without install", "Futtatás feltelepítés nélkül"),
("Always connected via relay", "Mindig relay által kapcsolódott"),
("Always connect via relay", "Mindig relay által kapcsolódik"),
("whitelist_tip", "Csak a fehérlistán lévő címek érhetnek el"),
("Always connected via relay", "Mindig közvetítőn keresztül csatlakozik"),
("Always connect via relay", "Mindig közvetítőn keresztüli csatlakozás"),
("whitelist_tip", "Csak az engedélyezési listán szereplő címek csatlakozhatnak"),
("Login", "Belépés"),
("Logout", "Kilépés"),
("Tags", "Tagok"),
("Search ID", "ID keresés"),
("Current Wayland display server is not supported", "Jelenleg a Wayland display szerver nem támogatott"),
("whitelist_sep", "Ide jönnek a címek, vesző, pontosvessző, space, vagy új sorral elválasztva"),
("Add ID", "ID Hozzáadása"),
("Add Tag", "Tag Hozzáadása"),
("Unselect all tags", "Az összes tag kiválasztásának törlése"),
("Search ID", "Azonosító keresése..."),
("Current Wayland display server is not supported", "A Wayland display szerver nem támogatott"),
("whitelist_sep", "A címeket veszővel, pontosvesszővel, szóközzel, vagy új sorral válassza el"),
("Add ID", "Azonosító hozzáadása"),
("Add Tag", "Címke hozzáadása"),
("Unselect all tags", "A címkék kijelölésének megszüntetése"),
("Network error", "Hálózati hiba"),
("Username missed", "A felhasználónév kimaradt"),
("Password missed", "A jelszó kimaradt"),
("Username missed", "Üres felhasználónév"),
("Password missed", "Üres jelszó"),
("Wrong credentials", "Hibás felhasználónév vagy jelszó"),
("Edit Tag", "A tag(ok) szerkeztése"),
("Edit Tag", "Címke szerkesztése"),
("Unremember Password", "A jelszó megjegyzésének törlése"),
("Favorites", "Kedvencek"),
("Add to Favorites", "Hozzáadás a kedvencekhez"),
("Remove from Favorites", "Eltávolítás a kedvencektől"),
("Remove from Favorites", "Eltávolítás a kedvencekből"),
("Empty", "Üres"),
("Invalid folder name", "Helytelen fájlnév"),
("Socks5 Proxy", "Socks5-ös Proxy"),
("Hostname", "Hostnév"),
("Discovered", "Felfedezés"),
("install_daemon_tip", "Ahhoz hogy a RustDesk bootkor elinduljon, telepítened kell a rendszer szolgáltatást."),
("Remote ID", "Távoli ID"),
("Invalid folder name", "Helytelen mappa név"),
("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Kiszolgáló név"),
("Discovered", "Felfedezett"),
("install_daemon_tip", "Az automatikus indításhoz szükséges a szolgáltatás telepítése"),
("Remote ID", "Távoli azonosító"),
("Paste", "Beillesztés"),
("Paste here?", "Beillesztés ide?"),
("Are you sure to close the connection?", "Biztos vagy benne hogy be szeretnéd zárni a kapcsolatot?"),
("Paste here?", "Beilleszti ide?"),
("Are you sure to close the connection?", "Biztos, hogy bezárja a kapcsolatot?"),
("Download new version", "Új verzó letöltése"),
("Touch mode", "Érintési mód bekapcsolása"),
("Mouse mode", "Egérhasználati mód bekapcsolása"),
("One-Finger Tap", "Egyújas érintés"),
("Left Mouse", "Baloldali Egér"),
("One-Long Tap", "Egy hosszú érintés"),
("Two-Finger Tap", "Két újas érintés"),
("Right Mouse", "Jobboldali Egér"),
("One-Finger Move", "Egyújas mozgatás"),
("Double Tap & Move", "Kétszeri érintés, és Mozgatás"),
("Mouse Drag", "Egérrel való húzás"),
("One-Finger Tap", "Egyujjas érintés"),
("Left Mouse", "Bal egér gomb"),
("One-Long Tap", "Hosszú érintés"),
("Two-Finger Tap", "Kétujjas érintés"),
("Right Mouse", "Jobb egér gomb"),
("One-Finger Move", "Egyujjas mozgatás"),
("Double Tap & Move", "Dupla érintés, és mozgatás"),
("Mouse Drag", "Mozgatás egérrel"),
("Three-Finger vertically", "Három ujj függőlegesen"),
("Mouse Wheel", "Egérgörgő"),
("Two-Finger Move", "Kátújas mozgatás"),
("Canvas Move", "Nézet Mozgatása"),
("Pinch to Zoom", "Húzd össze a nagyításhoz"),
("Canvas Zoom", "Nézet Nagyítása"),
("Two-Finger Move", "Kátujjas mozgatás"),
("Canvas Move", "Nézet mozgatása"),
("Pinch to Zoom", "Kétujjas nagyítás"),
("Canvas Zoom", "Nézet nagyítása"),
("Reset canvas", "Nézet visszaállítása"),
("No permission of file transfer", "Nincs jogod fájl transzer indításához"),
("No permission of file transfer", "Nincs engedély a fájlátvitelre"),
("Note", "Megyjegyzés"),
("Connection", "Kapcsolat"),
("Share Screen", "Képernyőmegosztás"),
("CLOSE", "LETILT"),
("OPEN", "ENGEDÉLYEZ"),
("CLOSE", "BEZÁRÁS"),
("OPEN", "MEGNYITÁS"),
("Chat", "Chat"),
("Total", "Összes"),
("items", "Tárgyak"),
("Selected", "Kiválasztott"),
("items", "elemek"),
("Selected", "Kijelölt"),
("Screen Capture", "Képernyőrögzítés"),
("Input Control", "Input Kontrol"),
("Audio Capture", "Audió Rögzítés"),
("File Connection", "Fájlkapcsolat"),
("Screen Connection", "Új Vizuális Kapcsolat"),
("Do you accept?", "Elfogadod?"),
("Open System Setting", "Rendszer beállítások megnyitása"),
("How to get Android input permission?", "Hogyan állíthatok be Android input jogokat?"),
("android_input_permission_tip1", "Ahhoz hogy egy távoli eszköz kontolálhassa az Android eszközödet egérrel vagy érintéssel, jogot kell adnod a RustDesk-nek, hogy használja az \"Elérhetőségi\" szolgáltatást."),
("android_input_permission_tip2", "Kérlek navigálj a rendszer beállításaihoz, keresd meg vagy írd be hogy [Feltelepített Szolgáltatások], és kapcsold be a [RustDesk Input] szolgáltatást."),
("android_new_connection_tip", "Új kontrollálási kérés érkezett, amely irányítani szeretné az eszközöded."),
("android_service_will_start_tip", "A \"Képernyőrögzítés\" engedélyezése automatikusan elindítja majd a szolgáltatást, amely megengedi más eszközöknek hogy kérést kezdeményezzenek az eszköz felé."),
("android_stop_service_tip", "A szolgáltatás bezárása automatikusan szétkapcsol minden létező kapcsolatot."),
("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, kérlek frissíts legalább Android 10-re, vagy egy újabb verzióra."),
("android_start_service_tip", "Nyomj a [Szolgáltatás Indítása] opcióra, vagy adj [Képernyőrözítési] jogot az applikációnak hogy elindítsd a képernyőmegosztó szolgáltatást."),
("Account", ""),
("Input Control", "Távoli vezérlés"),
("Audio Capture", "Hangrögzítés"),
("File Connection", "Fájlátvitel"),
("Screen Connection", "Képátvitel"),
("Do you accept?", "Elfogadja?"),
("Open System Setting", "Rendszerbeállítások megnyitása"),
("How to get Android input permission?", "Hogyan állíthatok be Android beviteli engedélyt?"),
("android_input_permission_tip1", "A távoli vezérléshez kérjük engedélyezze a \"Kisegítő lehetőség\" lehetőséget."),
("android_input_permission_tip2", "A következő rendszerbeállítások oldalon a letöltött alkalmazások menüponton belül, kapcsolja be a [RustDesk Input] szolgáltatást."),
("android_new_connection_tip", "Új kérés érkezett mely vezérelni szeretné az eszközét"),
("android_service_will_start_tip", "A \"Képernyőrögzítés\" bekapcsolásával automatikus elindul a szolgáltatás, lehetővé téve, hogy más eszközök csatlakozási kérelmet küldhessenek"),
("android_stop_service_tip", "A szolgáltatás leállítása automatikusan szétkapcsol minden létező kapcsolatot."),
("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, frissítsen legalább Android 10-re, vagy egy újabb verzióra."),
("android_start_service_tip", "Nyomjon a [Szolgáltatás indítása] gombra, vagy adjon [Képernyőrözítési] engedélyt az applikációnak hogy elindítsa a képernyőmegosztó szolgáltatást."),
("Account", "Fiók"),
("Overwrite", "Felülírás"),
("This file exists, skip or overwrite this file?", "Ez a fájl már létezik, skippeljünk, vagy felülírjuk ezt a fájlt?"),
("This file exists, skip or overwrite this file?", "Ez a fájl már létezik, kihagyja vagy felülírja ezt a fájlt?"),
("Quit", "Kilépés"),
("doc_mac_permission", "https://rustdesk.com/docs/hu/manual/mac/#enable-permissions"),
("Help", "Segítség"),
("Failed", "Sikertelen"),
("Succeeded", "Sikeres"),
("Someone turns on privacy mode, exit", "Valaki bekacsolta a privát módot, lépj ki"),
("Someone turns on privacy mode, exit", "Valaki bekacsolta az inkognitó módot, lépjen ki"),
("Unsupported", "Nem támogatott"),
("Peer denied", "Elutasítva a távoli fél álltal"),
("Please install plugins", "Kérlek telepítsd a pluginokat"),
("Please install plugins", "Kérem telepítse a bővítményeket"),
("Peer exit", "A távoli fél kilépett"),
("Failed to turn off", "Nem tudtuk kikapcsolni"),
("Failed to turn off", "Nem sikerült kikapcsolni"),
("Turned off", "Kikapcsolva"),
("In privacy mode", "Belépés a privát módba"),
("Out privacy mode", "Kilépés a privát módból"),
("In privacy mode", "Belépés inkognitó módba"),
("Out privacy mode", "Kilépés inkognitó módból"),
("Language", "Nyelv"),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Keep RustDesk background service", "RustDesk futtatása a háttérben"),
("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"),
("android_open_battery_optimizations_tip", "Ha le szeretné tiltani ezt a funkciót, lépjen a RustDesk alkalmazás beállítási oldalára, keresse meg az [Akkumulátorkímélő] lehetőséget és válassza a nincs korlátozás lehetőséget."),
("Connection not allowed", "A csatlakozás nem engedélyezett"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),
("Set permanent password", ""),
("Set temporary password length", ""),
("Enable Remote Restart", ""),
("Allow remote restart", ""),
("Restart Remote Device", ""),
("Are you sure you want to restart", ""),
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("Copied", ""),
("Exit Fullscreen", "Lépjen ki a teljes képernyőről"),
("Translate mode", "Fordító mód"),
("Use temporary password", "Ideiglenes jelszó használata"),
("Use permanent password", "Állandó jelszó használata"),
("Use both passwords", "Mindkét jelszó használata"),
("Set permanent password", "Állandó jelszó beállítása"),
("Set temporary password length", "Ideiglenes jelszó hosszának beállítása"),
("Enable Remote Restart", "Távoli újraindítás engedélyezése"),
("Allow remote restart", "Távoli újraindítás engedélyezése"),
("Restart Remote Device", "Távoli eszköz újraindítása"),
("Are you sure you want to restart", "Biztos szeretné újraindítani?"),
("Restarting Remote Device", "Távoli eszköz újraindítása..."),
("remote_restarting_tip", "A távoli eszköz újraindul, zárja be ezt az üzenetet, csatlakozzon újra, állandó jelszavával"),
("Copied", "Másolva"),
("Exit Fullscreen", "Kilépés teljes képernyős módból"),
("Fullscreen", "Teljes képernyő"),
("Mobile Actions", "mobil műveletek"),
("Select Monitor", "Válassza a Monitor lehetőséget"),
("Select Monitor", "Válasszon képernyőt"),
("Control Actions", "Irányítási műveletek"),
("Display Settings", "Megjelenítési beállítások"),
("Ratio", "Hányados"),
("Ratio", "Arány"),
("Image Quality", "Képminőség"),
("Scroll Style", "Görgetési stílus"),
("Show Menubar", "Menüsor megjelenítése"),
("Hide Menubar", "menüsor elrejtése"),
("Hide Menubar", "Menüsor elrejtése"),
("Direct Connection", "Közvetlen kapcsolat"),
("Relay Connection", "Relé csatlakozás"),
("Relay Connection", "Közvetett csatlakozás"),
("Secure Connection", "Biztonságos kapcsolat"),
("Insecure Connection", "Nem biztonságos kapcsolat"),
("Scale original", "Eredeti méretarány"),
("Scale adaptive", "Skála adaptív"),
("General", ""),
("Security", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Scale adaptive", "Adaptív méretarány"),
("General", "Általános"),
("Security", "Biztonság"),
("Account", "Fiók"),
("Theme", "Téma"),
("Dark Theme", "Sötét téma"),
("Dark", "Sötét"),
("Light", "Világos"),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
("Temporary Password Length", ""),
("Unlock Network Settings", ""),
("Server", ""),
("Direct IP Access", ""),
("Proxy", ""),
("Port", ""),
("Apply", ""),
("Disconnect all devices?", ""),
("Clear", ""),
("Audio Input Device", ""),
("Deny remote access", ""),
("Use IP Whitelisting", ""),
("Network", ""),
("Enable RDP", ""),
("Enable hardware codec", "Hardveres kodek engedélyezése"),
("Unlock Security Settings", "Biztonsági beállítások feloldása"),
("Enable Audio", "Hang engedélyezése"),
("Temporary Password Length", "Ideiglenes jelszó hossza"),
("Unlock Network Settings", "Hálózati beállítások feloldása"),
("Server", "Szerver"),
("Direct IP Access", "Közvetlen IP hozzáférés"),
("Proxy", "Proxy"),
("Port", "Port"),
("Apply", "Alkalmaz"),
("Disconnect all devices?", "Leválasztja az összes eszközt?"),
("Clear", "Tisztítás"),
("Audio Input Device", "Audio bemeneti eszköz"),
("Deny remote access", "Távoli hozzáférés megtagadása"),
("Use IP Whitelisting", "Engedélyezési lista használata"),
("Network", "Hálózat"),
("Enable RDP", "RDP engedélyezése"),
("Pin menubar", "Menüsor rögzítése"),
("Unpin menubar", "Menüsor rögzítésének feloldása"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
("Recording", "Felvétel"),
("Directory", "Könyvtár"),
("Automatically record incoming sessions", "A bejövő munkamenetek automatikus rögzítése"),
("Change", "Változtatás"),
("Start session recording", "Munkamenet rögzítés indítása"),
("Stop session recording", "Munkamenet rögzítés leállítása"),
("Enable Recording Session", "Munkamenet rögzítés engedélyezése"),
("Allow recording session", "Munkamenet rögzítés engedélyezése"),
("Enable LAN Discovery", "Felfedezés enegedélyezése"),
("Deny LAN Discovery", "Felfedezés tiltása"),
("Write a message", "Üzenet írása"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
("Keyboard Settings", ""),
("Custom", ""),
("Full Access", ""),
("Screen Share", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "Szétkapcsolva"),
("Other", "Egyéb"),
("Confirm before closing multiple tabs", "Biztos, hogy bezárja az összes lapot?"),
("Keyboard Settings", "Billentyűzet beállítások"),
("Custom", "Egyedi"),
("Full Access", "Teljes hozzáférés"),
("Screen Share", "Képernyőmegosztás"),
("Wayland requires Ubuntu 21.04 or higher version.", "A Waylandhoz Ubuntu 21.04 vagy újabb verzió szükséges."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "A Wayland a Linux disztró magasabb verzióját igényli. Próbálja ki az X11 desktopot, vagy változtassa meg az operációs rendszert."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Kérjük, válassza ki a megosztani kívánt képernyőt (a társoldalon működjön)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
("JumpLink", "Hiperhivatkozás"),
("Please Select the screen to be shared(Operate on the peer side).", "Kérjük, válassza ki a megosztani kívánt képernyőt."),
("Show RustDesk", "A RustDesk megjelenítése"),
("This PC", "Ez a számítógép"),
("or", "vagy"),
("Continue with", "Folytatás a következővel"),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Tolak Penemuan LAN"),
("Write a message", "Menulis pesan"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "Terputus"),
("Other", "Lainnya"),
("Confirm before closing multiple tabs", "Konfirmasi sebelum menutup banyak tab"),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -30,9 +30,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("IP Whitelisting", "IP autorizzati"),
("ID/Relay Server", "Server ID/Relay"),
("Import Server Config", "Importa configurazione Server"),
("Export Server Config", ""),
("Export Server Config", "Esporta configurazione Server"),
("Import server configuration successfully", "Configurazione Server importata con successo"),
("Export server configuration successfully", ""),
("Export server configuration successfully", "Configurazione Server esportata con successo"),
("Invalid server configuration", "Configurazione Server non valida"),
("Clipboard is empty", "Gli appunti sono vuoti"),
("Stop service", "Arresta servizio"),
@ -89,8 +89,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Delete", "Eliminare"),
("Properties", "Proprietà"),
("Multi Select", "Selezione multipla"),
("Select All", ""),
("Unselect All", ""),
("Select All", "Seleziona tutto"),
("Unselect All", "Deseleziona tutto"),
("Empty Directory", "Directory vuota"),
("Not an empty directory", "Non una directory vuota"),
("Are you sure you want to delete this file?", "Vuoi davvero eliminare questo file?"),
@ -113,12 +113,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stretch", "Allarga"),
("Scrollbar", "Barra di scorrimento"),
("ScrollAuto", "Scorri automaticamente"),
("Good image quality", "Buona qualità immagine"),
("Good image quality", "Qualità immagine migliore"),
("Balanced", "Bilanciato"),
("Optimize reaction time", "Ottimizza il tempo di reazione"),
("Custom", ""),
("Custom", "Personalizza"),
("Show remote cursor", "Mostra il cursore remoto"),
("Show quality monitor", ""),
("Show quality monitor", "Visualizza qualità video"),
("Disable clipboard", "Disabilita appunti"),
("Lock after session end", "Blocca al termine della sessione"),
("Insert", "Inserisci"),
@ -161,8 +161,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Action", "Azione"),
("Add", "Aggiungi"),
("Local Port", "Porta locale"),
("Local Address", ""),
("Change Local Port", ""),
("Local Address", "Indirizzo locale"),
("Change Local Port", "Cambia porta locale"),
("setup_server_tip", "Per una connessione più veloce, configura un tuo server"),
("Too short, at least 6 characters.", "Troppo breve, almeno 6 caratteri"),
("The confirmation is not identical.", "La conferma non corrisponde"),
@ -197,7 +197,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Riavvio necessario"),
("Unsupported display server ", "Display server non supportato"),
("x11 expected", "x11 necessario"),
("Port", ""),
("Port", "Porta"),
("Settings", "Impostazioni"),
("Username", " Nome utente"),
("Invalid port", "Porta non valida"),
@ -296,13 +296,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "In modalità privacy"),
("Out privacy mode", "Fuori modalità privacy"),
("Language", "Linguaggio"),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"),
("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", "Connessione non consentita"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Legacy mode", "Modalità legacy"),
("Map mode", "Modalità mappa"),
("Translate mode", "Modalità di traduzione"),
("Use temporary password", "Usa password temporanea"),
("Use permanent password", "Usa password permanente"),
("Use both passwords", "Usa entrambe le password"),
@ -314,11 +314,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Are you sure you want to restart", "Sei sicuro di voler riavviare?"),
("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"),
("remote_restarting_tip", "Riavviare il dispositivo remoto"),
("Copied", ""),
("Copied", "Copiato"),
("Exit Fullscreen", "Esci dalla modalità schermo intero"),
("Fullscreen", "A schermo intero"),
("Mobile Actions", "Azioni mobili"),
("Select Monitor", "Seleziona Monitora"),
("Select Monitor", "Seleziona schermo"),
("Control Actions", "Azioni di controllo"),
("Display Settings", "Impostazioni di visualizzazione"),
("Ratio", "Rapporto"),
@ -332,62 +332,63 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Insecure Connection", "Connessione insicura"),
("Scale original", "Scala originale"),
("Scale adaptive", "Scala adattiva"),
("General", ""),
("Security", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
("Temporary Password Length", ""),
("Unlock Network Settings", ""),
("General", "Generale"),
("Security", "Sicurezza"),
("Account", "Account"),
("Theme", "Tema"),
("Dark Theme", "Tema Scuro"),
("Dark", "Scuro"),
("Light", "Chiaro"),
("Follow System", "Segui il sistema"),
("Enable hardware codec", "Abilita codec hardware"),
("Unlock Security Settings", "Sblocca impostazioni di sicurezza"),
("Enable Audio", "Abilita audio"),
("Temporary Password Length", "Lunghezza password temporanea"),
("Unlock Network Settings", "Sblocca impostazioni di rete"),
("Server", ""),
("Direct IP Access", ""),
("Direct IP Access", "Accesso IP diretto"),
("Proxy", ""),
("Port", ""),
("Apply", ""),
("Disconnect all devices?", ""),
("Clear", ""),
("Audio Input Device", ""),
("Deny remote access", ""),
("Use IP Whitelisting", ""),
("Network", ""),
("Enable RDP", ""),
("Port", "Porta"),
("Apply", "Applica"),
("Disconnect all devices?", "Disconnettere tutti i dispositivi?"),
("Clear", "Ripulisci"),
("Audio Input Device", "Dispositivo di input audio"),
("Deny remote access", "Nega accesso remoto"),
("Use IP Whitelisting", "Utilizza la whitelist di IP"),
("Network", "Rete"),
("Enable RDP", "Abilita RDP"),
("Pin menubar", "Blocca la barra dei menu"),
("Unpin menubar", "Sblocca la barra dei menu"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
("Recording", "Registrazione"),
("Directory", "Cartella"),
("Automatically record incoming sessions", "Registra automaticamente le sessioni in entrata"),
("Change", "Cambia"),
("Start session recording", "Inizia registrazione della sessione"),
("Stop session recording", "Ferma registrazione della sessione"),
("Enable Recording Session", "Abilita registrazione della sessione"),
("Allow recording session", "Permetti di registrare la sessione"),
("Enable LAN Discovery", "Abilita il rilevamento della LAN"),
("Deny LAN Discovery", "Nega il rilevamento della LAN"),
("Write a message", "Scrivi un messaggio"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
("Keyboard Settings", ""),
("Custom", ""),
("Full Access", ""),
("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland richiede Ubuntu 21.04 o versione successiva."),
("Please wait for confirmation of UAC...", "Attendi la conferma dell'UAC..."),
("elevated_foreground_window_tip", ""),
("Disconnected", "Disconnesso"),
("Other", "Altro"),
("Confirm before closing multiple tabs", "Conferma prima di chiudere più schede"),
("Keyboard Settings", "Impostazioni tastiera"),
("Custom", "Personalizzato"),
("Full Access", "Accesso completo"),
("Screen Share", "Condivisione dello schermo"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland richiede Ubuntu 21.04 o successiva."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland richiede una versione superiore della distribuzione Linux. Prova X11 desktop o cambia il tuo sistema operativo."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
("Show RustDesk", "Mostra RustDesk"),
("This PC", "Questo PC"),
("or", "O"),
("Continue with", "Continua con"),
("Elevate", "Eleva"),
("Zoom cursor", "Cursore zoom"),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", "他の"),
("Confirm before closing multiple tabs", "同時に複数のタブを閉じる前に確認する"),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -7,7 +7,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Password", "Hasło"),
("Ready", "Gotowe"),
("Established", "Nawiązano"),
("connecting_status", "Status połączenia"),
("connecting_status", "Łączenie"),
("Enable Service", "Włącz usługę"),
("Start Service", "Uruchom usługę"),
("Service is running", "Usługa uruchomiona"),
@ -30,9 +30,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("IP Whitelisting", "Biała lista IP"),
("ID/Relay Server", "Serwer ID/Pośredniczący"),
("Import Server Config", "Importuj konfigurację serwera"),
("Export Server Config", ""),
("Import server configuration successfully", "Importowanie konfiguracji serwera powiodło się"),
("Export server configuration successfully", ""),
("Export Server Config", "Eksportuj konfigurację serwera"),
("Import server configuration successfully", "Import konfiguracji serwera zakończono pomyślnie"),
("Export server configuration successfully", "Eksport konfiguracji serwera zakończono pomyślnie"),
("Invalid server configuration", "Nieprawidłowa konfiguracja serwera"),
("Clipboard is empty", "Schowek jest pusty"),
("Stop service", "Zatrzymaj usługę"),
@ -47,7 +47,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("ID Server", "Serwer ID"),
("Relay Server", "Serwer pośredniczący"),
("API Server", "Serwer API"),
("invalid_http", "Nieprawidłowy żądanie http"),
("invalid_http", "Nieprawidłowe żądanie http"),
("Invalid IP", "Nieprawidłowe IP"),
("id_change_tip", "Nowy ID może być złożony z małych i dużych liter a-zA-z, cyfry 0-9 oraz _ (podkreślenie). Pierwszym znakiem powinna być litera a-zA-Z, a całe ID powinno składać się z 6 do 16 znaków."),
("Invalid format", "Nieprawidłowy format"),
@ -72,7 +72,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please try 1 minute later", "Spróbuj za minutę"),
("Login Error", "Błąd logowania"),
("Successful", "Sukces"),
("Connected, waiting for image...", "Połączono, czekam na obraz..."),
("Connected, waiting for image...", "Połączono, oczekiwanie na obraz..."),
("Name", "Nazwa"),
("Type", "Typ"),
("Modified", "Zmodyfikowany"),
@ -95,8 +95,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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 the file of this directory?", "Czy na pewno chcesz usunąć pliki z tego katalog?"),
("Do this for all conflicts", "Zrób to dla wszystkich konfliktów"),
("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!"),
("Deleting", "Usuwanie"),
("files", "pliki"),
@ -121,15 +121,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show quality monitor", "Pokazuj jakość monitora"),
("Disable clipboard", "Wyłącz schowek"),
("Lock after session end", "Zablokuj po zakończeniu sesji"),
("Insert", "Wstaw"),
("Insert Lock", "Wstaw blokadę"),
("Insert", "Wyślij"),
("Insert Lock", "Wyślij Zablokuj"),
("Refresh", "Odśwież"),
("ID does not exist", "ID nie istnieje"),
("Failed to connect to rendezvous server", "Nie udało się połączyć z serwerem połączeń"),
("Please try later", "Spróbuj później"),
("Remote desktop is offline", "Zdalny pulpit jest offline"),
("Key mismatch", "Niezgodność klucza"),
("Timeout", "Przekroczenie czasu"),
("Timeout", "Przekroczono czas oczekiwania"),
("Failed to connect to relay server", "Nie udało się połączyć z serwerem pośredniczącym"),
("Failed to connect via rendezvous server", "Nie udało się połączyć przez serwer połączeń"),
("Failed to connect via relay server", "Nie udało się połączyć przez serwer pośredniczący"),
@ -195,7 +195,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Warning", "Ostrzeżenie"),
("Login screen using Wayland is not supported", "Ekran logowania korzystający z Wayland nie jest obsługiwany"),
("Reboot required", "Wymagany ponowne uruchomienie"),
("Unsupported display server ", "Nieobsługiwany serwer wyświetlania "),
("Unsupported display server ", "Nieobsługiwany serwer wyświetlania"),
("x11 expected", "Wymagany jest X11"),
("Port", "Port"),
("Settings", "Ustawienia"),
@ -206,19 +206,19 @@ 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", "Podpowiedź do białej listy"),
("whitelist_tip", "Zezwlaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
("Login", "Zaloguj"),
("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", "Seperator białej listy"),
("whitelist_sep", "Oddzielone przecinkiem, średnikiem, spacją lub w nowej linii"),
("Add ID", "Dodaj ID"),
("Add Tag", "Dodaj Tag"),
("Unselect all tags", "Odznacz wszystkie tagi"),
("Network error", "Błąd sieci"),
("Username missed", "Brak użytkownika"),
("Password missed", "Brak hasła"),
("Username missed", "Nieprawidłowe nazwa użytkownika"),
("Password missed", "Nieprawidłowe hasło"),
("Wrong credentials", "Błędne dane uwierzytelniające"),
("Edit Tag", "Edytuj tag"),
("Unremember Password", "Zapomnij hasło"),
@ -226,7 +226,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Favorites", "Dodaj do ulubionych"),
("Remove from Favorites", "Usuń z ulubionych"),
("Empty", "Pusty"),
("Invalid folder name", "Błędna nazwa folderu"),
("Invalid folder name", "Nieprawidłowa nazwa folderu"),
("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Nazwa hosta"),
("Discovered", "Wykryte"),
@ -242,12 +242,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Left Mouse", "Lewy klik myszy"),
("One-Long Tap", "Przytrzymaj jednym palcem"),
("Two-Finger Tap", "Dotknij dwoma palcami"),
("Right Mouse", "Prawy klik myszy"),
("One-Finger Move", "Ruch jednym palcem"),
("Right Mouse", "Prawy przycisk myszy"),
("One-Finger Move", "Przesuń jednym palcem"),
("Double Tap & Move", "Dotknij dwukrotnie i przesuń"),
("Mouse Drag", "Przeciągnij myszą"),
("Three-Finger vertically", "Trzy palce wertykalnie"),
("Mouse Wheel", "Skrol myszy"),
("Three-Finger vertically", "Trzy palce pionowo"),
("Mouse Wheel", "Kółko myszy"),
("Two-Finger Move", "Ruch dwoma palcami"),
("Canvas Move", "Ruch ekranu"),
("Pinch to Zoom", "Uszczypnij, aby powiększyć"),
@ -289,7 +289,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Someone turns on privacy mode, exit", "Ktoś włącza tryb prywatności, wyjdź"),
("Unsupported", "Niewspierane"),
("Peer denied", "Odmowa dostępu"),
("Please install plugins", "Zainstaluj plugin"),
("Please install plugins", "Zainstaluj wtyczkę"),
("Peer exit", "Wyjście peer"),
("Failed to turn off", "Nie udało się wyłączyć"),
("Turned off", "Wyłączony"),
@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Zablokuj Wykrywanie LAN"),
("Write a message", "Napisz wiadomość"),
("Prompt", "Monit"),
("elevation_prompt", "Monit o podwyższeniu uprawnień"),
("uac_warning", "Ostrzeżenie UAC"),
("elevated_foreground_window_warning", "Pierwszoplanowe okno ostrzeżenia o podwyższeniu uprawnień"),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "Rozłączone"),
("Other", "Inne"),
("Confirm before closing multiple tabs", "Potwierdź przed zamknięciem wielu kart"),
@ -385,9 +384,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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."),
("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", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
("Show RustDesk", "Pokaż RustDesk"),
("This PC", "Ten komputer"),
("or", "albo"),
("Continue with", "Kontynuuj z"),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -30,7 +30,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("IP Whitelisting", "Whitelist de IP"),
("ID/Relay Server", "Servidor ID/Relay"),
("Import Server Config", "Importar Configuração do Servidor"),
("Export Server Config", ""),
("Export Server Config", "Exportar Configuração do Servidor"),
("Import server configuration successfully", "Configuração do servidor importada com sucesso"),
("Export server configuration successfully", ""),
("Invalid server configuration", "Configuração do servidor inválida"),
@ -39,7 +39,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Change ID", "Alterar ID"),
("Website", "Website"),
("About", "Sobre"),
("Mute", "Emudecer"),
("Mute", "Silenciar"),
("Audio Input", "Entrada de Áudio"),
("Enhancements", "Melhorias"),
("Hardware Codec", ""),
@ -89,8 +89,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Delete", "Apagar"),
("Properties", "Propriedades"),
("Multi Select", "Selecção Múltipla"),
("Select All", ""),
("Unselect All", ""),
("Select All", "Selecionar tudo"),
("Unselect All", "Desmarcar todos"),
("Empty Directory", "Directório Vazio"),
("Not an empty directory", "Directório não está vazio"),
("Are you sure you want to delete this file?", "Tem certeza que deseja apagar este ficheiro?"),
@ -116,7 +116,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Good image quality", "Qualidade visual boa"),
("Balanced", "Equilibrada"),
("Optimize reaction time", "Optimizar tempo de reacção"),
("Custom", ""),
("Custom", "Personalizado"),
("Show remote cursor", "Mostrar cursor remoto"),
("Show quality monitor", ""),
("Disable clipboard", "Desabilitar área de transferência"),
@ -137,7 +137,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Set Password", "Definir palavra-chave"),
("OS Password", "Senha do SO"),
("install_tip", "Devido ao UAC, o RustDesk não funciona correctamente em alguns casos. Para evitar o UAC, por favor clique no botão abaixo para instalar o RustDesk no sistema."),
("Click to upgrade", ""),
("Click to upgrade", "Clique para atualizar"),
("Click to download", "Clique para carregar"),
("Click to update", "Clique para fazer a actualização"),
("Configure", "Configurar"),
@ -161,8 +161,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Action", "Acção"),
("Add", "Adicionar"),
("Local Port", "Porta Local"),
("Local Address", ""),
("Change Local Port", ""),
("Local Address", "Endereço local"),
("Change Local Port", "Alterar porta local"),
("setup_server_tip", "Para uma ligação mais rápida, por favor configure seu próprio servidor"),
("Too short, at least 6 characters.", "Muito curto, pelo menos 6 caracteres."),
("The confirmation is not identical.", "A confirmação não é idêntica."),
@ -197,7 +197,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Reinicialização necessária"),
("Unsupported display server ", "Servidor de display não suportado"),
("x11 expected", "x11 em falha"),
("Port", ""),
("Port", "Porta"),
("Settings", "Configurações"),
("Username", "Nome de utilizador"),
("Invalid port", "Porta inválida"),
@ -250,13 +250,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Mouse Wheel", "Roda do rato"),
("Two-Finger Move", "Mover com dois dedos"),
("Canvas Move", "Mover Tela"),
("Pinch to Zoom", "Beliscar para Zoom"),
("Pinch to Zoom", "Clique para ampliar"),
("Canvas Zoom", "Zoom na Tela"),
("Reset canvas", "Reiniciar tela"),
("No permission of file transfer", "Sem permissões de transferência de ficheiro"),
("Note", "Nota"),
("Connection", "Ligação"),
("Share Screen", "Partilhar ecran"),
("Share Screen", "Partilhar ecrã"),
("CLOSE", "FECHAR"),
("OPEN", "ABRIR"),
("Chat", "Conversar"),
@ -371,15 +371,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
("Keyboard Settings", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "Desconectado"),
("Other", "Outro"),
("Confirm before closing multiple tabs", "Confirme antes de fechar vários separadores"),
("Keyboard Settings", "Configurações do teclado"),
("Custom", ""),
("Full Access", ""),
("Full Access", "Controlo total"),
("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland requer Ubuntu 21.04 ou versão superior."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland requer uma versão superior da distribuição linux. Por favor, tente o desktop X11 ou mude seu sistema operacional."),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Negar descoberta da LAN"),
("Write a message", "Escrever uma mensagem"),
("Prompt", "Prompt de comando"),
("elevation_prompt", "Prompt de comando (Admin)"),
("uac_warning", "Aviso UAC"),
("elevated_foreground_window_warning", "Aviso de janela de primeiro plano elevado"),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", "Desconectado"),
("Other", "Outro"),
("Confirm before closing multiple tabs", "Confirmar antes de fechar múltiplas abas"),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", "Este PC"),
("or", "ou"),
("Continue with", "Continuar com"),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -136,7 +136,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Failed to make direct connection to remote desktop", "Не удалось установить прямое подключение к удалённому рабочему столу"),
("Set Password", "Установить пароль"),
("OS Password", "Пароль ОС"),
("install_tip", "В некоторых случаях из-за UAC RustDesk может работать некорректно на удалённом узле. Чтобы избежать UAC, нажмите кнопку ниже, чтобы установить RustDesk в системе."),
("install_tip", "В некоторых случаях из-за UAC RustDesk может работать неправильно на удалённом узле. Чтобы избежать UAC, нажмите кнопку ниже, чтобы установить RustDesk в системе."),
("Click to upgrade", "Нажмите, чтобы проверить наличие обновлений"),
("Click to download", "Нажмите, чтобы скачать"),
("Click to update", "Нажмите, чтобы обновить"),
@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Запретить обнаружение в локальной сети"),
("Write a message", "Написать сообщение"),
("Prompt", "Подсказка"),
("elevation_prompt", "Запуск программного обеспечения без повышения привилегий может вызвать проблемы, когда удалённые пользователи работают с определёнными окнами."),
("uac_warning", "Временно отказано в доступе из-за запроса на повышение прав. Подождите, пока удалённый пользователь примет диалоговое окно UAC. Чтобы избежать этой проблемы, рекомендуется устанавливать программное обеспечение на удалённое устройство или запускать его с правами администратора."),
("elevated_foreground_window_warning", "Временно невозможно использовать мышь и клавиатуру, поскольку текущее окно удалённого рабочего стола требует более высоких привилегий для работы, вы можете попросить удалённого пользователя свернуть текущее окно. Чтобы избежать этой проблемы, рекомендуется устанавливать программное обеспечение на удалённое устройство или запускать его с правами администратора."),
("Please wait for confirmation of UAC...", "Дождитесь подтверждения UAC..."),
("elevated_foreground_window_tip", "Текущее окно удалённого рабочего стола требует более высоких привилегий для работы, поэтому временно невозможно использовать мышь и клавиатуру. Можно попросить удалённого пользователя свернуть текущее окно или нажать кнопку повышения прав в окне управления подключением. Чтобы избежать этой проблемы в дальнейшем, рекомендуется выполнить установку программного обеспечения на удалённом устройстве."),
("Disconnected", "Отключено"),
("Other", "Другое"),
("Confirm before closing multiple tabs", "Подтверждение закрытия несколько вкладок"),
@ -387,7 +386,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Пожалуйста, выберите экран для совместного использования (работайте на одноранговой стороне)."),
("Show RustDesk", "Показать RustDesk"),
("This PC", "Этот компьютер"),
("or", ""),
("Continue with", ""),
("or", "или"),
("Continue with", "Продолжить с"),
("Elevate", "Повысить"),
("Zoom cursor", "Масштабировать курсор"),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -41,9 +41,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("About", "Hakkında"),
("Mute", "Sustur"),
("Audio Input", "Ses Girişi"),
("Enhancements", ""),
("Hardware Codec", ""),
("Adaptive Bitrate", ""),
("Enhancements", "Geliştirmeler"),
("Hardware Codec", "Donanımsal Codec"),
("Adaptive Bitrate", "Uyarlanabilir Bit Hızı"),
("ID Server", "ID Sunucu"),
("Relay Server", "Relay Sunucu"),
("API Server", "API Sunucu"),
@ -89,8 +89,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Delete", "Sil"),
("Properties", "Özellikler"),
("Multi Select", "Çoklu Seçim"),
("Select All", ""),
("Unselect All", ""),
("Select All", "Tümünü Seç"),
("Unselect All", "Tüm Seçimi Kaldır"),
("Empty Directory", "Boş Klasör"),
("Not an empty directory", "Klasör boş değil"),
("Are you sure you want to delete this file?", "Bu dosyayı silmek istediğinize emin misiniz?"),
@ -116,9 +116,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Good image quality", "İyi görüntü kalitesi"),
("Balanced", "Dengelenmiş"),
("Optimize reaction time", "Tepki süresini optimize et"),
("Custom", ""),
("Custom", "Özel"),
("Show remote cursor", "Uzaktaki fare imlecini göster"),
("Show quality monitor", ""),
("Show quality monitor", "Kalite monitörünü göster"),
("Disable clipboard", "Hafızadaki kopyalanmışları engelle"),
("Lock after session end", "Bağlantıdan sonra kilitle"),
("Insert", "Ekle"),
@ -161,8 +161,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Action", "Eylem"),
("Add", "Ekle"),
("Local Port", "Yerel Port"),
("Local Address", ""),
("Change Local Port", ""),
("Local Address", "Yerel Adres"),
("Change Local Port", "Yerel Port'u Değiştir"),
("setup_server_tip", "Daha hızlı bağlantı için kendi sunucunuzu kurun"),
("Too short, at least 6 characters.", "Çok kısa en az 6 karakter gerekli."),
("The confirmation is not identical.", "Doğrulama yapılamadı."),
@ -197,7 +197,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Yeniden başlatma gerekli"),
("Unsupported display server ", "Desteklenmeyen görüntü sunucusu"),
("x11 expected", "x11 bekleniyor"),
("Port", ""),
("Port", "Port"),
("Settings", "Ayarlar"),
("Username", "Kullanıcı Adı"),
("Invalid port", "Geçersiz port"),
@ -278,7 +278,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Hizmetin kapatılması, kurulan tüm bağlantıları otomatik olarak kapatacaktır."),
("android_version_audio_tip", "Mevcut Android sürümü ses yakalamayı desteklemiyor, lütfen Android 10 veya sonraki bir sürüme yükseltin."),
("android_start_service_tip", "Ekran paylaşım hizmetini başlatmak için [Hizmeti Başlat] veya AÇ [Ekran Yakalama] iznine dokunun."),
("Account", ""),
("Account", "Hesap"),
("Overwrite", "üzerine yaz"),
("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"),
("Quit", "Çıkış"),
@ -300,21 +300,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", "bağlantıya izin verilmedi"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Legacy mode", "Eski mod"),
("Map mode", "Haritalama modu"),
("Translate mode", "Çeviri modu"),
("Use temporary password", "Geçici şifre kullan"),
("Use permanent password", "Kalıcı şifre kullan"),
("Use both passwords", "İki şifreyide kullan"),
("Set permanent password", "Kalıcı şifre oluştur"),
("Set temporary password length", ""),
("Set temporary password length", "Geçici şifre oluştur"),
("Enable Remote Restart", "Uzaktan yeniden başlatmayı aktif et"),
("Allow remote restart", "Uzaktan yeniden başlatmaya izin ver"),
("Restart Remote Device", "Uzaktaki cihazı yeniden başlat"),
("Are you sure you want to restart", "Yeniden başlatmak istediğinize emin misin?"),
("Restarting Remote Device", "Uzaktan yeniden başlatılıyor"),
("remote_restarting_tip", ""),
("Copied", ""),
("remote_restarting_tip", "remote_restarting_tip"),
("Copied", "Kopyalandı"),
("Exit Fullscreen", "Tam ekrandan çık"),
("Fullscreen", "Tam ekran"),
("Mobile Actions", "Mobil İşlemler"),
@ -332,62 +332,63 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Insecure Connection", "Güvenli Bağlantı"),
("Scale original", "Orijinali ölçeklendir"),
("Scale adaptive", "Ölçek uyarlanabilir"),
("General", ""),
("Security", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
("Temporary Password Length", ""),
("Unlock Network Settings", ""),
("Server", ""),
("Direct IP Access", ""),
("Proxy", ""),
("Port", ""),
("Apply", ""),
("Disconnect all devices?", ""),
("Clear", ""),
("Audio Input Device", ""),
("Deny remote access", ""),
("Use IP Whitelisting", ""),
("Network", ""),
("Enable RDP", ""),
("General", "Genel"),
("Security", "Güvenlik"),
("Account", "Hesap"),
("Theme", "Tema"),
("Dark Theme", "Koyu Tema"),
("Dark", "Koyu"),
("Light", "ık"),
("Follow System", "Sisteme Uy"),
("Enable hardware codec", "Donanımsal codec aktif et"),
("Unlock Security Settings", "Güvenlik Ayarlarını"),
("Enable Audio", "Sesi Aktif Et"),
("Temporary Password Length", "Geçici Şifre Uzunluğu"),
("Unlock Network Settings", "Ağ Ayarlarını"),
("Server", "Sunucu"),
("Direct IP Access", "Direk IP Erişimi"),
("Proxy", "Vekil"),
("Port", "Port"),
("Apply", "Uygula"),
("Disconnect all devices?", "Tüm cihazların bağlantısını kes?"),
("Clear", "Temizle"),
("Audio Input Device", "Ses Giriş Aygıtı"),
("Deny remote access", "Uzak erişime izin verme"),
("Use IP Whitelisting", "IP Beyaz Listeyi Kullan"),
("Network", ""),
("Enable RDP", "RDP Aktif Et"),
("Pin menubar", "Menü çubuğunu sabitle"),
("Unpin menubar", "Menü çubuğunun sabitlemesini kaldır"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
("Recording", "Kayıt Ediliyor"),
("Directory", "Klasör"),
("Automatically record incoming sessions", "Gelen oturumları otomatik olarak kayıt et"),
("Change", "Değiştir"),
("Start session recording", "Oturum kaydını başlat"),
("Stop session recording", "Oturum kaydını sonlandır"),
("Enable Recording Session", "Kayıt Oturumunu Aktif Et"),
("Allow recording session", "Oturum kaydına izin ver"),
("Enable LAN Discovery", "Yerel Ağ Keşfine İzin Ver"),
("Deny LAN Discovery", "Yerl Ağ Keşfine İzin Verme"),
("Write a message", "Bir mesaj yazın"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
("Keyboard Settings", ""),
("Custom", ""),
("Full Access", ""),
("Screen Share", ""),
("Please wait for confirmation of UAC...", "UAC onayı için lütfen bekleyiniz..."),
("elevated_foreground_window_tip", "elevated_foreground_window_tip"),
("Disconnected", "Bağlantı Kesildi"),
("Other", "Diğer"),
("Confirm before closing multiple tabs", "Çoklu sekmeleri kapatmadan önce onayla"),
("Keyboard Settings", "Klavye Ayarları"),
("Custom", "Özel"),
("Full Access", "Tam Erişim"),
("Screen Share", "Ekran Paylaşımı"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland, Ubuntu 21.04 veya daha yüksek bir sürüm gerektirir."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland, linux dağıtımının daha yüksek bir sürümünü gerektirir. Lütfen X11 masaüstünü deneyin veya işletim sisteminizi değiştirin."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Lütfen paylaşılacak ekranı seçiniz (Ekran tarafında çalıştırın)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
("Show RustDesk", "RustDesk'i Göster"),
("This PC", "Bu PC"),
("or", "veya"),
("Continue with", "bununla devam et"),
("Elevate", "Yükseltme"),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "拒絕局域網發現"),
("Write a message", "輸入聊天消息"),
("Prompt", "提示"),
("elevation_prompt", "以當前用戶權限運行軟件,可能導致遠端在訪問本機時,沒有足夠的權限來操作部分窗口。"),
("uac_warning", "暂时无法访问远端设备因为远端设备正在请求用户账户权限请等待对方关闭UAC窗口。为避免这个问题建议在远端设备上安装或者以管理员权限运行本软件。"),
("elevated_foreground_window_warning", "暫時無法使用鼠標鍵盤,因為遠端桌面的當前窗口需要更高的權限才能操作, 可以請求對方最小化當前窗口。為避免這個問題,建議在遠端設備上安裝或者以管理員權限運行本軟件。"),
("Please wait for confirmation of UAC...", "請等待對方確認UAC"),
("elevated_foreground_window_tip", "遠端桌面的當前窗口需要更高的權限才能操作, 暫時無法使用鼠標鍵盤, 可以請求對方最小化當前窗口, 或者在連接管理窗口點擊提升。為避免這個問題,建議在遠端設備上安裝本軟件。"),
("Disconnected", "會話已結束"),
("Other", "其他"),
("Confirm before closing multiple tabs", "關閉多個分頁前跟我確認"),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", "提權"),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Заборонити виявлення локальної мережі"),
("Write a message", "Написати повідомлення"),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -371,9 +371,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("elevation_prompt", ""),
("uac_warning", ""),
("elevated_foreground_window_warning", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
@ -389,5 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
].iter().cloned().collect();
}

View File

@ -14,6 +14,7 @@ pub mod macos;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::{message_proto::CursorData, ResultType};
#[cfg(not(target_os = "macos"))]
const SERVICE_INTERVAL: u64 = 300;

View File

@ -63,7 +63,7 @@ pub fn get_cursor() -> ResultType<Option<u64>> {
unsafe {
let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init();
ci.cbSize = std::mem::size_of::<CURSORINFO>() as _;
if GetCursorInfo(&mut ci) == FALSE {
if crate::portable_service::client::get_cursor_info(&mut ci) == FALSE {
return Err(io::Error::last_os_error().into());
}
if ci.flags & CURSOR_SHOWING == 0 {
@ -1480,6 +1480,27 @@ pub fn get_user_token(session_id: u32, as_user: bool) -> HANDLE {
}
}
pub fn run_background(exe: &str, arg: &str) -> ResultType<bool> {
let wexe = wide_string(exe);
let warg;
unsafe {
let ret = ShellExecuteW(
NULL as _,
NULL as _,
wexe.as_ptr() as _,
if arg.is_empty() {
NULL as _
} else {
warg = wide_string(arg);
warg.as_ptr() as _
},
NULL as _,
SW_HIDE,
);
return Ok(ret as i32 > 32);
}
}
pub fn run_uac(exe: &str, arg: &str) -> ResultType<bool> {
let wop = wide_string("runas");
let wexe = wide_string(exe);
@ -1532,6 +1553,13 @@ pub fn run_as_system(arg: &str) -> ResultType<()> {
pub fn elevate_or_run_as_system(is_setup: bool, is_elevate: bool, is_run_as_system: bool) {
// avoid possible run recursively due to failed run.
log::info!(
"elevate:{}->{:?}, run_as_system:{}->{}",
is_elevate,
is_elevated(None),
is_run_as_system,
crate::username(),
);
let arg_elevate = if is_setup {
"--noinstall --elevate"
} else {
@ -1542,9 +1570,11 @@ pub fn elevate_or_run_as_system(is_setup: bool, is_elevate: bool, is_run_as_syst
} else {
"--run-as-system"
};
if is_root() {
log::debug!("portable run as system user");
if is_run_as_system {
log::info!("run portable service");
crate::portable_service::server::run_portable_service();
}
} else {
match is_elevated(None) {
Ok(elevated) => {
@ -1646,3 +1676,39 @@ fn wide_string(s: &str) -> Vec<u16> {
.chain(Some(0).into_iter())
.collect()
}
/// send message to currently shown window
pub fn send_message_to_hnwd(
class_name: &str,
window_name: &str,
dw_data: usize,
data: &str,
show_window: bool,
) -> bool {
unsafe {
let class_name_utf16 = wide_string(class_name);
let window_name_utf16 = wide_string(window_name);
let window = FindWindowW(class_name_utf16.as_ptr(), window_name_utf16.as_ptr());
if window.is_null() {
log::warn!("no such window {}:{}", class_name, window_name);
return false;
}
let mut data_struct = COPYDATASTRUCT::default();
data_struct.dwData = dw_data;
let mut data_zero: String = data.chars().chain(Some('\0').into_iter()).collect();
println!("send {:?}", data_zero);
data_struct.cbData = data_zero.len() as _;
data_struct.lpData = data_zero.as_mut_ptr() as _;
SendMessageW(
window,
WM_COPYDATA,
0,
&data_struct as *const COPYDATASTRUCT as _,
);
if show_window {
ShowWindow(window, SW_NORMAL);
SetForegroundWindow(window);
}
}
return true;
}

View File

@ -5,7 +5,7 @@ use hbb_common::{
allow_err,
anyhow::{anyhow, Context},
bail,
config::{Config, Config2, CONNECT_TIMEOUT, RELAY_PORT},
config::{Config, CONNECT_TIMEOUT, RELAY_PORT},
log,
message_proto::*,
protobuf::{Enum, Message as _},
@ -14,7 +14,11 @@ use hbb_common::{
sodiumoxide::crypto::{box_, secretbox, sign},
timeout, tokio, ResultType, Stream,
};
use service::{GenericService, Service, ServiceTmpl, Subscriber};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::config::Config2;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use service::ServiceTmpl;
use service::{GenericService, Service, Subscriber};
use std::{
collections::HashMap,
net::SocketAddr,
@ -45,6 +49,8 @@ pub const NAME_POS: &'static str = "";
}
mod connection;
#[cfg(windows)]
pub mod portable_service;
mod service;
mod video_qos;
pub mod video_service;
@ -56,6 +62,7 @@ type ConnMap = HashMap<i32, ConnInner>;
lazy_static::lazy_static! {
pub static ref CHILD_PROCESS: Childs = Default::default();
pub static ref CONN_COUNT: Arc<Mutex<usize>> = Default::default();
}
pub struct Server {
@ -255,6 +262,7 @@ impl Server {
}
}
self.connections.insert(conn.id(), conn);
*CONN_COUNT.lock().unwrap() = self.connections.len();
}
pub fn remove_connection(&mut self, conn: &ConnInner) {
@ -262,6 +270,7 @@ impl Server {
s.on_unsubscribe(conn.id());
}
self.connections.remove(&conn.id());
*CONN_COUNT.lock().unwrap() = self.connections.len();
}
pub fn close_connections(&mut self) {

View File

@ -26,10 +26,9 @@ use hbb_common::{
use scrap::android::call_main_service_mouse_input;
use serde_json::{json, value::Value};
use sha2::{Digest, Sha256};
use std::sync::{
atomic::{AtomicI64, Ordering},
mpsc as std_mpsc,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use std::sync::atomic::Ordering;
use std::sync::{atomic::AtomicI64, mpsc as std_mpsc};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use system_shutdown;
@ -234,8 +233,12 @@ 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 is_installed = crate::platform::is_installed();
loop {
tokio::select! {
@ -339,6 +342,12 @@ impl Connection {
};
conn.send(msg_out).await;
}
#[cfg(windows)]
ipc::Data::DataPortableService(ipc::DataPortableService::RequestStart) => {
if let Err(e) = crate::portable_service::client::start_portable_service() {
log::error!("Failed to start portable service from cm:{:?}", e);
}
}
_ => {}
}
},
@ -415,23 +424,36 @@ impl Connection {
}
},
_ = second_timer.tick() => {
let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone();
if last_uac != uac {
last_uac = uac;
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;
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());
#[cfg(windows)]
{
if !is_installed {
let portable_service_running = crate::portable_service::client::PORTABLE_SERVICE_RUNNING.lock().unwrap().clone();
let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone();
if last_uac != uac {
last_uac = uac;
if !uac || !portable_service_running{
let mut misc = Misc::new();
misc.set_uac(uac);
let mut msg = Message::new();
msg.set_misc(misc);
conn.inner.send(msg.into());
}
}
let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap().clone();
if last_foreground_window_elevated != foreground_window_elevated {
last_foreground_window_elevated = foreground_window_elevated;
if !foreground_window_elevated || !portable_service_running {
let mut misc = Misc::new();
misc.set_foreground_window_elevated(foreground_window_elevated);
let mut msg = Message::new();
msg.set_misc(misc);
conn.inner.send(msg.into());
}
}
let show_elevation = !portable_service_running;
conn.send_to_cm(ipc::Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show_elevation)));
}
}
}
_ = test_delay_timer.tick() => {

View File

@ -6,7 +6,9 @@
use dbus::blocking::Connection;
use dbus_crossroads::{Crossroads, IfaceBuilder};
use hbb_common::{log};
use std::{error::Error, fmt, time::Duration, collections::HashMap};
use std::{error::Error, fmt, time::Duration};
#[cfg(feature = "flutter")]
use std::collections::HashMap;
const DBUS_NAME: &str = "org.rustdesk.rustdesk";
const DBUS_PREFIX: &str = "/dbus";
@ -65,7 +67,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) {
DBUS_METHOD_NEW_CONNECTION,
(DBUS_METHOD_NEW_CONNECTION_ID,),
(DBUS_METHOD_RETURN,),
move |_, _, (peer_id,): (String,)| {
move |_, _, (_peer_id,): (String,)| {
#[cfg(feature = "flutter")]
{
use crate::flutter::{self, APP_TYPE_MAIN};
@ -77,7 +79,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) {
{
let data = HashMap::from([
("name", "new_connection"),
("peer_id", peer_id.as_str())
("peer_id", _peer_id.as_str())
]);
if !stream.add(serde_json::ser::to_string(&data).unwrap_or("".to_string())) {
log::error!("failed to add dbus message to flutter global dbus stream.");

View File

@ -13,6 +13,8 @@ use std::{
time::Instant,
};
const INVALID_CURSOR_POS: i32 = i32::MIN;
#[derive(Default)]
struct StateCursor {
hcursor: u64,
@ -28,14 +30,33 @@ impl super::service::Reset for StateCursor {
}
}
#[derive(Default)]
struct StatePos {
cursor_pos: (i32, i32),
}
impl Default for StatePos {
fn default() -> Self {
Self {
cursor_pos: (INVALID_CURSOR_POS, INVALID_CURSOR_POS),
}
}
}
impl super::service::Reset for StatePos {
fn reset(&mut self) {
self.cursor_pos = (0, 0);
self.cursor_pos = (INVALID_CURSOR_POS, INVALID_CURSOR_POS);
}
}
impl StatePos {
#[inline]
fn is_valid(&self) -> bool {
self.cursor_pos.0 != INVALID_CURSOR_POS
}
#[inline]
fn is_moved(&self, x: i32, y: i32) -> bool {
self.is_valid() && (self.cursor_pos.0 != x || self.cursor_pos.1 != y)
}
}
@ -105,7 +126,7 @@ pub fn new_pos() -> GenericService {
}
fn update_last_cursor_pos(x: i32, y: i32) {
let mut lock = LATEST_CURSOR_POS.lock().unwrap();
let mut lock = LATEST_SYS_CURSOR_POS.lock().unwrap();
if lock.1 .0 != x || lock.1 .1 != y {
(lock.0, lock.1) = (Instant::now(), (x, y))
}
@ -114,8 +135,7 @@ fn update_last_cursor_pos(x: i32, y: i32) {
fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
if let Some((x, y)) = crate::get_cursor_pos() {
update_last_cursor_pos(x, y);
if state.cursor_pos.0 != x || state.cursor_pos.1 != y {
state.cursor_pos = (x, y);
if state.is_moved(x, y) {
let mut msg_out = Message::new();
msg_out.set_cursor_position(CursorPosition {
x,
@ -124,7 +144,7 @@ fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
});
let exclude = {
let now = get_time();
let lock = LATEST_INPUT_CURSOR.lock().unwrap();
let lock = LATEST_PEER_INPUT_CURSOR.lock().unwrap();
if now - lock.time < 300 {
lock.conn
} else {
@ -133,6 +153,7 @@ fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
};
sp.send_without(msg_out, exclude);
}
state.cursor_pos = (x, y);
}
sp.snapshot(|sps| {
@ -182,12 +203,13 @@ lazy_static::lazy_static! {
Arc::new(Mutex::new(Enigo::new()))
};
static ref KEYS_DOWN: Arc<Mutex<HashMap<u64, Instant>>> = Default::default();
static ref LATEST_INPUT_CURSOR: Arc<Mutex<Input>> = Default::default();
static ref LATEST_CURSOR_POS: Arc<Mutex<(Instant, (i32, i32))>> = Arc::new(Mutex::new((Instant::now().sub(MOUSE_MOVE_PROTECTION_TIMEOUT), (0, 0))));
static ref LATEST_PEER_INPUT_CURSOR: Arc<Mutex<Input>> = Default::default();
static ref LATEST_SYS_CURSOR_POS: Arc<Mutex<(Instant, (i32, i32))>> = Arc::new(Mutex::new((Instant::now().sub(MOUSE_MOVE_PROTECTION_TIMEOUT), (0, 0))));
}
static EXITING: AtomicBool = AtomicBool::new(false);
const MOUSE_MOVE_PROTECTION_TIMEOUT: Duration = Duration::from_millis(1_000);
// Actual diff of (x,y) is (1,1) here. But 5 may be tolerant.
const MOUSE_ACTIVE_DISTANCE: i32 = 5;
// mac key input must be run in main thread, otherwise crash on >= osx 10.15
@ -258,14 +280,30 @@ fn get_modifier_state(key: Key, en: &mut Enigo) -> bool {
}
pub fn handle_mouse(evt: &MouseEvent, conn: i32) {
if !active_mouse_(conn) {
return;
}
let evt_type = evt.mask & 0x7;
if evt_type == 0 {
let time = get_time();
*LATEST_PEER_INPUT_CURSOR.lock().unwrap() = Input {
time,
conn,
x: evt.x,
y: evt.y,
};
}
#[cfg(target_os = "macos")]
if !*IS_SERVER {
// having GUI, run main GUI thread, otherwise crash
let evt = evt.clone();
QUEUE.exec_async(move || handle_mouse_(&evt, conn));
QUEUE.exec_async(move || handle_mouse_(&evt));
return;
}
handle_mouse_(evt, conn);
#[cfg(windows)]
crate::portable_service::client::handle_mouse(evt);
#[cfg(not(windows))]
handle_mouse_(evt);
}
pub fn fix_key_down_timeout_loop() {
@ -373,55 +411,58 @@ fn fix_modifiers(modifiers: &[EnumOrUnknown<ControlKey>], en: &mut Enigo, ck: i3
}
}
fn is_mouse_active_by_conn(conn: i32) -> bool {
fn active_mouse_(conn: i32) -> bool {
// out of time protection
if LATEST_CURSOR_POS.lock().unwrap().0.elapsed() > MOUSE_MOVE_PROTECTION_TIMEOUT {
if LATEST_SYS_CURSOR_POS.lock().unwrap().0.elapsed() > MOUSE_MOVE_PROTECTION_TIMEOUT {
return true;
}
let mut last_input = LATEST_INPUT_CURSOR.lock().unwrap();
// last conn input may be protected
if last_input.conn != conn {
if LATEST_PEER_INPUT_CURSOR.lock().unwrap().conn != conn {
return false;
}
// check if input is in valid range
let in_actived_dist = |a: i32, b: i32| -> bool { (a - b).abs() < MOUSE_ACTIVE_DISTANCE };
// Check if input is in valid range
match crate::get_cursor_pos() {
Some((x, y)) => {
let is_same_input = (last_input.x - x).abs() < MOUSE_ACTIVE_DISTANCE
&& (last_input.y - y).abs() < MOUSE_ACTIVE_DISTANCE;
if !is_same_input {
last_input.x = -MOUSE_ACTIVE_DISTANCE * 2;
last_input.y = -MOUSE_ACTIVE_DISTANCE * 2;
let (last_in_x, last_in_y) = {
let lock = LATEST_PEER_INPUT_CURSOR.lock().unwrap();
(lock.x, lock.y)
};
let mut can_active = in_actived_dist(last_in_x, x) && in_actived_dist(last_in_y, y);
// The cursor may not have been moved to last input position if system is busy now.
// While this is not a common case, we check it again after some time later.
if !can_active {
// 10 micros may be enough for system to move cursor.
// We do not care about the situation which system is too slow(more than 10 micros is required).
std::thread::sleep(std::time::Duration::from_micros(10));
// Sleep here can also somehow suppress delay accumulation.
if let Some((x2, y2)) = crate::get_cursor_pos() {
can_active = in_actived_dist(last_in_x, x2) && in_actived_dist(last_in_y, y2);
}
}
is_same_input
if !can_active {
let mut lock = LATEST_PEER_INPUT_CURSOR.lock().unwrap();
lock.x = INVALID_CURSOR_POS / 2;
lock.y = INVALID_CURSOR_POS / 2;
}
can_active
}
None => true,
}
}
fn handle_mouse_(evt: &MouseEvent, conn: i32) {
pub fn handle_mouse_(evt: &MouseEvent) {
if EXITING.load(Ordering::SeqCst) {
return;
}
if !is_mouse_active_by_conn(conn) {
return;
}
#[cfg(windows)]
crate::platform::windows::try_change_desktop();
let buttons = evt.mask >> 3;
let evt_type = evt.mask & 0x7;
if evt_type == 0 {
let time = get_time();
*LATEST_INPUT_CURSOR.lock().unwrap() = Input {
time,
conn,
x: evt.x,
y: evt.y,
};
}
let mut en = ENIGO.lock().unwrap();
#[cfg(not(target_os = "macos"))]
let mut to_release = Vec::new();
@ -677,6 +718,9 @@ pub fn handle_key(evt: &KeyEvent) {
QUEUE.exec_async(move || handle_key_(&evt));
return;
}
#[cfg(windows)]
crate::portable_service::client::handle_key(evt);
#[cfg(not(windows))]
handle_key_(evt);
}
@ -928,7 +972,7 @@ fn legacy_keyboard_mode(evt: &KeyEvent) {
}
}
fn handle_key_(evt: &KeyEvent) {
pub fn handle_key_(evt: &KeyEvent) {
if EXITING.load(Ordering::SeqCst) {
return;
}

View File

@ -0,0 +1,800 @@
use core::slice;
use hbb_common::{
allow_err,
anyhow::anyhow,
bail,
config::Config,
log,
message_proto::{KeyEvent, MouseEvent},
protobuf::Message,
tokio::{self, sync::mpsc},
ResultType,
};
use scrap::{Capturer, Frame, TraitCapturer};
use shared_memory::*;
use std::{
mem::size_of,
ops::{Deref, DerefMut},
sync::{Arc, Mutex},
time::Duration,
};
use winapi::{
shared::minwindef::{BOOL, FALSE, TRUE},
um::winuser::{self, CURSORINFO, PCURSORINFO},
};
use crate::{
ipc::{self, new_listener, Connection, Data, DataPortableService},
video_service::get_current_display,
};
use super::video_qos;
const SIZE_COUNTER: usize = size_of::<i32>() * 2;
const FRAME_ALIGN: usize = 64;
const ADDR_CURSOR_PARA: usize = 0;
const ADDR_CURSOR_COUNTER: usize = ADDR_CURSOR_PARA + size_of::<CURSORINFO>();
const ADDR_CAPTURER_PARA: usize = ADDR_CURSOR_COUNTER + SIZE_COUNTER;
const ADDR_CAPTURE_FRAME_SIZE: usize = ADDR_CAPTURER_PARA + size_of::<CapturerPara>();
const ADDR_CAPTURE_WOULDBLOCK: usize = ADDR_CAPTURE_FRAME_SIZE + size_of::<i32>();
const ADDR_CAPTURE_FRAME_COUNTER: usize = ADDR_CAPTURE_WOULDBLOCK + size_of::<i32>();
const ADDR_CAPTURE_FRAME: usize =
(ADDR_CAPTURE_FRAME_COUNTER + SIZE_COUNTER + FRAME_ALIGN - 1) / FRAME_ALIGN * FRAME_ALIGN;
const IPC_PROFIX: &str = "_portable_service";
pub const SHMEM_NAME: &str = "_portable_service";
const MAX_NACK: usize = 3;
const MAX_DXGI_FAIL_TIME: usize = 5;
pub struct SharedMemory {
inner: Shmem,
}
unsafe impl Send for SharedMemory {}
unsafe impl Sync for SharedMemory {}
impl Deref for SharedMemory {
type Target = Shmem;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for SharedMemory {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl SharedMemory {
pub fn create(name: &str, size: usize) -> ResultType<Self> {
let flink = Self::flink(name.to_string());
let shmem = match ShmemConf::new()
.size(size)
.flink(&flink)
.force_create_flink()
.create()
{
Ok(m) => m,
Err(ShmemError::LinkExists) => {
bail!(
"Unable to force create shmem flink {}, which should not happen.",
flink
)
}
Err(e) => {
bail!("Unable to create shmem flink {} : {}", flink, e);
}
};
log::info!("Create shared memory, size:{}, flink:{}", size, flink);
Self::set_all_perm(&flink);
Ok(SharedMemory { inner: shmem })
}
pub fn open_existing(name: &str) -> ResultType<Self> {
let flink = Self::flink(name.to_string());
let shmem = match ShmemConf::new().flink(&flink).allow_raw(true).open() {
Ok(m) => m,
Err(e) => {
bail!("Unable to open existing shmem flink {} : {}", flink, e);
}
};
log::info!("open existing shared memory, flink:{:?}", flink);
Ok(SharedMemory { inner: shmem })
}
pub fn write(&self, addr: usize, data: &[u8]) {
unsafe {
assert!(addr + data.len() <= self.inner.len());
let ptr = self.inner.as_ptr().add(addr);
let shared_mem_slice = slice::from_raw_parts_mut(ptr, data.len());
shared_mem_slice.copy_from_slice(data);
}
}
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);
} else {
shmem_flink = Config::ipc_path("").replace("ipc", "") + &shmem_flink;
}
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();
}
}
}
mod utils {
use core::slice;
use std::mem::size_of;
pub fn i32_to_vec(i: i32) -> Vec<u8> {
i.to_ne_bytes().to_vec()
}
pub fn ptr_to_i32(ptr: *const u8) -> i32 {
unsafe {
let v = slice::from_raw_parts(ptr, size_of::<i32>());
i32::from_ne_bytes([v[0], v[1], v[2], v[3]])
}
}
pub fn counter_ready(counter: *const u8) -> bool {
unsafe {
let wptr = counter;
let rptr = counter.add(size_of::<i32>());
let iw = ptr_to_i32(wptr);
let ir = ptr_to_i32(rptr);
if ir != iw {
std::ptr::copy_nonoverlapping(wptr, rptr as *mut _, size_of::<i32>());
true
} else {
false
}
}
}
pub fn counter_equal(counter: *const u8) -> bool {
unsafe {
let wptr = counter;
let rptr = counter.add(size_of::<i32>());
let iw = ptr_to_i32(wptr);
let ir = ptr_to_i32(rptr);
iw == ir
}
}
pub fn increase_counter(counter: *mut u8) {
unsafe {
let wptr = counter;
let rptr = counter.add(size_of::<i32>());
let iw = ptr_to_i32(counter);
let ir = ptr_to_i32(counter);
let v = i32_to_vec(iw + 1);
std::ptr::copy_nonoverlapping(v.as_ptr(), wptr, size_of::<i32>());
if ir == iw + 1 {
let v = i32_to_vec(iw);
std::ptr::copy_nonoverlapping(v.as_ptr(), rptr, size_of::<i32>());
}
}
}
pub fn align(v: usize, align: usize) -> usize {
(v + align - 1) / align * align
}
}
// functions called in seperate SYSTEM user process.
pub mod server {
use super::*;
lazy_static::lazy_static! {
static ref EXIT: Arc<Mutex<bool>> = Default::default();
}
pub fn run_portable_service() {
let shmem = Arc::new(SharedMemory::open_existing(SHMEM_NAME).unwrap());
let shmem1 = shmem.clone();
let shmem2 = shmem.clone();
let mut threads = vec![];
threads.push(std::thread::spawn(|| {
run_get_cursor_info(shmem1);
}));
threads.push(std::thread::spawn(|| {
run_capture(shmem2);
}));
threads.push(std::thread::spawn(|| {
run_ipc_client();
}));
threads.push(std::thread::spawn(|| {
run_exit_check();
}));
for th in threads.drain(..) {
th.join().unwrap();
log::info!("thread joined");
}
}
fn run_exit_check() {
loop {
if EXIT.lock().unwrap().clone() {
std::thread::sleep(Duration::from_millis(50));
std::process::exit(0);
}
std::thread::sleep(Duration::from_millis(50));
}
}
fn run_get_cursor_info(shmem: Arc<SharedMemory>) {
loop {
if EXIT.lock().unwrap().clone() {
break;
}
unsafe {
let para = shmem.as_ptr().add(ADDR_CURSOR_PARA) as *mut CURSORINFO;
(*para).cbSize = size_of::<CURSORINFO>() as _;
let result = winuser::GetCursorInfo(para);
if result == TRUE {
utils::increase_counter(shmem.as_ptr().add(ADDR_CURSOR_COUNTER));
}
}
// more frequent in case of `Error of mouse_cursor service`
std::thread::sleep(Duration::from_millis(15));
}
}
fn run_capture(shmem: Arc<SharedMemory>) {
let mut c = None;
let mut last_current_display = usize::MAX;
let mut last_use_yuv = false;
let mut last_timeout_ms: i32 = 33;
let mut spf = Duration::from_millis(last_timeout_ms as _);
let mut first_frame_captured = false;
let mut dxgi_failed_times = 0;
loop {
if EXIT.lock().unwrap().clone() {
break;
}
unsafe {
let para_ptr = shmem.as_ptr().add(ADDR_CAPTURER_PARA);
let para = para_ptr as *const CapturerPara;
let current_display = (*para).current_display;
let use_yuv = (*para).use_yuv;
let use_yuv_set = (*para).use_yuv_set;
let timeout_ms = (*para).timeout_ms;
if !use_yuv_set {
c = None;
std::thread::sleep(spf);
continue;
}
if c.is_none() {
*crate::video_service::CURRENT_DISPLAY.lock().unwrap() = current_display;
let (_, _current, display) = get_current_display().unwrap();
match Capturer::new(display, use_yuv) {
Ok(mut v) => {
c = {
last_current_display = current_display;
last_use_yuv = use_yuv;
first_frame_captured = false;
if dxgi_failed_times > MAX_DXGI_FAIL_TIME {
dxgi_failed_times = 0;
v.set_gdi();
}
Some(v)
}
}
Err(e) => {
log::error!("Failed to create gdi capturer:{:?}", e);
std::thread::sleep(std::time::Duration::from_secs(1));
continue;
}
}
} else {
if current_display != last_current_display || use_yuv != last_use_yuv {
log::info!(
"display:{}->{}, use_yuv:{}->{}",
last_current_display,
current_display,
last_use_yuv,
use_yuv
);
c = None;
continue;
}
if timeout_ms != last_timeout_ms
&& timeout_ms >= 1000 / video_qos::MAX_FPS as i32
&& timeout_ms <= 1000 / video_qos::MIN_FPS as i32
{
last_timeout_ms = timeout_ms;
spf = Duration::from_millis(timeout_ms as _);
}
}
if first_frame_captured {
if !utils::counter_equal(shmem.as_ptr().add(ADDR_CAPTURE_FRAME_COUNTER)) {
std::thread::sleep(spf);
continue;
}
}
match c.as_mut().unwrap().frame(spf) {
Ok(f) => {
let len = f.0.len();
let len_slice = utils::i32_to_vec(len as _);
shmem.write(ADDR_CAPTURE_FRAME_SIZE, &len_slice);
shmem.write(ADDR_CAPTURE_FRAME, f.0);
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
utils::increase_counter(shmem.as_ptr().add(ADDR_CAPTURE_FRAME_COUNTER));
first_frame_captured = true;
dxgi_failed_times = 0;
}
Err(e) => {
if e.kind() != std::io::ErrorKind::WouldBlock {
// DXGI_ERROR_INVALID_CALL after each success on Microsoft GPU driver
// log::error!("capture frame failed:{:?}", e);
if crate::platform::windows::desktop_changed() {
crate::platform::try_change_desktop();
c = None;
std::thread::sleep(spf);
continue;
}
if !c.as_ref().unwrap().is_gdi() {
dxgi_failed_times += 1;
}
if dxgi_failed_times > MAX_DXGI_FAIL_TIME {
c = None;
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(FALSE));
std::thread::sleep(spf);
}
} else {
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
}
}
}
}
}
}
#[tokio::main(flavor = "current_thread")]
async fn run_ipc_client() {
use DataPortableService::*;
let postfix = IPC_PROFIX;
match ipc::connect(1000, postfix).await {
Ok(mut stream) => {
let mut timer = tokio::time::interval(Duration::from_secs(1));
let mut nack = 0;
loop {
tokio::select! {
res = stream.next() => {
match res {
Err(err) => {
log::error!(
"ipc{} connection closed: {}",
postfix,
err
);
break;
}
Ok(Some(Data::DataPortableService(data))) => match data {
Ping => {
allow_err!(
stream
.send(&Data::DataPortableService(Pong))
.await
);
}
Pong => {
nack = 0;
}
ConnCount(Some(n)) => {
if n == 0 {
log::info!("Connnection count equals 0, exit");
stream.send(&Data::DataPortableService(WillClose)).await.ok();
break;
}
}
Mouse(v) => {
if let Ok(evt) = MouseEvent::parse_from_bytes(&v) {
crate::input_service::handle_mouse_(&evt);
}
}
Key(v) => {
if let Ok(evt) = KeyEvent::parse_from_bytes(&v) {
crate::input_service::handle_key_(&evt);
}
}
_ => {}
},
_ => {}
}
}
_ = timer.tick() => {
nack+=1;
if nack > MAX_NACK {
log::info!("max ping nack, exit");
break;
}
stream.send(&Data::DataPortableService(Ping)).await.ok();
stream.send(&Data::DataPortableService(ConnCount(None))).await.ok();
}
}
}
}
Err(e) => {
log::error!("Failed to connect portable service ipc:{:?}", e);
}
}
*EXIT.lock().unwrap() = true;
}
}
// functions called in main process.
pub mod client {
use hbb_common::anyhow::Context;
use super::*;
lazy_static::lazy_static! {
pub static ref PORTABLE_SERVICE_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());
}
pub(crate) fn start_portable_service() -> ResultType<()> {
log::info!("start portable service");
if PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
bail!("already running");
}
if SHMEM.lock().unwrap().is_none() {
let displays = scrap::Display::all()?;
if displays.is_empty() {
bail!("no display available!");
}
let mut max_pixel = 0;
let align = 64;
for d in displays {
let pixel = utils::align(d.width(), align) * utils::align(d.height(), align);
if max_pixel < pixel {
max_pixel = pixel;
}
}
let shmem_size = utils::align(ADDR_CAPTURE_FRAME + max_pixel * 4, align);
// os error 112, no enough space
*SHMEM.lock().unwrap() = Some(crate::portable_service::SharedMemory::create(
crate::portable_service::SHMEM_NAME,
shmem_size,
)?);
shutdown_hooks::add_shutdown_hook(drop_portable_service_shared_memory);
}
let mut option = SHMEM.lock().unwrap();
let shmem = option.as_mut().unwrap();
unsafe {
libc::memset(shmem.as_ptr() as _, 0, shmem.len() as _);
}
if crate::platform::run_background(
&std::env::current_exe()?.to_string_lossy().to_string(),
"--portable-service",
)
.is_err()
{
*SHMEM.lock().unwrap() = None;
bail!("Failed to run portable service process");
}
let _sender = SENDER.lock().unwrap();
Ok(())
}
pub extern "C" fn drop_portable_service_shared_memory() {
log::info!("drop shared memory");
*SHMEM.lock().unwrap() = None;
}
pub struct CapturerPortable;
impl CapturerPortable {
pub fn new(current_display: usize, use_yuv: bool) -> Self
where
Self: Sized,
{
let mut option = SHMEM.lock().unwrap();
let shmem = option.as_mut().unwrap();
Self::set_para(
shmem,
CapturerPara {
current_display,
use_yuv,
use_yuv_set: false,
timeout_ms: 33,
},
);
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
CapturerPortable {}
}
fn set_para(shmem: &mut SharedMemory, para: CapturerPara) {
let para_ptr = &para as *const CapturerPara as *const u8;
let para_data;
unsafe {
para_data = slice::from_raw_parts(para_ptr, size_of::<CapturerPara>());
}
shmem.write(ADDR_CAPTURER_PARA, para_data);
}
}
impl TraitCapturer for CapturerPortable {
fn set_use_yuv(&mut self, use_yuv: bool) {
let mut option = SHMEM.lock().unwrap();
let shmem = option.as_mut().unwrap();
unsafe {
let para_ptr = shmem.as_ptr().add(ADDR_CAPTURER_PARA);
let para = para_ptr as *const CapturerPara;
Self::set_para(
shmem,
CapturerPara {
current_display: (*para).current_display,
use_yuv,
use_yuv_set: true,
timeout_ms: (*para).timeout_ms,
},
);
}
}
fn frame<'a>(&'a mut self, timeout: Duration) -> std::io::Result<Frame<'a>> {
let mut option = SHMEM.lock().unwrap();
let shmem = option.as_mut().unwrap();
unsafe {
let base = shmem.as_ptr();
let para_ptr = base.add(ADDR_CAPTURER_PARA);
let para = para_ptr as *const CapturerPara;
if timeout.as_millis() != (*para).timeout_ms as _ {
Self::set_para(
shmem,
CapturerPara {
current_display: (*para).current_display,
use_yuv: (*para).use_yuv,
use_yuv_set: (*para).use_yuv_set,
timeout_ms: timeout.as_millis() as _,
},
);
}
if utils::counter_ready(base.add(ADDR_CAPTURE_FRAME_COUNTER)) {
let frame_len_ptr = base.add(ADDR_CAPTURE_FRAME_SIZE);
let frame_len = utils::ptr_to_i32(frame_len_ptr);
let frame_ptr = base.add(ADDR_CAPTURE_FRAME);
let data = slice::from_raw_parts(frame_ptr, frame_len as usize);
Ok(Frame(data))
} else {
let ptr = base.add(ADDR_CAPTURE_WOULDBLOCK);
let wouldblock = utils::ptr_to_i32(ptr);
if wouldblock == TRUE {
Err(std::io::Error::new(
std::io::ErrorKind::WouldBlock,
"wouldblock error".to_string(),
))
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"other error".to_string(),
))
}
}
}
}
// control by itself
fn is_gdi(&self) -> bool {
true
}
fn set_gdi(&mut self) -> bool {
true
}
}
pub(super) fn start_ipc_server() -> mpsc::UnboundedSender<Data> {
let (tx, rx) = mpsc::unbounded_channel::<Data>();
std::thread::spawn(move || start_ipc_server_async(rx));
tx
}
#[tokio::main(flavor = "current_thread")]
async fn start_ipc_server_async(rx: mpsc::UnboundedReceiver<Data>) {
use DataPortableService::*;
let rx = Arc::new(tokio::sync::Mutex::new(rx));
let postfix = IPC_PROFIX;
#[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");
match new_listener(postfix).await {
Ok(mut incoming) => loop {
{
tokio::select! {
Some(result) = incoming.next() => {
match result {
Ok(stream) => {
log::info!("Got portable service ipc connection");
let rx_clone = rx.clone();
tokio::spawn(async move {
let mut stream = Connection::new(stream);
let postfix = postfix.to_owned();
let mut timer = tokio::time::interval(Duration::from_secs(1));
let mut nack = 0;
let mut rx = rx_clone.lock().await;
loop {
tokio::select! {
res = stream.next() => {
match res {
Err(err) => {
log::info!(
"ipc{} connection closed: {}",
postfix,
err
);
break;
}
Ok(Some(Data::DataPortableService(data))) => match data {
Ping => {
stream.send(&Data::DataPortableService(Pong)).await.ok();
}
Pong => {
nack = 0;
*PORTABLE_SERVICE_RUNNING.lock().unwrap() = true;
},
ConnCount(None) => {
if !quick_support {
let cnt = crate::server::CONN_COUNT.lock().unwrap().clone();
stream.send(&Data::DataPortableService(ConnCount(Some(cnt)))).await.ok();
}
},
WillClose => {
log::info!("portable service will close");
break;
}
_=>{}
}
_=>{}
}
}
_ = timer.tick() => {
nack+=1;
if nack > MAX_NACK {
// In fact, this will not happen, ipc will be closed before max nack.
log::error!("max ipc nack");
break;
}
stream.send(&Data::DataPortableService(Ping)).await.ok();
}
Some(data) = rx.recv() => {
allow_err!(stream.send(&data).await);
}
}
}
*PORTABLE_SERVICE_RUNNING.lock().unwrap() = false;
});
}
Err(err) => {
log::error!("Couldn't get portable client: {:?}", err);
}
}
}
}
}
},
Err(err) => {
log::error!("Failed to start portable service ipc server: {}", err);
}
}
}
fn ipc_send(data: Data) -> ResultType<()> {
let sender = SENDER.lock().unwrap();
sender
.send(data)
.map_err(|e| anyhow!("ipc send error:{:?}", e))
}
fn get_cursor_info_(shmem: &mut SharedMemory, pci: PCURSORINFO) -> BOOL {
unsafe {
let shmem_addr_para = shmem.as_ptr().add(ADDR_CURSOR_PARA);
if utils::counter_ready(shmem.as_ptr().add(ADDR_CURSOR_COUNTER)) {
std::ptr::copy_nonoverlapping(shmem_addr_para, pci as _, size_of::<CURSORINFO>());
return TRUE;
}
FALSE
}
}
fn handle_mouse_(evt: &MouseEvent) -> ResultType<()> {
let mut v = vec![];
evt.write_to_vec(&mut v)?;
ipc_send(Data::DataPortableService(DataPortableService::Mouse(v)))
}
fn handle_key_(evt: &KeyEvent) -> ResultType<()> {
let mut v = vec![];
evt.write_to_vec(&mut v)?;
ipc_send(Data::DataPortableService(DataPortableService::Key(v)))
}
pub fn create_capturer(
current_display: usize,
display: scrap::Display,
use_yuv: bool,
portable_service_running: bool,
) -> ResultType<Box<dyn TraitCapturer>> {
if portable_service_running != PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
log::info!("portable service status mismatch");
}
if portable_service_running {
log::info!("Create shared memeory capturer");
return Ok(Box::new(CapturerPortable::new(current_display, use_yuv)));
} else {
log::debug!("Create capturer dxgi|gdi");
return Ok(Box::new(
Capturer::new(display, use_yuv).with_context(|| "Failed to create capturer")?,
));
}
}
pub fn get_cursor_info(pci: PCURSORINFO) -> BOOL {
if PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
get_cursor_info_(&mut SHMEM.lock().unwrap().as_mut().unwrap(), pci)
} else {
unsafe { winuser::GetCursorInfo(pci) }
}
}
pub fn handle_mouse(evt: &MouseEvent) {
if PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
handle_mouse_(evt).ok();
} else {
crate::input_service::handle_mouse_(evt);
}
}
pub fn handle_key(evt: &KeyEvent) {
if PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
handle_key_(evt).ok();
} else {
crate::input_service::handle_key_(evt);
}
}
}
#[repr(C)]
struct CapturerPara {
current_display: usize,
use_yuv: bool,
use_yuv_set: bool,
timeout_ms: i32,
}

View File

@ -1,8 +1,8 @@
use super::*;
use std::time::Duration;
const FPS: u8 = 30;
const MIN_FPS: u8 = 10;
const MAX_FPS: u8 = 120;
pub const FPS: u8 = 30;
pub const MIN_FPS: u8 = 10;
pub const MAX_FPS: u8 = 120;
trait Percent {
fn as_percent(&self) -> u32;
}
@ -225,11 +225,7 @@ impl VideoQoS {
}
pub fn check_abr_config(&mut self) -> bool {
self.enable_abr = if let Some(v) = Config2::get().options.get("enable-abr") {
v != "N"
} else {
true // default is true
};
self.enable_abr = "N" != Config::get_option("enable-abr");
self.enable_abr
}

View File

@ -19,21 +19,26 @@
// https://slhck.info/video/2017/03/01/rate-control.html
use super::{video_qos::VideoQoS, *};
#[cfg(windows)]
use crate::portable_service::client::PORTABLE_SERVICE_RUNNING;
use hbb_common::tokio::sync::{
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
Mutex as TokioMutex,
};
#[cfg(not(windows))]
use scrap::Capturer;
use scrap::{
codec::{Encoder, EncoderCfg, HwEncoderConfig},
record::{Recorder, RecorderContext},
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
Capturer, Display, TraitCapturer,
Display, TraitCapturer,
};
#[cfg(windows)]
use std::sync::Once;
use std::{
collections::HashSet,
io::ErrorKind::WouldBlock,
ops::{Deref, DerefMut},
sync::Once,
time::{self, Duration, Instant},
};
#[cfg(windows)]
@ -48,7 +53,7 @@ pub const SCRAP_X11_REF_URL: &str = "https://rustdesk.com/docs/en/manual/linux/#
pub const NAME: &'static str = "video";
lazy_static::lazy_static! {
static ref CURRENT_DISPLAY: Arc<Mutex<usize>> = Arc::new(Mutex::new(usize::MAX));
pub static ref CURRENT_DISPLAY: Arc<Mutex<usize>> = Arc::new(Mutex::new(usize::MAX));
static ref LAST_ACTIVE: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
static ref SWITCH: Arc<Mutex<bool>> = Default::default();
static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option<Instant>)>, Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>) = {
@ -187,6 +192,8 @@ fn create_capturer(
privacy_mode_id: i32,
display: Display,
use_yuv: bool,
current: usize,
_portable_service_running: bool,
) -> ResultType<Box<dyn TraitCapturer>> {
#[cfg(not(windows))]
let c: Option<Box<dyn TraitCapturer>> = None;
@ -243,17 +250,23 @@ fn create_capturer(
}
}
let c = match c {
Some(c1) => c1,
match c {
Some(c1) => return Ok(c1),
None => {
let c1 =
Capturer::new(display, use_yuv).with_context(|| "Failed to create capturer")?;
log::debug!("Create capturer dxgi|gdi");
Box::new(c1)
#[cfg(windows)]
return crate::portable_service::client::create_capturer(
current,
display,
use_yuv,
_portable_service_running,
);
#[cfg(not(windows))]
return Ok(Box::new(
Capturer::new(display, use_yuv).with_context(|| "Failed to create capturer")?,
));
}
};
Ok(c)
}
#[cfg(windows)]
@ -276,8 +289,8 @@ fn ensure_close_virtual_device() -> ResultType<()> {
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
let test_begin = Instant::now();
while test_begin.elapsed().as_millis() < timeout_millis as _ {
if let Ok((_, _, display)) = get_current_display() {
if let Ok(_) = create_capturer(privacy_mode_id, display, true) {
if let Ok((_, current, display)) = get_current_display() {
if let Ok(_) = create_capturer(privacy_mode_id, display, true, current, false) {
return true;
}
}
@ -326,7 +339,7 @@ impl DerefMut for CapturerInfo {
}
}
fn get_capturer(use_yuv: bool) -> ResultType<CapturerInfo> {
fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<CapturerInfo> {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
@ -368,7 +381,13 @@ fn get_capturer(use_yuv: bool) -> ResultType<CapturerInfo> {
} else {
log::info!("In privacy mode, the peer side cannot watch the screen");
}
let capturer = create_capturer(captuerer_privacy_mode_id, display, use_yuv)?;
let capturer = create_capturer(
captuerer_privacy_mode_id,
display,
use_yuv,
current,
portable_service_running,
)?;
Ok(CapturerInfo {
origin,
width,
@ -388,8 +407,12 @@ fn run(sp: GenericService) -> ResultType<()> {
// ensure_inited() is needed because release_resouce() may be called.
#[cfg(target_os = "linux")]
super::wayland::ensure_inited()?;
#[cfg(windows)]
let last_portable_service_running = PORTABLE_SERVICE_RUNNING.lock().unwrap().clone();
#[cfg(not(windows))]
let last_portable_service_running = false;
let mut c = get_capturer(true)?;
let mut c = get_capturer(true, last_portable_service_running)?;
let mut video_qos = VIDEO_QOS.lock().unwrap();
video_qos.set_size(c.width as _, c.height as _);
@ -497,10 +520,16 @@ fn run(sp: GenericService) -> ResultType<()> {
if codec_name != Encoder::current_hw_encoder_name() {
bail!("SWITCH");
}
#[cfg(windows)]
if last_portable_service_running != PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
bail!("SWITCH");
}
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
#[cfg(windows)]
{
if crate::platform::windows::desktop_changed() {
if crate::platform::windows::desktop_changed()
&& !PORTABLE_SERVICE_RUNNING.lock().unwrap().clone()
{
bail!("Desktop changed");
}
}
@ -529,7 +558,7 @@ fn run(sp: GenericService) -> ResultType<()> {
frame_controller.set_send(now, send_conn_ids);
}
scrap::Frame::RAW(data) => {
if (data.len() != 0) {
if data.len() != 0 {
let send_conn_ids =
handle_one_frame(&sp, data, ms, &mut encoder, recorder.clone())?;
frame_controller.set_send(now, send_conn_ids);
@ -873,7 +902,7 @@ pub(super) fn get_current_display_2(mut all: Vec<Display>) -> ResultType<(usize,
return Ok((n, current, all.remove(current)));
}
fn get_current_display() -> ResultType<(usize, usize, Display)> {
pub fn get_current_display() -> ResultType<(usize, usize, Display)> {
get_current_display_2(try_get_displays()?)
}

View File

@ -1,14 +1,12 @@
use hbb_common::log::debug;
use super::ui_interface::get_option_opt;
#[cfg(target_os = "linux")]
use hbb_common::log::{error, info};
use hbb_common::log::{debug, error, info};
#[cfg(target_os = "linux")]
use libappindicator::AppIndicator;
#[cfg(target_os = "linux")]
use std::env::temp_dir;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
#[cfg(target_os = "windows")]
use std::sync::{Arc, Mutex};
#[cfg(target_os = "windows")]
use trayicon::{MenuBuilder, TrayIconBuilder};
#[cfg(target_os = "windows")]
@ -17,6 +15,7 @@ use winit::{
event_loop::{ControlFlow, EventLoop},
};
#[cfg(target_os = "windows")]
#[derive(Clone, Eq, PartialEq, Debug)]
enum Events {
DoubleClickTrayIcon,
@ -25,7 +24,7 @@ enum Events {
}
#[cfg(target_os = "windows")]
pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
pub fn start_tray() {
let event_loop = EventLoop::<Events>::with_user_event();
let proxy = event_loop.create_proxy();
let icon = include_bytes!("../res/tray-icon.ico");
@ -39,23 +38,19 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
let old_state = Arc::new(Mutex::new(0));
let _sender = crate::ui_interface::SENDER.lock().unwrap();
event_loop.run(move |event, _, control_flow| {
if options.lock().unwrap().get("ipc-closed").is_some() {
if get_option_opt("ipc-closed").is_some() {
*control_flow = ControlFlow::Exit;
return;
} else {
*control_flow = ControlFlow::Wait;
}
let stopped = if let Some(v) = options.lock().unwrap().get("stop-service") {
!v.is_empty()
} else {
false
};
let stopped = if stopped { 2 } else { 1 };
let stopped = is_service_stoped();
let state = if stopped { 2 } else { 1 };
let old = *old_state.lock().unwrap();
if stopped != old {
if state != old {
hbb_common::log::info!("State changed");
let mut m = MenuBuilder::new();
if stopped == 2 {
if state == 2 {
m = m.item(
&crate::client::translate("Start Service".to_owned()),
Events::StartService,
@ -67,7 +62,7 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
);
}
tray_icon.set_menu(&m).ok();
*old_state.lock().unwrap() = stopped;
*old_state.lock().unwrap() = state;
}
match event {
@ -92,10 +87,9 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
/// [Block]
/// This function will block current execution, show the tray icon and handle events.
#[cfg(target_os = "linux")]
pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
use std::time::Duration;
pub fn start_tray() {
use gtk::traits::{GtkMenuItemExt, MenuShellExt, WidgetExt};
info!("configuring tray");
// init gtk context
if let Err(err) = gtk::init() {
@ -104,17 +98,17 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
}
if let Some(mut appindicator) = get_default_app_indicator() {
let mut menu = gtk::Menu::new();
let running = get_service_status(options.clone());
let stoped = is_service_stoped();
// start/stop service
let label = if !running {
let label = if stoped {
crate::client::translate("Start Service".to_owned())
} else {
crate::client::translate("Stop service".to_owned())
};
let menu_item_service = gtk::MenuItem::with_label(label.as_str());
menu_item_service.connect_activate(move |item| {
let lock = crate::ui_interface::SENDER.lock().unwrap();
update_tray_service_item(options.clone(), item);
let _lock = crate::ui_interface::SENDER.lock().unwrap();
update_tray_service_item(item);
});
menu.append(&menu_item_service);
// show tray item
@ -129,19 +123,17 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
}
#[cfg(target_os = "linux")]
fn update_tray_service_item(options: Arc<Mutex<HashMap<String, String>>>, item: &gtk::MenuItem) {
use gtk::{
traits::{GtkMenuItemExt, ListBoxRowExt},
MenuItem,
};
if get_service_status(options.clone()) {
debug!("Now try to stop service");
item.set_label(&crate::client::translate("Start Service".to_owned()));
crate::ipc::set_option("stop-service", "Y");
} else {
fn update_tray_service_item(item: &gtk::MenuItem) {
use gtk::traits::GtkMenuItemExt;
if is_service_stoped() {
debug!("Now try to start service");
item.set_label(&crate::client::translate("Stop service".to_owned()));
crate::ipc::set_option("stop-service", "");
} else {
debug!("Now try to stop service");
item.set_label(&crate::client::translate("Start Service".to_owned()));
crate::ipc::set_option("stop-service", "Y");
}
}
@ -158,6 +150,13 @@ fn get_default_app_indicator() -> Option<AppIndicator> {
match std::fs::File::create(icon_path.clone()) {
Ok(mut f) => {
f.write_all(icon).unwrap();
// set .png icon file to be writable
// this ensures successful file rewrite when switching between x11 and wayland.
let mut perm = f.metadata().unwrap().permissions();
if perm.readonly() {
perm.set_readonly(false);
f.set_permissions(perm).unwrap();
}
}
Err(err) => {
error!("Error when writing icon to {:?}: {}", icon_path, err);
@ -171,14 +170,45 @@ fn get_default_app_indicator() -> Option<AppIndicator> {
Some(appindicator)
}
/// Get service status
/// Return [`true`] if service is running, [`false`] otherwise.
/// Check if service is stoped.
/// Return [`true`] if service is stoped, [`false`] otherwise.
#[inline]
fn get_service_status(options: Arc<Mutex<HashMap<String, String>>>) -> bool {
if let Some(v) = options.lock().unwrap().get("stop-service") {
debug!("service stopped: {}", v);
v.is_empty()
fn is_service_stoped() -> bool {
if let Some(v) = get_option_opt("stop-service") {
v == "Y"
} else {
true
false
}
}
#[cfg(target_os = "macos")]
pub fn make_tray() {
use tray_item::TrayItem;
let mode = dark_light::detect();
let mut icon_path = "";
match mode {
dark_light::Mode::Dark => {
icon_path = "mac-tray-light.png";
},
dark_light::Mode::Light => {
icon_path = "mac-tray-dark.png";
},
}
if let Ok(mut tray) = TrayItem::new(&crate::get_app_name(), icon_path) {
tray.add_label(&format!(
"{} {}",
crate::get_app_name(),
crate::lang::translate("Service is running".to_owned())
))
.ok();
let inner = tray.inner_mut();
inner.add_quit_item(&crate::lang::translate("Quit".to_owned()));
inner.display();
} else {
loop {
std::thread::sleep(std::time::Duration::from_secs(3));
}
}
}

View File

@ -15,7 +15,7 @@ use hbb_common::{
protobuf::Message as _,
rendezvous_proto::*,
tcp::FramedStream,
tokio::{self, sync::mpsc},
tokio,
};
use crate::common::get_app_name;
@ -33,7 +33,8 @@ pub mod win_privacy;
type Message = RendezvousMessage;
pub type Childs = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
#[allow(dead_code)]
type Status = (i32, bool, i64, String);
lazy_static::lazy_static! {
@ -43,30 +44,9 @@ lazy_static::lazy_static! {
struct UIHostHandler;
fn check_connect_status(
reconnect: bool,
) -> (
Arc<Mutex<Status>>,
Arc<Mutex<HashMap<String, String>>>,
mpsc::UnboundedSender<ipc::Data>,
Arc<Mutex<String>>,
) {
let status = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
let options = Arc::new(Mutex::new(Config::get_options()));
let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
let password = Arc::new(Mutex::new(String::default()));
std::thread::spawn(move || crate::ui_interface::check_connect_status_(reconnect, rx));
(status, options, tx, password)
}
pub fn start(args: &mut [String]) {
#[cfg(target_os = "macos")]
if args.len() == 1 && args[0] == "--server" {
macos::make_tray();
return;
} else {
macos::show_dock();
}
macos::show_dock();
#[cfg(all(target_os = "linux", feature = "inline"))]
{
#[cfg(feature = "appimage")]
@ -111,8 +91,8 @@ pub fn start(args: &mut [String]) {
args[1] = id;
}
if args.is_empty() {
let child: Childs = Default::default();
std::thread::spawn(move || check_zombie(child));
let children: Children = Default::default();
std::thread::spawn(move || check_zombie(children));
crate::common::check_software_update();
frame.event_handler(UI {});
frame.sciter_handler(UIHostHandler {});
@ -662,10 +642,10 @@ impl sciter::host::HostHandler for UIHostHandler {
}
}
pub fn check_zombie(childs: Childs) {
pub fn check_zombie(children: Children) {
let mut deads = Vec::new();
loop {
let mut lock = childs.lock().unwrap();
let mut lock = children.lock().unwrap();
let mut n = 0;
for (id, c) in lock.1.iter_mut() {
if let Ok(Some(_)) = c.try_wait() {

View File

@ -68,7 +68,7 @@ div.permissions {
}
div.permissions > div {
size: 48px;
size: 42px;
background: color(accent);
}
@ -112,20 +112,44 @@ icon.recording {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAANpJREFUWEftltENAiEMhtsJ1NcynG6gI+gGugEOR591gppeQoIYSDBILxEeydH/57u2FMF4obE+TAOTwLoIhBDOAHBExG2n6rgR0akW640AM0sn4SWMiDycc7s8JjN7Ijro/k8NqAAR5RoeAPZxv2ggP9hCJiWZxtGbq3hqbJiBVHy4gVx8qAER8Yi4JFy6huVAKXemgb8icI+1b5KEitq0DOO/Nm1EEX1TK27p/bVvv36MOhl4EtHHbFF7jq8AoG1z08OAiFycczrkFNe6RrIet26NMQlMAuYEXiayryF/QQktAAAAAElFTkSuQmCC');
}
div.buttons {
width: *;
border-spacing: 0.5em;
text-align: center;
div.outer_buttons {
flow:vertical;
border-spacing:8;
}
div.buttons button {
width: 80px;
height: 40px;
margin: 0.5em;
div.inner_buttons {
flow:horizontal;
border-spacing:8;
}
button.control {
width: *;
}
button.elevate {
background:green;
}
button.elevate:active {
background: rgb(2, 104, 2);
border-color: color(hover-border);
}
button.elevate>span {
flow:horizontal;
width: *;
}
button.elevate>span>span {
margin-left:*;
margin-right:*;
}
button.elevate>span>span>span {
vertical-align: middle;
}
button#disconnect {
width: 160px;
background: color(blood-red);
border: none;
}

View File

@ -51,6 +51,10 @@ impl InvokeUiCM for SciterHandler {
fn change_language(&self) {
// TODO
}
fn show_elevation(&self, show: bool) {
self.call("showElevation", &make_args!(show));
}
}
impl SciterHandler {
@ -123,6 +127,14 @@ impl SciterConnectionManager {
fn t(&self, name: String) -> String {
crate::client::translate(name)
}
fn can_elevate(&self) -> bool {
crate::ui_cm_interface::can_elevate()
}
fn elevate_portable(&self, id: i32) {
crate::ui_cm_interface::elevate_portable(id);
}
}
impl sciter::EventHandler for SciterConnectionManager {
@ -141,5 +153,7 @@ impl sciter::EventHandler for SciterConnectionManager {
fn authorize(i32);
fn switch_permission(i32, String, bool);
fn send_msg(i32, String);
fn can_elevate();
fn elevate_portable(i32);
}
}

Some files were not shown because too many files have changed in this diff Show More