mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-06-11 12:43:12 +08:00
Merge remote-tracking branch 'rd/master' into feat/x11/clipboard-file/init
Signed-off-by: ClSlaid <cailue@bupt.edu.cn>
This commit is contained in:
commit
8f9ba44c2c
2
.github/workflows/flutter-build.yml
vendored
2
.github/workflows/flutter-build.yml
vendored
@ -22,7 +22,7 @@ env:
|
||||
# vcpkg version: 2023.04.15
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1"
|
||||
VERSION: "1.2.3"
|
||||
VERSION: "1.2.4"
|
||||
NDK_VERSION: "r25c"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: '${{ secrets.ANDROID_SIGNING_KEY }}'
|
||||
|
2
.github/workflows/flutter-tag.yml
vendored
2
.github/workflows/flutter-tag.yml
vendored
@ -15,4 +15,4 @@ jobs:
|
||||
secrets: inherit
|
||||
with:
|
||||
upload-artifact: true
|
||||
upload-tag: "1.2.3"
|
||||
upload-tag: "1.2.4"
|
||||
|
2
.github/workflows/history.yml
vendored
2
.github/workflows/history.yml
vendored
@ -10,7 +10,7 @@ env:
|
||||
# vcpkg version: 2022.05.10
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1"
|
||||
VERSION: "1.2.3"
|
||||
VERSION: "1.2.4"
|
||||
|
||||
jobs:
|
||||
build-for-history-windows:
|
||||
|
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -1842,6 +1842,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enquote"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c36cb11dbde389f4096111698d8b567c0720e3452fd5ac3e6b4e47e1939932"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator"
|
||||
version = "1.4.1"
|
||||
@ -5197,7 +5206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk"
|
||||
version = "1.2.3"
|
||||
version = "1.2.4"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"arboard",
|
||||
@ -5278,6 +5287,7 @@ dependencies = [
|
||||
"users 0.11.0",
|
||||
"uuid",
|
||||
"virtual_display",
|
||||
"wallpaper",
|
||||
"whoami",
|
||||
"winapi 0.3.9",
|
||||
"windows-service",
|
||||
@ -6662,6 +6672,19 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wallpaper"
|
||||
version = "3.2.0"
|
||||
source = "git+https://github.com/21pages/wallpaper.rs#ce4a0cd3f58327c7cc44d15a63706fb0c022bacf"
|
||||
dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"enquote",
|
||||
"rust-ini",
|
||||
"thiserror",
|
||||
"winapi 0.3.9",
|
||||
"winreg 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.2.3"
|
||||
version = "1.2.4"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
@ -88,6 +88,7 @@ clipboard = { path = "libs/clipboard" }
|
||||
ctrlc = "3.2"
|
||||
arboard = "3.2"
|
||||
system_shutdown = "4.0"
|
||||
shutdown_hooks = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser", "wincrypt", "shellscalingapi"] }
|
||||
@ -96,7 +97,6 @@ windows-service = "0.6"
|
||||
virtual_display = { path = "libs/virtual_display", optional = true }
|
||||
impersonate_system = { git = "https://github.com/21pages/impersonate-system" }
|
||||
shared_memory = "0.12"
|
||||
shutdown_hooks = "0.1"
|
||||
tauri-winrt-notification = "0.1.2"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
@ -118,6 +118,9 @@ image = "0.24"
|
||||
[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies]
|
||||
keepawake = { git = "https://github.com/rustdesk-org/keepawake-rs" }
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
|
||||
wallpaper = { git = "https://github.com/21pages/wallpaper.rs" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
psimple = { package = "libpulse-simple-binding", version = "2.27" }
|
||||
pulse = { package = "libpulse-binding", version = "2.27" }
|
||||
|
@ -2,7 +2,7 @@
|
||||
version: 1
|
||||
script:
|
||||
- rm -rf ./AppDir || true
|
||||
- bsdtar -zxvf ../rustdesk-1.2.3.deb
|
||||
- bsdtar -zxvf ../rustdesk-1.2.4.deb
|
||||
- tar -xvf ./data.tar.xz
|
||||
- mkdir ./AppDir
|
||||
- mv ./usr ./AppDir/usr
|
||||
@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.3
|
||||
version: 1.2.4
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
@ -2,7 +2,7 @@
|
||||
version: 1
|
||||
script:
|
||||
- rm -rf ./AppDir || true
|
||||
- bsdtar -zxvf ../rustdesk-1.2.3.deb
|
||||
- bsdtar -zxvf ../rustdesk-1.2.4.deb
|
||||
- tar -xvf ./data.tar.xz
|
||||
- mkdir ./AppDir
|
||||
- mv ./usr ./AppDir/usr
|
||||
@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.3
|
||||
version: 1.2.4
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
@ -13,12 +13,24 @@ Mari mengobrol bersama kami: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
[](https://console.algora.io/org/rustdesk/bounties?status=open)
|
||||
|
||||
Merupakan perangkat lunak Remote Desktop yang baru, dan dibangun dengan Rust. Bahkan kamu bisa langsung menggunakannya tanpa perlu melakukan konfigurasi tambahan. Serta memiliki kontrol penuh terhadap semua data, tanpa perlu merasa was-was tentang isu keamanan, dan yang lebih menarik adalah memiliki opsi untuk menggunakan server rendezvous/relay milik kami, [konfigurasi server sendiri](https://rustdesk.com/server), atau [tulis rendezvous/relay server anda sendiri](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
RustDesk mengajak semua orang untuk ikut berkontribusi. Lihat [`docs/CONTRIBUTING-ID.md`](CONTRIBUTING-ID.md) untuk melihat panduan.
|
||||
|
||||
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
|
||||
[**UNDUH BINARY**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||
|
||||
## Server Publik Gratis
|
||||
|
||||
Di bawah ini merupakan server gratis yang bisa kamu gunakan, seiring dengan waktu mungkin akan terjadi perubahan spesifikasi pada setiap server yang ada. Jika lokasi kamu berada jauh dengan salah satu server yang tersedia, kemungkinan koneksi akan terasa lambat ketika melakukan proses remote.
|
||||
|
@ -12,7 +12,7 @@
|
||||
"name": "rustdesk",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"bsdtar -zxvf rustdesk-1.2.3.deb",
|
||||
"bsdtar -zxvf rustdesk-1.2.4.deb",
|
||||
"tar -xvf ./data.tar.xz",
|
||||
"cp -r ./usr/* /app/",
|
||||
"mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk",
|
||||
@ -26,7 +26,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"path": "../rustdesk-1.2.3.deb"
|
||||
"path": "../rustdesk-1.2.4.deb"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
|
@ -14,6 +14,6 @@ import Flutter
|
||||
|
||||
public func dummyMethodToEnforceBundling() {
|
||||
dummy_method_to_enforce_bundling();
|
||||
session_get_rgba(nil);
|
||||
session_get_rgba(nil, 0);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
@ -593,7 +594,7 @@ closeConnection({String? id}) {
|
||||
}
|
||||
}
|
||||
|
||||
void windowOnTop(int? id) async {
|
||||
Future<void> windowOnTop(int? id) async {
|
||||
if (!isDesktop) {
|
||||
return;
|
||||
}
|
||||
@ -1054,7 +1055,7 @@ Widget msgboxIcon(String type) {
|
||||
if (type == 'on-uac' || type == 'on-foreground-elevated') {
|
||||
iconData = Icons.admin_panel_settings;
|
||||
}
|
||||
if (type == "info") {
|
||||
if (type.contains('info')) {
|
||||
iconData = Icons.info;
|
||||
}
|
||||
if (iconData != null) {
|
||||
@ -1834,10 +1835,10 @@ enum UriLinkType {
|
||||
// uri link handler
|
||||
bool handleUriLink({List<String>? cmdArgs, Uri? uri, String? uriString}) {
|
||||
List<String>? args;
|
||||
if (cmdArgs != null) {
|
||||
if (cmdArgs != null && cmdArgs.isNotEmpty) {
|
||||
args = cmdArgs;
|
||||
// rustdesk <uri link>
|
||||
if (args.isNotEmpty && args[0].startsWith(kUniLinksPrefix)) {
|
||||
if (args[0].startsWith(kUniLinksPrefix)) {
|
||||
final uri = Uri.tryParse(args[0]);
|
||||
if (uri != null) {
|
||||
args = urlLinkToCmdArgs(uri);
|
||||
@ -2317,7 +2318,7 @@ Widget dialogButton(String text,
|
||||
}
|
||||
}
|
||||
|
||||
int version_cmp(String v1, String v2) {
|
||||
int versionCmp(String v1, String v2) {
|
||||
return bind.versionToNumber(v: v1) - bind.versionToNumber(v: v2);
|
||||
}
|
||||
|
||||
@ -2589,3 +2590,18 @@ String getDesktopTabLabel(String peerId, String alias) {
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async {
|
||||
if (pi.currentDisplay == kAllDisplayValue) {
|
||||
for (int i = 0; i < pi.displays.length; i++) {
|
||||
await bind.sessionRefresh(sessionId: sessionId, display: i);
|
||||
}
|
||||
} else {
|
||||
await bind.sessionRefresh(sessionId: sessionId, display: pi.currentDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
bool isChooseDisplayToOpenInNewWindow(PeerInfo pi, SessionID sessionId) =>
|
||||
pi.isSupportMultiDisplay &&
|
||||
useTextureRender &&
|
||||
bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y';
|
||||
|
@ -229,6 +229,22 @@ class _AddressBookState extends State<AddressBook> {
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> filterMenuItem() {
|
||||
return MenuEntrySwitch<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Filter by intersection'),
|
||||
getter: () async {
|
||||
return filterAbTagByIntersection();
|
||||
},
|
||||
setter: (bool v) async {
|
||||
bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : '');
|
||||
gFFI.abModel.filterByIntersection.value = v;
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
|
||||
void _showMenu(RelativeRect pos) {
|
||||
final items = [
|
||||
getEntry(translate("Add ID"), abAddId),
|
||||
@ -236,6 +252,7 @@ class _AddressBookState extends State<AddressBook> {
|
||||
getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags),
|
||||
sortMenuItem(),
|
||||
syncMenuItem(),
|
||||
filterMenuItem(),
|
||||
];
|
||||
|
||||
mod_menu.showMenu(
|
||||
|
@ -1295,7 +1295,7 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
|
||||
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
|
||||
} catch (_) {}
|
||||
bool notShowFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
|
||||
version_cmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
|
||||
versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
|
||||
|
||||
final content = customImageQualityWidget(
|
||||
initQuality: qualityInitValue,
|
||||
|
@ -600,7 +600,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
await _openNewConnInAction(id, 'Open in New Tab', kOptionOpenInTabs);
|
||||
|
||||
_openInWindowsAction(String id) async => await _openNewConnInAction(
|
||||
id, 'Open in New Window', kOptionOpenInWindows);
|
||||
id, 'Open in new window', kOptionOpenInWindows);
|
||||
|
||||
_openNewConnInOptAction(String id) async =>
|
||||
mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs)
|
||||
|
@ -450,12 +450,21 @@ class AddressBookPeersView extends BasePeersView {
|
||||
if (selectedTags.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
for (final tag in selectedTags) {
|
||||
if (idents.contains(tag)) {
|
||||
return true;
|
||||
if (gFFI.abModel.filterByIntersection.value) {
|
||||
for (final tag in selectedTags) {
|
||||
if (!idents.contains(tag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
for (final tag in selectedTags) {
|
||||
if (idents.contains(tag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
bool isEditOsPassword = false;
|
||||
@ -90,7 +91,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Row(children: [
|
||||
Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')),
|
||||
Text(translate(pi.isHeadless ? 'OS Account' : 'OS Password')),
|
||||
Offstage(
|
||||
offstage: isDesktop,
|
||||
child: Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12),
|
||||
@ -99,13 +100,13 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
trailingIcon: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: InkWell(
|
||||
onTap: () => pi.is_headless
|
||||
onTap: () => pi.isHeadless
|
||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||
: handleOsPasswordEditIcon(sessionId, ffi.dialogManager),
|
||||
child: Icon(Icons.edit),
|
||||
),
|
||||
),
|
||||
onPressed: () => pi.is_headless
|
||||
onPressed: () => pi.isHeadless
|
||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||
: handleOsPasswordAction(sessionId, ffi.dialogManager),
|
||||
),
|
||||
@ -208,7 +209,8 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
ffiModel.keyboard &&
|
||||
pi.platform != kPeerPlatformAndroid &&
|
||||
pi.platform != kPeerPlatformMacOS &&
|
||||
version_cmp(pi.version, '1.2.0') >= 0) {
|
||||
versionCmp(pi.version, '1.2.0') >= 0 &&
|
||||
bind.peerGetDefaultSessionsCount(id: id) == 1) {
|
||||
v.add(TTextMenu(
|
||||
child: Text(translate('Switch Sides')),
|
||||
onPressed: () =>
|
||||
@ -217,8 +219,9 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
// refresh
|
||||
if (pi.version.isNotEmpty) {
|
||||
v.add(TTextMenu(
|
||||
child: Text(translate('Refresh')),
|
||||
onPressed: () => bind.sessionRefresh(sessionId: sessionId)));
|
||||
child: Text(translate('Refresh')),
|
||||
onPressed: () => sessionRefreshVideo(sessionId, pi),
|
||||
));
|
||||
}
|
||||
// record
|
||||
var codecFormat = ffi.qualityMonitorModel.data.codecFormat;
|
||||
@ -377,7 +380,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
// show remote cursor
|
||||
if (pi.platform != kPeerPlatformAndroid &&
|
||||
!ffi.canvasModel.cursorEmbedded &&
|
||||
!pi.is_wayland) {
|
||||
!pi.isWayland) {
|
||||
final state = ShowRemoteCursorState.find(id);
|
||||
final enabled = !ffiModel.viewOnly;
|
||||
final option = 'show-remote-cursor';
|
||||
@ -486,7 +489,8 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
value: rxValue.value,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
if (ffiModel.pi.currentDisplay != 0) {
|
||||
if (ffiModel.pi.currentDisplay != 0 &&
|
||||
ffiModel.pi.currentDisplay != kAllDisplayValue) {
|
||||
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
|
||||
'Please switch to Display 1 first', '', ffi.dialogManager);
|
||||
return;
|
||||
@ -510,5 +514,24 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
},
|
||||
child: Text(translate('Swap control-command key'))));
|
||||
}
|
||||
|
||||
if (useTextureRender &&
|
||||
pi.isSupportMultiDisplay &&
|
||||
PrivacyModeState.find(id).isFalse &&
|
||||
pi.displaysCount.value > 1 &&
|
||||
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
|
||||
final value =
|
||||
bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) ==
|
||||
'Y';
|
||||
v.add(TToggleMenu(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
bind.sessionSetDisplaysAsIndividualWindows(
|
||||
sessionId: sessionId, value: value ? 'Y' : '');
|
||||
},
|
||||
child: Text(translate('Show displays as individual windows'))));
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ const double kDesktopRemoteTabBarHeight = 28.0;
|
||||
const int kInvalidWindowId = -1;
|
||||
const int kMainWindowId = 0;
|
||||
|
||||
const kAllDisplayValue = -1;
|
||||
|
||||
const String kPeerPlatformWindows = "Windows";
|
||||
const String kPeerPlatformLinux = "Linux";
|
||||
const String kPeerPlatformMacOS = "Mac OS";
|
||||
@ -37,11 +39,13 @@ const String kWindowEventNewRemoteDesktop = "new_remote_desktop";
|
||||
const String kWindowEventNewFileTransfer = "new_file_transfer";
|
||||
const String kWindowEventNewPortForward = "new_port_forward";
|
||||
const String kWindowEventActiveSession = "active_session";
|
||||
const String kWindowEventActiveDisplaySession = "active_display_session";
|
||||
const String kWindowEventGetRemoteList = "get_remote_list";
|
||||
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
||||
|
||||
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
||||
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
|
||||
const String kWindowEventOpenMonitorSession = "open_monitor_session";
|
||||
|
||||
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
||||
const String kOptionOpenInTabs = "allow-open-in-tabs";
|
||||
@ -60,6 +64,9 @@ const int kWindowMainId = 0;
|
||||
const String kPointerEventKindTouch = "touch";
|
||||
const String kPointerEventKindMouse = "mouse";
|
||||
|
||||
const String kKeyShowDisplaysAsIndividualWindows = 'displays_as_individual_windows';
|
||||
const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar';
|
||||
|
||||
// the executable name of the portable version
|
||||
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
|
||||
|
||||
|
@ -187,12 +187,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
? Theme.of(context).scaffoldBackgroundColor
|
||||
: Theme.of(context).colorScheme.background,
|
||||
child: Tooltip(
|
||||
message: translate('Settings'),
|
||||
child: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
size: 20,
|
||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||
)),
|
||||
message: translate('Settings'),
|
||||
child: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
size: 20,
|
||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||
)),
|
||||
),
|
||||
),
|
||||
onHover: (value) => hover.value = value,
|
||||
@ -256,27 +256,27 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
child: Obx(() => RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: Tooltip(
|
||||
message: translate('Refresh Password'),
|
||||
child: Icon(
|
||||
Icons.refresh,
|
||||
color: refreshHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
))
|
||||
)),
|
||||
message: translate('Refresh Password'),
|
||||
child: Icon(
|
||||
Icons.refresh,
|
||||
color: refreshHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
)))),
|
||||
onHover: (value) => refreshHover.value = value,
|
||||
).marginOnly(right: 8, top: 4),
|
||||
InkWell(
|
||||
child: Obx(
|
||||
() => Tooltip(
|
||||
message: translate('Change Password'),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
color:
|
||||
editHover.value ? textColor : Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
)).marginOnly(right: 8, top: 4),
|
||||
message: translate('Change Password'),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
color: editHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
)).marginOnly(right: 8, top: 4),
|
||||
),
|
||||
onTap: () => DesktopSettingPage.switch2page(1),
|
||||
onHover: (value) => editHover.value = value,
|
||||
@ -604,8 +604,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
debugPrint("Failed to parse window id '${call.arguments}': $e");
|
||||
}
|
||||
if (windowId != null) {
|
||||
await rustDeskWinManager.moveTabToNewWindow(windowId, args[1], args[2]);
|
||||
await rustDeskWinManager.moveTabToNewWindow(
|
||||
windowId, args[1], args[2]);
|
||||
}
|
||||
} else if (call.method == kWindowEventOpenMonitorSession) {
|
||||
final args = jsonDecode(call.arguments);
|
||||
final windowId = args['window_id'] as int;
|
||||
final peerId = args['peer_id'] as String;
|
||||
final display = args['display'] as int;
|
||||
final displayCount = args['display_count'] as int;
|
||||
await rustDeskWinManager.openMonitorSession(
|
||||
windowId, peerId, display, displayCount);
|
||||
}
|
||||
});
|
||||
_uniLinksSubscription = listenUniLinks();
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@ -9,6 +10,7 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.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/models/desktop_render_texture.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/plugin/manager.dart';
|
||||
@ -322,6 +324,7 @@ class _GeneralState extends State<_General> {
|
||||
'enable-confirm-closing-tabs',
|
||||
isServer: false),
|
||||
_OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'),
|
||||
wallpaper(),
|
||||
_OptionCheckBox(
|
||||
context,
|
||||
'Open connection in new tab',
|
||||
@ -348,6 +351,42 @@ class _GeneralState extends State<_General> {
|
||||
return _Card(title: 'Other', children: children);
|
||||
}
|
||||
|
||||
Widget wallpaper() {
|
||||
return futureBuilder(future: () async {
|
||||
final support = await bind.mainSupportRemoveWallpaper();
|
||||
return support;
|
||||
}(), hasData: (data) {
|
||||
if (data is bool && data == true) {
|
||||
final option = 'allow-remove-wallpaper';
|
||||
bool value = mainGetBoolOptionSync(option);
|
||||
return Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: _OptionCheckBox(
|
||||
context,
|
||||
'Remove wallpaper during incoming sessions',
|
||||
option,
|
||||
update: () {
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
if (value)
|
||||
_CountDownButton(
|
||||
text: 'Test',
|
||||
second: 5,
|
||||
onPressed: () {
|
||||
bind.mainTestWallpaper(second: 5);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Offstage();
|
||||
});
|
||||
}
|
||||
|
||||
Widget hwcodec() {
|
||||
return Offstage(
|
||||
offstage: !bind.mainHasHwcodec(),
|
||||
@ -729,11 +768,6 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
Widget more(BuildContext context) {
|
||||
bool enabled = !locked;
|
||||
return _Card(title: 'Security', children: [
|
||||
Offstage(
|
||||
offstage: !Platform.isWindows,
|
||||
child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp',
|
||||
enabled: enabled),
|
||||
),
|
||||
shareRdp(context, enabled),
|
||||
_OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery',
|
||||
reverse: true, enabled: enabled),
|
||||
@ -1273,9 +1307,9 @@ class _DisplayState extends State<_Display> {
|
||||
}
|
||||
|
||||
Widget other(BuildContext context) {
|
||||
return _Card(title: 'Other Default Options', children: [
|
||||
final children = [
|
||||
otherRow('View Mode', 'view_only'),
|
||||
otherRow('show_monitors_tip', 'show_monitors_toolbar'),
|
||||
otherRow('show_monitors_tip', kKeyShowMonitorsToolbar),
|
||||
otherRow('Collapse toolbar', 'collapse_toolbar'),
|
||||
otherRow('Show remote cursor', 'show_remote_cursor'),
|
||||
otherRow('Zoom cursor', 'zoom-cursor'),
|
||||
@ -1286,7 +1320,12 @@ class _DisplayState extends State<_Display> {
|
||||
otherRow('Lock after session end', 'lock_after_session_end'),
|
||||
otherRow('Privacy mode', 'privacy_mode'),
|
||||
otherRow('Reverse mouse wheel', 'reverse_mouse_wheel'),
|
||||
]);
|
||||
];
|
||||
if (useTextureRender) {
|
||||
children.add(otherRow('Show displays as individual windows',
|
||||
kKeyShowDisplaysAsIndividualWindows));
|
||||
}
|
||||
return _Card(title: 'Other Default Options', children: children);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1878,6 +1917,69 @@ class _ComboBox extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _CountDownButton extends StatefulWidget {
|
||||
_CountDownButton({
|
||||
Key? key,
|
||||
required this.text,
|
||||
required this.second,
|
||||
required this.onPressed,
|
||||
}) : super(key: key);
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
final int second;
|
||||
|
||||
@override
|
||||
State<_CountDownButton> createState() => _CountDownButtonState();
|
||||
}
|
||||
|
||||
class _CountDownButtonState extends State<_CountDownButton> {
|
||||
bool _isButtonDisabled = false;
|
||||
|
||||
late int _countdownSeconds = widget.second;
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startCountdownTimer() {
|
||||
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
if (_countdownSeconds <= 0) {
|
||||
setState(() {
|
||||
_isButtonDisabled = false;
|
||||
});
|
||||
timer.cancel();
|
||||
} else {
|
||||
setState(() {
|
||||
_countdownSeconds--;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ElevatedButton(
|
||||
onPressed: _isButtonDisabled
|
||||
? null
|
||||
: () {
|
||||
widget.onPressed?.call();
|
||||
setState(() {
|
||||
_isButtonDisabled = true;
|
||||
_countdownSeconds = widget.second;
|
||||
});
|
||||
_startCountdownTimer();
|
||||
},
|
||||
child: Text(
|
||||
_isButtonDisabled ? '$_countdownSeconds s' : translate(widget.text),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region dialogs
|
||||
|
@ -28,6 +28,7 @@ import '../widgets/tabbar_widget.dart';
|
||||
|
||||
final SimpleWrapper<bool> _firstEnterImage = SimpleWrapper(false);
|
||||
|
||||
// Used to skip session close if "move to new window" is clicked.
|
||||
final Map<String, bool> closeSessionOnDispose = {};
|
||||
|
||||
class RemotePage extends StatefulWidget {
|
||||
@ -36,6 +37,8 @@ class RemotePage extends StatefulWidget {
|
||||
required this.id,
|
||||
required this.sessionId,
|
||||
required this.tabWindowId,
|
||||
required this.display,
|
||||
required this.displays,
|
||||
required this.password,
|
||||
required this.toolbarState,
|
||||
required this.tabController,
|
||||
@ -46,6 +49,8 @@ class RemotePage extends StatefulWidget {
|
||||
final String id;
|
||||
final SessionID? sessionId;
|
||||
final int? tabWindowId;
|
||||
final int? display;
|
||||
final List<int>? displays;
|
||||
final String? password;
|
||||
final ToolbarState toolbarState;
|
||||
final String? switchUuid;
|
||||
@ -73,7 +78,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
late RxBool _zoomCursor;
|
||||
late RxBool _remoteCursorMoved;
|
||||
late RxBool _keyboardEnabled;
|
||||
late RenderTexture _renderTexture;
|
||||
final Map<int, RenderTexture> _renderTextures = {};
|
||||
|
||||
final _blockableOverlayState = BlockableOverlayState();
|
||||
|
||||
@ -109,6 +114,8 @@ class _RemotePageState extends State<RemotePage>
|
||||
switchUuid: widget.switchUuid,
|
||||
forceRelay: widget.forceRelay,
|
||||
tabWindowId: widget.tabWindowId,
|
||||
display: widget.display,
|
||||
displays: widget.displays,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
@ -118,9 +125,6 @@ class _RemotePageState extends State<RemotePage>
|
||||
if (!Platform.isLinux) {
|
||||
Wakelock.enable();
|
||||
}
|
||||
// Register texture.
|
||||
_renderTexture = RenderTexture();
|
||||
_renderTexture.create(sessionId);
|
||||
|
||||
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
|
||||
bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
||||
@ -207,7 +211,9 @@ class _RemotePageState extends State<RemotePage>
|
||||
// https://github.com/flutter/flutter/issues/64935
|
||||
super.dispose();
|
||||
debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}");
|
||||
await _renderTexture.destroy(closeSession);
|
||||
for (final texture in _renderTextures.values) {
|
||||
await texture.destroy(closeSession);
|
||||
}
|
||||
// ensure we leave this session, this is a double check
|
||||
_ffi.inputModel.enterOrLeave(false);
|
||||
DesktopMultiWindow.removeListener(this);
|
||||
@ -245,6 +251,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
onEnterOrLeaveImageSetter: (func) =>
|
||||
_onEnterOrLeaveImage4Toolbar = func,
|
||||
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Toolbar = null,
|
||||
setRemoteState: setState,
|
||||
);
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
@ -392,6 +399,38 @@ class _RemotePageState extends State<RemotePage>
|
||||
);
|
||||
}
|
||||
|
||||
Map<int, RenderTexture> _updateGetRenderTextures(int curDisplay) {
|
||||
tryCreateTexture(int idx) {
|
||||
if (!_renderTextures.containsKey(idx)) {
|
||||
final renderTexture = RenderTexture();
|
||||
_renderTextures[idx] = renderTexture;
|
||||
renderTexture.create(idx, sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
tryRemoveTexture(int idx) {
|
||||
if (_renderTextures.containsKey(idx)) {
|
||||
_renderTextures[idx]!.destroy(true);
|
||||
_renderTextures.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (curDisplay == kAllDisplayValue) {
|
||||
final displays = _ffi.ffiModel.pi.getCurDisplays();
|
||||
for (var i = 0; i < displays.length; i++) {
|
||||
tryCreateTexture(i);
|
||||
}
|
||||
} else {
|
||||
tryCreateTexture(curDisplay);
|
||||
for (var i = 0; i < _ffi.ffiModel.pi.displays.length; i++) {
|
||||
if (i != curDisplay) {
|
||||
tryRemoveTexture(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _renderTextures;
|
||||
}
|
||||
|
||||
Widget getBodyForDesktop(BuildContext context) {
|
||||
var paints = <Widget>[
|
||||
MouseRegion(onEnter: (evt) {
|
||||
@ -402,16 +441,20 @@ class _RemotePageState extends State<RemotePage>
|
||||
Future.delayed(Duration.zero, () {
|
||||
Provider.of<CanvasModel>(context, listen: false).updateViewStyle();
|
||||
});
|
||||
return ImagePaint(
|
||||
id: widget.id,
|
||||
zoomCursor: _zoomCursor,
|
||||
cursorOverImage: _cursorOverImage,
|
||||
keyboardEnabled: _keyboardEnabled,
|
||||
remoteCursorMoved: _remoteCursorMoved,
|
||||
textureId: _renderTexture.textureId,
|
||||
useTextureRender: RenderTexture.useTextureRender,
|
||||
listenerBuilder: (child) =>
|
||||
_buildRawTouchAndPointerRegion(child, enterView, leaveView),
|
||||
final peerDisplay = CurrentDisplayState.find(widget.id);
|
||||
return Obx(
|
||||
() => _ffi.ffiModel.pi.isSet.isFalse
|
||||
? Container(color: Colors.transparent)
|
||||
: Obx(() => ImagePaint(
|
||||
id: widget.id,
|
||||
zoomCursor: _zoomCursor,
|
||||
cursorOverImage: _cursorOverImage,
|
||||
keyboardEnabled: _keyboardEnabled,
|
||||
remoteCursorMoved: _remoteCursorMoved,
|
||||
renderTextures: _updateGetRenderTextures(peerDisplay.value),
|
||||
listenerBuilder: (child) => _buildRawTouchAndPointerRegion(
|
||||
child, enterView, leaveView),
|
||||
)),
|
||||
);
|
||||
}))
|
||||
];
|
||||
@ -447,8 +490,7 @@ class ImagePaint extends StatefulWidget {
|
||||
final RxBool cursorOverImage;
|
||||
final RxBool keyboardEnabled;
|
||||
final RxBool remoteCursorMoved;
|
||||
final RxInt textureId;
|
||||
final bool useTextureRender;
|
||||
final Map<int, RenderTexture> renderTextures;
|
||||
final Widget Function(Widget)? listenerBuilder;
|
||||
|
||||
ImagePaint(
|
||||
@ -458,8 +500,7 @@ class ImagePaint extends StatefulWidget {
|
||||
required this.cursorOverImage,
|
||||
required this.keyboardEnabled,
|
||||
required this.remoteCursorMoved,
|
||||
required this.textureId,
|
||||
required this.useTextureRender,
|
||||
required this.renderTextures,
|
||||
this.listenerBuilder})
|
||||
: super(key: key);
|
||||
|
||||
@ -530,27 +571,13 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
});
|
||||
|
||||
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
|
||||
final imageWidth = c.getDisplayWidth() * s;
|
||||
final imageHeight = c.getDisplayHeight() * s;
|
||||
final imageSize = Size(imageWidth, imageHeight);
|
||||
late final Widget imageWidget;
|
||||
if (widget.useTextureRender) {
|
||||
imageWidget = SizedBox(
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
child: Obx(() => Texture(
|
||||
textureId: widget.textureId.value,
|
||||
filterQuality:
|
||||
isViewOriginal() ? FilterQuality.none : FilterQuality.low,
|
||||
)),
|
||||
);
|
||||
} else {
|
||||
imageWidget = CustomPaint(
|
||||
size: imageSize,
|
||||
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
|
||||
);
|
||||
}
|
||||
|
||||
final paintWidth = c.getDisplayWidth() * s;
|
||||
final paintHeight = c.getDisplayHeight() * s;
|
||||
final paintSize = Size(paintWidth, paintHeight);
|
||||
final paintWidget = useTextureRender
|
||||
? _BuildPaintTextureRender(
|
||||
c, s, Offset.zero, paintSize, isViewOriginal())
|
||||
: _buildScrollbarNonTextureRender(m, paintSize, s);
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
final percentX = _horizontal.hasClients
|
||||
@ -570,43 +597,79 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
},
|
||||
child: mouseRegion(
|
||||
child: Obx(() => _buildCrossScrollbarFromLayout(
|
||||
context, _buildListener(imageWidget), c.size, imageSize)),
|
||||
context, _buildListener(paintWidget), c.size, paintSize)),
|
||||
));
|
||||
} else {
|
||||
late final Widget imageWidget;
|
||||
if (c.size.width > 0 && c.size.height > 0) {
|
||||
if (widget.useTextureRender) {
|
||||
final x = Platform.isLinux ? c.x.toInt().toDouble() : c.x;
|
||||
final y = Platform.isLinux ? c.y.toInt().toDouble() : c.y;
|
||||
imageWidget = Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: x,
|
||||
top: y,
|
||||
width: c.getDisplayWidth() * s,
|
||||
height: c.getDisplayHeight() * s,
|
||||
child: Texture(
|
||||
textureId: widget.textureId.value,
|
||||
filterQuality:
|
||||
isViewOriginal() ? FilterQuality.none : FilterQuality.low,
|
||||
final paintWidget = useTextureRender
|
||||
? _BuildPaintTextureRender(
|
||||
c,
|
||||
s,
|
||||
Offset(
|
||||
Platform.isLinux ? c.x.toInt().toDouble() : c.x,
|
||||
Platform.isLinux ? c.y.toInt().toDouble() : c.y,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
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));
|
||||
c.size,
|
||||
isViewOriginal())
|
||||
: _buildScrollAuthNonTextureRender(m, c, s);
|
||||
return mouseRegion(child: _buildListener(paintWidget));
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildScrollbarNonTextureRender(
|
||||
ImageModel m, Size imageSize, double s) {
|
||||
return CustomPaint(
|
||||
size: imageSize,
|
||||
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScrollAuthNonTextureRender(
|
||||
ImageModel m, CanvasModel c, double s) {
|
||||
return CustomPaint(
|
||||
size: Size(c.size.width, c.size.height),
|
||||
painter: ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _BuildPaintTextureRender(
|
||||
CanvasModel c, double s, Offset offset, Size size, bool isViewOriginal) {
|
||||
final ffiModel = c.parent.target!.ffiModel;
|
||||
final displays = ffiModel.pi.getCurDisplays();
|
||||
final children = <Widget>[];
|
||||
final rect = ffiModel.rect;
|
||||
if (rect == null) {
|
||||
return Container();
|
||||
}
|
||||
final curDisplay = ffiModel.pi.currentDisplay;
|
||||
for (var i = 0; i < displays.length; i++) {
|
||||
final textureId = widget
|
||||
.renderTextures[curDisplay == kAllDisplayValue ? i : curDisplay]
|
||||
?.textureId;
|
||||
if (textureId != null) {
|
||||
children.add(Positioned(
|
||||
left: (displays[i].x - rect.left) * s + offset.dx,
|
||||
top: (displays[i].y - rect.top) * s + offset.dy,
|
||||
width: displays[i].width * s,
|
||||
height: displays[i].height * s,
|
||||
child: Obx(() => Texture(
|
||||
textureId: textureId.value,
|
||||
filterQuality:
|
||||
isViewOriginal ? FilterQuality.none : FilterQuality.low,
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
||||
return SizedBox(
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
child: Stack(children: children),
|
||||
);
|
||||
}
|
||||
|
||||
MouseCursor _buildCursorOfCache(
|
||||
CursorModel cursor, double scale, CursorData? cache) {
|
||||
if (cache == null) {
|
||||
@ -731,7 +794,11 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
);
|
||||
}
|
||||
|
||||
return widget;
|
||||
return Container(
|
||||
child: widget,
|
||||
width: layoutSize.width,
|
||||
height: layoutSize.height,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListener(Widget child) {
|
||||
@ -770,9 +837,14 @@ class CursorPaint extends StatelessWidget {
|
||||
double cy = c.y;
|
||||
if (c.viewStyle.style == kRemoteViewStyleOriginal &&
|
||||
c.scrollStyle == ScrollStyle.scrollbar) {
|
||||
final d = c.parent.target!.ffiModel.display;
|
||||
final imageWidth = d.width * c.scale;
|
||||
final imageHeight = d.height * c.scale;
|
||||
final rect = c.parent.target!.ffiModel.rect;
|
||||
if (rect == null) {
|
||||
// unreachable!
|
||||
debugPrint('unreachable! The displays rect is null.');
|
||||
return Container();
|
||||
}
|
||||
final imageWidth = rect.width * c.scale;
|
||||
final imageHeight = rect.height * c.scale;
|
||||
cx = -imageWidth * c.scrollX;
|
||||
cy = -imageHeight * c.scrollY;
|
||||
}
|
||||
|
@ -57,6 +57,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
peerId = params['id'];
|
||||
final sessionId = params['session_id'];
|
||||
final tabWindowId = params['tab_window_id'];
|
||||
final display = params['display'];
|
||||
final displays = params['displays'];
|
||||
if (peerId != null) {
|
||||
ConnectionTypeState.init(peerId!);
|
||||
tabController.onSelected = (id) {
|
||||
@ -80,6 +82,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
id: peerId!,
|
||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||
tabWindowId: tabWindowId,
|
||||
display: display,
|
||||
displays: displays?.cast<int>(),
|
||||
password: params['password'],
|
||||
toolbarState: _toolbarState,
|
||||
tabController: tabController,
|
||||
@ -109,6 +113,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
final switchUuid = args['switch_uuid'];
|
||||
final sessionId = args['session_id'];
|
||||
final tabWindowId = args['tab_window_id'];
|
||||
final display = args['display'];
|
||||
final displays = args['displays'];
|
||||
windowOnTop(windowId());
|
||||
if (tabController.length == 0) {
|
||||
if (Platform.isMacOS && stateGlobal.closeOnFullscreen) {
|
||||
@ -129,6 +135,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
id: id,
|
||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||
tabWindowId: tabWindowId,
|
||||
display: display,
|
||||
displays: displays?.cast<int>(),
|
||||
password: args['password'],
|
||||
toolbarState: _toolbarState,
|
||||
tabController: tabController,
|
||||
@ -148,6 +156,15 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
windowOnTop(windowId());
|
||||
}
|
||||
return jumpOk;
|
||||
} else if (call.method == kWindowEventActiveDisplaySession) {
|
||||
final args = jsonDecode(call.arguments);
|
||||
final id = args['id'];
|
||||
final display = args['display'];
|
||||
final jumpOk = tabController.jumpToByKeyAndDisplay(id, display);
|
||||
if (jumpOk) {
|
||||
windowOnTop(windowId());
|
||||
}
|
||||
return jumpOk;
|
||||
} else if (call.method == kWindowEventGetRemoteList) {
|
||||
return tabController.state.value.tabs
|
||||
.map((e) => e.key)
|
||||
@ -160,18 +177,20 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
.join(';');
|
||||
} else if (call.method == kWindowEventGetCachedSessionData) {
|
||||
// Ready to show new window and close old tab.
|
||||
final peerId = call.arguments;
|
||||
final args = jsonDecode(call.arguments);
|
||||
final id = args['id'];
|
||||
final close = args['close'];
|
||||
try {
|
||||
final remotePage = tabController.state.value.tabs
|
||||
.firstWhere((tab) => tab.key == peerId)
|
||||
.firstWhere((tab) => tab.key == id)
|
||||
.page as RemotePage;
|
||||
returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString();
|
||||
} catch (e) {
|
||||
debugPrint('Failed to get cached session data: $e');
|
||||
}
|
||||
if (returnValue != null) {
|
||||
closeSessionOnDispose[peerId] = false;
|
||||
tabController.closeBy(peerId);
|
||||
if (close && returnValue != null) {
|
||||
closeSessionOnDispose[id] = false;
|
||||
tabController.closeBy(id);
|
||||
}
|
||||
}
|
||||
_update_remote_count();
|
||||
|
@ -1,12 +1,15 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
|
||||
@ -325,7 +328,8 @@ class RemoteToolbar extends StatefulWidget {
|
||||
final FFI ffi;
|
||||
final ToolbarState state;
|
||||
final Function(Function(bool)) onEnterOrLeaveImageSetter;
|
||||
final Function() onEnterOrLeaveImageCleaner;
|
||||
final VoidCallback onEnterOrLeaveImageCleaner;
|
||||
final Function(VoidCallback) setRemoteState;
|
||||
|
||||
RemoteToolbar({
|
||||
Key? key,
|
||||
@ -334,6 +338,7 @@ class RemoteToolbar extends StatefulWidget {
|
||||
required this.state,
|
||||
required this.onEnterOrLeaveImageSetter,
|
||||
required this.onEnterOrLeaveImageCleaner,
|
||||
required this.setRemoteState,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -450,13 +455,17 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
toolbarItems.add(_MobileActionMenu(ffi: widget.ffi));
|
||||
}
|
||||
|
||||
if (PrivacyModeState.find(widget.id).isFalse && pi.displays.length > 1) {
|
||||
toolbarItems.add(
|
||||
bind.mainGetUserDefaultOption(key: 'show_monitors_toolbar') == 'Y'
|
||||
? _MultiMonitorMenu(id: widget.id, ffi: widget.ffi)
|
||||
: _MonitorMenu(id: widget.id, ffi: widget.ffi),
|
||||
);
|
||||
}
|
||||
toolbarItems.add(Obx(() {
|
||||
if (PrivacyModeState.find(widget.id).isFalse &&
|
||||
pi.displaysCount.value > 1) {
|
||||
return _MonitorMenu(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
setRemoteState: widget.setRemoteState);
|
||||
} else {
|
||||
return Offstage();
|
||||
}
|
||||
}));
|
||||
|
||||
toolbarItems
|
||||
.add(_ControlMenu(id: widget.id, ffi: widget.ffi, state: widget.state));
|
||||
@ -581,11 +590,25 @@ class _MobileActionMenu extends StatelessWidget {
|
||||
class _MonitorMenu extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
const _MonitorMenu({Key? key, required this.id, required this.ffi})
|
||||
: super(key: key);
|
||||
final Function(VoidCallback) setRemoteState;
|
||||
const _MonitorMenu({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
required this.setRemoteState,
|
||||
}) : super(key: key);
|
||||
|
||||
bool get showMonitorsToolbar =>
|
||||
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y';
|
||||
|
||||
bool get supportIndividualWindows =>
|
||||
useTextureRender && ffi.ffiModel.pi.isSupportMultiDisplay;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context) =>
|
||||
showMonitorsToolbar ? buildMultiMonitorMenu() : buildMonitorMenu();
|
||||
|
||||
Widget buildMonitorMenu() {
|
||||
return _IconSubmenuButton(
|
||||
tooltip: 'Select Monitor',
|
||||
icon: icon(),
|
||||
@ -595,7 +618,106 @@ class _MonitorMenu extends StatelessWidget {
|
||||
menuStyle: MenuStyle(
|
||||
padding:
|
||||
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
|
||||
menuChildren: [Row(children: displays(context))]);
|
||||
menuChildren: [buildMonitorSubmenuWidget()]);
|
||||
}
|
||||
|
||||
Widget buildMultiMonitorMenu() {
|
||||
return Row(children: buildMonitorList(true));
|
||||
}
|
||||
|
||||
Widget buildMonitorSubmenuWidget() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(children: buildMonitorList(false)),
|
||||
supportIndividualWindows ? Divider() : Offstage(),
|
||||
supportIndividualWindows ? chooseDisplayBehavior() : Offstage(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget chooseDisplayBehavior() {
|
||||
final value =
|
||||
bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) ==
|
||||
'Y';
|
||||
return CkbMenuButton(
|
||||
value: value,
|
||||
onChanged: (value) async {
|
||||
if (value == null) return;
|
||||
await bind.sessionSetDisplaysAsIndividualWindows(
|
||||
sessionId: ffi.sessionId, value: value ? 'Y' : '');
|
||||
},
|
||||
ffi: ffi,
|
||||
child: Text(translate('Show displays as individual windows')));
|
||||
}
|
||||
|
||||
List<Widget> buildMonitorList(bool isMulti) {
|
||||
final List<Widget> monitorList = [];
|
||||
final pi = ffi.ffiModel.pi;
|
||||
|
||||
getMonitorText(int i) {
|
||||
if (i == kAllDisplayValue) {
|
||||
if (pi.displays.length == 2) {
|
||||
return '1|2';
|
||||
} else {
|
||||
return 'ALL';
|
||||
}
|
||||
} else {
|
||||
return (i + 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
buildMonitorButton(int i) => Obx(() {
|
||||
RxInt display = CurrentDisplayState.find(id);
|
||||
return _IconMenuButton(
|
||||
tooltip: isMulti ? '' : '#${i + 1} monitor',
|
||||
hMargin: isMulti ? null : 6,
|
||||
vMargin: isMulti ? null : 12,
|
||||
topLevel: false,
|
||||
color: i == display.value
|
||||
? _ToolbarTheme.blueColor
|
||||
: _ToolbarTheme.inactiveColor,
|
||||
hoverColor: i == display.value
|
||||
? _ToolbarTheme.hoverBlueColor
|
||||
: _ToolbarTheme.hoverInactiveColor,
|
||||
icon: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
constraints:
|
||||
const BoxConstraints(minHeight: _ToolbarTheme.height),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/screen.svg",
|
||||
colorFilter:
|
||||
ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
),
|
||||
Obx(
|
||||
() => Text(
|
||||
getMonitorText(i),
|
||||
style: TextStyle(
|
||||
color: i == display.value
|
||||
? _ToolbarTheme.blueColor
|
||||
: _ToolbarTheme.inactiveColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () => onPressed(i, pi),
|
||||
);
|
||||
});
|
||||
|
||||
for (int i = 0; i < pi.displays.length; i++) {
|
||||
monitorList.add(buildMonitorButton(i));
|
||||
}
|
||||
if (supportIndividualWindows && pi.displays.length > 1) {
|
||||
monitorList.add(buildMonitorButton(kAllDisplayValue));
|
||||
}
|
||||
return monitorList;
|
||||
}
|
||||
|
||||
icon() {
|
||||
@ -610,7 +732,7 @@ class _MonitorMenu extends StatelessWidget {
|
||||
Obx(() {
|
||||
RxInt display = CurrentDisplayState.find(id);
|
||||
return Text(
|
||||
'${display.value + 1}/${pi.displays.length}',
|
||||
'${display.value == kAllDisplayValue ? 'A' : '${display.value + 1}'}/${pi.displays.length}',
|
||||
style: const TextStyle(
|
||||
color: _ToolbarTheme.blueColor,
|
||||
fontSize: 8,
|
||||
@ -622,48 +744,44 @@ class _MonitorMenu extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> displays(BuildContext context) {
|
||||
final List<Widget> rowChildren = [];
|
||||
final pi = ffi.ffiModel.pi;
|
||||
for (int i = 0; i < pi.displays.length; i++) {
|
||||
rowChildren.add(_IconMenuButton(
|
||||
topLevel: false,
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
tooltip: "#${i + 1} monitor",
|
||||
hMargin: 6,
|
||||
vMargin: 12,
|
||||
icon: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
constraints: const BoxConstraints(minHeight: _ToolbarTheme.height),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/screen.svg",
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
),
|
||||
Text(
|
||||
(i + 1).toString(),
|
||||
style: TextStyle(
|
||||
color: _ToolbarTheme.blueColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
_menuDismissCallback(ffi);
|
||||
RxInt display = CurrentDisplayState.find(id);
|
||||
if (display.value != i) {
|
||||
bind.sessionSwitchDisplay(sessionId: ffi.sessionId, value: i);
|
||||
}
|
||||
},
|
||||
));
|
||||
// Open new tab or window to show this monitor.
|
||||
// For now just open new window.
|
||||
openMonitorInNewTabOrWindow(int i, PeerInfo pi) {
|
||||
if (kWindowId == null) {
|
||||
// unreachable
|
||||
debugPrint('openMonitorInNewTabOrWindow, unreachable! kWindowId is null');
|
||||
return;
|
||||
}
|
||||
DesktopMultiWindow.invokeMethod(
|
||||
kMainWindowId,
|
||||
kWindowEventOpenMonitorSession,
|
||||
jsonEncode({
|
||||
'window_id': kWindowId!,
|
||||
'peer_id': ffi.id,
|
||||
'display': i,
|
||||
'display_count': pi.displays.length,
|
||||
}));
|
||||
}
|
||||
|
||||
openMonitorInTheSameTab(int i, PeerInfo pi) {
|
||||
final displays = i == kAllDisplayValue
|
||||
? List.generate(pi.displays.length, (index) => index)
|
||||
: [i];
|
||||
bind.sessionSwitchDisplay(
|
||||
sessionId: ffi.sessionId, value: Int32List.fromList(displays));
|
||||
ffi.ffiModel.switchToNewDisplay(i, ffi.sessionId, id);
|
||||
}
|
||||
|
||||
onPressed(int i, PeerInfo pi) {
|
||||
_menuDismissCallback(ffi);
|
||||
RxInt display = CurrentDisplayState.find(id);
|
||||
if (display.value != i) {
|
||||
if (isChooseDisplayToOpenInNewWindow(pi, ffi.sessionId)) {
|
||||
openMonitorInNewTabOrWindow(i, pi);
|
||||
} else {
|
||||
openMonitorInTheSameTab(i, pi);
|
||||
}
|
||||
}
|
||||
return rowChildren;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1044,14 +1162,14 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
Resolution? _localResolution;
|
||||
|
||||
late final TextEditingController _customWidth =
|
||||
TextEditingController(text: display.width.toString());
|
||||
TextEditingController(text: rect?.width.toInt().toString() ?? '');
|
||||
late final TextEditingController _customHeight =
|
||||
TextEditingController(text: display.height.toString());
|
||||
TextEditingController(text: rect?.height.toInt().toString() ?? '');
|
||||
|
||||
FFI get ffi => widget.ffi;
|
||||
PeerInfo get pi => widget.ffi.ffiModel.pi;
|
||||
FfiModel get ffiModel => widget.ffi.ffiModel;
|
||||
Display get display => ffiModel.display;
|
||||
Rect? get rect => ffiModel.rect;
|
||||
List<Resolution> get resolutions => pi.resolutions;
|
||||
bool get isWayland => bind.mainCurrentIsWayland();
|
||||
|
||||
@ -1063,12 +1181,12 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isVirtualDisplay = display.isVirtualDisplayResolution;
|
||||
final isVirtualDisplay = ffiModel.isVirtualDisplayResolution;
|
||||
final visible =
|
||||
ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1);
|
||||
if (!visible) return Offstage();
|
||||
final showOriginalBtn =
|
||||
display.isOriginalResolutionSet && !display.isOriginalResolution;
|
||||
ffiModel.isOriginalResolutionSet && !ffiModel.isOriginalResolution;
|
||||
final showFitLocalBtn = !_isRemoteResolutionFitLocal();
|
||||
_setGroupValue();
|
||||
return _SubmenuButton(
|
||||
@ -1085,12 +1203,15 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
}
|
||||
|
||||
_setGroupValue() {
|
||||
if (pi.currentDisplay == kAllDisplayValue) {
|
||||
return;
|
||||
}
|
||||
final lastGroupValue =
|
||||
stateGlobal.getLastResolutionGroupValue(widget.id, pi.currentDisplay);
|
||||
if (lastGroupValue == _kCustomResolutionValue) {
|
||||
_groupValue = _kCustomResolutionValue;
|
||||
} else {
|
||||
_groupValue = '${display.width}x${display.height}';
|
||||
_groupValue = '${rect?.width.toInt()}x${rect?.height.toInt()}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1118,20 +1239,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
|
||||
_getLocalResolution() {
|
||||
_localResolution = null;
|
||||
final String currentDisplay = bind.mainGetCurrentDisplay();
|
||||
if (currentDisplay.isNotEmpty) {
|
||||
final String mainDisplay = bind.mainGetMainDisplay();
|
||||
if (mainDisplay.isNotEmpty) {
|
||||
try {
|
||||
final display = json.decode(currentDisplay);
|
||||
final display = json.decode(mainDisplay);
|
||||
if (display['w'] != null && display['h'] != null) {
|
||||
_localResolution = Resolution(display['w'], display['h']);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Failed to decode $currentDisplay, $e');
|
||||
debugPrint('Failed to decode $mainDisplay, $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onChanged(BuildContext context, String? value) async {
|
||||
if (pi.currentDisplay == kAllDisplayValue) {
|
||||
return;
|
||||
}
|
||||
stateGlobal.setLastResolutionGroupValue(
|
||||
widget.id, pi.currentDisplay, value);
|
||||
if (value == null) return;
|
||||
@ -1150,13 +1274,16 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
}
|
||||
|
||||
if (w != null && h != null) {
|
||||
if (w != display.width || h != display.height) {
|
||||
if (w != rect?.width.toInt() || h != rect?.height.toInt()) {
|
||||
await _changeResolution(context, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_changeResolution(BuildContext context, int w, int h) async {
|
||||
if (pi.currentDisplay == kAllDisplayValue) {
|
||||
return;
|
||||
}
|
||||
await bind.sessionChangeResolution(
|
||||
sessionId: ffi.sessionId,
|
||||
display: pi.currentDisplay,
|
||||
@ -1164,8 +1291,11 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
height: h,
|
||||
);
|
||||
Future.delayed(Duration(seconds: 3), () async {
|
||||
final display = ffiModel.display;
|
||||
if (w == display.width && h == display.height) {
|
||||
final rect = ffiModel.rect;
|
||||
if (rect == null) {
|
||||
return;
|
||||
}
|
||||
if (w == rect.width.toInt() && h == rect.height.toInt()) {
|
||||
if (await widget.screenAdjustor.isWindowCanBeAdjusted()) {
|
||||
widget.screenAdjustor.doAdjustWindow(context);
|
||||
}
|
||||
@ -1175,6 +1305,10 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
|
||||
Widget _OriginalResolutionMenuButton(
|
||||
BuildContext context, bool showOriginalBtn) {
|
||||
final display = pi.tryGetDisplayIfNotAllDisplay();
|
||||
if (display == null) {
|
||||
return Offstage();
|
||||
}
|
||||
return Offstage(
|
||||
offstage: !showOriginalBtn,
|
||||
child: MenuButton(
|
||||
@ -1262,7 +1396,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (display.isVirtualDisplayResolution) {
|
||||
if (ffiModel.isVirtualDisplayResolution) {
|
||||
return _localResolution!;
|
||||
}
|
||||
|
||||
@ -1284,8 +1418,8 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
if (bestFitResolution == null) {
|
||||
return true;
|
||||
}
|
||||
return bestFitResolution.width == display.width &&
|
||||
bestFitResolution.height == display.height;
|
||||
return bestFitResolution.width == rect?.width.toInt() &&
|
||||
bestFitResolution.height == rect?.height.toInt();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1361,7 +1495,7 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pi.is_wayland && mode.key != _kKeyMapMode) {
|
||||
if (pi.isWayland && mode.key != _kKeyMapMode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1404,7 +1538,7 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
|
||||
viewMode() {
|
||||
final ffiModel = ffi.ffiModel;
|
||||
final enabled = version_cmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard;
|
||||
final enabled = versionCmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard;
|
||||
return CkbMenuButton(
|
||||
value: ffiModel.viewOnly,
|
||||
onChanged: enabled
|
||||
@ -2037,71 +2171,3 @@ Widget _buildPointerTrackWidget(Widget child, FFI ffi) {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _MultiMonitorMenu extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
|
||||
const _MultiMonitorMenu({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> rowChildren = [];
|
||||
final pi = ffi.ffiModel.pi;
|
||||
|
||||
for (int i = 0; i < pi.displays.length; i++) {
|
||||
rowChildren.add(
|
||||
Obx(() {
|
||||
RxInt display = CurrentDisplayState.find(id);
|
||||
return _IconMenuButton(
|
||||
tooltip: "",
|
||||
topLevel: false,
|
||||
color: i == display.value
|
||||
? _ToolbarTheme.blueColor
|
||||
: Colors.grey[800]!,
|
||||
hoverColor: i == display.value
|
||||
? _ToolbarTheme.hoverBlueColor
|
||||
: Colors.grey[850]!,
|
||||
icon: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
constraints:
|
||||
const BoxConstraints(minHeight: _ToolbarTheme.height),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/screen.svg",
|
||||
colorFilter:
|
||||
ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
),
|
||||
Obx(
|
||||
() => Text(
|
||||
(i + 1).toString(),
|
||||
style: TextStyle(
|
||||
color: i == display.value
|
||||
? _ToolbarTheme.blueColor
|
||||
: Colors.grey[800]!,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (display.value != i) {
|
||||
bind.sessionSwitchDisplay(sessionId: ffi.sessionId, value: i);
|
||||
}
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
return Row(children: rowChildren);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' hide TabBarTheme;
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
@ -176,6 +177,19 @@ class DesktopTabController {
|
||||
jumpTo(state.value.tabs.indexWhere((tab) => tab.key == key),
|
||||
callOnSelected: callOnSelected);
|
||||
|
||||
bool jumpToByKeyAndDisplay(String key, int display) {
|
||||
for (int i = 0; i < state.value.tabs.length; i++) {
|
||||
final tab = state.value.tabs[i];
|
||||
if (tab.key == key) {
|
||||
final ffi = (tab.page as RemotePage).ffi;
|
||||
if (ffi.ffiModel.pi.currentDisplay == display) {
|
||||
return jumpTo(i, callOnSelected: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void closeBy(String? key) {
|
||||
if (!isDesktop) return;
|
||||
assert(onRemoved != null);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -755,14 +756,14 @@ void showOptions(
|
||||
if (image != null) {
|
||||
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
|
||||
}
|
||||
if (pi.displays.length > 1) {
|
||||
if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) {
|
||||
final cur = pi.currentDisplay;
|
||||
final children = <Widget>[];
|
||||
for (var i = 0; i < pi.displays.length; ++i) {
|
||||
children.add(InkWell(
|
||||
onTap: () {
|
||||
if (i == cur) return;
|
||||
bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: i);
|
||||
bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: Int32List.fromList([i]));
|
||||
gFFI.dialogManager.dismissAll();
|
||||
},
|
||||
child: Ink(
|
||||
|
@ -328,13 +328,20 @@ class _ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 18),
|
||||
Text(
|
||||
translate("scam_text1")+"\n\n"
|
||||
+translate("scam_text2")+"\n",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16.0,
|
||||
SizedBox(
|
||||
height: 220,
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
translate("scam_text1")+"\n\n"
|
||||
+translate("scam_text2")+"\n",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
@ -361,7 +368,9 @@ class _ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: 150),
|
||||
child: ElevatedButton(
|
||||
onPressed: isButtonLocked
|
||||
? null
|
||||
: () {
|
||||
@ -380,10 +389,15 @@ class _ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13.0,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 15),
|
||||
ElevatedButton(
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: 150),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
@ -396,8 +410,11 @@ class _ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13.0,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)])),
|
||||
contentPadding: EdgeInsets.all(0.0),
|
||||
|
@ -23,6 +23,11 @@ bool shouldSortTags() {
|
||||
return bind.mainGetLocalOption(key: sortAbTagsOption).isNotEmpty;
|
||||
}
|
||||
|
||||
final filterAbTagOption = 'filter-ab-by-intersection';
|
||||
bool filterAbTagByIntersection() {
|
||||
return bind.mainGetLocalOption(key: filterAbTagOption).isNotEmpty;
|
||||
}
|
||||
|
||||
class AbModel {
|
||||
final abLoading = false.obs;
|
||||
final pullError = "".obs;
|
||||
@ -31,6 +36,7 @@ class AbModel {
|
||||
final RxMap<String, int> tagColors = Map<String, int>.fromEntries([]).obs;
|
||||
final peers = List<Peer>.empty(growable: true).obs;
|
||||
final sortTags = shouldSortTags().obs;
|
||||
final filterByIntersection = filterAbTagByIntersection().obs;
|
||||
final retrying = false.obs;
|
||||
bool get emtpy => peers.isEmpty && tags.isEmpty;
|
||||
|
||||
|
@ -4,25 +4,30 @@ import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
|
||||
import '../../common.dart';
|
||||
import './platform_model.dart';
|
||||
|
||||
final useTextureRender = bind.mainUseTextureRender();
|
||||
|
||||
class RenderTexture {
|
||||
final RxInt textureId = RxInt(-1);
|
||||
int _textureKey = -1;
|
||||
int _display = 0;
|
||||
SessionID? _sessionId;
|
||||
static final useTextureRender = bind.mainUseTextureRender();
|
||||
|
||||
final textureRenderer = TextureRgbaRenderer();
|
||||
|
||||
RenderTexture();
|
||||
|
||||
create(SessionID sessionId) {
|
||||
int get display => _display;
|
||||
|
||||
create(int d, SessionID sessionId) {
|
||||
if (useTextureRender) {
|
||||
_display = d;
|
||||
_textureKey = bind.getNextTextureKey();
|
||||
_sessionId = sessionId;
|
||||
|
||||
textureRenderer.createTexture(_textureKey).then((id) async {
|
||||
if (id != -1) {
|
||||
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
||||
platformFFI.registerTexture(sessionId, ptr);
|
||||
platformFFI.registerTexture(sessionId, display, ptr);
|
||||
textureId.value = id;
|
||||
}
|
||||
});
|
||||
@ -32,7 +37,7 @@ class RenderTexture {
|
||||
destroy(bool unregisterTexture) async {
|
||||
if (useTextureRender && _textureKey != -1 && _sessionId != null) {
|
||||
if (unregisterTexture) {
|
||||
platformFFI.registerTexture(_sessionId!, 0);
|
||||
platformFFI.registerTexture(_sessionId!, display, 0);
|
||||
}
|
||||
await textureRenderer.closeTexture(_textureKey);
|
||||
_textureKey = -1;
|
||||
|
@ -552,22 +552,22 @@ class InputModel {
|
||||
return v;
|
||||
}
|
||||
|
||||
Offset setNearestEdge(double x, double y, Display d) {
|
||||
double left = x - d.x;
|
||||
double right = d.x + d.width - 1 - x;
|
||||
double top = y - d.y;
|
||||
double bottom = d.y + d.height - 1 - y;
|
||||
Offset setNearestEdge(double x, double y, Rect rect) {
|
||||
double left = x - rect.left;
|
||||
double right = rect.right - 1 - x;
|
||||
double top = y - rect.top;
|
||||
double bottom = rect.bottom - 1 - y;
|
||||
if (left < right && left < top && left < bottom) {
|
||||
x = d.x;
|
||||
x = rect.left;
|
||||
}
|
||||
if (right < left && right < top && right < bottom) {
|
||||
x = d.x + d.width - 1;
|
||||
x = rect.right - 1;
|
||||
}
|
||||
if (top < left && top < right && top < bottom) {
|
||||
y = d.y;
|
||||
y = rect.top;
|
||||
}
|
||||
if (bottom < left && bottom < right && bottom < top) {
|
||||
y = d.y + d.height - 1;
|
||||
y = rect.bottom - 1;
|
||||
}
|
||||
return Offset(x, y);
|
||||
}
|
||||
@ -711,9 +711,12 @@ class InputModel {
|
||||
final nearThr = 3;
|
||||
var nearRight = (canvasModel.size.width - x) < nearThr;
|
||||
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
||||
final d = ffiModel.display;
|
||||
final imageWidth = d.width * canvasModel.scale;
|
||||
final imageHeight = d.height * canvasModel.scale;
|
||||
final rect = ffiModel.rect;
|
||||
if (rect == null) {
|
||||
return null;
|
||||
}
|
||||
final imageWidth = rect.width * canvasModel.scale;
|
||||
final imageHeight = rect.height * canvasModel.scale;
|
||||
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
|
||||
x += imageWidth * canvasModel.scrollX;
|
||||
y += imageHeight * canvasModel.scrollY;
|
||||
@ -741,11 +744,11 @@ class InputModel {
|
||||
y += step;
|
||||
}
|
||||
}
|
||||
x += d.x;
|
||||
y += d.y;
|
||||
x += rect.left;
|
||||
y += rect.top;
|
||||
|
||||
if (onExit) {
|
||||
final pos = setNearestEdge(x, y, d);
|
||||
final pos = setNearestEdge(x, y, rect);
|
||||
x = pos.dx;
|
||||
y = pos.dy;
|
||||
}
|
||||
@ -761,10 +764,10 @@ class InputModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
int minX = d.x.toInt();
|
||||
int maxX = (d.x + d.width).toInt() - 1;
|
||||
int minY = d.y.toInt();
|
||||
int maxY = (d.y + d.height).toInt() - 1;
|
||||
int minX = rect.left.toInt();
|
||||
int maxX = (rect.left + rect.width).toInt() - 1;
|
||||
int minY = rect.top.toInt();
|
||||
int maxY = (rect.top + rect.height).toInt() - 1;
|
||||
evtX = trySetNearestRange(evtX, minX, maxX, 5);
|
||||
evtY = trySetNearestRange(evtY, minY, maxY, 5);
|
||||
if (kind == kPointerEventKindMouse) {
|
||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
@ -18,6 +19,7 @@ import 'package:flutter_hbb/models/peer_tab_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/models/desktop_render_texture.dart';
|
||||
import 'package:flutter_hbb/plugin/event.dart';
|
||||
import 'package:flutter_hbb/plugin/manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
|
||||
@ -86,7 +88,7 @@ class CachedPeerData {
|
||||
class FfiModel with ChangeNotifier {
|
||||
CachedPeerData cachedPeerData = CachedPeerData();
|
||||
PeerInfo _pi = PeerInfo();
|
||||
Display _display = Display();
|
||||
Rect? _rect;
|
||||
|
||||
var _inputBlocked = false;
|
||||
final _permissions = <String, bool>{};
|
||||
@ -103,9 +105,15 @@ class FfiModel with ChangeNotifier {
|
||||
Timer? waitForImageTimer;
|
||||
RxBool waitForFirstImage = true.obs;
|
||||
|
||||
Map<String, bool> get permissions => _permissions;
|
||||
Rect? get rect => _rect;
|
||||
bool get isOriginalResolutionSet =>
|
||||
_pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolutionSet ?? false;
|
||||
bool get isVirtualDisplayResolution =>
|
||||
_pi.tryGetDisplayIfNotAllDisplay()?.isVirtualDisplayResolution ?? false;
|
||||
bool get isOriginalResolution =>
|
||||
_pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolution ?? false;
|
||||
|
||||
Display get display => _display;
|
||||
Map<String, bool> get permissions => _permissions;
|
||||
|
||||
bool? get secure => _secure;
|
||||
|
||||
@ -130,6 +138,24 @@ class FfiModel with ChangeNotifier {
|
||||
sessionId = parent.target!.sessionId;
|
||||
}
|
||||
|
||||
Rect? displaysRect() {
|
||||
final displays = _pi.getCurDisplays();
|
||||
if (displays.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
double l = displays[0].x;
|
||||
double t = displays[0].y;
|
||||
double r = displays[0].x + displays[0].width;
|
||||
double b = displays[0].y + displays[0].height;
|
||||
for (var display in displays.sublist(1)) {
|
||||
l = min(l, display.x);
|
||||
t = min(t, display.y);
|
||||
r = max(r, display.x + display.width);
|
||||
b = max(b, display.y + display.height);
|
||||
}
|
||||
return Rect.fromLTRB(l, t, r, b);
|
||||
}
|
||||
|
||||
toggleTouchMode() {
|
||||
if (!isPeerAndroid) {
|
||||
_touchMode = !_touchMode;
|
||||
@ -154,7 +180,6 @@ class FfiModel with ChangeNotifier {
|
||||
|
||||
clear() {
|
||||
_pi = PeerInfo();
|
||||
_display = Display();
|
||||
_secure = null;
|
||||
_direct = null;
|
||||
_inputBlocked = false;
|
||||
@ -207,8 +232,10 @@ class FfiModel with ChangeNotifier {
|
||||
updateLastCursorId(element);
|
||||
await handleCursorData(element);
|
||||
}
|
||||
updateLastCursorId(data.lastCursorId);
|
||||
handleCursorId(data.lastCursorId);
|
||||
if (data.lastCursorId.isNotEmpty) {
|
||||
updateLastCursorId(data.lastCursorId);
|
||||
handleCursorId(data.lastCursorId);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: why called by two position
|
||||
@ -220,11 +247,12 @@ class FfiModel with ChangeNotifier {
|
||||
} else if (name == 'peer_info') {
|
||||
handlePeerInfo(evt, peerId);
|
||||
} else if (name == 'sync_peer_info') {
|
||||
handleSyncPeerInfo(evt, sessionId);
|
||||
handleSyncPeerInfo(evt, sessionId, peerId);
|
||||
} else if (name == 'connection_ready') {
|
||||
setConnectionType(
|
||||
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
|
||||
} else if (name == 'switch_display') {
|
||||
// switch display is kept for backward compatibility
|
||||
handleSwitchDisplay(evt, sessionId, peerId);
|
||||
} else if (name == 'cursor_data') {
|
||||
updateLastCursorId(evt);
|
||||
@ -279,7 +307,7 @@ class FfiModel with ChangeNotifier {
|
||||
await bind.sessionSwitchSides(sessionId: sessionId);
|
||||
closeConnection(id: peer_id);
|
||||
} else if (name == 'portable_service_running') {
|
||||
parent.target?.elevationModel.onPortableServiceRunning(evt);
|
||||
_handlePortableServiceRunning(peerId, evt);
|
||||
} else if (name == 'on_url_scheme_received') {
|
||||
// currently comes from "_url" ipc of mac and dbus of linux
|
||||
onUrlSchemeReceived(evt);
|
||||
@ -354,20 +382,65 @@ class FfiModel with ChangeNotifier {
|
||||
platformFFI.setEventCallback(startEventListener(sessionId, peerId));
|
||||
}
|
||||
|
||||
_updateCurDisplay(SessionID sessionId, Display newDisplay) {
|
||||
if (newDisplay != _display) {
|
||||
if (newDisplay.x != _display.x || newDisplay.y != _display.y) {
|
||||
parent.target?.cursorModel
|
||||
.updateDisplayOrigin(newDisplay.x, newDisplay.y);
|
||||
_handlePortableServiceRunning(String peerId, Map<String, dynamic> evt) {
|
||||
final running = evt['running'] == 'true';
|
||||
parent.target?.elevationModel.onPortableServiceRunning(running);
|
||||
if (running) {
|
||||
if (pi.primaryDisplay != kInvalidDisplayIndex) {
|
||||
if (pi.currentDisplay != pi.primaryDisplay) {
|
||||
// Notify to switch display
|
||||
msgBox(sessionId, 'custom-nook-nocancel-hasclose-info', 'Prompt',
|
||||
'elevated_switch_display_msg', '', parent.target!.dialogManager);
|
||||
bind.sessionSwitchDisplay(
|
||||
sessionId: sessionId,
|
||||
value: Int32List.fromList([pi.primaryDisplay]));
|
||||
}
|
||||
}
|
||||
_display = newDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
handleAliasChanged(Map<String, dynamic> evt) {
|
||||
if (!isDesktop) return;
|
||||
final String peerId = evt['id'];
|
||||
final String alias = evt['alias'];
|
||||
String label = getDesktopTabLabel(peerId, alias);
|
||||
final rxTabLabel = PeerStringOption.find(evt['id'], 'tabLabel');
|
||||
if (rxTabLabel.value != label) {
|
||||
rxTabLabel.value = label;
|
||||
}
|
||||
}
|
||||
|
||||
updateCurDisplay(SessionID sessionId) {
|
||||
final newRect = displaysRect();
|
||||
if (newRect == null) {
|
||||
return;
|
||||
}
|
||||
if (newRect != _rect) {
|
||||
if (newRect.left != _rect?.left || newRect.top != _rect?.top) {
|
||||
parent.target?.cursorModel
|
||||
.updateDisplayOrigin(newRect.left, newRect.top);
|
||||
}
|
||||
_rect = newRect;
|
||||
parent.target?.canvasModel.updateViewStyle();
|
||||
_updateSessionWidthHeight(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
handleSwitchDisplay(
|
||||
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
|
||||
_pi.currentDisplay = int.parse(evt['display']);
|
||||
final curDisplay = int.parse(evt['display']);
|
||||
|
||||
// The message should be handled by the another UI session.
|
||||
if (isChooseDisplayToOpenInNewWindow(_pi, sessionId)) {
|
||||
if (curDisplay != _pi.currentDisplay) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pi.currentDisplay != kAllDisplayValue) {
|
||||
_pi.currentDisplay = curDisplay;
|
||||
}
|
||||
|
||||
var newDisplay = Display();
|
||||
newDisplay.x = double.tryParse(evt['x']) ?? newDisplay.x;
|
||||
newDisplay.y = double.tryParse(evt['y']) ?? newDisplay.y;
|
||||
@ -378,11 +451,11 @@ class FfiModel with ChangeNotifier {
|
||||
int.tryParse(evt['original_width']) ?? kInvalidResolutionValue;
|
||||
newDisplay.originalHeight =
|
||||
int.tryParse(evt['original_height']) ?? kInvalidResolutionValue;
|
||||
_pi.displays[curDisplay] = newDisplay;
|
||||
|
||||
_updateCurDisplay(sessionId, newDisplay);
|
||||
|
||||
updateCurDisplay(sessionId);
|
||||
try {
|
||||
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
||||
CurrentDisplayState.find(peerId).value = curDisplay;
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
@ -522,13 +595,30 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
_updateSessionWidthHeight(SessionID sessionId) {
|
||||
parent.target?.canvasModel.updateViewStyle();
|
||||
if (display.width <= 0 || display.height <= 0) {
|
||||
if (_rect == null) return;
|
||||
if (_rect!.width <= 0 || _rect!.height <= 0) {
|
||||
debugPrintStack(
|
||||
label: 'invalid display size (${display.width},${display.height})');
|
||||
label: 'invalid display size (${_rect!.width},${_rect!.height})');
|
||||
} else {
|
||||
bind.sessionSetSize(
|
||||
sessionId: sessionId, width: display.width, height: display.height);
|
||||
final displays = _pi.getCurDisplays();
|
||||
if (displays.length == 1) {
|
||||
bind.sessionSetSize(
|
||||
sessionId: sessionId,
|
||||
display:
|
||||
pi.currentDisplay == kAllDisplayValue ? 0 : pi.currentDisplay,
|
||||
width: _rect!.width.toInt(),
|
||||
height: _rect!.height.toInt(),
|
||||
);
|
||||
} else {
|
||||
for (int i = 0; i < displays.length; ++i) {
|
||||
bind.sessionSetSize(
|
||||
sessionId: sessionId,
|
||||
display: i,
|
||||
width: displays[i].width.toInt(),
|
||||
height: displays[i].height.toInt(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -541,11 +631,20 @@ class FfiModel with ChangeNotifier {
|
||||
|
||||
parent.target?.dialogManager.dismissAll();
|
||||
_pi.version = evt['version'];
|
||||
_pi.isSupportMultiUiSession =
|
||||
bind.isSupportMultiUiSession(version: _pi.version);
|
||||
_pi.username = evt['username'];
|
||||
_pi.hostname = evt['hostname'];
|
||||
_pi.platform = evt['platform'];
|
||||
_pi.sasEnabled = evt['sas_enabled'] == 'true';
|
||||
_pi.currentDisplay = int.parse(evt['current_display']);
|
||||
final currentDisplay = int.parse(evt['current_display']);
|
||||
if (_pi.primaryDisplay == kInvalidDisplayIndex) {
|
||||
_pi.primaryDisplay = currentDisplay;
|
||||
}
|
||||
|
||||
if (bind.peerGetDefaultSessionsCount(id: peerId) <= 1) {
|
||||
_pi.currentDisplay = currentDisplay;
|
||||
}
|
||||
|
||||
try {
|
||||
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
||||
@ -569,10 +668,10 @@ class FfiModel with ChangeNotifier {
|
||||
for (int i = 0; i < displays.length; ++i) {
|
||||
_pi.displays.add(evtToDisplay(displays[i]));
|
||||
}
|
||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
||||
_pi.displaysCount.value = _pi.displays.length;
|
||||
if (_pi.currentDisplay < _pi.displays.length) {
|
||||
_display = _pi.displays[_pi.currentDisplay];
|
||||
_updateSessionWidthHeight(sessionId);
|
||||
// now replaced to _updateCurDisplay
|
||||
updateCurDisplay(sessionId);
|
||||
}
|
||||
if (displays.isNotEmpty) {
|
||||
_reconnects = 1;
|
||||
@ -590,12 +689,12 @@ class FfiModel with ChangeNotifier {
|
||||
sessionId: sessionId, arg: 'view-only'));
|
||||
}
|
||||
if (connType == ConnType.defaultConn) {
|
||||
final platform_additions = evt['platform_additions'];
|
||||
if (platform_additions != null && platform_additions != '') {
|
||||
final platformDdditions = evt['platform_additions'];
|
||||
if (platformDdditions != null && platformDdditions != '') {
|
||||
try {
|
||||
_pi.platform_additions = json.decode(platform_additions);
|
||||
_pi.platformDdditions = json.decode(platformDdditions);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to decode platform_additions $e');
|
||||
debugPrint('Failed to decode platformDdditions $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -670,7 +769,8 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Handle the peer info synchronization event based on [evt].
|
||||
handleSyncPeerInfo(Map<String, dynamic> evt, SessionID sessionId) async {
|
||||
handleSyncPeerInfo(
|
||||
Map<String, dynamic> evt, SessionID sessionId, String peerId) async {
|
||||
if (evt['displays'] != null) {
|
||||
cachedPeerData.peerInfo['displays'] = evt['displays'];
|
||||
List<dynamic> displays = json.decode(evt['displays']);
|
||||
@ -679,14 +779,54 @@ class FfiModel with ChangeNotifier {
|
||||
newDisplays.add(evtToDisplay(displays[i]));
|
||||
}
|
||||
_pi.displays = newDisplays;
|
||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
||||
if (_pi.currentDisplay >= 0 && _pi.currentDisplay < _pi.displays.length) {
|
||||
_updateCurDisplay(sessionId, _pi.displays[_pi.currentDisplay]);
|
||||
_pi.displaysCount.value = _pi.displays.length;
|
||||
if (_pi.currentDisplay == kAllDisplayValue) {
|
||||
updateCurDisplay(sessionId);
|
||||
// to-do: What if the displays are changed?
|
||||
} else {
|
||||
if (_pi.currentDisplay >= 0 &&
|
||||
_pi.currentDisplay < _pi.displays.length) {
|
||||
updateCurDisplay(sessionId);
|
||||
} else {
|
||||
if (_pi.displays.isNotEmpty) {
|
||||
// Notify to switch display
|
||||
msgBox(sessionId, 'custom-nook-nocancel-hasclose-info', 'Prompt',
|
||||
'display_is_plugged_out_msg', '', parent.target!.dialogManager);
|
||||
final newDisplay = pi.primaryDisplay == kInvalidDisplayIndex
|
||||
? 0
|
||||
: pi.primaryDisplay;
|
||||
final displays = newDisplay;
|
||||
bind.sessionSwitchDisplay(
|
||||
sessionId: sessionId, value: Int32List.fromList([displays]));
|
||||
|
||||
if (_pi.isSupportMultiUiSession) {
|
||||
// If the peer supports multi-ui-session, no switch display message will be send back.
|
||||
// We need to update the display manually.
|
||||
switchToNewDisplay(newDisplay, sessionId, peerId);
|
||||
}
|
||||
} else {
|
||||
msgBox(sessionId, 'nocancel-error', 'Prompt', 'No Displays', '',
|
||||
parent.target!.dialogManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Directly switch to the new display without waiting for the response.
|
||||
switchToNewDisplay(int display, SessionID sessionId, String peerId) {
|
||||
// no need to wait for the response
|
||||
pi.currentDisplay = display;
|
||||
updateCurDisplay(sessionId);
|
||||
try {
|
||||
CurrentDisplayState.find(peerId).value = display;
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
parent.target?.recordingModel.onSwitchDisplay();
|
||||
}
|
||||
|
||||
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
||||
_inputBlocked = evt['input_state'] == 'on';
|
||||
notifyListeners();
|
||||
@ -709,7 +849,7 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
void setViewOnly(String id, bool value) {
|
||||
if (version_cmp(_pi.version, '1.2.0') < 0) return;
|
||||
if (versionCmp(_pi.version, '1.2.0') < 0) return;
|
||||
// tmp fix for https://github.com/rustdesk/rustdesk/pull/3706#issuecomment-1481242389
|
||||
// because below rx not used in mobile version, so not initialized, below code will cause crash
|
||||
// current our flutter code quality is fucking shit now. !!!!!!!!!!!!!!!!
|
||||
@ -749,16 +889,16 @@ class ImageModel with ChangeNotifier {
|
||||
|
||||
addCallbackOnFirstImage(Function(String) cb) => callbacksOnFirstImage.add(cb);
|
||||
|
||||
onRgba(Uint8List rgba) {
|
||||
onRgba(int display, Uint8List rgba) {
|
||||
final pid = parent.target?.id;
|
||||
img.decodeImageFromPixels(
|
||||
rgba,
|
||||
parent.target?.ffiModel.display.width ?? 0,
|
||||
parent.target?.ffiModel.display.height ?? 0,
|
||||
parent.target?.ffiModel.rect?.width.toInt() ?? 0,
|
||||
parent.target?.ffiModel.rect?.height.toInt() ?? 0,
|
||||
isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888,
|
||||
onPixelsCopied: () {
|
||||
// Unlock the rgba memory from rust codes.
|
||||
platformFFI.nextRgba(sessionId);
|
||||
platformFFI.nextRgba(sessionId, display);
|
||||
}).then((image) {
|
||||
if (parent.target?.id != pid) return;
|
||||
try {
|
||||
@ -1017,20 +1157,20 @@ class CanvasModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
bool get cursorEmbedded =>
|
||||
parent.target?.ffiModel.display.cursorEmbedded ?? false;
|
||||
parent.target?.ffiModel._pi.cursorEmbedded ?? false;
|
||||
|
||||
int getDisplayWidth() {
|
||||
final defaultWidth = (isDesktop || isWebDesktop)
|
||||
? kDesktopDefaultDisplayWidth
|
||||
: kMobileDefaultDisplayWidth;
|
||||
return parent.target?.ffiModel.display.width ?? defaultWidth;
|
||||
return parent.target?.ffiModel.rect?.width.toInt() ?? defaultWidth;
|
||||
}
|
||||
|
||||
int getDisplayHeight() {
|
||||
final defaultHeight = (isDesktop || isWebDesktop)
|
||||
? kDesktopDefaultDisplayHeight
|
||||
: kMobileDefaultDisplayHeight;
|
||||
return parent.target?.ffiModel.display.height ?? defaultHeight;
|
||||
return parent.target?.ffiModel.rect?.height.toInt() ?? defaultHeight;
|
||||
}
|
||||
|
||||
static double get windowBorderWidth => stateGlobal.windowBorderWidth.value;
|
||||
@ -1619,7 +1759,27 @@ class QualityMonitorModel with ChangeNotifier {
|
||||
updateQualityStatus(Map<String, dynamic> evt) {
|
||||
try {
|
||||
if ((evt['speed'] as String).isNotEmpty) _data.speed = evt['speed'];
|
||||
if ((evt['fps'] as String).isNotEmpty) _data.fps = evt['fps'];
|
||||
if ((evt['fps'] as String).isNotEmpty) {
|
||||
final fps = jsonDecode(evt['fps']) as Map<String, dynamic>;
|
||||
final pi = parent.target?.ffiModel.pi;
|
||||
if (pi != null) {
|
||||
final currentDisplay = pi.currentDisplay;
|
||||
if (currentDisplay != kAllDisplayValue) {
|
||||
final fps2 = fps[currentDisplay.toString()];
|
||||
if (fps2 != null) {
|
||||
_data.fps = fps2.toString();
|
||||
}
|
||||
} else if (fps.isNotEmpty) {
|
||||
final fpsList = [];
|
||||
for (var i = 0; i < pi.displays.length; i++) {
|
||||
fpsList.add((fps[i.toString()] ?? 0).toString());
|
||||
}
|
||||
_data.fps = fpsList.join(' ');
|
||||
}
|
||||
} else {
|
||||
_data.fps = null;
|
||||
}
|
||||
}
|
||||
if ((evt['delay'] as String).isNotEmpty) _data.delay = evt['delay'];
|
||||
if ((evt['target_bitrate'] as String).isNotEmpty) {
|
||||
_data.targetBitrate = evt['target_bitrate'];
|
||||
@ -1646,8 +1806,15 @@ class RecordingModel with ChangeNotifier {
|
||||
int? width = parent.target?.canvasModel.getDisplayWidth();
|
||||
int? height = parent.target?.canvasModel.getDisplayHeight();
|
||||
if (sessionId == null || width == null || height == null) return;
|
||||
bind.sessionRecordScreen(
|
||||
sessionId: sessionId, start: true, width: width, height: height);
|
||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
||||
if (currentDisplay != kAllDisplayValue) {
|
||||
bind.sessionRecordScreen(
|
||||
sessionId: sessionId,
|
||||
start: true,
|
||||
display: currentDisplay!,
|
||||
width: width,
|
||||
height: height);
|
||||
}
|
||||
}
|
||||
|
||||
toggle() async {
|
||||
@ -1658,10 +1825,20 @@ class RecordingModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
await bind.sessionRecordStatus(sessionId: sessionId, status: _start);
|
||||
if (_start) {
|
||||
bind.sessionRefresh(sessionId: sessionId);
|
||||
final pi = parent.target?.ffiModel.pi;
|
||||
if (pi != null) {
|
||||
sessionRefreshVideo(sessionId, pi);
|
||||
}
|
||||
} else {
|
||||
bind.sessionRecordScreen(
|
||||
sessionId: sessionId, start: false, width: 0, height: 0);
|
||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
||||
if (currentDisplay != kAllDisplayValue) {
|
||||
bind.sessionRecordScreen(
|
||||
sessionId: sessionId,
|
||||
start: false,
|
||||
display: currentDisplay!,
|
||||
width: 0,
|
||||
height: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1670,8 +1847,15 @@ class RecordingModel with ChangeNotifier {
|
||||
final sessionId = parent.target?.sessionId;
|
||||
if (sessionId == null) return;
|
||||
_start = false;
|
||||
bind.sessionRecordScreen(
|
||||
sessionId: sessionId, start: false, width: 0, height: 0);
|
||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
||||
if (currentDisplay != kAllDisplayValue) {
|
||||
bind.sessionRecordScreen(
|
||||
sessionId: sessionId,
|
||||
start: false,
|
||||
display: currentDisplay!,
|
||||
width: 0,
|
||||
height: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1686,9 +1870,7 @@ class ElevationModel with ChangeNotifier {
|
||||
_running = false;
|
||||
}
|
||||
|
||||
onPortableServiceRunning(Map<String, dynamic> evt) {
|
||||
_running = evt['running'] == 'true';
|
||||
}
|
||||
onPortableServiceRunning(bool running) => _running = running;
|
||||
}
|
||||
|
||||
enum ConnType { defaultConn, fileTransfer, portForward, rdp }
|
||||
@ -1751,14 +1933,18 @@ class FFI {
|
||||
}
|
||||
|
||||
/// Start with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward].
|
||||
void start(String id,
|
||||
{bool isFileTransfer = false,
|
||||
bool isPortForward = false,
|
||||
bool isRdp = false,
|
||||
String? switchUuid,
|
||||
String? password,
|
||||
bool? forceRelay,
|
||||
int? tabWindowId}) {
|
||||
void start(
|
||||
String id, {
|
||||
bool isFileTransfer = false,
|
||||
bool isPortForward = false,
|
||||
bool isRdp = false,
|
||||
String? switchUuid,
|
||||
String? password,
|
||||
bool? forceRelay,
|
||||
int? tabWindowId,
|
||||
int? display,
|
||||
List<int>? displays,
|
||||
}) {
|
||||
closed = false;
|
||||
auditNote = '';
|
||||
if (isMobile) mobileReset();
|
||||
@ -1788,10 +1974,32 @@ class FFI {
|
||||
forceRelay: forceRelay ?? false,
|
||||
password: password ?? '',
|
||||
);
|
||||
} else if (display != null) {
|
||||
if (displays == null) {
|
||||
debugPrint(
|
||||
'Unreachable, failed to add existed session to $id, the displays is null while display is $display');
|
||||
return;
|
||||
}
|
||||
final addRes = bind.sessionAddExistedSync(id: id, sessionId: sessionId);
|
||||
if (addRes != '') {
|
||||
debugPrint(
|
||||
'Unreachable, failed to add existed session to $id, $addRes');
|
||||
return;
|
||||
}
|
||||
bind.sessionTryAddDisplay(
|
||||
sessionId: sessionId, displays: Int32List.fromList(displays));
|
||||
ffiModel.pi.currentDisplay = display;
|
||||
}
|
||||
final stream = bind.sessionStart(sessionId: sessionId, id: id);
|
||||
final cb = ffiModel.startEventListener(sessionId, id);
|
||||
final useTextureRender = bind.mainUseTextureRender();
|
||||
|
||||
// Force refresh displays.
|
||||
// The controlled side may not refresh the image when the (peer,display) is already subscribed.
|
||||
if (displays != null) {
|
||||
for (final display in displays) {
|
||||
bind.sessionRefresh(sessionId: sessionId, display: display);
|
||||
}
|
||||
}
|
||||
|
||||
final SimpleWrapper<bool> isToNewWindowNotified = SimpleWrapper(false);
|
||||
// Preserved for the rgba data.
|
||||
@ -1801,8 +2009,9 @@ class FFI {
|
||||
// Session is read to be moved to a new window.
|
||||
// Get the cached data and handle the cached data.
|
||||
Future.delayed(Duration.zero, () async {
|
||||
final args = jsonEncode({'id': id, 'close': display == null});
|
||||
final cachedData = await DesktopMultiWindow.invokeMethod(
|
||||
tabWindowId, kWindowEventGetCachedSessionData, id);
|
||||
tabWindowId, kWindowEventGetCachedSessionData, args);
|
||||
if (cachedData == null) {
|
||||
// unreachable
|
||||
debugPrint('Unreachable, the cached data is empty.');
|
||||
@ -1814,7 +2023,7 @@ class FFI {
|
||||
return;
|
||||
}
|
||||
await ffiModel.handleCachedPeerData(data, id);
|
||||
await bind.sessionRefresh(sessionId: sessionId);
|
||||
await sessionRefreshVideo(sessionId, ffiModel.pi);
|
||||
});
|
||||
isToNewWindowNotified.value = true;
|
||||
}
|
||||
@ -1836,18 +2045,19 @@ class FFI {
|
||||
await cb(event);
|
||||
}
|
||||
} else if (message is EventToUI_Rgba) {
|
||||
final display = message.field0;
|
||||
if (useTextureRender) {
|
||||
onEvent2UIRgba();
|
||||
} else {
|
||||
// Fetch the image buffer from rust codes.
|
||||
final sz = platformFFI.getRgbaSize(sessionId);
|
||||
final sz = platformFFI.getRgbaSize(sessionId, display);
|
||||
if (sz == 0) {
|
||||
return;
|
||||
}
|
||||
final rgba = platformFFI.getRgba(sessionId, sz);
|
||||
final rgba = platformFFI.getRgba(sessionId, display, sz);
|
||||
if (rgba != null) {
|
||||
onEvent2UIRgba();
|
||||
imageModel.onRgba(rgba);
|
||||
imageModel.onRgba(display, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1979,22 +2189,72 @@ class Features {
|
||||
bool privacyMode = false;
|
||||
}
|
||||
|
||||
const kInvalidDisplayIndex = -1;
|
||||
|
||||
class PeerInfo with ChangeNotifier {
|
||||
String version = '';
|
||||
String username = '';
|
||||
String hostname = '';
|
||||
String platform = '';
|
||||
bool sasEnabled = false;
|
||||
bool isSupportMultiUiSession = false;
|
||||
int currentDisplay = 0;
|
||||
int primaryDisplay = kInvalidDisplayIndex;
|
||||
List<Display> displays = [];
|
||||
Features features = Features();
|
||||
List<Resolution> resolutions = [];
|
||||
Map<String, dynamic> platform_additions = {};
|
||||
Map<String, dynamic> platformDdditions = {};
|
||||
|
||||
RxInt displaysCount = 0.obs;
|
||||
RxBool isSet = false.obs;
|
||||
|
||||
bool get is_wayland => platform_additions['is_wayland'] == true;
|
||||
bool get is_headless => platform_additions['headless'] == true;
|
||||
bool get isWayland => platformDdditions['is_wayland'] == true;
|
||||
bool get isHeadless => platformDdditions['headless'] == true;
|
||||
|
||||
bool get isSupportMultiDisplay => isDesktop && isSupportMultiUiSession;
|
||||
|
||||
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
|
||||
|
||||
Display? tryGetDisplay() {
|
||||
if (displays.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
if (currentDisplay == kAllDisplayValue) {
|
||||
return displays[0];
|
||||
} else {
|
||||
if (currentDisplay > 0 && currentDisplay < displays.length) {
|
||||
return displays[currentDisplay];
|
||||
} else {
|
||||
return displays[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Display? tryGetDisplayIfNotAllDisplay() {
|
||||
if (displays.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
if (currentDisplay == kAllDisplayValue) {
|
||||
return null;
|
||||
}
|
||||
if (currentDisplay > 0 && currentDisplay < displays.length) {
|
||||
return displays[currentDisplay];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<Display> getCurDisplays() {
|
||||
if (currentDisplay == kAllDisplayValue) {
|
||||
return displays;
|
||||
} else {
|
||||
if (currentDisplay >= 0 && currentDisplay < displays.length) {
|
||||
return [displays[currentDisplay]];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const canvasKey = 'canvas';
|
||||
@ -2038,8 +2298,8 @@ Future<void> initializeCursorAndCanvas(FFI ffi) async {
|
||||
currentDisplay = p['currentDisplay'];
|
||||
}
|
||||
if (p == null || currentDisplay != ffi.ffiModel.pi.currentDisplay) {
|
||||
ffi.cursorModel
|
||||
.updateDisplayOrigin(ffi.ffiModel.display.x, ffi.ffiModel.display.y);
|
||||
ffi.cursorModel.updateDisplayOrigin(
|
||||
ffi.ffiModel.rect?.left ?? 0, ffi.ffiModel.rect?.top ?? 0);
|
||||
return;
|
||||
}
|
||||
double xCursor = p['xCursor'];
|
||||
@ -2047,8 +2307,8 @@ Future<void> initializeCursorAndCanvas(FFI ffi) async {
|
||||
double xCanvas = p['xCanvas'];
|
||||
double yCanvas = p['yCanvas'];
|
||||
double scale = p['scale'];
|
||||
ffi.cursorModel.updateDisplayOriginWithCursor(
|
||||
ffi.ffiModel.display.x, ffi.ffiModel.display.y, xCursor, yCursor);
|
||||
ffi.cursorModel.updateDisplayOriginWithCursor(ffi.ffiModel.rect?.left ?? 0,
|
||||
ffi.ffiModel.rect?.top ?? 0, xCursor, yCursor);
|
||||
ffi.canvasModel.update(xCanvas, yCanvas, scale);
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,8 @@ class RgbaFrame extends Struct {
|
||||
external Pointer<Uint8> data;
|
||||
}
|
||||
|
||||
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>);
|
||||
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>, int);
|
||||
typedef F3Dart = Pointer<Uint8> Function(Pointer<Utf8>, Int32);
|
||||
typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt);
|
||||
|
||||
/// FFI wrapper around the native Rust core.
|
||||
@ -80,12 +81,12 @@ class PlatformFFI {
|
||||
String translate(String name, String locale) =>
|
||||
_ffiBind.translate(name: name, locale: locale);
|
||||
|
||||
Uint8List? getRgba(SessionID sessionId, int bufSize) {
|
||||
Uint8List? getRgba(SessionID sessionId, int display, int bufSize) {
|
||||
if (_session_get_rgba == null) return null;
|
||||
final sessionIdStr = sessionId.toString();
|
||||
var a = sessionIdStr.toNativeUtf8();
|
||||
try {
|
||||
final buffer = _session_get_rgba!(a);
|
||||
final buffer = _session_get_rgba!(a, display);
|
||||
if (buffer == nullptr) {
|
||||
return null;
|
||||
}
|
||||
@ -96,12 +97,11 @@ class PlatformFFI {
|
||||
}
|
||||
}
|
||||
|
||||
int getRgbaSize(SessionID sessionId) =>
|
||||
_ffiBind.sessionGetRgbaSize(sessionId: sessionId);
|
||||
void nextRgba(SessionID sessionId) =>
|
||||
_ffiBind.sessionNextRgba(sessionId: sessionId);
|
||||
void registerTexture(SessionID sessionId, int ptr) =>
|
||||
_ffiBind.sessionRegisterTexture(sessionId: sessionId, ptr: ptr);
|
||||
int getRgbaSize(SessionID sessionId, int display) =>
|
||||
_ffiBind.sessionGetRgbaSize(sessionId: sessionId, display: display);
|
||||
void nextRgba(SessionID sessionId, int display) => _ffiBind.sessionNextRgba(sessionId: sessionId, display: display);
|
||||
void registerTexture(SessionID sessionId, int display, int ptr) =>
|
||||
_ffiBind.sessionRegisterTexture(sessionId: sessionId, display: display, ptr: ptr);
|
||||
|
||||
/// Init the FFI class, loads the native Rust core library.
|
||||
Future<void> init(String appType) async {
|
||||
@ -117,7 +117,7 @@ class PlatformFFI {
|
||||
: DynamicLibrary.process();
|
||||
debugPrint('initializing FFI $_appType');
|
||||
try {
|
||||
_session_get_rgba = dylib.lookupFunction<F3, F3>("session_get_rgba");
|
||||
_session_get_rgba = dylib.lookupFunction<F3Dart, F3>("session_get_rgba");
|
||||
try {
|
||||
// SYSTEM user failed
|
||||
_dir = (await getApplicationDocumentsDirectory()).path;
|
||||
|
@ -18,7 +18,6 @@ class StateGlobal {
|
||||
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
|
||||
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
||||
final RxBool showRemoteToolBar = false.obs;
|
||||
final RxInt displaysCount = 0.obs;
|
||||
final svcStatus = SvcStatus.notReady.obs;
|
||||
// Only used for macOS
|
||||
bool closeOnFullscreen = false;
|
||||
|
@ -67,6 +67,44 @@ class RustDeskMultiWindowManager {
|
||||
);
|
||||
}
|
||||
|
||||
// This function must be called in the main window thread.
|
||||
// Because the _remoteDesktopWindows is managed in that thread.
|
||||
openMonitorSession(
|
||||
int windowId, String peerId, int display, int displayCount) async {
|
||||
if (_remoteDesktopWindows.length > 1) {
|
||||
for (final windowId in _remoteDesktopWindows) {
|
||||
if (await DesktopMultiWindow.invokeMethod(
|
||||
windowId,
|
||||
kWindowEventActiveDisplaySession,
|
||||
jsonEncode({
|
||||
'id': peerId,
|
||||
'display': display,
|
||||
}))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final displays = display == kAllDisplayValue
|
||||
? List.generate(displayCount, (index) => index)
|
||||
: [display];
|
||||
var params = {
|
||||
'type': WindowType.RemoteDesktop.index,
|
||||
'id': peerId,
|
||||
'tab_window_id': windowId,
|
||||
'display': display,
|
||||
'displays': displays,
|
||||
};
|
||||
await _newSession(
|
||||
false,
|
||||
WindowType.RemoteDesktop,
|
||||
kWindowEventNewRemoteDesktop,
|
||||
peerId,
|
||||
_remoteDesktopWindows,
|
||||
jsonEncode(params),
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> newSessionWindow(
|
||||
WindowType type, String remoteId, String msg, List<int> windows) async {
|
||||
final windowController = await DesktopMultiWindow.createWindow(msg);
|
||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||
version: 1.2.3+39
|
||||
version: 1.2.4+39
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0"
|
||||
|
@ -10,7 +10,7 @@ use hbb_common::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex as TokioMutex,
|
||||
},
|
||||
ResultType, SessionID,
|
||||
ResultType,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
@ -106,7 +106,7 @@ pub enum ClipboardFile {
|
||||
}
|
||||
|
||||
struct MsgChannel {
|
||||
session_uuid: SessionID,
|
||||
peer_id: String,
|
||||
conn_id: i32,
|
||||
sender: UnboundedSender<ClipboardFile>,
|
||||
receiver: Arc<TokioMutex<UnboundedReceiver<ClipboardFile>>>,
|
||||
@ -135,12 +135,12 @@ impl ClipboardFile {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_client_conn_id(session_uuid: &SessionID) -> Option<i32> {
|
||||
pub fn get_client_conn_id(peer_id: &str) -> Option<i32> {
|
||||
VEC_MSG_CHANNEL
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|x| x.session_uuid == session_uuid.to_owned())
|
||||
.find(|x| x.peer_id == peer_id)
|
||||
.map(|x| x.conn_id)
|
||||
}
|
||||
|
||||
@ -151,13 +151,10 @@ fn get_conn_id() -> i32 {
|
||||
}
|
||||
|
||||
pub fn get_rx_cliprdr_client(
|
||||
session_uuid: &SessionID,
|
||||
peer_id: &str,
|
||||
) -> (i32, Arc<TokioMutex<UnboundedReceiver<ClipboardFile>>>) {
|
||||
let mut lock = VEC_MSG_CHANNEL.write().unwrap();
|
||||
match lock
|
||||
.iter()
|
||||
.find(|x| x.session_uuid == session_uuid.to_owned())
|
||||
{
|
||||
match lock.iter().find(|x| x.peer_id == peer_id) {
|
||||
Some(msg_channel) => (msg_channel.conn_id, msg_channel.receiver.clone()),
|
||||
None => {
|
||||
let (sender, receiver) = unbounded_channel();
|
||||
@ -165,7 +162,7 @@ pub fn get_rx_cliprdr_client(
|
||||
let receiver2 = receiver.clone();
|
||||
let conn_id = get_conn_id();
|
||||
let msg_channel = MsgChannel {
|
||||
session_uuid: session_uuid.to_owned(),
|
||||
peer_id: peer_id.to_owned(),
|
||||
conn_id,
|
||||
sender,
|
||||
receiver,
|
||||
@ -185,7 +182,7 @@ pub fn get_rx_cliprdr_server(conn_id: i32) -> Arc<TokioMutex<UnboundedReceiver<C
|
||||
let receiver = Arc::new(TokioMutex::new(receiver));
|
||||
let receiver2 = receiver.clone();
|
||||
let msg_channel = MsgChannel {
|
||||
session_uuid: SessionID::nil(),
|
||||
peer_id: "".to_string(),
|
||||
conn_id,
|
||||
sender,
|
||||
receiver,
|
||||
|
@ -27,6 +27,7 @@ message VideoFrame {
|
||||
EncodedVideoFrames vp8s = 12;
|
||||
EncodedVideoFrames av1s = 13;
|
||||
}
|
||||
int32 display = 14;
|
||||
}
|
||||
|
||||
message IdPk {
|
||||
@ -491,6 +492,12 @@ message SwitchDisplay {
|
||||
Resolution original_resolution = 8;
|
||||
}
|
||||
|
||||
message CaptureDisplays {
|
||||
repeated int32 add = 1;
|
||||
repeated int32 sub = 2;
|
||||
repeated int32 set = 3;
|
||||
}
|
||||
|
||||
message PermissionInfo {
|
||||
enum Permission {
|
||||
Keyboard = 0;
|
||||
@ -688,6 +695,8 @@ message Misc {
|
||||
uint32 full_speed_fps = 27;
|
||||
uint32 auto_adjust_fps = 28;
|
||||
bool client_record_status = 29;
|
||||
CaptureDisplays capture_displays = 30;
|
||||
int32 refresh_video_display = 31;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,6 +284,12 @@ pub struct PeerConfig {
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub reverse_mouse_wheel: String,
|
||||
#[serde(
|
||||
default = "PeerConfig::default_displays_as_individual_windows",
|
||||
deserialize_with = "PeerConfig::deserialize_displays_as_individual_windows",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub displays_as_individual_windows: String,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
@ -328,6 +334,7 @@ impl Default for PeerConfig {
|
||||
keyboard_mode: Default::default(),
|
||||
view_only: Default::default(),
|
||||
reverse_mouse_wheel: Self::default_reverse_mouse_wheel(),
|
||||
displays_as_individual_windows: Self::default_displays_as_individual_windows(),
|
||||
custom_resolutions: Default::default(),
|
||||
options: Self::default_options(),
|
||||
ui_flutter: Default::default(),
|
||||
@ -1144,6 +1151,11 @@ impl PeerConfig {
|
||||
deserialize_reverse_mouse_wheel,
|
||||
UserDefaultConfig::read().get("reverse_mouse_wheel")
|
||||
);
|
||||
serde_field_string!(
|
||||
default_displays_as_individual_windows,
|
||||
deserialize_displays_as_individual_windows,
|
||||
UserDefaultConfig::read().get("displays_as_individual_windows")
|
||||
);
|
||||
|
||||
fn default_custom_image_quality() -> Vec<i32> {
|
||||
let f: f64 = UserDefaultConfig::read()
|
||||
|
@ -13,7 +13,7 @@ use hbb_common::{
|
||||
anyhow::{anyhow, Context},
|
||||
bytes::Bytes,
|
||||
log,
|
||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
||||
ResultType,
|
||||
};
|
||||
use std::{ptr, slice};
|
||||
@ -240,7 +240,7 @@ impl EncoderApi for AomEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message> {
|
||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<VideoFrame> {
|
||||
let mut frames = Vec::new();
|
||||
for ref frame in self
|
||||
.encode(ms, frame, STRIDE_ALIGN)
|
||||
@ -249,7 +249,7 @@ impl EncoderApi for AomEncoder {
|
||||
frames.push(Self::create_frame(frame));
|
||||
}
|
||||
if frames.len() > 0 {
|
||||
Ok(Self::create_msg(frames))
|
||||
Ok(Self::create_video_frame(frames))
|
||||
} else {
|
||||
Err(anyhow!("no valid frame"))
|
||||
}
|
||||
@ -311,16 +311,14 @@ impl AomEncoder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_msg(frames: Vec<EncodedVideoFrame>) -> Message {
|
||||
let mut msg_out = Message::new();
|
||||
pub fn create_video_frame(frames: Vec<EncodedVideoFrame>) -> VideoFrame {
|
||||
let mut vf = VideoFrame::new();
|
||||
let av1s = EncodedVideoFrames {
|
||||
frames: frames.into(),
|
||||
..Default::default()
|
||||
};
|
||||
vf.set_av1s(av1s);
|
||||
msg_out.set_video_frame(vf);
|
||||
msg_out
|
||||
vf
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -23,8 +23,8 @@ use hbb_common::{
|
||||
config::PeerConfig,
|
||||
log,
|
||||
message_proto::{
|
||||
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, Message,
|
||||
SupportedDecoding, SupportedEncoding,
|
||||
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames,
|
||||
SupportedDecoding, SupportedEncoding, VideoFrame,
|
||||
},
|
||||
sysinfo::{System, SystemExt},
|
||||
tokio::time::Instant,
|
||||
@ -60,7 +60,7 @@ pub trait EncoderApi {
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message>;
|
||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<VideoFrame>;
|
||||
|
||||
fn use_yuv(&self) -> bool;
|
||||
|
||||
|
@ -8,7 +8,7 @@ use hbb_common::{
|
||||
bytes::Bytes,
|
||||
config::HwCodecConfig,
|
||||
log,
|
||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
||||
ResultType,
|
||||
};
|
||||
use hwcodec::{
|
||||
@ -92,12 +92,7 @@ impl EncoderApi for HwEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_to_message(
|
||||
&mut self,
|
||||
frame: &[u8],
|
||||
_ms: i64,
|
||||
) -> ResultType<hbb_common::message_proto::Message> {
|
||||
let mut msg_out = Message::new();
|
||||
fn encode_to_message(&mut self, frame: &[u8], _ms: i64) -> ResultType<VideoFrame> {
|
||||
let mut vf = VideoFrame::new();
|
||||
let mut frames = Vec::new();
|
||||
for frame in self.encode(frame).with_context(|| "Failed to encode")? {
|
||||
@ -117,8 +112,7 @@ impl EncoderApi for HwEncoder {
|
||||
DataFormat::H264 => vf.set_h264s(frames),
|
||||
DataFormat::H265 => vf.set_h265s(frames),
|
||||
}
|
||||
msg_out.set_video_frame(vf);
|
||||
Ok(msg_out)
|
||||
Ok(vf)
|
||||
} else {
|
||||
Err(anyhow!("no valid frame"))
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
use hbb_common::anyhow::{anyhow, Context};
|
||||
use hbb_common::log;
|
||||
use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame};
|
||||
use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame};
|
||||
use hbb_common::ResultType;
|
||||
|
||||
use crate::codec::{base_bitrate, codec_thread_num, EncoderApi, Quality};
|
||||
@ -172,7 +172,7 @@ impl EncoderApi for VpxEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message> {
|
||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<VideoFrame> {
|
||||
let mut frames = Vec::new();
|
||||
for ref frame in self
|
||||
.encode(ms, frame, STRIDE_ALIGN)
|
||||
@ -186,7 +186,7 @@ impl EncoderApi for VpxEncoder {
|
||||
|
||||
// to-do: flush periodically, e.g. 1 second
|
||||
if frames.len() > 0 {
|
||||
Ok(VpxEncoder::create_msg(self.id, frames))
|
||||
Ok(VpxEncoder::create_video_frame(self.id, frames))
|
||||
} else {
|
||||
Err(anyhow!("no valid frame"))
|
||||
}
|
||||
@ -266,8 +266,10 @@ impl VpxEncoder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_msg(codec_id: VpxVideoCodecId, frames: Vec<EncodedVideoFrame>) -> Message {
|
||||
let mut msg_out = Message::new();
|
||||
pub fn create_video_frame(
|
||||
codec_id: VpxVideoCodecId,
|
||||
frames: Vec<EncodedVideoFrame>,
|
||||
) -> VideoFrame {
|
||||
let mut vf = VideoFrame::new();
|
||||
let vpxs = EncodedVideoFrames {
|
||||
frames: frames.into(),
|
||||
@ -277,8 +279,7 @@ impl VpxEncoder {
|
||||
VpxVideoCodecId::VP8 => vf.set_vp8s(vpxs),
|
||||
VpxVideoCodecId::VP9 => vf.set_vp9s(vpxs),
|
||||
}
|
||||
msg_out.set_video_frame(vf);
|
||||
msg_out
|
||||
vf
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -1,5 +1,5 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.2.3
|
||||
pkgver=1.2.4
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
|
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.3
|
||||
Version: 1.2.4
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.3
|
||||
Version: 1.2.4
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.3
|
||||
Version: 1.2.4
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
201
src/client.rs
201
src/client.rs
@ -3,10 +3,7 @@ use std::{
|
||||
net::SocketAddr,
|
||||
ops::Deref,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc, Arc, Mutex, RwLock,
|
||||
},
|
||||
sync::{mpsc, Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
pub use async_trait::async_trait;
|
||||
@ -60,6 +57,7 @@ use scrap::{
|
||||
use crate::{
|
||||
common::input::{MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP},
|
||||
is_keyboard_mode_supported,
|
||||
ui_session_interface::{InvokeUiSession, Session},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
@ -675,9 +673,12 @@ impl Client {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn try_stop_clipboard(_self_uuid: &uuid::Uuid) {
|
||||
fn try_stop_clipboard(_self_id: &str) {
|
||||
#[cfg(feature = "flutter")]
|
||||
if crate::flutter::sessions::other_sessions_running(_self_uuid) {
|
||||
if crate::flutter::sessions::other_sessions_running(
|
||||
_self_id.to_string(),
|
||||
ConnType::DEFAULT_CONN,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
|
||||
@ -1206,6 +1207,17 @@ impl LoginConfigHandler {
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Save reverse mouse wheel ("", "Y") to the current config.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The "displays_as_individual_windows" value ("", "Y").
|
||||
pub fn save_displays_as_individual_windows(&mut self, value: String) {
|
||||
let mut config = self.load_config();
|
||||
config.displays_as_individual_windows = value;
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Save scroll style to the current config.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -1523,6 +1535,15 @@ impl LoginConfigHandler {
|
||||
msg_out
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for refreshing video.
|
||||
pub fn refresh_display(display: usize) -> Message {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_refresh_video_display(display as _);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
msg_out
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for saving custom image quality.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -1789,89 +1810,158 @@ impl LoginConfigHandler {
|
||||
|
||||
/// Media data.
|
||||
pub enum MediaData {
|
||||
VideoQueue,
|
||||
VideoQueue(usize),
|
||||
VideoFrame(Box<VideoFrame>),
|
||||
AudioFrame(Box<AudioFrame>),
|
||||
AudioFormat(AudioFormat),
|
||||
Reset,
|
||||
RecordScreen(bool, i32, i32, String),
|
||||
Reset(usize),
|
||||
RecordScreen(bool, usize, i32, i32, String),
|
||||
}
|
||||
|
||||
pub type MediaSender = mpsc::Sender<MediaData>;
|
||||
|
||||
struct VideoHandlerController {
|
||||
handler: VideoHandler,
|
||||
count: u128,
|
||||
duration: std::time::Duration,
|
||||
skip_beginning: u32,
|
||||
}
|
||||
|
||||
/// Start video and audio thread.
|
||||
/// Return two [`MediaSender`], they should be given to the media producer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `video_callback` - The callback for video frame. Being called when a video frame is ready.
|
||||
pub fn start_video_audio_threads<F>(
|
||||
pub fn start_video_audio_threads<F, T>(
|
||||
session: Session<T>,
|
||||
video_callback: F,
|
||||
) -> (
|
||||
MediaSender,
|
||||
MediaSender,
|
||||
Arc<ArrayQueue<VideoFrame>>,
|
||||
Arc<AtomicUsize>,
|
||||
Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
|
||||
Arc<RwLock<HashMap<usize, usize>>>,
|
||||
)
|
||||
where
|
||||
F: 'static + FnMut(&mut scrap::ImageRgb) + Send,
|
||||
F: 'static + FnMut(usize, &mut scrap::ImageRgb) + Send,
|
||||
T: InvokeUiSession,
|
||||
{
|
||||
let (video_sender, video_receiver) = mpsc::channel::<MediaData>();
|
||||
let video_queue = Arc::new(ArrayQueue::<VideoFrame>::new(VIDEO_QUEUE_SIZE));
|
||||
let video_queue_cloned = video_queue.clone();
|
||||
let video_queue_map: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>> = Default::default();
|
||||
let video_queue_map_cloned = video_queue_map.clone();
|
||||
let mut video_callback = video_callback;
|
||||
let mut duration = std::time::Duration::ZERO;
|
||||
let mut count = 0;
|
||||
let fps = Arc::new(AtomicUsize::new(0));
|
||||
let decode_fps = fps.clone();
|
||||
let mut skip_beginning = 0;
|
||||
let fps_map = Arc::new(RwLock::new(HashMap::new()));
|
||||
let decode_fps_map = fps_map.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
#[cfg(windows)]
|
||||
sync_cpu_usage();
|
||||
let mut video_handler = VideoHandler::new();
|
||||
let mut handler_controller_map = Vec::new();
|
||||
// let mut count = Vec::new();
|
||||
// let mut duration = std::time::Duration::ZERO;
|
||||
// let mut skip_beginning = Vec::new();
|
||||
loop {
|
||||
if let Ok(data) = video_receiver.recv() {
|
||||
match data {
|
||||
MediaData::VideoFrame(_) | MediaData::VideoQueue => {
|
||||
let vf = if let MediaData::VideoFrame(vf) = data {
|
||||
*vf
|
||||
} else {
|
||||
if let Some(vf) = video_queue.pop() {
|
||||
vf
|
||||
} else {
|
||||
MediaData::VideoFrame(_) | MediaData::VideoQueue(_) => {
|
||||
let vf = match data {
|
||||
MediaData::VideoFrame(vf) => *vf,
|
||||
MediaData::VideoQueue(display) => {
|
||||
if let Some(video_queue) =
|
||||
video_queue_map.read().unwrap().get(&display)
|
||||
{
|
||||
if let Some(vf) = video_queue.pop() {
|
||||
vf
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// unreachable!();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let display = vf.display as usize;
|
||||
let start = std::time::Instant::now();
|
||||
if let Ok(true) = video_handler.handle_frame(vf) {
|
||||
video_callback(&mut video_handler.rgb);
|
||||
// fps calculation
|
||||
// The first frame will be very slow
|
||||
if skip_beginning < 5 {
|
||||
skip_beginning += 1;
|
||||
continue;
|
||||
if handler_controller_map.len() <= display {
|
||||
for _i in handler_controller_map.len()..=display {
|
||||
handler_controller_map.push(VideoHandlerController {
|
||||
handler: VideoHandler::new(),
|
||||
count: 0,
|
||||
duration: std::time::Duration::ZERO,
|
||||
skip_beginning: 0,
|
||||
});
|
||||
}
|
||||
duration += start.elapsed();
|
||||
count += 1;
|
||||
if count % 10 == 0 {
|
||||
fps.store(
|
||||
(count * 1000 / duration.as_millis()) as usize,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
// Clear to get real-time fps
|
||||
if count > 150 {
|
||||
count = 0;
|
||||
duration = Duration::ZERO;
|
||||
}
|
||||
if let Some(handler_controller) = handler_controller_map.get_mut(display) {
|
||||
match handler_controller.handler.handle_frame(vf) {
|
||||
Ok(true) => {
|
||||
video_callback(display, &mut handler_controller.handler.rgb);
|
||||
|
||||
// fps calculation
|
||||
// The first frame will be very slow
|
||||
if handler_controller.skip_beginning < 5 {
|
||||
handler_controller.skip_beginning += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
handler_controller.duration += start.elapsed();
|
||||
handler_controller.count += 1;
|
||||
if handler_controller.count % 10 == 0 {
|
||||
fps_map.write().unwrap().insert(
|
||||
display,
|
||||
(handler_controller.count * 1000
|
||||
/ handler_controller.duration.as_millis())
|
||||
as usize,
|
||||
);
|
||||
}
|
||||
// Clear to get real-time fps
|
||||
if handler_controller.count > 150 {
|
||||
handler_controller.count = 0;
|
||||
handler_controller.duration = Duration::ZERO;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// This is a simple workaround.
|
||||
//
|
||||
// I only see the following error:
|
||||
// FailedCall("errcode=1 scrap::common::vpxcodec:libs\\scrap\\src\\common\\vpxcodec.rs:433:9")
|
||||
// When switching from all displays to one display, the error occurs.
|
||||
// eg:
|
||||
// 1. Connect to a device with two displays (A and B).
|
||||
// 2. Switch to display A. The error occurs.
|
||||
// 3. If the error does not occur. Switch from A to display B. The error occurs.
|
||||
//
|
||||
// to-do: fix the error
|
||||
log::error!("handle video frame error, {}", e);
|
||||
session.refresh_video(display as _);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
MediaData::Reset => {
|
||||
video_handler.reset();
|
||||
MediaData::Reset(display) => {
|
||||
if let Some(handler_controler) = handler_controller_map.get_mut(display) {
|
||||
handler_controler.handler.reset();
|
||||
}
|
||||
}
|
||||
MediaData::RecordScreen(start, w, h, id) => {
|
||||
video_handler.record_screen(start, w, h, id)
|
||||
MediaData::RecordScreen(start, display, w, h, id) => {
|
||||
if handler_controller_map.len() == 1 {
|
||||
// Compatible with the sciter version(single ui session).
|
||||
// For the sciter version, there're no multi-ui-sessions for one connection.
|
||||
// The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler.
|
||||
handler_controller_map[0]
|
||||
.handler
|
||||
.record_screen(start, w, h, id);
|
||||
} else {
|
||||
if let Some(handler_controler) = handler_controller_map.get_mut(display)
|
||||
{
|
||||
handler_controler.handler.record_screen(start, w, h, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -1882,7 +1972,12 @@ where
|
||||
log::info!("Video decoder loop exits");
|
||||
});
|
||||
let audio_sender = start_audio_thread();
|
||||
return (video_sender, audio_sender, video_queue_cloned, decode_fps);
|
||||
return (
|
||||
video_sender,
|
||||
audio_sender,
|
||||
video_queue_map_cloned,
|
||||
decode_fps_map,
|
||||
);
|
||||
}
|
||||
|
||||
/// Start an audio thread
|
||||
@ -2500,7 +2595,7 @@ pub enum Data {
|
||||
SetConfirmOverrideFile((i32, i32, bool, bool, bool)),
|
||||
AddJob((i32, String, String, i32, bool, bool)),
|
||||
ResumeJob((i32, bool)),
|
||||
RecordScreen(bool, i32, i32, String),
|
||||
RecordScreen(bool, usize, i32, i32, String),
|
||||
ElevateDirect,
|
||||
ElevateWithLogon(String, String),
|
||||
NewVoiceCall,
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use hbb_common::{
|
||||
get_time,
|
||||
message_proto::{Message, VoiceCallRequest, VoiceCallResponse},
|
||||
@ -7,7 +8,7 @@ use scrap::CodecFormat;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct QualityStatus {
|
||||
pub speed: Option<String>,
|
||||
pub fps: Option<i32>,
|
||||
pub fps: HashMap<usize, i32>,
|
||||
pub delay: Option<i32>,
|
||||
pub target_bitrate: Option<i32>,
|
||||
pub codec_format: Option<CodecFormat>,
|
||||
|
@ -1,33 +1,41 @@
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroI64;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
num::NonZeroI64,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, RwLock,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
use clipboard::ContextSend;
|
||||
use crossbeam_queue::ArrayQueue;
|
||||
use hbb_common::config::{PeerConfig, TransferSerde};
|
||||
use hbb_common::fs::{
|
||||
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
|
||||
RemoveJobMeta,
|
||||
};
|
||||
use hbb_common::message_proto::permission_info::Permission;
|
||||
use hbb_common::protobuf::Message as _;
|
||||
use hbb_common::rendezvous_proto::ConnType;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::sleep;
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
use hbb_common::tokio::{
|
||||
self,
|
||||
sync::mpsc,
|
||||
time::{self, Duration, Instant, Interval},
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::{PeerConfig, TransferSerde},
|
||||
fs,
|
||||
fs::{
|
||||
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
|
||||
RemoveJobMeta,
|
||||
},
|
||||
get_time, log,
|
||||
message_proto::permission_info::Permission,
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::ConnType,
|
||||
tokio::{
|
||||
self,
|
||||
sync::mpsc,
|
||||
time::{self, Duration, Instant, Interval},
|
||||
},
|
||||
ResultType, Stream,
|
||||
};
|
||||
use hbb_common::{allow_err, fs, get_time, log, message_proto::*, ResultType, Stream};
|
||||
use scrap::CodecFormat;
|
||||
|
||||
use crate::client::{
|
||||
@ -43,7 +51,7 @@ use crate::{client::Data, client::Interface};
|
||||
|
||||
pub struct Remote<T: InvokeUiSession> {
|
||||
handler: Session<T>,
|
||||
video_queue: Arc<ArrayQueue<VideoFrame>>,
|
||||
video_queue_map: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
|
||||
video_sender: MediaSender,
|
||||
audio_sender: MediaSender,
|
||||
receiver: mpsc::UnboundedReceiver<Data>,
|
||||
@ -61,27 +69,27 @@ pub struct Remote<T: InvokeUiSession> {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
client_conn_id: i32, // used for file clipboard
|
||||
data_count: Arc<AtomicUsize>,
|
||||
frame_count: Arc<AtomicUsize>,
|
||||
frame_count_map: Arc<RwLock<HashMap<usize, usize>>>,
|
||||
video_format: CodecFormat,
|
||||
elevation_requested: bool,
|
||||
fps_control: FpsControl,
|
||||
decode_fps: Arc<AtomicUsize>,
|
||||
fps_control_map: HashMap<usize, FpsControl>,
|
||||
decode_fps_map: Arc<RwLock<HashMap<usize, usize>>>,
|
||||
}
|
||||
|
||||
impl<T: InvokeUiSession> Remote<T> {
|
||||
pub fn new(
|
||||
handler: Session<T>,
|
||||
video_queue: Arc<ArrayQueue<VideoFrame>>,
|
||||
video_queue: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
|
||||
video_sender: MediaSender,
|
||||
audio_sender: MediaSender,
|
||||
receiver: mpsc::UnboundedReceiver<Data>,
|
||||
sender: mpsc::UnboundedSender<Data>,
|
||||
frame_count: Arc<AtomicUsize>,
|
||||
decode_fps: Arc<AtomicUsize>,
|
||||
frame_count_map: Arc<RwLock<HashMap<usize, usize>>>,
|
||||
decode_fps: Arc<RwLock<HashMap<usize, usize>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
handler,
|
||||
video_queue,
|
||||
video_queue_map: video_queue,
|
||||
video_sender,
|
||||
audio_sender,
|
||||
receiver,
|
||||
@ -96,13 +104,13 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
client_conn_id: 0,
|
||||
data_count: Arc::new(AtomicUsize::new(0)),
|
||||
frame_count,
|
||||
frame_count_map,
|
||||
video_format: CodecFormat::Unknown,
|
||||
stop_voice_call_sender: None,
|
||||
voice_call_request_timestamp: None,
|
||||
elevation_requested: false,
|
||||
fps_control: Default::default(),
|
||||
decode_fps,
|
||||
fps_control_map: Default::default(),
|
||||
decode_fps_map: decode_fps,
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +161,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if !is_conn_not_default {
|
||||
log::debug!("get cliprdr client for conn_id {}", self.client_conn_id);
|
||||
(self.client_conn_id, rx_clip_client_lock) =
|
||||
clipboard::get_rx_cliprdr_client(&self.handler.session_id);
|
||||
clipboard::get_rx_cliprdr_client(&self.handler.id);
|
||||
};
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
@ -230,12 +238,18 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
let mut speed = self.data_count.swap(0, Ordering::Relaxed);
|
||||
speed = speed * 1000 / elapsed as usize;
|
||||
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
|
||||
let mut fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
|
||||
// Correcting the inaccuracy of status_timer
|
||||
fps = fps * 1000 / elapsed as i32;
|
||||
|
||||
let mut frame_count_map_write = self.frame_count_map.write().unwrap();
|
||||
let frame_count_map = frame_count_map_write.clone();
|
||||
frame_count_map_write.values_mut().for_each(|v| *v = 0);
|
||||
drop(frame_count_map_write);
|
||||
let fps = frame_count_map.iter().map(|(k, v)| {
|
||||
// Correcting the inaccuracy of status_timer
|
||||
(k.clone(), (*v as i32) * 1000 / elapsed as i32)
|
||||
}).collect::<HashMap<usize, i32>>();
|
||||
self.handler.update_quality_status(QualityStatus {
|
||||
speed:Some(speed),
|
||||
fps:Some(fps),
|
||||
speed: Some(speed),
|
||||
fps,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
@ -261,7 +275,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if _set_disconnected_ok {
|
||||
Client::try_stop_clipboard(&self.handler.session_id);
|
||||
Client::try_stop_clipboard(&self.handler.id);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
@ -763,10 +777,10 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Data::RecordScreen(start, w, h, id) => {
|
||||
Data::RecordScreen(start, display, w, h, id) => {
|
||||
let _ = self
|
||||
.video_sender
|
||||
.send(MediaData::RecordScreen(start, w, h, id));
|
||||
.send(MediaData::RecordScreen(start, display, w, h, id));
|
||||
}
|
||||
Data::ElevateDirect => {
|
||||
let mut request = ElevationRequest::new();
|
||||
@ -907,89 +921,100 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fps_control(&mut self, direct: bool) {
|
||||
let len = self.video_queue.len();
|
||||
let ctl = &mut self.fps_control;
|
||||
// Current full speed decoding fps
|
||||
let decode_fps = self.decode_fps.load(std::sync::atomic::Ordering::Relaxed);
|
||||
if decode_fps == 0 {
|
||||
return;
|
||||
}
|
||||
let limited_fps = if direct {
|
||||
decode_fps * 9 / 10 // 30 got 27
|
||||
} else {
|
||||
decode_fps * 4 / 5 // 30 got 24
|
||||
};
|
||||
// send full speed fps
|
||||
let version = self.handler.lc.read().unwrap().version;
|
||||
let max_encode_speed = 144 * 10 / 9;
|
||||
if version >= hbb_common::get_version_number("1.2.1")
|
||||
&& (ctl.last_full_speed_fps.is_none() // First time
|
||||
|| ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5
|
||||
&& !(decode_fps > max_encode_speed // already exceed max encoding speed
|
||||
&& ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32)))
|
||||
{
|
||||
let mut misc = Misc::new();
|
||||
misc.set_full_speed_fps(decode_fps as _);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.sender.send(Data::Message(msg)).ok();
|
||||
ctl.last_full_speed_fps = Some(decode_fps as _);
|
||||
}
|
||||
// decrease judgement
|
||||
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms
|
||||
let should_decrease = len >= debounce // exceed debounce
|
||||
&& len > ctl.last_queue_size + 5 // still caching
|
||||
&& !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one
|
||||
let decode_fps_read = self.decode_fps_map.read().unwrap();
|
||||
for (display, decode_fps) in decode_fps_read.iter() {
|
||||
let video_queue_map_read = self.video_queue_map.read().unwrap();
|
||||
let Some(video_queue) = video_queue_map_read.get(display) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// increase judgement
|
||||
if len <= 1 {
|
||||
ctl.idle_counter += 1;
|
||||
} else {
|
||||
ctl.idle_counter = 0;
|
||||
}
|
||||
let mut should_increase = false;
|
||||
if let Some(last_custom_fps) = ctl.last_custom_fps {
|
||||
// ever set
|
||||
if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 {
|
||||
// limited_fps is 5 larger than last set, and idle time is more than 3 seconds
|
||||
should_increase = true;
|
||||
if !self.fps_control_map.contains_key(display) {
|
||||
self.fps_control_map.insert(*display, FpsControl::default());
|
||||
}
|
||||
}
|
||||
if should_decrease || should_increase {
|
||||
// limited_fps to ensure decoding is faster than encoding
|
||||
let mut custom_fps = limited_fps as i32;
|
||||
if custom_fps < 1 {
|
||||
custom_fps = 1;
|
||||
}
|
||||
// send custom fps
|
||||
let mut misc = Misc::new();
|
||||
if version > hbb_common::get_version_number("1.2.1") {
|
||||
// avoid confusion with custom image quality fps
|
||||
misc.set_auto_adjust_fps(custom_fps as _);
|
||||
let Some(ctl) = self.fps_control_map.get_mut(display) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let len = video_queue.len();
|
||||
let decode_fps = *decode_fps;
|
||||
let limited_fps = if direct {
|
||||
decode_fps * 9 / 10 // 30 got 27
|
||||
} else {
|
||||
misc.set_option(OptionMessage {
|
||||
custom_fps,
|
||||
..Default::default()
|
||||
});
|
||||
decode_fps * 4 / 5 // 30 got 24
|
||||
};
|
||||
// send full speed fps
|
||||
let version = self.handler.lc.read().unwrap().version;
|
||||
let max_encode_speed = 144 * 10 / 9;
|
||||
if version >= hbb_common::get_version_number("1.2.1")
|
||||
&& (ctl.last_full_speed_fps.is_none() // First time
|
||||
|| ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5
|
||||
&& !(decode_fps > max_encode_speed // already exceed max encoding speed
|
||||
&& ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32)))
|
||||
{
|
||||
let mut misc = Misc::new();
|
||||
misc.set_full_speed_fps(decode_fps as _);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.sender.send(Data::Message(msg)).ok();
|
||||
ctl.last_full_speed_fps = Some(decode_fps as _);
|
||||
}
|
||||
// decrease judgement
|
||||
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms
|
||||
let should_decrease = len >= debounce // exceed debounce
|
||||
&& len > ctl.last_queue_size + 5 // still caching
|
||||
&& !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one
|
||||
|
||||
// increase judgement
|
||||
if len <= 1 {
|
||||
ctl.idle_counter += 1;
|
||||
} else {
|
||||
ctl.idle_counter = 0;
|
||||
}
|
||||
let mut should_increase = false;
|
||||
if let Some(last_custom_fps) = ctl.last_custom_fps {
|
||||
// ever set
|
||||
if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 {
|
||||
// limited_fps is 5 larger than last set, and idle time is more than 3 seconds
|
||||
should_increase = true;
|
||||
}
|
||||
}
|
||||
if should_decrease || should_increase {
|
||||
// limited_fps to ensure decoding is faster than encoding
|
||||
let mut custom_fps = limited_fps as i32;
|
||||
if custom_fps < 1 {
|
||||
custom_fps = 1;
|
||||
}
|
||||
// send custom fps
|
||||
let mut misc = Misc::new();
|
||||
if version > hbb_common::get_version_number("1.2.1") {
|
||||
// avoid confusion with custom image quality fps
|
||||
misc.set_auto_adjust_fps(custom_fps as _);
|
||||
} else {
|
||||
misc.set_option(OptionMessage {
|
||||
custom_fps,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.sender.send(Data::Message(msg)).ok();
|
||||
ctl.last_queue_size = len;
|
||||
ctl.last_custom_fps = Some(custom_fps);
|
||||
}
|
||||
// send refresh
|
||||
if ctl.refresh_times < 10 // enough
|
||||
&& (len > video_queue.capacity() / 2
|
||||
&& (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30))
|
||||
{
|
||||
// Refresh causes client set_display, left frames cause flickering.
|
||||
while let Some(_) = video_queue.pop() {}
|
||||
self.handler.refresh_video(*display as _);
|
||||
ctl.refresh_times += 1;
|
||||
ctl.last_refresh_instant = Instant::now();
|
||||
}
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.sender.send(Data::Message(msg)).ok();
|
||||
ctl.last_queue_size = len;
|
||||
ctl.last_custom_fps = Some(custom_fps);
|
||||
}
|
||||
// send refresh
|
||||
if ctl.refresh_times < 10 // enough
|
||||
&& (len > self.video_queue.capacity() / 2
|
||||
&& (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30))
|
||||
{
|
||||
// Refresh causes client set_display, left frames cause flickering.
|
||||
while let Some(_) = self.video_queue.pop() {}
|
||||
self.handler.refresh_video();
|
||||
ctl.refresh_times += 1;
|
||||
ctl.last_refresh_instant = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1011,14 +1036,27 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
|
||||
let display = vf.display as usize;
|
||||
let mut video_queue_write = self.video_queue_map.write().unwrap();
|
||||
if !video_queue_write.contains_key(&display) {
|
||||
video_queue_write.insert(
|
||||
display,
|
||||
ArrayQueue::<VideoFrame>::new(crate::client::VIDEO_QUEUE_SIZE),
|
||||
);
|
||||
}
|
||||
if Self::contains_key_frame(&vf) {
|
||||
while let Some(_) = self.video_queue.pop() {}
|
||||
if let Some(video_queue) = video_queue_write.get_mut(&display) {
|
||||
while let Some(_) = video_queue.pop() {}
|
||||
}
|
||||
self.video_sender
|
||||
.send(MediaData::VideoFrame(Box::new(vf)))
|
||||
.ok();
|
||||
} else {
|
||||
self.video_queue.force_push(vf);
|
||||
self.video_sender.send(MediaData::VideoQueue).ok();
|
||||
if let Some(video_queue) = video_queue_write.get_mut(&display) {
|
||||
video_queue.force_push(vf);
|
||||
}
|
||||
self.video_sender.send(MediaData::VideoQueue(display)).ok();
|
||||
}
|
||||
}
|
||||
Some(message::Union::Hash(hash)) => {
|
||||
@ -1300,7 +1338,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
Some(misc::Union::SwitchDisplay(s)) => {
|
||||
self.handler.handle_peer_switch_display(&s);
|
||||
self.video_sender.send(MediaData::Reset).ok();
|
||||
self.video_sender
|
||||
.send(MediaData::Reset(s.display as _))
|
||||
.ok();
|
||||
if s.width > 0 && s.height > 0 {
|
||||
self.handler.set_display(
|
||||
s.x,
|
||||
@ -1677,7 +1717,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
#[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()).unwrap_or(0)
|
||||
!= clipboard::get_client_conn_id(&crate::flutter::get_cur_peer_id()).unwrap_or(0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -54,6 +54,8 @@ pub const PLATFORM_LINUX: &str = "Linux";
|
||||
pub const PLATFORM_MACOS: &str = "Mac OS";
|
||||
pub const PLATFORM_ANDROID: &str = "Android";
|
||||
|
||||
const MIN_VER_MULTI_UI_SESSION: &str = "1.2.4";
|
||||
|
||||
pub mod input {
|
||||
pub const MOUSE_TYPE_MOVE: i32 = 0;
|
||||
pub const MOUSE_TYPE_DOWN: i32 = 1;
|
||||
@ -120,6 +122,16 @@ pub fn set_server_running(b: bool) {
|
||||
*SERVER_RUNNING.write().unwrap() = b;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_support_multi_ui_session(ver: &str) -> bool {
|
||||
is_support_multi_ui_session_num(hbb_common::get_version_number(ver))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_support_multi_ui_session_num(ver: i64) -> bool {
|
||||
ver >= hbb_common::get_version_number(MIN_VER_MULTI_UI_SESSION)
|
||||
}
|
||||
|
||||
// is server process, with "--server" args
|
||||
#[inline]
|
||||
pub fn is_server() -> bool {
|
||||
@ -780,13 +792,17 @@ pub fn get_sysinfo() -> serde_json::Value {
|
||||
}
|
||||
let hostname = hostname(); // sys.hostname() return localhost on android in my test
|
||||
use serde_json::json;
|
||||
let mut out = json!({
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let out;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let mut out;
|
||||
out = json!({
|
||||
"cpu": format!("{cpu}{num_cpus}/{num_pcpus} cores"),
|
||||
"memory": format!("{memory}GB"),
|
||||
"os": os,
|
||||
"hostname": hostname,
|
||||
});
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
out["username"] = json!(crate::platform::get_active_username());
|
||||
}
|
||||
|
579
src/flutter.rs
579
src/flutter.rs
@ -18,8 +18,6 @@ use hbb_common::{
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::CString,
|
||||
@ -150,25 +148,40 @@ pub unsafe extern "C" fn free_c_args(ptr: *mut *mut c_char, len: c_int) {
|
||||
// Afterwards the vector will be dropped and thus freed.
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SessionHandler {
|
||||
event_stream: Option<StreamSink<EventToUI>>,
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
notify_rendered: bool,
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
renderer: VideoRenderer,
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct FlutterHandler {
|
||||
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
|
||||
notify_rendered: Arc<RwLock<bool>>,
|
||||
renderer: Arc<RwLock<VideoRenderer>>,
|
||||
// ui session id -> display handler data
|
||||
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
|
||||
peer_info: Arc<RwLock<PeerInfo>>,
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct FlutterHandler {
|
||||
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
|
||||
struct RgbaData {
|
||||
// SAFETY: [rgba] is guarded by [rgba_valid], and it's safe to reach [rgba] with `rgba_valid == true`.
|
||||
// We must check the `rgba_valid` before reading [rgba].
|
||||
pub rgba: Arc<RwLock<Vec<u8>>>,
|
||||
pub rgba_valid: Arc<AtomicBool>,
|
||||
data: Vec<u8>,
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct FlutterHandler {
|
||||
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
|
||||
display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>,
|
||||
peer_info: Arc<RwLock<PeerInfo>>,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
|
||||
@ -184,14 +197,22 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
|
||||
dst_rgba_stride: c_int,
|
||||
);
|
||||
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
pub(super) type TextureRgbaPtr = usize;
|
||||
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
struct DisplaySessionInfo {
|
||||
// TextureRgba pointer in flutter native.
|
||||
texture_rgba_ptr: TextureRgbaPtr,
|
||||
size: (usize, usize),
|
||||
}
|
||||
|
||||
// Video Texture Renderer in Flutter
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
#[derive(Clone)]
|
||||
struct VideoRenderer {
|
||||
// TextureRgba pointer in flutter native.
|
||||
ptr: Arc<RwLock<usize>>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
is_support_multi_ui_session: bool,
|
||||
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
|
||||
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
||||
}
|
||||
|
||||
@ -217,9 +238,8 @@ impl Default for VideoRenderer {
|
||||
}
|
||||
};
|
||||
Self {
|
||||
ptr: Default::default(),
|
||||
width: 0,
|
||||
height: 0,
|
||||
map_display_sessions: Default::default(),
|
||||
is_support_multi_ui_session: false,
|
||||
on_rgba_func,
|
||||
}
|
||||
}
|
||||
@ -228,33 +248,74 @@ impl Default for VideoRenderer {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
impl VideoRenderer {
|
||||
#[inline]
|
||||
pub fn set_size(&mut self, width: usize, height: usize) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
fn set_size(&mut self, display: usize, width: usize, height: usize) {
|
||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||
info.size = (width, height);
|
||||
} else {
|
||||
sessions_lock.insert(
|
||||
display,
|
||||
DisplaySessionInfo {
|
||||
texture_rgba_ptr: usize::default(),
|
||||
size: (width, height),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
||||
let ptr = self.ptr.read().unwrap();
|
||||
if *ptr == usize::default() {
|
||||
fn register_texture(&self, display: usize, ptr: usize) {
|
||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||
if ptr == 0 {
|
||||
sessions_lock.remove(&display);
|
||||
} else {
|
||||
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||
if info.texture_rgba_ptr != 0 && info.texture_rgba_ptr != ptr as TextureRgbaPtr {
|
||||
log::error!("unreachable, texture_rgba_ptr is not null and not equal to ptr");
|
||||
}
|
||||
info.texture_rgba_ptr = ptr as _;
|
||||
} else {
|
||||
if ptr != 0 {
|
||||
sessions_lock.insert(
|
||||
display,
|
||||
DisplaySessionInfo {
|
||||
texture_rgba_ptr: ptr as _,
|
||||
size: (0, 0),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_rgba(&self, display: usize, rgba: &scrap::ImageRgb) {
|
||||
let read_lock = self.map_display_sessions.read().unwrap();
|
||||
let opt_info = if !self.is_support_multi_ui_session {
|
||||
read_lock.values().next()
|
||||
} else {
|
||||
read_lock.get(&display)
|
||||
};
|
||||
let Some(info) = opt_info else {
|
||||
return;
|
||||
};
|
||||
if info.texture_rgba_ptr == usize::default() {
|
||||
return;
|
||||
}
|
||||
|
||||
// It is also Ok to skip this check.
|
||||
if self.width != rgba.w || self.height != rgba.h {
|
||||
if info.size.0 != rgba.w || info.size.1 != rgba.h {
|
||||
log::error!(
|
||||
"width/height mismatch: ({},{}) != ({},{})",
|
||||
self.width,
|
||||
self.height,
|
||||
info.size.0,
|
||||
info.size.1,
|
||||
rgba.w,
|
||||
rgba.h
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(func) = &self.on_rgba_func {
|
||||
unsafe {
|
||||
func(
|
||||
*ptr as _,
|
||||
info.texture_rgba_ptr as _,
|
||||
rgba.raw.as_ptr() as _,
|
||||
rgba.raw.len() as _,
|
||||
rgba.w as _,
|
||||
@ -266,34 +327,42 @@ impl VideoRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionHandler {
|
||||
pub fn on_waiting_for_image_dialog_show(&mut self) {
|
||||
#[cfg(any(feature = "flutter_texture_render"))]
|
||||
{
|
||||
self.notify_rendered = false;
|
||||
}
|
||||
// rgba array render will notify every frame
|
||||
}
|
||||
}
|
||||
|
||||
impl FlutterHandler {
|
||||
/// Push an event to the event queue.
|
||||
/// An event is stored as json in the event queue.
|
||||
/// Push an event to all the event queues.
|
||||
/// An event is stored as json in the event queues.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the event.
|
||||
/// * `event` - Fields of the event content.
|
||||
pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) -> Option<bool> {
|
||||
pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) {
|
||||
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
||||
debug_assert!(h.get("name").is_none());
|
||||
h.insert("name", name);
|
||||
let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned());
|
||||
Some(
|
||||
self.event_stream
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()?
|
||||
.add(EventToUI::Event(out)),
|
||||
)
|
||||
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
||||
if let Some(stream) = &session.event_stream {
|
||||
stream.add(EventToUI::Event(out.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn close_event_stream(&self) {
|
||||
let mut stream_lock = self.event_stream.write().unwrap();
|
||||
if let Some(stream) = &*stream_lock {
|
||||
stream.add(EventToUI::Event("close".to_owned()));
|
||||
pub(crate) fn close_event_stream(&self, session_id: SessionID) {
|
||||
// to-do: Make sure the following logic is correct.
|
||||
// No need to remove the display handler, because it will be removed when the connection is closed.
|
||||
if let Some(session) = self.session_handlers.write().unwrap().get_mut(&session_id) {
|
||||
try_send_close_event(&session.event_stream);
|
||||
}
|
||||
*stream_lock = None;
|
||||
}
|
||||
|
||||
fn make_displays_msg(displays: &Vec<DisplayInfo>) -> String {
|
||||
@ -314,6 +383,7 @@ impl FlutterHandler {
|
||||
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool {
|
||||
let mut hooks = self.hooks.write().unwrap();
|
||||
@ -325,6 +395,7 @@ impl FlutterHandler {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(crate) fn remove_session_hook(&self, key: &String) -> bool {
|
||||
let mut hooks = self.hooks.write().unwrap();
|
||||
@ -335,27 +406,6 @@ impl FlutterHandler {
|
||||
let _ = hooks.remove(key);
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
pub fn register_texture(&self, ptr: usize) {
|
||||
*self.renderer.read().unwrap().ptr.write().unwrap() = ptr;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
pub fn set_size(&self, width: usize, height: usize) {
|
||||
*self.notify_rendered.write().unwrap() = false;
|
||||
self.renderer.write().unwrap().set_size(width, height);
|
||||
}
|
||||
|
||||
pub fn on_waiting_for_image_dialog_show(&self) {
|
||||
#[cfg(any(feature = "flutter_texture_render"))]
|
||||
{
|
||||
*self.notify_rendered.write().unwrap() = false;
|
||||
}
|
||||
// rgba array render will notify every frame
|
||||
}
|
||||
}
|
||||
|
||||
impl InvokeUiSession for FlutterHandler {
|
||||
@ -408,7 +458,10 @@ impl InvokeUiSession for FlutterHandler {
|
||||
"update_quality_status",
|
||||
vec![
|
||||
("speed", &status.speed.map_or(NULL, |it| it)),
|
||||
("fps", &status.fps.map_or(NULL, |it| it.to_string())),
|
||||
(
|
||||
"fps",
|
||||
&serde_json::ser::to_string(&status.fps).unwrap_or(NULL.to_owned()),
|
||||
),
|
||||
("delay", &status.delay.map_or(NULL, |it| it.to_string())),
|
||||
(
|
||||
"target_bitrate",
|
||||
@ -529,7 +582,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
||||
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
|
||||
// Give a chance for plugins or etc to hook a rgba data.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
for (key, hook) in self.hooks.read().unwrap().iter() {
|
||||
@ -541,27 +594,51 @@ impl InvokeUiSession for FlutterHandler {
|
||||
}
|
||||
// If the current rgba is not fetched by flutter, i.e., is valid.
|
||||
// We give up sending a new event to flutter.
|
||||
if self.rgba_valid.load(Ordering::Relaxed) {
|
||||
return;
|
||||
let mut rgba_write_lock = self.display_rgbas.write().unwrap();
|
||||
if let Some(rgba_data) = rgba_write_lock.get_mut(&display) {
|
||||
if rgba_data.valid {
|
||||
return;
|
||||
} else {
|
||||
rgba_data.valid = true;
|
||||
}
|
||||
// Return the rgba buffer to the video handler for reusing allocated rgba buffer.
|
||||
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut rgba_data.data);
|
||||
} else {
|
||||
let mut rgba_data = RgbaData::default();
|
||||
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut rgba_data.data);
|
||||
rgba_write_lock.insert(display, rgba_data);
|
||||
}
|
||||
self.rgba_valid.store(true, Ordering::Relaxed);
|
||||
// Return the rgba buffer to the video handler for reusing allocated rgba buffer.
|
||||
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut *self.rgba.write().unwrap());
|
||||
if let Some(stream) = &*self.event_stream.read().unwrap() {
|
||||
stream.add(EventToUI::Rgba);
|
||||
drop(rgba_write_lock);
|
||||
|
||||
// Non-texture-render UI does not support multiple displays in the one UI session.
|
||||
// It's Ok to notify each session for now.
|
||||
for h in self.session_handlers.read().unwrap().values() {
|
||||
if let Some(stream) = &h.event_stream {
|
||||
stream.add(EventToUI::Rgba(display));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
||||
self.renderer.read().unwrap().on_rgba(rgba);
|
||||
if *self.notify_rendered.read().unwrap() {
|
||||
return;
|
||||
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
|
||||
let mut try_notify_sessions = Vec::new();
|
||||
for (id, session) in self.session_handlers.read().unwrap().iter() {
|
||||
session.renderer.on_rgba(display, rgba);
|
||||
if !session.notify_rendered {
|
||||
try_notify_sessions.push(id.clone());
|
||||
}
|
||||
}
|
||||
if let Some(stream) = &*self.event_stream.read().unwrap() {
|
||||
stream.add(EventToUI::Rgba);
|
||||
*self.notify_rendered.write().unwrap() = true;
|
||||
if try_notify_sessions.len() > 0 {
|
||||
let mut write_lock = self.session_handlers.write().unwrap();
|
||||
for id in try_notify_sessions.iter() {
|
||||
if let Some(session) = write_lock.get_mut(id) {
|
||||
if let Some(stream) = &session.event_stream {
|
||||
stream.add(EventToUI::Rgba(display));
|
||||
session.notify_rendered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,6 +655,17 @@ impl InvokeUiSession for FlutterHandler {
|
||||
let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned());
|
||||
let resolutions = serialize_resolutions(&pi.resolutions.resolutions);
|
||||
*self.peer_info.write().unwrap() = pi.clone();
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
{
|
||||
self.session_handlers
|
||||
.write()
|
||||
.unwrap()
|
||||
.values_mut()
|
||||
.for_each(|h| {
|
||||
h.renderer.is_support_multi_ui_session =
|
||||
crate::common::is_support_multi_ui_session(&pi.version);
|
||||
});
|
||||
}
|
||||
self.push_event(
|
||||
"peer_info",
|
||||
vec![
|
||||
@ -701,21 +789,31 @@ impl InvokeUiSession for FlutterHandler {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_rgba(&self) -> *const u8 {
|
||||
fn get_rgba(&self, _display: usize) -> *const u8 {
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
if self.rgba_valid.load(Ordering::Relaxed) {
|
||||
return self.rgba.read().unwrap().as_ptr();
|
||||
if let Some(rgba_data) = self.display_rgbas.read().unwrap().get(&_display) {
|
||||
if rgba_data.valid {
|
||||
return rgba_data.data.as_ptr();
|
||||
}
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn next_rgba(&self) {
|
||||
fn next_rgba(&self, _display: usize) {
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
self.rgba_valid.store(false, Ordering::Relaxed);
|
||||
if let Some(rgba_data) = self.display_rgbas.write().unwrap().get_mut(&_display) {
|
||||
rgba_data.valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is only used for the default connection session.
|
||||
pub fn session_add_existed(peer_id: String, session_id: SessionID) -> ResultType<()> {
|
||||
sessions::insert_peer_session_id(peer_id, ConnType::DEFAULT_CONN, session_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new remote session with the given id.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -733,18 +831,6 @@ pub fn session_add(
|
||||
force_relay: bool,
|
||||
password: String,
|
||||
) -> ResultType<FlutterSession> {
|
||||
LocalConfig::set_remote_id(&id);
|
||||
|
||||
let session: Session<FlutterHandler> = Session {
|
||||
session_id: session_id.clone(),
|
||||
id: id.to_owned(),
|
||||
password,
|
||||
server_keyboard_enabled: Arc::new(RwLock::new(true)),
|
||||
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
|
||||
server_clipboard_enabled: Arc::new(RwLock::new(true)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let conn_type = if is_file_transfer {
|
||||
ConnType::FILE_TRANSFER
|
||||
} else if is_port_forward {
|
||||
@ -757,6 +843,26 @@ pub fn session_add(
|
||||
ConnType::DEFAULT_CONN
|
||||
};
|
||||
|
||||
// to-do: check the same id session.
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
if session.lc.read().unwrap().conn_type != conn_type {
|
||||
bail!("same session id is found with different conn type?");
|
||||
}
|
||||
// The same session is added before?
|
||||
bail!("same session id is found");
|
||||
}
|
||||
|
||||
LocalConfig::set_remote_id(&id);
|
||||
|
||||
let session: Session<FlutterHandler> = Session {
|
||||
id: id.to_owned(),
|
||||
password,
|
||||
server_keyboard_enabled: Arc::new(RwLock::new(true)),
|
||||
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
|
||||
server_clipboard_enabled: Arc::new(RwLock::new(true)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let switch_uuid = if switch_uuid.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@ -768,12 +874,8 @@ pub fn session_add(
|
||||
.write()
|
||||
.unwrap()
|
||||
.initialize(id.to_owned(), conn_type, switch_uuid, force_relay);
|
||||
|
||||
let session = Arc::new(session.clone());
|
||||
if let Some(same_id_session) = sessions::add_session(session_id.to_owned(), session.clone()) {
|
||||
log::error!("Should not happen");
|
||||
same_id_session.close();
|
||||
}
|
||||
sessions::insert_session(session_id.to_owned(), conn_type, session.clone());
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
@ -789,18 +891,39 @@ pub fn session_start_(
|
||||
id: &str,
|
||||
event_stream: StreamSink<EventToUI>,
|
||||
) -> ResultType<()> {
|
||||
if let Some(session) = sessions::get_session(session_id) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
log::info!(
|
||||
"Session {} start, render by flutter texture rgba plugin",
|
||||
id
|
||||
// is_connected is used to indicate whether to start a peer connection. For two cases:
|
||||
// 1. "Move tab to new window"
|
||||
// 2. multi ui session within the same peer connnection.
|
||||
let mut is_connected = false;
|
||||
let mut is_found = false;
|
||||
for s in sessions::get_sessions() {
|
||||
if let Some(h) = s.session_handlers.write().unwrap().get_mut(session_id) {
|
||||
is_connected = h.event_stream.is_some();
|
||||
try_send_close_event(&h.event_stream);
|
||||
h.event_stream = Some(event_stream);
|
||||
is_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !is_found {
|
||||
bail!(
|
||||
"No session with peer id {}, session id: {}",
|
||||
id,
|
||||
session_id.to_string()
|
||||
);
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
log::info!("Session {} start, render by flutter paint widget", id);
|
||||
let is_pre_added = session.event_stream.read().unwrap().is_some();
|
||||
session.close_event_stream();
|
||||
*session.event_stream.write().unwrap() = Some(event_stream);
|
||||
if !is_pre_added {
|
||||
}
|
||||
|
||||
if let Some(session) = sessions::get_session_by_session_id(session_id) {
|
||||
let is_first_ui_session = session.session_handlers.read().unwrap().len() == 1;
|
||||
if !is_connected && is_first_ui_session {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
log::info!(
|
||||
"Session {} start, render by flutter texture rgba plugin",
|
||||
id
|
||||
);
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
log::info!("Session {} start, render by flutter paint widget", id);
|
||||
|
||||
let session = (*session).clone();
|
||||
std::thread::spawn(move || {
|
||||
let round = session.connection_round_state.lock().unwrap().new_round();
|
||||
@ -813,19 +936,26 @@ pub fn session_start_(
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_send_close_event(event_stream: &Option<StreamSink<EventToUI>>) {
|
||||
if let Some(stream) = &event_stream {
|
||||
stream.add(EventToUI::Event("close".to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn update_text_clipboard_required() {
|
||||
let is_required = sessions::get_sessions()
|
||||
.iter()
|
||||
.any(|session| session.is_text_clipboard_required());
|
||||
.any(|s| s.is_text_clipboard_required());
|
||||
Client::set_is_text_clipboard_required(is_required);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn send_text_clipboard_msg(msg: Message) {
|
||||
for session in sessions::get_sessions() {
|
||||
if session.is_text_clipboard_required() {
|
||||
session.send(Data::Message(msg.clone()));
|
||||
for s in sessions::get_sessions() {
|
||||
if s.is_text_clipboard_required() {
|
||||
s.send(Data::Message(msg.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -998,6 +1128,11 @@ pub fn get_cur_session_id() -> SessionID {
|
||||
CUR_SESSION_ID.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn get_cur_peer_id() -> String {
|
||||
sessions::get_peer_id_by_session_id(&get_cur_session_id(), ConnType::DEFAULT_CONN)
|
||||
.unwrap_or("".to_string())
|
||||
}
|
||||
|
||||
pub fn set_cur_session_id(session_id: SessionID) {
|
||||
if get_cur_session_id() != session_id {
|
||||
*CUR_SESSION_ID.write().unwrap() = session_id;
|
||||
@ -1034,47 +1169,76 @@ fn char_to_session_id(c: *const char) -> ResultType<SessionID> {
|
||||
SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e))
|
||||
}
|
||||
|
||||
pub fn session_get_rgba_size(_session_id: SessionID) -> usize {
|
||||
pub fn session_get_rgba_size(_session_id: SessionID, _display: usize) -> usize {
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
if let Some(session) = sessions::get_session(&_session_id) {
|
||||
return session.rgba.read().unwrap().len();
|
||||
if let Some(session) = sessions::get_session_by_session_id(&_session_id) {
|
||||
return session
|
||||
.display_rgbas
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&_display)
|
||||
.map_or(0, |rgba| rgba.data.len());
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
|
||||
pub extern "C" fn session_get_rgba(session_uuid_str: *const char, display: usize) -> *const u8 {
|
||||
if let Ok(session_id) = char_to_session_id(session_uuid_str) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
return session.get_rgba();
|
||||
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
|
||||
return s.ui_handler.get_rgba(display);
|
||||
}
|
||||
}
|
||||
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
pub fn session_next_rgba(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
return session.next_rgba();
|
||||
pub fn session_next_rgba(session_id: SessionID, display: usize) {
|
||||
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
|
||||
return s.ui_handler.next_rgba(display);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn session_register_texture(_session_id: SessionID, _ptr: usize) {
|
||||
pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
if let Some(session) = sessions::get_session(&_session_id) {
|
||||
session.register_texture(_ptr);
|
||||
return;
|
||||
for s in sessions::get_sessions() {
|
||||
if let Some(h) = s
|
||||
.ui_handler
|
||||
.session_handlers
|
||||
.write()
|
||||
.unwrap()
|
||||
.get_mut(&_session_id)
|
||||
{
|
||||
h.notify_rendered = false;
|
||||
h.renderer.set_size(_display, _width, _height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_session_event(
|
||||
session_id: &SessionID,
|
||||
name: &str,
|
||||
event: Vec<(&str, &str)>,
|
||||
) -> Option<bool> {
|
||||
sessions::get_session(session_id)?.push_event(name, event)
|
||||
pub fn session_register_texture(_session_id: SessionID, _display: usize, _ptr: usize) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
for s in sessions::get_sessions() {
|
||||
if let Some(h) = s
|
||||
.ui_handler
|
||||
.session_handlers
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&_session_id)
|
||||
{
|
||||
h.renderer.register_texture(_display, _ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_session_event(session_id: &SessionID, name: &str, event: Vec<(&str, &str)>) {
|
||||
if let Some(s) = sessions::get_session_by_session_id(session_id) {
|
||||
s.push_event(name, event);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -1123,7 +1287,7 @@ fn session_send_touch_scale(
|
||||
) {
|
||||
match v.get("v").and_then(|s| s.as_i64()) {
|
||||
Some(scale) => {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
@ -1147,7 +1311,7 @@ fn session_send_touch_pan(
|
||||
v.get("y").and_then(|y| y.as_i64()),
|
||||
) {
|
||||
(Some(x), Some(y)) => {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session
|
||||
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
|
||||
}
|
||||
@ -1191,6 +1355,15 @@ pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
||||
for s in sessions::get_sessions() {
|
||||
if let Some(h) = s.session_handlers.write().unwrap().get_mut(&session_id) {
|
||||
h.on_waiting_for_image_dialog_show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hooks for session.
|
||||
#[derive(Clone)]
|
||||
pub enum SessionHook {
|
||||
@ -1199,7 +1372,7 @@ pub enum SessionHook {
|
||||
|
||||
#[inline]
|
||||
pub fn get_cur_session() -> Option<FlutterSession> {
|
||||
sessions::get_session(&*CUR_SESSION_ID.read().unwrap())
|
||||
sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap())
|
||||
}
|
||||
|
||||
// sessions mod is used to avoid the big lock of sessions' map.
|
||||
@ -1207,22 +1380,118 @@ pub mod sessions {
|
||||
use super::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref SESSIONS: RwLock<HashMap<SessionID, FlutterSession>> = Default::default();
|
||||
// peer -> peer session, peer session -> ui sessions
|
||||
static ref SESSIONS: RwLock<HashMap<(String, ConnType), FlutterSession>> = Default::default();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_session(session_id: SessionID, session: FlutterSession) -> Option<FlutterSession> {
|
||||
SESSIONS.write().unwrap().insert(session_id, session)
|
||||
pub fn get_session_count(peer_id: String, conn_type: ConnType) -> usize {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&(peer_id, conn_type))
|
||||
.map(|s| s.ui_handler.session_handlers.read().unwrap().len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove_session(session_id: &SessionID) -> Option<FlutterSession> {
|
||||
SESSIONS.write().unwrap().remove(session_id)
|
||||
pub fn get_peer_id_by_session_id(id: &SessionID, conn_type: ConnType) -> Option<String> {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find_map(|((peer_id, t), s)| {
|
||||
if *t == conn_type
|
||||
&& s.ui_handler
|
||||
.session_handlers
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(id)
|
||||
{
|
||||
Some(peer_id.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_session(session_id: &SessionID) -> Option<FlutterSession> {
|
||||
SESSIONS.read().unwrap().get(session_id).cloned()
|
||||
pub fn get_session_by_session_id(id: &SessionID) -> Option<FlutterSession> {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.find(|s| {
|
||||
s.ui_handler
|
||||
.session_handlers
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(id)
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_session_by_peer_id(peer_id: String, conn_type: ConnType) -> Option<FlutterSession> {
|
||||
SESSIONS.read().unwrap().get(&(peer_id, conn_type)).cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove_session_by_session_id(id: &SessionID) -> Option<FlutterSession> {
|
||||
let mut remove_peer_key = None;
|
||||
for (peer_key, s) in SESSIONS.write().unwrap().iter_mut() {
|
||||
let mut write_lock = s.ui_handler.session_handlers.write().unwrap();
|
||||
if write_lock.remove(id).is_some() {
|
||||
if write_lock.is_empty() {
|
||||
remove_peer_key = Some(peer_key.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
SESSIONS.write().unwrap().remove(&remove_peer_key?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert_session(session_id: SessionID, conn_type: ConnType, session: FlutterSession) {
|
||||
SESSIONS
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry((session.id.clone(), conn_type))
|
||||
.or_insert(session)
|
||||
.ui_handler
|
||||
.session_handlers
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(session_id, Default::default());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert_peer_session_id(
|
||||
peer_id: String,
|
||||
conn_type: ConnType,
|
||||
session_id: SessionID,
|
||||
) -> bool {
|
||||
if let Some(s) = SESSIONS.read().unwrap().get(&(peer_id, conn_type)) {
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
let h = SessionHandler::default();
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
let mut h = SessionHandler::default();
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
{
|
||||
h.renderer.is_support_multi_ui_session = crate::common::is_support_multi_ui_session(
|
||||
&s.ui_handler.peer_info.read().unwrap().version,
|
||||
);
|
||||
}
|
||||
let _ = s
|
||||
.ui_handler
|
||||
.session_handlers
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(session_id, h);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -1230,26 +1499,14 @@ pub mod sessions {
|
||||
SESSIONS.read().unwrap().values().cloned().collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_session_by_peer_id(peer_id: &str) -> Option<FlutterSession> {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.find(|session| session.id == peer_id)
|
||||
.map(|s| s.clone())
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn other_sessions_running(session_id: &SessionID) -> bool {
|
||||
pub fn other_sessions_running(peer_id: String, conn_type: ConnType) -> bool {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.filter(|k| *k != session_id)
|
||||
.count()
|
||||
!= 0
|
||||
.get(&(peer_id, conn_type))
|
||||
.map(|s| s.session_handlers.read().unwrap().len() != 0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
client::file_trait::FileManager,
|
||||
common::is_keyboard_mode_supported,
|
||||
common::make_fd_to_json,
|
||||
flutter::{self, session_add, session_start_, sessions},
|
||||
flutter::{self, session_add, session_add_existed, session_start_, sessions},
|
||||
input::*,
|
||||
ui_interface::{self, *},
|
||||
};
|
||||
@ -16,6 +16,7 @@ use hbb_common::{
|
||||
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
|
||||
fs, lazy_static, log,
|
||||
message_proto::KeyboardMode,
|
||||
rendezvous_proto::ConnType,
|
||||
ResultType,
|
||||
};
|
||||
use std::{
|
||||
@ -67,7 +68,7 @@ pub fn stop_global_event_stream(app_type: String) {
|
||||
}
|
||||
pub enum EventToUI {
|
||||
Event(String),
|
||||
Rgba,
|
||||
Rgba(usize),
|
||||
}
|
||||
|
||||
pub fn host_stop_system_key_propagate(_stopped: bool) {
|
||||
@ -75,8 +76,26 @@ pub fn host_stop_system_key_propagate(_stopped: bool) {
|
||||
crate::platform::windows::stop_system_key_propagate(_stopped);
|
||||
}
|
||||
|
||||
// FIXME: -> ResultType<()> cannot be parsed by frb_codegen
|
||||
// thread 'main' panicked at 'Failed to parse function output type `ResultType<()>`', $HOME\.cargo\git\checkouts\flutter_rust_bridge-ddba876d3ebb2a1e\e5adce5\frb_codegen\src\parser\mod.rs:151:25
|
||||
// This function is only used to count the number of control sessions.
|
||||
pub fn peer_get_default_sessions_count(id: String) -> SyncReturn<usize> {
|
||||
SyncReturn(sessions::get_session_count(id, ConnType::DEFAULT_CONN))
|
||||
}
|
||||
|
||||
pub fn session_add_existed_sync(id: String, session_id: SessionID) -> SyncReturn<String> {
|
||||
if let Err(e) = session_add_existed(id.clone(), session_id) {
|
||||
SyncReturn(format!("Failed to add session with id {}, {}", &id, e))
|
||||
} else {
|
||||
SyncReturn("".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_try_add_display(session_id: SessionID, displays: Vec<i32>) -> SyncReturn<()> {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.capture_displays(displays, vec![], vec![]);
|
||||
}
|
||||
SyncReturn(())
|
||||
}
|
||||
|
||||
pub fn session_add_sync(
|
||||
session_id: SessionID,
|
||||
id: String,
|
||||
@ -112,7 +131,7 @@ pub fn session_start(
|
||||
}
|
||||
|
||||
pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_remember())
|
||||
} else {
|
||||
None
|
||||
@ -120,7 +139,7 @@ pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
|
||||
}
|
||||
|
||||
pub fn session_get_toggle_option(session_id: SessionID, arg: String) -> Option<bool> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_toggle_option(arg))
|
||||
} else {
|
||||
None
|
||||
@ -133,7 +152,7 @@ pub fn session_get_toggle_option_sync(session_id: SessionID, arg: String) -> Syn
|
||||
}
|
||||
|
||||
pub fn session_get_option(session_id: SessionID, arg: String) -> Option<String> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_option(arg))
|
||||
} else {
|
||||
None
|
||||
@ -147,55 +166,61 @@ pub fn session_login(
|
||||
password: String,
|
||||
remember: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.login(os_username, os_password, password, remember);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_close(session_id: SessionID) {
|
||||
if let Some(session) = sessions::remove_session(&session_id) {
|
||||
session.close_event_stream();
|
||||
if let Some(session) = sessions::remove_session_by_session_id(&session_id) {
|
||||
session.close_event_stream(session_id);
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_refresh(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.refresh_video();
|
||||
pub fn session_refresh(session_id: SessionID, display: usize) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.refresh_video(display as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_record_screen(session_id: SessionID, start: bool, width: usize, height: usize) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.record_screen(start, width as _, height as _);
|
||||
pub fn session_record_screen(
|
||||
session_id: SessionID,
|
||||
start: bool,
|
||||
display: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.record_screen(start, display as _, width as _, height as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_record_status(session_id: SessionID, status: bool) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.record_status(status);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.reconnect(force_relay);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_toggle_option(session_id: SessionID, value: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
log::warn!("toggle option {}", &value);
|
||||
session.toggle_option(value.clone());
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if sessions::get_session(&session_id).is_some() && value == "disable-clipboard" {
|
||||
if sessions::get_session_by_session_id(&session_id).is_some() && value == "disable-clipboard" {
|
||||
crate::flutter::update_text_clipboard_required();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<String> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_flutter_option(k))
|
||||
} else {
|
||||
None
|
||||
@ -203,13 +228,14 @@ pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<St
|
||||
}
|
||||
|
||||
pub fn session_set_flutter_option(session_id: SessionID, k: String, v: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.save_flutter_option(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
// This function is only used for the default connection session.
|
||||
pub fn session_get_flutter_option_by_peer_id(id: String, k: String) -> Option<String> {
|
||||
if let Some(session) = sessions::get_session_by_peer_id(&id) {
|
||||
if let Some(session) = sessions::get_session_by_peer_id(id, ConnType::DEFAULT_CONN) {
|
||||
Some(session.get_flutter_option(k))
|
||||
} else {
|
||||
None
|
||||
@ -238,7 +264,7 @@ pub fn set_local_kb_layout_type(kb_layout_type: String) {
|
||||
}
|
||||
|
||||
pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_view_style())
|
||||
} else {
|
||||
None
|
||||
@ -246,13 +272,13 @@ pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn session_set_view_style(session_id: SessionID, value: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.save_view_style(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_scroll_style())
|
||||
} else {
|
||||
None
|
||||
@ -260,13 +286,13 @@ pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn session_set_scroll_style(session_id: SessionID, value: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.save_scroll_style(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_image_quality())
|
||||
} else {
|
||||
None
|
||||
@ -274,13 +300,13 @@ pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn session_set_image_quality(session_id: SessionID, value: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.save_image_quality(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_keyboard_mode())
|
||||
} else {
|
||||
None
|
||||
@ -289,7 +315,7 @@ pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
|
||||
|
||||
pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
|
||||
let mut _mode_updated = false;
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.save_keyboard_mode(value.clone());
|
||||
_mode_updated = true;
|
||||
}
|
||||
@ -300,7 +326,7 @@ pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
|
||||
}
|
||||
|
||||
pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_reverse_mouse_wheel())
|
||||
} else {
|
||||
None
|
||||
@ -308,13 +334,27 @@ pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String>
|
||||
}
|
||||
|
||||
pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.save_reverse_mouse_wheel(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_displays_as_individual_windows(session_id: SessionID) -> SyncReturn<Option<String>> {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
SyncReturn(Some(session.get_displays_as_individual_windows()))
|
||||
} else {
|
||||
SyncReturn(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_displays_as_individual_windows(session_id: SessionID, value: String) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.save_displays_as_individual_windows(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32>> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
Some(session.get_custom_image_quality())
|
||||
} else {
|
||||
None
|
||||
@ -322,7 +362,7 @@ pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32
|
||||
}
|
||||
|
||||
pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -> SyncReturn<bool> {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
if let Ok(mode) = KeyboardMode::from_str(&mode[..]) {
|
||||
SyncReturn(is_keyboard_mode_supported(
|
||||
&mode,
|
||||
@ -337,32 +377,36 @@ pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -
|
||||
}
|
||||
|
||||
pub fn session_set_custom_image_quality(session_id: SessionID, value: i32) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.save_custom_image_quality(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_custom_fps(session_id: SessionID, fps: i32) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.set_custom_fps(fps);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_lock_screen(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.lock_screen();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_ctrl_alt_del(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.ctrl_alt_del();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_switch_display(session_id: SessionID, value: i32) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.switch_display(value);
|
||||
pub fn session_switch_display(session_id: SessionID, value: Vec<i32>) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
if value.len() == 1 {
|
||||
session.switch_display(value[0]);
|
||||
} else {
|
||||
session.capture_displays(vec![], vec![], value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,7 +418,7 @@ pub fn session_handle_flutter_key_event(
|
||||
lock_modes: i32,
|
||||
down_or_up: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
let keyboard_mode = session.get_keyboard_mode();
|
||||
session.handle_flutter_key_event(
|
||||
&keyboard_mode,
|
||||
@ -395,7 +439,7 @@ pub fn session_handle_flutter_key_event(
|
||||
// This will cause the keyboard input to take no effect.
|
||||
pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncReturn<()> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(session) = sessions::get_session(&_session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&_session_id) {
|
||||
let keyboard_mode = session.get_keyboard_mode();
|
||||
if _enter {
|
||||
set_cur_session_id_(_session_id, &keyboard_mode);
|
||||
@ -417,14 +461,14 @@ pub fn session_input_key(
|
||||
shift: bool,
|
||||
command: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
session.input_key(&name, down, press, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_input_string(session_id: SessionID, value: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
session.input_string(&value);
|
||||
}
|
||||
@ -432,33 +476,33 @@ pub fn session_input_string(session_id: SessionID, value: String) {
|
||||
|
||||
// chat_client_mode
|
||||
pub fn session_send_chat(session_id: SessionID, text: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.send_chat(text);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_peer_option(session_id: SessionID, name: String, value: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.set_option(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_peer_option(session_id: SessionID, name: String) -> String {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
return session.get_option(name);
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_input_os_password(session_id: SessionID, value: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.input_os_password(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
// File Action
|
||||
pub fn session_read_remote_dir(session_id: SessionID, path: String, include_hidden: bool) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.read_remote_dir(path, include_hidden);
|
||||
}
|
||||
}
|
||||
@ -472,7 +516,7 @@ pub fn session_send_files(
|
||||
include_hidden: bool,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
|
||||
}
|
||||
}
|
||||
@ -485,7 +529,7 @@ pub fn session_set_confirm_override_file(
|
||||
remember: bool,
|
||||
is_upload: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.set_confirm_override_file(act_id, file_num, need_override, remember, is_upload);
|
||||
}
|
||||
}
|
||||
@ -497,7 +541,7 @@ pub fn session_remove_file(
|
||||
file_num: i32,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.remove_file(act_id, path, file_num, is_remote);
|
||||
}
|
||||
}
|
||||
@ -509,7 +553,7 @@ pub fn session_read_dir_recursive(
|
||||
is_remote: bool,
|
||||
show_hidden: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.remove_dir_all(act_id, path, is_remote, show_hidden);
|
||||
}
|
||||
}
|
||||
@ -520,19 +564,19 @@ pub fn session_remove_all_empty_dirs(
|
||||
path: String,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.remove_dir(act_id, path, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_cancel_job(session_id: SessionID, act_id: i32) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.cancel_job(act_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_create_dir(session_id: SessionID, act_id: i32, path: String, is_remote: bool) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.create_dir(act_id, path, is_remote);
|
||||
}
|
||||
}
|
||||
@ -549,14 +593,14 @@ pub fn session_read_local_dir_sync(
|
||||
}
|
||||
|
||||
pub fn session_get_platform(session_id: SessionID, is_remote: bool) -> String {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
return session.get_platform(is_remote);
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_load_last_transfer_jobs(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
return session.load_last_jobs();
|
||||
} else {
|
||||
// a tip for flutter dev
|
||||
@ -576,46 +620,44 @@ pub fn session_add_job(
|
||||
include_hidden: bool,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.add_job(act_id, path, to, file_num, include_hidden, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_resume_job(session_id: SessionID, act_id: i32, is_remote: bool) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.resume_job(act_id, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_elevate_direct(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.elevate_direct();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_elevate_with_logon(session_id: SessionID, username: String, password: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.elevate_with_logon(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_switch_sides(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.switch_sides();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32, height: i32) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.change_resolution(display, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
|
||||
pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
if let Some(session) = sessions::get_session(&_session_id) {
|
||||
session.set_size(_width, _height);
|
||||
}
|
||||
super::flutter::session_set_size(_session_id, _display, _width, _height)
|
||||
}
|
||||
|
||||
pub fn main_get_sound_inputs() -> Vec<String> {
|
||||
@ -1008,18 +1050,22 @@ pub fn main_handle_relay_id(id: String) -> String {
|
||||
handle_relay_id(id)
|
||||
}
|
||||
|
||||
pub fn main_get_current_display() -> SyncReturn<String> {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
let display_info = match crate::video_service::get_current_display() {
|
||||
Ok((_, _, display)) => serde_json::to_string(&HashMap::from([
|
||||
("w", display.width()),
|
||||
("h", display.height()),
|
||||
]))
|
||||
.unwrap_or_default(),
|
||||
Err(..) => "".to_string(),
|
||||
};
|
||||
pub fn main_get_main_display() -> SyncReturn<String> {
|
||||
#[cfg(target_os = "ios")]
|
||||
let display_info = "".to_owned();
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
let mut display_info = "".to_owned();
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if let Ok(displays) = crate::display_service::try_get_displays() {
|
||||
// to-do: Need to detect current display index.
|
||||
if let Some(display) = displays.iter().next() {
|
||||
display_info = serde_json::to_string(&HashMap::from([
|
||||
("w", display.width()),
|
||||
("h", display.height()),
|
||||
]))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
SyncReturn(display_info)
|
||||
}
|
||||
|
||||
@ -1029,31 +1075,31 @@ pub fn session_add_port_forward(
|
||||
remote_host: String,
|
||||
remote_port: i32,
|
||||
) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.add_port_forward(local_port, remote_host, remote_port);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_remove_port_forward(session_id: SessionID, local_port: i32) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.remove_port_forward(local_port);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_new_rdp(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.new_rdp();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_request_voice_call(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.request_voice_call();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_close_voice_call(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.close_voice_call();
|
||||
}
|
||||
}
|
||||
@ -1238,20 +1284,20 @@ pub fn session_send_mouse(session_id: SessionID, msg: String) {
|
||||
_ => 0,
|
||||
} << 3;
|
||||
}
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.send_mouse(mask, x, y, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_restart_remote_device(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.restart_remote_device();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> SyncReturn<String> {
|
||||
let res = if let Some(session) = sessions::get_session(&session_id) {
|
||||
let res = if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.get_audit_server(typ)
|
||||
} else {
|
||||
"".to_owned()
|
||||
@ -1260,13 +1306,13 @@ pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> Sync
|
||||
}
|
||||
|
||||
pub fn session_send_note(session_id: SessionID, note: String) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.send_note(note)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_alternative_codecs(session_id: SessionID) -> String {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
let (vp8, av1, h264, h265) = session.alternative_codecs();
|
||||
let msg = HashMap::from([("vp8", vp8), ("av1", av1), ("h264", h264), ("h265", h265)]);
|
||||
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
|
||||
@ -1276,15 +1322,13 @@ pub fn session_alternative_codecs(session_id: SessionID) -> String {
|
||||
}
|
||||
|
||||
pub fn session_change_prefer_codec(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.change_prefer_codec();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.ui_handler.on_waiting_for_image_dialog_show();
|
||||
}
|
||||
super::flutter::session_on_waiting_for_image_dialog_show(session_id);
|
||||
}
|
||||
|
||||
pub fn main_set_home_dir(_home: String) {
|
||||
@ -1434,16 +1478,22 @@ pub fn translate(name: String, locale: String) -> SyncReturn<String> {
|
||||
SyncReturn(crate::client::translate_locale(name, &locale))
|
||||
}
|
||||
|
||||
pub fn session_get_rgba_size(session_id: SessionID) -> SyncReturn<usize> {
|
||||
SyncReturn(super::flutter::session_get_rgba_size(session_id))
|
||||
pub fn session_get_rgba_size(session_id: SessionID, display: usize) -> SyncReturn<usize> {
|
||||
SyncReturn(super::flutter::session_get_rgba_size(session_id, display))
|
||||
}
|
||||
|
||||
pub fn session_next_rgba(session_id: SessionID) -> SyncReturn<()> {
|
||||
SyncReturn(super::flutter::session_next_rgba(session_id))
|
||||
pub fn session_next_rgba(session_id: SessionID, display: usize) -> SyncReturn<()> {
|
||||
SyncReturn(super::flutter::session_next_rgba(session_id, display))
|
||||
}
|
||||
|
||||
pub fn session_register_texture(session_id: SessionID, ptr: usize) -> SyncReturn<()> {
|
||||
SyncReturn(super::flutter::session_register_texture(session_id, ptr))
|
||||
pub fn session_register_texture(
|
||||
session_id: SessionID,
|
||||
display: usize,
|
||||
ptr: usize,
|
||||
) -> SyncReturn<()> {
|
||||
SyncReturn(super::flutter::session_register_texture(
|
||||
session_id, display, ptr,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn query_onlines(ids: Vec<String>) {
|
||||
@ -1522,15 +1572,15 @@ pub fn main_update_me() -> SyncReturn<bool> {
|
||||
}
|
||||
|
||||
pub fn set_cur_session_id(session_id: SessionID) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
set_cur_session_id_(session_id, &session.get_keyboard_mode())
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cur_session_id_(session_id: SessionID, keyboard_mode: &str) {
|
||||
fn set_cur_session_id_(session_id: SessionID, _keyboard_mode: &str) {
|
||||
super::flutter::set_cur_session_id(session_id);
|
||||
#[cfg(windows)]
|
||||
crate::keyboard::update_grab_get_key_name(keyboard_mode);
|
||||
crate::keyboard::update_grab_get_key_name(_keyboard_mode);
|
||||
}
|
||||
|
||||
pub fn install_show_run_without_install() -> SyncReturn<bool> {
|
||||
@ -1613,6 +1663,22 @@ pub fn main_start_ipc_url_server() {
|
||||
std::thread::spawn(move || crate::server::start_ipc_url_server());
|
||||
}
|
||||
|
||||
pub fn main_test_wallpaper(_second: u64) {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
std::thread::spawn(move || match crate::platform::WallPaperRemover::new() {
|
||||
Ok(_remover) => {
|
||||
std::thread::sleep(std::time::Duration::from_secs(_second));
|
||||
}
|
||||
Err(e) => {
|
||||
log::info!("create wallpaper remover failed:{:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn main_support_remove_wallpaper() -> bool {
|
||||
support_remove_wallpaper()
|
||||
}
|
||||
|
||||
/// Send a url scheme throught the ipc.
|
||||
///
|
||||
/// * macOS only
|
||||
@ -1796,6 +1862,10 @@ pub fn plugin_install(_id: String, _b: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_support_multi_ui_session(version: String) -> SyncReturn<bool> {
|
||||
SyncReturn(crate::common::is_support_multi_ui_session(&version))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::{config, log};
|
||||
|
@ -147,10 +147,22 @@ fn handle_config_options(config_options: HashMap<String, String>) {
|
||||
config_options
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
if v.is_empty() {
|
||||
options.remove(k);
|
||||
if k == "allow-share-rdp" {
|
||||
// only changes made after installation take effect.
|
||||
#[cfg(windows)]
|
||||
if crate::ui_interface::is_rdp_service_open() {
|
||||
let current = crate::ui_interface::is_share_rdp();
|
||||
let set = v == "Y";
|
||||
if current != set {
|
||||
crate::platform::windows::set_share_rdp(set);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
options.insert(k.to_string(), v.to_string());
|
||||
if v.is_empty() {
|
||||
options.remove(k);
|
||||
} else {
|
||||
options.insert(k.to_string(), v.to_string());
|
||||
}
|
||||
}
|
||||
})
|
||||
.count();
|
||||
|
@ -214,6 +214,7 @@ static mut IS_0X021D_DOWN: bool = false;
|
||||
#[cfg(target_os = "macos")]
|
||||
static mut IS_LEFT_OPTION_DOWN: bool = false;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn get_keyboard_mode() -> String {
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "استبدال"),
|
||||
("This file exists, skip or overwrite this file?", "الملف موجود, هل تريد التجاوز او الاستبدال؟"),
|
||||
("Quit", "خروج"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "مساعدة"),
|
||||
("Failed", "فشل"),
|
||||
("Succeeded", "نجاح"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "جهاز ادخال الصوت"),
|
||||
("Use IP Whitelisting", "استخدام قائمة الـ IP البيضاء"),
|
||||
("Network", "الشبكة"),
|
||||
("Enable RDP", "تفعيل RDP"),
|
||||
("Pin Toolbar", "تثبيت شريط الادوات"),
|
||||
("Unpin Toolbar", "الغاء تثبيت شريط الادوات"),
|
||||
("Recording", "التسجيل"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Dispositiu d'entrada d'àudio"),
|
||||
("Use IP Whitelisting", "Utilitza llista de IPs admeses"),
|
||||
("Network", "Xarxa"),
|
||||
("Enable RDP", "Habilitar RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Gravant"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "覆盖"),
|
||||
("This file exists, skip or overwrite this file?", "这个文件/文件夹已存在,跳过/覆盖?"),
|
||||
("Quit", "退出"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/zh-cn/manual/mac#%E5%90%AF%E7%94%A8%E6%9D%83%E9%99%90"),
|
||||
("Help", "帮助"),
|
||||
("Failed", "失败"),
|
||||
("Succeeded", "成功"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "音频输入设备"),
|
||||
("Use IP Whitelisting", "只允许白名单上的 IP 访问"),
|
||||
("Network", "网络"),
|
||||
("Enable RDP", "允许 RDP 访问"),
|
||||
("Pin Toolbar", "固定工具栏"),
|
||||
("Unpin Toolbar", "取消固定工具栏"),
|
||||
("Recording", "录屏"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", "启动时检查软件更新"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "请升级专业版服务器到{}或更高版本!"),
|
||||
("pull_group_failed_tip", "获取组信息失败"),
|
||||
("Filter by intersection", "按交集过滤"),
|
||||
("Remove wallpaper during incoming sessions", "接受会话时移除桌面壁纸"),
|
||||
("Test", "测试"),
|
||||
("switch_display_elevated_connections_tip", "提权后,被控有多个连接,不能切换到非主显示器。若要控制多显示器,请安装后再试。"),
|
||||
("display_is_plugged_out_msg", "显示器被拔出,切换到第一个显示器。"),
|
||||
("No displays", "没有显示器。"),
|
||||
("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"),
|
||||
("Open in new window", "在新的窗口中打开"),
|
||||
("Show displays as individual windows", "在单个窗口中打开显示器"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Přepsat"),
|
||||
("This file exists, skip or overwrite this file?", "Tento soubor existuje, přeskočit, nebo přepsat tento soubor?"),
|
||||
("Quit", "Ukončit"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Nápověda"),
|
||||
("Failed", "Nepodařilo se"),
|
||||
("Succeeded", "Úspěšný"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Vstupní zvukové zařízení"),
|
||||
("Use IP Whitelisting", "Použít bílou listinu IP"),
|
||||
("Network", "Síť"),
|
||||
("Enable RDP", "Povolit protokol RDP"),
|
||||
("Pin Toolbar", "Připnout panel nástrojů"),
|
||||
("Unpin Toolbar", "Odepnout panel nástrojů"),
|
||||
("Recording", "Nahrávání"),
|
||||
@ -555,7 +553,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("auto_disconnect_option_tip", "Automatické ukončení příchozích relací při nečinnosti uživatele"),
|
||||
("Connection failed due to inactivity", "Připojení se nezdařilo z důvodu nečinnosti"),
|
||||
("Check for software update on startup", "Kontrola aktualizace softwaru při spuštění"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Upgradujte prosím RustDesk Server Pro na verzi {} nebo novější!"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Aktualizujte prosím RustDesk Server Pro na verzi {} nebo novější!"),
|
||||
("pull_group_failed_tip", "Nepodařilo se obnovit skupinu"),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Overskriv"),
|
||||
("This file exists, skip or overwrite this file?", "Denne fil findes allerede, vil du springe over eller overskrive denne fil?"),
|
||||
("Quit", "Afslut"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Hjælp"),
|
||||
("Failed", "Mislykkedet"),
|
||||
("Succeeded", "Vellykket"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Lydindgangsenhed"),
|
||||
("Use IP Whitelisting", "Brug IP Whitelisting"),
|
||||
("Network", "Netværk"),
|
||||
("Enable RDP", "Aktivér RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Optager"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Überschreiben"),
|
||||
("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"),
|
||||
("Quit", "Beenden"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/de/manual/mac/#berechtigungen-aktivieren"),
|
||||
("Help", "Hilfe"),
|
||||
("Failed", "Fehlgeschlagen"),
|
||||
("Succeeded", "Erfolgreich"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Audioeingabegerät"),
|
||||
("Use IP Whitelisting", "IP-Whitelist verwenden"),
|
||||
("Network", "Netzwerk"),
|
||||
("Enable RDP", "RDP aktivieren"),
|
||||
("Pin Toolbar", "Symbolleiste anpinnen"),
|
||||
("Unpin Toolbar", "Symbolleiste lösen"),
|
||||
("Recording", "Aufnahme"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", "Beim Start auf Softwareaktualisierung prüfen"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Bitte aktualisieren Sie RustDesk Server Pro auf die Version {} oder neuer!"),
|
||||
("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"),
|
||||
("Filter by intersection", "Nach Schnittmenge filtern"),
|
||||
("Remove wallpaper during incoming sessions", "Hintergrundbild während eingehender Sitzungen entfernen"),
|
||||
("Test", "Test"),
|
||||
("switch_display_elevated_connections_tip", "Das Umschalten auf eine nicht primäre Anzeige wird mit erhöhten Rechten nicht unterstützt, wenn mehrere Verbindungen bestehen. Bitte versuchen Sie es nach der Installation erneut, wenn Sie mehrere Anzeigen steuern möchten."),
|
||||
("display_is_plugged_out_msg", "Das Anzeigegerät ist nicht angeschlossen, schalten Sie auf das erste Anzeigegerät um."),
|
||||
("No displays", "Keine Anzeigegeräte"),
|
||||
("elevated_switch_display_msg", "Wechseln Sie zur primären Anzeige, da die Mehrfachanzeige im erweiterten Modus nicht unterstützt wird."),
|
||||
("Open in new window", "In einem neuen Fenster öffnen"),
|
||||
("Show displays as individual windows", "Anzeigen als einzelne Fenster darstellen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Αντικατάσταση"),
|
||||
("This file exists, skip or overwrite this file?", "Αυτό το αρχείο υπάρχει, παράβλεψη ή αντικατάσταση αυτού του αρχείου;"),
|
||||
("Quit", "Έξοδος"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Βοήθεια"),
|
||||
("Failed", "Απέτυχε"),
|
||||
("Succeeded", "Επιτυχής"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Συσκευή εισόδου ήχου"),
|
||||
("Use IP Whitelisting", "Χρήση λίστας επιτρεπόμενων IP"),
|
||||
("Network", "Δίκτυο"),
|
||||
("Enable RDP", "Ενεργοποίηση RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Εγγραφή"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
179
src/lang/en.rs
179
src/lang/en.rs
@ -3,21 +3,101 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("desk_tip", "Your desktop can be accessed with this ID and password."),
|
||||
("connecting_status", "Connecting to the RustDesk network..."),
|
||||
("Enable Service", "Enable service"),
|
||||
("Start Service", "Enable service"),
|
||||
("not_ready_status", "Not ready. Please check your connection"),
|
||||
("Transfer File", "Transfer file"),
|
||||
("Recent Sessions", "Recent sessions"),
|
||||
("Address Book", "Address book"),
|
||||
("TCP Tunneling", "TCP tunneling"),
|
||||
("Enable Keyboard/Mouse", "Enable keyboard/mouse"),
|
||||
("Enable Clipboard", "Enable clipboard"),
|
||||
("Enable File Transfer", "Enable file transfer"),
|
||||
("Enable TCP Tunneling", "Enable TCP tunneling"),
|
||||
("IP Whitelisting", "IP whitelisting"),
|
||||
("ID/Relay Server", "ID/Relay server"),
|
||||
("Import Server Config", "Import server config"),
|
||||
("Export Server Config", "Export server config"),
|
||||
("id_change_tip", "Only a-z, A-Z, 0-9 and _ (underscore) characters allowed. The first letter must be a-z, A-Z. Length between 6 and 16."),
|
||||
("Slogan_tip", "Made with heart in this chaotic world!"),
|
||||
("Build Date", "Build date"),
|
||||
("Audio Input", "Audio input"),
|
||||
("Hardware Codec", "Hardware codec"),
|
||||
("ID Server", "ID server"),
|
||||
("Relay Server", "Relay server"),
|
||||
("API Server", "API server"),
|
||||
("invalid_http", "must start with http:// or https://"),
|
||||
("server_not_support", "Not yet supported by the server"),
|
||||
("Password Required", "Password required"),
|
||||
("Wrong Password", "Wrong password"),
|
||||
("Connection Error", "Connection error"),
|
||||
("Login Error", "Login error"),
|
||||
("Show Hidden Files", "Show hidden files"),
|
||||
("Refresh File", "Refresh file"),
|
||||
("Remote Computer", "Remote computer"),
|
||||
("Local Computer", "Local computer"),
|
||||
("Confirm Delete", "Confirm delete"),
|
||||
("Multi Select", "Multi select"),
|
||||
("Select All", "Select all"),
|
||||
("Unselect All", "Unselect all"),
|
||||
("Empty Directory", "Empty directory"),
|
||||
("Custom Image Quality", "Custom image quality"),
|
||||
("Adjust Window", "Adjust window"),
|
||||
("Insert Lock", "Insert lock"),
|
||||
("Set Password", "Set password"),
|
||||
("OS Password", "OS password"),
|
||||
("install_tip", "Due to UAC, RustDesk can not work properly as the remote side in some cases. To avoid UAC, please click the button below to install RustDesk to the system."),
|
||||
("config_acc", "In order to control your Desktop remotely, you need to grant RustDesk \"Accessibility\" permissions."),
|
||||
("config_screen", "In order to access your Desktop remotely, you need to grant RustDesk \"Screen Recording\" permissions."),
|
||||
("Installation Path", "Installation path"),
|
||||
("agreement_tip", "By starting the installation, you accept the license agreement."),
|
||||
("Accept and Install", "Accept and install"),
|
||||
("not_close_tcp_tip", "Don't close this window while you are using the tunnel"),
|
||||
("Remote Host", "Remote host"),
|
||||
("Remote Port", "Remote port"),
|
||||
("Local Port", "Local port"),
|
||||
("Local Address", "Local address"),
|
||||
("Change Local Port", "Change local port"),
|
||||
("setup_server_tip", "For faster connection, please set up your own server"),
|
||||
("Enter Remote ID", "Enter remote ID"),
|
||||
("Auto Login", "Auto Login (Only valid if you set \"Lock after session end\")"),
|
||||
("Always connect via relay", "Always Connect via Relay"),
|
||||
("Enable Direct IP Access", "Enable direct IP access"),
|
||||
("Create Desktop Shortcut", "Create desktop shortcut"),
|
||||
("Change Path", "Change path"),
|
||||
("Create Folder", "Create folder"),
|
||||
("whitelist_tip", "Only whitelisted IP can access me"),
|
||||
("verification_tip", "A verification code has been sent to the registered email address, enter the verification code to continue logging in."),
|
||||
("whitelist_sep", "Separated by comma, semicolon, spaces or new line"),
|
||||
("Add Tag", "Add tag"),
|
||||
("Wrong credentials", "Wrong username or password"),
|
||||
("invalid_http", "must start with http:// or https://"),
|
||||
("Edit Tag", "Edit tag"),
|
||||
("Forget Password", "Forget password"),
|
||||
("Add to Favorites", "Add to favorites"),
|
||||
("Remove from Favorites", "Remove from favorites"),
|
||||
("Socks5 Proxy", "Socks5 proxy"),
|
||||
("install_daemon_tip", "For starting on boot, you need to install system service."),
|
||||
("Are you sure to close the connection?", "Are you sure you want to close the connection?"),
|
||||
("One-Finger Tap", "One-finger tap"),
|
||||
("Left Mouse", "Left mouse"),
|
||||
("One-Long Tap", "One-long tap"),
|
||||
("Two-Finger Tap", "Two-finger tap"),
|
||||
("Right Mouse", "Right mouse"),
|
||||
("One-Finger Move", "One-finger move"),
|
||||
("Double Tap & Move", "Double tap & move"),
|
||||
("Mouse Drag", "Mouse drag"),
|
||||
("Three-Finger vertically", "Three-finger vertically"),
|
||||
("Mouse Wheel", "Mouse wheel"),
|
||||
("Two-Finger Move", "Two-finger move"),
|
||||
("Canvas Move", "Canvas move"),
|
||||
("Pinch to Zoom", "Pinch to zoom"),
|
||||
("Canvas Zoom", "Canvas zoom"),
|
||||
("Share Screen", "Share screen"),
|
||||
("Screen Capture", "Screen capture"),
|
||||
("Input Control", "Input control"),
|
||||
("Audio Capture", "Audio capture"),
|
||||
("File Connection", "File connection"),
|
||||
("Screen Connection", "Screen connection"),
|
||||
("Open System Setting", "Open system setting"),
|
||||
("android_input_permission_tip1", "In order for a remote device to control your Android device via mouse or touch, you need to allow RustDesk to use the \"Accessibility\" service."),
|
||||
("android_input_permission_tip2", "Please go to the next system settings page, find and enter [Installed Services], turn on [RustDesk Input] service."),
|
||||
("android_new_connection_tip", "New control request has been received, which wants to control your current device."),
|
||||
@ -27,70 +107,123 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_start_service_tip", "Tap [Start Service] or enable [Screen Capture] permission to start the screen sharing service."),
|
||||
("android_permission_may_not_change_tip", "Permissions for established connections may not be changed instantly until reconnected."),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
||||
("server_not_support", "Not yet supported by the server"),
|
||||
("Ignore Battery Optimizations", "Ignore battery optimizations"),
|
||||
("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]"),
|
||||
("Start on Boot", "Start on boot"),
|
||||
("Enable Remote Restart", "Enable remote restart"),
|
||||
("Restart Remote Device", "Restart remote device"),
|
||||
("Restarting Remote Device", "Restarting remote device"),
|
||||
("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?"),
|
||||
("Exit Fullscreen", "Exit fullscreen"),
|
||||
("Mobile Actions", "Mobile actions"),
|
||||
("Select Monitor", "Select monitor"),
|
||||
("Control Actions", "Control actions"),
|
||||
("Display Settings", "Display settings"),
|
||||
("Image Quality", "Image quality"),
|
||||
("Scroll Style", "Scroll style"),
|
||||
("Show Toolbar", "Show toolbar"),
|
||||
("Hide Toolbar", "Hide toolbar"),
|
||||
("Direct Connection", "Direct connection"),
|
||||
("Relay Connection", "Relay connection"),
|
||||
("Secure Connection", "Secure connection"),
|
||||
("Insecure Connection", "Insecure connection"),
|
||||
("Dark Theme", "Dark theme"),
|
||||
("Light Theme", "Light theme"),
|
||||
("Follow System", "Follow system"),
|
||||
("Unlock Security Settings", "Unlock security settings"),
|
||||
("Enable Audio", "Enable audio"),
|
||||
("Unlock Network Settings", "Unlock network settings"),
|
||||
("Direct IP Access", "Direct IP access"),
|
||||
("Audio Input Device", "Audio input device"),
|
||||
("Use IP Whitelisting", "Use IP whitelisting"),
|
||||
("Pin Toolbar", "Pin toolbar"),
|
||||
("Unpin Toolbar", "Unpin toolbar"),
|
||||
("Enable Recording Session", "Enable recording session"),
|
||||
("Enable LAN Discovery", "Enable LAN discovery"),
|
||||
("Deny LAN Discovery", "Deny LAN discovery"),
|
||||
("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."),
|
||||
("Keyboard Settings", "Keyboard settings"),
|
||||
("Full Access", "Full access"),
|
||||
("Screen Share", "Screen share"),
|
||||
("JumpLink", "View"),
|
||||
("Stop service", "Stop Service"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Please select the screen to be shared(Operate on the peer side)."),
|
||||
("One-time Password", "One-time password"),
|
||||
("hide_cm_tip", "Allow hiding only if accepting sessions via password and using permanent password"),
|
||||
("wayland_experiment_tip", "Wayland support is in experimental stage, please use X11 if you require unattended access."),
|
||||
("Slogan_tip", "Made with heart in this chaotic world!"),
|
||||
("verification_tip", "A verification code has been sent to the registered email address, enter the verification code to continue logging in."),
|
||||
("Add to Address Book", "Add to address book"),
|
||||
("software_render_tip", "If you're using Nvidia graphics card under Linux and the remote window closes immediately after connecting, switching to the open-source Nouveau driver and choosing to use software rendering may help. A software restart is required."),
|
||||
("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."),
|
||||
("request_elevation_tip", "You can also request elevation if there is someone on the remote side."),
|
||||
("wait_accept_uac_tip", "Please wait for the remote user to accept the UAC dialog."),
|
||||
("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."),
|
||||
("config_microphone", "In order to speak remotely, you need to grant RustDesk \"Record Audio\" permissions."),
|
||||
("request_elevation_tip", "You can also request elevation if there is someone on the remote side."),
|
||||
("Elevation Error", "Elevation error"),
|
||||
("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."),
|
||||
("Request Elevation", "Request elevation"),
|
||||
("wait_accept_uac_tip", "Please wait for the remote user to accept the UAC dialog."),
|
||||
("Switch Sides", "Switch sides"),
|
||||
("Default View Style", "Default view style"),
|
||||
("Default Scroll Style", "Default scroll style"),
|
||||
("Default Image Quality", "Default image quality"),
|
||||
("Default Codec", "Default codec"),
|
||||
("Other Default Options", "Other default options"),
|
||||
("relay_hint_tip", "It may not be possible to connect directly; you can try connecting via relay. Additionally, if you want to use a relay on your first attempt, you can add the \"/r\" suffix to the ID or select the option \"Always connect via relay\" in the card of recent sessions if it exists."),
|
||||
("No transfers in progress", ""),
|
||||
("install_cert_tip", "Install RustDesk certificate"),
|
||||
("confirm_install_cert_tip", "This is a RustDesk testing certificate, which can be trusted. The certificate will be used to trust and install RustDesk drivers when required."),
|
||||
("RDP Settings", "RDP settings"),
|
||||
("New Connection", "New connection"),
|
||||
("Your Device", "Your device"),
|
||||
("empty_recent_tip", "Oops, no recent sessions!\nTime to plan a new one."),
|
||||
("empty_favorite_tip", "No favorite peers yet?\nLet's find someone to connect with and add it to your favorites!"),
|
||||
("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."),
|
||||
("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."),
|
||||
("Empty Username", "Empty username"),
|
||||
("Empty Password", "Empty password"),
|
||||
("identical_file_tip", "This file is identical with the peer's one."),
|
||||
("show_monitors_tip", "Show monitors in toolbar"),
|
||||
("enter_rustdesk_passwd_tip", "Enter RustDesk password"),
|
||||
("remember_rustdesk_passwd_tip", "Remember RustDesk password"),
|
||||
("View Mode", "View mode"),
|
||||
("login_linux_tip", "You need to login to remote Linux account to enable a X desktop session"),
|
||||
("verify_rustdesk_password_tip", "Verify RustDesk password"),
|
||||
("remember_account_tip", "Remember this account"),
|
||||
("os_account_desk_tip", "This account is used to login the remote OS and enable the desktop session in headless"),
|
||||
("OS Account", "Os account"),
|
||||
("another_user_login_title_tip", "Another user already logged in"),
|
||||
("another_user_login_text_tip", "Disconnect"),
|
||||
("xorg_not_found_title_tip", "Xorg not found"),
|
||||
("xorg_not_found_text_tip", "Please install Xorg"),
|
||||
("no_desktop_title_tip", "No desktop is available"),
|
||||
("no_desktop_text_tip", "Please install GNOME desktop"),
|
||||
("System Sound", "System sound"),
|
||||
("Copy Fingerprint", "Copy fingerprint"),
|
||||
("no fingerprints", "No fingerprints"),
|
||||
("resolution_original_tip", "Original resolution"),
|
||||
("resolution_fit_local_tip", "Fit local resolution"),
|
||||
("resolution_custom_tip", "Custom resolution"),
|
||||
("Accept and Elevate", "Accept and elevate"),
|
||||
("accept_and_elevate_btn_tooltip", "Accept the connection and elevate UAC permissions."),
|
||||
("clipboard_wait_response_timeout_tip", "Timed out waiting for copy response."),
|
||||
("logout_tip", "Are you sure you want to log out?"),
|
||||
("exceed_max_devices", "You have reached the maximum number of managed devices."),
|
||||
("Change Password", "Change password"),
|
||||
("Refresh Password", "Refresh password"),
|
||||
("Grid View", "Grid view"),
|
||||
("List View", "List view"),
|
||||
("Toggle Tags", "Toggle tags"),
|
||||
("pull_ab_failed_tip", "Failed to refresh address book"),
|
||||
("push_ab_failed_tip", "Failed to sync address book to server"),
|
||||
("synced_peer_readded_tip", "The devices that were present in the recent sessions will be synchronized back to the address book."),
|
||||
("View Mode", "View mode"),
|
||||
("Block user input", "Block User Input"),
|
||||
("Start session recording", "Start Session Recording"),
|
||||
("Stop session recording", "Stop Session Recording"),
|
||||
("Enable remote configuration modification", "Enable Remote Configuration Modification"),
|
||||
("Change Color", "Change color"),
|
||||
("Primary Color", "Primary color"),
|
||||
("HSV Color", "HSV color"),
|
||||
("Installation Successful!", "Installation successful!"),
|
||||
("scam_title", "You May Be Being SCAMMED!"),
|
||||
("scam_text1", "If you are on the phone with someone you DON'T know AND TRUST who has asked you to use RustDesk and start the service, do not proceed and hang up immediately."),
|
||||
("scam_text2", "They are likely a scammer trying to steal your money or other private information."),
|
||||
("Don't show again", "Don't show again"),
|
||||
("I Agree", "I Agree"),
|
||||
("Decline", "Decline"),
|
||||
("auto_disconnect_option_tip", "Automatically close incoming sessions on user inactivity"),
|
||||
("Connection failed due to inactivity", "Automatically disconnected due to inactivity"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!"),
|
||||
("pull_group_failed_tip", "Failed to refresh group"),
|
||||
].iter().cloned().collect();
|
||||
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
||||
("switch_display_elevated_connections_tip", "Switching to non-primary display is not supported in the elevated mode when there are multiple connections. Please try again after installation if you want to control multiple displays."),
|
||||
("display_is_plugged_out_msg", "The diplay is plugged out, switch to the first display."),
|
||||
("elevated_switch_display_msg", "Switch to the primary display because multiple display is not supported in elevated mode."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", ""),
|
||||
("This file exists, skip or overwrite this file?", ""),
|
||||
("Quit", ""),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", ""),
|
||||
("Failed", ""),
|
||||
("Succeeded", ""),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", ""),
|
||||
("Use IP Whitelisting", ""),
|
||||
("Network", ""),
|
||||
("Enable RDP", ""),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", ""),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Sobrescribir"),
|
||||
("This file exists, skip or overwrite this file?", "Este archivo existe, ¿omitir o sobrescribir este archivo?"),
|
||||
("Quit", "Salir"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Ayuda"),
|
||||
("Failed", "Fallido"),
|
||||
("Succeeded", "Logrado"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Dispositivo de entrada de audio"),
|
||||
("Use IP Whitelisting", "Usar lista de IPs admitidas"),
|
||||
("Network", "Red"),
|
||||
("Enable RDP", "Habilitar RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Grabando"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", "Comprobar actualización al iniciar"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "¡Por favor, actualiza RustDesk Server Pro a la versión {} o superior"),
|
||||
("pull_group_failed_tip", "No se ha podido refrescar el grupo"),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "بازنویسی"),
|
||||
("This file exists, skip or overwrite this file?", "این فایل وجود دارد، از فایل رد شود یا آن را بازنویسی کند؟"),
|
||||
("Quit", "خروج"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "راهنما"),
|
||||
("Failed", "ناموفق"),
|
||||
("Succeeded", "موفقیت آمیز"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "منبع صدا"),
|
||||
("Use IP Whitelisting", "های مجاز IP استفاده از"),
|
||||
("Network", "شبکه"),
|
||||
("Enable RDP", "RDP فعال شدن"),
|
||||
("Pin Toolbar", "سجاق کردن نوار ابزار"),
|
||||
("Unpin Toolbar", "خروج از حالت سجاق نوار ابزار"),
|
||||
("Recording", "در حال ضبط"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Écraser"),
|
||||
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
|
||||
("Quit", "Quitter"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/fr/manual/mac/#enable-permissions"),
|
||||
("Help", "Aider"),
|
||||
("Failed", "échouer"),
|
||||
("Succeeded", "Succès"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Périphérique source audio"),
|
||||
("Use IP Whitelisting", "Utiliser une liste blanche d'IP"),
|
||||
("Network", "Réseau"),
|
||||
("Enable RDP", "Activer connection RDP"),
|
||||
("Pin Toolbar", "Épingler la barre d'outil"),
|
||||
("Unpin Toolbar", "Détacher la barre d'outil"),
|
||||
("Recording", "Enregistrement"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", "Vérifier la disponibilité des mises à jour au démarrage"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Veuillez mettre à jour RustDesk Server Pro avec la version {} ou une version plus récente !"),
|
||||
("pull_group_failed_tip", "Échec de l'actualisation du groupe"),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Felülírás"),
|
||||
("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"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Audio bemeneti eszköz"),
|
||||
("Use IP Whitelisting", "Engedélyezési lista használata"),
|
||||
("Network", "Hálózat"),
|
||||
("Enable RDP", "RDP engedélyezése"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Felvétel"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Your Desktop", "Desktop Anda"),
|
||||
("desk_tip", "Desktop Anda dapat diakses dengan ID dan kata sandi ini."),
|
||||
("Password", "Kata sandi"),
|
||||
("Ready", "Siap"),
|
||||
("Ready", "Sudah siap"),
|
||||
("Established", "Didirikan"),
|
||||
("connecting_status", "Menghubungkan ke jaringan RustDesk..."),
|
||||
("Enable Service", "Aktifkan Layanan"),
|
||||
@ -19,7 +19,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Recent Sessions", "Sesi Terkini"),
|
||||
("Address Book", "Buku Alamat"),
|
||||
("Confirmation", "Konfirmasi"),
|
||||
("TCP Tunneling", "TCP Tunneling"),
|
||||
("TCP Tunneling", "Tunneling TCP"),
|
||||
("Remove", "Hapus"),
|
||||
("Refresh random password", "Perbarui kata sandi acak"),
|
||||
("Set your own password", "Tetapkan kata sandi Anda"),
|
||||
@ -172,10 +172,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Local Port", "Port Lokal"),
|
||||
("Local Address", "Alamat lokal"),
|
||||
("Change Local Port", "Ubah Port Lokal"),
|
||||
("setup_server_tip", "Sudah siap, Untuk mendapatkan koneksi yang lebih baik, disarankan untuk menginstal di server anda sendiri"),
|
||||
("setup_server_tip", "Untuk mendapatkan koneksi yang lebih baik, disarankan untuk menginstal di server anda sendiri"),
|
||||
("Too short, at least 6 characters.", "Terlalu pendek, setidaknya 6 karekter."),
|
||||
("The confirmation is not identical.", "Konfirmasi tidak identik."),
|
||||
("Permissions", "Izin"),
|
||||
("Permissions", "Perizinan"),
|
||||
("Accept", "Terima"),
|
||||
("Dismiss", "Hentikan"),
|
||||
("Disconnect", "Terputus"),
|
||||
@ -241,7 +241,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remove from Favorites", "Hapus dari favorit"),
|
||||
("Empty", "Kosong"),
|
||||
("Invalid folder name", "Nama folder tidak valid"),
|
||||
("Socks5 Proxy", "Proxy Socks5"),
|
||||
("Socks5 Proxy", "Proksi Socks5"),
|
||||
("Hostname", "Hostname"),
|
||||
("Discovered", "Telah ditemukan"),
|
||||
("install_daemon_tip", "Untuk memulai saat boot, Anda perlu menginstal system service."),
|
||||
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Ganti"),
|
||||
("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau ganti file ini?"),
|
||||
("Quit", "Keluar"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Bantuan"),
|
||||
("Failed", "Gagal"),
|
||||
("Succeeded", "Berhasil"),
|
||||
@ -359,17 +358,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Unlock Network Settings", "Buka Keamanan Pengaturan Jaringan"),
|
||||
("Server", "Server"),
|
||||
("Direct IP Access", "Akses IP Langsung"),
|
||||
("Proxy", "Proxy"),
|
||||
("Proxy", "Proksi"),
|
||||
("Apply", "Terapkan"),
|
||||
("Disconnect all devices?", "Putuskan sambungan semua perangkat?"),
|
||||
("Clear", "Bersihkan"),
|
||||
("Audio Input Device", "Input Perangkat Audio"),
|
||||
("Use IP Whitelisting", "Gunakan daftar IP yang diizinkan"),
|
||||
("Network", "Jaringan"),
|
||||
("Enable RDP", "Aktifkan RDP"),
|
||||
("Pin Toolbar", "Sematkan Toolbar"),
|
||||
("Unpin Toolbar", "Batal sematkan Toolbar"),
|
||||
("Recording", "Sedang Merekam"),
|
||||
("Recording", "Perekaman"),
|
||||
("Directory", "Direktori"),
|
||||
("Automatically record incoming sessions", "Secara otomatis merekam sesi masuk"),
|
||||
("Change", "Ubah"),
|
||||
@ -377,9 +375,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", "Hentikan sesi perekaman"),
|
||||
("Enable Recording Session", "Aktifkan Sesi Perekaman"),
|
||||
("Allow recording session", "Izinkan sesi perekaman"),
|
||||
("Enable LAN Discovery", "Aktifkan Penemuan LAN"),
|
||||
("Deny LAN Discovery", "Tolak Penemuan LAN"),
|
||||
("Write a message", "Menulis pesan"),
|
||||
("Enable LAN Discovery", "Aktifkan Pencarian Jaringan Lokal (LAN)"),
|
||||
("Deny LAN Discovery", "Tolak Pencarian Jaringan Lokal (LAN)"),
|
||||
("Write a message", "Tulis pesan"),
|
||||
("Prompt", ""),
|
||||
("Please wait for confirmation of UAC...", "Harap tunggu konfirmasi UAC"),
|
||||
("elevated_foreground_window_tip", "Jendela remote desktop ini memerlukan hak akses khusus, jadi anda tidak bisa menggunakan mouse dan keyboard untuk sementara. Anda bisa meminta pihak pengguna yang diremote untuk menyembunyikan jendela ini atau klik tombol elevasi di jendela pengaturan koneksi. Untuk menghindari masalah ini, direkomendasikan untuk menginstall aplikasi secara permanen"),
|
||||
@ -403,9 +401,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept sessions via click", "Izinkan sesi dengan klik"),
|
||||
("Accept sessions via both", "Izinkan sesi dengan keduanya"),
|
||||
("Please wait for the remote side to accept your session request...", "Harap tunggu pihak pengguna remote untuk menerima permintaan sesi..."),
|
||||
("One-time Password", "Kata sandi satu kali"),
|
||||
("Use one-time password", "Gunakan kata sandi satu kali"),
|
||||
("One-time password length", "Panjang kata sandi satu kali pakai"),
|
||||
("One-time Password", "Kata sandi sekali pakai"),
|
||||
("Use one-time password", "Gunakan kata sandi sekali pakai"),
|
||||
("One-time password length", "Panjang kata sandi sekali pakai"),
|
||||
("Request access to your device", "Permintaan akses ke perangkat ini"),
|
||||
("Hide connection management window", "Sembunyikan jendela pengaturan koneksi"),
|
||||
("hide_cm_tip", "Izinkan untuk menyembunyikan hanya jika menerima sesi melalui kata sandi dan menggunakan kata sandi permanen"),
|
||||
@ -459,7 +457,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Resolusi"),
|
||||
("No transfers in progress", "Tidak ada transfer data yang sedang berlangsung"),
|
||||
("Set one-time password length", "Atur panjang kata sandi satu kali pakai"),
|
||||
("Set one-time password length", "Atur panjang kata sandi sekali pakai"),
|
||||
("install_cert_tip", "Install sertifikat RustDesk"),
|
||||
("confirm_install_cert_tip", "Ini adalah sertifikat pengujian RustDesk, yang dapat dipercaya. Sertifikat ini akan digunakan untuk menginstal driver RustDesk saat diperlukan"),
|
||||
("RDP Settings", "Pengaturan RDP"),
|
||||
@ -471,7 +469,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Your Device", "Perangkat anda"),
|
||||
("empty_recent_tip", "Tidak ada sesi terbaru!"),
|
||||
("empty_favorite_tip", "Belum ada rekan favorit?\nTemukan seseorang untuk terhubung dan tambahkan ke favorit!"),
|
||||
("empty_lan_tip", "Sepertinya kami belum menemukan rekan"),
|
||||
("empty_lan_tip", "Sepertinya kami belum memiliki rekan"),
|
||||
("empty_address_book_tip", "Tampaknya saat ini tidak ada rekan yang terdaftar dalam buku alamat Anda"),
|
||||
("eg: admin", "contoh: admin"),
|
||||
("Empty Username", "Nama pengguna kosong"),
|
||||
@ -519,8 +517,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Open", "Buka"),
|
||||
("logout_tip", "Apakah Anda yakin ingin keluar?"),
|
||||
("Service", "Service"),
|
||||
("Start", "Mulai"),
|
||||
("Stop", "Berhenti"),
|
||||
("Start", "Jalankan"),
|
||||
("Stop", "Hentikan"),
|
||||
("exceed_max_devices", "Anda telah mencapai jumlah maksimal perangkat yang dikelola"),
|
||||
("Sync with recent sessions", "Sinkronkan dengan sesi terbaru"),
|
||||
("Sort tags", "Urutkan tag"),
|
||||
@ -552,10 +550,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("I Agree", "Saya setuju"),
|
||||
("Decline", "Tolak"),
|
||||
("Timeout in minutes", "Batasan Waktu dalam Menit"),
|
||||
("auto_disconnect_option_tip", "Secara otomatis akan menutup sesi ketika pengguna tidak beraktivitas"),
|
||||
("auto_disconnect_option_tip", "Secara otomatis akan menutup sesi ketika tidak ada aktivitas"),
|
||||
("Connection failed due to inactivity", "Secara otomatis akan terputus ketik tidak ada aktivitas."),
|
||||
("Check for software update on startup", "Periksa pembaruan aplikasi saat sistem dinyalakan."),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Silahkan perbarui RustDesk Server Pro ke versi {} atau yang lebih baru!"),
|
||||
("pull_group_failed_tip", "Gagal memperbarui grup"),
|
||||
("Filter by intersection", "Filter berdasarkan persilangan"),
|
||||
("Remove wallpaper during incoming sessions", "Hilangkan latar dinding ketika ada sesi yang masuk"),
|
||||
("Test", "Tes"),
|
||||
("switch_display_elevated_connections_tip", "Pada mode elevasi, jika terdapat beberapa tampilan yang aktif, maka tidak diizinkan berpindah ke yang bukan tampilan utama, silahkan coba lagi setelah proses instalasi jika kamu ingin melakukan kontrol ke tampilan layar lainnya"),
|
||||
("display_is_plugged_out_msg", "Layar terputus, pindah ke layar pertama"),
|
||||
("No displays", "Tidak ada tampilan"),
|
||||
("elevated_switch_display_msg", "Pindah ke tampilan utama, pada mode elevasi, pengggunaan lebih dari satu layar tidak diizinkan"),
|
||||
("Open in new window", "Buka di jendela baru"),
|
||||
("Show displays as individual windows", "Tampilkan layar sebagai jendela terpisah"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -179,8 +179,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept", "Accetta"),
|
||||
("Dismiss", "Rifiuta"),
|
||||
("Disconnect", "Disconnetti"),
|
||||
("Allow using keyboard and mouse", "Consenti l'uso di tastiera e mouse"),
|
||||
("Allow using clipboard", "Consenti l'uso degli appunti"),
|
||||
("Allow using keyboard and mouse", "Consenti uso tastiera e mouse"),
|
||||
("Allow using clipboard", "Consenti uso degli appunti"),
|
||||
("Allow hearing sound", "Consenti la riproduzione dell'audio"),
|
||||
("Allow file copy and paste", "Consenti copia e incolla di file"),
|
||||
("Connected", "Connesso"),
|
||||
@ -188,12 +188,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Relayed and encrypted connection", "Connessione tramite relay e cifrata"),
|
||||
("Direct and unencrypted connection", "Connessione diretta e non cifrata"),
|
||||
("Relayed and unencrypted connection", "Connessione tramite relay e non cifrata"),
|
||||
("Enter Remote ID", "Inserisci l'ID remoto"),
|
||||
("Enter Remote ID", "Inserisci ID remoto"),
|
||||
("Enter your password", "Inserisci la password"),
|
||||
("Logging in...", "Autenticazione..."),
|
||||
("Enable RDP session sharing", "Abilita condivisione sessione RDP"),
|
||||
("Auto Login", "Accesso automatico"),
|
||||
("Enable Direct IP Access", "Abilita l'accesso diretto tramite IP"),
|
||||
("Enable Direct IP Access", "Abilita accesso diretto tramite IP"),
|
||||
("Rename", "Rinomina"),
|
||||
("Space", "Spazio"),
|
||||
("Create Desktop Shortcut", "Crea collegamento sul desktop"),
|
||||
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Sovrascrivi"),
|
||||
("This file exists, skip or overwrite this file?", "Questo file esiste, vuoi ignorarlo o sovrascrivere questo file?"),
|
||||
("Quit", "Esci"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Aiuto"),
|
||||
("Failed", "Fallito"),
|
||||
("Succeeded", "Completato"),
|
||||
@ -406,7 +405,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("One-time Password", "Password monouso"),
|
||||
("Use one-time password", "Usa password monouso"),
|
||||
("One-time password length", "Lunghezza password monouso"),
|
||||
("Request access to your device", "Richiedi l'accesso al dispositivo"),
|
||||
("Request access to your device", "Richiedi accesso al dispositivo"),
|
||||
("Hide connection management window", "Nascondi la finestra di gestione delle connessioni"),
|
||||
("hide_cm_tip", "Permetti di nascondere solo se si accettano sessioni con password permanente"),
|
||||
("wayland_experiment_tip", "Il supporto Wayland è in fase sperimentale, se vuoi un accesso stabile usa X11."),
|
||||
@ -421,14 +420,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("software_render_tip", "Se nel computer con Linux è presente una scheda grafica Nvidia e la finestra remota si chiude immediatamente dopo la connessione, installa il nuovo driver open source e usa il rendering software.\nPotrebbe essere necessario un riavvio del programma."),
|
||||
("Always use software rendering", "Usa sempre rendering software"),
|
||||
("config_input", "Per controllare il desktop remoto con la tastiera, è necessario concedere le autorizzazioni a RustDesk 'Monitoraggio input'."),
|
||||
("config_microphone", "Per poter chiamare, è necessario concedere l'autorizzazione a RustDesk 'Registra audio'."),
|
||||
("config_microphone", "Per poter chiamare, è necessario concedere l'autorizzazione 'Registra audio' a RustDesk."),
|
||||
("request_elevation_tip", "Se c'è qualcuno nel lato remoto è possibile richiedere l'elevazione."),
|
||||
("Wait", "Attendi"),
|
||||
("Elevation Error", "Errore durante l'elevazione dei diritti"),
|
||||
("Ask the remote user for authentication", "Chiedi l'autenticazione all'utente remoto"),
|
||||
("Elevation Error", "Errore durante elevazione dei diritti"),
|
||||
("Ask the remote user for authentication", "Chiedi autenticazione all'utente remoto"),
|
||||
("Choose this if the remote account is administrator", "Scegli questa opzione se l'account remoto è amministratore"),
|
||||
("Transmit the username and password of administrator", "Trasmetti il nome utente e la password dell'amministratore"),
|
||||
("still_click_uac_tip", "Richiedi ancora che l'utente remoto faccia clic su OK nella finestra UAC dell'esecuzione di RustDesk."),
|
||||
("still_click_uac_tip", "Richiedi ancora che l'utente remoto selezioni 'OK' nella finestra UAC dell'esecuzione di RustDesk."),
|
||||
("Request Elevation", "Richiedi elevazione dei diritti"),
|
||||
("wait_accept_uac_tip", "Attendi che l'utente remoto accetti la finestra di dialogo UAC."),
|
||||
("Elevate successfully", "Elevazione dei diritti effettuata correttamente"),
|
||||
@ -552,10 +551,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("I Agree", "Accetto"),
|
||||
("Decline", "Non accetto"),
|
||||
("Timeout in minutes", "Timeout in minuti"),
|
||||
("auto_disconnect_option_tip", ""),
|
||||
("auto_disconnect_option_tip", "Chiudi automaticamente sessioni in entrata in caso di inattività utente"),
|
||||
("Connection failed due to inactivity", "Connessione non riuscita a causa di inattività"),
|
||||
("Check for software update on startup", "All'avvio verifica presenza aggiornamenti programma"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Aggiorna RustDesk Server Pro alla versione {} o successiva!"),
|
||||
("pull_group_failed_tip", "Impossibile aggiornare il gruppo"),
|
||||
("Filter by intersection", "Filtra per incrocio"),
|
||||
("Remove wallpaper during incoming sessions", "Rimuovi lo sfondo durante le sessioni in entrata"),
|
||||
("Test", "Test"),
|
||||
("switch_display_elevated_connections_tip", "Nella modalità elevata quando sono presenti più connessioni non è supportato il passaggio allo schermo non primario. Se vuoi controllare più schermi riprova dopo l'installazione."),
|
||||
("display_is_plugged_out_msg", "Lo schermo è scollegato, passo al primo schermo."),
|
||||
("No displays", "Nessuno schermo"),
|
||||
("elevated_switch_display_msg", "Passo allo schermo principale perché in modalità elevata non sono supportati più schermi."),
|
||||
("Open in new window", "Apri in una nuova finestra"),
|
||||
("Show displays as individual windows", "Visualizza schermi come finestre individuali"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "上書き"),
|
||||
("This file exists, skip or overwrite this file?", "このファイルは存在しています。スキップするか上書きしますか?"),
|
||||
("Quit", "終了"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), // @TODO: Update url when someone translates the docum"),
|
||||
("Help", "ヘルプ"),
|
||||
("Failed", "失敗"),
|
||||
("Succeeded", "成功"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", ""),
|
||||
("Use IP Whitelisting", ""),
|
||||
("Network", ""),
|
||||
("Enable RDP", ""),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", ""),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "덮어쓰기"),
|
||||
("This file exists, skip or overwrite this file?", "해당 파일이 이미 존재합니다, 넘어가거나 덮어쓰시겠습니까?"),
|
||||
("Quit", "종료"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "지원"),
|
||||
("Failed", "실패"),
|
||||
("Succeeded", "성공"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", ""),
|
||||
("Use IP Whitelisting", ""),
|
||||
("Network", ""),
|
||||
("Enable RDP", ""),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", ""),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Үстінен қайта жазу"),
|
||||
("This file exists, skip or overwrite this file?", "Бұл файыл бар, өткізіп жіберу әлде үстінен қайта жазу керек пе?"),
|
||||
("Quit", "Шығу"),
|
||||
("doc_mac_permission", ""),
|
||||
("Help", "Көмек"),
|
||||
("Failed", "Сәтсіз"),
|
||||
("Succeeded", "Сәтті"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", ""),
|
||||
("Use IP Whitelisting", ""),
|
||||
("Network", ""),
|
||||
("Enable RDP", ""),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", ""),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Perrašyti"),
|
||||
("This file exists, skip or overwrite this file?", "Šis failas egzistuoja, praleisti arba perrašyti šį failą?"),
|
||||
("Quit", "Išeiti"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/"),
|
||||
("Help", "Pagalba"),
|
||||
("Failed", "Nepavyko"),
|
||||
("Succeeded", "Pavyko"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Garso įvestis"),
|
||||
("Use IP Whitelisting", "Naudoti patikimą IP sąrašą"),
|
||||
("Network", "Tinklas"),
|
||||
("Enable RDP", "Įgalinti RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Įrašymas"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ID Server", "ID serveris"),
|
||||
("Relay Server", "Releja serveris"),
|
||||
("API Server", "API serveris"),
|
||||
("Key", "Atslēga"),
|
||||
("invalid_http", "jāsākas ar http:// vai https://"),
|
||||
("Invalid IP", "Nederīga IP"),
|
||||
("Invalid format", "Nederīgs formāts"),
|
||||
@ -296,8 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Pārrakstīt"),
|
||||
("This file exists, skip or overwrite this file?", "Šis fails pastāv, izlaist vai pārrakstīt šo failu?"),
|
||||
("Quit", "Iziet"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
||||
("Help", "Palīdzība"),
|
||||
("Failed", "Neizdevās"),
|
||||
("Succeeded", "Izdevās"),
|
||||
@ -368,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Audio ievades ierīce"),
|
||||
("Use IP Whitelisting", "Izmantot balto IP sarakstu"),
|
||||
("Network", "Tīkls"),
|
||||
("Enable RDP", "Iespējot RDP"),
|
||||
("Pin Toolbar", "Piespraust rīkjoslu"),
|
||||
("Unpin Toolbar", "Atspraust rīkjoslu"),
|
||||
("Recording", "Ierakstīšana"),
|
||||
@ -481,7 +477,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Me", "Es"),
|
||||
("identical_file_tip", "Šis fails ir identisks sesijas failam."),
|
||||
("show_monitors_tip", "Rādīt monitorus rīkjoslā"),
|
||||
("enter_rustdesk_passwd_tip", "Ievadiet RustDesk paroli"),
|
||||
("View Mode", "Skatīšanas režīms"),
|
||||
("login_linux_tip", "Jums ir jāpiesakās attālajā Linux kontā, lai iespējotu X darbvirsmas sesiju"),
|
||||
("verify_rustdesk_password_tip", "Pārbaudīt RustDesk paroli"),
|
||||
@ -560,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", "Startējot pārbaudīt, vai nav programmatūras atjauninājumu"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Lūdzu, jauniniet RustDesk Server Pro uz versiju {} vai jaunāku!"),
|
||||
("pull_group_failed_tip", "Neizdevās atsvaidzināt grupu"),
|
||||
("Filter by intersection", "Filtrēt pēc krustpunkta"),
|
||||
("Remove wallpaper during incoming sessions", "Noņemt fona tapeti ienākošo sesiju laikā"),
|
||||
("Test", "Pārbaudīt"),
|
||||
("switch_display_elevated_connections_tip", "Pārslēgšanās uz ne primāro displeju netiek atbalstīta paaugstinātajā režīmā, ja ir vairāki savienojumi. Lūdzu, mēģiniet vēlreiz pēc instalēšanas, ja vēlaties kontrolēt vairākus displejus."),
|
||||
("display_is_plugged_out_msg", "Displejs ir atvienots, pārslēdzieties uz pirmo displeju."),
|
||||
("No displays", "Nav displeju"),
|
||||
("elevated_switch_display_msg", "Pārslēdzieties uz primāro displeju, jo paaugstinātajā režīmā netiek atbalstīti vairāki displeji."),
|
||||
("Open in new window", "Atvērt jaunā logā"),
|
||||
("Show displays as individual windows", "Rādīt displejus kā atsevišķus logus"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("TCP Tunneling", "TCP Tunneling"),
|
||||
("Remove", "Verwijder"),
|
||||
("Refresh random password", "Vernieuw willekeurig wachtwoord"),
|
||||
("Set your own password", "Stel je eigen wachtwoord in"),
|
||||
("Set your own password", "Stel uw eigen wachtwoord in"),
|
||||
("Enable Keyboard/Mouse", "Toetsenbord/Muis Inschakelen"),
|
||||
("Enable Clipboard", "Klembord Inschakelen"),
|
||||
("Enable File Transfer", "Bestandsoverdracht Inschakelen"),
|
||||
@ -72,7 +72,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please enter your password", "Geef uw wachtwoord in"),
|
||||
("Remember password", "Wachtwoord onthouden"),
|
||||
("Wrong Password", "Verkeerd wachtwoord"),
|
||||
("Do you want to enter again?", "Wil je opnieuw ingeven?"),
|
||||
("Do you want to enter again?", "Wilt u het opnieuw invoeren?"),
|
||||
("Connection Error", "Fout bij verbinding"),
|
||||
("Error", "Fout"),
|
||||
("Reset by the peer", "Reset door de peer"),
|
||||
@ -99,12 +99,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Properties", "Eigenschappen"),
|
||||
("Multi Select", "Meervoudig selecteren"),
|
||||
("Select All", "Selecteer Alle"),
|
||||
("Unselect All", "Deselecteer alles"),
|
||||
("Unselect All", "De-selecteer alles"),
|
||||
("Empty Directory", "Lege Map"),
|
||||
("Not an empty directory", "Geen Lege Map"),
|
||||
("Are you sure you want to delete this file?", "Weet je zeker dat je dit bestand wilt verwijderen?"),
|
||||
("Are you sure you want to delete this empty directory?", "Weet je zeker dat je deze lege map wilt verwijderen?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Weet je zeker dat je het bestand uit deze map wilt verwijderen?"),
|
||||
("Not an empty directory", "Geen lege map"),
|
||||
("Are you sure you want to delete this file?", "Weet u zeker dat u dit bestand wilt verwijderen?"),
|
||||
("Are you sure you want to delete this empty directory?", "Weet u zeker dat u deze lege map wilt verwijderen?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Weet u zeker dat u het bestand uit deze map wilt verwijderen?"),
|
||||
("Do this for all conflicts", "Doe dit voor alle conflicten"),
|
||||
("This is irreversible!", "Dit is onomkeerbaar!"),
|
||||
("Deleting", "Verwijderen"),
|
||||
@ -145,13 +145,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to make direct connection to remote desktop", "Onmogelijk direct verbinding te maken met extern bureaublad"),
|
||||
("Set Password", "Wachtwoord Instellen"),
|
||||
("OS Password", "OS Wachtwoord"),
|
||||
("install_tip", "Je gebruikt een niet geinstalleerde versie. Als gevolg van UAC-beperkingen is het in sommige gevallen niet mogelijk om als controleterminal de muis en het toetsenbord te bedienen of het scherm over te nemen. Klik op de knop hieronder om RustDesk op het systeem te installeren om het bovenstaande probleem te voorkomen."),
|
||||
("install_tip", "U gebruikt een niet geïnstalleerde versie. Als gevolg van UAC-beperkingen is het in sommige gevallen niet mogelijk om als controleterminal de muis en het toetsenbord te bedienen of het scherm over te nemen. Klik op de knop hieronder om RustDesk op het systeem te installeren om het bovenstaande probleem te voorkomen."),
|
||||
("Click to upgrade", "Klik voor upgrade"),
|
||||
("Click to download", "Klik om te downloaden"),
|
||||
("Click to update", "Klik om bij te werken"),
|
||||
("Configure", "Configureren"),
|
||||
("config_acc", "Om je bureaublad op afstand te kunnen bedienen, moet je RustDesk \"toegankelijkheid\" toestemming geven."),
|
||||
("config_screen", "Om toegang te krijgen tot het externe bureaublad, moet je RustDesk de toestemming \"schermregistratie\" geven."),
|
||||
("config_acc", "Om uw bureaublad op afstand te kunnen bedienen, moet u RustDesk \"toegankelijkheid\" toestemming geven."),
|
||||
("config_screen", "Om toegang te krijgen tot het externe bureaublad, moet u RustDesk de toestemming \"schermregistratie\" geven."),
|
||||
("Installing ...", "Installeren ..."),
|
||||
("Install", "Installeer"),
|
||||
("Installation", "Installatie"),
|
||||
@ -182,7 +182,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Allow using keyboard and mouse", "Gebruik toetsenbord en muis toestaan"),
|
||||
("Allow using clipboard", "Gebruik klembord toestaan"),
|
||||
("Allow hearing sound", "Geluidsweergave toestaan"),
|
||||
("Allow file copy and paste", "Kopieren en plakken van bestanden toestaan"),
|
||||
("Allow file copy and paste", "Kopiëren en plakken van bestanden toestaan"),
|
||||
("Connected", "Verbonden"),
|
||||
("Direct and encrypted connection", "Directe en versleutelde verbinding"),
|
||||
("Relayed and encrypted connection", "Doorgeschakelde en versleutelde verbinding"),
|
||||
@ -244,11 +244,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Socks5 Proxy", "Socks5 Proxy"),
|
||||
("Hostname", "Hostnaam"),
|
||||
("Discovered", "Ontdekt"),
|
||||
("install_daemon_tip", "Om bij het opstarten van de computer te kunnen beginnen, moet je de systeemdienst installeren."),
|
||||
("install_daemon_tip", "Om bij het opstarten van de computer te kunnen beginnen, moet u de systeemservice installeren."),
|
||||
("Remote ID", "Externe ID"),
|
||||
("Paste", "Plakken"),
|
||||
("Paste here?", "Hier plakken"),
|
||||
("Are you sure to close the connection?", "Weet je zeker dat je de verbinding wilt sluiten?"),
|
||||
("Are you sure to close the connection?", "Weet u zeker dat u de verbinding wilt sluiten?"),
|
||||
("Download new version", "Download nieuwe versie"),
|
||||
("Touch mode", "Aanraak modus"),
|
||||
("Mouse mode", "Muismodus"),
|
||||
@ -280,11 +280,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Capture", "Audio Opnemen"),
|
||||
("File Connection", "Bestandsverbinding"),
|
||||
("Screen Connection", "Schermverbinding"),
|
||||
("Do you accept?", "Sta je toe?"),
|
||||
("Do you accept?", "Geeft u toestemming?"),
|
||||
("Open System Setting", "Systeeminstelling Openen"),
|
||||
("How to get Android input permission?", "Hoe krijg ik Android invoer toestemming?"),
|
||||
("android_input_permission_tip1", "Om ervoor te zorgen dat een extern apparaat uw Android-apparaat kan besturen via muis of aanraking, moet u RustDesk toestaan om de \"Toegankelijkheid\" service te gebruiken."),
|
||||
("android_input_permission_tip2", "Ga naar de volgende pagina met systeeminstellingen, zoek en ga naar [Geinstalleerde Services], schakel de service [RustDesk Input] in."),
|
||||
("android_input_permission_tip2", "Ga naar de volgende pagina met systeeminstellingen, zoek en ga naar [Geïnstalleerde Services], schakel de service [RustDesk Input] in."),
|
||||
("android_new_connection_tip", "Er is een nieuw controleverzoek binnengekomen, dat uw huidige apparaat wil controleren."),
|
||||
("android_service_will_start_tip", "Als u \"Schermopname\" inschakelt, wordt de service automatisch gestart, zodat andere apparaten een verbinding met uw apparaat kunnen aanvragen."),
|
||||
("android_stop_service_tip", "Het sluiten van de service zal automatisch alle gemaakte verbindingen sluiten."),
|
||||
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Overschrijven"),
|
||||
("This file exists, skip or overwrite this file?", "Dit bestand bestaat reeds, overslaan of overschrijven?"),
|
||||
("Quit", "Afsluiten"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
||||
("Failed", "Mislukt"),
|
||||
("Succeeded", "Geslaagd"),
|
||||
@ -324,7 +323,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable Remote Restart", "Schakel Herstart op afstand in"),
|
||||
("Allow remote restart", "Opnieuw Opstarten op afstand toestaan"),
|
||||
("Restart Remote Device", "Apparaat op afstand herstarten"),
|
||||
("Are you sure you want to restart", "Weet je zeker dat je wilt herstarten"),
|
||||
("Are you sure you want to restart", "Weet u zeker dat u wilt herstarten"),
|
||||
("Restarting Remote Device", "Apparaat op afstand herstarten"),
|
||||
("remote_restarting_tip", "Apparaat op afstand wordt opnieuw opgestart, sluit dit bericht en maak na een ogenblik opnieuw verbinding met het permanente wachtwoord."),
|
||||
("Copied", "Gekopieerd"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Audio-invoerapparaat"),
|
||||
("Use IP Whitelisting", "Gebruik een witte lijst van IP-adressen"),
|
||||
("Network", "Netwerk"),
|
||||
("Enable RDP", "Zet RDP aan"),
|
||||
("Pin Toolbar", "Werkbalk Vastzetten"),
|
||||
("Unpin Toolbar", "Werkbalk Losmaken"),
|
||||
("Recording", "Opnemen"),
|
||||
@ -390,7 +388,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Full Access", "Volledige Toegang"),
|
||||
("Screen Share", "Scherm Delen"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vereist Ubuntu 21.04 of een hogere versie."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vereist een hogere versie van Linux distro. Probeer X11 desktop of verander je OS."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vereist een hogere versie van Linux distro. Probeer X11 desktop of verander van OS."),
|
||||
("JumpLink", "JumpLink"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Selecteer het scherm dat moet worden gedeeld (Bediening aan de kant van de peer)."),
|
||||
("Show RustDesk", "Toon RustDesk"),
|
||||
@ -405,11 +403,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please wait for the remote side to accept your session request...", "Wacht tot de andere kant uw sessieverzoek accepteert..."),
|
||||
("One-time Password", "Eenmalig Wachtwoord"),
|
||||
("Use one-time password", "Gebruik een eenmalig Wachtwoord"),
|
||||
("One-time password length", "Eenmalig Wachtwoord lengre"),
|
||||
("One-time password length", "Eenmalig Wachtwoordlengte"),
|
||||
("Request access to your device", "Toegang tot uw toestel aanvragen"),
|
||||
("Hide connection management window", "Verberg het venster voor verbindingsbeheer"),
|
||||
("hide_cm_tip", "Dit kan alleen als de toegang via een permanent wachtwoord verloopt."),
|
||||
("wayland_experiment_tip", "Wayland ondersteuning is slechts experimenteel. Gebruik alsjeblieft X11 als je onbeheerde toegang nodig hebt."),
|
||||
("wayland_experiment_tip", "Wayland ondersteuning is slechts experimenteel. Gebruik alsjeblieft X11 als u onbeheerde toegang nodig hebt."),
|
||||
("Right click to select tabs", "Rechts klikken om tabbladen te selecteren"),
|
||||
("Skipped", "Overgeslagen"),
|
||||
("Add to Address Book", "Toevoegen aan Adresboek"),
|
||||
@ -438,10 +436,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("special character", "speciaal teken"),
|
||||
("length>=8", "lengte>=8"),
|
||||
("Weak", "Zwak"),
|
||||
("Medium", "Midelmatig"),
|
||||
("Medium", "Middelmatig"),
|
||||
("Strong", "Sterk"),
|
||||
("Switch Sides", "Wissel van kant"),
|
||||
("Please confirm if you want to share your desktop?", "bevestig als je je bureaublad wilt delen?"),
|
||||
("Please confirm if you want to share your desktop?", "Bevestig als u uw bureaublad wilt delen?"),
|
||||
("Display", "Weergave"),
|
||||
("Default View Style", "Standaard Weergave Stijl"),
|
||||
("Default Scroll Style", "Standaard Scroll Stijl"),
|
||||
@ -455,7 +453,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Text chat", "Tekst chat"),
|
||||
("Stop voice call", "Stop spraakoproep"),
|
||||
("relay_hint_tip", "Indien een directe verbinding niet mogelijk is, kunt u proberen verbinding te maken via een Relay Server. \nAls u bij de eerste poging een relaisverbinding tot stand wilt brengen, kunt u het achtervoegsel \"/r\" toevoegen aan het ID of de optie \"Altijd verbinden via relaisserver\" selecteren op de externe terminal."),
|
||||
("Reconnect", "Herverbinden"),
|
||||
("Reconnect", "Opnieuw verbinden"),
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Resolutie"),
|
||||
("No transfers in progress", "Geen overdrachten in uitvoering"),
|
||||
@ -470,10 +468,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Maximize", "Maximaliseren"),
|
||||
("Your Device", "Uw Apparaat"),
|
||||
("empty_recent_tip", "Oeps, geen actuele situatie!\nTijd om een nieuwe te plannen."),
|
||||
("empty_favorite_tip", "Nog geen favoriete Station op afstand? Laat ons iemand vinden om mee te verbinden en voeg hem toe aan je favorieten!"),
|
||||
("empty_favorite_tip", "Nog geen favoriete Station op afstand? Laat ons iemand vinden om mee te verbinden en voeg hem toe aan uw favorieten!"),
|
||||
("empty_lan_tip", "Oh nee, het lijkt erop dat we nog geen extern station hebben ontdekt."),
|
||||
("empty_address_book_tip", "Oh jee, het lijkt erop dat er momenteel geen externe stations in je adresboek staan."),
|
||||
("eg: admin", "bijv: admin"),
|
||||
("empty_address_book_tip", "Oh jee, het lijkt erop dat er momenteel geen externe stations in uw adresboek staan."),
|
||||
("eg: admin", "bijvoorbeeld: admin"),
|
||||
("Empty Username", "Gebruikersnaam Leeg"),
|
||||
("Empty Password", "Wachtwoord Leeg"),
|
||||
("Me", "Ik"),
|
||||
@ -515,9 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("clipboard_wait_response_timeout_tip", "Time-out in afwachting van kopieer-antwoord."),
|
||||
("Incoming connection", "Inkomende verbinding"),
|
||||
("Outgoing connection", "Uitgaande verbinding"),
|
||||
("Exit", "Verlaten"),
|
||||
("Exit", "Afsluiten"),
|
||||
("Open", "Open"),
|
||||
("logout_tip", "Weet je zeker dat je je wilt afmelden?"),
|
||||
("logout_tip", "Weet u zeker dat u zich wilt afmelden?"),
|
||||
("Service", "Service"),
|
||||
("Start", "Start"),
|
||||
("Stop", "Stop"),
|
||||
@ -527,7 +525,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Open connection in new tab", "Verbinding openen in een nieuw tabblad"),
|
||||
("Move tab to new window", "Tabblad verplaatsen naar nieuw venster"),
|
||||
("Can not be empty", "Mag niet leeg zijn"),
|
||||
("Already exists", "Bestaat reeds"),
|
||||
("Already exists", "Bestaat al"),
|
||||
("Change Password", "Wijzig Wachtwoord"),
|
||||
("Refresh Password", "Wachtwoord Vernieuwen"),
|
||||
("ID", "ID"),
|
||||
@ -541,21 +539,30 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Change Color", "Kleur Aanpassen"),
|
||||
("Primary Color", "Hoofdkleur"),
|
||||
("HSV Color", "HSV Kleur"),
|
||||
("Installation Successful!", ""),
|
||||
("Installation failed!", ""),
|
||||
("Reverse mouse wheel", ""),
|
||||
("{} sessions", ""),
|
||||
("scam_title", ""),
|
||||
("scam_text1", ""),
|
||||
("scam_text2", ""),
|
||||
("Don't show again", ""),
|
||||
("I Agree", ""),
|
||||
("Decline", ""),
|
||||
("Timeout in minutes", ""),
|
||||
("auto_disconnect_option_tip", ""),
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Installation Successful!", "Installatie geslaagd!"),
|
||||
("Installation failed!", "Installatie mislukt!"),
|
||||
("Reverse mouse wheel", "Muiswiel omkeren"),
|
||||
("{} sessions", "{} sessies"),
|
||||
("scam_title", "U wordt misschien opgelicht!"),
|
||||
("scam_text1", "Als u aan de telefoon bent met iemand die u NIET kent EN VERTROUWT en die u heeft gevraagd om RustDesk te gebruiken en de service te starten, ga dan niet verder en hang onmiddellijk op."),
|
||||
("scam_text2", "Het is waarschijnlijk een oplichter die probeert uw geld of andere privégegevens te stelen."),
|
||||
("Don't show again", "Niet opnieuw tonen"),
|
||||
("I Agree", "Ik ga akkoord"),
|
||||
("Decline", "Afwijzen"),
|
||||
("Timeout in minutes", "Time-out in minuten"),
|
||||
("auto_disconnect_option_tip", "Inkomende sessies automatisch sluiten bij inactiviteit van de gebruiker"),
|
||||
("Connection failed due to inactivity", "Automatisch verbinding verbroken wegens inactiviteit"),
|
||||
("Check for software update on startup", "Checken voor updates bij opstarten"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Upgrade RustDesk Server Pro naar versie {} of nieuwer!"),
|
||||
("pull_group_failed_tip", "Vernieuwen van groep mislukt"),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Status"),
|
||||
("Your Desktop", "Twój pulpit"),
|
||||
("desk_tip", "W celu połączenia się z tym urządzeniem należy użyć poniższego ID i hasła"),
|
||||
("desk_tip", "Aby połączyć się z tym urządzeniem, użyj poniższego ID i hasła"),
|
||||
("Password", "Hasło"),
|
||||
("Ready", "Gotowe"),
|
||||
("Established", "Nawiązano"),
|
||||
@ -244,10 +244,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Socks5 Proxy", "Proxy Socks5"),
|
||||
("Hostname", "Nazwa hosta"),
|
||||
("Discovered", "Wykryte"),
|
||||
("install_daemon_tip", "Podpowiedź instalacji daemona"),
|
||||
("install_daemon_tip", "By uruchomić RustDesk przy starcie systemu, musisz zainstalować usługę systemową."),
|
||||
("Remote ID", "Zdalne ID"),
|
||||
("Paste", "Wklej"),
|
||||
("Paste here?", "Wkleić tu?"),
|
||||
("Paste here?", "Wkleić tutaj?"),
|
||||
("Are you sure to close the connection?", "Czy na pewno chcesz zakończyć połączenie?"),
|
||||
("Download new version", "Pobierz nową wersję"),
|
||||
("Touch mode", "Tryb dotykowy"),
|
||||
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Nadpisz"),
|
||||
("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"),
|
||||
("Quit", "Zrezygnuj"),
|
||||
("doc_mac_permission", "doc_mac_permission"),
|
||||
("Help", "Pomoc"),
|
||||
("Failed", "Niepowodzenie"),
|
||||
("Succeeded", "Udało się"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Urządzenie wejściowe Audio"),
|
||||
("Use IP Whitelisting", "Użyj białej listy IP"),
|
||||
("Network", "Sieć"),
|
||||
("Enable RDP", "Włącz RDP"),
|
||||
("Pin Toolbar", "Przypnij pasek narzędzi"),
|
||||
("Unpin Toolbar", "Odepnij pasek narzędzi"),
|
||||
("Recording", "Nagrywanie"),
|
||||
@ -492,7 +490,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no_desktop_title_tip", "Żaden pulpit nie jest dostępny"),
|
||||
("no_desktop_text_tip", "Proszę zainstalować pulpit GNOME"),
|
||||
("No need to elevate", "Podniesienie uprawnień nie jest wymagane"),
|
||||
("System Sound", "Dźwięk Systemowy"),
|
||||
("System Sound", "Dźwięk systemowy"),
|
||||
("Default", "Domyślne"),
|
||||
("New RDP", "Nowe RDP"),
|
||||
("Fingerprint", "Sygnatura"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", "Sprawdź aktualizacje przy starcie programu"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Proszę zaktualizować RustDesk Server Pro do wersji {} lub nowszej!"),
|
||||
("pull_group_failed_tip", "Błąd odświeżania grup"),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", "Usuń tapetę podczas sesji przychodzących"),
|
||||
("Test", "Test"),
|
||||
("switch_display_elevated_connections_tip", "Przełączanie na ekran inny niż główny nie jest obsługiwane przy podniesionych uprawnieniach, gdy istnieje wiele połączeń. Jeśli chcesz sterować wieloma ekranami, należy zainstalować program."),
|
||||
("display_is_plugged_out_msg", "Ekran został odłączony, przełącz się na pierwszy ekran."),
|
||||
("No displays", "Brak ekranów"),
|
||||
("elevated_switch_display_msg", "Przełącz się na ekran główny, ponieważ wyświetlanie kilku ekranów nie jest obsługiwane przy podniesionych uprawnieniach."),
|
||||
("Open in new window", "Otwórz w nowym oknie"),
|
||||
("Show displays as individual windows", "Pokaż ekrany w osobnych oknach"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Substituir"),
|
||||
("This file exists, skip or overwrite this file?", "Este ficheiro já existe, ignorar ou substituir este ficheiro?"),
|
||||
("Quit", "Saída"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Ajuda"),
|
||||
("Failed", "Falhou"),
|
||||
("Succeeded", "Conseguiu"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", ""),
|
||||
("Use IP Whitelisting", ""),
|
||||
("Network", ""),
|
||||
("Enable RDP", ""),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", ""),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Substituir"),
|
||||
("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"),
|
||||
("Quit", "Sair"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Ajuda"),
|
||||
("Failed", "Falhou"),
|
||||
("Succeeded", "Sucesso"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Dispositivo de entrada de áudio"),
|
||||
("Use IP Whitelisting", "Utilizar lista de IPs confiáveis"),
|
||||
("Network", "Rede"),
|
||||
("Enable RDP", "Habilitar RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Gravando"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Suprascrie"),
|
||||
("This file exists, skip or overwrite this file?", "Fișier deja existent. Omite sau suprascrie?"),
|
||||
("Quit", "Ieși"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Ajutor"),
|
||||
("Failed", "Nereușit"),
|
||||
("Succeeded", "Reușit"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Dispozitiv de intrare audio"),
|
||||
("Use IP Whitelisting", "Folosește lista de IP-uri autorizate"),
|
||||
("Network", "Rețea"),
|
||||
("Enable RDP", "Activează RDP"),
|
||||
("Pin Toolbar", "Fixează bara de instrumente"),
|
||||
("Unpin Toolbar", "Detașează bara de instrumente"),
|
||||
("Recording", "Înregistrare"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Balanced", "Сбалансировано"),
|
||||
("Optimize reaction time", "Оптимальное время реакции"),
|
||||
("Custom", "Своё"),
|
||||
("Show remote cursor", "Показать удалённый курсор"),
|
||||
("Show remote cursor", "Показывать удалённый курсор"),
|
||||
("Show quality monitor", "Показать качество"),
|
||||
("Disable clipboard", "Отключить буфер обмена"),
|
||||
("Lock after session end", "Выход из учётной записи после завершения сеанса"),
|
||||
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Перезаписать"),
|
||||
("This file exists, skip or overwrite this file?", "Файл существует, пропустить или перезаписать его?"),
|
||||
("Quit", "Выйти"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/ru/manual/mac/#включение-разрешений"),
|
||||
("Help", "Помощь"),
|
||||
("Failed", "Не выполнено"),
|
||||
("Succeeded", "Выполнено"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Источник звука"),
|
||||
("Use IP Whitelisting", "Использовать белый список IP"),
|
||||
("Network", "Сеть"),
|
||||
("Enable RDP", "Включить RDP"),
|
||||
("Pin Toolbar", "Закрепить панель инструментов"),
|
||||
("Unpin Toolbar", "Открепить панель инструментов"),
|
||||
("Recording", "Запись"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", "Проверять обновления программы при запуске"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"),
|
||||
("pull_group_failed_tip", "Невозможно обновить группу"),
|
||||
("Filter by intersection", "Фильтровать по пересечению"),
|
||||
("Remove wallpaper during incoming sessions", "Удалять обои в сеансе"),
|
||||
("Test", "Тест"),
|
||||
("switch_display_elevated_connections_tip", "Переключение на неосновной дисплей не поддерживается в режиме повышенных прав при наличии нескольких подключений. Повторите попытку после установки, если хотите управлять несколькими дисплеями."),
|
||||
("display_is_plugged_out_msg", "Дисплей отключён, переключитесь на первый дисплей."),
|
||||
("No displays", "Нет дисплеев"),
|
||||
("elevated_switch_display_msg", "Переключитесь на основной дисплей, поскольку в режиме повышенных прав несколько дисплеев не поддерживаются."),
|
||||
("Open in new window", "Открыть в новом окне"),
|
||||
("Show displays as individual windows", "Показывать дисплеи в отдельных окнах"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Prepísať"),
|
||||
("This file exists, skip or overwrite this file?", "Preskočiť alebo prepísať existujúci súbor?"),
|
||||
("Quit", "Ukončiť"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Nápoveda"),
|
||||
("Failed", "Nepodarilo sa"),
|
||||
("Succeeded", "Podarilo sa"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", ""),
|
||||
("Use IP Whitelisting", ""),
|
||||
("Network", ""),
|
||||
("Enable RDP", ""),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", ""),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Prepiši"),
|
||||
("This file exists, skip or overwrite this file?", "Datoteka obstaja, izpusti ali prepiši?"),
|
||||
("Quit", "Izhod"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Pomoč"),
|
||||
("Failed", "Ni uspelo"),
|
||||
("Succeeded", "Uspelo"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Vhodna naprava za zvok"),
|
||||
("Use IP Whitelisting", "Omogoči seznam dovoljenih IP naslovov"),
|
||||
("Network", "Mreža"),
|
||||
("Enable RDP", "Omogoči RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Snemanje"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Përshkruaj"),
|
||||
("This file exists, skip or overwrite this file?", "Ky skedar ekziston , tejkalo ose përshkruaj këtë skedarë"),
|
||||
("Quit", "Hiq"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Ndihmë"),
|
||||
("Failed", "Deshtoi"),
|
||||
("Succeeded", "Sukses"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Pajisja e hyrjes audio"),
|
||||
("Use IP Whitelisting", "Përdor listën e bardhë IP"),
|
||||
("Network", "Rrjeti"),
|
||||
("Enable RDP", "Aktivizo RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Regjistrimi"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Prepiši preko"),
|
||||
("This file exists, skip or overwrite this file?", "Ova datoteka postoji, preskoči ili prepiši preko?"),
|
||||
("Quit", "Izlaz"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Pomoć"),
|
||||
("Failed", "Greška"),
|
||||
("Succeeded", "Uspešno"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Uređaj za ulaz zvuka"),
|
||||
("Use IP Whitelisting", "Koristi listu pouzdanih IP"),
|
||||
("Network", "Mreža"),
|
||||
("Enable RDP", "Dozvoli RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Snimanje"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Skriv över"),
|
||||
("This file exists, skip or overwrite this file?", "Filen finns redan, hoppa över eller skriv över filen?"),
|
||||
("Quit", "Avsluta"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Hjälp"),
|
||||
("Failed", "Misslyckades"),
|
||||
("Succeeded", "Lyckades"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Inmatningsenhet för ljud"),
|
||||
("Use IP Whitelisting", "Använd IP-Vitlistning"),
|
||||
("Network", "Nätverk"),
|
||||
("Enable RDP", "Aktivera RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Spelar in"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", ""),
|
||||
("This file exists, skip or overwrite this file?", ""),
|
||||
("Quit", ""),
|
||||
("doc_mac_permission", ""),
|
||||
("Help", ""),
|
||||
("Failed", ""),
|
||||
("Succeeded", ""),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", ""),
|
||||
("Use IP Whitelisting", ""),
|
||||
("Network", ""),
|
||||
("Enable RDP", ""),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", ""),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "เขียนทับ"),
|
||||
("This file exists, skip or overwrite this file?", "พบไฟล์ที่มีอยู่แล้ว ต้องการเขียนทับหรือไม่?"),
|
||||
("Quit", "ออก"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "ช่วยเหลือ"),
|
||||
("Failed", "ล้มเหลว"),
|
||||
("Succeeded", "สำเร็จ"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "อุปกรณ์รับอินพุทข้อมูลเสียง"),
|
||||
("Use IP Whitelisting", "ใช้งาน IP ไวท์ลิสต์"),
|
||||
("Network", "เครือข่าย"),
|
||||
("Enable RDP", "เปิดการใช้งาน RDP"),
|
||||
("Pin Toolbar", "ปักหมุดแถบเครื่องมือ"),
|
||||
("Unpin Toolbar", "ยกเลิกการปักหมุดแถบเครื่องมือ"),
|
||||
("Recording", "การบันทึก"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", "ตรวจสอบการอัปเดตโปรแกรมเมื่อเริ่มต้นใช้งาน"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "กรุณาอัปเดต Rustdesk Server Pro ไปยังเวอร์ชัน {} หรือใหม่กว่า!"),
|
||||
("pull_group_failed_tip", "การเรียกใช้งานกลุ่มล้มเหลว"),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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ış"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Yardım"),
|
||||
("Failed", "Arızalı"),
|
||||
("Succeeded", "başarılı"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Ses Giriş Aygıtı"),
|
||||
("Use IP Whitelisting", "IP Beyaz Listeyi Kullan"),
|
||||
("Network", "Ağ"),
|
||||
("Enable RDP", "RDP Aktif Et"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Recording", "Kayıt Ediliyor"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "取代"),
|
||||
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要略過或是取代此檔案嗎?"),
|
||||
("Quit", "退出"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/zh-tw/manual/mac/#啟用權限"),
|
||||
("Help", "說明"),
|
||||
("Failed", "失敗"),
|
||||
("Succeeded", "成功"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "音訊輸入裝置"),
|
||||
("Use IP Whitelisting", "只允許白名單上的 IP 進行連線"),
|
||||
("Network", "網路"),
|
||||
("Enable RDP", "允許 RDP 存取"),
|
||||
("Pin Toolbar", "釘選工具列"),
|
||||
("Unpin Toolbar", "取消釘選工具列"),
|
||||
("Recording", "錄製"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Перезаписати"),
|
||||
("This file exists, skip or overwrite this file?", "Цей файл існує, пропустити чи перезаписати файл?"),
|
||||
("Quit", "Вийти"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Допомога"),
|
||||
("Failed", "Не вдалося"),
|
||||
("Succeeded", "Успішно"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Пристрій введення звуку"),
|
||||
("Use IP Whitelisting", "Використовувати білий список IP"),
|
||||
("Network", "Мережа"),
|
||||
("Enable RDP", "Увімкнути RDP"),
|
||||
("Pin Toolbar", "Закріпити панель інструментів"),
|
||||
("Unpin Toolbar", "Відкріпити панель інструментів"),
|
||||
("Recording", "Запис"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Ghi đè"),
|
||||
("This file exists, skip or overwrite this file?", "Tệp tin này đã tồn tại, bạn có muốn bỏ qua hay ghi đè lên tệp tin này?"),
|
||||
("Quit", "Thoát"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Trợ giúp"),
|
||||
("Failed", "Thất bại"),
|
||||
("Succeeded", "Thành công"),
|
||||
@ -366,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Thiết bị âm thanh đầu vào"),
|
||||
("Use IP Whitelisting", "Dùng danh sách các IP cho phép"),
|
||||
("Network", "Mạng"),
|
||||
("Enable RDP", "Bật RDP"),
|
||||
("Pin Toolbar", "Ghim thanh công cụ"),
|
||||
("Unpin Toolbar", "Bỏ ghim thanh công cụ"),
|
||||
("Recording", "Đang ghi hình"),
|
||||
@ -557,5 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", ""),
|
||||
("Remove wallpaper during incoming sessions", ""),
|
||||
("Test", ""),
|
||||
("switch_display_elevated_connections_tip", ""),
|
||||
("display_is_plugged_out_msg", ""),
|
||||
("No displays", ""),
|
||||
("elevated_switch_display_msg", ""),
|
||||
("Open in new window", ""),
|
||||
("Show displays as individual windows", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ use desktop::Desktop;
|
||||
use hbb_common::config::CONFIG_OPTION_ALLOW_LINUX_HEADLESS;
|
||||
pub use hbb_common::platform::linux::*;
|
||||
use hbb_common::{
|
||||
allow_err, bail,
|
||||
allow_err,
|
||||
anyhow::anyhow,
|
||||
bail,
|
||||
config::Config,
|
||||
libc::{c_char, c_int, c_long, c_void},
|
||||
log,
|
||||
@ -26,6 +28,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use users::{get_user_by_name, os::unix::UserExt};
|
||||
use wallpaper;
|
||||
|
||||
type Xdo = *const c_void;
|
||||
|
||||
@ -1311,3 +1314,49 @@ NoDisplay=false
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct WallPaperRemover {
|
||||
old_path: String,
|
||||
old_path_dark: Option<String>, // ubuntu 22.04 light/dark theme have different uri
|
||||
}
|
||||
|
||||
impl WallPaperRemover {
|
||||
pub fn new() -> ResultType<Self> {
|
||||
let start = std::time::Instant::now();
|
||||
let old_path = wallpaper::get().map_err(|e| anyhow!(e.to_string()))?;
|
||||
let old_path_dark = wallpaper::get_dark().ok();
|
||||
if old_path.is_empty() && old_path_dark.clone().unwrap_or_default().is_empty() {
|
||||
bail!("already solid color");
|
||||
}
|
||||
wallpaper::set_from_path("").map_err(|e| anyhow!(e.to_string()))?;
|
||||
wallpaper::set_dark_from_path("").ok();
|
||||
log::info!(
|
||||
"created wallpaper remover, old_path:{:?}, old_path_dark:{:?}, elapsed:{:?}",
|
||||
old_path,
|
||||
old_path_dark,
|
||||
start.elapsed(),
|
||||
);
|
||||
Ok(Self {
|
||||
old_path,
|
||||
old_path_dark,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn support() -> bool {
|
||||
let desktop = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default();
|
||||
if wallpaper::gnome::is_compliant(&desktop) || desktop.as_str() == "XFCE" {
|
||||
return wallpaper::get().is_ok();
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WallPaperRemover {
|
||||
fn drop(&mut self) {
|
||||
allow_err!(wallpaper::set_from_path(&self.old_path).map_err(|e| anyhow!(e.to_string())));
|
||||
if let Some(old_path_dark) = &self.old_path_dark {
|
||||
allow_err!(wallpaper::set_dark_from_path(old_path_dark.as_str())
|
||||
.map_err(|e| anyhow!(e.to_string())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ pub const PA_SAMPLE_RATE: u32 = 48000;
|
||||
pub(crate) struct InstallingService; // please use new
|
||||
|
||||
impl InstallingService {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
pub fn new() -> Self {
|
||||
*INSTALLING_SERVICE.lock().unwrap() = true;
|
||||
Self
|
||||
|
@ -14,6 +14,7 @@ use hbb_common::{
|
||||
message_proto::Resolution,
|
||||
sleep, timeout, tokio,
|
||||
};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::OsString,
|
||||
@ -26,6 +27,7 @@ use std::{
|
||||
sync::{atomic::Ordering, Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use wallpaper;
|
||||
use winapi::{
|
||||
ctypes::c_void,
|
||||
shared::{minwindef::*, ntdef::NULL, windef::*, winerror::*},
|
||||
@ -2335,3 +2337,102 @@ fn get_license() -> Option<License> {
|
||||
}
|
||||
Some(lic)
|
||||
}
|
||||
|
||||
fn get_sid_of_user(username: &str) -> ResultType<String> {
|
||||
let mut output = Command::new("wmic")
|
||||
.args(&[
|
||||
"useraccount",
|
||||
"where",
|
||||
&format!("name='{}'", username),
|
||||
"get",
|
||||
"sid",
|
||||
"/value",
|
||||
])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?
|
||||
.stdout
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to open stdout"))?;
|
||||
let mut result = String::new();
|
||||
output.read_to_string(&mut result)?;
|
||||
let sid_start_index = result
|
||||
.find('=')
|
||||
.map(|i| i + 1)
|
||||
.ok_or(anyhow!("bad output format"))?;
|
||||
if sid_start_index > 0 && sid_start_index < result.len() + 1 {
|
||||
Ok(result[sid_start_index..].trim().to_string())
|
||||
} else {
|
||||
bail!("bad output format");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WallPaperRemover {
|
||||
old_path: String,
|
||||
}
|
||||
|
||||
impl WallPaperRemover {
|
||||
pub fn new() -> ResultType<Self> {
|
||||
let start = std::time::Instant::now();
|
||||
if !Self::need_remove() {
|
||||
bail!("already solid color");
|
||||
}
|
||||
let old_path = match Self::get_recent_wallpaper() {
|
||||
Ok(old_path) => old_path,
|
||||
Err(e) => {
|
||||
log::info!("Failed to get recent wallpaper:{:?}, use fallback", e);
|
||||
wallpaper::get().map_err(|e| anyhow!(e.to_string()))?
|
||||
}
|
||||
};
|
||||
Self::set_wallpaper(None)?;
|
||||
log::info!(
|
||||
"created wallpaper remover, old_path:{:?}, elapsed:{:?}",
|
||||
old_path,
|
||||
start.elapsed(),
|
||||
);
|
||||
Ok(Self { old_path })
|
||||
}
|
||||
|
||||
pub fn support() -> bool {
|
||||
wallpaper::get().is_ok() || !Self::get_recent_wallpaper().unwrap_or_default().is_empty()
|
||||
}
|
||||
|
||||
fn get_recent_wallpaper() -> ResultType<String> {
|
||||
// SystemParametersInfoW may return %appdata%\Microsoft\Windows\Themes\TranscodedWallpaper, not real path and may not real cache
|
||||
// https://www.makeuseof.com/find-desktop-wallpapers-file-location-windows-11/
|
||||
// https://superuser.com/questions/1218413/write-to-current-users-registry-through-a-different-admin-account
|
||||
let (hkcu, sid) = if is_root() {
|
||||
let username = get_active_username();
|
||||
let sid = get_sid_of_user(&username)?;
|
||||
log::info!("username:{username}, sid:{sid}");
|
||||
(RegKey::predef(HKEY_USERS), format!("{}\\", sid))
|
||||
} else {
|
||||
(RegKey::predef(HKEY_CURRENT_USER), "".to_string())
|
||||
};
|
||||
let explorer_key = hkcu.open_subkey_with_flags(
|
||||
&format!(
|
||||
"{}Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Wallpapers",
|
||||
sid
|
||||
),
|
||||
KEY_READ,
|
||||
)?;
|
||||
Ok(explorer_key.get_value("BackgroundHistoryPath0")?)
|
||||
}
|
||||
|
||||
fn need_remove() -> bool {
|
||||
if let Ok(wallpaper) = wallpaper::get() {
|
||||
return !wallpaper.is_empty();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn set_wallpaper(path: Option<String>) -> ResultType<()> {
|
||||
wallpaper::set_from_path(&path.unwrap_or_default()).map_err(|e| anyhow!(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WallPaperRemover {
|
||||
fn drop(&mut self) {
|
||||
// If the old background is a slideshow, it will be converted into an image. AnyDesk does the same.
|
||||
allow_err!(Self::set_wallpaper(Some(self.old_path.clone())));
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ use hbb_common::{
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use service::ServiceTmpl;
|
||||
use service::{GenericService, Service, Subscriber};
|
||||
use service::{EmptyExtraFieldService, GenericService, Service, Subscriber};
|
||||
|
||||
use crate::ipc::Data;
|
||||
|
||||
@ -53,6 +53,7 @@ pub const NAME_POS: &'static str = "";
|
||||
}
|
||||
|
||||
mod connection;
|
||||
pub mod display_service;
|
||||
#[cfg(windows)]
|
||||
pub mod portable_service;
|
||||
mod service;
|
||||
@ -80,7 +81,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
pub struct Server {
|
||||
connections: ConnMap,
|
||||
services: HashMap<&'static str, Box<dyn Service>>,
|
||||
services: HashMap<String, Box<dyn Service>>,
|
||||
id_count: i32,
|
||||
}
|
||||
|
||||
@ -94,11 +95,15 @@ pub fn new() -> ServerPtr {
|
||||
id_count: hbb_common::rand::random::<i32>() % 1000 + 1000, // ensure positive
|
||||
};
|
||||
server.add_service(Box::new(audio_service::new()));
|
||||
server.add_service(Box::new(video_service::new()));
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
server.add_service(Box::new(display_service::new()));
|
||||
server.add_service(Box::new(video_service::new(
|
||||
*display_service::PRIMARY_DISPLAY_IDX,
|
||||
)));
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
server.add_service(Box::new(clipboard_service::new()));
|
||||
if !video_service::capture_cursor_embedded() {
|
||||
if !display_service::capture_cursor_embedded() {
|
||||
server.add_service(Box::new(input_service::new_cursor()));
|
||||
server.add_service(Box::new(input_service::new_pos()));
|
||||
}
|
||||
@ -253,9 +258,19 @@ async fn create_relay_connection_(
|
||||
}
|
||||
|
||||
impl Server {
|
||||
fn is_video_service_name(name: &str) -> bool {
|
||||
name.starts_with(video_service::NAME)
|
||||
}
|
||||
|
||||
pub fn add_connection(&mut self, conn: ConnInner, noperms: &Vec<&'static str>) {
|
||||
let primary_video_service_name =
|
||||
video_service::get_service_name(*display_service::PRIMARY_DISPLAY_IDX);
|
||||
for s in self.services.values() {
|
||||
if !noperms.contains(&s.name()) {
|
||||
let name = s.name();
|
||||
if Self::is_video_service_name(&name) && name != primary_video_service_name {
|
||||
continue;
|
||||
}
|
||||
if !noperms.contains(&(&name as _)) {
|
||||
s.on_subscribe(conn.clone());
|
||||
}
|
||||
}
|
||||
@ -287,8 +302,12 @@ impl Server {
|
||||
self.services.insert(name, service);
|
||||
}
|
||||
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
self.services.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn subscribe(&mut self, name: &str, conn: ConnInner, sub: bool) {
|
||||
if let Some(s) = self.services.get(&name) {
|
||||
if let Some(s) = self.services.get(name) {
|
||||
if s.is_subed(conn.id()) == sub {
|
||||
return;
|
||||
}
|
||||
@ -305,6 +324,47 @@ impl Server {
|
||||
self.id_count += 1;
|
||||
self.id_count
|
||||
}
|
||||
|
||||
pub fn set_video_service_opt(&self, display: Option<usize>, opt: &str, value: &str) {
|
||||
for (k, v) in self.services.iter() {
|
||||
if let Some(display) = display {
|
||||
if k != &video_service::get_service_name(display) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if Self::is_video_service_name(k) {
|
||||
v.set_option(opt, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn capture_displays(
|
||||
&mut self,
|
||||
conn: ConnInner,
|
||||
displays: &[usize],
|
||||
include: bool,
|
||||
exclude: bool,
|
||||
) {
|
||||
let displays = displays
|
||||
.iter()
|
||||
.map(|d| video_service::get_service_name(*d))
|
||||
.collect::<Vec<_>>();
|
||||
let keys = self.services.keys().cloned().collect::<Vec<_>>();
|
||||
for name in keys.iter() {
|
||||
if Self::is_video_service_name(&name) {
|
||||
if displays.contains(&name) {
|
||||
if include {
|
||||
self.subscribe(&name, conn.clone(), true);
|
||||
}
|
||||
} else {
|
||||
if exclude {
|
||||
self.subscribe(&name, conn.clone(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
|
@ -24,16 +24,16 @@ static RESTARTING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn new() -> GenericService {
|
||||
let sp = GenericService::new(NAME, true);
|
||||
sp.repeat::<cpal_impl::State, _>(33, cpal_impl::run);
|
||||
sp
|
||||
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
|
||||
GenericService::repeat::<cpal_impl::State, _, _>(&svc.clone(), 33, cpal_impl::run);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn new() -> GenericService {
|
||||
let sp = GenericService::new(NAME, true);
|
||||
sp.run(pa_impl::run);
|
||||
sp
|
||||
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
|
||||
GenericService::run(&svc.clone(), pa_impl::run);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
pub fn restart() {
|
||||
@ -48,7 +48,7 @@ pub fn restart() {
|
||||
mod pa_impl {
|
||||
use super::*;
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn run(sp: GenericService) -> ResultType<()> {
|
||||
pub async fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||
hbb_common::sleep(0.1).await; // one moment to wait for _pa ipc
|
||||
RESTARTING.store(false, Ordering::SeqCst);
|
||||
#[cfg(target_os = "linux")]
|
||||
@ -125,7 +125,7 @@ mod cpal_impl {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(sp: GenericService, state: &mut State) -> ResultType<()> {
|
||||
pub fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> {
|
||||
sp.snapshot(|sps| {
|
||||
match &state.stream {
|
||||
None => {
|
||||
|
@ -28,12 +28,12 @@ impl super::service::Reset for State {
|
||||
}
|
||||
|
||||
pub fn new() -> GenericService {
|
||||
let sp = GenericService::new(NAME, true);
|
||||
sp.repeat::<State, _>(INTERVAL, run);
|
||||
sp
|
||||
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
|
||||
GenericService::repeat::<State, _, _>(&svc.clone(), INTERVAL, run);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
fn run(sp: GenericService, state: &mut State) -> ResultType<()> {
|
||||
fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> {
|
||||
if let Some(ctx) = state.ctx.as_mut() {
|
||||
if let Some(msg) = check_clipboard(ctx, None) {
|
||||
sp.send(msg);
|
||||
|
@ -6,6 +6,8 @@ use crate::common::update_clipboard;
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
use crate::platform::linux_desktop_manager;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
use crate::platform::WallPaperRemover;
|
||||
#[cfg(windows)]
|
||||
use crate::portable_service::client as portable_client;
|
||||
use crate::{
|
||||
@ -13,7 +15,7 @@ use crate::{
|
||||
new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender,
|
||||
},
|
||||
common::{get_default_sound_input, set_sound_input},
|
||||
video_service,
|
||||
display_service, video_service,
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
||||
@ -60,8 +62,14 @@ lazy_static::lazy_static! {
|
||||
static ref LOGIN_FAILURES: Arc::<Mutex<HashMap<String, (i32, i32, i32)>>> = Default::default();
|
||||
static ref SESSIONS: Arc::<Mutex<HashMap<String, Session>>> = Default::default();
|
||||
static ref ALIVE_CONNS: Arc::<Mutex<Vec<i32>>> = Default::default();
|
||||
static ref AUTHED_CONNS: Arc::<Mutex<Vec<(i32, AuthConnType)>>> = Default::default();
|
||||
static ref SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref WALLPAPER_REMOVER: Arc<Mutex<Option<WallPaperRemover>>> = Default::default();
|
||||
}
|
||||
pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
@ -143,8 +151,16 @@ struct StartCmIpcPara {
|
||||
tx_cm_stream_ready: mpsc::Sender<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum AuthConnType {
|
||||
Remote,
|
||||
FileTransfer,
|
||||
PortForward,
|
||||
}
|
||||
|
||||
pub struct Connection {
|
||||
inner: ConnInner,
|
||||
display_idx: usize,
|
||||
stream: super::Stream,
|
||||
server: super::ServerPtrWeak,
|
||||
hash: Hash,
|
||||
@ -205,6 +221,7 @@ pub struct Connection {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
start_cm_ipc_para: Option<StartCmIpcPara>,
|
||||
auto_disconnect_timer: Option<(Instant, u64)>,
|
||||
authed_conn_id: Option<self::raii::AuthedConnID>,
|
||||
}
|
||||
|
||||
impl ConnInner {
|
||||
@ -287,6 +304,7 @@ impl Connection {
|
||||
tx: Some(tx),
|
||||
tx_video: Some(tx_video),
|
||||
},
|
||||
display_idx: *display_service::PRIMARY_DISPLAY_IDX,
|
||||
stream,
|
||||
server,
|
||||
hash,
|
||||
@ -345,6 +363,7 @@ impl Connection {
|
||||
tx_cm_stream_ready,
|
||||
}),
|
||||
auto_disconnect_timer: None,
|
||||
authed_conn_id: None,
|
||||
};
|
||||
let addr = hbb_common::try_into_v4(addr);
|
||||
if !conn.on_open(addr).await {
|
||||
@ -592,6 +611,9 @@ impl Connection {
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
Some(message::Union::PeerInfo(_)) => {
|
||||
conn.refresh_video_display(None);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if let Err(err) = conn.stream.send(msg).await {
|
||||
@ -976,13 +998,17 @@ impl Connection {
|
||||
if self.authorized {
|
||||
return;
|
||||
}
|
||||
let conn_type = if self.file_transfer.is_some() {
|
||||
1
|
||||
let (conn_type, auth_conn_type) = if self.file_transfer.is_some() {
|
||||
(1, AuthConnType::FileTransfer)
|
||||
} else if self.port_forward_socket.is_some() {
|
||||
2
|
||||
(2, AuthConnType::PortForward)
|
||||
} else {
|
||||
0
|
||||
(0, AuthConnType::Remote)
|
||||
};
|
||||
self.authed_conn_id = Some(self::raii::AuthedConnID::new(
|
||||
self.inner.id(),
|
||||
auth_conn_type,
|
||||
));
|
||||
self.post_conn_audit(
|
||||
json!({"peer": ((&self.lr.my_id, &self.lr.my_name)), "type": conn_type}),
|
||||
);
|
||||
@ -1077,19 +1103,20 @@ impl Connection {
|
||||
pi.username = username;
|
||||
pi.sas_enabled = sas_enabled;
|
||||
pi.features = Some(Features {
|
||||
privacy_mode: video_service::is_privacy_mode_supported(),
|
||||
privacy_mode: display_service::is_privacy_mode_supported(),
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
// `try_reset_current_display` is needed because `get_displays` may change the current display,
|
||||
// which may cause the mismatch of current display and the current display name.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
video_service::try_reset_current_display();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
pi.resolutions = Some(SupportedResolutions {
|
||||
resolutions: video_service::get_current_display()
|
||||
.map(|(_, _, d)| crate::platform::resolutions(&d.name()))
|
||||
resolutions: display_service::try_get_displays()
|
||||
.map(|displays| {
|
||||
displays
|
||||
.get(self.display_idx)
|
||||
.map(|d| crate::platform::resolutions(&d.name()))
|
||||
.unwrap_or(vec![])
|
||||
})
|
||||
.unwrap_or(vec![]),
|
||||
..Default::default()
|
||||
})
|
||||
@ -1105,18 +1132,18 @@ impl Connection {
|
||||
self.send(msg_out).await;
|
||||
}
|
||||
|
||||
match super::video_service::get_displays().await {
|
||||
match super::display_service::get_displays().await {
|
||||
Err(err) => {
|
||||
res.set_error(format!("{}", err));
|
||||
}
|
||||
Ok((current, displays)) => {
|
||||
Ok(displays) => {
|
||||
pi.displays = displays.clone();
|
||||
pi.current_display = current as _;
|
||||
pi.current_display = self.display_idx as _;
|
||||
res.set_peer_info(pi);
|
||||
sub_service = true;
|
||||
*super::video_service::LAST_SYNC_DISPLAYS.write().unwrap() = displays;
|
||||
}
|
||||
}
|
||||
Self::on_remote_authorized();
|
||||
}
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_login_response(res);
|
||||
@ -1155,6 +1182,29 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remote_authorized() {
|
||||
use std::sync::Once;
|
||||
static ONCE: Once = Once::new();
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
if !Config::get_option("allow-remove-wallpaper").is_empty() {
|
||||
// multi connections set once
|
||||
let mut wallpaper = WALLPAPER_REMOVER.lock().unwrap();
|
||||
if wallpaper.is_none() {
|
||||
match crate::platform::WallPaperRemover::new() {
|
||||
Ok(remover) => {
|
||||
*wallpaper = Some(remover);
|
||||
ONCE.call_once(|| {
|
||||
shutdown_hooks::add_shutdown_hook(shutdown_hook);
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
log::info!("create wallpaper remover failed:{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn peer_keyboard_enabled(&self) -> bool {
|
||||
self.keyboard && !self.disable_keyboard
|
||||
}
|
||||
@ -1244,6 +1294,8 @@ impl Connection {
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn input_key(&self, msg: KeyEvent, press: bool) {
|
||||
// to-do: if is the legacy mode, and the key is function key "LockScreen".
|
||||
// Switch to the primary display.
|
||||
self.tx_input.send(MessageInput::Key((msg, press))).ok();
|
||||
}
|
||||
|
||||
@ -1403,23 +1455,17 @@ impl Connection {
|
||||
self.file_transfer = Some((ft.dir, ft.show_hidden));
|
||||
}
|
||||
Some(login_request::Union::PortForward(mut pf)) => {
|
||||
if !Connection::permission("enable-tunnel") {
|
||||
self.send_login_error("No permission of IP tunneling").await;
|
||||
sleep(1.).await;
|
||||
return false;
|
||||
}
|
||||
let mut is_rdp = false;
|
||||
if pf.host == "RDP" && pf.port == 0 {
|
||||
pf.host = "localhost".to_owned();
|
||||
pf.port = 3389;
|
||||
is_rdp = true;
|
||||
}
|
||||
if is_rdp && !Connection::permission("enable-rdp")
|
||||
|| !is_rdp && !Connection::permission("enable-tunnel")
|
||||
{
|
||||
if is_rdp {
|
||||
self.send_login_error("No permission of RDP").await;
|
||||
} else {
|
||||
self.send_login_error("No permission of IP tunneling").await;
|
||||
}
|
||||
sleep(1.).await;
|
||||
return false;
|
||||
}
|
||||
if pf.host.is_empty() {
|
||||
pf.host = "localhost".to_owned();
|
||||
}
|
||||
@ -1906,15 +1952,13 @@ impl Connection {
|
||||
},
|
||||
Some(message::Union::Misc(misc)) => match misc.union {
|
||||
Some(misc::Union::SwitchDisplay(s)) => {
|
||||
video_service::switch_display(s.display).await;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if s.width != 0 && s.height != 0 {
|
||||
self.change_resolution(&Resolution {
|
||||
width: s.width,
|
||||
height: s.height,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
self.handle_switch_display(s).await;
|
||||
}
|
||||
Some(misc::Union::CaptureDisplays(displays)) => {
|
||||
let add = displays.add.iter().map(|d| *d as usize).collect::<Vec<_>>();
|
||||
let sub = displays.sub.iter().map(|d| *d as usize).collect::<Vec<_>>();
|
||||
let set = displays.set.iter().map(|d| *d as usize).collect::<Vec<_>>();
|
||||
self.capture_displays(&add, &sub, &set).await;
|
||||
}
|
||||
Some(misc::Union::ChatMessage(c)) => {
|
||||
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
|
||||
@ -1926,10 +1970,16 @@ impl Connection {
|
||||
}
|
||||
Some(misc::Union::RefreshVideo(r)) => {
|
||||
if r {
|
||||
super::video_service::refresh();
|
||||
// Refresh all videos.
|
||||
// Compatibility with old versions and sciter(remote).
|
||||
self.refresh_video_display(None);
|
||||
}
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
Some(misc::Union::RefreshVideoDisplay(display)) => {
|
||||
self.refresh_video_display(Some(display as usize));
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
Some(misc::Union::VideoReceived(_)) => {
|
||||
video_service::notify_video_frame_fetched(
|
||||
self.inner.id,
|
||||
@ -2045,6 +2095,75 @@ impl Connection {
|
||||
true
|
||||
}
|
||||
|
||||
fn refresh_video_display(&self, display: Option<usize>) {
|
||||
video_service::refresh();
|
||||
self.server.upgrade().map(|s| {
|
||||
s.read().unwrap().set_video_service_opt(
|
||||
display,
|
||||
video_service::OPTION_REFRESH,
|
||||
super::service::SERVICE_OPTION_VALUE_TRUE,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
async fn handle_switch_display(&mut self, s: SwitchDisplay) {
|
||||
#[cfg(windows)]
|
||||
if portable_client::running()
|
||||
&& *CONN_COUNT.lock().unwrap() > 1
|
||||
&& s.display != (*display_service::PRIMARY_DISPLAY_IDX as i32)
|
||||
{
|
||||
log::info!("Switch to non-primary display is not supported in the elevated mode when there are multiple connections.");
|
||||
let mut msg_out = Message::new();
|
||||
let res = MessageBox {
|
||||
msgtype: "nook-nocancel-hasclose".to_owned(),
|
||||
title: "Prompt".to_owned(),
|
||||
text: "switch_display_elevated_connections_tip".to_owned(),
|
||||
link: "".to_owned(),
|
||||
..Default::default()
|
||||
};
|
||||
msg_out.set_message_box(res);
|
||||
self.send(msg_out).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let display_idx = s.display as usize;
|
||||
if self.display_idx != display_idx {
|
||||
if let Some(server) = self.server.upgrade() {
|
||||
self.switch_display_to(display_idx, server.clone());
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if s.width != 0 && s.height != 0 {
|
||||
self.change_resolution(&Resolution {
|
||||
width: s.width,
|
||||
height: s.height,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
// send display changed message
|
||||
if let Some(msg_out) =
|
||||
video_service::make_display_changed_msg(self.display_idx, None)
|
||||
{
|
||||
self.send(msg_out).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_display_to(&mut self, display_idx: usize, server: Arc<RwLock<Server>>) {
|
||||
let new_service_name = video_service::get_service_name(display_idx);
|
||||
let old_service_name = video_service::get_service_name(self.display_idx);
|
||||
let mut lock = server.write().unwrap();
|
||||
if display_idx != *display_service::PRIMARY_DISPLAY_IDX {
|
||||
if !lock.contains(&new_service_name) {
|
||||
lock.add_service(Box::new(video_service::new(display_idx)));
|
||||
}
|
||||
}
|
||||
lock.subscribe(&old_service_name, self.inner.clone(), false);
|
||||
lock.subscribe(&new_service_name, self.inner.clone(), true);
|
||||
self.display_idx = display_idx;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn handle_elevation_request(&mut self, para: portable_client::StartPara) {
|
||||
let mut err;
|
||||
@ -2067,36 +2186,64 @@ impl Connection {
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
|
||||
async fn capture_displays(&mut self, add: &[usize], sub: &[usize], set: &[usize]) {
|
||||
if let Some(sever) = self.server.upgrade() {
|
||||
let mut lock = sever.write().unwrap();
|
||||
for display in add.iter() {
|
||||
let service_name = video_service::get_service_name(*display);
|
||||
if !lock.contains(&service_name) {
|
||||
lock.add_service(Box::new(video_service::new(*display)));
|
||||
}
|
||||
}
|
||||
for display in set.iter() {
|
||||
let service_name = video_service::get_service_name(*display);
|
||||
if !lock.contains(&service_name) {
|
||||
lock.add_service(Box::new(video_service::new(*display)));
|
||||
}
|
||||
}
|
||||
if !add.is_empty() {
|
||||
lock.capture_displays(self.inner.clone(), add, true, false);
|
||||
} else if !sub.is_empty() {
|
||||
lock.capture_displays(self.inner.clone(), sub, false, true);
|
||||
} else {
|
||||
lock.capture_displays(self.inner.clone(), set, true, true);
|
||||
}
|
||||
drop(lock);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn change_resolution(&mut self, r: &Resolution) {
|
||||
if self.keyboard {
|
||||
if let Ok((_, _, display)) = video_service::get_current_display() {
|
||||
let name = display.name();
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
if let Some(_ok) =
|
||||
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
|
||||
if let Ok(displays) = display_service::try_get_displays() {
|
||||
if let Some(display) = displays.get(self.display_idx) {
|
||||
let name = display.name();
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
if let Some(_ok) =
|
||||
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
|
||||
&name,
|
||||
r.width as _,
|
||||
r.height as _,
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
display_service::set_last_changed_resolution(
|
||||
&name,
|
||||
r.width as _,
|
||||
r.height as _,
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
video_service::set_last_changed_resolution(
|
||||
&name,
|
||||
(display.width() as _, display.height() as _),
|
||||
(r.width, r.height),
|
||||
);
|
||||
if let Err(e) =
|
||||
crate::platform::change_resolution(&name, r.width as _, r.height as _)
|
||||
{
|
||||
log::error!(
|
||||
"Failed to change resolution '{}' to ({},{}):{:?}",
|
||||
&name,
|
||||
r.width,
|
||||
r.height,
|
||||
e
|
||||
(display.width() as _, display.height() as _),
|
||||
(r.width, r.height),
|
||||
);
|
||||
if let Err(e) =
|
||||
crate::platform::change_resolution(&name, r.width as _, r.height as _)
|
||||
{
|
||||
log::error!(
|
||||
"Failed to change resolution '{}' to ({},{}):{:?}",
|
||||
&name,
|
||||
r.width,
|
||||
r.height,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2242,7 +2389,7 @@ impl Connection {
|
||||
if self.keyboard {
|
||||
match q {
|
||||
BoolOption::Yes => {
|
||||
let msg_out = if !video_service::is_privacy_mode_supported() {
|
||||
let msg_out = if !display_service::is_privacy_mode_supported() {
|
||||
crate::common::make_privacy_mode_msg_with_details(
|
||||
back_notification::PrivacyModeState::PrvNotSupported,
|
||||
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
|
||||
@ -2250,8 +2397,11 @@ impl Connection {
|
||||
} else {
|
||||
match privacy_mode::turn_on_privacy(self.inner.id) {
|
||||
Ok(true) => {
|
||||
let err_msg =
|
||||
video_service::test_create_capturer(self.inner.id, 5_000);
|
||||
let err_msg = video_service::test_create_capturer(
|
||||
self.inner.id,
|
||||
self.display_idx,
|
||||
5_000,
|
||||
);
|
||||
if err_msg.is_empty() {
|
||||
video_service::set_privacy_mode_conn_id(self.inner.id);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
@ -2287,7 +2437,7 @@ impl Connection {
|
||||
self.send(msg_out).await;
|
||||
}
|
||||
BoolOption::No => {
|
||||
let msg_out = if !video_service::is_privacy_mode_supported() {
|
||||
let msg_out = if !display_service::is_privacy_mode_supported() {
|
||||
crate::common::make_privacy_mode_msg_with_details(
|
||||
back_notification::PrivacyModeState::PrvNotSupported,
|
||||
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
|
||||
@ -2740,6 +2890,11 @@ impl LinuxHeadlessHandle {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
extern "C" fn shutdown_hook() {
|
||||
*WALLPAPER_REMOVER.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
mod raii {
|
||||
use super::*;
|
||||
pub struct ConnectionID(i32);
|
||||
@ -2757,11 +2912,11 @@ mod raii {
|
||||
active_conns_lock.retain(|&c| c != self.0);
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if active_conns_lock.is_empty() {
|
||||
video_service::reset_resolutions();
|
||||
display_service::reset_resolutions();
|
||||
}
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
if active_conns_lock.is_empty() {
|
||||
video_service::try_plug_out_virtual_display();
|
||||
display_service::try_plug_out_virtual_display();
|
||||
}
|
||||
#[cfg(all(windows))]
|
||||
if active_conns_lock.is_empty() {
|
||||
@ -2773,4 +2928,26 @@ mod raii {
|
||||
.on_connection_close(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthedConnID(i32);
|
||||
|
||||
impl AuthedConnID {
|
||||
pub fn new(id: i32, conn_type: AuthConnType) -> Self {
|
||||
AUTHED_CONNS.lock().unwrap().push((id, conn_type));
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AuthedConnID {
|
||||
fn drop(&mut self) {
|
||||
let mut lock = AUTHED_CONNS.lock().unwrap();
|
||||
lock.retain(|&c| c.0 != self.0);
|
||||
if lock.iter().filter(|c| c.1 == AuthConnType::Remote).count() == 0 {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
{
|
||||
*WALLPAPER_REMOVER.lock().unwrap() = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
262
src/server/display_service.rs
Normal file
262
src/server/display_service.rs
Normal file
@ -0,0 +1,262 @@
|
||||
use super::*;
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
use crate::virtual_display_manager;
|
||||
#[cfg(windows)]
|
||||
use hbb_common::get_version_number;
|
||||
use hbb_common::protobuf::MessageField;
|
||||
use scrap::Display;
|
||||
|
||||
pub const NAME: &'static str = "display";
|
||||
|
||||
struct ChangedResolution {
|
||||
original: (i32, i32),
|
||||
changed: (i32, i32),
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
|
||||
static ref CHANGED_RESOLUTIONS: Arc<RwLock<HashMap<String, ChangedResolution>>> = Default::default();
|
||||
// Initial primary display index.
|
||||
// It should only be updated when the rustdesk server is started, and should not be updated when displays changed.
|
||||
pub static ref PRIMARY_DISPLAY_IDX: usize = get_primary();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), changed: (i32, i32)) {
|
||||
let mut lock = CHANGED_RESOLUTIONS.write().unwrap();
|
||||
match lock.get_mut(display_name) {
|
||||
Some(res) => res.changed = changed,
|
||||
None => {
|
||||
lock.insert(
|
||||
display_name.to_owned(),
|
||||
ChangedResolution { original, changed },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn reset_resolutions() {
|
||||
for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() {
|
||||
let (w, h) = res.original;
|
||||
if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) {
|
||||
log::error!(
|
||||
"Failed to reset resolution of display '{}' to ({},{}): {}",
|
||||
name,
|
||||
w,
|
||||
h,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_capturer_mag_supported() -> bool {
|
||||
#[cfg(windows)]
|
||||
return scrap::CapturerMag::is_supported();
|
||||
#[cfg(not(windows))]
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn capture_cursor_embedded() -> bool {
|
||||
scrap::is_cursor_embedded()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_privacy_mode_supported() -> bool {
|
||||
#[cfg(windows)]
|
||||
return *IS_CAPTURER_MAGNIFIER_SUPPORTED
|
||||
&& get_version_number(&crate::VERSION) > get_version_number("1.1.9");
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StateDisplay {
|
||||
synced_displays: Vec<DisplayInfo>,
|
||||
}
|
||||
|
||||
impl super::service::Reset for StateDisplay {
|
||||
fn reset(&mut self) {
|
||||
self.synced_displays.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> GenericService {
|
||||
let svc = EmptyExtraFieldService::new(NAME.to_owned(), false);
|
||||
GenericService::repeat::<StateDisplay, _, _>(&svc.clone(), 300, run);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
fn check_get_displays_changed_msg(last_synced_displays: &mut Vec<DisplayInfo>) -> Option<Message> {
|
||||
let displays = try_get_displays().ok()?;
|
||||
if displays.len() == last_synced_displays.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Display to DisplayInfo
|
||||
let displays = to_display_info(&displays);
|
||||
if last_synced_displays.len() == 0 {
|
||||
*last_synced_displays = displays;
|
||||
None
|
||||
} else {
|
||||
let mut pi = PeerInfo {
|
||||
..Default::default()
|
||||
};
|
||||
pi.displays = displays.clone();
|
||||
pi.current_display = 0;
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_peer_info(pi);
|
||||
*last_synced_displays = displays;
|
||||
Some(msg_out)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
pub fn try_plug_out_virtual_display() {
|
||||
let _res = virtual_display_manager::plug_out_headless();
|
||||
}
|
||||
|
||||
fn run(sp: EmptyExtraFieldService, state: &mut StateDisplay) -> ResultType<()> {
|
||||
if let Some(msg_out) = check_get_displays_changed_msg(&mut state.synced_displays) {
|
||||
sp.send(msg_out);
|
||||
log::info!("Displays changed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn get_original_resolution(
|
||||
display_name: &str,
|
||||
w: usize,
|
||||
h: usize,
|
||||
) -> MessageField<Resolution> {
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name);
|
||||
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
|
||||
let is_virtual_display = false;
|
||||
Some(if is_virtual_display {
|
||||
Resolution {
|
||||
width: 0,
|
||||
height: 0,
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
let mut changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap();
|
||||
let (width, height) = match changed_resolutions.get(display_name) {
|
||||
Some(res) => {
|
||||
if res.changed.0 != w as i32 || res.changed.1 != h as i32 {
|
||||
// If the resolution is changed by third process, remove the record in changed_resolutions.
|
||||
changed_resolutions.remove(display_name);
|
||||
(w as _, h as _)
|
||||
} else {
|
||||
res.original
|
||||
}
|
||||
}
|
||||
None => (w as _, h as _),
|
||||
};
|
||||
Resolution {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn to_display_info(all: &Vec<Display>) -> Vec<DisplayInfo> {
|
||||
all.iter()
|
||||
.map(|d| {
|
||||
let display_name = d.name();
|
||||
let original_resolution = get_original_resolution(&display_name, d.width(), d.height());
|
||||
DisplayInfo {
|
||||
x: d.origin().0 as _,
|
||||
y: d.origin().1 as _,
|
||||
width: d.width() as _,
|
||||
height: d.height() as _,
|
||||
name: display_name,
|
||||
online: d.is_online(),
|
||||
cursor_embedded: false,
|
||||
original_resolution,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<DisplayInfo>>()
|
||||
}
|
||||
|
||||
pub fn is_inited_msg() -> Option<Message> {
|
||||
#[cfg(target_os = "linux")]
|
||||
if !scrap::is_x11() {
|
||||
return super::wayland::is_inited();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn get_displays() -> ResultType<Vec<DisplayInfo>> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !scrap::is_x11() {
|
||||
return super::wayland::get_displays().await;
|
||||
}
|
||||
}
|
||||
Ok(to_display_info(&try_get_displays()?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary() -> usize {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !scrap::is_x11() {
|
||||
return match super::wayland::get_primary() {
|
||||
Ok(n) => n,
|
||||
Err(_) => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try_get_displays().map(|d| get_primary_2(&d)).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_2(all: &Vec<Display>) -> usize {
|
||||
all.iter().position(|d| d.is_primary()).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
fn no_displays(displays: &Vec<Display>) -> bool {
|
||||
let display_len = displays.len();
|
||||
if display_len == 0 {
|
||||
true
|
||||
} else if display_len == 1 {
|
||||
let display = &displays[0];
|
||||
let dummy_display_side_max_size = 800;
|
||||
display.width() <= dummy_display_side_max_size
|
||||
&& display.height() <= dummy_display_side_max_size
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
|
||||
pub fn try_get_displays() -> ResultType<Vec<Display>> {
|
||||
Ok(Display::all()?)
|
||||
}
|
||||
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
pub fn try_get_displays() -> ResultType<Vec<Display>> {
|
||||
let mut displays = Display::all()?;
|
||||
if no_displays(&displays) {
|
||||
log::debug!("no displays, create virtual display");
|
||||
if let Err(e) = virtual_display_manager::plug_in_headless() {
|
||||
log::error!("plug in headless failed {}", e);
|
||||
} else {
|
||||
displays = Display::all()?;
|
||||
}
|
||||
}
|
||||
Ok(displays)
|
||||
}
|
@ -17,7 +17,7 @@ use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
|
||||
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
ops::Sub,
|
||||
ops::{Deref, DerefMut, Sub},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
thread,
|
||||
time::{self, Duration, Instant},
|
||||
@ -236,18 +236,43 @@ fn should_disable_numlock(evt: &KeyEvent) -> bool {
|
||||
|
||||
pub const NAME_CURSOR: &'static str = "mouse_cursor";
|
||||
pub const NAME_POS: &'static str = "mouse_pos";
|
||||
pub type MouseCursorService = ServiceTmpl<MouseCursorSub>;
|
||||
#[derive(Clone)]
|
||||
pub struct MouseCursorService {
|
||||
pub sp: ServiceTmpl<MouseCursorSub>,
|
||||
}
|
||||
|
||||
pub fn new_cursor() -> MouseCursorService {
|
||||
let sp = MouseCursorService::new(NAME_CURSOR, true);
|
||||
sp.repeat::<StateCursor, _>(33, run_cursor);
|
||||
sp
|
||||
impl Deref for MouseCursorService {
|
||||
type Target = ServiceTmpl<MouseCursorSub>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.sp
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MouseCursorService {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.sp
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseCursorService {
|
||||
pub fn new(name: String, need_snapshot: bool) -> Self {
|
||||
Self {
|
||||
sp: ServiceTmpl::<MouseCursorSub>::new(name, need_snapshot),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_cursor() -> ServiceTmpl<MouseCursorSub> {
|
||||
let svc = MouseCursorService::new(NAME_CURSOR.to_owned(), true);
|
||||
ServiceTmpl::<MouseCursorSub>::repeat::<StateCursor, _, _>(&svc.clone(), 33, run_cursor);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
pub fn new_pos() -> GenericService {
|
||||
let sp = GenericService::new(NAME_POS, false);
|
||||
sp.repeat::<StatePos, _>(33, run_pos);
|
||||
sp
|
||||
let svc = EmptyExtraFieldService::new(NAME_POS.to_owned(), false);
|
||||
GenericService::repeat::<StatePos, _, _>(&svc.clone(), 33, run_pos);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -258,7 +283,7 @@ fn update_last_cursor_pos(x: i32, y: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
|
||||
fn run_pos(sp: EmptyExtraFieldService, state: &mut StatePos) -> ResultType<()> {
|
||||
let (_, (x, y)) = *LATEST_SYS_CURSOR_POS.lock().unwrap();
|
||||
if x == INVALID_CURSOR_POS || y == INVALID_CURSOR_POS {
|
||||
return Ok(());
|
||||
@ -988,7 +1013,6 @@ pub async fn lock_screen() {
|
||||
crate::platform::lock_screen();
|
||||
}
|
||||
}
|
||||
super::video_service::switch_to_primary().await;
|
||||
}
|
||||
|
||||
pub fn handle_key(evt: &KeyEvent) {
|
||||
|
@ -25,7 +25,6 @@ use winapi::{
|
||||
use crate::{
|
||||
ipc::{self, new_listener, Connection, Data, DataPortableService},
|
||||
platform::set_path_permission,
|
||||
video_service::get_current_display,
|
||||
};
|
||||
|
||||
use super::video_qos;
|
||||
@ -224,6 +223,8 @@ mod utils {
|
||||
pub mod server {
|
||||
use hbb_common::message_proto::PointerDeviceEvent;
|
||||
|
||||
use crate::display_service;
|
||||
|
||||
use super::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@ -324,12 +325,17 @@ pub mod server {
|
||||
continue;
|
||||
}
|
||||
if c.is_none() {
|
||||
*crate::video_service::CURRENT_DISPLAY.lock().unwrap() = current_display;
|
||||
let Ok((_, _current, display)) = get_current_display() else {
|
||||
log::error!("Failed to get current display");
|
||||
let Ok(mut displays) = display_service::try_get_displays() else {
|
||||
log::error!("Failed to get displays");
|
||||
*EXIT.lock().unwrap() = true;
|
||||
return;
|
||||
};
|
||||
if displays.len() <= current_display {
|
||||
log::error!("Invalid display index:{}", current_display);
|
||||
*EXIT.lock().unwrap() = true;
|
||||
return;
|
||||
}
|
||||
let display = displays.remove(current_display);
|
||||
display_width = display.width();
|
||||
display_height = display.height();
|
||||
match Capturer::new(display, use_yuv) {
|
||||
@ -522,6 +528,8 @@ pub mod server {
|
||||
pub mod client {
|
||||
use hbb_common::{anyhow::Context, message_proto::PointerDeviceEvent};
|
||||
|
||||
use crate::display_service;
|
||||
|
||||
use super::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@ -665,8 +673,8 @@ pub mod client {
|
||||
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
|
||||
}
|
||||
let (mut width, mut height) = (0, 0);
|
||||
if let Ok((_, current, display)) = get_current_display() {
|
||||
if current_display == current {
|
||||
if let Ok(displays) = display_service::try_get_displays() {
|
||||
if let Some(display) = displays.get(current_display) {
|
||||
width = display.width();
|
||||
height = display.height();
|
||||
}
|
||||
@ -910,7 +918,15 @@ pub mod client {
|
||||
}
|
||||
if portable_service_running {
|
||||
log::info!("Create shared memory capturer");
|
||||
return Ok(Box::new(CapturerPortable::new(current_display, use_yuv)));
|
||||
if current_display == *display_service::PRIMARY_DISPLAY_IDX {
|
||||
return Ok(Box::new(CapturerPortable::new(current_display, use_yuv)));
|
||||
} else {
|
||||
bail!(
|
||||
"Ignore capture display index: {}, the primary display index is: {}",
|
||||
current_display,
|
||||
*display_service::PRIMARY_DISPLAY_IDX
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::debug!("Create capturer dxgi|gdi");
|
||||
return Ok(Box::new(
|
||||
|
@ -1,16 +1,19 @@
|
||||
use super::*;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
ops::{Deref, DerefMut},
|
||||
thread::{self, JoinHandle},
|
||||
time,
|
||||
};
|
||||
|
||||
pub trait Service: Send + Sync {
|
||||
fn name(&self) -> &'static str;
|
||||
fn name(&self) -> String;
|
||||
fn on_subscribe(&self, sub: ConnInner);
|
||||
fn on_unsubscribe(&self, id: i32);
|
||||
fn is_subed(&self, id: i32) -> bool;
|
||||
fn join(&self);
|
||||
fn get_option(&self, opt: &str) -> Option<String>;
|
||||
fn set_option(&self, opt: &str, val: &str) -> Option<String>;
|
||||
}
|
||||
|
||||
pub trait Subscriber: Default + Send + Sync + 'static {
|
||||
@ -20,12 +23,13 @@ pub trait Subscriber: Default + Send + Sync + 'static {
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ServiceInner<T: Subscriber + From<ConnInner>> {
|
||||
name: &'static str,
|
||||
name: String,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
subscribes: HashMap<i32, T>,
|
||||
new_subscribes: HashMap<i32, T>,
|
||||
active: bool,
|
||||
need_snapshot: bool,
|
||||
options: HashMap<String, String>,
|
||||
}
|
||||
|
||||
pub trait Reset {
|
||||
@ -37,6 +41,35 @@ pub struct ServiceSwap<T: Subscriber + From<ConnInner>>(ServiceTmpl<T>);
|
||||
pub type GenericService = ServiceTmpl<ConnInner>;
|
||||
pub const HIBERNATE_TIMEOUT: u64 = 30;
|
||||
pub const MAX_ERROR_TIMEOUT: u64 = 1_000;
|
||||
pub const SERVICE_OPTION_VALUE_TRUE: &str = "1";
|
||||
pub const SERVICE_OPTION_VALUE_FALSE: &str = "0";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EmptyExtraFieldService {
|
||||
pub sp: GenericService,
|
||||
}
|
||||
|
||||
impl Deref for EmptyExtraFieldService {
|
||||
type Target = ServiceTmpl<ConnInner>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.sp
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for EmptyExtraFieldService {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.sp
|
||||
}
|
||||
}
|
||||
|
||||
impl EmptyExtraFieldService {
|
||||
pub fn new(name: String, need_snapshot: bool) -> Self {
|
||||
Self {
|
||||
sp: GenericService::new(name, need_snapshot),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Subscriber + From<ConnInner>> ServiceInner<T> {
|
||||
fn send_new_subscribes(&mut self, msg: Arc<Message>) {
|
||||
@ -60,8 +93,8 @@ impl<T: Subscriber + From<ConnInner>> ServiceInner<T> {
|
||||
|
||||
impl<T: Subscriber + From<ConnInner>> Service for ServiceTmpl<T> {
|
||||
#[inline]
|
||||
fn name(&self) -> &'static str {
|
||||
self.0.read().unwrap().name
|
||||
fn name(&self) -> String {
|
||||
self.0.read().unwrap().name.clone()
|
||||
}
|
||||
|
||||
fn is_subed(&self, id: i32) -> bool {
|
||||
@ -96,6 +129,18 @@ impl<T: Subscriber + From<ConnInner>> Service for ServiceTmpl<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_option(&self, opt: &str) -> Option<String> {
|
||||
self.0.read().unwrap().options.get(opt).cloned()
|
||||
}
|
||||
|
||||
fn set_option(&self, opt: &str, val: &str) -> Option<String> {
|
||||
self.0
|
||||
.write()
|
||||
.unwrap()
|
||||
.options
|
||||
.insert(opt.to_string(), val.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Subscriber + From<ConnInner>> Clone for ServiceTmpl<T> {
|
||||
@ -105,7 +150,7 @@ impl<T: Subscriber + From<ConnInner>> Clone for ServiceTmpl<T> {
|
||||
}
|
||||
|
||||
impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
||||
pub fn new(name: &'static str, need_snapshot: bool) -> Self {
|
||||
pub fn new(name: String, need_snapshot: bool) -> Self {
|
||||
Self(Arc::new(RwLock::new(ServiceInner::<T> {
|
||||
name,
|
||||
active: true,
|
||||
@ -114,6 +159,21 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
||||
})))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_option_true(&self, opt: &str) -> bool {
|
||||
self.get_option(opt)
|
||||
.map_or(false, |v| v == SERVICE_OPTION_VALUE_TRUE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_option_bool(&self, opt: &str, val: bool) {
|
||||
if val {
|
||||
self.set_option(opt, SERVICE_OPTION_VALUE_TRUE);
|
||||
} else {
|
||||
self.set_option(opt, SERVICE_OPTION_VALUE_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_subscribes(&self) -> bool {
|
||||
self.0.read().unwrap().has_subscribes()
|
||||
@ -189,14 +249,15 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn repeat<S, F>(&self, interval_ms: u64, callback: F)
|
||||
pub fn repeat<S, F, Svc>(svc: &Svc, interval_ms: u64, callback: F)
|
||||
where
|
||||
F: 'static + FnMut(Self, &mut S) -> ResultType<()> + Send,
|
||||
F: 'static + FnMut(Svc, &mut S) -> ResultType<()> + Send,
|
||||
S: 'static + Default + Reset,
|
||||
Svc: 'static + Clone + Send + DerefMut<Target = ServiceTmpl<T>>,
|
||||
{
|
||||
let interval = time::Duration::from_millis(interval_ms);
|
||||
let mut callback = callback;
|
||||
let sp = self.clone();
|
||||
let sp = svc.clone();
|
||||
let thread = thread::spawn(move || {
|
||||
let mut state = S::default();
|
||||
let mut may_reset = false;
|
||||
@ -223,14 +284,15 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
||||
}
|
||||
log::info!("Service {} exit", sp.name());
|
||||
});
|
||||
self.0.write().unwrap().handle = Some(thread);
|
||||
svc.0.write().unwrap().handle = Some(thread);
|
||||
}
|
||||
|
||||
pub fn run<F>(&self, callback: F)
|
||||
pub fn run<F, Svc>(svc: &Svc, callback: F)
|
||||
where
|
||||
F: 'static + FnMut(Self) -> ResultType<()> + Send,
|
||||
F: 'static + FnMut(Svc) -> ResultType<()> + Send,
|
||||
Svc: 'static + Clone + Send + DerefMut<Target = ServiceTmpl<T>>,
|
||||
{
|
||||
let sp = self.clone();
|
||||
let sp = svc.clone();
|
||||
let mut callback = callback;
|
||||
let thread = thread::spawn(move || {
|
||||
let mut error_timeout = HIBERNATE_TIMEOUT;
|
||||
@ -259,7 +321,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
||||
}
|
||||
log::info!("Service {} exit", sp.name());
|
||||
});
|
||||
self.0.write().unwrap().handle = Some(thread);
|
||||
svc.0.write().unwrap().handle = Some(thread);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user