mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-22 17:48:34 +08:00
Compare commits
52 Commits
1.4.5
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f65f5812b2 | ||
|
|
b8f5b91168 | ||
|
|
6306f83316 | ||
|
|
96075fdf49 | ||
|
|
8c6dcf53a6 | ||
|
|
e1b1a927b8 | ||
|
|
1e6bfa7bb1 | ||
|
|
79ef4c4501 | ||
|
|
5f3ceef592 | ||
|
|
1a90e6b6c7 | ||
|
|
f112d097dc | ||
|
|
45cab7f808 | ||
|
|
216ec9d52b | ||
|
|
56a8f6b97b | ||
|
|
c76d10a438 | ||
|
|
f05f2178e5 | ||
|
|
226d7417b2 | ||
|
|
b0c8e65c6e | ||
|
|
4ae577c3c4 | ||
|
|
204e81a700 | ||
|
|
1f35830570 | ||
|
|
6b334f2977 | ||
|
|
0dc3c12aa5 | ||
|
|
ceffcce20e | ||
|
|
e4b06dadf5 | ||
|
|
087eb55299 | ||
|
|
341eb0c671 | ||
|
|
43b39102a4 | ||
|
|
be4bbd018d | ||
|
|
21a7cef98a | ||
|
|
a6724b1c07 | ||
|
|
7437593ee7 | ||
|
|
f21829b075 | ||
|
|
b4f60e6057 | ||
|
|
b9ebddff0c | ||
|
|
a2243484a3 | ||
|
|
c4a9835ae5 | ||
|
|
92ad279324 | ||
|
|
7276025cf9 | ||
|
|
9808d585cf | ||
|
|
dab9ed711c | ||
|
|
b27a93fc77 | ||
|
|
e3f66973b7 | ||
|
|
21529d6ca2 | ||
|
|
775b0a3c93 | ||
|
|
070d4d029f | ||
|
|
5355702e9c | ||
|
|
a97997952d | ||
|
|
b0c12bd86b | ||
|
|
82fcab26b1 | ||
|
|
f3bbcc4f55 | ||
|
|
98362eaca0 |
8
.github/workflows/flutter-build.yml
vendored
8
.github/workflows/flutter-build.yml
vendored
@@ -234,7 +234,7 @@ jobs:
|
||||
path: rustdesk
|
||||
|
||||
- name: Sign rustdesk files
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2'
|
||||
shell: bash
|
||||
run: |
|
||||
pip3 install requests argparse
|
||||
@@ -266,7 +266,7 @@ jobs:
|
||||
sha256sum ../../SignOutput/rustdesk-*.msi
|
||||
|
||||
- name: Sign rustdesk self-extracted file
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2'
|
||||
shell: bash
|
||||
run: |
|
||||
BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./SignOutput
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
path: Release
|
||||
|
||||
- name: Sign rustdesk files
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2'
|
||||
shell: bash
|
||||
run: |
|
||||
pip3 install requests argparse
|
||||
@@ -418,7 +418,7 @@ jobs:
|
||||
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.exe
|
||||
|
||||
- name: Sign rustdesk self-extracted file
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2'
|
||||
shell: bash
|
||||
run: |
|
||||
BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./SignOutput/
|
||||
|
||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -2517,6 +2517,7 @@ version = "0.0.14"
|
||||
dependencies = [
|
||||
"core-graphics 0.22.3",
|
||||
"hbb_common",
|
||||
"libxdo-sys",
|
||||
"log",
|
||||
"objc",
|
||||
"pkg-config",
|
||||
@@ -3720,6 +3721,7 @@ dependencies = [
|
||||
"httparse",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libloading 0.8.4",
|
||||
"log",
|
||||
"mac_address",
|
||||
"machine-uid",
|
||||
@@ -3755,6 +3757,7 @@ dependencies = [
|
||||
"webrtc",
|
||||
"whoami",
|
||||
"winapi 0.3.9",
|
||||
"x11 2.21.0",
|
||||
"zstd 0.13.1",
|
||||
]
|
||||
|
||||
@@ -4546,11 +4549,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libxdo-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"x11 2.21.0",
|
||||
"hbb_common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7181,9 +7181,9 @@ dependencies = [
|
||||
"kcp-sys",
|
||||
"keepawake",
|
||||
"lazy_static",
|
||||
"libloading 0.8.4",
|
||||
"libpulse-binding",
|
||||
"libpulse-simple-binding",
|
||||
"libxdo-sys",
|
||||
"mac_address",
|
||||
"magnum-opus",
|
||||
"nix 0.29.0",
|
||||
|
||||
@@ -76,7 +76,6 @@ crossbeam-queue = "0.3"
|
||||
hex = "0.4"
|
||||
chrono = "0.4"
|
||||
cidr-utils = "0.5"
|
||||
libloading = "0.8"
|
||||
fon = "0.6"
|
||||
zip = "0.6"
|
||||
shutdown_hooks = "0.1"
|
||||
@@ -177,6 +176,7 @@ bytemuck = "1.23"
|
||||
ttf-parser = "0.25"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libxdo-sys = "0.11"
|
||||
psimple = { package = "libpulse-simple-binding", version = "2.27" }
|
||||
pulse = { package = "libpulse-binding", version = "2.27" }
|
||||
rust-pulsectl = { git = "https://github.com/rustdesk-org/pulsectl" }
|
||||
@@ -207,6 +207,11 @@ android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" }
|
||||
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"]
|
||||
exclude = ["vdi/host", "examples/custom_plugin"]
|
||||
|
||||
# Patch libxdo-sys to use a stub implementation that doesn't require libxdo
|
||||
# This allows building and running on systems without libxdo installed (e.g., Wayland-only)
|
||||
[patch.crates-io]
|
||||
libxdo-sys = { path = "libs/libxdo-sys-stub" }
|
||||
|
||||
[package.metadata.winres]
|
||||
LegalCopyright = "Copyright © 2025 Purslane Ltd. All rights reserved."
|
||||
ProductName = "RustDesk"
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<p align="center">
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#freie-öffentliche-server">Server</a> •
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Dein Remote-Desktop"><br>
|
||||
<a href="#grobe-schritte-zum-kompilieren">Kompilieren</a> •
|
||||
<a href="#auf-docker-kompilieren">Docker</a> •
|
||||
<a href="#dateistruktur">Dateistruktur</a> •
|
||||
<a href="#screenshots">Screenshots</a><br>
|
||||
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-DA.md">Dansk</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
|
||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a>] | [<a href="docs/README-RO.md">Română</a>]<br>
|
||||
<b>Wir brauchen Ihre Hilfe, um dieses README, die <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk-Benutzeroberfläche</a> und die <a href="https://github.com/rustdesk/doc.rustdesk.com">Dokumentation</a> in Ihre Muttersprache zu übersetzen.</b>
|
||||
</p>
|
||||
|
||||
> [!Vorsicht]
|
||||
> [!Caution]
|
||||
> **Haftungsausschluss bei Missbrauch::** <br>
|
||||
> Die Entwickler von RustDesk billigen oder unterstützen keine unethische oder illegale Nutzung dieser Software. Missbrauch, wie unbefugter Zugriff, unbefugte Kontrolle oder Verletzung der Privatsphäre, verstößt strikt gegen unsere Richtlinien. Die Autoren sind nicht verantwortlich für jeglichen Missbrauch der Anwendung.
|
||||
|
||||
@@ -28,11 +27,14 @@ RustDesk heißt jegliche Mitarbeit willkommen. Schauen Sie sich [CONTRIBUTING-DE
|
||||
|
||||
[**Programm herunterladen**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
[**Nächtliche Erstellung**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
[**Nightly Builds**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||
[<img src="https://flathub.org/api/badge?svg&locale=en"
|
||||
alt="Get it on Flathub"
|
||||
height="80">](https://flathub.org/apps/com.rustdesk.RustDesk)
|
||||
|
||||
## Abhängigkeiten
|
||||
|
||||
@@ -64,18 +66,19 @@ Bitte laden Sie die dynamische Bibliothek Sciter selbst herunter.
|
||||
```sh
|
||||
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
|
||||
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
|
||||
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev
|
||||
```
|
||||
|
||||
### openSUSE Tumbleweed
|
||||
|
||||
```sh
|
||||
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel
|
||||
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
@@ -114,7 +117,7 @@ cd
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
git clone --recurse-submodules https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
@@ -129,6 +132,7 @@ Beginnen Sie damit, das Repository zu klonen und den Docker-Container zu bauen:
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
git submodule update --init --recursive
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
@@ -157,6 +161,7 @@ Bitte stellen Sie sicher, dass Sie diese Befehle im Stammverzeichnis des RustDes
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: Video-Codec, Konfiguration, TCP/UDP-Wrapper, Protokoll-Puffer, fs-Funktionen für Dateitransfer und ein paar andere nützliche Funktionen
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: Bildschirmaufnahme
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: Plattformspezifische Maus- und Tastatursteuerung
|
||||
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: Datei kopieren und einfügen Implementierung für Windows, Linux, macOS.
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: Audio/Zwischenablage/Eingabe/Videodienste und Netzwerkverbindungen
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: Starten einer Peer-Verbindung
|
||||
@@ -167,10 +172,11 @@ Bitte stellen Sie sicher, dass Sie diese Befehle im Stammverzeichnis des RustDes
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -13,7 +13,9 @@ Porozmawiaj z nami na: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](http
|
||||
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Kolejny program do zdalnego pulpitu, napisany w Rust. Działa od samego początku, nie wymaga konfiguracji. Masz pełną kontrolę nad swoimi danymi, bez obaw o bezpieczeństwo. Możesz skorzystać z naszego darmowego serwera publicznego, [skonfigurować własny](https://rustdesk.com/server), lub [napisać własny serwer](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
## O projekcie
|
||||
|
||||
RustDesk to wieloplatformowe oprogramowanie do zdalnego pulpitu, napisane w języku Rust, zaprojektowane z myślą o prostocie wdrożenia, bezpieczeństwie i pełnej kontroli użytkownika nad danymi. Aplikacja działa od razu po uruchomieniu i nie wymaga skomplikowanej konfiguracji. Możesz skorzystać z naszego darmowego serwera publicznego, [skonfigurować własny](https://rustdesk.com/server), lub [napisać własny serwer](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
@@ -31,7 +33,7 @@ RustDesk zaprasza do współpracy każdego. Zobacz [`docs/CONTRIBUTING-PL.md`](C
|
||||
|
||||
## Zależności
|
||||
|
||||
Wersje desktopowe używają [sciter](https://sciter.com/) dla GUI, proszę pobrać samodzielnie bibliotekę sciter.
|
||||
Wersje desktopowe korzystają z biblioteki [sciter](https://sciter.com/) jako silnika GUI. Bibliotekę Sciter należy pobrać i zainstalować samodzielnie.
|
||||
|
||||
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
|
||||
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
|
||||
|
||||
@@ -501,10 +501,10 @@ class MyTheme {
|
||||
)
|
||||
: null,
|
||||
textTheme: const TextTheme(
|
||||
titleLarge: TextStyle(fontSize: 19),
|
||||
titleSmall: TextStyle(fontSize: 14),
|
||||
bodySmall: TextStyle(fontSize: 12, height: 1.25),
|
||||
bodyMedium: TextStyle(fontSize: 14, height: 1.25),
|
||||
titleLarge: TextStyle(fontSize: 19, color: Colors.white70),
|
||||
titleSmall: TextStyle(fontSize: 14, color: Colors.white70),
|
||||
bodySmall: TextStyle(fontSize: 12, color: Colors.white70, height: 1.25),
|
||||
bodyMedium: TextStyle(fontSize: 14, color: Colors.white70, height: 1.25),
|
||||
labelLarge: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -1124,18 +1124,23 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
|
||||
Widget createDialogContent(String text) {
|
||||
final RegExp linkRegExp = RegExp(r'(https?://[^\s]+)');
|
||||
bool hasLink = linkRegExp.hasMatch(text);
|
||||
|
||||
// Early return: no link, use default theme color
|
||||
if (!hasLink) {
|
||||
return SelectableText(text, style: const TextStyle(fontSize: 15));
|
||||
}
|
||||
|
||||
final List<TextSpan> spans = [];
|
||||
int start = 0;
|
||||
bool hasLink = false;
|
||||
|
||||
linkRegExp.allMatches(text).forEach((match) {
|
||||
hasLink = true;
|
||||
if (match.start > start) {
|
||||
spans.add(TextSpan(text: text.substring(start, match.start)));
|
||||
}
|
||||
spans.add(TextSpan(
|
||||
text: match.group(0) ?? '',
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
@@ -1153,13 +1158,9 @@ Widget createDialogContent(String text) {
|
||||
spans.add(TextSpan(text: text.substring(start)));
|
||||
}
|
||||
|
||||
if (!hasLink) {
|
||||
return SelectableText(text, style: const TextStyle(fontSize: 15));
|
||||
}
|
||||
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(color: Colors.black, fontSize: 15),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
children: spans,
|
||||
),
|
||||
);
|
||||
@@ -1578,7 +1579,7 @@ bool option2bool(String option, String value) {
|
||||
option == kOptionForceAlwaysRelay) {
|
||||
res = value == "Y";
|
||||
} else {
|
||||
assert(false);
|
||||
// "" is true
|
||||
res = value != "N";
|
||||
}
|
||||
return res;
|
||||
@@ -1596,9 +1597,6 @@ String bool2option(String option, bool b) {
|
||||
option == kOptionForceAlwaysRelay) {
|
||||
res = b ? 'Y' : defaultOptionNo;
|
||||
} else {
|
||||
if (option != kOptionEnableUdpPunch && option != kOptionEnableIpv6Punch) {
|
||||
assert(false);
|
||||
}
|
||||
res = b ? 'Y' : 'N';
|
||||
}
|
||||
return res;
|
||||
@@ -2684,20 +2682,44 @@ class SimpleWrapper<T> {
|
||||
/// This manager handles multiple tabs within the same isolate.
|
||||
class WakelockManager {
|
||||
static final Set<UniqueKey> _enabledKeys = {};
|
||||
// Don't use WakelockPlus.enabled, it causes error on Android:
|
||||
// Unhandled Exception: FormatException: Message corrupted
|
||||
//
|
||||
// On Linux, multiple enable() calls create only one inhibit, but each disable()
|
||||
// only releases if _cookie != null. So we need our own _enabled state to avoid
|
||||
// calling disable() when not enabled.
|
||||
// See: https://github.com/fluttercommunity/wakelock_plus/blob/0c74e5bbc6aefac57b6c96bb7ef987705ed559ec/wakelock_plus/lib/src/wakelock_plus_linux_plugin.dart#L48
|
||||
static bool _enabled = false;
|
||||
|
||||
static void enable(UniqueKey key) {
|
||||
if (isLinux) return;
|
||||
_enabledKeys.add(key);
|
||||
WakelockPlus.enable();
|
||||
static void enable(UniqueKey key, {bool isServer = false}) {
|
||||
// Check if we should keep awake during outgoing sessions
|
||||
if (!isServer) {
|
||||
final keepAwake =
|
||||
mainGetLocalBoolOptionSync(kOptionKeepAwakeDuringOutgoingSessions);
|
||||
if (!keepAwake) {
|
||||
return; // Don't enable wakelock if user disabled keep awake
|
||||
}
|
||||
}
|
||||
if (isDesktop) {
|
||||
_enabledKeys.add(key);
|
||||
}
|
||||
if (!_enabled) {
|
||||
_enabled = true;
|
||||
WakelockPlus.enable();
|
||||
}
|
||||
}
|
||||
|
||||
static void disable(UniqueKey key) {
|
||||
if (isLinux) return;
|
||||
if (_enabledKeys.remove(key)) {
|
||||
if (_enabledKeys.isEmpty) {
|
||||
WakelockPlus.disable();
|
||||
if (isDesktop) {
|
||||
_enabledKeys.remove(key);
|
||||
if (_enabledKeys.isNotEmpty) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_enabled) {
|
||||
WakelockPlus.disable();
|
||||
_enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
|
||||
GestureDragStartCallback? onOneFingerPanStart;
|
||||
GestureDragUpdateCallback? onOneFingerPanUpdate;
|
||||
GestureDragEndCallback? onOneFingerPanEnd;
|
||||
GestureDragCancelCallback? onOneFingerPanCancel;
|
||||
|
||||
// twoFingerScale : scale + pan event
|
||||
GestureScaleStartCallback? onTwoFingerScaleStart;
|
||||
@@ -169,6 +170,27 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
|
||||
|
||||
DragEndDetails _getDragEndDetails(ScaleEndDetails d) =>
|
||||
DragEndDetails(velocity: d.velocity);
|
||||
|
||||
@override
|
||||
void rejectGesture(int pointer) {
|
||||
super.rejectGesture(pointer);
|
||||
switch (_currentState) {
|
||||
case GestureState.oneFingerPan:
|
||||
if (onOneFingerPanCancel != null) {
|
||||
onOneFingerPanCancel!();
|
||||
}
|
||||
break;
|
||||
case GestureState.twoFingerScale:
|
||||
// Reset scale state if needed, currently self-contained
|
||||
break;
|
||||
case GestureState.threeFingerVerticalDrag:
|
||||
// Reset drag state if needed, currently self-contained
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_currentState = GestureState.none;
|
||||
}
|
||||
}
|
||||
|
||||
class HoldTapMoveGestureRecognizer extends GestureRecognizer {
|
||||
@@ -717,6 +739,7 @@ RawGestureDetector getMixinGestureDetector({
|
||||
GestureDragStartCallback? onOneFingerPanStart,
|
||||
GestureDragUpdateCallback? onOneFingerPanUpdate,
|
||||
GestureDragEndCallback? onOneFingerPanEnd,
|
||||
GestureDragCancelCallback? onOneFingerPanCancel,
|
||||
GestureScaleUpdateCallback? onTwoFingerScaleUpdate,
|
||||
GestureScaleEndCallback? onTwoFingerScaleEnd,
|
||||
GestureDragUpdateCallback? onThreeFingerVerticalDragUpdate,
|
||||
@@ -765,6 +788,7 @@ RawGestureDetector getMixinGestureDetector({
|
||||
..onOneFingerPanStart = onOneFingerPanStart
|
||||
..onOneFingerPanUpdate = onOneFingerPanUpdate
|
||||
..onOneFingerPanEnd = onOneFingerPanEnd
|
||||
..onOneFingerPanCancel = onOneFingerPanCancel
|
||||
..onTwoFingerScaleUpdate = onTwoFingerScaleUpdate
|
||||
..onTwoFingerScaleEnd = onTwoFingerScaleEnd
|
||||
..onThreeFingerVerticalDragUpdate = onThreeFingerVerticalDragUpdate;
|
||||
|
||||
@@ -107,6 +107,8 @@ class _RawTouchGestureDetectorRegionState
|
||||
// For mouse mode, we need to block the events when the cursor is in a blocked area.
|
||||
// So we need to cache the last tap down position.
|
||||
Offset? _lastTapDownPositionForMouseMode;
|
||||
// Cache global position for onTap (which lacks position info).
|
||||
Offset? _lastTapDownGlobalPosition;
|
||||
|
||||
FFI get ffi => widget.ffi;
|
||||
FfiModel get ffiModel => widget.ffiModel;
|
||||
@@ -136,6 +138,7 @@ class _RawTouchGestureDetectorRegionState
|
||||
|
||||
onTapDown(TapDownDetails d) async {
|
||||
lastDeviceKind = d.kind;
|
||||
_lastTapDownGlobalPosition = d.globalPosition;
|
||||
if (isNotTouchBasedDevice()) {
|
||||
return;
|
||||
}
|
||||
@@ -154,11 +157,16 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (isNotTouchBasedDevice()) {
|
||||
return;
|
||||
}
|
||||
// Filter duplicate touch tap events on iOS (Magic Mouse issue).
|
||||
if (inputModel.shouldIgnoreTouchTap(d.globalPosition)) {
|
||||
return;
|
||||
}
|
||||
if (handleTouch) {
|
||||
final isMoved =
|
||||
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||
if (isMoved) {
|
||||
if (lastTapDownDetails != null) {
|
||||
// If pan already handled 'down', don't send it again.
|
||||
if (lastTapDownDetails != null && !_touchModePanStarted) {
|
||||
await inputModel.tapDown(MouseButtons.left);
|
||||
}
|
||||
await inputModel.tapUp(MouseButtons.left);
|
||||
@@ -170,6 +178,11 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (isNotTouchBasedDevice()) {
|
||||
return;
|
||||
}
|
||||
// Filter duplicate touch tap events on iOS (Magic Mouse issue).
|
||||
final lastPos = _lastTapDownGlobalPosition;
|
||||
if (lastPos != null && inputModel.shouldIgnoreTouchTap(lastPos)) {
|
||||
return;
|
||||
}
|
||||
if (!handleTouch) {
|
||||
// Cannot use `_lastTapDownDetails` because Flutter calls `onTapUp` before `onTap`, clearing the cached details.
|
||||
// Using `_lastTapDownPositionForMouseMode` instead.
|
||||
@@ -424,6 +437,14 @@ class _RawTouchGestureDetectorRegionState
|
||||
}
|
||||
}
|
||||
|
||||
// Reset `_touchModePanStarted` if the one-finger pan gesture is cancelled
|
||||
// or rejected by the gesture arena. Without this, the flag can remain
|
||||
// stuck in the "started" state and cause issues such as the Magic Mouse
|
||||
// double-click problem on iPad with magic mouse.
|
||||
onOneFingerPanCancel() {
|
||||
_touchModePanStarted = false;
|
||||
}
|
||||
|
||||
// scale + pan event
|
||||
onTwoFingerScaleStart(ScaleStartDetails d) {
|
||||
_lastTapDownDetails = null;
|
||||
@@ -557,6 +578,7 @@ class _RawTouchGestureDetectorRegionState
|
||||
instance
|
||||
..onOneFingerPanUpdate = onOneFingerPanUpdate
|
||||
..onOneFingerPanEnd = onOneFingerPanEnd
|
||||
..onOneFingerPanCancel = onOneFingerPanCancel
|
||||
..onTwoFingerScaleStart = onTwoFingerScaleStart
|
||||
..onTwoFingerScaleUpdate = onTwoFingerScaleUpdate
|
||||
..onTwoFingerScaleEnd = onTwoFingerScaleEnd
|
||||
|
||||
@@ -194,6 +194,9 @@ const String kOptionDisableFloatingWindow = "disable-floating-window";
|
||||
|
||||
const String kOptionKeepScreenOn = "keep-screen-on";
|
||||
|
||||
const String kOptionKeepAwakeDuringIncomingSessions = "keep-awake-during-incoming-sessions";
|
||||
const String kOptionKeepAwakeDuringOutgoingSessions = "keep-awake-during-outgoing-sessions";
|
||||
|
||||
const String kOptionShowMobileAction = "showMobileActions";
|
||||
|
||||
const String kUrlActionClose = "close";
|
||||
|
||||
@@ -450,7 +450,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
"${translate("new-version-of-{${bind.mainGetAppNameSync()}}-tip")} (${bind.mainGetNewVersion()}).",
|
||||
btnText,
|
||||
onPressed,
|
||||
closeButton: true);
|
||||
closeButton: true,
|
||||
help: isToUpdate ? 'Changelog' : null,
|
||||
link: isToUpdate
|
||||
? 'https://github.com/rustdesk/rustdesk/releases/tag/${bind.mainGetNewVersion()}'
|
||||
: null);
|
||||
}
|
||||
if (systemError.isNotEmpty) {
|
||||
return buildInstallCard("", systemError, "", () {});
|
||||
|
||||
@@ -557,6 +557,17 @@ class _GeneralState extends State<_General> {
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Add client-side wakelock option for desktop platforms
|
||||
if (!bind.isIncomingOnly()) {
|
||||
children.add(_OptionCheckBox(
|
||||
context,
|
||||
'keep-awake-during-outgoing-sessions-label',
|
||||
kOptionKeepAwakeDuringOutgoingSessions,
|
||||
isServer: false,
|
||||
));
|
||||
}
|
||||
|
||||
if (!isWeb && bind.mainShowOption(key: kOptionAllowLinuxHeadless)) {
|
||||
children.add(_OptionCheckBox(
|
||||
context, 'Allow linux headless', kOptionAllowLinuxHeadless));
|
||||
@@ -1219,6 +1230,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
...directIp(context),
|
||||
whitelist(),
|
||||
...autoDisconnect(context),
|
||||
_OptionCheckBox(context, 'keep-awake-during-incoming-sessions-label',
|
||||
kOptionKeepAwakeDuringIncomingSessions,
|
||||
reverse: false, enabled: enabled),
|
||||
if (bind.mainIsInstalled())
|
||||
_OptionCheckBox(context, 'allow-only-conn-window-open-tip',
|
||||
'allow-only-conn-window-open',
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
||||
import 'package:flutter_hbb/models/file_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:toggle_switch/toggle_switch.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../common/widgets/dialog.dart';
|
||||
@@ -72,6 +71,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
showLocal ? model.localController : model.remoteController;
|
||||
FileDirectory get currentDir => currentFileController.directory.value;
|
||||
DirectoryOptions get currentOptions => currentFileController.options.value;
|
||||
final _uniqueKey = UniqueKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -86,7 +86,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||
});
|
||||
gFFI.ffiModel.updateEventListener(gFFI.sessionId, widget.id);
|
||||
WakelockPlus.enable();
|
||||
WakelockManager.enable(_uniqueKey);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -94,7 +94,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
model.close().whenComplete(() {
|
||||
gFFI.close();
|
||||
gFFI.dialogManager.dismissAll();
|
||||
WakelockPlus.disable();
|
||||
WakelockManager.disable(_uniqueKey);
|
||||
});
|
||||
model.jobController.clear();
|
||||
super.dispose();
|
||||
|
||||
@@ -14,7 +14,6 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../common/widgets/overlay.dart';
|
||||
@@ -67,7 +66,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
String _value = '';
|
||||
Orientation? _currentOrientation;
|
||||
double _viewInsetsBottom = 0;
|
||||
|
||||
final _uniqueKey = UniqueKey();
|
||||
Timer? _timerDidChangeMetrics;
|
||||
|
||||
final _blockableOverlayState = BlockableOverlayState();
|
||||
@@ -105,9 +104,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
gFFI.dialogManager
|
||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||
});
|
||||
if (!isWeb) {
|
||||
WakelockPlus.enable();
|
||||
}
|
||||
WakelockManager.enable(_uniqueKey);
|
||||
_physicalFocusNode.requestFocus();
|
||||
gFFI.inputModel.listenToMouse(true);
|
||||
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||
@@ -146,9 +143,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
gFFI.dialogManager.dismissAll();
|
||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values);
|
||||
if (!isWeb) {
|
||||
await WakelockPlus.disable();
|
||||
}
|
||||
WakelockManager.disable(_uniqueKey);
|
||||
await keyboardSubscription.cancel();
|
||||
removeSharedStates(widget.id);
|
||||
// `on_voice_call_closed` should be called when the connection is ended.
|
||||
|
||||
@@ -100,6 +100,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
var _enableIpv6Punch = false;
|
||||
var _isUsingPublicServer = false;
|
||||
var _allowAskForNoteAtEndOfConnection = false;
|
||||
var _preventSleepWhileConnected = true;
|
||||
|
||||
_SettingsState() {
|
||||
_enableAbr = option2bool(
|
||||
@@ -140,6 +141,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
_enableIpv6Punch = mainGetLocalBoolOptionSync(kOptionEnableIpv6Punch);
|
||||
_allowAskForNoteAtEndOfConnection =
|
||||
mainGetLocalBoolOptionSync(kOptionAllowAskForNoteAtEndOfConnection);
|
||||
_preventSleepWhileConnected =
|
||||
mainGetLocalBoolOptionSync(kOptionKeepAwakeDuringOutgoingSessions);
|
||||
_showTerminalExtraKeys =
|
||||
mainGetLocalBoolOptionSync(kOptionEnableShowTerminalExtraKeys);
|
||||
}
|
||||
@@ -823,7 +826,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
_allowAskForNoteAtEndOfConnection = newValue;
|
||||
});
|
||||
},
|
||||
)
|
||||
),
|
||||
if (!incomingOnly)
|
||||
SettingsTile.switchTile(
|
||||
title: Text(translate('keep-awake-during-outgoing-sessions-label')),
|
||||
initialValue: _preventSleepWhileConnected,
|
||||
onToggle: (v) async {
|
||||
await mainSetLocalBoolOption(kOptionKeepAwakeDuringOutgoingSessions, v);
|
||||
setState(() {
|
||||
_preventSleepWhileConnected = v;
|
||||
});
|
||||
},
|
||||
),
|
||||
]),
|
||||
if (isAndroid)
|
||||
SettingsSection(title: Text(translate('Hardware Codec')), tiles: [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
@@ -41,6 +42,9 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
final GlobalKey _keyboardKey = GlobalKey();
|
||||
double _keyboardHeight = 0;
|
||||
late bool _showTerminalExtraKeys;
|
||||
// For iOS edge swipe gesture
|
||||
double _swipeStartX = 0;
|
||||
double _swipeCurrentX = 0;
|
||||
|
||||
// For web only.
|
||||
// 'monospace' does not work on web, use Google Fonts, `??` is only for null safety.
|
||||
@@ -147,7 +151,7 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
}
|
||||
|
||||
Widget buildBody() {
|
||||
return Scaffold(
|
||||
final scaffold = Scaffold(
|
||||
resizeToAvoidBottomInset: false, // Disable automatic layout adjustment; manually control UI updates to prevent flickering when the keyboard shows/hides
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
body: Stack(
|
||||
@@ -164,6 +168,13 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
autofocus: true,
|
||||
textStyle: _getTerminalStyle(),
|
||||
backgroundOpacity: 0.7,
|
||||
// The following comment is from xterm.dart source code:
|
||||
// Workaround to detect delete key for platforms and IMEs that do not
|
||||
// emit a hardware delete event. Preferred on mobile platforms. [false] by
|
||||
// default.
|
||||
//
|
||||
// Android works fine without this workaround.
|
||||
deleteDetection: isIOS,
|
||||
padding: _calculatePadding(heightPx),
|
||||
onSecondaryTapDown: (details, offset) async {
|
||||
final selection = _terminalModel.terminalController.selection;
|
||||
@@ -185,9 +196,108 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
),
|
||||
),
|
||||
if (_showTerminalExtraKeys) _buildFloatingKeyboard(),
|
||||
// iOS-style circular close button in top-right corner
|
||||
if (isIOS) _buildCloseButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// Add iOS edge swipe gesture to exit (similar to Android back button)
|
||||
if (isIOS) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final screenWidth = constraints.maxWidth;
|
||||
// Base thresholds on screen width but clamp to reasonable logical pixel ranges
|
||||
// Edge detection region: ~10% of width, clamped between 20 and 80 logical pixels
|
||||
final edgeThreshold = (screenWidth * 0.1).clamp(20.0, 80.0);
|
||||
// Required horizontal movement: ~25% of width, clamped between 80 and 300 logical pixels
|
||||
final swipeThreshold = (screenWidth * 0.25).clamp(80.0, 300.0);
|
||||
|
||||
return RawGestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
gestures: <Type, GestureRecognizerFactory>{
|
||||
HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
|
||||
() => HorizontalDragGestureRecognizer(
|
||||
debugOwner: this,
|
||||
// Only respond to touch input, exclude mouse/trackpad
|
||||
supportedDevices: kTouchBasedDeviceKinds,
|
||||
),
|
||||
(HorizontalDragGestureRecognizer instance) {
|
||||
instance
|
||||
// Capture initial touch-down position (before touch slop)
|
||||
..onDown = (details) {
|
||||
_swipeStartX = details.localPosition.dx;
|
||||
_swipeCurrentX = details.localPosition.dx;
|
||||
}
|
||||
..onUpdate = (details) {
|
||||
_swipeCurrentX = details.localPosition.dx;
|
||||
}
|
||||
..onEnd = (details) {
|
||||
// Check if swipe started from left edge and moved right
|
||||
if (_swipeStartX < edgeThreshold && (_swipeCurrentX - _swipeStartX) > swipeThreshold) {
|
||||
clientClose(sessionId, _ffi);
|
||||
}
|
||||
_swipeStartX = 0;
|
||||
_swipeCurrentX = 0;
|
||||
}
|
||||
..onCancel = () {
|
||||
_swipeStartX = 0;
|
||||
_swipeCurrentX = 0;
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
child: scaffold,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return scaffold;
|
||||
}
|
||||
|
||||
Widget _buildCloseButton() {
|
||||
return Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: SafeArea(
|
||||
minimum: const EdgeInsets.only(
|
||||
top: 16, // iOS standard margin
|
||||
right: 16, // iOS standard margin
|
||||
),
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: translate('Close'),
|
||||
child: Container(
|
||||
width: 44, // iOS standard tap target size
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.5), // Half transparency
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
shape: const CircleBorder(),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () {
|
||||
clientClose(sessionId, _ffi);
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Close'),
|
||||
child: const Icon(
|
||||
Icons.chevron_left, // iOS-style back arrow
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFloatingKeyboard() {
|
||||
|
||||
@@ -11,7 +11,6 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../common/widgets/overlay.dart';
|
||||
@@ -62,7 +61,7 @@ class _ViewCameraPageState extends State<ViewCameraPage>
|
||||
bool _showGestureHelp = false;
|
||||
Orientation? _currentOrientation;
|
||||
double _viewInsetsBottom = 0;
|
||||
|
||||
final _uniqueKey = UniqueKey();
|
||||
Timer? _timerDidChangeMetrics;
|
||||
|
||||
final _blockableOverlayState = BlockableOverlayState();
|
||||
@@ -100,9 +99,7 @@ class _ViewCameraPageState extends State<ViewCameraPage>
|
||||
gFFI.dialogManager
|
||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||
});
|
||||
if (!isWeb) {
|
||||
WakelockPlus.enable();
|
||||
}
|
||||
WakelockManager.enable(_uniqueKey);
|
||||
_physicalFocusNode.requestFocus();
|
||||
gFFI.inputModel.listenToMouse(true);
|
||||
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||
@@ -139,9 +136,7 @@ class _ViewCameraPageState extends State<ViewCameraPage>
|
||||
gFFI.dialogManager.dismissAll();
|
||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values);
|
||||
if (!isWeb) {
|
||||
await WakelockPlus.disable();
|
||||
}
|
||||
WakelockManager.disable(_uniqueKey);
|
||||
removeSharedStates(widget.id);
|
||||
// `on_voice_call_closed` should be called when the connection is ended.
|
||||
// The inner logic of `on_voice_call_closed` will check if the voice call is active.
|
||||
|
||||
@@ -59,7 +59,8 @@ class CanvasCoords {
|
||||
model.scale = json['scale'];
|
||||
model.scrollX = json['scrollX'];
|
||||
model.scrollY = json['scrollY'];
|
||||
model.scrollStyle = ScrollStyle.fromJson(json['scrollStyle'], ScrollStyle.scrollauto);
|
||||
model.scrollStyle =
|
||||
ScrollStyle.fromJson(json['scrollStyle'], ScrollStyle.scrollauto);
|
||||
model.size = Size(json['size']['w'], json['size']['h']);
|
||||
return model;
|
||||
}
|
||||
@@ -418,6 +419,74 @@ class InputModel {
|
||||
});
|
||||
}
|
||||
|
||||
// https://github.com/flutter/flutter/issues/157241
|
||||
// Infer CapsLock state from the character output.
|
||||
// This is needed because Flutter's HardwareKeyboard.lockModesEnabled may report
|
||||
// incorrect CapsLock state on iOS.
|
||||
bool _getIosCapsFromCharacter(KeyEvent e) {
|
||||
if (!isIOS) return false;
|
||||
final ch = e.character;
|
||||
return _getIosCapsFromCharacterImpl(
|
||||
ch, HardwareKeyboard.instance.isShiftPressed);
|
||||
}
|
||||
|
||||
// RawKeyEvent version of _getIosCapsFromCharacter.
|
||||
bool _getIosCapsFromRawCharacter(RawKeyEvent e) {
|
||||
if (!isIOS) return false;
|
||||
final ch = e.character;
|
||||
return _getIosCapsFromCharacterImpl(ch, e.isShiftPressed);
|
||||
}
|
||||
|
||||
// Shared implementation for inferring CapsLock state from character.
|
||||
// Uses Unicode-aware case detection to support non-ASCII letters (e.g., ü/Ü, é/É).
|
||||
//
|
||||
// Limitations:
|
||||
// 1. This inference assumes the client and server use the same keyboard layout.
|
||||
// If layouts differ (e.g., client uses EN, server uses DE), the character output
|
||||
// may not match expectations. For example, ';' on EN layout maps to 'ö' on DE
|
||||
// layout, making it impossible to correctly infer CapsLock state from the
|
||||
// character alone.
|
||||
// 2. On iOS, CapsLock+Shift produces uppercase letters (unlike desktop where it
|
||||
// produces lowercase). This method cannot handle that case correctly.
|
||||
bool _getIosCapsFromCharacterImpl(String? ch, bool shiftPressed) {
|
||||
if (ch == null || ch.length != 1) return false;
|
||||
// Use Dart's built-in Unicode-aware case detection
|
||||
final upper = ch.toUpperCase();
|
||||
final lower = ch.toLowerCase();
|
||||
final isUpper = upper == ch && lower != ch;
|
||||
final isLower = lower == ch && upper != ch;
|
||||
// Skip non-letter characters (e.g., numbers, symbols, CJK characters without case)
|
||||
if (!isUpper && !isLower) return false;
|
||||
return isUpper != shiftPressed;
|
||||
}
|
||||
|
||||
int _buildLockModes(bool iosCapsLock) {
|
||||
const capslock = 1;
|
||||
const numlock = 2;
|
||||
const scrolllock = 3;
|
||||
int lockModes = 0;
|
||||
if (isIOS) {
|
||||
if (iosCapsLock) {
|
||||
lockModes |= (1 << capslock);
|
||||
}
|
||||
// Ignore "NumLock/ScrollLock" on iOS for now.
|
||||
} else {
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.capsLock)) {
|
||||
lockModes |= (1 << capslock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.numLock)) {
|
||||
lockModes |= (1 << numlock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.scrollLock)) {
|
||||
lockModes |= (1 << scrolllock);
|
||||
}
|
||||
}
|
||||
return lockModes;
|
||||
}
|
||||
|
||||
// This function must be called after the peer info is received.
|
||||
// Because `sessionGetKeyboardMode` relies on the peer version.
|
||||
updateKeyboardMode() async {
|
||||
@@ -550,6 +619,11 @@ class InputModel {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
bool iosCapsLock = false;
|
||||
if (isIOS && e is RawKeyDownEvent) {
|
||||
iosCapsLock = _getIosCapsFromRawCharacter(e);
|
||||
}
|
||||
|
||||
final key = e.logicalKey;
|
||||
if (e is RawKeyDownEvent) {
|
||||
if (!e.repeat) {
|
||||
@@ -586,7 +660,7 @@ class InputModel {
|
||||
|
||||
// * Currently mobile does not enable map mode
|
||||
if ((isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) {
|
||||
mapKeyboardModeRaw(e);
|
||||
mapKeyboardModeRaw(e, iosCapsLock);
|
||||
} else {
|
||||
legacyKeyboardModeRaw(e);
|
||||
}
|
||||
@@ -622,6 +696,11 @@ class InputModel {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
bool iosCapsLock = false;
|
||||
if (isIOS && (e is KeyDownEvent || e is KeyRepeatEvent)) {
|
||||
iosCapsLock = _getIosCapsFromCharacter(e);
|
||||
}
|
||||
|
||||
if (e is KeyUpEvent) {
|
||||
handleKeyUpEventModifiers(e);
|
||||
} else if (e is KeyDownEvent) {
|
||||
@@ -667,7 +746,8 @@ class InputModel {
|
||||
e.character ?? '',
|
||||
e.physicalKey.usbHidUsage & 0xFFFF,
|
||||
// Show repeat event be converted to "release+press" events?
|
||||
e is KeyDownEvent || e is KeyRepeatEvent);
|
||||
e is KeyDownEvent || e is KeyRepeatEvent,
|
||||
iosCapsLock);
|
||||
} else {
|
||||
legacyKeyboardMode(e);
|
||||
}
|
||||
@@ -676,23 +756,9 @@ class InputModel {
|
||||
}
|
||||
|
||||
/// Send Key Event
|
||||
void newKeyboardMode(String character, int usbHid, bool down) {
|
||||
const capslock = 1;
|
||||
const numlock = 2;
|
||||
const scrolllock = 3;
|
||||
int lockModes = 0;
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.capsLock)) {
|
||||
lockModes |= (1 << capslock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.numLock)) {
|
||||
lockModes |= (1 << numlock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.scrollLock)) {
|
||||
lockModes |= (1 << scrolllock);
|
||||
}
|
||||
void newKeyboardMode(
|
||||
String character, int usbHid, bool down, bool iosCapsLock) {
|
||||
final lockModes = _buildLockModes(iosCapsLock);
|
||||
bind.sessionHandleFlutterKeyEvent(
|
||||
sessionId: sessionId,
|
||||
character: character,
|
||||
@@ -701,7 +767,7 @@ class InputModel {
|
||||
downOrUp: down);
|
||||
}
|
||||
|
||||
void mapKeyboardModeRaw(RawKeyEvent e) {
|
||||
void mapKeyboardModeRaw(RawKeyEvent e, bool iosCapsLock) {
|
||||
int positionCode = -1;
|
||||
int platformCode = -1;
|
||||
bool down;
|
||||
@@ -732,27 +798,14 @@ class InputModel {
|
||||
} else {
|
||||
down = false;
|
||||
}
|
||||
inputRawKey(e.character ?? '', platformCode, positionCode, down);
|
||||
inputRawKey(
|
||||
e.character ?? '', platformCode, positionCode, down, iosCapsLock);
|
||||
}
|
||||
|
||||
/// Send raw Key Event
|
||||
void inputRawKey(String name, int platformCode, int positionCode, bool down) {
|
||||
const capslock = 1;
|
||||
const numlock = 2;
|
||||
const scrolllock = 3;
|
||||
int lockModes = 0;
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.capsLock)) {
|
||||
lockModes |= (1 << capslock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.numLock)) {
|
||||
lockModes |= (1 << numlock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.scrollLock)) {
|
||||
lockModes |= (1 << scrolllock);
|
||||
}
|
||||
void inputRawKey(String name, int platformCode, int positionCode, bool down,
|
||||
bool iosCapsLock) {
|
||||
final lockModes = _buildLockModes(iosCapsLock);
|
||||
bind.sessionHandleFlutterRawKeyEvent(
|
||||
sessionId: sessionId,
|
||||
name: name,
|
||||
@@ -826,6 +879,9 @@ class InputModel {
|
||||
Map<String, dynamic> _getMouseEvent(PointerEvent evt, String type) {
|
||||
final Map<String, dynamic> out = {};
|
||||
|
||||
bool hasStaleButtonsOnMouseUp =
|
||||
type == _kMouseEventUp && evt.buttons == _lastButtons;
|
||||
|
||||
// Check update event type and set buttons to be sent.
|
||||
int buttons = _lastButtons;
|
||||
if (type == _kMouseEventMove) {
|
||||
@@ -850,7 +906,7 @@ class InputModel {
|
||||
buttons = evt.buttons;
|
||||
}
|
||||
}
|
||||
_lastButtons = evt.buttons;
|
||||
_lastButtons = hasStaleButtonsOnMouseUp ? 0 : evt.buttons;
|
||||
|
||||
out['buttons'] = buttons;
|
||||
out['type'] = type;
|
||||
@@ -1048,6 +1104,14 @@ class InputModel {
|
||||
if (isViewOnly && !showMyCursor) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
|
||||
// May fix https://github.com/rustdesk/rustdesk/issues/13009
|
||||
if (isIOS && e.synthesized && e.position == Offset.zero && e.buttons == 0) {
|
||||
// iOS may emit a synthesized hover event at (0,0) when the mouse is disconnected.
|
||||
// Ignore this event to prevent cursor jumping.
|
||||
debugPrint('Ignored synthesized hover at (0,0) on iOS');
|
||||
return;
|
||||
}
|
||||
|
||||
// Only update pointer region when relative mouse mode is enabled.
|
||||
// This avoids unnecessary tracking when not in relative mode.
|
||||
if (_relativeMouse.enabled.value) {
|
||||
@@ -1210,6 +1274,28 @@ class InputModel {
|
||||
_trackpadLastDelta = Offset.zero;
|
||||
}
|
||||
|
||||
// iOS Magic Mouse duplicate event detection.
|
||||
// When using Magic Mouse on iPad, iOS may emit both mouse and touch events
|
||||
// for the same click in certain areas (like top-left corner).
|
||||
int _lastMouseDownTimeMs = 0;
|
||||
ui.Offset _lastMouseDownPos = ui.Offset.zero;
|
||||
|
||||
/// Check if a touch tap event should be ignored because it's a duplicate
|
||||
/// of a recent mouse event (iOS Magic Mouse issue).
|
||||
bool shouldIgnoreTouchTap(ui.Offset pos) {
|
||||
if (!isIOS) return false;
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||
final dt = nowMs - _lastMouseDownTimeMs;
|
||||
final distance = (_lastMouseDownPos - pos).distance;
|
||||
// If touch tap is within 2000ms and 80px of the last mouse down,
|
||||
// it's likely a duplicate event from the same Magic Mouse click.
|
||||
if (dt >= 0 && dt < 2000 && distance < 80.0) {
|
||||
debugPrint("shouldIgnoreTouchTap: IGNORED (dt=$dt, dist=$distance)");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onPointDownImage(PointerDownEvent e) {
|
||||
debugPrint("onPointDownImage ${e.kind}");
|
||||
_stopFling = true;
|
||||
@@ -1219,6 +1305,13 @@ class InputModel {
|
||||
if (isViewOnly && !showMyCursor) return;
|
||||
if (isViewCamera) return;
|
||||
|
||||
// Track mouse down events for duplicate detection on iOS.
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||
if (e.kind == ui.PointerDeviceKind.mouse) {
|
||||
_lastMouseDownTimeMs = nowMs;
|
||||
_lastMouseDownPos = e.position;
|
||||
}
|
||||
|
||||
if (_relativeMouse.enabled.value) {
|
||||
_relativeMouse.updatePointerRegionTopLeftGlobal(e);
|
||||
}
|
||||
@@ -1760,9 +1853,9 @@ class InputModel {
|
||||
// Simulate a key press event.
|
||||
// `usbHidUsage` is the USB HID usage code of the key.
|
||||
Future<void> tapHidKey(int usbHidUsage) async {
|
||||
newKeyboardMode(kKeyFlutterKey, usbHidUsage, true);
|
||||
newKeyboardMode(kKeyFlutterKey, usbHidUsage, true, false);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
newKeyboardMode(kKeyFlutterKey, usbHidUsage, false);
|
||||
newKeyboardMode(kKeyFlutterKey, usbHidUsage, false, false);
|
||||
}
|
||||
|
||||
Future<void> onMobileVolumeUp() async =>
|
||||
|
||||
@@ -120,6 +120,7 @@ class FfiModel with ChangeNotifier {
|
||||
late VirtualMouseMode virtualMouseMode;
|
||||
Timer? _timer;
|
||||
var _reconnects = 1;
|
||||
DateTime? _offlineReconnectStartTime;
|
||||
bool _viewOnly = false;
|
||||
bool _showMyCursor = false;
|
||||
WeakReference<FFI> parent;
|
||||
@@ -783,7 +784,8 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateCurDisplay(SessionID sessionId, {updateCursorPos = false}) async {
|
||||
Future<void> updateCurDisplay(SessionID sessionId,
|
||||
{updateCursorPos = false}) async {
|
||||
final newRect = displaysRect();
|
||||
if (newRect == null) {
|
||||
return;
|
||||
@@ -939,11 +941,46 @@ class FfiModel with ChangeNotifier {
|
||||
showPrivacyFailedDialog(
|
||||
sessionId, type, title, text, link, hasRetry, dialogManager);
|
||||
} else {
|
||||
final hasRetry = evt['hasRetry'] == 'true';
|
||||
var hasRetry = evt['hasRetry'] == 'true';
|
||||
if (!hasRetry) {
|
||||
hasRetry = shouldAutoRetryOnOffline(type, title, text);
|
||||
}
|
||||
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
|
||||
}
|
||||
}
|
||||
|
||||
/// Auto-retry check for "Remote desktop is offline" error.
|
||||
/// returns true to auto-retry, false otherwise.
|
||||
bool shouldAutoRetryOnOffline(
|
||||
String type,
|
||||
String title,
|
||||
String text,
|
||||
) {
|
||||
if (type == 'error' &&
|
||||
title == 'Connection Error' &&
|
||||
text == 'Remote desktop is offline' &&
|
||||
_pi.isSet.isTrue) {
|
||||
// Auto retry for ~30s (server's peer offline threshold) when controlled peer's account changes
|
||||
// (e.g., signout, switch user, login into OS) causes temporary offline via websocket/tcp connection.
|
||||
// The actual wait may exceed 30s (e.g., 20s elapsed + 16s next retry = 36s), which is acceptable
|
||||
// since the controlled side reconnects quickly after account changes.
|
||||
// Uses time-based check instead of _reconnects count because user can manually retry.
|
||||
// https://github.com/rustdesk/rustdesk/discussions/14048
|
||||
if (_offlineReconnectStartTime == null) {
|
||||
// First offline, record time and start retry
|
||||
_offlineReconnectStartTime = DateTime.now();
|
||||
return true;
|
||||
} else {
|
||||
final elapsed =
|
||||
DateTime.now().difference(_offlineReconnectStartTime!).inSeconds;
|
||||
if (elapsed < 30) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
handleToast(Map<String, dynamic> evt, SessionID sessionId, String peerId) {
|
||||
final type = evt['type'] ?? 'info';
|
||||
final text = evt['text'] ?? '';
|
||||
@@ -1001,6 +1038,7 @@ class FfiModel with ChangeNotifier {
|
||||
_reconnects *= 2;
|
||||
} else {
|
||||
_reconnects = 1;
|
||||
_offlineReconnectStartTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,6 +1361,7 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
if (displays.isNotEmpty) {
|
||||
_reconnects = 1;
|
||||
_offlineReconnectStartTime = null;
|
||||
waitForFirstImage.value = true;
|
||||
isRefreshing = false;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:flutter_hbb/mobile/pages/settings_page.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../common.dart';
|
||||
@@ -51,6 +50,8 @@ class ServerModel with ChangeNotifier {
|
||||
|
||||
Timer? cmHiddenTimer;
|
||||
|
||||
final _wakelockKey = UniqueKey();
|
||||
|
||||
bool get isStart => _isStart;
|
||||
|
||||
bool get mediaOk => _mediaOk;
|
||||
@@ -466,10 +467,8 @@ class ServerModel with ChangeNotifier {
|
||||
await parent.target?.invokeMethod("stop_service");
|
||||
await bind.mainStopService();
|
||||
notifyListeners();
|
||||
if (!isLinux) {
|
||||
// current linux is not supported
|
||||
WakelockPlus.disable();
|
||||
}
|
||||
// for androidUpdatekeepScreenOn only
|
||||
WakelockManager.disable(_wakelockKey);
|
||||
}
|
||||
|
||||
Future<bool> setPermanentPassword(String newPW) async {
|
||||
@@ -613,12 +612,12 @@ class ServerModel with ChangeNotifier {
|
||||
void showLoginDialog(Client client) {
|
||||
showClientDialog(
|
||||
client,
|
||||
client.isFileTransfer
|
||||
? "Transfer file"
|
||||
client.isFileTransfer
|
||||
? "Transfer file"
|
||||
: client.isViewCamera
|
||||
? "View camera"
|
||||
: client.isTerminal
|
||||
? "Terminal"
|
||||
: client.isTerminal
|
||||
? "Terminal"
|
||||
: "Share screen",
|
||||
'Do you accept?',
|
||||
'android_new_connection_tip',
|
||||
@@ -797,12 +796,10 @@ class ServerModel with ChangeNotifier {
|
||||
final on = ((keepScreenOn == KeepScreenOn.serviceOn) && _isStart) ||
|
||||
(keepScreenOn == KeepScreenOn.duringControlled &&
|
||||
_clients.map((e) => !e.disconnected).isNotEmpty);
|
||||
if (on != await WakelockPlus.enabled) {
|
||||
if (on) {
|
||||
WakelockPlus.enable();
|
||||
} else {
|
||||
WakelockPlus.disable();
|
||||
}
|
||||
if (on) {
|
||||
WakelockManager.enable(_wakelockKey, isServer: true);
|
||||
} else {
|
||||
WakelockManager.disable(_wakelockKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ core-graphics = "0.22"
|
||||
objc = "0.2"
|
||||
unicode-segmentation = "1.10"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libxdo-sys = "0.11"
|
||||
|
||||
[build-dependencies]
|
||||
pkg-config = "0.3"
|
||||
|
||||
@@ -1,50 +1,22 @@
|
||||
//! XDO-based input emulation for Linux.
|
||||
//!
|
||||
//! This module uses libxdo-sys (patched to use dynamic loading stub) for input emulation.
|
||||
//! The stub handles dynamic loading of libxdo, so we just call the functions directly.
|
||||
//!
|
||||
//! If libxdo is not available at runtime, all operations become no-ops.
|
||||
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
|
||||
use hbb_common::libc::{c_char, c_int, c_void, useconds_t};
|
||||
use std::{borrow::Cow, ffi::CString, ptr};
|
||||
use hbb_common::libc::c_int;
|
||||
use libxdo_sys::{self, xdo_t, CURRENTWINDOW};
|
||||
use std::{borrow::Cow, ffi::CString};
|
||||
|
||||
const CURRENT_WINDOW: c_int = 0;
|
||||
/// Default delay per keypress in microseconds.
|
||||
/// This value is passed to libxdo functions and must fit in `useconds_t` (u32).
|
||||
const DEFAULT_DELAY: u64 = 12000;
|
||||
type Window = c_int;
|
||||
type Xdo = *const c_void;
|
||||
|
||||
#[link(name = "xdo")]
|
||||
extern "C" {
|
||||
fn xdo_free(xdo: Xdo);
|
||||
fn xdo_new(display: *const c_char) -> Xdo;
|
||||
|
||||
fn xdo_click_window(xdo: Xdo, window: Window, button: c_int) -> c_int;
|
||||
fn xdo_mouse_down(xdo: Xdo, window: Window, button: c_int) -> c_int;
|
||||
fn xdo_mouse_up(xdo: Xdo, window: Window, button: c_int) -> c_int;
|
||||
fn xdo_move_mouse(xdo: Xdo, x: c_int, y: c_int, screen: c_int) -> c_int;
|
||||
fn xdo_move_mouse_relative(xdo: Xdo, x: c_int, y: c_int) -> c_int;
|
||||
|
||||
fn xdo_enter_text_window(
|
||||
xdo: Xdo,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int;
|
||||
fn xdo_send_keysequence_window(
|
||||
xdo: Xdo,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int;
|
||||
fn xdo_send_keysequence_window_down(
|
||||
xdo: Xdo,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int;
|
||||
fn xdo_send_keysequence_window_up(
|
||||
xdo: Xdo,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int;
|
||||
fn xdo_get_input_state(xdo: Xdo) -> u32;
|
||||
}
|
||||
/// Maximum allowed delay value (u32::MAX as u64).
|
||||
const MAX_DELAY: u64 = u32::MAX as u64;
|
||||
|
||||
fn mousebutton(button: MouseButton) -> c_int {
|
||||
match button {
|
||||
@@ -62,7 +34,7 @@ fn mousebutton(button: MouseButton) -> c_int {
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub(super) struct EnigoXdo {
|
||||
xdo: Xdo,
|
||||
xdo: *mut xdo_t,
|
||||
delay: u64,
|
||||
}
|
||||
// This is safe, we have a unique pointer.
|
||||
@@ -70,37 +42,61 @@ pub(super) struct EnigoXdo {
|
||||
unsafe impl Send for EnigoXdo {}
|
||||
|
||||
impl Default for EnigoXdo {
|
||||
/// Create a new EnigoXdo instance
|
||||
/// Create a new EnigoXdo instance.
|
||||
///
|
||||
/// If libxdo is not available, the xdo pointer will be null and all
|
||||
/// input operations will be no-ops.
|
||||
fn default() -> Self {
|
||||
let xdo = unsafe { libxdo_sys::xdo_new(std::ptr::null()) };
|
||||
if xdo.is_null() {
|
||||
log::warn!("Failed to create xdo context, xdo functions will be disabled");
|
||||
} else {
|
||||
log::info!("xdo context created successfully");
|
||||
}
|
||||
Self {
|
||||
xdo: unsafe { xdo_new(ptr::null()) },
|
||||
xdo,
|
||||
delay: DEFAULT_DELAY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EnigoXdo {
|
||||
/// Get the delay per keypress.
|
||||
/// Default value is 12000.
|
||||
/// This is Linux-specific.
|
||||
/// Get the delay per keypress in microseconds.
|
||||
///
|
||||
/// Default value is 12000 (12ms). This is Linux-specific.
|
||||
pub fn delay(&self) -> u64 {
|
||||
self.delay
|
||||
}
|
||||
/// Set the delay per keypress.
|
||||
/// This is Linux-specific.
|
||||
|
||||
/// Set the delay per keypress in microseconds.
|
||||
///
|
||||
/// This is Linux-specific. The value is clamped to `u32::MAX` (approximately
|
||||
/// 4295 seconds) because libxdo uses `useconds_t` which is typically `u32`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `delay` - Delay in microseconds. Values exceeding `u32::MAX` will be clamped.
|
||||
pub fn set_delay(&mut self, delay: u64) {
|
||||
self.delay = delay;
|
||||
self.delay = delay.min(MAX_DELAY);
|
||||
if delay > MAX_DELAY {
|
||||
log::warn!(
|
||||
"delay value {} exceeds maximum {}, clamped",
|
||||
delay,
|
||||
MAX_DELAY
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnigoXdo {
|
||||
fn drop(&mut self) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_free(self.xdo);
|
||||
if !self.xdo.is_null() {
|
||||
unsafe {
|
||||
libxdo_sys::xdo_free(self.xdo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseControllable for EnigoXdo {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
@@ -115,42 +111,47 @@ impl MouseControllable for EnigoXdo {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0);
|
||||
libxdo_sys::xdo_move_mouse(self.xdo as *const _, x, y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_move_mouse_relative(self.xdo, x as c_int, y as c_int);
|
||||
libxdo_sys::xdo_move_mouse_relative(self.xdo as *const _, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
|
||||
if self.xdo.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
unsafe {
|
||||
xdo_mouse_down(self.xdo, CURRENT_WINDOW, mousebutton(button));
|
||||
libxdo_sys::xdo_mouse_down(self.xdo as *const _, CURRENTWINDOW, mousebutton(button));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_mouse_up(self.xdo, CURRENT_WINDOW, mousebutton(button));
|
||||
libxdo_sys::xdo_mouse_up(self.xdo as *const _, CURRENTWINDOW, mousebutton(button));
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_click_window(self.xdo, CURRENT_WINDOW, mousebutton(button));
|
||||
libxdo_sys::xdo_click_window(self.xdo as *const _, CURRENTWINDOW, mousebutton(button));
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
let button;
|
||||
let mut length = length;
|
||||
@@ -169,6 +170,7 @@ impl MouseControllable for EnigoXdo {
|
||||
self.mouse_click(button);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
let button;
|
||||
let mut length = length;
|
||||
@@ -188,6 +190,7 @@ impl MouseControllable for EnigoXdo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn keysequence<'a>(key: Key) -> Cow<'a, str> {
|
||||
if let Key::Layout(c) = key {
|
||||
return Cow::Owned(format!("U{:X}", c as u32));
|
||||
@@ -284,6 +287,7 @@ fn keysequence<'a>(key: Key) -> Cow<'a, str> {
|
||||
_ => "",
|
||||
})
|
||||
}
|
||||
|
||||
impl KeyboardControllable for EnigoXdo {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
@@ -314,7 +318,7 @@ impl KeyboardControllable for EnigoXdo {
|
||||
let mod_alt = 1 << 3;
|
||||
let mod_numlock = 1 << 4;
|
||||
let mod_meta = 1 << 6;
|
||||
let mask = unsafe { xdo_get_input_state(self.xdo) };
|
||||
let mask = unsafe { libxdo_sys::xdo_get_input_state(self.xdo as *const _) };
|
||||
match key {
|
||||
Key::Shift => mask & mod_shift != 0,
|
||||
Key::CapsLock => mask & mod_lock != 0,
|
||||
@@ -332,56 +336,59 @@ impl KeyboardControllable for EnigoXdo {
|
||||
}
|
||||
if let Ok(string) = CString::new(sequence) {
|
||||
unsafe {
|
||||
xdo_enter_text_window(
|
||||
self.xdo,
|
||||
CURRENT_WINDOW,
|
||||
libxdo_sys::xdo_enter_text_window(
|
||||
self.xdo as *const _,
|
||||
CURRENTWINDOW,
|
||||
string.as_ptr(),
|
||||
self.delay as useconds_t,
|
||||
self.delay as libxdo_sys::useconds_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
if self.xdo.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
let string = CString::new(&*keysequence(key))?;
|
||||
unsafe {
|
||||
xdo_send_keysequence_window_down(
|
||||
self.xdo,
|
||||
CURRENT_WINDOW,
|
||||
libxdo_sys::xdo_send_keysequence_window_down(
|
||||
self.xdo as *const _,
|
||||
CURRENTWINDOW,
|
||||
string.as_ptr(),
|
||||
self.delay as useconds_t,
|
||||
self.delay as libxdo_sys::useconds_t,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_up(&mut self, key: Key) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(string) = CString::new(&*keysequence(key)) {
|
||||
unsafe {
|
||||
xdo_send_keysequence_window_up(
|
||||
self.xdo,
|
||||
CURRENT_WINDOW,
|
||||
libxdo_sys::xdo_send_keysequence_window_up(
|
||||
self.xdo as *const _,
|
||||
CURRENTWINDOW,
|
||||
string.as_ptr(),
|
||||
self.delay as useconds_t,
|
||||
self.delay as libxdo_sys::useconds_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_click(&mut self, key: Key) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(string) = CString::new(&*keysequence(key)) {
|
||||
unsafe {
|
||||
xdo_send_keysequence_window(
|
||||
self.xdo,
|
||||
CURRENT_WINDOW,
|
||||
libxdo_sys::xdo_send_keysequence_window(
|
||||
self.xdo as *const _,
|
||||
CURRENTWINDOW,
|
||||
string.as_ptr(),
|
||||
self.delay as useconds_t,
|
||||
self.delay as libxdo_sys::useconds_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Submodule libs/hbb_common updated: 073403edbf...900077a2c2
9
libs/libxdo-sys-stub/Cargo.toml
Normal file
9
libs/libxdo-sys-stub/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "libxdo-sys"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
description = "Dynamic loading wrapper for libxdo-sys that doesn't require libxdo at compile/link time"
|
||||
|
||||
[dependencies]
|
||||
hbb_common = { path = "../hbb_common" }
|
||||
505
libs/libxdo-sys-stub/src/lib.rs
Normal file
505
libs/libxdo-sys-stub/src/lib.rs
Normal file
@@ -0,0 +1,505 @@
|
||||
//! Dynamic loading wrapper for libxdo.
|
||||
//!
|
||||
//! Provides the same API as libxdo-sys but loads libxdo at runtime,
|
||||
//! allowing the program to run on systems without libxdo installed
|
||||
//! (e.g., Wayland-only environments).
|
||||
|
||||
use hbb_common::{
|
||||
libc::{c_char, c_int, c_uint},
|
||||
libloading::{Library, Symbol},
|
||||
log,
|
||||
};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub use hbb_common::x11::xlib::{Display, Screen, Window};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct xdo_t {
|
||||
_private: [u8; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct charcodemap_t {
|
||||
_private: [u8; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct xdo_search_t {
|
||||
_private: [u8; 0],
|
||||
}
|
||||
|
||||
pub type useconds_t = c_uint;
|
||||
|
||||
pub const CURRENTWINDOW: Window = 0;
|
||||
|
||||
type FnXdoNew = unsafe extern "C" fn(*const c_char) -> *mut xdo_t;
|
||||
type FnXdoNewWithOpenedDisplay =
|
||||
unsafe extern "C" fn(*mut Display, *const c_char, c_int) -> *mut xdo_t;
|
||||
type FnXdoFree = unsafe extern "C" fn(*mut xdo_t);
|
||||
type FnXdoSendKeysequenceWindow =
|
||||
unsafe extern "C" fn(*const xdo_t, Window, *const c_char, useconds_t) -> c_int;
|
||||
type FnXdoSendKeysequenceWindowDown =
|
||||
unsafe extern "C" fn(*const xdo_t, Window, *const c_char, useconds_t) -> c_int;
|
||||
type FnXdoSendKeysequenceWindowUp =
|
||||
unsafe extern "C" fn(*const xdo_t, Window, *const c_char, useconds_t) -> c_int;
|
||||
type FnXdoEnterTextWindow =
|
||||
unsafe extern "C" fn(*const xdo_t, Window, *const c_char, useconds_t) -> c_int;
|
||||
type FnXdoClickWindow = unsafe extern "C" fn(*const xdo_t, Window, c_int) -> c_int;
|
||||
type FnXdoMouseDown = unsafe extern "C" fn(*const xdo_t, Window, c_int) -> c_int;
|
||||
type FnXdoMouseUp = unsafe extern "C" fn(*const xdo_t, Window, c_int) -> c_int;
|
||||
type FnXdoMoveMouse = unsafe extern "C" fn(*const xdo_t, c_int, c_int, c_int) -> c_int;
|
||||
type FnXdoMoveMouseRelative = unsafe extern "C" fn(*const xdo_t, c_int, c_int) -> c_int;
|
||||
type FnXdoMoveMouseRelativeToWindow =
|
||||
unsafe extern "C" fn(*const xdo_t, Window, c_int, c_int) -> c_int;
|
||||
type FnXdoGetMouseLocation =
|
||||
unsafe extern "C" fn(*const xdo_t, *mut c_int, *mut c_int, *mut c_int) -> c_int;
|
||||
type FnXdoGetMouseLocation2 =
|
||||
unsafe extern "C" fn(*const xdo_t, *mut c_int, *mut c_int, *mut c_int, *mut Window) -> c_int;
|
||||
type FnXdoGetActiveWindow = unsafe extern "C" fn(*const xdo_t, *mut Window) -> c_int;
|
||||
type FnXdoGetFocusedWindow = unsafe extern "C" fn(*const xdo_t, *mut Window) -> c_int;
|
||||
type FnXdoGetFocusedWindowSane = unsafe extern "C" fn(*const xdo_t, *mut Window) -> c_int;
|
||||
type FnXdoGetWindowLocation =
|
||||
unsafe extern "C" fn(*const xdo_t, Window, *mut c_int, *mut c_int, *mut *mut Screen) -> c_int;
|
||||
type FnXdoGetWindowSize =
|
||||
unsafe extern "C" fn(*const xdo_t, Window, *mut c_uint, *mut c_uint) -> c_int;
|
||||
type FnXdoGetInputState = unsafe extern "C" fn(*const xdo_t) -> c_uint;
|
||||
type FnXdoActivateWindow = unsafe extern "C" fn(*const xdo_t, Window) -> c_int;
|
||||
type FnXdoWaitForMouseMoveFrom = unsafe extern "C" fn(*const xdo_t, c_int, c_int) -> c_int;
|
||||
type FnXdoWaitForMouseMoveTo = unsafe extern "C" fn(*const xdo_t, c_int, c_int) -> c_int;
|
||||
type FnXdoSetWindowClass =
|
||||
unsafe extern "C" fn(*const xdo_t, Window, *const c_char, *const c_char) -> c_int;
|
||||
type FnXdoSearchWindows =
|
||||
unsafe extern "C" fn(*const xdo_t, *const xdo_search_t, *mut *mut Window, *mut c_uint) -> c_int;
|
||||
|
||||
struct XdoLib {
|
||||
_lib: Library,
|
||||
xdo_new: FnXdoNew,
|
||||
xdo_new_with_opened_display: Option<FnXdoNewWithOpenedDisplay>,
|
||||
xdo_free: FnXdoFree,
|
||||
xdo_send_keysequence_window: FnXdoSendKeysequenceWindow,
|
||||
xdo_send_keysequence_window_down: Option<FnXdoSendKeysequenceWindowDown>,
|
||||
xdo_send_keysequence_window_up: Option<FnXdoSendKeysequenceWindowUp>,
|
||||
xdo_enter_text_window: Option<FnXdoEnterTextWindow>,
|
||||
xdo_click_window: Option<FnXdoClickWindow>,
|
||||
xdo_mouse_down: Option<FnXdoMouseDown>,
|
||||
xdo_mouse_up: Option<FnXdoMouseUp>,
|
||||
xdo_move_mouse: Option<FnXdoMoveMouse>,
|
||||
xdo_move_mouse_relative: Option<FnXdoMoveMouseRelative>,
|
||||
xdo_move_mouse_relative_to_window: Option<FnXdoMoveMouseRelativeToWindow>,
|
||||
xdo_get_mouse_location: Option<FnXdoGetMouseLocation>,
|
||||
xdo_get_mouse_location2: Option<FnXdoGetMouseLocation2>,
|
||||
xdo_get_active_window: Option<FnXdoGetActiveWindow>,
|
||||
xdo_get_focused_window: Option<FnXdoGetFocusedWindow>,
|
||||
xdo_get_focused_window_sane: Option<FnXdoGetFocusedWindowSane>,
|
||||
xdo_get_window_location: Option<FnXdoGetWindowLocation>,
|
||||
xdo_get_window_size: Option<FnXdoGetWindowSize>,
|
||||
xdo_get_input_state: Option<FnXdoGetInputState>,
|
||||
xdo_activate_window: Option<FnXdoActivateWindow>,
|
||||
xdo_wait_for_mouse_move_from: Option<FnXdoWaitForMouseMoveFrom>,
|
||||
xdo_wait_for_mouse_move_to: Option<FnXdoWaitForMouseMoveTo>,
|
||||
xdo_set_window_class: Option<FnXdoSetWindowClass>,
|
||||
xdo_search_windows: Option<FnXdoSearchWindows>,
|
||||
}
|
||||
|
||||
impl XdoLib {
|
||||
fn load() -> Option<Self> {
|
||||
// https://github.com/rustdesk/rustdesk/issues/13711
|
||||
const LIB_NAMES: [&str; 3] = ["libxdo.so.4", "libxdo.so.3", "libxdo.so"];
|
||||
|
||||
unsafe {
|
||||
let (lib, lib_name) = LIB_NAMES
|
||||
.iter()
|
||||
.find_map(|name| Library::new(name).ok().map(|lib| (lib, *name)))?;
|
||||
|
||||
log::info!("libxdo-sys Loaded {}", lib_name);
|
||||
|
||||
let xdo_new: FnXdoNew = *lib.get(b"xdo_new").ok()?;
|
||||
let xdo_free: FnXdoFree = *lib.get(b"xdo_free").ok()?;
|
||||
let xdo_send_keysequence_window: FnXdoSendKeysequenceWindow =
|
||||
*lib.get(b"xdo_send_keysequence_window").ok()?;
|
||||
|
||||
let xdo_new_with_opened_display = lib
|
||||
.get(b"xdo_new_with_opened_display")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoNewWithOpenedDisplay>| *s);
|
||||
let xdo_send_keysequence_window_down = lib
|
||||
.get(b"xdo_send_keysequence_window_down")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoSendKeysequenceWindowDown>| *s);
|
||||
let xdo_send_keysequence_window_up = lib
|
||||
.get(b"xdo_send_keysequence_window_up")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoSendKeysequenceWindowUp>| *s);
|
||||
let xdo_enter_text_window = lib
|
||||
.get(b"xdo_enter_text_window")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoEnterTextWindow>| *s);
|
||||
let xdo_click_window = lib
|
||||
.get(b"xdo_click_window")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoClickWindow>| *s);
|
||||
let xdo_mouse_down = lib
|
||||
.get(b"xdo_mouse_down")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoMouseDown>| *s);
|
||||
let xdo_mouse_up = lib
|
||||
.get(b"xdo_mouse_up")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoMouseUp>| *s);
|
||||
let xdo_move_mouse = lib
|
||||
.get(b"xdo_move_mouse")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoMoveMouse>| *s);
|
||||
let xdo_move_mouse_relative = lib
|
||||
.get(b"xdo_move_mouse_relative")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoMoveMouseRelative>| *s);
|
||||
let xdo_move_mouse_relative_to_window = lib
|
||||
.get(b"xdo_move_mouse_relative_to_window")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoMoveMouseRelativeToWindow>| *s);
|
||||
let xdo_get_mouse_location = lib
|
||||
.get(b"xdo_get_mouse_location")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoGetMouseLocation>| *s);
|
||||
let xdo_get_mouse_location2 = lib
|
||||
.get(b"xdo_get_mouse_location2")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoGetMouseLocation2>| *s);
|
||||
let xdo_get_active_window = lib
|
||||
.get(b"xdo_get_active_window")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoGetActiveWindow>| *s);
|
||||
let xdo_get_focused_window = lib
|
||||
.get(b"xdo_get_focused_window")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoGetFocusedWindow>| *s);
|
||||
let xdo_get_focused_window_sane = lib
|
||||
.get(b"xdo_get_focused_window_sane")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoGetFocusedWindowSane>| *s);
|
||||
let xdo_get_window_location = lib
|
||||
.get(b"xdo_get_window_location")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoGetWindowLocation>| *s);
|
||||
let xdo_get_window_size = lib
|
||||
.get(b"xdo_get_window_size")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoGetWindowSize>| *s);
|
||||
let xdo_get_input_state = lib
|
||||
.get(b"xdo_get_input_state")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoGetInputState>| *s);
|
||||
let xdo_activate_window = lib
|
||||
.get(b"xdo_activate_window")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoActivateWindow>| *s);
|
||||
let xdo_wait_for_mouse_move_from = lib
|
||||
.get(b"xdo_wait_for_mouse_move_from")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoWaitForMouseMoveFrom>| *s);
|
||||
let xdo_wait_for_mouse_move_to = lib
|
||||
.get(b"xdo_wait_for_mouse_move_to")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoWaitForMouseMoveTo>| *s);
|
||||
let xdo_set_window_class = lib
|
||||
.get(b"xdo_set_window_class")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoSetWindowClass>| *s);
|
||||
let xdo_search_windows = lib
|
||||
.get(b"xdo_search_windows")
|
||||
.ok()
|
||||
.map(|s: Symbol<FnXdoSearchWindows>| *s);
|
||||
|
||||
Some(Self {
|
||||
_lib: lib,
|
||||
xdo_new,
|
||||
xdo_new_with_opened_display,
|
||||
xdo_free,
|
||||
xdo_send_keysequence_window,
|
||||
xdo_send_keysequence_window_down,
|
||||
xdo_send_keysequence_window_up,
|
||||
xdo_enter_text_window,
|
||||
xdo_click_window,
|
||||
xdo_mouse_down,
|
||||
xdo_mouse_up,
|
||||
xdo_move_mouse,
|
||||
xdo_move_mouse_relative,
|
||||
xdo_move_mouse_relative_to_window,
|
||||
xdo_get_mouse_location,
|
||||
xdo_get_mouse_location2,
|
||||
xdo_get_active_window,
|
||||
xdo_get_focused_window,
|
||||
xdo_get_focused_window_sane,
|
||||
xdo_get_window_location,
|
||||
xdo_get_window_size,
|
||||
xdo_get_input_state,
|
||||
xdo_activate_window,
|
||||
xdo_wait_for_mouse_move_from,
|
||||
xdo_wait_for_mouse_move_to,
|
||||
xdo_set_window_class,
|
||||
xdo_search_windows,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static XDO_LIB: OnceLock<Option<XdoLib>> = OnceLock::new();
|
||||
|
||||
fn get_lib() -> Option<&'static XdoLib> {
|
||||
XDO_LIB
|
||||
.get_or_init(|| {
|
||||
let lib = XdoLib::load();
|
||||
if lib.is_none() {
|
||||
log::info!("libxdo-sys libxdo not found, xdo functions will be disabled");
|
||||
}
|
||||
lib
|
||||
})
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_new(display: *const c_char) -> *mut xdo_t {
|
||||
get_lib().map_or(std::ptr::null_mut(), |lib| (lib.xdo_new)(display))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_new_with_opened_display(
|
||||
xdpy: *mut Display,
|
||||
display: *const c_char,
|
||||
close_display_when_freed: c_int,
|
||||
) -> *mut xdo_t {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_new_with_opened_display)
|
||||
.map_or(std::ptr::null_mut(), |f| {
|
||||
f(xdpy, display, close_display_when_freed)
|
||||
})
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_free(xdo: *mut xdo_t) {
|
||||
if xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Some(lib) = get_lib() {
|
||||
(lib.xdo_free)(xdo);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_send_keysequence_window(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
keysequence: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int {
|
||||
get_lib().map_or(1, |lib| {
|
||||
(lib.xdo_send_keysequence_window)(xdo, window, keysequence, delay)
|
||||
})
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_send_keysequence_window_down(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
keysequence: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_send_keysequence_window_down)
|
||||
.map_or(1, |f| f(xdo, window, keysequence, delay))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_send_keysequence_window_up(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
keysequence: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_send_keysequence_window_up)
|
||||
.map_or(1, |f| f(xdo, window, keysequence, delay))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_enter_text_window(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_enter_text_window)
|
||||
.map_or(1, |f| f(xdo, window, string, delay))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_click_window(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
button: c_int,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_click_window)
|
||||
.map_or(1, |f| f(xdo, window, button))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_mouse_down(xdo: *const xdo_t, window: Window, button: c_int) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_mouse_down)
|
||||
.map_or(1, |f| f(xdo, window, button))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_mouse_up(xdo: *const xdo_t, window: Window, button: c_int) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_mouse_up)
|
||||
.map_or(1, |f| f(xdo, window, button))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_move_mouse(
|
||||
xdo: *const xdo_t,
|
||||
x: c_int,
|
||||
y: c_int,
|
||||
screen: c_int,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_move_mouse)
|
||||
.map_or(1, |f| f(xdo, x, y, screen))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_move_mouse_relative(xdo: *const xdo_t, x: c_int, y: c_int) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_move_mouse_relative)
|
||||
.map_or(1, |f| f(xdo, x, y))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_move_mouse_relative_to_window(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
x: c_int,
|
||||
y: c_int,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_move_mouse_relative_to_window)
|
||||
.map_or(1, |f| f(xdo, window, x, y))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_get_mouse_location(
|
||||
xdo: *const xdo_t,
|
||||
x: *mut c_int,
|
||||
y: *mut c_int,
|
||||
screen_num: *mut c_int,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_get_mouse_location)
|
||||
.map_or(1, |f| f(xdo, x, y, screen_num))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_get_mouse_location2(
|
||||
xdo: *const xdo_t,
|
||||
x: *mut c_int,
|
||||
y: *mut c_int,
|
||||
screen_num: *mut c_int,
|
||||
window: *mut Window,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_get_mouse_location2)
|
||||
.map_or(1, |f| f(xdo, x, y, screen_num, window))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_get_active_window(
|
||||
xdo: *const xdo_t,
|
||||
window_ret: *mut Window,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_get_active_window)
|
||||
.map_or(1, |f| f(xdo, window_ret))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_get_focused_window(
|
||||
xdo: *const xdo_t,
|
||||
window_ret: *mut Window,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_get_focused_window)
|
||||
.map_or(1, |f| f(xdo, window_ret))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_get_focused_window_sane(
|
||||
xdo: *const xdo_t,
|
||||
window_ret: *mut Window,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_get_focused_window_sane)
|
||||
.map_or(1, |f| f(xdo, window_ret))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_get_window_location(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
x: *mut c_int,
|
||||
y: *mut c_int,
|
||||
screen_ret: *mut *mut Screen,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_get_window_location)
|
||||
.map_or(1, |f| f(xdo, window, x, y, screen_ret))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_get_window_size(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
width: *mut c_uint,
|
||||
height: *mut c_uint,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_get_window_size)
|
||||
.map_or(1, |f| f(xdo, window, width, height))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_get_input_state(xdo: *const xdo_t) -> c_uint {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_get_input_state)
|
||||
.map_or(0, |f| f(xdo))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_activate_window(xdo: *const xdo_t, wid: Window) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_activate_window)
|
||||
.map_or(1, |f| f(xdo, wid))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_wait_for_mouse_move_from(
|
||||
xdo: *const xdo_t,
|
||||
origin_x: c_int,
|
||||
origin_y: c_int,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_wait_for_mouse_move_from)
|
||||
.map_or(1, |f| f(xdo, origin_x, origin_y))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_wait_for_mouse_move_to(
|
||||
xdo: *const xdo_t,
|
||||
dest_x: c_int,
|
||||
dest_y: c_int,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_wait_for_mouse_move_to)
|
||||
.map_or(1, |f| f(xdo, dest_x, dest_y))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_set_window_class(
|
||||
xdo: *const xdo_t,
|
||||
wid: Window,
|
||||
name: *const c_char,
|
||||
class: *const c_char,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_set_window_class)
|
||||
.map_or(1, |f| f(xdo, wid, name, class))
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xdo_search_windows(
|
||||
xdo: *const xdo_t,
|
||||
search: *const xdo_search_t,
|
||||
windowlist_ret: *mut *mut Window,
|
||||
nwindows_ret: *mut c_uint,
|
||||
) -> c_int {
|
||||
get_lib()
|
||||
.and_then(|lib| lib.xdo_search_windows)
|
||||
.map_or(1, |f| f(xdo, search, windowlist_ret, nwindows_ret))
|
||||
}
|
||||
@@ -187,7 +187,10 @@ fn main() {
|
||||
i += 1;
|
||||
}
|
||||
let click_setup = args.is_empty() && arg_exe.to_lowercase().ends_with("install.exe");
|
||||
let quick_support = args.is_empty() && arg_exe.to_lowercase().ends_with("qs.exe");
|
||||
#[cfg(windows)]
|
||||
let quick_support = args.is_empty() && win::is_quick_support_exe(&arg_exe);
|
||||
#[cfg(not(windows))]
|
||||
let quick_support = false;
|
||||
|
||||
let mut ui = false;
|
||||
let reader = BinaryReader::default();
|
||||
@@ -234,4 +237,12 @@ mod win {
|
||||
.output();
|
||||
let _allow_err = std::fs::copy(src, &format!("{}\\{}", dir.to_string_lossy(), tgt));
|
||||
}
|
||||
|
||||
/// Check if the executable is a Quick Support version.
|
||||
/// Note: This function must be kept in sync with `src/core_main.rs`.
|
||||
#[inline]
|
||||
pub(super) fn is_quick_support_exe(exe: &str) -> bool {
|
||||
let exe = exe.to_lowercase();
|
||||
exe.contains("-qs-") || exe.contains("-qs.exe") || exe.contains("_qs.exe")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,9 +205,13 @@ def sign_files(dir_path, only_ext=None):
|
||||
if not only_ext[i].startswith("."):
|
||||
only_ext[i] = "." + only_ext[i]
|
||||
for root, dirs, files in os.walk(dir_path):
|
||||
is_signed_dir = "RustDeskPrinterDriver" in root or "usbmmidd_v2" in root
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
_, ext = os.path.splitext(file_path)
|
||||
# only sign the exe files in signed dirs
|
||||
if is_signed_dir and ext not in [".exe"]:
|
||||
continue
|
||||
if only_ext and ext not in only_ext:
|
||||
continue
|
||||
if ext in SIGN_EXTENSIONS:
|
||||
|
||||
@@ -5,8 +5,8 @@ Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Recommends: libayatana-appindicator3-1
|
||||
Requires: gtk3 libxcb1 libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Recommends: libayatana-appindicator3-1 xdotool
|
||||
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit)
|
||||
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
|
||||
|
||||
@@ -5,8 +5,8 @@ Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb libxdo libXfixes alsa-lib libva pam gstreamer1-plugins-base
|
||||
Recommends: libayatana-appindicator-gtk3
|
||||
Requires: gtk3 libxcb libXfixes alsa-lib libva pam gstreamer1-plugins-base
|
||||
Recommends: libayatana-appindicator-gtk3 libxdo
|
||||
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit)
|
||||
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
|
||||
|
||||
@@ -3,8 +3,8 @@ Version: 1.1.9
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Recommends: libayatana-appindicator3-1
|
||||
Requires: gtk3 libxcb1 libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Recommends: libayatana-appindicator3-1 xdotool
|
||||
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb libxdo libXfixes alsa-lib libva2 pam gstreamer1-plugins-base
|
||||
Recommends: libayatana-appindicator-gtk3
|
||||
Requires: gtk3 libxcb libXfixes alsa-lib libva2 pam gstreamer1-plugins-base
|
||||
Recommends: libayatana-appindicator-gtk3 libxdo
|
||||
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
{
|
||||
_is_quick_support |= !crate::platform::is_installed()
|
||||
&& args.is_empty()
|
||||
&& (arg_exe.to_lowercase().contains("-qs-")
|
||||
&& (is_quick_support_exe(&arg_exe)
|
||||
|| config::LocalConfig::get_option("pre-elevate-service") == "Y"
|
||||
|| (!click_setup && crate::platform::is_elevated(None).unwrap_or(false)));
|
||||
crate::portable_service::client::set_quick_support(_is_quick_support);
|
||||
@@ -829,3 +829,12 @@ fn is_root() -> bool {
|
||||
#[allow(unreachable_code)]
|
||||
crate::platform::is_root()
|
||||
}
|
||||
|
||||
/// Check if the executable is a Quick Support version.
|
||||
/// Note: This function must be kept in sync with `libs/portable/src/main.rs`.
|
||||
#[cfg(windows)]
|
||||
#[inline]
|
||||
fn is_quick_support_exe(exe: &str) -> bool {
|
||||
let exe = exe.to_lowercase();
|
||||
exe.contains("-qs-") || exe.contains("-qs.exe") || exe.contains("_qs.exe")
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", "无法锁定鼠标,相对鼠标模式已禁用"),
|
||||
("rel-mouse-exit-{}-tip", "按下 {} 退出"),
|
||||
("rel-mouse-permission-lost-tip", "键盘权限被撤销。相对鼠标模式已被禁用。"),
|
||||
("Changelog", "更新日志"),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -562,8 +562,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("id_input_tip", "Sie können eine ID, eine direkte IP oder eine Domäne mit einem Port (<domain>:<port>) eingeben.\nWenn Sie auf ein Gerät auf einem anderen Server zugreifen wollen, fügen Sie bitte die Serveradresse (<id>@<server_address>?key=<key_value>) hinzu, zum Beispiel\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nWenn Sie auf ein Gerät auf einem öffentlichen Server zugreifen wollen, geben Sie bitte \"<id>@public\" ein. Der Schlüssel wird für öffentliche Server nicht benötigt.\n\nWenn Sie bei der ersten Verbindung die Verwendung einer Relay-Verbindung erzwingen wollen, fügen Sie \"/r\" am Ende der ID hinzu, zum Beispiel \"9123456234/r\"."),
|
||||
("privacy_mode_impl_mag_tip", "Modus 1"),
|
||||
("privacy_mode_impl_virtual_display_tip", "Modus 2"),
|
||||
("Enter privacy mode", "Datenschutzmodus aktivieren"),
|
||||
("Exit privacy mode", "Datenschutzmodus beenden"),
|
||||
("Enter privacy mode", "Datenschutzmodus aktiviert"),
|
||||
("Exit privacy mode", "Datenschutzmodus beendet"),
|
||||
("idd_not_support_under_win10_2004_tip", "Indirekter Grafiktreiber wird nicht unterstützt. Windows 10, Version 2004 oder neuer ist erforderlich."),
|
||||
("input_source_1_tip", "Eingangsquelle 1"),
|
||||
("input_source_2_tip", "Eingangsquelle 2"),
|
||||
@@ -730,11 +730,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "Hier eine Notiz eingeben"),
|
||||
("note-at-conn-end-tip", "Am Ende der Verbindung um eine Notiz bitten."),
|
||||
("Show terminal extra keys", "Zusätzliche Tasten des Terminals anzeigen"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Relative mouse mode", "Relativer Mausmodus"),
|
||||
("rel-mouse-not-supported-peer-tip", "Der relative Mausmodus wird von der verbundenen Gegenstelle nicht unterstützt."),
|
||||
("rel-mouse-not-ready-tip", "Der relative Mausmodus ist noch nicht bereit. Bitte versuchen Sie es erneut."),
|
||||
("rel-mouse-lock-failed-tip", "Cursor konnte nicht gesperrt werden. Der relative Mausmodus wurde deaktiviert."),
|
||||
("rel-mouse-exit-{}-tip", "Drücken Sie {} zum Beenden."),
|
||||
("rel-mouse-permission-lost-tip", "Die Tastaturberechtigung wurde widerrufen. Der relative Mausmodus wurde deaktiviert."),
|
||||
("Changelog", "Änderungsprotokoll"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Bildschirm während ausgehender Sitzungen aktiv halten"),
|
||||
("keep-awake-during-incoming-sessions-label", "Bildschirm während eingehender Sitzungen aktiv halten"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -267,5 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", "Failed to lock cursor. Relative Mouse Mode has been disabled."),
|
||||
("rel-mouse-exit-{}-tip", "Press {} to exit."),
|
||||
("rel-mouse-permission-lost-tip", "Keyboard permission was revoked. Relative Mouse Mode has been disabled."),
|
||||
("keep-awake-during-outgoing-sessions-label", "Keep screen awake during outgoing sessions"),
|
||||
("keep-awake-during-incoming-sessions-label", "Keep screen awake during incoming sessions"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to delete this empty directory?", "Voulez-vous vraiment supprimer ce répertoire vide ?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Voulez-vous vraiment supprimer le fichier de ce répertoire ?"),
|
||||
("Do this for all conflicts", "Appliquer à tous les conflits"),
|
||||
("This is irreversible!", "Ceci est irréversible !"),
|
||||
("This is irreversible!", "Cette action est irréversible !"),
|
||||
("Deleting", "Suppression"),
|
||||
("files", "fichiers"),
|
||||
("Waiting", "En attente"),
|
||||
@@ -730,11 +730,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "saisir la note ici"),
|
||||
("note-at-conn-end-tip", "Proposer de rédiger une note une fois la connexion terminée"),
|
||||
("Show terminal extra keys", "Afficher les touches supplémentaires du terminal"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Relative mouse mode", "Mode souris relative"),
|
||||
("rel-mouse-not-supported-peer-tip", "Le mode souris relative n’est pas pris en charge par l’appareil distant."),
|
||||
("rel-mouse-not-ready-tip", "Le mode souris relative n’est pas encore prêt ; veuillez réessayer."),
|
||||
("rel-mouse-lock-failed-tip", "Échec du verrouillage du curseur. Le mode souris relative a été désactivé."),
|
||||
("rel-mouse-exit-{}-tip", "Appuyez sur {} pour quitter."),
|
||||
("rel-mouse-permission-lost-tip", "L’autorisation de contrôle du clavier a été révoquée. Le mode souris relative a été désactivé."),
|
||||
("Changelog", "Journal des modifications"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Maintenir l’écran allumé lors des sessions sortantes"),
|
||||
("keep-awake-during-incoming-sessions-label", "Maintenir l’écran allumé lors des sessions entrantes"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Click to upgrade", "Kattintson ide a frissítés telepítéséhez"),
|
||||
("Configure", "Beállítás"),
|
||||
("config_acc", "A számítógép távoli vezérléséhez a RustDesknek hozzáférési jogokat kell adnia."),
|
||||
("config_screen", "Ahhoz, hogy távolról hozzáférhessen a számítógépéhez, meg kell adnia a RustDesknek a „Képernyőfelvétel” jogosultságot."),
|
||||
("config_screen", "Ahhoz, hogy távolról hozzáférhessen a számítógépéhez, meg kell adnia a RustDesknek a \"Képernyőfelvétel\" jogosultságot."),
|
||||
("Installing ...", "Telepítés…"),
|
||||
("Install", "Telepítés"),
|
||||
("Installation", "Telepítés"),
|
||||
@@ -276,13 +276,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you accept?", "Elfogadás?"),
|
||||
("Open System Setting", "Rendszerbeállítások megnyitása"),
|
||||
("How to get Android input permission?", "Hogyan állítható be az Androidos beviteli engedély?"),
|
||||
("android_input_permission_tip1", "Ahhoz, hogy egy távoli eszköz vezérelhesse Android készülékét, engedélyeznie kell a RustDesk számára a „Hozzáférhetőség” szolgáltatás használatát."),
|
||||
("android_input_permission_tip1", "Ahhoz, hogy egy távoli eszköz vezérelhesse Android készülékét, engedélyeznie kell a RustDesk számára a \"Hozzáférhetőség\" szolgáltatás használatát."),
|
||||
("android_input_permission_tip2", "A következő rendszerbeállítások oldalon a letöltött alkalmazások menüponton belül, kapcsolja be a [RustDesk Input] szolgáltatást."),
|
||||
("android_new_connection_tip", "Új kérés érkezett, mely vezérelni szeretné az eszközét"),
|
||||
("android_service_will_start_tip", "A képernyőmegosztás aktiválása automatikusan elindítja a szolgáltatást, így más eszközök is vezérelhetik ezt az Android-eszközt."),
|
||||
("android_stop_service_tip", "A szolgáltatás leállítása automatikusan szétkapcsol minden létező kapcsolatot."),
|
||||
("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, frissítsen legalább Android 10-re, vagy egy újabb verzióra."),
|
||||
("android_start_service_tip", "A képernyőmegosztó szolgáltatás elindításához koppintson a „Kapcsolási szolgáltatás indítása” gombra, vagy aktiválja a „Képernyőfelvétel” engedélyt."),
|
||||
("android_start_service_tip", "A képernyőmegosztó szolgáltatás elindításához koppintson a \"Kapcsolási szolgáltatás indítása\" gombra, vagy aktiválja a \"Képernyőfelvétel\" engedélyt."),
|
||||
("android_permission_may_not_change_tip", "A meglévő kapcsolatok engedélyei csak új kapcsolódás után módosulnak."),
|
||||
("Account", "Fiók"),
|
||||
("Overwrite", "Felülírás"),
|
||||
@@ -408,15 +408,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Select local keyboard type", "Helyi billentyűzet típusának kiválasztása"),
|
||||
("software_render_tip", "Ha Nvidia grafikus kártyát használ Linux alatt, és a távoli ablak a kapcsolat létrehozása után azonnal bezáródik, akkor a Nouveau nyílt forráskódú illesztőprogramra való váltás és a szoftveres leképezés alkalmazása segíthet. A szoftvert újra kell indítani."),
|
||||
("Always use software rendering", "Mindig szoftveres leképezést használjon"),
|
||||
("config_input", "Ahhoz, hogy a távoli asztalt a billentyűzettel vezérelhesse, a RustDesknek meg kell adnia a „Bemenet figyelése” jogosultságot."),
|
||||
("config_microphone", "Ahhoz, hogy távolról beszélhessen, meg kell adnia a RustDesknek a „Hangfelvétel” jogosultságot."),
|
||||
("config_input", "Ahhoz, hogy a távoli asztalt a billentyűzettel vezérelhesse, a RustDesknek meg kell adnia a \"Bemenet figyelése\" jogosultságot."),
|
||||
("config_microphone", "Ahhoz, hogy távolról beszélhessen, meg kell adnia a RustDesknek a \"Hangfelvétel\" jogosultságot."),
|
||||
("request_elevation_tip", "Akkor is kérhet megnövelt jogokat, ha valaki a partneroldalon van."),
|
||||
("Wait", "Várjon"),
|
||||
("Elevation Error", "Emelt szintű hozzáférési hiba"),
|
||||
("Ask the remote user for authentication", "Hitelesítés kérése a távoli felhasználótól"),
|
||||
("Choose this if the remote account is administrator", "Akkor válassza ezt, ha a távoli fiók rendszergazda"),
|
||||
("Transmit the username and password of administrator", "Küldje el a rendszergazda felhasználónevét és jelszavát"),
|
||||
("still_click_uac_tip", "A távoli felhasználónak továbbra is az „Igen” gombra kell kattintania a RustDesk UAC ablakában. Kattintson!"),
|
||||
("still_click_uac_tip", "A távoli felhasználónak továbbra is az \"Igen\" gombra kell kattintania a RustDesk UAC ablakában. Kattintson!"),
|
||||
("Request Elevation", "Emelt szintű jogok igénylése"),
|
||||
("wait_accept_uac_tip", "Várjon, amíg a távoli felhasználó elfogadja az UAC párbeszédet."),
|
||||
("Elevate successfully", "Emelt szintű jogok megadva"),
|
||||
@@ -442,7 +442,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "Hanghívás"),
|
||||
("Text chat", "Szöveges csevegés"),
|
||||
("Stop voice call", "Hanghívás leállítása"),
|
||||
("relay_hint_tip", "Ha a közvetlen kapcsolat nem lehetséges, megpróbálhat kapcsolatot létesíteni egy továbbító-kiszolgálón keresztül.\nHa az első próbálkozáskor továbbító-kiszolgálón keresztüli kapcsolatot szeretne létrehozni, használhatja az „/r” utótagot. Az azonosítóhoz vagy a „Mindig továbbító-kiszolgálón keresztül kapcsolódom” opcióhoz a legutóbbi munkamenetek listájában, ha van ilyen."),
|
||||
("relay_hint_tip", "Ha a közvetlen kapcsolat nem lehetséges, megpróbálhat kapcsolatot létesíteni egy továbbító-kiszolgálón keresztül.\nHa az első próbálkozáskor továbbító-kiszolgálón keresztüli kapcsolatot szeretne létrehozni, használhatja az \"/r\" utótagot. Az azonosítóhoz vagy a \"Mindig továbbító-kiszolgálón keresztül kapcsolódom\" opcióhoz a legutóbbi munkamenetek listájában, ha van ilyen."),
|
||||
("Reconnect", "Újrakapcsolódás"),
|
||||
("Codec", "Kodek"),
|
||||
("Resolution", "Felbontás"),
|
||||
@@ -490,7 +490,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Update", "Frissítés"),
|
||||
("Enable", "Engedélyezés"),
|
||||
("Disable", "Letiltás"),
|
||||
("Options", "Beállítások"),
|
||||
("Options", "Opciók"),
|
||||
("resolution_original_tip", "Eredeti felbontás"),
|
||||
("resolution_fit_local_tip", "Helyi felbontás beállítása"),
|
||||
("resolution_custom_tip", "Testre szabható felbontás"),
|
||||
@@ -559,7 +559,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Plug out all", "Kapcsolja ki az összeset"),
|
||||
("True color (4:4:4)", "Valódi szín (4:4:4)"),
|
||||
("Enable blocking user input", "Engedélyezze a felhasználói bevitel blokkolását"),
|
||||
("id_input_tip", "Megadhat egy azonosítót, egy közvetlen IP-címet vagy egy tartományt egy porttal (<domain>:<port>).\nHa egy másik kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a kiszolgáló címét (<id>@<kiszolgáló_cím>?key=<key_value>), például\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHa egy nyilvános kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a „<id>@public” lehetőséget. A kulcsra nincs szükség nyilvános kiszolgálók esetén.\n\nHa az első kapcsolathoz továbbító-kiszolgálón keresztüli kapcsolatot akar kényszeríteni, adja hozzá az „/r” az azonosítót a végén, például „9123456234/r”."),
|
||||
("id_input_tip", "Megadhat egy azonosítót, egy közvetlen IP-címet vagy egy tartományt egy porttal (<domain>:<port>).\nHa egy másik kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a kiszolgáló címét (<id>@<kiszolgáló_cím>?key=<key_value>), például\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHa egy nyilvános kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a \"<id>@public\" lehetőséget. A kulcsra nincs szükség nyilvános kiszolgálók esetén.\n\nHa az első kapcsolathoz továbbító-kiszolgálón keresztüli kapcsolatot akar kényszeríteni, adja hozzá az \"/r\" az azonosítót a végén, például \"9123456234/r\"."),
|
||||
("privacy_mode_impl_mag_tip", "1. mód"),
|
||||
("privacy_mode_impl_virtual_display_tip", "2. mód"),
|
||||
("Enter privacy mode", "Lépjen be az adatvédelmi módba"),
|
||||
@@ -622,7 +622,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Power", "Főkapcsoló"),
|
||||
("Telegram bot", "Telegram bot"),
|
||||
("enable-bot-tip", "Ha aktiválja ezt a funkciót, akkor a 2FA-kódot a botjától kaphatja meg. Kapcsolati értesítésként is használható."),
|
||||
("enable-bot-desc", "1. Nyisson csevegést @BotFather.\n2. Küldje el a „/newbot” parancsot. Miután ezt a lépést elvégezte, kap egy tokent.\n3. Indítson csevegést az újonnan létrehozott botjával. Küldjön egy olyan üzenetet, amely egy perjel („/”) kezdetű, pl. „/hello” az aktiváláshoz.\n"),
|
||||
("enable-bot-desc", "1. Nyisson csevegést @BotFather.\n2. Küldje el a \"/newbot\" parancsot. Miután ezt a lépést elvégezte, kap egy tokent.\n3. Indítson csevegést az újonnan létrehozott botjával. Küldjön egy olyan üzenetet, amely egy perjel (\"/\") kezdetű, pl. \"/hello\" az aktiváláshoz.\n"),
|
||||
("cancel-2fa-confirm-tip", "Biztosan vissza akarja vonni a 2FA-hitelesítést?"),
|
||||
("cancel-bot-confirm-tip", "Biztosan le akarja mondani a Telegram botot?"),
|
||||
("About RustDesk", "A RustDesk névjegye"),
|
||||
@@ -643,7 +643,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("one-way-file-transfer-tip", "Az egyirányú fájlátvitel engedélyezve van a vezérelt oldalon."),
|
||||
("Authentication Required", "Hitelesítés szükséges"),
|
||||
("Authenticate", "Hitelesítés"),
|
||||
("web_id_input_tip", "Azonos kiszolgálón lévő azonosítót adhat meg, a közvetlen IP elérés nem támogatott a webkliensben.\nHa egy másik kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a kiszolgáló címét (<id>@<kiszolgáló_cím>?key=<key_value>), például\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHa egy nyilvános kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a „<id>@public” betűt. A kulcsra nincs szükség a nyilvános kiszolgálók esetében."),
|
||||
("web_id_input_tip", "Azonos kiszolgálón lévő azonosítót adhat meg, a közvetlen IP elérés nem támogatott a webkliensben.\nHa egy másik kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a kiszolgáló címét (<id>@<kiszolgáló_cím>?key=<key_value>), például\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHa egy nyilvános kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a \"<id>@public\" betűt. A kulcsra nincs szükség a nyilvános kiszolgálók esetében."),
|
||||
("Download", "Letöltés"),
|
||||
("Upload folder", "Mappa feltöltése"),
|
||||
("Upload files", "Fájlok feltöltése"),
|
||||
@@ -682,9 +682,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Downloading {}", "{} letöltése"),
|
||||
("{} Update", "{} frissítés"),
|
||||
("{}-to-update-tip", "A(z) {} bezárása és az új verzió telepítése."),
|
||||
("download-new-version-failed-tip", "Ha a letöltés sikertelen, akkor vagy újrapróbálkozhat, vagy a „Letöltés” gombra kattintva letöltheti a kiadási oldalról, és manuálisan frissíthet."),
|
||||
("download-new-version-failed-tip", "Ha a letöltés sikertelen, akkor vagy újrapróbálkozhat, vagy a \"Letöltés\" gombra kattintva letöltheti a kiadási oldalról, és manuálisan frissíthet."),
|
||||
("Auto update", "Automatikus frissítés"),
|
||||
("update-failed-check-msi-tip", "A telepítési módszer felismerése nem sikerült. Kattintson a „Letöltés” gombra, hogy letöltse a kiadási oldalról, és manuálisan frissítse."),
|
||||
("update-failed-check-msi-tip", "A telepítési módszer felismerése nem sikerült. Kattintson a \"Letöltés\" gombra, hogy letöltse a kiadási oldalról, és manuálisan frissítse."),
|
||||
("websocket_tip", "WebSocket használatakor csak a relé-kapcsolatok támogatottak."),
|
||||
("Use WebSocket", "WebSocket használata"),
|
||||
("Trackpad speed", "Érintőpad sebessége"),
|
||||
@@ -727,14 +727,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Disable UDP", "UDP letiltása"),
|
||||
("disable-udp-tip", "Meghatározza, hogy csak TCP-t használjon-e. Ha ez az beállítás engedélyezve van, a RustDesk nem fogja többé használni a 21116-os UDP-portot, helyette a 21116-os TCP-portot fogja használni."),
|
||||
("server-oss-not-support-tip", "MEGJEGYZÉS: Az OSS RustDesk kiszolgáló nem támogatja ezt a funkciót."),
|
||||
("input note here", "Megjegyzés bevitele"),
|
||||
("note-at-conn-end-tip", "Megjegyzés a kapcsolat végén"),
|
||||
("input note here", "Megjegyzés beírása"),
|
||||
("note-at-conn-end-tip", "Kérjen megjegyzést a kapcsolat végén"),
|
||||
("Show terminal extra keys", "További terminálgombok megjelenítése"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Relative mouse mode", "Relatív egér mód"),
|
||||
("rel-mouse-not-supported-peer-tip", "A kapcsolódott partner nem támogatja a relatív egér módot."),
|
||||
("rel-mouse-not-ready-tip", "A relatív egér mód még nem elérhető. Próbálja meg újra."),
|
||||
("rel-mouse-lock-failed-tip", "Nem sikerült zárolni a kurzort. A relatív egér mód le lett tiltva."),
|
||||
("rel-mouse-exit-{}-tip", "A kilépéshez nyomja meg a(z) {} gombot."),
|
||||
("rel-mouse-permission-lost-tip", "A billentyűzet-hozzáférés vissza lett vonva. A relatív egér mód le lett tilva."),
|
||||
("Changelog", "Változáslista"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Képernyő aktív állapotban tartása a kimenő munkamenetek során"),
|
||||
("keep-awake-during-incoming-sessions-label", "Képernyő aktív állapotban tartása a bejövő munkamenetek során"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,11 +730,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "Inserisci nota qui"),
|
||||
("note-at-conn-end-tip", "Visualizza nota alla fine della connessione"),
|
||||
("Show terminal extra keys", "Visualizza tasti aggiuntivi terminale"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Relative mouse mode", "Modalità relativa mouse"),
|
||||
("rel-mouse-not-supported-peer-tip", "La modalità mouse relativa non è supportata dal peer connesso."),
|
||||
("rel-mouse-not-ready-tip", "La modalità mouse relativa non è ancora pronta. Riprova."),
|
||||
("rel-mouse-lock-failed-tip", "Impossibile bloccare il cursore. La modalità mouse relativa è stata disabilitata."),
|
||||
("rel-mouse-exit-{}-tip", "Premi {} per uscire."),
|
||||
("rel-mouse-permission-lost-tip", "È stata revocato l'accesso alla tastiera. La modalità mouse relativa è stata disabilitata."),
|
||||
("Changelog", "Novità programma"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Mantieni lo schermo attivo durante le sessioni in uscita"),
|
||||
("keep-awake-during-incoming-sessions-label", "Mantieni lo schermo attivo durante le sessioni in ingresso"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -3,30 +3,30 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "상태"),
|
||||
("Your Desktop", "내 데스크탑"),
|
||||
("desk_tip", "이 ID와 비밀번호로 데스크톱에 액세스할 수 있습니다."),
|
||||
("desk_tip", "이 ID와 비밀번호로 데스크탑에 액세스할 수 있습니다."),
|
||||
("Password", "비밀번호"),
|
||||
("Ready", "준비 완료"),
|
||||
("Established", "연결됨"),
|
||||
("connecting_status", "RustDesk 네트워크에 연결 중..."),
|
||||
("Enable service", "서비스 활성화"),
|
||||
("Start service", "서비스 시작"),
|
||||
("Service is running", "서비스가 실행 중 입니다"),
|
||||
("Service is running", "서비스가 실행 중입니다"),
|
||||
("Service is not running", "서비스가 실행되지 않았습니다"),
|
||||
("not_ready_status", "준비되지 않았습니다. 연결을 확인해 주세요"),
|
||||
("Control Remote Desktop", "원격 데스크탑 제어"),
|
||||
("Transfer file", "파일 전송"),
|
||||
("Connect", "연결"),
|
||||
("Recent sessions", "최근 세션"),
|
||||
("Address book", "세션 주소록"),
|
||||
("Address book", "주소록"),
|
||||
("Confirmation", "확인"),
|
||||
("TCP tunneling", "TCP 터널링"),
|
||||
("Remove", "삭제"),
|
||||
("Refresh random password", "임의의 비밀번호 새로 고침"),
|
||||
("Set your own password", "자신만의 비밀번호 설정"),
|
||||
("Enable keyboard/mouse", "키보드/마우스 사용함"),
|
||||
("Enable clipboard", "클립보드 사용함"),
|
||||
("Enable file transfer", "파일 전송 사용함"),
|
||||
("Enable TCP tunneling", "TCP 터널링 사용함"),
|
||||
("Enable keyboard/mouse", "키보드/마우스 허용"),
|
||||
("Enable clipboard", "클립보드 허용"),
|
||||
("Enable file transfer", "파일 전송 허용"),
|
||||
("Enable TCP tunneling", "TCP 터널링 허용"),
|
||||
("IP Whitelisting", "IP 화이트리스트"),
|
||||
("ID/Relay Server", "ID/릴레이 서버"),
|
||||
("Import server config", "서버 구성 가져오기"),
|
||||
@@ -81,7 +81,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please try 1 minute later", "1분 후에 다시 시도하세요"),
|
||||
("Login Error", "로그인 오류"),
|
||||
("Successful", "성공"),
|
||||
("Connected, waiting for image...", "연결되었습니다, 이미지를 기다리는 중..."),
|
||||
("Connected, waiting for image...", "연결됨, 화면을 기다리는 중..."),
|
||||
("Name", "이름"),
|
||||
("Type", "유형"),
|
||||
("Modified", "수정 날짜"),
|
||||
@@ -136,20 +136,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ID does not exist", "ID가 존재하지 않습니다"),
|
||||
("Failed to connect to rendezvous server", "랑데부 서버 연결에 실패했습니다"),
|
||||
("Please try later", "나중에 시도해 주세요"),
|
||||
("Remote desktop is offline", "원격 데스크톱이 오프라인입니다"),
|
||||
("Remote desktop is offline", "원격 데스크탑이 오프라인입니다"),
|
||||
("Key mismatch", "키가 일치하지 않습니다"),
|
||||
("Timeout", "시간 초과"),
|
||||
("Failed to connect to relay server", "릴레이 서버 연결에 실패했습니다"),
|
||||
("Failed to connect via rendezvous server", "랑데부 서버를 통한 연결에 실패했습니다"),
|
||||
("Failed to connect via relay server", "릴레이 서버를 통한 연결에 실패했습니다"),
|
||||
("Failed to make direct connection to remote desktop", "원격 데스크톱에 직접 연결에 실패했습니다"),
|
||||
("Failed to make direct connection to remote desktop", "원격 데스크탑 직접 연결에 실패했습니다"),
|
||||
("Set Password", "비밀번호 설정"),
|
||||
("OS Password", "OS 비밀번호"),
|
||||
("install_tip", "UAC로 인해 경우에 따라 RustDesk가 원격 쪽에서 제대로 작동하지 않을 수 있습니다. UAC를 피하려면 아래 버튼을 클릭하여 시스템에 RustDesk를 설치하세요."),
|
||||
("Click to upgrade", "업그레이드"),
|
||||
("Configure", "구성"),
|
||||
("config_acc", "데스크톱을 원격으로 제어하려면 RustDesk에 \"접근성\" 권한을 부여해야 합니다."),
|
||||
("config_screen", "데스크톱에 원격으로 액세스하려면 RustDesk에 \"화면 녹화\" 권한을 부여해야 합니다."),
|
||||
("config_acc", "데스크탑을 원격으로 제어하려면 RustDesk에 \"접근성\" 권한을 부여해야 합니다."),
|
||||
("config_screen", "데스크탑에 원격으로 액세스하려면 RustDesk에 \"화면 녹화\" 권한을 부여해야 합니다."),
|
||||
("Installing ...", "설치 중..."),
|
||||
("Install", "설치하기"),
|
||||
("Installation", "설치"),
|
||||
@@ -162,7 +162,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Generating ...", "생성 중 ..."),
|
||||
("Your installation is lower version.", "설치된 버전이 낮습니다."),
|
||||
("not_close_tcp_tip", "터널을 사용하는 동안에는 이 창을 닫지 마세요"),
|
||||
("Listening ...", "청취 중 ..."),
|
||||
("Listening ...", "수신 대기 중 ..."),
|
||||
("Remote Host", "원격 호스트"),
|
||||
("Remote Port", "원격 포트"),
|
||||
("Action", "동작"),
|
||||
@@ -177,7 +177,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept", "수락"),
|
||||
("Dismiss", "거부"),
|
||||
("Disconnect", "연결 해제"),
|
||||
("Enable file copy and paste", "파일 복사 및 붙여넣기 사용함"),
|
||||
("Enable file copy and paste", "파일 복사 및 붙여넣기 허용"),
|
||||
("Connected", "연결됨"),
|
||||
("Direct and encrypted connection", "직접 및 암호화된 연결"),
|
||||
("Relayed and encrypted connection", "릴레이 및 암호화된 연결"),
|
||||
@@ -186,9 +186,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enter Remote ID", "원격 ID 입력"),
|
||||
("Enter your password", "비밀번호 입력"),
|
||||
("Logging in...", "로그인 중..."),
|
||||
("Enable RDP session sharing", "RDP 세션 공유 사용함"),
|
||||
("Enable RDP session sharing", "RDP 세션 공유 허용"),
|
||||
("Auto Login", "자동 로그인"),
|
||||
("Enable direct IP access", "직접 IP 액세스 사용함"),
|
||||
("Enable direct IP access", "직접 IP 액세스 허용"),
|
||||
("Rename", "이름 바꾸기"),
|
||||
("Space", "공백"),
|
||||
("Create desktop shortcut", "바탕 화면 바로가기 만들기"),
|
||||
@@ -200,13 +200,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Login screen using Wayland is not supported", "Wayland를 사용한 로그인 화면은 지원되지 않습니다"),
|
||||
("Reboot required", "재부팅이 필요합니다"),
|
||||
("Unsupported display server", "지원하지 않는 디스플레이 서버"),
|
||||
("x11 expected", "x11 예상"),
|
||||
("x11 expected", "x11 환경이 필요합니다"),
|
||||
("Port", "포트"),
|
||||
("Settings", "설정"),
|
||||
("Username", "사용자 이름"),
|
||||
("Invalid port", "유효하지 않은 포트입니다"),
|
||||
("Closed manually by the peer", "피어가 수동으로 닫았습니다"),
|
||||
("Enable remote configuration modification", "원격 구성 수정 사용함"),
|
||||
("Enable remote configuration modification", "원격 구성 수정 허용"),
|
||||
("Run without install", "설치 없이 실행"),
|
||||
("Connect via relay", "릴레이를 통해 연결"),
|
||||
("Always connect via relay", "항상 릴레이를 통해 연결"),
|
||||
@@ -214,7 +214,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Login", "로그인"),
|
||||
("Verify", "확인"),
|
||||
("Remember me", "기억하기"),
|
||||
("Trust this device", "이 장치 신뢰"),
|
||||
("Trust this device", "이 장치를 신뢰"),
|
||||
("Verification code", "인증 코드"),
|
||||
("verification_tip", "등록한 이메일 주소로 인증 코드가 전송되었으니 인증 코드를 입력하여 로그인을 계속하세요."),
|
||||
("Logout", "로그아웃"),
|
||||
@@ -291,7 +291,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Help", "도움말"),
|
||||
("Failed", "실패"),
|
||||
("Succeeded", "성공"),
|
||||
("Someone turns on privacy mode, exit", "누군가가 개인정보 보호 모드를 켭니다, 종료합니다"),
|
||||
("Someone turns on privacy mode, exit", "누군가 개인정보 보호 모드를 켰습니다, 연결을 종료합니다"),
|
||||
("Unsupported", "지원되지 않음"),
|
||||
("Peer denied", "연결 거부됨"),
|
||||
("Please install plugins", "플러그인을 설치해주세요"),
|
||||
@@ -311,7 +311,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Use permanent password", "영구 비밀번호 사용"),
|
||||
("Use both passwords", "두 가지 비밀번호 모두 사용"),
|
||||
("Set permanent password", "영구 비밀번호 설정"),
|
||||
("Enable remote restart", "원격 재시작 사용함"),
|
||||
("Enable remote restart", "원격 재시작 허용"),
|
||||
("Restart remote device", "원격 장치 다시 시작"),
|
||||
("Are you sure you want to restart", "다시 시작하시겠습니까"),
|
||||
("Restarting remote device", "원격 장치를 다시 시작하는 중"),
|
||||
@@ -344,7 +344,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Follow System", "시스템 설정 따름"),
|
||||
("Enable hardware codec", "하드웨어 코덱 활성화"),
|
||||
("Unlock Security Settings", "보안 설정 잠금 해제"),
|
||||
("Enable audio", "오디오 사용함"),
|
||||
("Enable audio", "오디오 허용"),
|
||||
("Unlock Network Settings", "네트워크 설정 잠금 해제"),
|
||||
("Server", "서버"),
|
||||
("Direct IP Access", "직접 IP 연결"),
|
||||
@@ -364,13 +364,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Change", "변경"),
|
||||
("Start session recording", "세션 녹화 시작"),
|
||||
("Stop session recording", "세션 녹화 중지"),
|
||||
("Enable recording session", "세션 녹화 사용함"),
|
||||
("Enable LAN discovery", "LAN 검색 사용함"),
|
||||
("Enable recording session", "세션 녹화 허용"),
|
||||
("Enable LAN discovery", "LAN 검색 허용"),
|
||||
("Deny LAN discovery", "LAN 검색 거부"),
|
||||
("Write a message", "메시지 쓰기"),
|
||||
("Prompt", "프롬프트"),
|
||||
("Please wait for confirmation of UAC...", "UAC 확인을 기다려주세요..."),
|
||||
("elevated_foreground_window_tip", "원격 데스크톱의 현재 창을 작동하려면 더 높은 권한이 필요하므로 일시적으로 마우스와 키보드를 사용할 수 없습니다. 원격 사용자에게 현재 창을 최소화하도록 요청하거나 연결 관리 창에서 권한 상승 버튼을 클릭할 수 있습니다. 이 문제를 방지하려면 원격 장치에 소프트웨어를 설치하는 것이 좋습니다."),
|
||||
("elevated_foreground_window_tip", "원격 데스크탑의 현재 창을 작동하려면 더 높은 권한이 필요하므로 일시적으로 마우스와 키보드를 사용할 수 없습니다. 원격 사용자에게 현재 창을 최소화하도록 요청하거나 연결 관리 창에서 권한 상승 버튼을 클릭할 수 있습니다. 이 문제를 방지하려면 원격 장치에 소프트웨어를 설치하는 것이 좋습니다."),
|
||||
("Disconnected", "연결 끊김"),
|
||||
("Other", "기타"),
|
||||
("Confirm before closing multiple tabs", "여러 탭을 닫기 전에 확인"),
|
||||
@@ -378,7 +378,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Full Access", "전체 액세스"),
|
||||
("Screen Share", "화면 공유"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland는 Ubuntu 21.04 이상 버전이 필요합니다."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland는 상위 버전의 Linux 배포판이 필요합니다. X11 데스크톱을 사용하거나 OS를 변경하세요."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland는 상위 버전의 Linux 배포판이 필요합니다. X11 데스크탑을 사용하거나 OS를 변경하세요."),
|
||||
("JumpLink", "점프 링크"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "공유할 화면을 선택하세요 (피어 측에서 작동)"),
|
||||
("Show RustDesk", "RustDesk 표시"),
|
||||
@@ -408,7 +408,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Select local keyboard type", "로컬 키보드 유형 선택"),
|
||||
("software_render_tip", "Linux에서 Nvidia 그래픽 카드를 사용 중인데 원격 창이 연결 즉시 닫히는 경우 오픈 소스 Nouveau 드라이버로 전환하고 소프트웨어 렌더링을 사용하기로 선택하는 것이 도움이 될 수 있습니다. 소프트웨어를 재시작해야 합니다."),
|
||||
("Always use software rendering", "항상 소프트웨어 렌더링 사용"),
|
||||
("config_input", "키보드로 원격 데스크톱을 제어하려면 RustDesk에 \"입력 모니터링\" 권한을 부여해야 합니다."),
|
||||
("config_input", "키보드로 원격 데스크탑을 제어하려면 RustDesk에 \"입력 모니터링\" 권한을 부여해야 합니다."),
|
||||
("config_microphone", "원격으로 통화하려면 RustDesk에 \"오디오 녹음\" 권한을 부여해야 합니다."),
|
||||
("request_elevation_tip", "원격 측에 사람이 있는 경우 권한 상승을 요청할 수도 있습니다."),
|
||||
("Wait", "대기"),
|
||||
@@ -428,7 +428,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Weak", "약함"),
|
||||
("Medium", "보통"),
|
||||
("Strong", "강력"),
|
||||
("Switch Sides", "측면 전환"),
|
||||
("Switch Sides", "역할 전환"),
|
||||
("Please confirm if you want to share your desktop?", "데스크탑을 공유하시겠습니까?"),
|
||||
("Display", "디스플레이"),
|
||||
("Default View Style", "기본 보기 스타일"),
|
||||
@@ -468,14 +468,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("login_linux_tip", "X 데스크탑을 활성화하려면 제어되는 터미널의 Linux 계정에 로그인하세요"),
|
||||
("verify_rustdesk_password_tip", "RustDesk 비밀번호 확인"),
|
||||
("remember_account_tip", "이 계정 기억하기"),
|
||||
("os_account_desk_tip", "이 계정은 원격 OS에 로그인하고 헤드리스에서 데스크톱 세션을 활성화하는 데 사용됩니다."),
|
||||
("os_account_desk_tip", "이 계정은 원격 OS에 로그인하고 헤드리스에서 데스크탑 세션을 활성화하는 데 사용됩니다."),
|
||||
("OS Account", "OS 계정"),
|
||||
("another_user_login_title_tip", "다른 사용자가 이미 로그인했습니다"),
|
||||
("another_user_login_text_tip", "연결 끊기"),
|
||||
("xorg_not_found_title_tip", "Xorg를 찾을 수 없습니다"),
|
||||
("xorg_not_found_text_tip", "Xorg를 설치해 주세요"),
|
||||
("no_desktop_title_tip", "사용 가능한 데스크톱 환경이 없습니다"),
|
||||
("no_desktop_text_tip", "GNOME 데스크톱을 설치해 주세요"),
|
||||
("no_desktop_title_tip", "사용 가능한 데스크탑 환경이 없습니다"),
|
||||
("no_desktop_text_tip", "GNOME 데스크탑을 설치해 주세요"),
|
||||
("No need to elevate", "권한 상승이 필요없습니다"),
|
||||
("System Sound", "시스템 소리"),
|
||||
("Default", "기본"),
|
||||
@@ -488,7 +488,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Plugins", "플러그인"),
|
||||
("Uninstall", "설치 제거"),
|
||||
("Update", "업데이트"),
|
||||
("Enable", "사용함"),
|
||||
("Enable", "허용"),
|
||||
("Disable", "사용 안 함"),
|
||||
("Options", "옵션"),
|
||||
("resolution_original_tip", "원본 해상도"),
|
||||
@@ -558,7 +558,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Virtual display", "가상 디스플레이"),
|
||||
("Plug out all", "모든 플러그를 뽑으세요"),
|
||||
("True color (4:4:4)", "트루컬러 (4:4:4)"),
|
||||
("Enable blocking user input", "사용자 입력 차단 사용함"),
|
||||
("Enable blocking user input", "사용자 입력 차단 허용"),
|
||||
("id_input_tip", "ID, 직접 IP 또는 포트가 있는 도메인 (<domain>:<port>)을 입력할 수 있습니다.\n다른 서버에 있는 장치에 액세스하려면 서버 주소 (<id>@<server_address>?key=<key_value>)를 추가하세요. 예를들어 \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\n공용 서버의 장치에 액세스하려면 \"<id>@public\"을 입력하세요. 공용 서버에서는 키가 필요하지 않습니다.\n\n첫 번째 연결에서 릴레이 연결을 강제로 사용하려면 ID 끝에 \"/r\"을 추가합니다, 예를들면 \"9123456234/r\"."),
|
||||
("privacy_mode_impl_mag_tip", "모드 1"),
|
||||
("privacy_mode_impl_virtual_display_tip", "모드 2"),
|
||||
@@ -571,8 +571,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("swap-left-right-mouse", "마우스 왼쪽 버튼과 오른쪽 버튼 교체"),
|
||||
("2FA code", "이중 인증 코드"),
|
||||
("More", "더 많은"),
|
||||
("enable-2fa-title", "이중 인증 사용함"),
|
||||
("enable-2fa-desc", "지금 인증앱을 설정해 주세요. 휴대폰이나 데스크톱에서 Authy, Microsoft 또는 Google 인증기와 같은 인증기 앱을 사용할 수 있습니다.\n\n앱으로 QR 코드를 스캔하고 앱에 표시된 코드를 입력하면 이중 인증이 가능합니다."),
|
||||
("enable-2fa-title", "이중 인증 허용"),
|
||||
("enable-2fa-desc", "지금 인증앱을 설정해 주세요. 휴대폰이나 데스크탑에서 Authy, Microsoft 또는 Google 인증기와 같은 인증기 앱을 사용할 수 있습니다.\n\n앱으로 QR 코드를 스캔하고 앱에 표시된 코드를 입력하면 이중 인증이 가능합니다."),
|
||||
("wrong-2fa-code", "코드를 확인할 수 없습니다. 코드와 현지 시간 설정이 올바른지 확인합니다"),
|
||||
("enter-2fa-title", "이중 인증"),
|
||||
("Email verification code must be 6 characters.", "이메일 인증 코드는 6자여야 합니다."),
|
||||
@@ -621,7 +621,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Volume down", "볼륨 낮추기"),
|
||||
("Power", "전원"),
|
||||
("Telegram bot", "Telegram 봇"),
|
||||
("enable-bot-tip", "이 기능을 활성화하면 봇에서 이중 인중 코드를 받을 수 있습니다. 또한 연결 알림 기능도 할 수 있습니다."),
|
||||
("enable-bot-tip", "이 기능을 활성화하면 봇에서 이중 인증 코드를 받을 수 있습니다. 또한 연결 알림 기능도 할 수 있습니다."),
|
||||
("enable-bot-desc", "1. @BotFather와 채팅을 시작합니다.\n2. \"/newbot\" 명령을 보내주세요. 이 단계를 완료하면 토큰을 받게 됩니다.\n3. 새로 만든 봇과 채팅을 시작합니다. \"/hello\"와 같이 앞에 슬래시 (\"/\")로 시작하는 메시지를 보내 활성화합니다."),
|
||||
("cancel-2fa-confirm-tip", "이중 인증을 취소하시겠습니까?"),
|
||||
("cancel-bot-confirm-tip", "Telegram 봇을 취소하시겠습니까?"),
|
||||
@@ -632,7 +632,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Requires at least {} characters", "최소 {}자 이상 필요합니다."),
|
||||
("Wrong PIN", "잘못된 PIN"),
|
||||
("Set PIN", "PIN 설정"),
|
||||
("Enable trusted devices", "신뢰할 수 있는 장치 사용함"),
|
||||
("Enable trusted devices", "신뢰할 수 있는 장치 허용"),
|
||||
("Manage trusted devices", "신뢰할 수 있는 장치 관리"),
|
||||
("Platform", "플랫폼"),
|
||||
("Days remaining", "일 남음"),
|
||||
@@ -678,7 +678,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-action-tip", "스크린샷을 계속 진행할 방법을 선택해 주세요."),
|
||||
("Save as", "다른 이름으로 저장"),
|
||||
("Copy to clipboard", "클립보드에 복사"),
|
||||
("Enable remote printer", "원격 프린터 사용함"),
|
||||
("Enable remote printer", "원격 프린터 허용"),
|
||||
("Downloading {}", "{} 다운로드 중"),
|
||||
("{} Update", "{} 업데이트"),
|
||||
("{}-to-update-tip", "{}가 지금 닫히고 새 버전을 설치합니다."),
|
||||
@@ -693,11 +693,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable IPv6 P2P connection", "IPv6 P2P 연결 사용"),
|
||||
("Enable UDP hole punching", "UDP 홀 펀칭 사용"),
|
||||
("View camera", "카메라 보기"),
|
||||
("Enable camera", "카메라 사용함"),
|
||||
("Enable camera", "카메라 허용"),
|
||||
("No cameras", "카메라 없음"),
|
||||
("view_camera_unsupported_tip", "원격 장치가 카메라 보기를 지원하지 않습니다."),
|
||||
("Terminal", "터미널"),
|
||||
("Enable terminal", "터미널 사용함"),
|
||||
("Enable terminal", "터미널 허용"),
|
||||
("New tab", "새 탭"),
|
||||
("Keep terminal sessions on disconnect", "연결이 끊어져도 터미널 세션 유지"),
|
||||
("Terminal (Run as administrator)", "터미널 (관리자 권한으로 실행)"),
|
||||
@@ -730,11 +730,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "여기에 노트 입력"),
|
||||
("note-at-conn-end-tip", "연결이 끝날 때 메모 요청"),
|
||||
("Show terminal extra keys", "터미널 추가 키 표시"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Relative mouse mode", "상대 마우스 모드"),
|
||||
("rel-mouse-not-supported-peer-tip", "연결된 피어에서 상대 마우스 모드를 지원하지 않습니다."),
|
||||
("rel-mouse-not-ready-tip", "상대 마우스 모드가 아직 준비되지 않았습니다. 다시 시도해 주세요."),
|
||||
("rel-mouse-lock-failed-tip", "커서 잠금에 실패했습니다. 상대 마우스 모드가 비활성화되었습니다"),
|
||||
("rel-mouse-exit-{}-tip", "종료하려면 {}을(를) 누르세요."),
|
||||
("rel-mouse-permission-lost-tip", "키보드 권한이 취소되었습니다. 상대 마우스 모드가 비활성화되었습니다."),
|
||||
("Changelog", "변경 기록"),
|
||||
("keep-awake-during-outgoing-sessions-label", "발신 세션 중 화면 켜짐 유지"),
|
||||
("keep-awake-during-incoming-sessions-label", "수신 세션 중 화면 켜짐 유지"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,11 +730,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "voeg hier een opmerking toe"),
|
||||
("note-at-conn-end-tip", "Vraag om een opmerking aan het einde van de verbinding"),
|
||||
("Show terminal extra keys", "Toon extra toetsen voor terminal"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Relative mouse mode", "Relatieve muismodus"),
|
||||
("rel-mouse-not-supported-peer-tip", "De relatieve muismodus wordt niet ondersteund door het externe apparaat."),
|
||||
("rel-mouse-not-ready-tip", "De relatieve muismodus was nog niet klaar, probeer het later opnieuw."),
|
||||
("rel-mouse-lock-failed-tip", "Het vergrendelen van de cursor is mislukt. De relatieve muismodus is uitgeschakeld."),
|
||||
("rel-mouse-exit-{}-tip", "Druk op {} om af te sluiten."),
|
||||
("rel-mouse-permission-lost-tip", "De toetsenbordcontrole is uitgeschakeld. De relatieve muismodus is uitgeschakeld."),
|
||||
("Changelog", "Wijzigingenlogboek"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Houd het scherm open tijdens de uitgaande sessies."),
|
||||
("keep-awake-during-incoming-sessions-label", "Houd het scherm open tijdens de inkomende sessies."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Service is running", "Usługa uruchomiona"),
|
||||
("Service is not running", "Usługa nie jest uruchomiona"),
|
||||
("not_ready_status", "Brak gotowości"),
|
||||
("Control Remote Desktop", "Połącz się z"),
|
||||
("Control Remote Desktop", "Steruj pulpitem zdalnym"),
|
||||
("Transfer file", "Transfer plików"),
|
||||
("Connect", "Połącz"),
|
||||
("Recent sessions", "Ostatnie sesje"),
|
||||
@@ -75,7 +75,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you want to enter again?", "Czy chcesz wprowadzić ponownie?"),
|
||||
("Connection Error", "Błąd połączenia"),
|
||||
("Error", "Błąd"),
|
||||
("Reset by the peer", "Połączenie zresetowanie przez zdalne urządzenie"),
|
||||
("Reset by the peer", "Połączenie zresetowane przez zdalne urządzenie"),
|
||||
("Connecting...", "Łączenie..."),
|
||||
("Connection in progress. Please wait.", "Trwa łączenie. Proszę czekać."),
|
||||
("Please try 1 minute later", "Spróbuj za minutę"),
|
||||
@@ -120,7 +120,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Oryginalny"),
|
||||
("Shrink", "Zmniejsz"),
|
||||
("Stretch", "Rozciągnij"),
|
||||
("Scrollbar", "Przewijanie ręczne"),
|
||||
("Scrollbar", "Pasek przewijania"),
|
||||
("ScrollAuto", "Przewijanie automatyczne"),
|
||||
("Good image quality", "Wysoka jakość obrazu"),
|
||||
("Balanced", "Tryb zbalansowany"),
|
||||
@@ -161,7 +161,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("End-user license agreement", "Umowa licencyjna użytkownika końcowego"),
|
||||
("Generating ...", "Trwa generowanie..."),
|
||||
("Your installation is lower version.", "Twoja instalacja jest w niższej wersji"),
|
||||
("not_close_tcp_tip", "Podczas korzystanie z tunelowania, nie zamykaj tego okna."),
|
||||
("not_close_tcp_tip", "Podczas korzystania z tunelowania, nie zamykaj tego okna."),
|
||||
("Listening ...", "Nasłuchiwanie..."),
|
||||
("Remote Host", "Host zdalny"),
|
||||
("Remote Port", "Port zdalny"),
|
||||
@@ -198,7 +198,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fix it", "Napraw to"),
|
||||
("Warning", "Ostrzeżenie"),
|
||||
("Login screen using Wayland is not supported", "Ekran logowania korzystający z Wayland nie jest obsługiwany"),
|
||||
("Reboot required", "Wymagany ponowne uruchomienie"),
|
||||
("Reboot required", "Wymagane ponowne uruchomienie"),
|
||||
("Unsupported display server", "Nieobsługiwany serwer wyświetlania"),
|
||||
("x11 expected", "Wymagany jest X11"),
|
||||
("Port", "Port"),
|
||||
@@ -225,7 +225,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add Tag", "Dodaj Tag"),
|
||||
("Unselect all tags", "Odznacz wszystkie tagi"),
|
||||
("Network error", "Błąd sieci"),
|
||||
("Username missed", "Nieprawidłowe nazwa użytkownika"),
|
||||
("Username missed", "Nieprawidłowa nazwa użytkownika"),
|
||||
("Password missed", "Nieprawidłowe hasło"),
|
||||
("Wrong credentials", "Błędne dane uwierzytelniające"),
|
||||
("The verification code is incorrect or has expired", "Kod weryfikacyjny jest niepoprawny lub wygasł"),
|
||||
@@ -265,7 +265,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("No permission of file transfer", "Brak uprawnień na przesyłanie plików"),
|
||||
("Note", "Notatka"),
|
||||
("Connection", "Połączenie"),
|
||||
("Share screen", "Udostępnij ekran"),
|
||||
("Share screen", "Udostępnianie ekranu"),
|
||||
("Chat", "Czat"),
|
||||
("Total", "Łącznie"),
|
||||
("items", "elementów"),
|
||||
@@ -314,10 +314,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable remote restart", "Włącz zdalne restartowanie"),
|
||||
("Restart remote device", "Zrestartuj zdalne urządzenie"),
|
||||
("Are you sure you want to restart", "Czy na pewno uruchomić ponownie"),
|
||||
("Restarting remote device", "Trwa restartowanie Zdalnego Urządzenia"),
|
||||
("Restarting remote device", "Trwa restartowanie zdalnego urządzenia"),
|
||||
("remote_restarting_tip", "Trwa ponownie uruchomienie zdalnego urządzenia, zamknij ten komunikat i ponownie nawiąż za chwilę połączenie używając hasła permanentnego"),
|
||||
("Copied", "Skopiowano"),
|
||||
("Exit Fullscreen", "Wyłączyć tryb pełnoekranowy"),
|
||||
("Exit Fullscreen", "Wyłącz tryb pełnoekranowy"),
|
||||
("Fullscreen", "Tryb pełnoekranowy"),
|
||||
("Mobile Actions", "Dostępne mobilne polecenia"),
|
||||
("Select Monitor", "Wybierz ekran"),
|
||||
@@ -379,7 +379,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Screen Share", "Udostępnianie ekranu"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland wymaga Ubuntu 21.04 lub nowszego."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland wymaga nowszej dystrybucji Linuksa. Wypróbuj pulpit X11 lub zmień system operacyjny."),
|
||||
("JumpLink", "View"),
|
||||
("JumpLink", "Podgląd"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po zdalnego urządzenia)."),
|
||||
("Show RustDesk", "Pokaż RustDesk"),
|
||||
("This PC", "Ten komputer"),
|
||||
@@ -403,13 +403,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to address book", "Dodaj do Książki Adresowej"),
|
||||
("Group", "Grupy"),
|
||||
("Search", "Szukaj"),
|
||||
("Closed manually by web console", "Zakończone manualnie z konsoli Web"),
|
||||
("Closed manually by web console", "Zakończone ręcznie z poziomu konsoli webowej"),
|
||||
("Local keyboard type", "Lokalny typ klawiatury"),
|
||||
("Select local keyboard type", "Wybierz lokalny typ klawiatury"),
|
||||
("software_render_tip", "Jeżeli posiadasz kartę graficzną Nvidia i okno zamyka się natychmiast po nawiązaniu połączenia, instalacja sterownika nouveau i wybór renderowania programowego mogą pomóc. Restart aplikacji jest wymagany."),
|
||||
("Always use software rendering", "Zawsze używaj renderowania programowego"),
|
||||
("config_input", "By kontrolować zdalne urządzenie przy pomocy klawiatury, musisz udzielić aplikacji RustDesk uprawnień do \"Urządzeń Wejściowych\"."),
|
||||
("config_microphone", "Aby umożliwić zdalne rozmowy należy przyznać RuskDesk uprawnienia do \"Nagrań audio\"."),
|
||||
("config_microphone", "Aby umożliwić zdalne rozmowy należy przyznać RustDesk uprawnienia do \"Nagrań audio\"."),
|
||||
("request_elevation_tip", "Możesz poprosić o podniesienie uprawnień jeżeli ktoś posiada dostęp do zdalnego urządzenia."),
|
||||
("Wait", "Czekaj"),
|
||||
("Elevation Error", "Błąd przy podnoszeniu uprawnień"),
|
||||
@@ -459,8 +459,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("empty_favorite_tip", "Brak ulubionych?\nZnajdźmy kogoś, z kim możesz się połączyć i dodaj Go do ulubionych!"),
|
||||
("empty_lan_tip", "Ojej, wygląda na to, że nie odkryliśmy żadnych urządzeń z RustDesk w Twojej sieci."),
|
||||
("empty_address_book_tip", "Ojej, wygląda na to, że nie ma żadnych wpisów w Twojej książce adresowej."),
|
||||
("Empty Username", "Pusty użytkownik"),
|
||||
("Empty Password", "Puste hasło"),
|
||||
("Empty Username", "Pole nazwy użytkownika jest puste"),
|
||||
("Empty Password", "Pole hasła jest puste"),
|
||||
("Me", "Ja"),
|
||||
("identical_file_tip", "Ten plik jest identyczny z plikiem na drugim komputerze."),
|
||||
("show_monitors_tip", "Pokaż monitory w zasobniku"),
|
||||
@@ -729,12 +729,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("server-oss-not-support-tip", "UWAGA: Serwer OSS RustDesk nie obsługuje tej funkcji."),
|
||||
("input note here", "Wstaw tutaj notatkę"),
|
||||
("note-at-conn-end-tip", "Poproś o notatkę po zakończeniu połączenia."),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Show terminal extra keys", "Pokaż dodatkowe klawisze terminala"),
|
||||
("Relative mouse mode", "Tryb przechwytywania myszy"),
|
||||
("rel-mouse-not-supported-peer-tip", "Zdalne urządzenie nie obsługuje trybu przechwytywania myszy"),
|
||||
("rel-mouse-not-ready-tip", "Tryb przechwytywania myszy nie jest gotowy"),
|
||||
("rel-mouse-lock-failed-tip", "Nie udało się przechwycić kursora myszy"),
|
||||
("rel-mouse-exit-{}-tip", "Aby wyłączyć tryb przechwytywania myszy, naciśnij {}"),
|
||||
("rel-mouse-permission-lost-tip", "Utracono uprawnienia do trybu przechwytywania myszy"),
|
||||
("Changelog", "Dziennik zmian"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Utrzymuj urządzenie w stanie aktywnym podczas sesji wychodzących"),
|
||||
("keep-awake-during-incoming-sessions-label", "Utrzymuj urządzenie w stanie aktywnym podczas sesji przychodzących"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -672,69 +672,72 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("remote-printing-disallowed-text-tip", "As configurações do dispositivo controlado não permitem impressão remota."),
|
||||
("save-settings-tip", "Salvar configurações"),
|
||||
("dont-show-again-tip", "Não mostrar novamente"),
|
||||
("Take screenshot", ""),
|
||||
("Taking screenshot", ""),
|
||||
("Take screenshot", "Capturar de tela"),
|
||||
("Taking screenshot", "Capturando tela"),
|
||||
("screenshot-merged-screen-not-supported-tip", ""),
|
||||
("screenshot-action-tip", ""),
|
||||
("Save as", ""),
|
||||
("Copy to clipboard", ""),
|
||||
("Enable remote printer", ""),
|
||||
("Save as", "Salvar como"),
|
||||
("Copy to clipboard", "Copiar para área de transferência"),
|
||||
("Enable remote printer", "Habilitar impressora remota"),
|
||||
("Downloading {}", ""),
|
||||
("{} Update", ""),
|
||||
("{}-to-update-tip", ""),
|
||||
("download-new-version-failed-tip", ""),
|
||||
("Auto update", ""),
|
||||
("update-failed-check-msi-tip", ""),
|
||||
("websocket_tip", ""),
|
||||
("Use WebSocket", ""),
|
||||
("Trackpad speed", ""),
|
||||
("download-new-version-failed-tip", "Falha no download. Você pode tentar novamente ou clicar no botão \"Download\" para baixar da página releases e atualizar manualmente."),
|
||||
("Auto update", "Atualização automática"),
|
||||
("update-failed-check-msi-tip", "Falha na verificação do método de instalação. Clique no botão \"Download\" para baixar da página releases e atualizar manualmente."),
|
||||
("websocket_tip", "Usando WebSocket, apenas conexões via relay são suportadas."),
|
||||
("Use WebSocket", "Usar WebSocket"),
|
||||
("Trackpad speed", "Velocidade do trackpad"),
|
||||
("Default trackpad speed", ""),
|
||||
("Numeric one-time password", ""),
|
||||
("Enable IPv6 P2P connection", ""),
|
||||
("Enable UDP hole punching", ""),
|
||||
("Numeric one-time password", "Senha numérica de uso único"),
|
||||
("Enable IPv6 P2P connection", "Habilitar conexão IPv6 P2P"),
|
||||
("Enable UDP hole punching", "Habilitar UDP hole punching"),
|
||||
("View camera", "Visualizar câmera"),
|
||||
("Enable camera", "Ativar câmera"),
|
||||
("No cameras", "Sem câmeras"),
|
||||
("view_camera_unsupported_tip", "O dispositivo remoto não suporta visualização da câmera."),
|
||||
("Terminal", ""),
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only in the installed version.", ""),
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Terminal", "Terminal"),
|
||||
("Enable terminal", "Habilitar Terminal"),
|
||||
("New tab", "Nova aba"),
|
||||
("Keep terminal sessions on disconnect", "Manter sessões de terminal ao desconectar"),
|
||||
("Terminal (Run as administrator)", "Terminal (Executar como administrador)"),
|
||||
("terminal-admin-login-tip", "Insira o nome do usuário e senha de administrador do dispositivo controlado."),
|
||||
("Failed to get user token.", "Falha ao obter token do usuário."),
|
||||
("Incorrect username or password.", "Usuário ou senha incorretos"),
|
||||
("The user is not an administrator.", "O usuário não é administrador"),
|
||||
("Failed to check if the user is an administrator.", "Falha ao verificar se o usuário é administrador"),
|
||||
("Supported only in the installed version.", "Funciona somente na versão instalada"),
|
||||
("elevation_username_tip", "Insira o nome do usuário ou domínio\\usuário"),
|
||||
("Preparing for installation ...", "Preparando para instalação ..."),
|
||||
("Show my cursor", "Mostrar meu cursor"),
|
||||
("Scale custom", "Escala personalizada"),
|
||||
("Custom scale slider", "Controle deslizante de escala personalizada"),
|
||||
("Decrease", "Diminuir"),
|
||||
("Increase", "Aumentar"),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual mouse", "Mostrar mouse virtual"),
|
||||
("Virtual mouse size", "Tamanho do mouse virtual"),
|
||||
("Small", "Pequeno"),
|
||||
("Large", "Grande"),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
("ScrollEdge", ""),
|
||||
("Edit note", "Editar nota"),
|
||||
("Alias", "Apelido"),
|
||||
("ScrollEdge", "Rolagem nas bordas"),
|
||||
("Allow insecure TLS fallback", ""),
|
||||
("allow-insecure-tls-fallback-tip", ""),
|
||||
("Disable UDP", ""),
|
||||
("disable-udp-tip", ""),
|
||||
("server-oss-not-support-tip", ""),
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("allow-insecure-tls-fallback-tip", "Por padrão, o RustDesk verifica o certificado do servidor para protocolos que usam TLS.\nCom esta opção habilitada, o RustDesk ignorará a verificação e prosseguirá em caso de falha."),
|
||||
("Disable UDP", "Desabilitar UDP"),
|
||||
("disable-udp-tip", "Controla se deve usar somente TCP.\nCom esta opção habilitada, o RustDesk não usará mais UDP 21116, TCP 21116 será usado no lugar."),
|
||||
("server-oss-not-support-tip", "NOTA: O servidor RustDesk OSS não inclui este recurso."),
|
||||
("input note here", "Insira uma nota aqui"),
|
||||
("note-at-conn-end-tip", "Solicitar nota ao final da conexão"),
|
||||
("Show terminal extra keys", "Mostrar teclas extras do terminal"),
|
||||
("Relative mouse mode", "Modo de Mouse Relativo"),
|
||||
("rel-mouse-not-supported-peer-tip", "O Modo de Mouse Relativo não é suportado pelo parceiro conectado."),
|
||||
("rel-mouse-not-ready-tip", "O Modo de Mouse Relativo ainda não está pronto. Por favor, tente novamente."),
|
||||
("rel-mouse-lock-failed-tip", "Falha ao bloquear o cursor. O Modo de Mouse Relativo foi desabilitado."),
|
||||
("rel-mouse-exit-{}-tip", "Pressione {} para sair."),
|
||||
("rel-mouse-permission-lost-tip", "Permissão de teclado revogada. O Modo Mouse Relativo foi desabilitado."),
|
||||
("Changelog", "Registro de alterações"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Manter tela ativa durante sessões de saída"),
|
||||
("keep-awake-during-incoming-sessions-label", "Manter tela ativa durante sessões de entrada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,11 +730,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "введите заметку"),
|
||||
("note-at-conn-end-tip", "Запрашивать заметку в конце соединения"),
|
||||
("Show terminal extra keys", "Показывать дополнительные кнопки терминала"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Relative mouse mode", "Режим относительного перемещения мыши"),
|
||||
("rel-mouse-not-supported-peer-tip", "Режим относительного перемещения мыши не поддерживается подключённым узлом."),
|
||||
("rel-mouse-not-ready-tip", "Режим относительного перемещения мыши ещё не готов. Попробуйте снова."),
|
||||
("rel-mouse-lock-failed-tip", "Невозможно заблокировать курсор. Режим относительного перемещения мыши отключён."),
|
||||
("rel-mouse-exit-{}-tip", "Нажмите {} для выхода."),
|
||||
("rel-mouse-permission-lost-tip", "Разрешение на использование клавиатуры отменено. Режим относительного перемещения мыши отключён."),
|
||||
("Changelog", "Журнал изменений"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Не отключать экран во время исходящих сеансов"),
|
||||
("keep-awake-during-incoming-sessions-label", "Не отключать экран во время входящих сеансов"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Mute", "Sustur"),
|
||||
("Build Date", "Yapım Tarihi"),
|
||||
("Version", "Sürüm"),
|
||||
("Home", ""),
|
||||
("Home", "Anasayfa"),
|
||||
("Audio Input", "Ses Girişi"),
|
||||
("Enhancements", "Geliştirmeler"),
|
||||
("Hardware Codec", "Donanımsal Codec"),
|
||||
@@ -309,7 +309,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Map mode", "Haritalama modu"),
|
||||
("Translate mode", "Çeviri modu"),
|
||||
("Use permanent password", "Kalıcı şifre kullan"),
|
||||
("Use both passwords", "İki şifreyide kullan"),
|
||||
("Use both passwords", "İki şifreyi de kullan"),
|
||||
("Set permanent password", "Kalıcı şifre oluştur"),
|
||||
("Enable remote restart", "Uzaktan yeniden başlatmayı aktif et"),
|
||||
("Restart remote device", "Uzaktaki cihazı yeniden başlat"),
|
||||
@@ -366,7 +366,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", "Oturum kaydını sonlandır"),
|
||||
("Enable recording session", "Kayıt Oturumunu Aktif Et"),
|
||||
("Enable LAN discovery", "Yerel Ağ Keşfine İzin Ver"),
|
||||
("Deny LAN discovery", "Yerl Ağ Keşfine İzin Verme"),
|
||||
("Deny LAN discovery", "Yerel Ağ Keşfine İzin Verme"),
|
||||
("Write a message", "Bir mesaj yazın"),
|
||||
("Prompt", "İstem"),
|
||||
("Please wait for confirmation of UAC...", "UAC onayı için lütfen bekleyiniz..."),
|
||||
@@ -568,7 +568,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input_source_1_tip", "Giriş kaynağı 1"),
|
||||
("input_source_2_tip", "Giriş kaynağı 2"),
|
||||
("Swap control-command key", "Kontrol-komut tuşunu değiştir"),
|
||||
("swap-left-right-mouse", "sol-sağ fareyi değiştir"),
|
||||
("swap-left-right-mouse", "Sol-sağ fare tuşlarını değiştir"),
|
||||
("2FA code", "2FA kodu"),
|
||||
("More", "Daha"),
|
||||
("enable-2fa-title", "İki faktörlü kimlik doğrulamayı etkinleştir"),
|
||||
@@ -598,7 +598,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Fiziksel ekran yok, gizlilik modunu kullanmaya gerek yok."),
|
||||
("Follow remote cursor", "Uzak imleci takip et"),
|
||||
("Follow remote window focus", "Uzak pencere odağını takip et"),
|
||||
("default_proxy_tip", ""),
|
||||
("default_proxy_tip", "Varsayılan protokol ve port Socks5 ve 1080'dir."),
|
||||
("no_audio_input_device_tip", "Varsayılan protokol ve port, Socks5 ve 1080'dir"),
|
||||
("Incoming", "Gelen"),
|
||||
("Outgoing", "Giden"),
|
||||
@@ -696,8 +696,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable camera", "Kamerayı etkinleştir"),
|
||||
("No cameras", "Kamera yok"),
|
||||
("view_camera_unsupported_tip", "Uzak cihaz, kameranın görüntülenmesini desteklemiyor."),
|
||||
("Terminal", ""),
|
||||
("Enable terminal", ""),
|
||||
("Terminal", "Terminal"),
|
||||
("Enable terminal", "Terminali etkinleştir"),
|
||||
("New tab", "Yeni sekme"),
|
||||
("Keep terminal sessions on disconnect", "Bağlantı kesildiğinde terminal oturumlarını açık tut"),
|
||||
("Terminal (Run as administrator)", "Terminal (Yönetici olarak çalıştır)"),
|
||||
@@ -730,11 +730,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "Notu buraya girin"),
|
||||
("note-at-conn-end-tip", "Bağlantı bittiğinde not sorulsun"),
|
||||
("Show terminal extra keys", "Terminal ek tuşlarını göster"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Relative mouse mode", "Fareyi göreli modda kullan"),
|
||||
("rel-mouse-not-supported-peer-tip", "Karşı taraf göreli fare modunu desteklemiyor"),
|
||||
("rel-mouse-not-ready-tip", "Göreli fare modu henüz hazır değil"),
|
||||
("rel-mouse-lock-failed-tip", "Göreli fare kilitlenemedi"),
|
||||
("rel-mouse-exit-{}-tip", "Göreli fare modundan çıkmak için {}"),
|
||||
("rel-mouse-permission-lost-tip", "Göreli fare izinleri geçerli değil"),
|
||||
("Changelog", "Değişiklik Günlüğü"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Giden oturumlar süresince ekranı açık tutun"),
|
||||
("keep-awake-during-incoming-sessions-label", "Gelen oturumlar süresince ekranı açık tutun"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -729,12 +729,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("server-oss-not-support-tip", "注意:RustDesk 開源伺服器 (OSS server) 不包含此功能。"),
|
||||
("input note here", "輸入備註"),
|
||||
("note-at-conn-end-tip", "在連接結束時請求備註"),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Show terminal extra keys", "顯示終端機額外按鍵"),
|
||||
("Relative mouse mode", "相對滑鼠模式"),
|
||||
("rel-mouse-not-supported-peer-tip", "被控端不支援相對滑鼠模式"),
|
||||
("rel-mouse-not-ready-tip", "相對滑鼠模式尚未就緒,請稍候再試"),
|
||||
("rel-mouse-lock-failed-tip", "無法鎖定游標,相對滑鼠模式已停用"),
|
||||
("rel-mouse-exit-{}-tip", "按下 {} 退出"),
|
||||
("rel-mouse-permission-lost-tip", "鍵盤權限被撤銷,相對滑鼠模式已被停用"),
|
||||
("Changelog", "更新日誌"),
|
||||
("keep-awake-during-outgoing-sessions-label", "在連出工作階段期間保持螢幕喚醒"),
|
||||
("keep-awake-during-incoming-sessions-label", "在連入工作階段期間保持螢幕喚醒"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -736,5 +736,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
925
src/lang/vi.rs
925
src/lang/vi.rs
File diff suppressed because it is too large
Load Diff
@@ -6,29 +6,26 @@ use hbb_common::{
|
||||
anyhow::anyhow,
|
||||
bail,
|
||||
config::{keys::OPTION_ALLOW_LINUX_HEADLESS, Config},
|
||||
libc::{c_char, c_int, c_long, c_void},
|
||||
libc::{c_char, c_int, c_long, c_uint, c_void},
|
||||
log,
|
||||
message_proto::{DisplayInfo, Resolution},
|
||||
regex::{Captures, Regex},
|
||||
users::{get_user_by_name, os::unix::UserExt},
|
||||
};
|
||||
use libxdo_sys::{self, xdo_t, Window};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::{OsStr, OsString},
|
||||
path::{Path, PathBuf},
|
||||
process::{Child, Command},
|
||||
string::String,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use terminfo::{capability as cap, Database};
|
||||
use wallpaper;
|
||||
|
||||
type Xdo = *const c_void;
|
||||
|
||||
pub const PA_SAMPLE_RATE: u32 = 48000;
|
||||
static mut UNMODIFIED: bool = true;
|
||||
|
||||
@@ -86,35 +83,20 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static XDO: RefCell<Xdo> = RefCell::new(unsafe { xdo_new(std::ptr::null()) });
|
||||
// XDO context - created via libxdo-sys (which uses dynamic loading stub).
|
||||
// If libxdo is not available, xdo will be null and xdo-based functions become no-ops.
|
||||
static XDO: RefCell<*mut xdo_t> = RefCell::new({
|
||||
let xdo = unsafe { libxdo_sys::xdo_new(std::ptr::null()) };
|
||||
if xdo.is_null() {
|
||||
log::warn!("Failed to create xdo context, xdo functions will be disabled");
|
||||
} else {
|
||||
log::info!("xdo context created successfully");
|
||||
}
|
||||
xdo
|
||||
});
|
||||
static DISPLAY: RefCell<*mut c_void> = RefCell::new(unsafe { XOpenDisplay(std::ptr::null())});
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn xdo_get_mouse_location(
|
||||
xdo: Xdo,
|
||||
x: *mut c_int,
|
||||
y: *mut c_int,
|
||||
screen_num: *mut c_int,
|
||||
) -> c_int;
|
||||
fn xdo_move_mouse(xdo: Xdo, x: c_int, y: c_int, screen: c_int) -> c_int;
|
||||
fn xdo_new(display: *const c_char) -> Xdo;
|
||||
fn xdo_get_active_window(xdo: Xdo, window: *mut *mut c_void) -> c_int;
|
||||
fn xdo_get_window_location(
|
||||
xdo: Xdo,
|
||||
window: *mut c_void,
|
||||
x: *mut c_int,
|
||||
y: *mut c_int,
|
||||
screen_num: *mut c_int,
|
||||
) -> c_int;
|
||||
fn xdo_get_window_size(
|
||||
xdo: Xdo,
|
||||
window: *mut c_void,
|
||||
width: *mut c_int,
|
||||
height: *mut c_int,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
#[link(name = "X11")]
|
||||
extern "C" {
|
||||
fn XOpenDisplay(display_name: *const c_char) -> *mut c_void;
|
||||
@@ -160,14 +142,19 @@ fn sleep_millis(millis: u64) {
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
let mut res = None;
|
||||
XDO.with(|xdo| {
|
||||
if let Ok(xdo) = xdo.try_borrow_mut() {
|
||||
if let Ok(xdo) = xdo.try_borrow() {
|
||||
if xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
let mut x: c_int = 0;
|
||||
let mut y: c_int = 0;
|
||||
unsafe {
|
||||
xdo_get_mouse_location(*xdo, &mut x as _, &mut y as _, std::ptr::null_mut());
|
||||
libxdo_sys::xdo_get_mouse_location(
|
||||
*xdo as *const _,
|
||||
&mut x as _,
|
||||
&mut y as _,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
res = Some((x, y));
|
||||
}
|
||||
@@ -178,14 +165,14 @@ pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
pub fn set_cursor_pos(x: i32, y: i32) -> bool {
|
||||
let mut res = false;
|
||||
XDO.with(|xdo| {
|
||||
match xdo.try_borrow_mut() {
|
||||
match xdo.try_borrow() {
|
||||
Ok(xdo) => {
|
||||
if xdo.is_null() {
|
||||
log::debug!("set_cursor_pos: xdo is null");
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let ret = xdo_move_mouse(*xdo, x, y, 0);
|
||||
let ret = libxdo_sys::xdo_move_mouse(*xdo as *const _, x, y, 0);
|
||||
if ret != 0 {
|
||||
log::debug!(
|
||||
"set_cursor_pos: xdo_move_mouse failed with code {} for coordinates ({}, {})",
|
||||
@@ -230,22 +217,22 @@ pub fn reset_input_cache() {}
|
||||
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
let mut res = None;
|
||||
XDO.with(|xdo| {
|
||||
if let Ok(xdo) = xdo.try_borrow_mut() {
|
||||
if let Ok(xdo) = xdo.try_borrow() {
|
||||
if xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
let mut x: c_int = 0;
|
||||
let mut y: c_int = 0;
|
||||
let mut width: c_int = 0;
|
||||
let mut height: c_int = 0;
|
||||
let mut window: *mut c_void = std::ptr::null_mut();
|
||||
let mut width: c_uint = 0;
|
||||
let mut height: c_uint = 0;
|
||||
let mut window: Window = 0;
|
||||
|
||||
unsafe {
|
||||
if xdo_get_active_window(*xdo, &mut window) != 0 {
|
||||
if libxdo_sys::xdo_get_active_window(*xdo as *const _, &mut window) != 0 {
|
||||
return;
|
||||
}
|
||||
if xdo_get_window_location(
|
||||
*xdo,
|
||||
if libxdo_sys::xdo_get_window_location(
|
||||
*xdo as *const _,
|
||||
window,
|
||||
&mut x as _,
|
||||
&mut y as _,
|
||||
@@ -254,11 +241,17 @@ pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
{
|
||||
return;
|
||||
}
|
||||
if xdo_get_window_size(*xdo, window, &mut width as _, &mut height as _) != 0 {
|
||||
if libxdo_sys::xdo_get_window_size(
|
||||
*xdo as *const _,
|
||||
window,
|
||||
&mut width,
|
||||
&mut height,
|
||||
) != 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
let center_x = x + width / 2;
|
||||
let center_y = y + height / 2;
|
||||
let center_x = x + (width / 2) as c_int;
|
||||
let center_y = y + (height / 2) as c_int;
|
||||
res = displays.iter().position(|d| {
|
||||
center_x >= d.x
|
||||
&& center_x < d.x + d.width
|
||||
@@ -497,7 +490,10 @@ fn get_all_term_values(uid: &str) -> Vec<String> {
|
||||
let Ok(cmdline) = std::fs::read(&cmdline_path) else {
|
||||
continue;
|
||||
};
|
||||
let exe_end = cmdline.iter().position(|&b| b == 0).unwrap_or(cmdline.len());
|
||||
let exe_end = cmdline
|
||||
.iter()
|
||||
.position(|&b| b == 0)
|
||||
.unwrap_or(cmdline.len());
|
||||
let exe_str = String::from_utf8_lossy(&cmdline[..exe_end]);
|
||||
if !re.is_match(&exe_str) {
|
||||
continue;
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
#include <Security/Authorization.h>
|
||||
#include <Security/AuthorizationTags.h>
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
extern "C" bool CanUseNewApiForScreenCaptureCheck() {
|
||||
#ifdef NO_InputMonitoringAuthStatus
|
||||
return false;
|
||||
@@ -292,3 +299,611 @@ extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t h
|
||||
CFRelease(allModes);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static CFMachPortRef g_eventTap = NULL;
|
||||
static CFRunLoopSourceRef g_runLoopSource = NULL;
|
||||
static std::mutex g_privacyModeMutex;
|
||||
static bool g_privacyModeActive = false;
|
||||
|
||||
// Flag to request asynchronous shutdown of privacy mode.
|
||||
// This is set by DisplayReconfigurationCallback when an error occurs, instead of calling
|
||||
// TurnOffPrivacyModeInternal() directly from within the callback. This avoids potential
|
||||
// issues with unregistering a callback from within itself, which is not explicitly
|
||||
// guaranteed to be safe by Apple documentation.
|
||||
static bool g_privacyModeShutdownRequested = false;
|
||||
|
||||
// Timestamp of the last display reconfiguration event (in milliseconds).
|
||||
// Used for debouncing rapid successive changes (e.g., multiple resolution changes).
|
||||
static uint64_t g_lastReconfigTimestamp = 0;
|
||||
|
||||
// Flag indicating whether a delayed blackout reapplication is already scheduled.
|
||||
// Prevents multiple concurrent delayed tasks from being created.
|
||||
static bool g_blackoutReapplicationScheduled = false;
|
||||
|
||||
// Use CFStringRef (UUID) as key instead of CGDirectDisplayID for stability across reconnections
|
||||
// CGDirectDisplayID can change when displays are reconnected, but UUID remains stable
|
||||
static std::map<std::string, std::vector<CGGammaValue>> g_originalGammas;
|
||||
|
||||
// The event source user data value used by enigo library for injected events.
|
||||
// This allows us to distinguish remote input (which should be allowed) from local physical input.
|
||||
// See: libs/enigo/src/macos/macos_impl.rs - ENIGO_INPUT_EXTRA_VALUE
|
||||
static const int64_t ENIGO_INPUT_EXTRA_VALUE = 100;
|
||||
|
||||
// Duration in milliseconds to monitor and enforce blackout after display reconfiguration.
|
||||
// macOS may restore default gamma (via ColorSync) at unpredictable times after display changes,
|
||||
// so we need to actively monitor and reapply blackout during this period.
|
||||
static const int64_t DISPLAY_RECONFIG_MONITOR_DURATION_MS = 5000;
|
||||
|
||||
// Interval in milliseconds between gamma checks during the monitoring period.
|
||||
static const int64_t GAMMA_CHECK_INTERVAL_MS = 200;
|
||||
|
||||
// Helper function to get UUID string from DisplayID
|
||||
static std::string GetDisplayUUID(CGDirectDisplayID displayId) {
|
||||
CFUUIDRef uuid = CGDisplayCreateUUIDFromDisplayID(displayId);
|
||||
if (uuid == NULL) {
|
||||
return "";
|
||||
}
|
||||
CFStringRef uuidStr = CFUUIDCreateString(kCFAllocatorDefault, uuid);
|
||||
CFRelease(uuid);
|
||||
if (uuidStr == NULL) {
|
||||
return "";
|
||||
}
|
||||
char buffer[128];
|
||||
if (CFStringGetCString(uuidStr, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
|
||||
CFRelease(uuidStr);
|
||||
return std::string(buffer);
|
||||
}
|
||||
CFRelease(uuidStr);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Helper function to find DisplayID by UUID from current online displays
|
||||
static CGDirectDisplayID FindDisplayIdByUUID(const std::string& targetUuid) {
|
||||
uint32_t count = 0;
|
||||
CGGetOnlineDisplayList(0, NULL, &count);
|
||||
if (count == 0) return kCGNullDirectDisplay;
|
||||
|
||||
std::vector<CGDirectDisplayID> displays(count);
|
||||
CGGetOnlineDisplayList(count, displays.data(), &count);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
std::string uuid = GetDisplayUUID(displays[i]);
|
||||
if (uuid == targetUuid) {
|
||||
return displays[i];
|
||||
}
|
||||
}
|
||||
return kCGNullDirectDisplay;
|
||||
}
|
||||
|
||||
// Helper function to restore gamma values for all displays in g_originalGammas.
|
||||
// Returns true if all displays were restored successfully, false if any failed.
|
||||
// Note: This function does NOT clear g_originalGammas - caller should do that if needed.
|
||||
static bool RestoreAllGammas() {
|
||||
bool allSuccess = true;
|
||||
for (auto const& [uuid, gamma] : g_originalGammas) {
|
||||
CGDirectDisplayID d = FindDisplayIdByUUID(uuid);
|
||||
if (d == kCGNullDirectDisplay) {
|
||||
NSLog(@"Display with UUID %s no longer online, skipping gamma restore", uuid.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t sampleCount = gamma.size() / 3;
|
||||
if (sampleCount > 0) {
|
||||
const CGGammaValue* red = gamma.data();
|
||||
const CGGammaValue* green = red + sampleCount;
|
||||
const CGGammaValue* blue = green + sampleCount;
|
||||
CGError error = CGSetDisplayTransferByTable(d, sampleCount, red, green, blue);
|
||||
if (error != kCGErrorSuccess) {
|
||||
NSLog(@"Failed to restore gamma for display (ID: %u, UUID: %s, error: %d)", (unsigned)d, uuid.c_str(), error);
|
||||
allSuccess = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return allSuccess;
|
||||
}
|
||||
|
||||
// Helper function to apply blackout to a single display
|
||||
static bool ApplyBlackoutToDisplay(CGDirectDisplayID display) {
|
||||
uint32_t capacity = CGDisplayGammaTableCapacity(display);
|
||||
if (capacity > 0) {
|
||||
std::vector<CGGammaValue> zeros(capacity, 0.0f);
|
||||
CGError error = CGSetDisplayTransferByTable(display, capacity, zeros.data(), zeros.data(), zeros.data());
|
||||
if (error != kCGErrorSuccess) {
|
||||
NSLog(@"ApplyBlackoutToDisplay: Failed to set gamma for display %u (error %d)", (unsigned)display, error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
NSLog(@"ApplyBlackoutToDisplay: Display %u has zero gamma table capacity, blackout not supported", (unsigned)display);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Forward declaration - defined later in the file
|
||||
// Must be called while holding g_privacyModeMutex
|
||||
static bool TurnOffPrivacyModeInternal();
|
||||
|
||||
// Helper function to schedule asynchronous shutdown of privacy mode.
|
||||
// This is called from DisplayReconfigurationCallback when an error occurs,
|
||||
// instead of calling TurnOffPrivacyModeInternal() directly. This avoids
|
||||
// potential issues with unregistering a callback from within itself.
|
||||
// Note: This function should be called while holding g_privacyModeMutex.
|
||||
static void ScheduleAsyncPrivacyModeShutdown(const char* reason) {
|
||||
if (g_privacyModeShutdownRequested) {
|
||||
// Already requested, no need to schedule again
|
||||
return;
|
||||
}
|
||||
g_privacyModeShutdownRequested = true;
|
||||
NSLog(@"Privacy mode shutdown requested: %s", reason);
|
||||
|
||||
// Schedule the actual shutdown on the main queue asynchronously
|
||||
// This ensures we're outside the callback when we unregister it
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
std::lock_guard<std::mutex> lock(g_privacyModeMutex);
|
||||
if (g_privacyModeShutdownRequested && g_privacyModeActive) {
|
||||
NSLog(@"Executing deferred privacy mode shutdown");
|
||||
TurnOffPrivacyModeInternal();
|
||||
}
|
||||
g_privacyModeShutdownRequested = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to apply blackout to all online displays.
|
||||
// Must be called while holding g_privacyModeMutex.
|
||||
static void ApplyBlackoutToAllDisplays() {
|
||||
uint32_t onlineCount = 0;
|
||||
CGGetOnlineDisplayList(0, NULL, &onlineCount);
|
||||
std::vector<CGDirectDisplayID> onlineDisplays(onlineCount);
|
||||
CGGetOnlineDisplayList(onlineCount, onlineDisplays.data(), &onlineCount);
|
||||
|
||||
for (uint32_t i = 0; i < onlineCount; i++) {
|
||||
ApplyBlackoutToDisplay(onlineDisplays[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get current timestamp in milliseconds
|
||||
static uint64_t GetCurrentTimestampMs() {
|
||||
return (uint64_t)(CFAbsoluteTimeGetCurrent() * 1000.0);
|
||||
}
|
||||
|
||||
// Helper function to check if a display's gamma is currently blacked out (all zeros).
|
||||
// Returns true if gamma appears to be blacked out, false otherwise.
|
||||
static bool IsDisplayBlackedOut(CGDirectDisplayID display) {
|
||||
uint32_t capacity = CGDisplayGammaTableCapacity(display);
|
||||
if (capacity == 0) {
|
||||
return true; // Can't check, assume it's fine
|
||||
}
|
||||
|
||||
std::vector<CGGammaValue> red(capacity), green(capacity), blue(capacity);
|
||||
uint32_t sampleCount = 0;
|
||||
if (CGGetDisplayTransferByTable(display, capacity, red.data(), green.data(), blue.data(), &sampleCount) != kCGErrorSuccess) {
|
||||
return true; // Can't read, assume it's fine
|
||||
}
|
||||
|
||||
// Check if all values are zero (or very close to zero)
|
||||
for (uint32_t i = 0; i < sampleCount; i++) {
|
||||
if (red[i] > 0.01f || green[i] > 0.01f || blue[i] > 0.01f) {
|
||||
return false; // Not blacked out
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Internal function that monitors and enforces blackout for a period after display reconfiguration.
|
||||
// This function checks gamma values periodically and reapplies blackout if needed.
|
||||
// Must NOT be called while holding g_privacyModeMutex (it acquires the lock internally).
|
||||
static void RunBlackoutMonitor() {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(GAMMA_CHECK_INTERVAL_MS * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
|
||||
std::lock_guard<std::mutex> lock(g_privacyModeMutex);
|
||||
|
||||
if (!g_privacyModeActive) {
|
||||
g_blackoutReapplicationScheduled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t now = GetCurrentTimestampMs();
|
||||
|
||||
// Calculate effective end time based on the last reconfig event
|
||||
uint64_t effectiveEndTime = g_lastReconfigTimestamp + DISPLAY_RECONFIG_MONITOR_DURATION_MS;
|
||||
|
||||
// Check all displays and reapply blackout if any has been restored
|
||||
uint32_t onlineCount = 0;
|
||||
CGGetOnlineDisplayList(0, NULL, &onlineCount);
|
||||
std::vector<CGDirectDisplayID> onlineDisplays(onlineCount);
|
||||
CGGetOnlineDisplayList(onlineCount, onlineDisplays.data(), &onlineCount);
|
||||
|
||||
bool needsReapply = false;
|
||||
for (uint32_t i = 0; i < onlineCount; i++) {
|
||||
if (!IsDisplayBlackedOut(onlineDisplays[i])) {
|
||||
needsReapply = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsReapply) {
|
||||
NSLog(@"Gamma was restored by system, reapplying blackout");
|
||||
ApplyBlackoutToAllDisplays();
|
||||
}
|
||||
|
||||
// Continue monitoring if we haven't reached the end time
|
||||
if (now < effectiveEndTime) {
|
||||
RunBlackoutMonitor();
|
||||
} else {
|
||||
NSLog(@"Blackout monitoring period ended");
|
||||
g_blackoutReapplicationScheduled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to start monitoring and enforcing blackout after display reconfiguration.
|
||||
// This is used after display reconfiguration events because macOS may restore
|
||||
// default gamma (via ColorSync) at unpredictable times after display changes.
|
||||
// Note: This function should be called while holding g_privacyModeMutex.
|
||||
static void ScheduleDelayedBlackoutReapplication(const char* reason) {
|
||||
// Update timestamp to current time
|
||||
g_lastReconfigTimestamp = GetCurrentTimestampMs();
|
||||
|
||||
NSLog(@"Starting blackout monitor: %s", reason);
|
||||
|
||||
// Only schedule if not already scheduled
|
||||
if (!g_blackoutReapplicationScheduled) {
|
||||
g_blackoutReapplicationScheduled = true;
|
||||
RunBlackoutMonitor();
|
||||
}
|
||||
// If already scheduled, the running monitor will see the updated timestamp
|
||||
// and extend its monitoring period
|
||||
}
|
||||
|
||||
// Display reconfiguration callback to handle display connect/disconnect events
|
||||
//
|
||||
// IMPORTANT: When errors occur in this callback, we use ScheduleAsyncPrivacyModeShutdown()
|
||||
// instead of calling TurnOffPrivacyModeInternal() directly. This is because:
|
||||
// 1. TurnOffPrivacyModeInternal() calls CGDisplayRemoveReconfigurationCallback to unregister
|
||||
// this callback, and unregistering a callback from within itself is not explicitly
|
||||
// guaranteed to be safe by Apple documentation.
|
||||
// 2. Using async dispatch ensures we're completely outside the callback context when
|
||||
// performing the cleanup, avoiding any potential undefined behavior.
|
||||
static void DisplayReconfigurationCallback(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo) {
|
||||
(void)userInfo;
|
||||
|
||||
// Note: We need to handle the callback carefully because:
|
||||
// 1. macOS may call this callback multiple times during display reconfiguration
|
||||
// 2. The system may restore ColorSync settings after our gamma change
|
||||
// 3. We should not hold the lock for too long in the callback
|
||||
|
||||
// Skip begin configuration flag - wait for the actual change
|
||||
if (flags & kCGDisplayBeginConfigurationFlag) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_privacyModeMutex);
|
||||
|
||||
if (!g_privacyModeActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags & kCGDisplayAddFlag) {
|
||||
// A display was added - apply blackout to it
|
||||
NSLog(@"Display %u added during privacy mode, applying blackout", (unsigned)display);
|
||||
std::string uuid = GetDisplayUUID(display);
|
||||
if (uuid.empty()) {
|
||||
NSLog(@"Failed to get UUID for newly added display %u, exiting privacy mode", (unsigned)display);
|
||||
ScheduleAsyncPrivacyModeShutdown("Failed to get UUID for newly added display");
|
||||
return;
|
||||
}
|
||||
|
||||
// Save original gamma if not already saved for this UUID
|
||||
if (g_originalGammas.find(uuid) == g_originalGammas.end()) {
|
||||
uint32_t capacity = CGDisplayGammaTableCapacity(display);
|
||||
if (capacity > 0) {
|
||||
std::vector<CGGammaValue> red(capacity), green(capacity), blue(capacity);
|
||||
uint32_t sampleCount = 0;
|
||||
if (CGGetDisplayTransferByTable(display, capacity, red.data(), green.data(), blue.data(), &sampleCount) == kCGErrorSuccess) {
|
||||
std::vector<CGGammaValue> all;
|
||||
all.insert(all.end(), red.begin(), red.begin() + sampleCount);
|
||||
all.insert(all.end(), green.begin(), green.begin() + sampleCount);
|
||||
all.insert(all.end(), blue.begin(), blue.begin() + sampleCount);
|
||||
g_originalGammas[uuid] = all;
|
||||
} else {
|
||||
NSLog(@"DisplayReconfigurationCallback: Failed to get gamma table for display %u (UUID: %s), exiting privacy mode", (unsigned)display, uuid.c_str());
|
||||
ScheduleAsyncPrivacyModeShutdown("Failed to get gamma table for newly added display");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
NSLog(@"DisplayReconfigurationCallback: Display %u (UUID: %s) has zero gamma table capacity, exiting privacy mode", (unsigned)display, uuid.c_str());
|
||||
ScheduleAsyncPrivacyModeShutdown("Newly added display has zero gamma table capacity");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply blackout to the new display immediately
|
||||
if (!ApplyBlackoutToDisplay(display)) {
|
||||
NSLog(@"DisplayReconfigurationCallback: Failed to blackout display %u (UUID: %s), exiting privacy mode", (unsigned)display, uuid.c_str());
|
||||
ScheduleAsyncPrivacyModeShutdown("Failed to blackout newly added display");
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule a delayed re-application to handle ColorSync restoration
|
||||
// macOS may restore default gamma for ALL displays after a new display is added,
|
||||
// so we need to reapply blackout to all online displays, not just the new one
|
||||
ScheduleDelayedBlackoutReapplication("after new display added");
|
||||
} else if (flags & kCGDisplayRemoveFlag) {
|
||||
// A display was removed - update our mapping and reapply blackout to remaining displays
|
||||
NSLog(@"Display %u removed during privacy mode", (unsigned)display);
|
||||
std::string uuid = GetDisplayUUID(display);
|
||||
(void)uuid; // UUID retrieved for potential future use or logging
|
||||
|
||||
// When a display is removed, macOS may reconfigure other displays and restore their gamma.
|
||||
// Schedule a delayed re-application of blackout to all remaining online displays.
|
||||
ScheduleDelayedBlackoutReapplication("after display removal");
|
||||
} else if (flags & kCGDisplaySetModeFlag) {
|
||||
// Display mode changed (resolution change, ColorSync/Night Shift interference, etc.)
|
||||
// macOS resets gamma to default when display mode changes, so we need to reapply blackout.
|
||||
// Schedule a delayed re-application because ColorSync restoration happens asynchronously.
|
||||
NSLog(@"Display %u mode changed during privacy mode, reapplying blackout", (unsigned)display);
|
||||
ScheduleDelayedBlackoutReapplication("after display mode change");
|
||||
}
|
||||
}
|
||||
|
||||
CGEventRef MyEventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
|
||||
(void)proxy;
|
||||
(void)refcon;
|
||||
|
||||
// Handle EventTap being disabled by system timeout
|
||||
if (type == kCGEventTapDisabledByTimeout) {
|
||||
NSLog(@"EventTap was disabled by timeout, re-enabling");
|
||||
if (g_eventTap) {
|
||||
CGEventTapEnable(g_eventTap, true);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
// Handle EventTap being disabled by user input
|
||||
if (type == kCGEventTapDisabledByUserInput) {
|
||||
NSLog(@"EventTap was disabled by user input, re-enabling");
|
||||
if (g_eventTap) {
|
||||
CGEventTapEnable(g_eventTap, true);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
// Allow events explicitly injected by enigo (remote input), identified via custom user data.
|
||||
int64_t userData = CGEventGetIntegerValueField(event, kCGEventSourceUserData);
|
||||
if (userData == ENIGO_INPUT_EXTRA_VALUE) {
|
||||
return event;
|
||||
}
|
||||
// Block local physical HID input.
|
||||
if (CGEventGetIntegerValueField(event, kCGEventSourceStateID) == kCGEventSourceStateHIDSystemState) {
|
||||
return NULL;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
// Helper function to set up EventTap on the main thread
|
||||
// Returns true if EventTap was successfully created and enabled
|
||||
static bool SetupEventTapOnMainThread() {
|
||||
__block bool success = false;
|
||||
|
||||
void (^setupBlock)(void) = ^{
|
||||
if (g_eventTap) {
|
||||
// Already set up
|
||||
success = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: kCGEventTapDisabledByTimeout and kCGEventTapDisabledByUserInput are special
|
||||
// notification types (0xFFFFFFFE and 0xFFFFFFFF) that are delivered via the callback's
|
||||
// type parameter, not through the event mask. They should NOT be included in eventMask
|
||||
// as bit-shifting by these values causes undefined behavior.
|
||||
CGEventMask eventMask = (1 << kCGEventKeyDown) | (1 << kCGEventKeyUp) |
|
||||
(1 << kCGEventLeftMouseDown) | (1 << kCGEventLeftMouseUp) |
|
||||
(1 << kCGEventRightMouseDown) | (1 << kCGEventRightMouseUp) |
|
||||
(1 << kCGEventOtherMouseDown) | (1 << kCGEventOtherMouseUp) |
|
||||
(1 << kCGEventLeftMouseDragged) | (1 << kCGEventRightMouseDragged) |
|
||||
(1 << kCGEventOtherMouseDragged) |
|
||||
(1 << kCGEventMouseMoved) | (1 << kCGEventScrollWheel);
|
||||
|
||||
g_eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
|
||||
eventMask, MyEventTapCallback, NULL);
|
||||
if (g_eventTap) {
|
||||
g_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, g_eventTap, 0);
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), g_runLoopSource, kCFRunLoopCommonModes);
|
||||
CGEventTapEnable(g_eventTap, true);
|
||||
success = true;
|
||||
} else {
|
||||
NSLog(@"MacSetPrivacyMode: Failed to create CGEventTap; input blocking not enabled.");
|
||||
success = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Execute on main thread to ensure CFRunLoop operations are safe.
|
||||
// Use dispatch_sync if not on main thread, otherwise execute directly to avoid deadlock.
|
||||
//
|
||||
// IMPORTANT: Potential deadlock consideration:
|
||||
// Using dispatch_sync while holding g_privacyModeMutex could deadlock if the main thread
|
||||
// tries to acquire g_privacyModeMutex. Currently this is safe because:
|
||||
// 1. MacSetPrivacyMode (which holds the mutex) is only called from background threads
|
||||
// 2. The main thread never directly calls MacSetPrivacyMode
|
||||
// If this assumption changes in the future, consider releasing the mutex before dispatch_sync
|
||||
// or restructuring the locking strategy.
|
||||
if ([NSThread isMainThread]) {
|
||||
setupBlock();
|
||||
} else {
|
||||
dispatch_sync(dispatch_get_main_queue(), setupBlock);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Helper function to tear down EventTap on the main thread
|
||||
static void TeardownEventTapOnMainThread() {
|
||||
void (^teardownBlock)(void) = ^{
|
||||
if (g_eventTap) {
|
||||
CGEventTapEnable(g_eventTap, false);
|
||||
CFRunLoopRemoveSource(CFRunLoopGetMain(), g_runLoopSource, kCFRunLoopCommonModes);
|
||||
CFRelease(g_runLoopSource);
|
||||
CFRelease(g_eventTap);
|
||||
g_eventTap = NULL;
|
||||
g_runLoopSource = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
// Execute on main thread to ensure CFRunLoop operations are safe.
|
||||
//
|
||||
// NOTE: We use dispatch_sync here instead of dispatch_async because:
|
||||
// 1. TurnOffPrivacyModeInternal() expects EventTap to be fully torn down before
|
||||
// proceeding with gamma restoration - using async would cause race conditions.
|
||||
// 2. The caller (MacSetPrivacyMode) needs deterministic cleanup order.
|
||||
//
|
||||
// IMPORTANT: Potential deadlock consideration (same as SetupEventTapOnMainThread):
|
||||
// Using dispatch_sync while holding g_privacyModeMutex could deadlock if the main thread
|
||||
// tries to acquire g_privacyModeMutex. Currently this is safe because:
|
||||
// 1. MacSetPrivacyMode (which holds the mutex) is only called from background threads
|
||||
// 2. The main thread never directly calls MacSetPrivacyMode
|
||||
// If this assumption changes in the future, consider releasing the mutex before dispatch_sync
|
||||
// or restructuring the locking strategy.
|
||||
if ([NSThread isMainThread]) {
|
||||
teardownBlock();
|
||||
} else {
|
||||
dispatch_sync(dispatch_get_main_queue(), teardownBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal function to turn off privacy mode without acquiring the mutex
|
||||
// Must be called while holding g_privacyModeMutex
|
||||
static bool TurnOffPrivacyModeInternal() {
|
||||
if (!g_privacyModeActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1. Unregister display reconfiguration callback
|
||||
CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback, NULL);
|
||||
|
||||
// 2. Input - restore (tear down EventTap on main thread)
|
||||
TeardownEventTapOnMainThread();
|
||||
|
||||
// 3. Gamma - restore using UUID to find current DisplayID
|
||||
bool restoreSuccess = RestoreAllGammas();
|
||||
|
||||
// 4. Fallback: Always call CGDisplayRestoreColorSyncSettings as a safety net
|
||||
// This ensures displays return to normal even if our restoration failed or
|
||||
// if the system (ColorSync/Night Shift) modified gamma during privacy mode
|
||||
CGDisplayRestoreColorSyncSettings();
|
||||
|
||||
// Clean up
|
||||
g_originalGammas.clear();
|
||||
g_privacyModeActive = false;
|
||||
g_privacyModeShutdownRequested = false;
|
||||
g_lastReconfigTimestamp = 0;
|
||||
g_blackoutReapplicationScheduled = false;
|
||||
|
||||
return restoreSuccess;
|
||||
}
|
||||
|
||||
extern "C" bool MacSetPrivacyMode(bool on) {
|
||||
std::lock_guard<std::mutex> lock(g_privacyModeMutex);
|
||||
if (on) {
|
||||
// Already in privacy mode
|
||||
if (g_privacyModeActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1. Input Blocking - set up EventTap on main thread
|
||||
if (!SetupEventTapOnMainThread()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Register display reconfiguration callback to handle hot-plug events
|
||||
CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationCallback, NULL);
|
||||
|
||||
// 3. Gamma Blackout
|
||||
uint32_t count = 0;
|
||||
CGGetOnlineDisplayList(0, NULL, &count);
|
||||
std::vector<CGDirectDisplayID> displays(count);
|
||||
CGGetOnlineDisplayList(count, displays.data(), &count);
|
||||
|
||||
uint32_t blackoutSuccessCount = 0;
|
||||
uint32_t blackoutAttemptCount = 0;
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
CGDirectDisplayID d = displays[i];
|
||||
std::string uuid = GetDisplayUUID(d);
|
||||
|
||||
if (uuid.empty()) {
|
||||
NSLog(@"MacSetPrivacyMode: Failed to get UUID for display %u, privacy mode requires all displays", (unsigned)d);
|
||||
// Privacy mode requires ALL connected displays to be successfully blacked out
|
||||
// to ensure user privacy. If we can't identify a display (no UUID),
|
||||
// we can't safely manage its state or restore it later.
|
||||
// Therefore, we must abort the entire operation and clean up any resources
|
||||
// already allocated (like event taps and reconfiguration callbacks).
|
||||
CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback, NULL);
|
||||
TeardownEventTapOnMainThread();
|
||||
// Restore gamma for displays that were already blacked out before this failure
|
||||
if (!RestoreAllGammas()) {
|
||||
// If any display failed to restore, use system reset as fallback
|
||||
CGDisplayRestoreColorSyncSettings();
|
||||
}
|
||||
g_originalGammas.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save original gamma using UUID as key (stable across reconnections)
|
||||
if (g_originalGammas.find(uuid) == g_originalGammas.end()) {
|
||||
uint32_t capacity = CGDisplayGammaTableCapacity(d);
|
||||
if (capacity > 0) {
|
||||
std::vector<CGGammaValue> red(capacity), green(capacity), blue(capacity);
|
||||
uint32_t sampleCount = 0;
|
||||
if (CGGetDisplayTransferByTable(d, capacity, red.data(), green.data(), blue.data(), &sampleCount) == kCGErrorSuccess) {
|
||||
std::vector<CGGammaValue> all;
|
||||
all.insert(all.end(), red.begin(), red.begin() + sampleCount);
|
||||
all.insert(all.end(), green.begin(), green.begin() + sampleCount);
|
||||
all.insert(all.end(), blue.begin(), blue.begin() + sampleCount);
|
||||
g_originalGammas[uuid] = all;
|
||||
} else {
|
||||
NSLog(@"MacSetPrivacyMode: Failed to get gamma table for display %u (UUID: %s)", (unsigned)d, uuid.c_str());
|
||||
}
|
||||
} else {
|
||||
NSLog(@"MacSetPrivacyMode: Display %u (UUID: %s) has zero gamma table capacity, not supported", (unsigned)d, uuid.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Set to black only if we have saved original gamma for this display
|
||||
if (g_originalGammas.find(uuid) != g_originalGammas.end()) {
|
||||
uint32_t capacity = CGDisplayGammaTableCapacity(d);
|
||||
if (capacity > 0) {
|
||||
std::vector<CGGammaValue> zeros(capacity, 0.0f);
|
||||
blackoutAttemptCount++;
|
||||
CGError error = CGSetDisplayTransferByTable(d, capacity, zeros.data(), zeros.data(), zeros.data());
|
||||
if (error != kCGErrorSuccess) {
|
||||
NSLog(@"MacSetPrivacyMode: Failed to blackout display (ID: %u, UUID: %s, error: %d)", (unsigned)d, uuid.c_str(), error);
|
||||
} else {
|
||||
blackoutSuccessCount++;
|
||||
}
|
||||
} else {
|
||||
NSLog(@"MacSetPrivacyMode: Display %u (UUID: %s) has zero gamma table capacity for blackout", (unsigned)d, uuid.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return false if any display failed to blackout - privacy mode requires ALL displays to be blacked out
|
||||
if (blackoutAttemptCount > 0 && blackoutSuccessCount < blackoutAttemptCount) {
|
||||
NSLog(@"MacSetPrivacyMode: Failed to blackout all displays (%u/%u succeeded)", blackoutSuccessCount, blackoutAttemptCount);
|
||||
// Clean up: unregister callback and disable event tap since we're failing
|
||||
CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback, NULL);
|
||||
TeardownEventTapOnMainThread();
|
||||
// Restore gamma for displays that were successfully blacked out
|
||||
if (!RestoreAllGammas()) {
|
||||
// If any display failed to restore, use system reset as fallback
|
||||
NSLog(@"Some displays failed to restore gamma during cleanup, using CGDisplayRestoreColorSyncSettings as fallback");
|
||||
CGDisplayRestoreColorSyncSettings();
|
||||
}
|
||||
g_originalGammas.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
g_privacyModeActive = true;
|
||||
return true;
|
||||
|
||||
} else {
|
||||
return TurnOffPrivacyModeInternal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ pub mod win_mag;
|
||||
#[cfg(windows)]
|
||||
pub mod win_topmost_window;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod win_virtual_display;
|
||||
#[cfg(windows)]
|
||||
@@ -105,7 +108,14 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
"".to_owned()
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
macos::PRIVACY_MODE_IMPL.to_owned()
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -127,7 +137,13 @@ pub type PrivacyModeCreator = fn(impl_key: &str) -> Box<dyn PrivacyMode>;
|
||||
lazy_static::lazy_static! {
|
||||
static ref PRIVACY_MODE_CREATOR: Arc<Mutex<HashMap<&'static str, PrivacyModeCreator>>> = {
|
||||
#[cfg(not(windows))]
|
||||
let map: HashMap<&'static str, PrivacyModeCreator> = HashMap::new();
|
||||
let mut map: HashMap<&'static str, PrivacyModeCreator> = HashMap::new();
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
map.insert(macos::PRIVACY_MODE_IMPL, |impl_key: &str| {
|
||||
Box::new(macos::PrivacyModeImpl::new(impl_key))
|
||||
});
|
||||
}
|
||||
#[cfg(windows)]
|
||||
let mut map: HashMap<&'static str, PrivacyModeCreator> = HashMap::new();
|
||||
#[cfg(windows)]
|
||||
@@ -333,7 +349,14 @@ pub fn get_supported_privacy_mode_impl() -> Vec<(&'static str, &'static str)> {
|
||||
|
||||
vec_impls
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// No translation is intended for privacy_mode_impl_macos_tip as it is a
|
||||
// placeholder for macOS specific privacy mode implementation which currently
|
||||
// doesn't provide multiple modes like Windows does.
|
||||
vec![(macos::PRIVACY_MODE_IMPL, "privacy_mode_impl_macos_tip")]
|
||||
}
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||
{
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
81
src/privacy_mode/macos.rs
Normal file
81
src/privacy_mode/macos.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use super::{PrivacyMode, PrivacyModeState};
|
||||
use hbb_common::{anyhow::anyhow, ResultType};
|
||||
|
||||
extern "C" {
|
||||
fn MacSetPrivacyMode(on: bool) -> bool;
|
||||
}
|
||||
|
||||
pub const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_macos";
|
||||
|
||||
pub struct PrivacyModeImpl {
|
||||
impl_key: String,
|
||||
conn_id: i32,
|
||||
}
|
||||
|
||||
impl PrivacyModeImpl {
|
||||
pub fn new(impl_key: &str) -> Self {
|
||||
Self {
|
||||
impl_key: impl_key.to_owned(),
|
||||
conn_id: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivacyMode for PrivacyModeImpl {
|
||||
fn is_async_privacy_mode(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn init(&self) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
unsafe {
|
||||
MacSetPrivacyMode(false);
|
||||
}
|
||||
self.conn_id = 0;
|
||||
}
|
||||
|
||||
fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType<bool> {
|
||||
if self.check_on_conn_id(conn_id)? {
|
||||
return Ok(true);
|
||||
}
|
||||
let success = unsafe { MacSetPrivacyMode(true) };
|
||||
if !success {
|
||||
return Err(anyhow!("Failed to turn on privacy mode"));
|
||||
}
|
||||
self.conn_id = conn_id;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn turn_off_privacy(&mut self, conn_id: i32, _state: Option<PrivacyModeState>) -> ResultType<()> {
|
||||
// Note: The `_state` parameter is intentionally ignored on macOS.
|
||||
// On Windows, it's used to notify the connection manager about privacy mode state changes
|
||||
// (see win_topmost_window.rs). macOS currently has a simpler single-mode implementation
|
||||
// without the need for such cross-component state synchronization.
|
||||
self.check_off_conn_id(conn_id)?;
|
||||
let success = unsafe { MacSetPrivacyMode(false) };
|
||||
if !success {
|
||||
return Err(anyhow!("Failed to turn off privacy mode"));
|
||||
}
|
||||
self.conn_id = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pre_conn_id(&self) -> i32 {
|
||||
self.conn_id
|
||||
}
|
||||
|
||||
fn get_impl_key(&self) -> &str {
|
||||
&self.impl_key
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PrivacyModeImpl {
|
||||
fn drop(&mut self) {
|
||||
// Use the same cleanup logic as other code paths to keep conn_id consistent
|
||||
// and ensure all cleanup is centralized in one place.
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ lazy_static::lazy_static! {
|
||||
pub static ref CONTROL_PERMISSIONS_ARRAY: Arc::<Mutex<Vec<(i32, ControlPermissions)>>> = Default::default();
|
||||
static ref SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
|
||||
static ref WAKELOCK_SENDER: Arc::<Mutex<std::sync::mpsc::Sender<(usize, usize)>>> = Arc::new(Mutex::new(start_wakelock_thread()));
|
||||
static ref WAKELOCK_KEEP_AWAKE_OPTION: Arc::<Mutex<Option<bool>>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
@@ -906,6 +907,7 @@ impl Connection {
|
||||
_ = second_timer.tick() => {
|
||||
#[cfg(windows)]
|
||||
conn.portable_check();
|
||||
raii::AuthedConnID::check_wake_lock_on_setting_changed();
|
||||
if let Some((instant, minute)) = conn.auto_disconnect_timer.as_ref() {
|
||||
if instant.elapsed().as_secs() > minute * 60 {
|
||||
conn.send_close_reason_no_retry("Connection failed due to inactivity").await;
|
||||
@@ -1418,7 +1420,7 @@ impl Connection {
|
||||
pi.platform = "Android".into();
|
||||
}
|
||||
#[cfg(all(target_os = "macos", not(feature = "unix-file-copy-paste")))]
|
||||
let platform_additions = serde_json::Map::new();
|
||||
let mut platform_additions = serde_json::Map::new();
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "linux",
|
||||
@@ -1451,6 +1453,13 @@ impl Connection {
|
||||
json!(privacy_mode::get_supported_privacy_mode_impl()),
|
||||
);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
platform_additions.insert(
|
||||
"supported_privacy_mode_impl".into(),
|
||||
json!(privacy_mode::get_supported_privacy_mode_impl()),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
|
||||
{
|
||||
@@ -5008,6 +5017,7 @@ impl FileRemoveLogControl {
|
||||
}
|
||||
|
||||
fn start_wakelock_thread() -> std::sync::mpsc::Sender<(usize, usize)> {
|
||||
// Check if we should keep awake during incoming sessions
|
||||
use crate::platform::{get_wakelock, WakeLock};
|
||||
let (tx, rx) = std::sync::mpsc::channel::<(usize, usize)>();
|
||||
std::thread::spawn(move || {
|
||||
@@ -5016,9 +5026,15 @@ fn start_wakelock_thread() -> std::sync::mpsc::Sender<(usize, usize)> {
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok((conn_count, remote_count)) => {
|
||||
if conn_count == 0 {
|
||||
wakelock = None;
|
||||
log::info!("drop wakelock");
|
||||
let keep_awake = config::Config::get_bool_option(
|
||||
keys::OPTION_KEEP_AWAKE_DURING_INCOMING_SESSIONS,
|
||||
);
|
||||
*WAKELOCK_KEEP_AWAKE_OPTION.lock().unwrap() = Some(keep_awake);
|
||||
if conn_count == 0 || !keep_awake {
|
||||
if wakelock.is_some() {
|
||||
wakelock = None;
|
||||
log::info!("drop wakelock");
|
||||
}
|
||||
} else {
|
||||
let mut display = remote_count > 0;
|
||||
if let Some(_w) = wakelock.as_mut() {
|
||||
@@ -5329,6 +5345,16 @@ mod raii {
|
||||
.send((conn_count, remote_count)));
|
||||
}
|
||||
|
||||
pub fn check_wake_lock_on_setting_changed() {
|
||||
let current = config::Config::get_bool_option(
|
||||
keys::OPTION_KEEP_AWAKE_DURING_INCOMING_SESSIONS,
|
||||
);
|
||||
let cached = *WAKELOCK_KEEP_AWAKE_OPTION.lock().unwrap();
|
||||
if cached != Some(current) {
|
||||
Self::check_wake_lock();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn non_port_forward_conn_count() -> usize {
|
||||
AUTHED_CONNS
|
||||
|
||||
11
src/tray.rs
11
src/tray.rs
@@ -10,12 +10,6 @@ use std::time::Duration;
|
||||
|
||||
pub fn start_tray() {
|
||||
if crate::ui_interface::get_builtin_option(hbb_common::config::keys::OPTION_HIDE_TRAY) == "Y" {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
return;
|
||||
@@ -129,6 +123,11 @@ fn make_tray() -> hbb_common::ResultType<()> {
|
||||
);
|
||||
|
||||
if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
|
||||
// for fixing https://github.com/rustdesk/rustdesk/discussions/10210#discussioncomment-14600745
|
||||
// so we start tray, but not to show it
|
||||
if crate::ui_interface::get_builtin_option(hbb_common::config::keys::OPTION_HIDE_TRAY) == "Y" {
|
||||
return;
|
||||
}
|
||||
// We create the icon once the event loop is actually running
|
||||
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
|
||||
let tray = TrayIconBuilder::new()
|
||||
|
||||
@@ -268,6 +268,7 @@ class Enhancements: Reactor.Component {
|
||||
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive bitrate")} (beta)</li>
|
||||
<li #screen-recording>{translate("Recording")}</li>
|
||||
{support_remove_wallpaper ? <li #allow-remove-wallpaper><span>{svg_checkmark}</span>{translate("Remove wallpaper during incoming sessions")}</li> : ""}
|
||||
<li #keep-awake-during-incoming-sessions><span>{svg_checkmark}</span>{translate("keep-awake-during-incoming-sessions-label")}</li>
|
||||
</menu>
|
||||
</li>;
|
||||
}
|
||||
@@ -288,6 +289,13 @@ class Enhancements: Reactor.Component {
|
||||
if (is_opt_fixed) {
|
||||
el.state.disabled = true;
|
||||
}
|
||||
} else if (el.id == "keep-awake-during-incoming-sessions") {
|
||||
var enabled = handler.get_option(el.id) != "N";
|
||||
el.attributes.toggleClass("selected", enabled);
|
||||
var is_opt_fixed = handler.is_option_fixed(el.id);
|
||||
if (is_opt_fixed) {
|
||||
el.state.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +312,8 @@ class Enhancements: Reactor.Component {
|
||||
}
|
||||
} else if (v.indexOf("allow-") == 0) {
|
||||
handler.set_option(v, handler.get_option(v) == 'Y' ? default_option_no : 'Y');
|
||||
} else if (v == 'keep-awake-during-incoming-sessions') {
|
||||
handler.set_option(v, handler.get_option(v) != 'N' ? 'N' : default_option_yes);
|
||||
} else if (v == 'screen-recording') {
|
||||
var show_root_dir = is_win && handler.is_installed();
|
||||
var user_dir = handler.video_save_directory(false);
|
||||
|
||||
Reference in New Issue
Block a user