mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge branch 'master' of https://github.com/rustdesk/rustdesk
This commit is contained in:
commit
ce3434fd37
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -78,7 +78,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
case ${{ matrix.job.target }} in
|
||||
x86_64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt install -y 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 ;;
|
||||
x86_64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt install -y 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 libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev ;;
|
||||
# arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
|
||||
# aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
|
||||
esac
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
.vscode
|
||||
.idea
|
||||
.DS_Store
|
||||
libsciter-gtk.so
|
||||
src/ui/inline.rs
|
||||
extractor
|
||||
__pycache__
|
||||
|
||||
910
Cargo.lock
generated
910
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
23
Cargo.toml
23
Cargo.toml
@ -31,7 +31,7 @@ default = ["use_dasp"]
|
||||
|
||||
[dependencies]
|
||||
whoami = "1.2"
|
||||
scrap = { path = "libs/scrap" }
|
||||
scrap = { path = "libs/scrap", features = ["wayland"] }
|
||||
hbb_common = { path = "libs/hbb_common" }
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
@ -51,10 +51,12 @@ samplerate = { version = "0.2", optional = true }
|
||||
async-trait = "0.1"
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
clap = "3.0"
|
||||
rpassword = "6.0"
|
||||
rpassword = "7.0"
|
||||
base64 = "0.13"
|
||||
sysinfo = "0.23"
|
||||
sysinfo = "0.24"
|
||||
num_cpus = "1.13"
|
||||
default-net = "0.11.0"
|
||||
wol-rs = "0.9.1"
|
||||
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features=false }
|
||||
@ -67,7 +69,7 @@ machine-uid = "0.2"
|
||||
mac_address = "1.1"
|
||||
sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" }
|
||||
sys-locale = "0.2"
|
||||
enigo = { path = "libs/enigo" }
|
||||
enigo = { path = "libs/enigo", features = [ "with_serde" ] }
|
||||
clipboard = { path = "libs/clipboard" }
|
||||
rdev = { git = "https://github.com/asur4s/rdev" }
|
||||
ctrlc = "3.2"
|
||||
@ -76,9 +78,8 @@ arboard = "2.0"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
#systray = { git = "https://github.com/open-trade/systray-rs" }
|
||||
trayicon = { version = "0.1", features = ["winit"] }
|
||||
# > 0.25 not work with trayicon
|
||||
winit = "0.25"
|
||||
trayicon = { git = "https://github.com/open-trade/trayicon-rs", features = ["winit"] }
|
||||
winit = "0.26"
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
winreg = "0.10"
|
||||
windows-service = "0.4"
|
||||
@ -98,13 +99,15 @@ psimple = { package = "libpulse-simple-binding", version = "2.25" }
|
||||
pulse = { package = "libpulse-binding", version = "2.26" }
|
||||
rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" }
|
||||
async-process = "1.3"
|
||||
mouce = { git="https://github.com/fufesou/mouce.git" }
|
||||
evdev = { git="https://github.com/fufesou/evdev" }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.11"
|
||||
jni = "0.19.0"
|
||||
jni = "0.19"
|
||||
|
||||
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
|
||||
flutter_rust_bridge = "1.30.0"
|
||||
flutter_rust_bridge = "=1.30.0"
|
||||
|
||||
[workspace]
|
||||
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/simple_rc"]
|
||||
@ -122,7 +125,7 @@ winapi = { version = "0.3", features = [ "winnt" ] }
|
||||
cc = "1.0"
|
||||
hbb_common = { path = "libs/hbb_common" }
|
||||
simple_rc = { path = "libs/simple_rc", optional = true }
|
||||
flutter_rust_bridge_codegen = "1.30.0"
|
||||
flutter_rust_bridge_codegen = "=1.30.0"
|
||||
|
||||
[dev-dependencies]
|
||||
hound = "3.4"
|
||||
|
||||
8
DEBIAN/postinst
Normal file → Executable file
8
DEBIAN/postinst
Normal file → Executable file
@ -8,16 +8,20 @@ if [ "$1" = configure ]; then
|
||||
|
||||
if [ "systemd" == "$INITSYS" ]; then
|
||||
if [ -e /etc/systemd/system/rustdesk.service ]; then
|
||||
rm /etc/systemd/system/rustdesk.service
|
||||
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service /usr/lib/systemd/user/rustdesk.service >/dev/null 2>&1
|
||||
fi
|
||||
version=$(python3 -V 2>&1 | grep -Po '(?<=Python )(.+)')
|
||||
parsedVersion=$(echo "${version//./}")
|
||||
if [[ "$parsedVersion" -gt "360" ]]; then
|
||||
sudo -H pip3 install pynput
|
||||
fi
|
||||
cp /usr/share/rustdesk/files/systemd/rustdesk.service /etc/systemd/system/rustdesk.service
|
||||
cp /usr/share/rustdesk/files/systemd/rustdesk.service /usr/lib/systemd/system/rustdesk.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
|
||||
cp /usr/share/rustdesk/files/systemd/rustdesk.service.user /usr/lib/systemd/user/rustdesk.service
|
||||
curUser=$(who | awk '{print $1}' | head -1)
|
||||
systemctl --machine=${curUser}@.host --user daemon-reload
|
||||
fi
|
||||
fi
|
||||
|
||||
0
DEBIAN/postrm
Normal file → Executable file
0
DEBIAN/postrm
Normal file → Executable file
7
DEBIAN/preinst
Normal file → Executable file
7
DEBIAN/preinst
Normal file → Executable file
@ -7,6 +7,13 @@ case $1 in
|
||||
INITSYS=$(ls -al /proc/1/exe | awk -F' ' '{print $NF}' | awk -F'/' '{print $NF}')
|
||||
if [ "systemd" == "${INITSYS}" ]; then
|
||||
service rustdesk stop || true
|
||||
|
||||
serverUser=$(ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1)
|
||||
if [ "$serverUser" != "" ] && [ "$serverUser" != "root" ]
|
||||
then
|
||||
systemctl --machine=${serverUser}@.host --user stop rustdesk || true
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
rm -rf /usr/bin/libsciter-gtk.so
|
||||
fi
|
||||
|
||||
9
DEBIAN/prerm
Normal file → Executable file
9
DEBIAN/prerm
Normal file → Executable file
@ -8,7 +8,14 @@ case $1 in
|
||||
if [ "systemd" == "${INITSYS}" ]; then
|
||||
systemctl stop rustdesk || true
|
||||
systemctl disable rustdesk || true
|
||||
rm /etc/systemd/system/rustdesk.service || true
|
||||
|
||||
serverUser=$(ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1)
|
||||
if [ "$serverUser" != "" ] && [ "$serverUser" != "root" ]
|
||||
then
|
||||
systemctl --machine=${serverUser}@.host --user stop rustdesk || true
|
||||
fi
|
||||
|
||||
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service /usr/lib/systemd/user/rustdesk.service || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b> لغتك الأم, <a href="https://github.com/rustdesk/doc.rustdesk.com">Doc</a> و <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a>, README نحن بحاجة إلى مساعدتك لترجمة هذا </b>
|
||||
</p>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Struktura</a> •
|
||||
<a href="#snapshot">Ukázky</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Potřebujeme Vaši pomoc s překláním textů tohoto ČTIMNE, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">uživatelského rozhraní aplikace RustDesk</a> a <a href="https://github.com/rustdesk/doc.rustdesk.com">dokumentace k ní</a> do vašeho jazyka</b>
|
||||
</p>
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#auf-docker-kompilieren">Docker</a> •
|
||||
<a href="#dateistruktur">Dateistruktur</a> •
|
||||
<a href="#screenshots">Screenshots</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Wir brauchen deine Hilfe um diese README Datei zu verbessern und aktualisieren</b>
|
||||
</p>
|
||||
|
||||
Rede mit uns: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Rede mit uns: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#kiel-kompili-kun-docker">Docker</a> •
|
||||
<a href="#dosierstrukturo">Strukturo</a> •
|
||||
<a href="#ekrankopio">Ekrankopio</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Ni bezonas helpon traduki tiun README kaj <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">la interfacon</a> al via denaska lingvo</b>
|
||||
</p>
|
||||
|
||||
Babili kun ni: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Babili kun ni: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="#como-compilar-con-docker">Docker</a> •
|
||||
<a href="#estructura-de-archivos">Estructura</a> •
|
||||
<a href="#captura-de-pantalla">Captura de pantalla</a><br>
|
||||
[<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Necesitamos tu ayuda para traducir este README a tu idioma</b>
|
||||
</p>
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a dir="rtl" href="#نحوه-ساخت-با-داکر">داکر</a> •
|
||||
<a dir="rtl" href="#ساخت">ساخت</a> •
|
||||
<a dir="rtl" href="#سرورهای-عمومی-رایگان">سرور</a><br>
|
||||
[<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
‫<b>برای ترجمه این <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang"> RustDesk UI</a> ،README و <a href="https://github.com/rustdesk/doc.rustdesk.com">Doc</a> به زبان مادری شما به کمکتون نیاز داریم
|
||||
</p>
|
||||
|
||||
با ما گپ بزنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV)
|
||||
با ما گپ بزنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV)
|
||||
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Rakenne</a> •
|
||||
<a href="#snapshot">Tilannevedos</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Tarvitsemme apua tämän README-tiedoston kääntämiseksi äidinkielellesi</b>
|
||||
</p>
|
||||
|
||||
Juttele meidän kanssa: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Juttele meidän kanssa: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#comment-construire-avec-docker">Docker</a> -
|
||||
<a href="#structure-du-projet">Structure</a> -
|
||||
<a href="#images">Images</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Nous avons besoin de votre aide pour traduire ce README dans votre langue maternelle</b>.
|
||||
</p>
|
||||
|
||||
Chattez avec nous : [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Chattez avec nous : [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
182
README-HU.md
Normal file
182
README-HU.md
Normal file
@ -0,0 +1,182 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#ingyenes-publikus-szerverek">Szerverek</a> •
|
||||
<a href="#építési-pontok">Építés</a> •
|
||||
<a href="#hogyan-éptís-dockerrel">Docker</a> •
|
||||
<a href="#fájl-struktúra">Struktúra</a> •
|
||||
<a href="#képernyőképek">Képernyőképek</a><br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Kell a segítséged, hogy lefordítsuk ezt a README-t, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">a RustDesk UI-t</a> és a <a href="https://github.com/rustdesk/doc.rustdesk.com">Dokumentációt</a> az anyanyelvedre</b>
|
||||
</p>
|
||||
|
||||
Beszélgess velünk: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
A RustDesk egy távoli elérésű asztali szoftver, Rust-ban írva. Működik mindenféle konfiguráció nélkül, feltelepítéssel, vagy anélkül. Az adataidat teljesen te kezeled, nincs szükség aggódásra a harmadik felek miatt. Használhatod a RustDesk punblikus randevú/relay szervereit, [hostolhatsz sajátot](https://rustdesk.com/server), vagy akár [írhatsz is egyet](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
A RustDesk szívesen fogad minden contributiont, támogatást mindenkitől. Lásd a [`CONTRIBUTING.md`](CONTRIBUTING.md) fájlt a kezdéshez.
|
||||
|
||||
[**Hogyan működik a RustDesk?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F)
|
||||
|
||||
[**BINARY LELTÖLTÉS**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||
|
||||
## Ingyenes publikus szerverek
|
||||
|
||||
Ezalatt az üzenet alatt találhatóak azok a publikus szerverek, amelyeket ingyen használhatsz. Ezek a szerverek változhatnak a jövőben, illetve a hálózatuk lehet hogy lassú lehet.
|
||||
| Hely | Host | Specifikáció |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM |
|
||||
| Singapore | Vultr | 1 VCPU / 1GB RAM |
|
||||
| Dallas | Vultr | 1 VCPU / 1GB RAM | |
|
||||
|
||||
## Dependencies
|
||||
|
||||
Az asztali verziók [sciter](https://sciter.com/)-t használnak a GUI-hoz, kérlek telepítsd a dynamikus könyvtárat magad.
|
||||
|
||||
[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) |
|
||||
[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
|
||||
|
||||
A telefonos verziók Flutter-t hasznának. Később lehetséges hogy Sciterről Flutterre migrálunk az asztali verziókban is.
|
||||
|
||||
## Építési pontok
|
||||
|
||||
- Készítsd elő a Rust, C++ fejlesztői környezetet (env)
|
||||
|
||||
- Telepítsd a [vcpkg](https://github.com/microsoft/vcpkg)-t, és állítsd be a `VCPKG_ROOT` környezeti változót helyesen
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
||||
|
||||
- Futtasd a `cargo run` parancsot
|
||||
|
||||
## [Építés](https://rustdesk.com/docs/hu/dev/build/)
|
||||
|
||||
## Hogyan építs Linuxon
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y 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
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### Telepítsd a pynput csomagot
|
||||
|
||||
```sh
|
||||
pip3 install pynput
|
||||
```
|
||||
|
||||
### Telepítsd a vcpkg-t
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### Fixeld a libvpx-t (Fedora-n csak)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Építés
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone 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
|
||||
mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Válts Wayland-ról X11-re (Xorg)
|
||||
|
||||
A RustDesk nem támogatja a Waylendet. [Itt](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) található egy tutorial amelynek segítségével beállíthatod a Xorg-ot mint alap GNOME session.
|
||||
|
||||
## Hogyan építs Dockerrel
|
||||
|
||||
Kezdjünk a repo clónozásával, majd pedig a Docker container megépítésével:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Ezután, minden egyes alkalommal amikor meg kell építened a RustDesk-et, futtasd a kövezkező parancsot:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
```
|
||||
|
||||
Fontos, hogy az első építés lehet hogy több ideig fog tartani mint a következőek, mivel a dependenciek még nincsenek cachelve. Emelett, ha esetleg szeretnél valamilyen argumentumot hozzáadni az építő parancshoz, akkor megteheted a paracssor végén, a `<OPTIONAL-ARGS>` argumentum használatával. Például ha egy optimalizált release éptést szeretnél megépíteni, akkor add hozzá a fenti parancsorhoz a `--release` opciót. A futtatható binary elérhető lesz a target mappában a rendszereden, futtatni a következőképpen tudod:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Vagy ha release binary, akkor:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Kérlek mindenképpen nézd meg hogy ezeket a parancsokat a root RustDesk mappában futtatod e, különben a RustDesk lehet hogy nem fogja megtalálni az építéshez szükséges elemeket. Fontos az is, hogy jelenleg más cargo subparancsok, például `install`vagy `run` nem támogatottak, mivel egy Dockeres építés esetén elindítanák a programot a containeren belül.
|
||||
|
||||
|
||||
## Fájl Struktúra
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs functions for file transfer, and some other utility functions
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: screen capture
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specific keyboard/mouse control
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/clipboard/input/video services, and network connections
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start a peer connection
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Communicate with [rustdesk-server](https://github.com/rustdesk/rustdesk-server), wait for remote direct (TCP hole punching) or relayed connection
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform specific code
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript for Flutter web client
|
||||
|
||||
## Képernyőképek
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Kami membutuhkan bantuan Anda untuk menerjemahkan README ini dan <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> ke bahasa asli anda</b>
|
||||
</p>
|
||||
|
||||
Birbincang bersama kami: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Birbincang bersama kami: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#come-compilare-con-docker">Docker</a> •
|
||||
<a href="#struttura-dei-file">Struttura</a> •
|
||||
<a href="#screenshots">Screenshots</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Abbiamo bisogno del tuo aiuto per tradurre questo README e la <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> nella tua lingua nativa</b>
|
||||
</p>
|
||||
|
||||
Chatta con noi: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Chatta con noi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>このREADMEをあなたの母国語に翻訳するために、あなたの助けが必要です。</b>
|
||||
</p>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.</b>
|
||||
</p>
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>ഈ README നിങ്ങളുടെ മാതൃഭാഷയിലേക്ക് വിവർത്തനം ചെയ്യാൻ ഞങ്ങൾക്ക് നിങ്ങളുടെ സഹായം ആവശ്യമാണ്</b>
|
||||
</p>
|
||||
|
||||
ഞങ്ങളുമായി ചാറ്റ് ചെയ്യുക: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
ഞങ്ങളുമായി ചാറ്റ് ചെയ്യുക: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structuur</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>We hebben je hulp nodig om deze README te vertalen naar jouw moedertaal</b>
|
||||
</p>
|
||||
|
||||
Praat met ons: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Praat met ons: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#jak-kompilować-za-pomocą-dockera">Docker</a> •
|
||||
<a href="#struktura-plików">Struktura</a> •
|
||||
<a href="#migawkisnapshoty">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Potrzebujemy twojej pomocy w tłumaczeniu README na twój ojczysty język</b>
|
||||
</p>
|
||||
|
||||
Porozmawiaj z nami na: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Porozmawiaj z nami na: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<a href="#como-compilar-com-docker">Docker</a> •
|
||||
<a href="#estrutura-de-arquivos">Estrutura</a> •
|
||||
<a href="#screenshots">Screenshots</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Precisamos de sua ajuda para traduzir este README e a <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">UI do RustDesk</a> para sua língua nativa</b>
|
||||
</p>
|
||||
|
||||
Converse conosco: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Converse conosco: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>Нам нужна ваша помощь для перевода этого README и <a href="https://github.com/rustdesk/rustdesk/tree/master/src/rustdesk/tree/master/src/lang">RustDesk UI</a> на ваш родной язык</B>
|
||||
</p>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="#使用Docker编译">Docker</a> •
|
||||
<a href="#文件结构">结构</a> •
|
||||
<a href="#截图">截图</a><br>
|
||||
[<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</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>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
</p>
|
||||
|
||||
Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
[<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-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
|
||||
<b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">Doc</a> to your native language</b>
|
||||
</p>
|
||||
|
||||
|
||||
5
build.py
5
build.py
@ -209,12 +209,15 @@ rcodesign notarize --api-issuer 69a6de7d-2907-47e3-e053-5b8c7c11a4d1 --api-key 9
|
||||
os.system('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||
os.system(
|
||||
'cp rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||
os.system(
|
||||
'cp rustdesk.service.user tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||
os.system('cp pynput_service.py tmpdeb/usr/share/rustdesk/files/')
|
||||
os.system('cp DEBIAN/* tmpdeb/DEBIAN/')
|
||||
os.system('cp -a DEBIAN/* tmpdeb/DEBIAN/')
|
||||
os.system('strip tmpdeb/usr/bin/rustdesk')
|
||||
os.system('mkdir -p tmpdeb/usr/lib/rustdesk')
|
||||
os.system('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
|
||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service.user')
|
||||
md5_file('usr/share/rustdesk/files/pynput_service.py')
|
||||
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
|
||||
os.system('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
package="com.carriez.flutter_hbb">
|
||||
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
@ -8,14 +8,14 @@ package com.carriez.flutter_hbb
|
||||
|
||||
import android.accessibilityservice.AccessibilityService
|
||||
import android.accessibilityservice.GestureDescription
|
||||
import android.content.Context
|
||||
import android.graphics.Path
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
const val LIFT_DOWN = 9
|
||||
const val LIFT_MOVE = 8
|
||||
@ -49,28 +49,40 @@ class InputService : AccessibilityService() {
|
||||
|
||||
private val wheelActionsQueue = LinkedList<GestureDescription>()
|
||||
private var isWheelActionsPolling = false
|
||||
private var isWaitingLongPress = false
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun onMouseInput(mask: Int, _x: Int, _y: Int) {
|
||||
val x = if (_x < 0) {
|
||||
0
|
||||
} else {
|
||||
_x
|
||||
}
|
||||
|
||||
val y = if (_y < 0) {
|
||||
0
|
||||
} else {
|
||||
_y
|
||||
}
|
||||
val x = max(0, _x)
|
||||
val y = max(0, _y)
|
||||
|
||||
if (mask == 0 || mask == LIFT_MOVE) {
|
||||
val oldX = mouseX
|
||||
val oldY = mouseY
|
||||
mouseX = x * SCREEN_INFO.scale
|
||||
mouseY = y * SCREEN_INFO.scale
|
||||
if (isWaitingLongPress) {
|
||||
val delta = abs(oldX - mouseX) + abs(oldY - mouseY)
|
||||
Log.d(logTag,"delta:$delta")
|
||||
if (delta > 8) {
|
||||
isWaitingLongPress = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// left button down ,was up
|
||||
if (mask == LIFT_DOWN) {
|
||||
isWaitingLongPress = true
|
||||
timer.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
if (isWaitingLongPress) {
|
||||
isWaitingLongPress = false
|
||||
leftIsDown = false
|
||||
endGesture(mouseX, mouseY)
|
||||
}
|
||||
}
|
||||
}, LONG_TAP_DELAY * 4)
|
||||
|
||||
leftIsDown = true
|
||||
startGesture(mouseX, mouseY)
|
||||
return
|
||||
@ -83,9 +95,12 @@ class InputService : AccessibilityService() {
|
||||
|
||||
// left up ,was down
|
||||
if (mask == LIFT_UP) {
|
||||
leftIsDown = false
|
||||
endGesture(mouseX, mouseY)
|
||||
return
|
||||
if (leftIsDown) {
|
||||
leftIsDown = false
|
||||
isWaitingLongPress = false
|
||||
endGesture(mouseX, mouseY)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (mask == RIGHT_UP) {
|
||||
|
||||
@ -192,7 +192,6 @@ class MainActivity : FlutterActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val inputPer = InputService.isOpen
|
||||
Log.d(logTag, "onResume inputPer:$inputPer")
|
||||
activity.runOnUiThread {
|
||||
flutterMethodChannel.invokeMethod(
|
||||
"on_state_changed",
|
||||
|
||||
@ -2,20 +2,26 @@ package com.carriez.flutter_hbb
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.AudioRecord
|
||||
import android.media.AudioRecord.READ_BLOCKING
|
||||
import android.media.MediaCodecList
|
||||
import android.media.MediaFormat
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
|
||||
import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
|
||||
@SuppressLint("ConstantLocale")
|
||||
val LOCAL_NAME = Locale.getDefault().toString()
|
||||
val SCREEN_INFO = Info(0, 0, 1, 200)
|
||||
@ -38,8 +44,31 @@ fun testVP9Support(): Boolean {
|
||||
return res != null
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun requestPermission(context: Context, type: String) {
|
||||
val permission = when (type) {
|
||||
"ignore_battery_optimizations" -> {
|
||||
try {
|
||||
context.startActivity(Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
||||
data = Uri.parse("package:" + context.packageName)
|
||||
})
|
||||
} catch (e:Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return
|
||||
}
|
||||
"application_details_settings" -> {
|
||||
try {
|
||||
context.startActivity(Intent().apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
action = "android.settings.APPLICATION_DETAILS_SETTINGS"
|
||||
data = Uri.parse("package:" + context.packageName)
|
||||
})
|
||||
} catch (e:Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return
|
||||
}
|
||||
"audio" -> {
|
||||
Permission.RECORD_AUDIO
|
||||
}
|
||||
@ -52,7 +81,7 @@ fun requestPermission(context: Context, type: String) {
|
||||
}
|
||||
XXPermissions.with(context)
|
||||
.permission(permission)
|
||||
.request { permissions, all ->
|
||||
.request { _, all ->
|
||||
if (all) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
MainActivity.flutterMethodChannel.invokeMethod(
|
||||
@ -64,8 +93,13 @@ fun requestPermission(context: Context, type: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun checkPermission(context: Context, type: String): Boolean {
|
||||
val permission = when (type) {
|
||||
"ignore_battery_optimizations" -> {
|
||||
val pw = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
return pw.isIgnoringBatteryOptimizations(context.packageName)
|
||||
}
|
||||
"audio" -> {
|
||||
Permission.RECORD_AUDIO
|
||||
}
|
||||
|
||||
@ -260,7 +260,12 @@ class PermissionManager {
|
||||
static Timer? _timer;
|
||||
static var _current = "";
|
||||
|
||||
static final permissions = ["audio", "file"];
|
||||
static final permissions = [
|
||||
"audio",
|
||||
"file",
|
||||
"ignore_battery_optimizations",
|
||||
"application_details_settings"
|
||||
];
|
||||
|
||||
static bool isWaitingFile() {
|
||||
if (_completer != null) {
|
||||
@ -279,9 +284,12 @@ class PermissionManager {
|
||||
if (!permissions.contains(type))
|
||||
return Future.error("Wrong permission!$type");
|
||||
|
||||
FFI.invokeMethod("request_permission", type);
|
||||
if (type == "ignore_battery_optimizations") {
|
||||
return Future.value(false);
|
||||
}
|
||||
_current = type;
|
||||
_completer = Completer<bool>();
|
||||
FFI.invokeMethod("request_permission", type);
|
||||
|
||||
// timeout
|
||||
_timer?.cancel();
|
||||
|
||||
@ -262,7 +262,6 @@ class _RemotePageState extends State<RemotePage> {
|
||||
: SafeArea(child:
|
||||
OrientationBuilder(builder: (ctx, orientation) {
|
||||
if (_currentOrientation != orientation) {
|
||||
debugPrint("on orientation changed");
|
||||
Timer(Duration(milliseconds: 200), () {
|
||||
resetMobileActionsOverlay();
|
||||
_currentOrientation = orientation;
|
||||
@ -1061,6 +1060,8 @@ void showOptions() {
|
||||
getRadio('Optimize reaction time', 'low', quality, setQuality),
|
||||
Divider(color: MyTheme.border),
|
||||
getToggle(setState, 'show-remote-cursor', 'Show remote cursor'),
|
||||
getToggle(
|
||||
setState, 'show-quality-monitor', 'Show quality monitor'),
|
||||
] +
|
||||
more),
|
||||
actions: [],
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:settings_ui/settings_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@ -24,13 +26,100 @@ class SettingsPage extends StatefulWidget implements PageShape {
|
||||
_SettingsState createState() => _SettingsState();
|
||||
}
|
||||
|
||||
class _SettingsState extends State<SettingsPage> {
|
||||
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
static const url = 'https://rustdesk.com/';
|
||||
final _hasIgnoreBattery = androidVersion >= 26;
|
||||
var _ignoreBatteryOpt = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
if (_hasIgnoreBattery) {
|
||||
updateIgnoreBatteryStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
updateIgnoreBatteryStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> updateIgnoreBatteryStatus() async {
|
||||
final res = await PermissionManager.check("ignore_battery_optimizations");
|
||||
if (_ignoreBatteryOpt != res) {
|
||||
setState(() {
|
||||
_ignoreBatteryOpt = res;
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Provider.of<FfiModel>(context);
|
||||
final username = getUsername();
|
||||
final enableAbr = FFI.getByName("option", "enable-abr") != 'N';
|
||||
final enhancementsTiles = [
|
||||
SettingsTile.switchTile(
|
||||
title: Text(translate('Adaptive Bitrate') + '(beta)'),
|
||||
initialValue: enableAbr,
|
||||
onToggle: (v) {
|
||||
final msg = Map()
|
||||
..["name"] = "enable-abr"
|
||||
..["value"] = "";
|
||||
if (!v) {
|
||||
msg["value"] = "N";
|
||||
}
|
||||
FFI.setByName("option", json.encode(msg));
|
||||
setState(() {});
|
||||
},
|
||||
)
|
||||
];
|
||||
if (_hasIgnoreBattery) {
|
||||
enhancementsTiles.insert(
|
||||
0,
|
||||
SettingsTile.switchTile(
|
||||
initialValue: _ignoreBatteryOpt,
|
||||
title: Text(translate('Keep RustDesk background service')),
|
||||
description:
|
||||
Text('* ${translate('Ignore Battery Optimizations')}'),
|
||||
onToggle: (v) async {
|
||||
if (v) {
|
||||
PermissionManager.request("ignore_battery_optimizations");
|
||||
} else {
|
||||
final res = await DialogManager.show<bool>(
|
||||
(setState, close) => CustomAlertDialog(
|
||||
title: Text(translate("Open System Setting")),
|
||||
content: Text(translate(
|
||||
"android_open_battery_optimizations_tip")),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => close(),
|
||||
child: Text(translate("Cancel"))),
|
||||
ElevatedButton(
|
||||
onPressed: () => close(true),
|
||||
child:
|
||||
Text(translate("Open System Setting"))),
|
||||
],
|
||||
));
|
||||
if (res == true) {
|
||||
PermissionManager.request("application_details_settings");
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return SettingsList(
|
||||
sections: [
|
||||
SettingsSection(
|
||||
@ -51,17 +140,17 @@ class _SettingsState extends State<SettingsPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SettingsSection(
|
||||
title: Text(translate("Settings")),
|
||||
tiles: [
|
||||
SettingsTile.navigation(
|
||||
SettingsSection(title: Text(translate("Settings")), tiles: [
|
||||
SettingsTile.navigation(
|
||||
title: Text(translate('ID/Relay Server')),
|
||||
leading: Icon(Icons.cloud),
|
||||
onPressed: (context) {
|
||||
showServerSettings();
|
||||
},
|
||||
),
|
||||
],
|
||||
})
|
||||
]),
|
||||
SettingsSection(
|
||||
title: Text(translate("Enhancements")),
|
||||
tiles: enhancementsTiles,
|
||||
),
|
||||
SettingsSection(
|
||||
title: Text(translate("About")),
|
||||
|
||||
@ -23,6 +23,7 @@ serde = { version = "1.0", optional = true }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
log = "0.4"
|
||||
rdev = { git = "https://github.com/asur4s/rdev" }
|
||||
hbb_common = { path = "../hbb_common" }
|
||||
|
||||
[features]
|
||||
with_serde = ["serde", "serde_derive"]
|
||||
|
||||
@ -249,7 +249,7 @@ pub trait MouseControllable {
|
||||
/// For alphabetical keys, use Key::Layout for a system independent key.
|
||||
/// If a key is missing, you can use the raw keycode with Key::Raw.
|
||||
#[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Key {
|
||||
/// alt key on Linux and Windows (option key on macOS)
|
||||
Alt,
|
||||
|
||||
5
libs/enigo/src/linux/mod.rs
Normal file
5
libs/enigo/src/linux/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod nix_impl;
|
||||
mod pynput;
|
||||
mod xdo;
|
||||
|
||||
pub use self::nix_impl::Enigo;
|
||||
178
libs/enigo/src/linux/nix_impl.rs
Normal file
178
libs/enigo/src/linux/nix_impl.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use super::{pynput::EnigoPynput, xdo::EnigoXdo};
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
// #[derive(Default)]
|
||||
pub struct Enigo {
|
||||
xdo: EnigoXdo,
|
||||
pynput: EnigoPynput,
|
||||
is_x11: bool,
|
||||
uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
|
||||
uinput_mouse: Option<Box<dyn MouseControllable + Send>>,
|
||||
}
|
||||
|
||||
impl Enigo {
|
||||
/// Get delay of xdo implementation.
|
||||
pub fn delay(&self) -> u64 {
|
||||
self.xdo.delay()
|
||||
}
|
||||
/// Set delay of xdo implemetation.
|
||||
pub fn set_delay(&mut self, delay: u64) {
|
||||
self.xdo.set_delay(delay)
|
||||
}
|
||||
/// Reset pynput.
|
||||
pub fn reset(&mut self) {
|
||||
self.pynput.reset();
|
||||
}
|
||||
/// Set uinput keyboard.
|
||||
pub fn set_uinput_keyboard(
|
||||
&mut self,
|
||||
uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
|
||||
) {
|
||||
self.uinput_keyboard = uinput_keyboard
|
||||
}
|
||||
/// Set uinput mouse.
|
||||
pub fn set_uinput_mouse(&mut self, uinput_mouse: Option<Box<dyn MouseControllable + Send>>) {
|
||||
self.uinput_mouse = uinput_mouse
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Enigo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_x11: "x11" == hbb_common::platform::linux::get_display_server(),
|
||||
uinput_keyboard: None,
|
||||
uinput_mouse: None,
|
||||
xdo: EnigoXdo::default(),
|
||||
pynput: EnigoPynput::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseControllable for Enigo {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_move_to(x, y);
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_move_to(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_move_relative(x, y);
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_move_relative(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_down(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_down(button)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_up(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_up(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_click(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_click(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_scroll_x(length)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_scroll_x(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_scroll_y(length)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_scroll_y(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardControllable for Enigo {
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
if self.is_x11 {
|
||||
self.xdo.get_key_state(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.get_key_state(key)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
if self.is_x11 {
|
||||
self.xdo.key_sequence(sequence)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_sequence(sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
if self.is_x11 {
|
||||
if self.pynput.send_pynput(&key, true) {
|
||||
return Ok(());
|
||||
}
|
||||
self.xdo.key_down(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_down(key)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_up(&mut self, key: Key) {
|
||||
if self.is_x11 {
|
||||
if self.pynput.send_pynput(&key, false) {
|
||||
return;
|
||||
}
|
||||
self.xdo.key_up(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_up(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_click(&mut self, key: Key) {
|
||||
if self.is_x11 {
|
||||
self.xdo.key_click(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_click(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
280
libs/enigo/src/linux/pynput.rs
Normal file
280
libs/enigo/src/linux/pynput.rs
Normal file
@ -0,0 +1,280 @@
|
||||
use crate::Key;
|
||||
use std::{io::prelude::*, sync::mpsc};
|
||||
|
||||
enum PyMsg {
|
||||
Char(char),
|
||||
Str(&'static str),
|
||||
}
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub(super) struct EnigoPynput {
|
||||
tx: mpsc::Sender<(PyMsg, bool)>,
|
||||
}
|
||||
|
||||
impl Default for EnigoPynput {
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
start_pynput_service(rx);
|
||||
Self { tx }
|
||||
}
|
||||
}
|
||||
impl EnigoPynput {
|
||||
pub(super) fn reset(&mut self) {
|
||||
self.tx.send((PyMsg::Char('\0'), true)).ok();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn send_pynput(&mut self, key: &Key, is_press: bool) -> bool {
|
||||
if unsafe { PYNPUT_EXIT || !PYNPUT_REDAY } {
|
||||
return false;
|
||||
}
|
||||
if let Key::Layout(c) = key {
|
||||
return self.tx.send((PyMsg::Char(*c), is_press)).is_ok();
|
||||
}
|
||||
if let Key::Raw(_) = key {
|
||||
return false;
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
let s = match key {
|
||||
Key::Alt => "Alt_L",
|
||||
Key::Backspace => "BackSpace",
|
||||
Key::CapsLock => "Caps_Lock",
|
||||
Key::Control => "Control_L",
|
||||
Key::Delete => "Delete",
|
||||
Key::DownArrow => "Down",
|
||||
Key::End => "End",
|
||||
Key::Escape => "Escape",
|
||||
Key::F1 => "F1",
|
||||
Key::F10 => "F10",
|
||||
Key::F11 => "F11",
|
||||
Key::F12 => "F12",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F6 => "F6",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
Key::F9 => "F9",
|
||||
Key::Home => "Home",
|
||||
Key::LeftArrow => "Left",
|
||||
Key::Option => "Option",
|
||||
Key::PageDown => "Page_Down",
|
||||
Key::PageUp => "Page_Up",
|
||||
Key::Return => "Return",
|
||||
Key::RightArrow => "Right",
|
||||
Key::Shift => "Shift_L",
|
||||
Key::Space => "space",
|
||||
Key::Tab => "Tab",
|
||||
Key::UpArrow => "Up",
|
||||
Key::Numpad0 => "0",
|
||||
Key::Numpad1 => "1",
|
||||
Key::Numpad2 => "2",
|
||||
Key::Numpad3 => "3",
|
||||
Key::Numpad4 => "4",
|
||||
Key::Numpad5 => "5",
|
||||
Key::Numpad6 => "6",
|
||||
Key::Numpad7 => "7",
|
||||
Key::Numpad8 => "8",
|
||||
Key::Numpad9 => "9",
|
||||
Key::Decimal => "KP_Decimal",
|
||||
Key::Cancel => "Cancel",
|
||||
Key::Clear => "Clear",
|
||||
Key::Pause => "Pause",
|
||||
Key::Kana => "Kana",
|
||||
Key::Hangul => "Hangul",
|
||||
Key::Hanja => "Hanja",
|
||||
Key::Kanji => "Kanji",
|
||||
Key::Select => "Select",
|
||||
Key::Print => "Print",
|
||||
Key::Execute => "Execute",
|
||||
Key::Snapshot => "3270_PrintScreen",
|
||||
Key::Insert => "Insert",
|
||||
Key::Help => "Help",
|
||||
Key::Separator => "KP_Separator",
|
||||
Key::Scroll => "Scroll_Lock",
|
||||
Key::NumLock => "Num_Lock",
|
||||
Key::RWin => "Super_R",
|
||||
Key::Apps => "Menu",
|
||||
Key::Multiply => "KP_Multiply",
|
||||
Key::Add => "KP_Add",
|
||||
Key::Subtract => "KP_Subtract",
|
||||
Key::Divide => "KP_Divide",
|
||||
Key::Equals => "KP_Equal",
|
||||
Key::NumpadEnter => "KP_Enter",
|
||||
Key::RightShift => "Shift_R",
|
||||
Key::RightControl => "Control_R",
|
||||
Key::RightAlt => "Mode_switch",
|
||||
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super_L",
|
||||
_ => {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
log::info!("send pynput: {:?}", &s);
|
||||
return self.tx.send((PyMsg::Str(s), is_press)).is_ok();
|
||||
}
|
||||
}
|
||||
|
||||
// impl MouseControllable for EnigoPynput {
|
||||
// fn mouse_move_to(&mut self, _x: i32, _y: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_move_relative(&mut self, _x: i32, _y: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_down(&mut self, _button: MouseButton) -> crate::ResultType {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_up(&mut self, _button: MouseButton) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_click(&mut self, _button: MouseButton) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_scroll_x(&mut self, _length: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_scroll_y(&mut self, _length: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl KeyboardControllable for EnigoPynput {
|
||||
// fn get_key_state(&mut self, _key: Key) -> bool {
|
||||
// unimplemented!()
|
||||
// }
|
||||
|
||||
// fn key_sequence(&mut self, _sequence: &str) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
// let _ = self.send_pynput(&key, true);
|
||||
// Ok(())
|
||||
// }
|
||||
// fn key_up(&mut self, key: Key) {
|
||||
// let _ = self.send_pynput(&key, false);
|
||||
// }
|
||||
// fn key_click(&mut self, _key: Key) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// }
|
||||
|
||||
static mut PYNPUT_EXIT: bool = false;
|
||||
static mut PYNPUT_REDAY: bool = false;
|
||||
static IPC_FILE: &'static str = "/tmp/RustDesk/pynput_service";
|
||||
|
||||
fn start_pynput_service(rx: mpsc::Receiver<(PyMsg, bool)>) {
|
||||
let mut py = "./pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/share/rustdesk/files/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/lib/rustdesk/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
log::error!("{} not exits", py);
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("pynput service: {}", py);
|
||||
std::thread::spawn(move || {
|
||||
let username = std::env::var("PYNPUT_USERNAME").unwrap_or("".to_owned());
|
||||
let userid = std::env::var("PYNPUT_USERID").unwrap_or("".to_owned());
|
||||
let status = if username.is_empty() {
|
||||
std::process::Command::new("python3")
|
||||
.arg(&py)
|
||||
.arg(IPC_FILE)
|
||||
.status()
|
||||
.map(|x| x.success())
|
||||
} else {
|
||||
let mut status = Ok(true);
|
||||
for i in 0..100 {
|
||||
if i % 10 == 0 {
|
||||
log::info!("#{} try to start pynput server", i);
|
||||
}
|
||||
status = std::process::Command::new("sudo")
|
||||
.args(vec![
|
||||
"-E",
|
||||
&format!("XDG_RUNTIME_DIR=/run/user/{}", userid) as &str,
|
||||
"-u",
|
||||
&username,
|
||||
"python3",
|
||||
&py,
|
||||
IPC_FILE,
|
||||
])
|
||||
.status()
|
||||
.map(|x| x.success());
|
||||
match status {
|
||||
Ok(true) => break,
|
||||
_ => {}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
status
|
||||
};
|
||||
log::info!(
|
||||
"pynput server exit with username/id {}/{}: {:?}",
|
||||
username,
|
||||
userid,
|
||||
status
|
||||
);
|
||||
unsafe {
|
||||
PYNPUT_EXIT = true;
|
||||
}
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
for i in 0..300 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
let mut conn = match std::os::unix::net::UnixStream::connect(IPC_FILE) {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
if i % 15 == 0 {
|
||||
log::warn!("Failed to connect to {}: {}", IPC_FILE, err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) = conn.set_nonblocking(true) {
|
||||
log::error!("Failed to set ipc nonblocking: {}", err);
|
||||
return;
|
||||
}
|
||||
log::info!("Conntected to pynput server");
|
||||
let d = std::time::Duration::from_millis(30);
|
||||
unsafe {
|
||||
PYNPUT_REDAY = true;
|
||||
}
|
||||
let mut buf = [0u8; 1024];
|
||||
loop {
|
||||
if unsafe { PYNPUT_EXIT } {
|
||||
break;
|
||||
}
|
||||
match rx.recv_timeout(d) {
|
||||
Ok((msg, is_press)) => {
|
||||
let msg = match msg {
|
||||
PyMsg::Char(chr) => {
|
||||
format!("{}{}", if is_press { 'p' } else { 'r' }, chr)
|
||||
}
|
||||
PyMsg::Str(s) => format!("{}{}", if is_press { 'p' } else { 'r' }, s),
|
||||
};
|
||||
let n = msg.len();
|
||||
buf[0] = n as _;
|
||||
buf[1..(n + 1)].copy_from_slice(msg.as_bytes());
|
||||
if let Err(err) = conn.write_all(&buf[..n + 1]) {
|
||||
log::error!("Failed to write to ipc: {}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
mpsc::RecvTimeoutError::Disconnected => {
|
||||
log::error!("pynput sender disconnecte");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
PYNPUT_REDAY = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -4,6 +4,7 @@ use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
|
||||
use self::libc::{c_char, c_int, c_void, useconds_t};
|
||||
use std::{borrow::Cow, ffi::CString, io::prelude::*, ptr, sync::mpsc};
|
||||
|
||||
const CURRENT_WINDOW: c_int = 0;
|
||||
const DEFAULT_DELAY: u64 = 12000;
|
||||
type Window = c_int;
|
||||
@ -59,34 +60,25 @@ fn mousebutton(button: MouseButton) -> c_int {
|
||||
}
|
||||
}
|
||||
|
||||
enum PyMsg {
|
||||
Char(char),
|
||||
Str(&'static str),
|
||||
}
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub struct Enigo {
|
||||
pub(super) struct EnigoXdo {
|
||||
xdo: Xdo,
|
||||
delay: u64,
|
||||
tx: mpsc::Sender<(PyMsg, bool)>,
|
||||
}
|
||||
// This is safe, we have a unique pointer.
|
||||
// TODO: use Unique<c_char> once stable.
|
||||
unsafe impl Send for Enigo {}
|
||||
unsafe impl Send for EnigoXdo {}
|
||||
|
||||
impl Default for Enigo {
|
||||
/// Create a new Enigo instance
|
||||
impl Default for EnigoXdo {
|
||||
/// Create a new EnigoXdo instance
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
start_pynput_service(rx);
|
||||
Self {
|
||||
xdo: unsafe { xdo_new(ptr::null()) },
|
||||
delay: DEFAULT_DELAY,
|
||||
tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Enigo {
|
||||
impl EnigoXdo {
|
||||
/// Get the delay per keypress.
|
||||
/// Default value is 12000.
|
||||
/// This is Linux-specific.
|
||||
@ -98,102 +90,8 @@ impl Enigo {
|
||||
pub fn set_delay(&mut self, delay: u64) {
|
||||
self.delay = delay;
|
||||
}
|
||||
///
|
||||
pub fn reset(&mut self) {
|
||||
self.tx.send((PyMsg::Char('\0'), true)).ok();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send_pynput(&mut self, key: &Key, is_press: bool) -> bool {
|
||||
if unsafe { PYNPUT_EXIT || !PYNPUT_REDAY } {
|
||||
return false;
|
||||
}
|
||||
if let Key::Layout(c) = key {
|
||||
return self.tx.send((PyMsg::Char(*c), is_press)).is_ok();
|
||||
}
|
||||
if let Key::Raw(_) = key {
|
||||
return false;
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
let s = match key {
|
||||
Key::Alt => "Alt_L",
|
||||
Key::Backspace => "BackSpace",
|
||||
Key::CapsLock => "Caps_Lock",
|
||||
Key::Control => "Control_L",
|
||||
Key::Delete => "Delete",
|
||||
Key::DownArrow => "Down",
|
||||
Key::End => "End",
|
||||
Key::Escape => "Escape",
|
||||
Key::F1 => "F1",
|
||||
Key::F10 => "F10",
|
||||
Key::F11 => "F11",
|
||||
Key::F12 => "F12",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F6 => "F6",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
Key::F9 => "F9",
|
||||
Key::Home => "Home",
|
||||
Key::LeftArrow => "Left",
|
||||
Key::Option => "Option",
|
||||
Key::PageDown => "Page_Down",
|
||||
Key::PageUp => "Page_Up",
|
||||
Key::Return => "Return",
|
||||
Key::RightArrow => "Right",
|
||||
Key::Shift => "Shift_L",
|
||||
Key::Space => "space",
|
||||
Key::Tab => "Tab",
|
||||
Key::UpArrow => "Up",
|
||||
Key::Numpad0 => "0",
|
||||
Key::Numpad1 => "1",
|
||||
Key::Numpad2 => "2",
|
||||
Key::Numpad3 => "3",
|
||||
Key::Numpad4 => "4",
|
||||
Key::Numpad5 => "5",
|
||||
Key::Numpad6 => "6",
|
||||
Key::Numpad7 => "7",
|
||||
Key::Numpad8 => "8",
|
||||
Key::Numpad9 => "9",
|
||||
Key::Decimal => "KP_Decimal",
|
||||
Key::Cancel => "Cancel",
|
||||
Key::Clear => "Clear",
|
||||
Key::Pause => "Pause",
|
||||
Key::Kana => "Kana",
|
||||
Key::Hangul => "Hangul",
|
||||
Key::Hanja => "Hanja",
|
||||
Key::Kanji => "Kanji",
|
||||
Key::Select => "Select",
|
||||
Key::Print => "Print",
|
||||
Key::Execute => "Execute",
|
||||
Key::Snapshot => "3270_PrintScreen",
|
||||
Key::Insert => "Insert",
|
||||
Key::Help => "Help",
|
||||
Key::Separator => "KP_Separator",
|
||||
Key::Scroll => "Scroll_Lock",
|
||||
Key::NumLock => "Num_Lock",
|
||||
Key::RWin => "Super_R",
|
||||
Key::Apps => "Menu",
|
||||
Key::Multiply => "KP_Multiply",
|
||||
Key::Add => "KP_Add",
|
||||
Key::Subtract => "KP_Subtract",
|
||||
Key::Divide => "KP_Divide",
|
||||
Key::Equals => "KP_Equal",
|
||||
Key::NumpadEnter => "KP_Enter",
|
||||
Key::RightShift => "Shift_R",
|
||||
Key::RightControl => "Control_R",
|
||||
Key::RightAlt => "Mode_switch",
|
||||
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super_L",
|
||||
_ => {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return self.tx.send((PyMsg::Str(s), is_press)).is_ok();
|
||||
}
|
||||
}
|
||||
impl Drop for Enigo {
|
||||
impl Drop for EnigoXdo {
|
||||
fn drop(&mut self) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
@ -203,7 +101,7 @@ impl Drop for Enigo {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MouseControllable for Enigo {
|
||||
impl MouseControllable for EnigoXdo {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
@ -378,7 +276,7 @@ fn keysequence<'a>(key: Key) -> Cow<'a, str> {
|
||||
_ => "",
|
||||
})
|
||||
}
|
||||
impl KeyboardControllable for Enigo {
|
||||
impl KeyboardControllable for EnigoXdo {
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
if self.xdo.is_null() {
|
||||
return false;
|
||||
@ -431,9 +329,6 @@ impl KeyboardControllable for Enigo {
|
||||
if self.xdo.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
if self.send_pynput(&key, true) {
|
||||
return Ok(());
|
||||
}
|
||||
let string = CString::new(&*keysequence(key))?;
|
||||
unsafe {
|
||||
xdo_send_keysequence_window_down(
|
||||
@ -449,9 +344,6 @@ impl KeyboardControllable for Enigo {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
if self.send_pynput(&key, false) {
|
||||
return;
|
||||
}
|
||||
if let Ok(string) = CString::new(&*keysequence(key)) {
|
||||
unsafe {
|
||||
xdo_send_keysequence_window_up(
|
||||
@ -494,128 +386,3 @@ impl KeyboardControllable for Enigo {
|
||||
crate::dsl::eval(self, sequence)
|
||||
}
|
||||
}
|
||||
|
||||
static mut PYNPUT_EXIT: bool = false;
|
||||
static mut PYNPUT_REDAY: bool = false;
|
||||
static IPC_FILE: &'static str = "/tmp/RustDesk/pynput_service";
|
||||
|
||||
fn start_pynput_service(rx: mpsc::Receiver<(PyMsg, bool)>) {
|
||||
let mut py = "./pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/share/rustdesk/files/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/lib/rustdesk/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
// enigo libs, not rustdesk root project, so skip using appimage features
|
||||
py = std::env::var("APPDIR").unwrap_or("".to_string())
|
||||
+ "/usr/lib/rustdesk/pynput_service.py";
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
log::error!("{} not exists", py);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("pynput service: {}", py);
|
||||
std::thread::spawn(move || {
|
||||
let username = std::env::var("PYNPUT_USERNAME").unwrap_or("".to_owned());
|
||||
let userid = std::env::var("PYNPUT_USERID").unwrap_or("".to_owned());
|
||||
let status = if username.is_empty() {
|
||||
std::process::Command::new("python3")
|
||||
.arg(&py)
|
||||
.arg(IPC_FILE)
|
||||
.status()
|
||||
.map(|x| x.success())
|
||||
} else {
|
||||
let mut status = Ok(true);
|
||||
for i in 0..100 {
|
||||
if i % 10 == 0 {
|
||||
log::info!("#{} try to start pynput server", i);
|
||||
}
|
||||
status = std::process::Command::new("sudo")
|
||||
.args(vec![
|
||||
"-E",
|
||||
&format!("XDG_RUNTIME_DIR=/run/user/{}", userid) as &str,
|
||||
"-u",
|
||||
&username,
|
||||
"python3",
|
||||
&py,
|
||||
IPC_FILE,
|
||||
])
|
||||
.status()
|
||||
.map(|x| x.success());
|
||||
match status {
|
||||
Ok(true) => break,
|
||||
_ => {}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
status
|
||||
};
|
||||
log::info!(
|
||||
"pynput server exit with username/id {}/{}: {:?}",
|
||||
username,
|
||||
userid,
|
||||
status
|
||||
);
|
||||
unsafe {
|
||||
PYNPUT_EXIT = true;
|
||||
}
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
for i in 0..300 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
let mut conn = match std::os::unix::net::UnixStream::connect(IPC_FILE) {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
if i % 15 == 0 {
|
||||
log::warn!("Failed to connect to {}: {}", IPC_FILE, err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) = conn.set_nonblocking(true) {
|
||||
log::error!("Failed to set ipc nonblocking: {}", err);
|
||||
return;
|
||||
}
|
||||
log::info!("Conntected to pynput server");
|
||||
let d = std::time::Duration::from_millis(30);
|
||||
unsafe {
|
||||
PYNPUT_REDAY = true;
|
||||
}
|
||||
let mut buf = [0u8; 1024];
|
||||
loop {
|
||||
if unsafe { PYNPUT_EXIT } {
|
||||
break;
|
||||
}
|
||||
match rx.recv_timeout(d) {
|
||||
Ok((msg, is_press)) => {
|
||||
let msg = match msg {
|
||||
PyMsg::Char(chr) => {
|
||||
format!("{}{}", if is_press { 'p' } else { 'r' }, chr)
|
||||
}
|
||||
PyMsg::Str(s) => format!("{}{}", if is_press { 'p' } else { 'r' }, s),
|
||||
};
|
||||
let n = msg.len();
|
||||
buf[0] = n as _;
|
||||
buf[1..(n + 1)].copy_from_slice(msg.as_bytes());
|
||||
if let Err(err) = conn.write_all(&buf[..n + 1]) {
|
||||
log::error!("Failed to write to ipc: {}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
mpsc::RecvTimeoutError::Disconnected => {
|
||||
log::error!("pynput sender disconnecte");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
PYNPUT_REDAY = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -7,9 +7,9 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
protobuf = "3.0.0-alpha.2"
|
||||
tokio = { version = "1.15", features = ["full"] }
|
||||
tokio-util = { version = "0.6", features = ["full"] }
|
||||
protobuf = { version = "3.1", features = ["with-bytes"] }
|
||||
tokio = { version = "1.20", features = ["full"] }
|
||||
tokio-util = { version = "0.7", features = ["full"] }
|
||||
futures = "0.3"
|
||||
bytes = "1.1"
|
||||
log = "0.4"
|
||||
@ -23,6 +23,7 @@ directories-next = "2.0"
|
||||
rand = "0.8"
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
serde_with = "1.14.0"
|
||||
lazy_static = "1.4"
|
||||
confy = { git = "https://github.com/open-trade/confy" }
|
||||
dirs-next = "2.0"
|
||||
@ -33,12 +34,13 @@ tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
mac_address = "1.1"
|
||||
machine-uid = "0.2"
|
||||
|
||||
[features]
|
||||
quic = []
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen-pure = "3.0.0-alpha.2"
|
||||
protobuf-codegen = { version = "3.1" }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
fn main() {
|
||||
std::fs::create_dir_all("src/protos").unwrap();
|
||||
protobuf_codegen_pure::Codegen::new()
|
||||
protobuf_codegen::Codegen::new()
|
||||
.pure()
|
||||
.out_dir("src/protos")
|
||||
.inputs(&["protos/rendezvous.proto", "protos/message.proto"])
|
||||
.include("protos")
|
||||
|
||||
@ -63,6 +63,7 @@ message LoginRequest {
|
||||
PortForward port_forward = 8;
|
||||
}
|
||||
bool video_ack_required = 9;
|
||||
uint64 session_id = 10;
|
||||
}
|
||||
|
||||
message ChatMessage { string text = 1; }
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
use crate::log;
|
||||
use crate::{
|
||||
log,
|
||||
password_security::config::{
|
||||
decrypt_str_or_original, decrypt_vec_or_original, encrypt_str_or_original,
|
||||
encrypt_vec_or_original,
|
||||
},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use directories_next::ProjectDirs;
|
||||
use rand::Rng;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
@ -17,6 +24,7 @@ pub const CONNECT_TIMEOUT: u64 = 18_000;
|
||||
pub const REG_INTERVAL: i64 = 12_000;
|
||||
pub const COMPRESS_LEVEL: i32 = 3;
|
||||
const SERIAL: i32 = 3;
|
||||
const PASSWORD_ENC_VERSION: &'static str = "00";
|
||||
// 128x128
|
||||
#[cfg(target_os = "macos")] // 128x128 on 160x160 canvas, then shrink to 128, mac looks better with padding
|
||||
pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAyVBMVEUAAAAAcf8Acf8Acf8Acv8Acf8Acf8Acf8Acf8AcP8Acf8Ab/8AcP8Acf////8AaP/z+f/o8v/k7v/5/v/T5f8AYP/u9v/X6f+hx/+Kuv95pP8Aef/B1/+TwP9xoP8BdP/g6P+Irv9ZmP8Bgf/E3f98q/9sn/+01f+Es/9nm/9Jif8hhv8off/M4P+syP+avP86iP/c7f+xy/9yqf9Om/9hk/9Rjv+60P99tv9fpf88lv8yjf8Tgf8deP+kvP8BiP8NeP8hkP80gP8oj2VLAAAADXRSTlMA7o7qLvnaxZ1FOxYPjH9HWgAABHJJREFUeNrtm+tW4jAQgBfwuu7MtIUWsOUiCCioIIgLiqvr+z/UHq/LJKVkmwTcc/r9E2nzlU4mSTP9lpGRkZGR8VX5cZjfL+yCEXYL+/nDH//U/Pd8DgyTy39Xbv7oIAcWyB0cqbW/sweW2NtRaj8H1sgpGOwUIAH7Bkd7YJW9dXFwAJY5WNP/cmCZQnJvzIN18on5LwfWySXlxEPYAIcad8D6PdiHDbCfIFCADVBIENiFDbCbIACKPPXrZ+cP8E6/0znvP4EymgIEravIRcTxu8HxNSJ60a8W0AYECKrlAN+YwAthCd9wm1Ug6wKzIn5SgRduXfwkqDasCjx0XFzi9PV6zwNcIuhcWBOg+ikySq8C9UD4dEKWBCoOcspvAuLHTo9sCDQiFPHotRM48j8G5gVur1FdAN2uaYEuiz7xFsgEJ2RUoMUakXuBTHHoGxQYOBhHjeUBAefEnMAowFhaLBOKuOemBBbxLRQrH2PBCgMvNCPQGMeevTb9zLrPxz2Mo+QbEaijzPUcOOHMQZkKGRAIPem39+bypREMPTkQW/oCfk866zAkiIFG4yIKRE/aAnfiSd0WrORY6pFdXQEqi9mvAQm0RIOSnoCcZ8vJoz3diCnjRk+g8VP4/fuQDJ2Lxr6WwG0gXs9aTpDzW0vgDBlVUpixR8gYk44AD8FrUKHr8JQJGgIDnoDqoALxmWPQSi9AVVzm8gKUuEPGr/QCvptwJkbSYT/TC4S8C96DGjTj86aHtAI0x2WaBIq0eSYYpRa4EsdWVVwWu9O0Aj6f6dyBMnwEraeOgSYu0wZlauzA47QCbT7DgAQSE+hZWoEBF/BBmWOewNMK3BsSqKUW4MGcWqCSVmDkbvkXGKQOwg6PAUO9oL3xXhA20yaiCjuwYygRVQlUOTWTCf2SuNJTxeFjgaHByGuAIvd8ItdPLTDhS7IuqEE1YSKVOgbayLhSFQhMzYh8hwfBs1r7c505YVIQYEdNoKwxK06MJiyrpUFHiF0NAfCQUVHoiRclIXJIR6C2fqG37pBHvcWpgwzvAtYwkR5UGV2e42UISdBJETl3mg8ouo54Rcnti1/vaT+iuUQBt500Cgo4U10BeHSkk57FB0JjWkKRMWgLUA0lLodtImAQdaMiiri3+gIAPZQoutHNsgKF1aaDMhMyIdBf8Th+Bh8MTjGWCpl5Wv43tDmnF+IUVMrcZgRoiAxhtrloYizNkZaAnF5leglbNhj0wYCAbCDvGb0mP4nib7O7ZlcYQ2m1gPtIZgVgGNNMeaVAaWR+57TrqgtUnm3sHQ+kYeE6fufUubG1ez50FXbPnWgBlgSABmN3TTcsRl2yWkHRrwbiunvk/W2+Mg1hPZplPDeXRbZzStFH15s1QIVd3UImP5z/bHpeeQLvRJ7XLFUffQIlCvqlXETQbgN9/rlYABGosv+Vi9m2Xs639YLGrZd0br+odetlvdsvbN56abfd4vbCzv9Q3v/ygoOV21A4OPpfXvH4Ai+5ZGRkZGRkbJA/t/I0QMzoMiEAAAAASUVORK5CYII=
|
||||
@ -114,7 +122,7 @@ pub struct Config2 {
|
||||
pub options: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct PeerConfig {
|
||||
#[serde(default)]
|
||||
pub password: Vec<u8>,
|
||||
@ -168,7 +176,7 @@ pub struct PeerInfoSerde {
|
||||
pub platform: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct TransferSerde {
|
||||
#[serde(default)]
|
||||
pub write_jobs: Vec<String>,
|
||||
@ -207,7 +215,16 @@ fn patch(path: PathBuf) -> PathBuf {
|
||||
|
||||
impl Config2 {
|
||||
fn load() -> Config2 {
|
||||
Config::load_::<Config2>("2")
|
||||
let mut config = Config::load_::<Config2>("2");
|
||||
if let Some(mut socks) = config.socks {
|
||||
let (password, store) = decrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION);
|
||||
socks.password = password;
|
||||
config.socks = Some(socks);
|
||||
if store {
|
||||
config.store();
|
||||
}
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
pub fn file() -> PathBuf {
|
||||
@ -215,7 +232,12 @@ impl Config2 {
|
||||
}
|
||||
|
||||
fn store(&self) {
|
||||
Config::store_(self, "2");
|
||||
let mut config = self.clone();
|
||||
if let Some(mut socks) = config.socks {
|
||||
socks.password = encrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION);
|
||||
config.socks = Some(socks);
|
||||
}
|
||||
Config::store_(&config, "2");
|
||||
}
|
||||
|
||||
pub fn get() -> Config2 {
|
||||
@ -267,11 +289,19 @@ impl Config {
|
||||
}
|
||||
|
||||
fn load() -> Config {
|
||||
Config::load_::<Config>("")
|
||||
let mut config = Config::load_::<Config>("");
|
||||
let (password, store) = decrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
config.password = password;
|
||||
if store {
|
||||
config.store();
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
fn store(&self) {
|
||||
Config::store_(self, "");
|
||||
let mut config = self.clone();
|
||||
config.password = encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
Config::store_(&config, "");
|
||||
}
|
||||
|
||||
pub fn file() -> PathBuf {
|
||||
@ -627,7 +657,7 @@ impl Config {
|
||||
log::info!("id updated from {} to {}", id, new_id);
|
||||
}
|
||||
|
||||
pub fn set_password(password: &str) {
|
||||
pub fn set_security_password(password: &str) {
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if password == config.password {
|
||||
return;
|
||||
@ -636,13 +666,8 @@ impl Config {
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_password() -> String {
|
||||
let mut password = CONFIG.read().unwrap().password.clone();
|
||||
if password.is_empty() {
|
||||
password = Config::get_auto_password();
|
||||
Config::set_password(&password);
|
||||
}
|
||||
password
|
||||
pub fn get_security_password() -> String {
|
||||
CONFIG.read().unwrap().password.clone()
|
||||
}
|
||||
|
||||
pub fn set_salt(salt: &str) {
|
||||
@ -714,7 +739,28 @@ impl PeerConfig {
|
||||
pub fn load(id: &str) -> PeerConfig {
|
||||
let _ = CONFIG.read().unwrap(); // for lock
|
||||
match confy::load_path(&Self::path(id)) {
|
||||
Ok(config) => config,
|
||||
Ok(config) => {
|
||||
let mut config: PeerConfig = config;
|
||||
let mut store = false;
|
||||
let (password, store2) =
|
||||
decrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
config.password = password;
|
||||
store = store || store2;
|
||||
config.options.get_mut("rdp_password").map(|v| {
|
||||
let (password, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
||||
*v = password;
|
||||
store = store || store2;
|
||||
});
|
||||
config.options.get_mut("os-password").map(|v| {
|
||||
let (password, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
||||
*v = password;
|
||||
store = store || store2;
|
||||
});
|
||||
if store {
|
||||
config.store(id);
|
||||
}
|
||||
config
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to load config: {}", err);
|
||||
Default::default()
|
||||
@ -724,7 +770,17 @@ impl PeerConfig {
|
||||
|
||||
pub fn store(&self, id: &str) {
|
||||
let _ = CONFIG.read().unwrap(); // for lock
|
||||
if let Err(err) = confy::store_path(Self::path(id), self) {
|
||||
let mut config = self.clone();
|
||||
config.password = encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
config
|
||||
.options
|
||||
.get_mut("rdp_password")
|
||||
.map(|v| *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION));
|
||||
config
|
||||
.options
|
||||
.get_mut("os-password")
|
||||
.map(|v| *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION));
|
||||
if let Err(err) = confy::store_path(Self::path(id), config) {
|
||||
log::error!("Failed to store config: {}", err);
|
||||
}
|
||||
}
|
||||
@ -856,10 +912,26 @@ impl LocalConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct DiscoveryPeer {
|
||||
pub id: String,
|
||||
#[serde(with = "serde_with::rust::map_as_tuple_list")]
|
||||
pub ip_mac: HashMap<String, String>,
|
||||
pub username: String,
|
||||
pub hostname: String,
|
||||
pub platform: String,
|
||||
pub online: bool,
|
||||
}
|
||||
|
||||
impl DiscoveryPeer {
|
||||
pub fn is_same_peer(&self, other: &DiscoveryPeer) -> bool {
|
||||
self.id == other.id && self.username == other.username
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct LanPeers {
|
||||
#[serde(default)]
|
||||
pub peers: String,
|
||||
pub peers: Vec<DiscoveryPeer>,
|
||||
}
|
||||
|
||||
impl LanPeers {
|
||||
@ -874,8 +946,10 @@ impl LanPeers {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store(peers: String) {
|
||||
let f = LanPeers { peers };
|
||||
pub fn store(peers: &Vec<DiscoveryPeer>) {
|
||||
let f = LanPeers {
|
||||
peers: peers.clone(),
|
||||
};
|
||||
if let Err(err) = confy::store_path(Config::file_("_lan_peers"), f) {
|
||||
log::error!("Failed to store lan peers: {}", err);
|
||||
}
|
||||
|
||||
@ -573,7 +573,7 @@ impl TransferJob {
|
||||
log::info!("file num truncated, ignoring");
|
||||
} else {
|
||||
match r.union {
|
||||
Some(file_transfer_send_confirm_request::Union::skip(s)) => {
|
||||
Some(file_transfer_send_confirm_request::Union::Skip(s)) => {
|
||||
if s {
|
||||
log::debug!("skip file id:{}, file_num:{}", r.id, r.file_num);
|
||||
self.skip_current_file();
|
||||
@ -581,7 +581,7 @@ impl TransferJob {
|
||||
self.set_file_confirmed(true);
|
||||
}
|
||||
}
|
||||
Some(file_transfer_send_confirm_request::Union::offset_blk(_offset)) => {
|
||||
Some(file_transfer_send_confirm_request::Union::OffsetBlk(_offset)) => {
|
||||
self.set_file_confirmed(true);
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
pub mod compress;
|
||||
#[path = "./protos/message.rs"]
|
||||
pub mod message_proto;
|
||||
#[path = "./protos/rendezvous.rs"]
|
||||
pub mod rendezvous_proto;
|
||||
pub mod protos;
|
||||
pub mod platform;
|
||||
pub use protos::message as message_proto;
|
||||
pub use protos::rendezvous as rendezvous_proto;
|
||||
pub use bytes;
|
||||
use config::Config;
|
||||
pub use futures;
|
||||
pub use protobuf;
|
||||
use std::{
|
||||
@ -27,6 +28,7 @@ pub use anyhow::{self, bail};
|
||||
pub use futures_util;
|
||||
pub mod config;
|
||||
pub mod fs;
|
||||
pub use lazy_static;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use mac_address;
|
||||
pub use rand;
|
||||
@ -35,7 +37,7 @@ pub use sodiumoxide;
|
||||
pub use tokio_socks;
|
||||
pub use tokio_socks::IntoTargetAddr;
|
||||
pub use tokio_socks::TargetAddr;
|
||||
pub use lazy_static;
|
||||
pub mod password_security;
|
||||
|
||||
#[cfg(feature = "quic")]
|
||||
pub type Stream = quic::Connection;
|
||||
@ -200,6 +202,14 @@ pub fn get_modified_time(path: &std::path::Path) -> SystemTime {
|
||||
.unwrap_or(UNIX_EPOCH)
|
||||
}
|
||||
|
||||
pub fn get_uuid() -> Vec<u8> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(id) = machine_uid::get() {
|
||||
return id.into();
|
||||
}
|
||||
Config::get_key_pair().1
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
330
libs/hbb_common/src/password_security.rs
Normal file
330
libs/hbb_common/src/password_security.rs
Normal file
@ -0,0 +1,330 @@
|
||||
pub mod password {
|
||||
use crate::config::Config;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
str::FromStr,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref RANDOM_PASSWORD:Arc<RwLock<String>> = Arc::new(RwLock::new(Config::get_auto_password()));
|
||||
}
|
||||
|
||||
const SECURITY_ENABLED: &'static str = "security-password-enabled";
|
||||
const RANDOM_ENABLED: &'static str = "random-password-enabled";
|
||||
const ONETIME_ENABLED: &'static str = "onetime-password-enabled";
|
||||
const ONETIME_ACTIVATED: &'static str = "onetime-password-activated";
|
||||
const UPDATE_METHOD: &'static str = "random-password-update-method";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum UpdateMethod {
|
||||
KEEP,
|
||||
UPDATE,
|
||||
DISABLE,
|
||||
}
|
||||
|
||||
impl FromStr for UpdateMethod {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == "KEEP" {
|
||||
Ok(Self::KEEP)
|
||||
} else if s == "UPDATE" {
|
||||
Ok(Self::UPDATE)
|
||||
} else if s == "DISABLE" {
|
||||
Ok(Self::DISABLE)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UpdateMethod {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UpdateMethod::KEEP => write!(f, "KEEP"),
|
||||
UpdateMethod::UPDATE => write!(f, "UPDATE"),
|
||||
UpdateMethod::DISABLE => write!(f, "DISABLE"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_random_password(password: &str) {
|
||||
*RANDOM_PASSWORD.write().unwrap() = password.to_owned();
|
||||
}
|
||||
|
||||
pub fn random_password() -> String {
|
||||
let mut password = RANDOM_PASSWORD.read().unwrap().clone();
|
||||
if password.is_empty() {
|
||||
password = Config::get_auto_password();
|
||||
set_random_password(&password);
|
||||
}
|
||||
password
|
||||
}
|
||||
|
||||
pub fn random_password_valid() -> bool {
|
||||
if random_enabled() {
|
||||
onetime_password_activated() || !onetime_password_enabled()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn passwords() -> Vec<String> {
|
||||
let mut v = vec![];
|
||||
if random_password_valid() {
|
||||
v.push(random_password());
|
||||
}
|
||||
if security_enabled() {
|
||||
v.push(Config::get_security_password());
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn after_session(authorized: bool) {
|
||||
if authorized && random_enabled() {
|
||||
UpdateMethod::from_str(&update_method())
|
||||
.map(|method| match method {
|
||||
UpdateMethod::KEEP => {}
|
||||
UpdateMethod::UPDATE => set_random_password(&Config::get_auto_password()),
|
||||
UpdateMethod::DISABLE => set_random_enabled(false),
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_method() -> String {
|
||||
let mut method = Config::get_option(UPDATE_METHOD);
|
||||
if UpdateMethod::from_str(&method).is_err() {
|
||||
method = UpdateMethod::KEEP.to_string(); // default is keep
|
||||
set_update_method(&method);
|
||||
}
|
||||
method
|
||||
}
|
||||
|
||||
pub fn set_update_method(method: &str) {
|
||||
Config::set_option(UPDATE_METHOD.to_owned(), method.to_owned());
|
||||
}
|
||||
|
||||
pub fn random_enabled() -> bool {
|
||||
str2bool(RANDOM_ENABLED, true, || {
|
||||
set_onetime_password_activated(false);
|
||||
set_random_password(&Config::get_auto_password());
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_random_enabled(enabled: bool) {
|
||||
if enabled != random_enabled() {
|
||||
Config::set_option(RANDOM_ENABLED.to_owned(), bool2str(enabled));
|
||||
set_onetime_password_activated(false);
|
||||
if enabled {
|
||||
set_random_password(&Config::get_auto_password());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn security_enabled() -> bool {
|
||||
str2bool(SECURITY_ENABLED, true, || {})
|
||||
}
|
||||
|
||||
pub fn set_security_enabled(enabled: bool) {
|
||||
if enabled != security_enabled() {
|
||||
Config::set_option(SECURITY_ENABLED.to_owned(), bool2str(enabled));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onetime_password_enabled() -> bool {
|
||||
str2bool(ONETIME_ENABLED, false, || {
|
||||
set_onetime_password_activated(false);
|
||||
set_random_password(&Config::get_auto_password());
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_onetime_password_enabled(enabled: bool) {
|
||||
if enabled != onetime_password_enabled() {
|
||||
Config::set_option(ONETIME_ENABLED.to_owned(), bool2str(enabled));
|
||||
set_onetime_password_activated(false);
|
||||
set_random_password(&Config::get_auto_password());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onetime_password_activated() -> bool {
|
||||
str2bool(ONETIME_ACTIVATED, false, || {})
|
||||
}
|
||||
|
||||
pub fn set_onetime_password_activated(activated: bool) {
|
||||
if activated != onetime_password_activated() {
|
||||
Config::set_option(ONETIME_ACTIVATED.to_owned(), bool2str(activated));
|
||||
if activated {
|
||||
set_random_password(&Config::get_auto_password());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notice: Function nesting
|
||||
fn str2bool(key: &str, default: bool, default_set: impl Fn()) -> bool {
|
||||
let option = Config::get_option(key);
|
||||
if option == "Y" {
|
||||
true
|
||||
} else if option == "N" {
|
||||
false
|
||||
} else {
|
||||
Config::set_option(key.to_owned(), bool2str(default));
|
||||
default_set();
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
fn bool2str(option: bool) -> String {
|
||||
if option { "Y" } else { "N" }.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod config {
|
||||
use super::base64::decrypt as decrypt00;
|
||||
use super::base64::encrypt as encrypt00;
|
||||
|
||||
const VERSION_LEN: usize = 2;
|
||||
|
||||
pub fn encrypt_str_or_original(s: &str, version: &str) -> String {
|
||||
if version.len() == VERSION_LEN {
|
||||
if version == "00" {
|
||||
if let Ok(s) = encrypt00(s.as_bytes()) {
|
||||
return version.to_owned() + &s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.to_owned()
|
||||
}
|
||||
|
||||
// bool: whether should store to re-encrypt when load
|
||||
pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool) {
|
||||
if s.len() > VERSION_LEN {
|
||||
let version = &s[..VERSION_LEN];
|
||||
if version == "00" {
|
||||
if let Ok(v) = decrypt00(&s[VERSION_LEN..].as_bytes()) {
|
||||
return (
|
||||
String::from_utf8_lossy(&v).to_string(),
|
||||
version != current_version,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(s.to_owned(), !s.is_empty())
|
||||
}
|
||||
|
||||
pub fn encrypt_vec_or_original(v: &[u8], version: &str) -> Vec<u8> {
|
||||
if version.len() == VERSION_LEN {
|
||||
if version == "00" {
|
||||
if let Ok(s) = encrypt00(v) {
|
||||
let mut version = version.to_owned().into_bytes();
|
||||
version.append(&mut s.into_bytes());
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.to_owned()
|
||||
}
|
||||
|
||||
// bool: whether should store to re-encrypt when load
|
||||
pub fn decrypt_vec_or_original(v: &[u8], current_version: &str) -> (Vec<u8>, bool) {
|
||||
if v.len() > VERSION_LEN {
|
||||
let version = String::from_utf8_lossy(&v[..VERSION_LEN]);
|
||||
if version == "00" {
|
||||
if let Ok(v) = decrypt00(&v[VERSION_LEN..]) {
|
||||
return (v, version != current_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(v.to_owned(), !v.is_empty())
|
||||
}
|
||||
|
||||
mod test {
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::password_security::config::*;
|
||||
|
||||
println!("test str");
|
||||
let data = "Hello World";
|
||||
let encrypted = encrypt_str_or_original(data, "00");
|
||||
let (decrypted, store) = decrypt_str_or_original(&encrypted, "00");
|
||||
println!("data: {}", data);
|
||||
println!("encrypted: {}", encrypted);
|
||||
println!("decrypted: {}", decrypted);
|
||||
assert_eq!(data, decrypted);
|
||||
assert_eq!("00", &encrypted[..2]);
|
||||
assert_eq!(store, false);
|
||||
let (_, store2) = decrypt_str_or_original(&encrypted, "01");
|
||||
assert_eq!(store2, true);
|
||||
|
||||
println!("test vec");
|
||||
let data: Vec<u8> = vec![1, 2, 3, 4];
|
||||
let encrypted = encrypt_vec_or_original(&data, "00");
|
||||
let (decrypted, store) = decrypt_vec_or_original(&encrypted, "00");
|
||||
println!("data: {:?}", data);
|
||||
println!("encrypted: {:?}", encrypted);
|
||||
println!("decrypted: {:?}", decrypted);
|
||||
assert_eq!(data, decrypted);
|
||||
assert_eq!("00".as_bytes(), &encrypted[..2]);
|
||||
assert_eq!(store, false);
|
||||
let (_, store2) = decrypt_vec_or_original(&encrypted, "01");
|
||||
assert_eq!(store2, true);
|
||||
|
||||
println!("test old");
|
||||
let data = "00Hello World";
|
||||
let (decrypted, store) = decrypt_str_or_original(&data, "00");
|
||||
assert_eq!(data, decrypted);
|
||||
assert_eq!(store, true);
|
||||
let data: Vec<u8> = vec!['0' as u8, '0' as u8, 1, 2, 3, 4];
|
||||
let (decrypted, store) = decrypt_vec_or_original(&data, "00");
|
||||
assert_eq!(data, decrypted);
|
||||
assert_eq!(store, true);
|
||||
let (_, store) = decrypt_str_or_original("", "00");
|
||||
assert_eq!(store, false);
|
||||
let (_, store) = decrypt_vec_or_original(&vec![], "00");
|
||||
assert_eq!(store, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod base64 {
|
||||
use super::symmetric_crypt;
|
||||
use sodiumoxide::base64;
|
||||
|
||||
pub fn encrypt(v: &[u8]) -> Result<String, ()> {
|
||||
if v.len() > 0 {
|
||||
symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt(v: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
if v.len() > 0 {
|
||||
base64::decode(v, base64::Variant::Original).and_then(|v| symmetric_crypt(&v, false))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result<Vec<u8>, ()> {
|
||||
use sodiumoxide::crypto::secretbox;
|
||||
use std::convert::TryInto;
|
||||
|
||||
let mut keybuf = crate::get_uuid();
|
||||
keybuf.resize(secretbox::KEYBYTES, 0);
|
||||
let key = secretbox::Key(keybuf.try_into().map_err(|_| ())?);
|
||||
let nonce = secretbox::Nonce([0; secretbox::NONCEBYTES]);
|
||||
|
||||
if encrypt {
|
||||
Ok(secretbox::seal(data, &nonce, &key))
|
||||
} else {
|
||||
secretbox::open(data, &nonce, &key)
|
||||
}
|
||||
}
|
||||
102
libs/hbb_common/src/platform/linux.rs
Normal file
102
libs/hbb_common/src/platform/linux.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use crate::ResultType;
|
||||
|
||||
pub fn get_display_server() -> String {
|
||||
let session = get_value_of_seat0(0);
|
||||
get_display_server_of_session(&session)
|
||||
}
|
||||
|
||||
fn get_display_server_of_session(session: &str) -> String {
|
||||
if let Ok(output) = std::process::Command::new("loginctl")
|
||||
.args(vec!["show-session", "-p", "Type", session])
|
||||
.output()
|
||||
// Check session type of the session
|
||||
{
|
||||
let display_server = String::from_utf8_lossy(&output.stdout)
|
||||
.replace("Type=", "")
|
||||
.trim_end()
|
||||
.into();
|
||||
if display_server == "tty" {
|
||||
// If the type is tty...
|
||||
if let Ok(output) = std::process::Command::new("loginctl")
|
||||
.args(vec!["show-session", "-p", "TTY", session])
|
||||
.output()
|
||||
// Get the tty number
|
||||
{
|
||||
let tty: String = String::from_utf8_lossy(&output.stdout)
|
||||
.replace("TTY=", "")
|
||||
.trim_end()
|
||||
.into();
|
||||
if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty))
|
||||
// And check if Xorg is running on that tty
|
||||
{
|
||||
if xorg_results.trim_end().to_string() != "" {
|
||||
// If it is, manually return "x11", otherwise return tty
|
||||
"x11".to_owned()
|
||||
} else {
|
||||
display_server
|
||||
}
|
||||
} else {
|
||||
// If any of these commands fail just fall back to the display server
|
||||
display_server
|
||||
}
|
||||
} else {
|
||||
display_server
|
||||
}
|
||||
} else {
|
||||
// If the session is not a tty, then just return the type as usual
|
||||
display_server
|
||||
}
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_value_of_seat0(i: usize) -> String {
|
||||
if let Ok(output) = std::process::Command::new("loginctl").output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
if line.contains("seat0") {
|
||||
if let Some(sid) = line.split_whitespace().nth(0) {
|
||||
if is_active(sid) {
|
||||
if let Some(uid) = line.split_whitespace().nth(i) {
|
||||
return uid.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73
|
||||
if let Ok(output) = std::process::Command::new("loginctl").output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
if let Some(sid) = line.split_whitespace().nth(0) {
|
||||
let d = get_display_server_of_session(sid);
|
||||
if is_active(sid) && d != "tty" {
|
||||
if let Some(uid) = line.split_whitespace().nth(i) {
|
||||
return uid.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
fn is_active(sid: &str) -> bool {
|
||||
if let Ok(output) = std::process::Command::new("loginctl")
|
||||
.args(vec!["show-session", "-p", "State", sid])
|
||||
.output()
|
||||
{
|
||||
String::from_utf8_lossy(&output.stdout).contains("active")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_cmds(cmds: String) -> ResultType<String> {
|
||||
let output = std::process::Command::new("sh")
|
||||
.args(vec!["-c", &cmds])
|
||||
.output()?;
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
2
libs/hbb_common/src/platform/mod.rs
Normal file
2
libs/hbb_common/src/platform/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
@ -27,6 +27,8 @@ fn new_socket(addr: SocketAddr, reuse: bool, buf_size: usize) -> Result<Socket,
|
||||
socket.set_reuse_port(true)?;
|
||||
socket.set_reuse_address(true)?;
|
||||
}
|
||||
// only nonblocking work with tokio, https://stackoverflow.com/questions/64649405/receiver-on-tokiompscchannel-only-receives-messages-when-buffer-is-full
|
||||
socket.set_nonblocking(true)?;
|
||||
if buf_size > 0 {
|
||||
socket.set_recv_buffer_size(buf_size).ok();
|
||||
}
|
||||
|
||||
@ -251,11 +251,11 @@ impl Decoder {
|
||||
rgb: &mut Vec<u8>,
|
||||
) -> ResultType<bool> {
|
||||
match frame {
|
||||
video_frame::Union::vp9s(vp9s) => {
|
||||
video_frame::Union::Vp9s(vp9s) => {
|
||||
Decoder::handle_vp9s_video_frame(&mut self.vpx, vp9s, rgb)
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
video_frame::Union::h264s(h264s) => {
|
||||
video_frame::Union::H264s(h264s) => {
|
||||
if let Some(decoder) = &mut self.hw.h264 {
|
||||
Decoder::handle_hw_video_frame(decoder, h264s, rgb, &mut self.i420)
|
||||
} else {
|
||||
@ -263,7 +263,7 @@ impl Decoder {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
video_frame::Union::h265s(h265s) => {
|
||||
video_frame::Union::H265s(h265s) => {
|
||||
if let Some(decoder) = &mut self.hw.h265 {
|
||||
Decoder::handle_hw_video_frame(decoder, h265s, rgb, &mut self.i420)
|
||||
} else {
|
||||
|
||||
@ -21,6 +21,10 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.inner.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
pub fn is_gdi(&self) -> bool {
|
||||
self.inner.is_gdi()
|
||||
}
|
||||
@ -41,8 +45,8 @@ impl Capturer {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.inner.frame(timeout_ms.as_millis() as _) {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.inner.frame(timeout.as_millis() as _) {
|
||||
Ok(frame) => Ok(Frame(frame)),
|
||||
Err(ref error) if error.kind() == TimedOut => Err(WouldBlock.into()),
|
||||
Err(error) => Err(error),
|
||||
@ -129,6 +133,11 @@ impl CapturerMag {
|
||||
data: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.inner.set_use_yuv(use_yuv)
|
||||
}
|
||||
|
||||
pub fn exclude(&mut self, cls: &str, name: &str) -> io::Result<bool> {
|
||||
self.inner.exclude(cls, name)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::common::{
|
||||
wayland,
|
||||
x11::{self, Frame},
|
||||
};
|
||||
use std::io;
|
||||
use std::{io, time::Duration};
|
||||
|
||||
pub enum Capturer {
|
||||
X11(x11::Capturer),
|
||||
@ -17,6 +17,13 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
match self {
|
||||
Capturer::X11(d) => d.set_use_yuv(use_yuv),
|
||||
Capturer::WAYLAND(d) => d.set_use_yuv(use_yuv),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
match self {
|
||||
Capturer::X11(d) => d.width(),
|
||||
@ -31,10 +38,10 @@ impl Capturer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self {
|
||||
Capturer::X11(d) => d.frame(timeout_ms),
|
||||
Capturer::WAYLAND(d) => d.frame(timeout_ms),
|
||||
Capturer::X11(d) => d.frame(timeout),
|
||||
Capturer::WAYLAND(d) => d.frame(timeout),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,31 +52,30 @@ pub enum Display {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_wayland() -> bool {
|
||||
std::env::var("IS_WAYLAND").is_ok()
|
||||
|| std::env::var("XDG_SESSION_TYPE") == Ok("wayland".to_owned())
|
||||
pub fn is_x11() -> bool {
|
||||
"x11" == hbb_common::platform::linux::get_display_server()
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn primary() -> io::Result<Display> {
|
||||
Ok(if is_wayland() {
|
||||
Display::WAYLAND(wayland::Display::primary()?)
|
||||
} else {
|
||||
Ok(if is_x11() {
|
||||
Display::X11(x11::Display::primary()?)
|
||||
} else {
|
||||
Display::WAYLAND(wayland::Display::primary()?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Display>> {
|
||||
Ok(if is_wayland() {
|
||||
wayland::Display::all()?
|
||||
.drain(..)
|
||||
.map(|x| Display::WAYLAND(x))
|
||||
.collect()
|
||||
} else {
|
||||
Ok(if is_x11() {
|
||||
x11::Display::all()?
|
||||
.drain(..)
|
||||
.map(|x| Display::X11(x))
|
||||
.collect()
|
||||
} else {
|
||||
wayland::Display::all()?
|
||||
.drain(..)
|
||||
.map(|x| Display::WAYLAND(x))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ cfg_if! {
|
||||
mod wayland;
|
||||
mod x11;
|
||||
pub use self::linux::*;
|
||||
pub use self::x11::Frame;
|
||||
} else {
|
||||
mod x11;
|
||||
pub use self::x11::*;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::common::x11::Frame;
|
||||
use crate::wayland::{capturable::*, *};
|
||||
use std::io;
|
||||
use std::{io, time::Duration};
|
||||
|
||||
pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>);
|
||||
|
||||
@ -14,6 +14,10 @@ impl Capturer {
|
||||
Ok(Capturer(display, r, yuv, Default::default()))
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.2 = use_yuv;
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.width()
|
||||
}
|
||||
@ -22,8 +26,8 @@ impl Capturer {
|
||||
self.0.height()
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
match self.1.capture(timeout_ms as _).map_err(map_err)? {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.1.capture(timeout.as_millis() as _).map_err(map_err)? {
|
||||
PixelProvider::BGR0(w, h, x) => Ok(Frame(if self.2 {
|
||||
crate::common::bgra_to_i420(w as _, h as _, &x, &mut self.3);
|
||||
&self.3[..]
|
||||
|
||||
@ -8,6 +8,10 @@ impl Capturer {
|
||||
x11::Capturer::new(display.0, yuv).map(Capturer)
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.0.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.display().rect().w as usize
|
||||
}
|
||||
|
||||
@ -446,6 +446,10 @@ impl CapturerMag {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub(crate) fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
|
||||
let name_c = CString::new(name).unwrap();
|
||||
unsafe {
|
||||
|
||||
@ -156,6 +156,10 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub fn is_gdi(&self) -> bool {
|
||||
self.gdi_capturer.is_some()
|
||||
}
|
||||
|
||||
@ -74,6 +74,10 @@ impl Capturer {
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &Display {
|
||||
&self.display
|
||||
}
|
||||
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.62.0"
|
||||
@ -6,7 +6,7 @@ After=systemd-user-sessions.service
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/rustdesk --service
|
||||
PIDFile=/var/run/rustdesk.pid
|
||||
PIDFile=/run/rustdesk.pid
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=30
|
||||
User=root
|
||||
|
||||
15
rustdesk.service.user
Normal file
15
rustdesk.service.user
Normal file
@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=RustDesk user service (--server)
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/rustdesk --server
|
||||
PIDFile=/run/rustdesk.user.pid
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=30
|
||||
LimitNOFILE=100000
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -28,6 +28,7 @@ use hbb_common::{
|
||||
log,
|
||||
message_proto::{option_message::BoolOption, *},
|
||||
protobuf::Message as _,
|
||||
rand,
|
||||
rendezvous_proto::*,
|
||||
socket_client,
|
||||
sodiumoxide::crypto::{box_, secretbox, sign},
|
||||
@ -148,11 +149,25 @@ impl Client {
|
||||
true,
|
||||
));
|
||||
}
|
||||
let rendezvous_server = crate::get_rendezvous_server(1_000).await;
|
||||
log::info!("rendezvous server: {}", rendezvous_server);
|
||||
|
||||
let (mut rendezvous_server, servers, contained) = crate::get_rendezvous_server(1_000).await;
|
||||
let mut socket =
|
||||
socket_client::connect_tcp(&*rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT).await?;
|
||||
socket_client::connect_tcp(&*rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT).await;
|
||||
debug_assert!(!servers.contains(&rendezvous_server));
|
||||
if socket.is_err() && !servers.is_empty() {
|
||||
log::info!("try the other servers: {:?}", servers);
|
||||
for server in servers {
|
||||
socket = socket_client::connect_tcp(&*server, any_addr, RENDEZVOUS_TIMEOUT).await;
|
||||
if socket.is_ok() {
|
||||
rendezvous_server = server;
|
||||
break;
|
||||
}
|
||||
}
|
||||
crate::refresh_rendezvous_server();
|
||||
} else if !contained {
|
||||
crate::refresh_rendezvous_server();
|
||||
}
|
||||
log::info!("rendezvous server: {}", rendezvous_server);
|
||||
let mut socket = socket?;
|
||||
let my_addr = socket.local_addr();
|
||||
let mut signed_id_pk = Vec::new();
|
||||
let mut relay_server = "".to_owned();
|
||||
@ -165,7 +180,7 @@ impl Client {
|
||||
for i in 1..=3 {
|
||||
log::info!("#{} punch attempt with {}, id: {}", i, my_addr, peer);
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
use hbb_common::protobuf::ProtobufEnum;
|
||||
use hbb_common::protobuf::Enum;
|
||||
let nat_type = NatType::from_i32(my_nat_type).unwrap_or(NatType::UNKNOWN_NAT);
|
||||
msg_out.set_punch_hole_request(PunchHoleRequest {
|
||||
id: peer.to_owned(),
|
||||
@ -179,7 +194,7 @@ impl Client {
|
||||
if let Some(Ok(bytes)) = socket.next_timeout(i * 6000).await {
|
||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::punch_hole_response(ph)) => {
|
||||
Some(rendezvous_message::Union::PunchHoleResponse(ph)) => {
|
||||
if ph.socket_addr.is_empty() {
|
||||
if !ph.other_failure.is_empty() {
|
||||
bail!(ph.other_failure);
|
||||
@ -199,8 +214,8 @@ impl Client {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
peer_nat_type = ph.get_nat_type();
|
||||
is_local = ph.get_is_local();
|
||||
peer_nat_type = ph.nat_type();
|
||||
is_local = ph.is_local();
|
||||
signed_id_pk = ph.pk;
|
||||
relay_server = ph.relay_server;
|
||||
peer_addr = AddrMangle::decode(&ph.socket_addr);
|
||||
@ -208,13 +223,13 @@ impl Client {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(rendezvous_message::Union::relay_response(rr)) => {
|
||||
Some(rendezvous_message::Union::RelayResponse(rr)) => {
|
||||
log::info!(
|
||||
"relay requested from peer, time used: {:?}, relay_server: {}",
|
||||
start.elapsed(),
|
||||
rr.relay_server
|
||||
);
|
||||
signed_id_pk = rr.get_pk().into();
|
||||
signed_id_pk = rr.pk().into();
|
||||
let mut conn =
|
||||
Self::create_relay(peer, rr.uuid, rr.relay_server, key, conn_type)
|
||||
.await?;
|
||||
@ -383,7 +398,7 @@ impl Client {
|
||||
Some(res) => {
|
||||
let bytes = res?;
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||
if let Some(message::Union::signed_id(si)) = msg_in.union {
|
||||
if let Some(message::Union::SignedId(si)) = msg_in.union {
|
||||
if let Ok((id, their_pk_b)) = decode_id_pk(&si.id, &sign_pk) {
|
||||
if id == peer_id {
|
||||
let their_pk_b = box_::PublicKey(their_pk_b);
|
||||
@ -466,7 +481,7 @@ impl Client {
|
||||
socket.send(&msg_out).await?;
|
||||
if let Some(Ok(bytes)) = socket.next_timeout(CONNECT_TIMEOUT).await {
|
||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||
if let Some(rendezvous_message::Union::relay_response(rs)) = msg_in.union {
|
||||
if let Some(rendezvous_message::Union::RelayResponse(rs)) = msg_in.union {
|
||||
if !rs.refuse_reason.is_empty() {
|
||||
bail!(rs.refuse_reason);
|
||||
}
|
||||
@ -768,6 +783,7 @@ pub struct LoginConfigHandler {
|
||||
pub version: i64,
|
||||
pub conn_id: i32,
|
||||
features: Option<Features>,
|
||||
session_id: u64,
|
||||
}
|
||||
|
||||
impl Deref for LoginConfigHandler {
|
||||
@ -791,6 +807,7 @@ impl LoginConfigHandler {
|
||||
let config = self.load_config();
|
||||
self.remember = !config.password.is_empty();
|
||||
self.config = config;
|
||||
self.session_id = rand::random();
|
||||
}
|
||||
|
||||
pub fn should_auto_login(&self) -> String {
|
||||
@ -1126,6 +1143,7 @@ impl LoginConfigHandler {
|
||||
my_id,
|
||||
my_name: crate::username(),
|
||||
option: self.get_option_message(true).into(),
|
||||
session_id: self.session_id,
|
||||
..Default::default()
|
||||
};
|
||||
if self.is_file_transfer {
|
||||
|
||||
@ -69,9 +69,9 @@ pub enum CodecFormat {
|
||||
impl From<&VideoFrame> for CodecFormat {
|
||||
fn from(it: &VideoFrame) -> Self {
|
||||
match it.union {
|
||||
Some(video_frame::Union::vp9s(_)) => CodecFormat::VP9,
|
||||
Some(video_frame::Union::h264s(_)) => CodecFormat::H264,
|
||||
Some(video_frame::Union::h265s(_)) => CodecFormat::H265,
|
||||
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
|
||||
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
|
||||
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
|
||||
_ => CodecFormat::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,8 +17,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
});
|
||||
}
|
||||
Message {
|
||||
union: Some(message::Union::cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::format_list(CliprdrServerFormatList {
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::FormatList(CliprdrServerFormatList {
|
||||
conn_id,
|
||||
formats,
|
||||
..Default::default()
|
||||
@ -29,8 +29,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
}
|
||||
}
|
||||
ClipbaordFile::ServerFormatListResponse { conn_id, msg_flags } => Message {
|
||||
union: Some(message::Union::cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::format_list_response(
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::FormatListResponse(
|
||||
CliprdrServerFormatListResponse {
|
||||
conn_id,
|
||||
msg_flags,
|
||||
@ -45,8 +45,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
conn_id,
|
||||
requested_format_id,
|
||||
} => Message {
|
||||
union: Some(message::Union::cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::format_data_request(
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::FormatDataRequest(
|
||||
CliprdrServerFormatDataRequest {
|
||||
conn_id,
|
||||
requested_format_id,
|
||||
@ -62,8 +62,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
msg_flags,
|
||||
format_data,
|
||||
} => Message {
|
||||
union: Some(message::Union::cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::format_data_response(
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::FormatDataResponse(
|
||||
CliprdrServerFormatDataResponse {
|
||||
conn_id,
|
||||
msg_flags,
|
||||
@ -86,8 +86,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
have_clip_data_id,
|
||||
clip_data_id,
|
||||
} => Message {
|
||||
union: Some(message::Union::cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::file_contents_request(
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::FileContentsRequest(
|
||||
CliprdrFileContentsRequest {
|
||||
conn_id,
|
||||
stream_id,
|
||||
@ -111,8 +111,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
stream_id,
|
||||
requested_data,
|
||||
} => Message {
|
||||
union: Some(message::Union::cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::file_contents_response(
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::FileContentsResponse(
|
||||
CliprdrFileContentsResponse {
|
||||
conn_id,
|
||||
msg_flags,
|
||||
@ -130,7 +130,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
|
||||
pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
|
||||
match msg.union {
|
||||
Some(cliprdr::Union::format_list(data)) => {
|
||||
Some(cliprdr::Union::FormatList(data)) => {
|
||||
let mut format_list: Vec<(i32, String)> = Vec::new();
|
||||
for v in data.formats.iter() {
|
||||
format_list.push((v.id, v.format.clone()));
|
||||
@ -140,26 +140,26 @@ pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
|
||||
format_list,
|
||||
})
|
||||
}
|
||||
Some(cliprdr::Union::format_list_response(data)) => {
|
||||
Some(cliprdr::Union::FormatListResponse(data)) => {
|
||||
Some(ClipbaordFile::ServerFormatListResponse {
|
||||
conn_id: data.conn_id,
|
||||
msg_flags: data.msg_flags,
|
||||
})
|
||||
}
|
||||
Some(cliprdr::Union::format_data_request(data)) => {
|
||||
Some(cliprdr::Union::FormatDataRequest(data)) => {
|
||||
Some(ClipbaordFile::ServerFormatDataRequest {
|
||||
conn_id: data.conn_id,
|
||||
requested_format_id: data.requested_format_id,
|
||||
})
|
||||
}
|
||||
Some(cliprdr::Union::format_data_response(data)) => {
|
||||
Some(cliprdr::Union::FormatDataResponse(data)) => {
|
||||
Some(ClipbaordFile::ServerFormatDataResponse {
|
||||
conn_id: data.conn_id,
|
||||
msg_flags: data.msg_flags,
|
||||
format_data: data.format_data,
|
||||
})
|
||||
}
|
||||
Some(cliprdr::Union::file_contents_request(data)) => {
|
||||
Some(cliprdr::Union::FileContentsRequest(data)) => {
|
||||
Some(ClipbaordFile::FileContentsRequest {
|
||||
conn_id: data.conn_id,
|
||||
stream_id: data.stream_id,
|
||||
@ -172,7 +172,7 @@ pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
|
||||
clip_data_id: data.clip_data_id,
|
||||
})
|
||||
}
|
||||
Some(cliprdr::Union::file_contents_response(data)) => {
|
||||
Some(cliprdr::Union::FileContentsResponse(data)) => {
|
||||
Some(ClipbaordFile::FileContentsResponse {
|
||||
conn_id: data.conn_id,
|
||||
msg_flags: data.msg_flags,
|
||||
|
||||
@ -8,11 +8,10 @@ use hbb_common::{
|
||||
get_version_number, log,
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
protobuf::ProtobufEnum,
|
||||
protobuf::Enum,
|
||||
rendezvous_proto::*,
|
||||
sleep, socket_client, tokio, ResultType,
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@ -32,7 +31,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
#[inline]
|
||||
pub fn valid_for_numlock(evt: &KeyEvent) -> bool {
|
||||
if let Some(key_event::Union::control_key(ck)) = evt.union {
|
||||
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
|
||||
let v = ck.value();
|
||||
(v >= ControlKey::Numpad0.value() && v <= ControlKey::Numpad9.value())
|
||||
|| v == ControlKey::Decimal.value()
|
||||
@ -247,7 +246,7 @@ async fn test_nat_type_() -> ResultType<bool> {
|
||||
return Ok(true);
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
let rendezvous_server = get_rendezvous_server(1_000).await;
|
||||
let (rendezvous_server, _, _) = get_rendezvous_server(1_000).await;
|
||||
let server1 = rendezvous_server;
|
||||
let tmp: Vec<&str> = server1.split(":").collect();
|
||||
if tmp.len() != 2 {
|
||||
@ -284,7 +283,7 @@ async fn test_nat_type_() -> ResultType<bool> {
|
||||
socket.send(&msg_out).await?;
|
||||
if let Some(Ok(bytes)) = socket.next_timeout(RENDEZVOUS_TIMEOUT).await {
|
||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||
if let Some(rendezvous_message::Union::test_nat_response(tnr)) = msg_in.union {
|
||||
if let Some(rendezvous_message::Union::TestNatResponse(tnr)) = msg_in.union {
|
||||
if i == 0 {
|
||||
port1 = tnr.port;
|
||||
} else {
|
||||
@ -316,31 +315,62 @@ async fn test_nat_type_() -> ResultType<bool> {
|
||||
Ok(ok)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub async fn get_rendezvous_server(_ms_timeout: u64) -> String {
|
||||
Config::get_rendezvous_server()
|
||||
pub async fn get_rendezvous_server(ms_timeout: u64) -> (String, Vec<String>, bool) {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let (mut a, mut b) = get_rendezvous_server_(ms_timeout);
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let (mut a, mut b) = get_rendezvous_server_(ms_timeout).await;
|
||||
let mut b: Vec<String> = b
|
||||
.drain(..)
|
||||
.map(|x| {
|
||||
if !x.contains(":") {
|
||||
format!("{}:{}", x, config::RENDEZVOUS_PORT)
|
||||
} else {
|
||||
x
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let c = if b.contains(&a) {
|
||||
b = b.drain(..).filter(|x| x != &a).collect();
|
||||
true
|
||||
} else {
|
||||
a = b.pop().unwrap_or(a);
|
||||
false
|
||||
};
|
||||
(a, b, c)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
fn get_rendezvous_server_(_ms_timeout: u64) -> (String, Vec<String>) {
|
||||
(
|
||||
Config::get_rendezvous_server(),
|
||||
Config::get_rendezvous_servers(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub async fn get_rendezvous_server(ms_timeout: u64) -> String {
|
||||
async fn get_rendezvous_server_(ms_timeout: u64) -> (String, Vec<String>) {
|
||||
crate::ipc::get_rendezvous_server(ms_timeout).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub async fn get_nat_type(_ms_timeout: u64) -> i32 {
|
||||
Config::get_nat_type()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub async fn get_nat_type(ms_timeout: u64) -> i32 {
|
||||
crate::ipc::get_nat_type(ms_timeout).await
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn test_rendezvous_server_() {
|
||||
let servers = Config::get_rendezvous_servers();
|
||||
hbb_common::config::ONLINE.lock().unwrap().clear();
|
||||
Config::reset_online();
|
||||
let mut futs = Vec::new();
|
||||
for host in servers {
|
||||
futs.push(tokio::spawn(async move {
|
||||
@ -363,11 +393,21 @@ async fn test_rendezvous_server_() {
|
||||
join_all(futs).await;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
pub fn test_rendezvous_server() {
|
||||
std::thread::spawn(test_rendezvous_server_);
|
||||
}
|
||||
|
||||
pub fn refresh_rendezvous_server() {
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
test_rendezvous_server();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||
std::thread::spawn(|| {
|
||||
if crate::ipc::test_rendezvous_server().is_err() {
|
||||
test_rendezvous_server();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_time() -> i64 {
|
||||
std::time::SystemTime::now()
|
||||
@ -412,7 +452,7 @@ pub const POSTFIX_SERVICE: &'static str = "_service";
|
||||
|
||||
#[inline]
|
||||
pub fn is_control_key(evt: &KeyEvent, key: &ControlKey) -> bool {
|
||||
if let Some(key_event::Union::control_key(ck)) = evt.union {
|
||||
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
|
||||
ck.value() == key.value()
|
||||
} else {
|
||||
false
|
||||
@ -421,7 +461,7 @@ pub fn is_control_key(evt: &KeyEvent, key: &ControlKey) -> bool {
|
||||
|
||||
#[inline]
|
||||
pub fn is_modifier(evt: &KeyEvent) -> bool {
|
||||
if let Some(key_event::Union::control_key(ck)) = evt.union {
|
||||
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
|
||||
let v = ck.value();
|
||||
v == ControlKey::Alt.value()
|
||||
|| v == ControlKey::Shift.value()
|
||||
@ -437,14 +477,15 @@ pub fn is_modifier(evt: &KeyEvent) -> bool {
|
||||
}
|
||||
|
||||
pub fn check_software_update() {
|
||||
std::thread::spawn(move || allow_err!(_check_software_update()));
|
||||
std::thread::spawn(move || allow_err!(check_software_update_()));
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn _check_software_update() -> hbb_common::ResultType<()> {
|
||||
async fn check_software_update_() -> hbb_common::ResultType<()> {
|
||||
sleep(3.).await;
|
||||
|
||||
let rendezvous_server = socket_client::get_target_addr(&get_rendezvous_server(1_000).await)?;
|
||||
let rendezvous_server =
|
||||
socket_client::get_target_addr(&format!("rs-sg.rustdesk.com:{}", config::RENDEZVOUS_PORT))?;
|
||||
let mut socket =
|
||||
socket_client::new_udp(Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT).await?;
|
||||
|
||||
@ -457,7 +498,7 @@ async fn _check_software_update() -> hbb_common::ResultType<()> {
|
||||
use hbb_common::protobuf::Message;
|
||||
if let Some(Ok((bytes, _))) = socket.next_timeout(30_000).await {
|
||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||
if let Some(rendezvous_message::Union::software_update(su)) = msg_in.union {
|
||||
if let Some(rendezvous_message::Union::SoftwareUpdate(su)) = msg_in.union {
|
||||
let version = hbb_common::get_version_from_url(&su.url);
|
||||
if get_version_number(&version) > get_version_number(crate::VERSION) {
|
||||
*SOFTWARE_UPDATE_URL.lock().unwrap() = su.url;
|
||||
@ -496,14 +537,6 @@ pub fn is_setup(name: &str) -> bool {
|
||||
name.to_lowercase().ends_with("setdown.exe") || name.to_lowercase().ends_with("安装.exe")
|
||||
}
|
||||
|
||||
pub fn get_uuid() -> Vec<u8> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(id) = machine_uid::get() {
|
||||
return id.into();
|
||||
}
|
||||
Config::get_key_pair().1
|
||||
}
|
||||
|
||||
pub fn get_custom_rendezvous_server(custom: String) -> String {
|
||||
if !custom.is_empty() {
|
||||
return custom;
|
||||
|
||||
281
src/ipc.rs
281
src/ipc.rs
@ -7,7 +7,9 @@ use hbb_common::{
|
||||
config::{self, Config, Config2},
|
||||
futures::StreamExt as _,
|
||||
futures_util::sink::SinkExt,
|
||||
log, timeout, tokio,
|
||||
log,
|
||||
password_security::password,
|
||||
timeout, tokio,
|
||||
tokio::io::{AsyncRead, AsyncWrite},
|
||||
tokio_util::codec::Framed,
|
||||
ResultType,
|
||||
@ -20,6 +22,16 @@ use std::{collections::HashMap, sync::atomic::Ordering};
|
||||
#[cfg(not(windows))]
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
const STR_RANDOM_PASSWORD: &'static str = "random-password";
|
||||
const STR_SECURITY_PASSWORD: &'static str = "security-password";
|
||||
const STR_RANDOM_PASSWORD_UPDATE_METHOD: &'static str = "random-password-update-method";
|
||||
const STR_RANDOM_PASSWORD_ENABLED: &'static str = "random-password-enabled";
|
||||
const STR_SECURITY_PASSWORD_ENABLED: &'static str = "security-password-enabled";
|
||||
const STR_ONETIME_PASSWORD_ENABLED: &'static str = "onetime-password-enabled";
|
||||
const STR_ONETIME_PASSWORD_ACTIVATED: &'static str = "onetime-password-activated";
|
||||
const STR_RANDOM_PASSWORD_VALID: &'static str = "random-password-valid";
|
||||
pub const STR_PASSWORD_DESCRIPTION: &'static str = "password-description";
|
||||
|
||||
// State with timestamp, because std::time::Instant cannot be serialized
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
@ -84,6 +96,45 @@ pub enum FS {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum DataKeyboard {
|
||||
Sequence(String),
|
||||
KeyDown(enigo::Key),
|
||||
KeyUp(enigo::Key),
|
||||
KeyClick(enigo::Key),
|
||||
GetKeyState(enigo::Key),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum DataKeyboardResponse {
|
||||
GetKeyState(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum DataMouse {
|
||||
MoveTo(i32, i32),
|
||||
MoveRelative(i32, i32),
|
||||
Down(enigo::MouseButton),
|
||||
Up(enigo::MouseButton),
|
||||
Click(enigo::MouseButton),
|
||||
ScrollX(i32),
|
||||
ScrollY(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum DataControl {
|
||||
Resolution {
|
||||
minx: i32,
|
||||
maxx: i32,
|
||||
miny: i32,
|
||||
maxy: i32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum Data {
|
||||
@ -127,6 +178,13 @@ pub enum Data {
|
||||
ClipbaordFile(ClipbaordFile),
|
||||
ClipboardFileEnabled(bool),
|
||||
PrivacyModeState((i32, PrivacyModeState)),
|
||||
TestRendezvousServer,
|
||||
Bool((String, Option<bool>)),
|
||||
Keyboard(DataKeyboard),
|
||||
KeyboardResponse(DataKeyboardResponse),
|
||||
Mouse(DataMouse),
|
||||
Control(DataControl),
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
@ -281,12 +339,28 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
let value;
|
||||
if name == "id" {
|
||||
value = Some(Config::get_id());
|
||||
} else if name == "password" {
|
||||
value = Some(Config::get_password());
|
||||
} else if name == STR_RANDOM_PASSWORD {
|
||||
value = Some(password::random_password());
|
||||
} else if name == STR_SECURITY_PASSWORD {
|
||||
value = Some(Config::get_security_password());
|
||||
} else if name == STR_RANDOM_PASSWORD_UPDATE_METHOD {
|
||||
value = Some(password::update_method().to_string());
|
||||
} else if name == STR_PASSWORD_DESCRIPTION {
|
||||
value = Some(
|
||||
password::random_password()
|
||||
+ &password::security_enabled().to_string()
|
||||
+ &password::random_enabled().to_string()
|
||||
+ &password::onetime_password_enabled().to_string()
|
||||
+ &password::onetime_password_activated().to_string(),
|
||||
);
|
||||
} else if name == "salt" {
|
||||
value = Some(Config::get_salt());
|
||||
} else if name == "rendezvous_server" {
|
||||
value = Some(Config::get_rendezvous_server());
|
||||
value = Some(format!(
|
||||
"{},{}",
|
||||
Config::get_rendezvous_server(),
|
||||
Config::get_rendezvous_servers().join(",")
|
||||
));
|
||||
} else if name == "rendezvous_servers" {
|
||||
value = Some(Config::get_rendezvous_servers().join(","));
|
||||
} else {
|
||||
@ -298,8 +372,12 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
if name == "id" {
|
||||
Config::set_key_confirmed(false);
|
||||
Config::set_id(&value);
|
||||
} else if name == "password" {
|
||||
Config::set_password(&value);
|
||||
} else if name == STR_RANDOM_PASSWORD {
|
||||
password::set_random_password(&value);
|
||||
} else if name == STR_SECURITY_PASSWORD {
|
||||
Config::set_security_password(&value);
|
||||
} else if name == STR_RANDOM_PASSWORD_UPDATE_METHOD {
|
||||
password::set_update_method(&value);
|
||||
} else if name == "salt" {
|
||||
Config::set_salt(&value);
|
||||
} else {
|
||||
@ -336,7 +414,39 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
.await
|
||||
);
|
||||
}
|
||||
|
||||
Data::TestRendezvousServer => {
|
||||
crate::test_rendezvous_server();
|
||||
}
|
||||
Data::Bool((name, value)) => match value {
|
||||
None => {
|
||||
let value;
|
||||
if name == STR_SECURITY_PASSWORD_ENABLED {
|
||||
value = Some(password::security_enabled());
|
||||
} else if name == STR_RANDOM_PASSWORD_ENABLED {
|
||||
value = Some(password::random_enabled());
|
||||
} else if name == STR_ONETIME_PASSWORD_ENABLED {
|
||||
value = Some(password::onetime_password_enabled());
|
||||
} else if name == STR_ONETIME_PASSWORD_ACTIVATED {
|
||||
value = Some(password::onetime_password_activated());
|
||||
} else if name == STR_RANDOM_PASSWORD_VALID {
|
||||
value = Some(password::random_password_valid());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
allow_err!(stream.send(&Data::Bool((name, value))).await);
|
||||
}
|
||||
Some(value) => {
|
||||
if name == STR_SECURITY_PASSWORD_ENABLED {
|
||||
password::set_security_enabled(value);
|
||||
} else if name == STR_RANDOM_PASSWORD_ENABLED {
|
||||
password::set_random_enabled(value);
|
||||
} else if name == STR_ONETIME_PASSWORD_ENABLED {
|
||||
password::set_onetime_password_enabled(value);
|
||||
} else if name == STR_ONETIME_PASSWORD_ACTIVATED {
|
||||
password::set_onetime_password_activated(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -419,6 +529,10 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_bool(&mut self, name: &str, value: bool) -> ResultType<()> {
|
||||
self.send(&Data::Bool((name.to_owned(), Some(value)))).await
|
||||
}
|
||||
|
||||
pub async fn next_timeout(&mut self, ms_timeout: u64) -> ResultType<Option<Data>> {
|
||||
Ok(timeout(ms_timeout, self.next()).await??)
|
||||
}
|
||||
@ -490,9 +604,128 @@ pub async fn set_config(name: &str, value: String) -> ResultType<()> {
|
||||
set_config_async(name, value).await
|
||||
}
|
||||
|
||||
pub fn set_password(v: String) -> ResultType<()> {
|
||||
Config::set_password(&v);
|
||||
set_config("password", v)
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn get_bool(name: &str) -> ResultType<Option<bool>> {
|
||||
get_bool_async(name, 1_000).await
|
||||
}
|
||||
|
||||
async fn get_bool_async(name: &str, ms_timeout: u64) -> ResultType<Option<bool>> {
|
||||
let mut c = connect(ms_timeout, "").await?;
|
||||
c.send(&Data::Bool((name.to_owned(), None))).await?;
|
||||
if let Some(Data::Bool((name2, value))) = c.next_timeout(ms_timeout).await? {
|
||||
if name == name2 {
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
pub async fn set_bool_async(name: &str, value: bool) -> ResultType<()> {
|
||||
let mut c = connect(1000, "").await?;
|
||||
c.send_bool(name, value).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn set_bool(name: &str, value: bool) -> ResultType<()> {
|
||||
set_bool_async(name, value).await
|
||||
}
|
||||
|
||||
pub fn get_random_password() -> String {
|
||||
if let Ok(Some(password)) = get_config(STR_RANDOM_PASSWORD) {
|
||||
password::set_random_password(&password);
|
||||
password
|
||||
} else {
|
||||
password::random_password()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_random_password(v: String) -> ResultType<()> {
|
||||
password::set_random_password(&v);
|
||||
set_config(STR_RANDOM_PASSWORD, v)
|
||||
}
|
||||
|
||||
pub fn set_security_password(v: String) -> ResultType<()> {
|
||||
Config::set_security_password(&v);
|
||||
set_config(STR_SECURITY_PASSWORD, v)
|
||||
}
|
||||
|
||||
pub fn random_password_update_method() -> String {
|
||||
if let Ok(Some(method)) = get_config(STR_RANDOM_PASSWORD_UPDATE_METHOD) {
|
||||
password::set_update_method(&method);
|
||||
method
|
||||
} else {
|
||||
password::update_method()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_random_password_update_method(method: String) -> ResultType<()> {
|
||||
password::set_update_method(&method);
|
||||
set_config(STR_RANDOM_PASSWORD_UPDATE_METHOD, method)
|
||||
}
|
||||
|
||||
pub fn is_random_password_enabled() -> bool {
|
||||
if let Ok(Some(enabled)) = get_bool(STR_RANDOM_PASSWORD_ENABLED) {
|
||||
password::set_random_enabled(enabled);
|
||||
enabled
|
||||
} else {
|
||||
password::random_enabled()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_random_password_enabled(enabled: bool) -> ResultType<()> {
|
||||
password::set_random_enabled(enabled);
|
||||
set_bool(STR_RANDOM_PASSWORD_ENABLED, enabled)
|
||||
}
|
||||
|
||||
pub fn is_security_password_enabled() -> bool {
|
||||
if let Ok(Some(enabled)) = get_bool(STR_SECURITY_PASSWORD_ENABLED) {
|
||||
password::set_security_enabled(enabled);
|
||||
enabled
|
||||
} else {
|
||||
password::security_enabled()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_security_password_enabled(enabled: bool) -> ResultType<()> {
|
||||
password::set_security_enabled(enabled);
|
||||
set_bool(STR_SECURITY_PASSWORD_ENABLED, enabled)
|
||||
}
|
||||
|
||||
pub fn is_onetime_password_enabled() -> bool {
|
||||
if let Ok(Some(enabled)) = get_bool(STR_ONETIME_PASSWORD_ENABLED) {
|
||||
password::set_onetime_password_enabled(enabled);
|
||||
enabled
|
||||
} else {
|
||||
password::onetime_password_enabled()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_onetime_password_enabled(enabled: bool) -> ResultType<()> {
|
||||
password::set_onetime_password_enabled(enabled);
|
||||
set_bool(STR_ONETIME_PASSWORD_ENABLED, enabled)
|
||||
}
|
||||
|
||||
pub fn is_onetime_password_activated() -> bool {
|
||||
if let Ok(Some(activated)) = get_bool(STR_ONETIME_PASSWORD_ACTIVATED) {
|
||||
password::set_onetime_password_activated(activated);
|
||||
activated
|
||||
} else {
|
||||
password::onetime_password_activated()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_onetime_password_activated(activated: bool) -> ResultType<()> {
|
||||
password::set_onetime_password_activated(activated);
|
||||
set_bool(STR_ONETIME_PASSWORD_ACTIVATED, activated)
|
||||
}
|
||||
|
||||
pub fn is_random_password_valid() -> bool {
|
||||
if let Ok(Some(valid)) = get_bool(STR_RANDOM_PASSWORD_VALID) {
|
||||
valid
|
||||
} else {
|
||||
password::random_password_valid()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id() -> String {
|
||||
@ -511,20 +744,17 @@ pub fn get_id() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_password() -> String {
|
||||
if let Ok(Some(v)) = get_config("password") {
|
||||
Config::set_password(&v);
|
||||
v
|
||||
} else {
|
||||
Config::get_password()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_rendezvous_server(ms_timeout: u64) -> String {
|
||||
pub async fn get_rendezvous_server(ms_timeout: u64) -> (String, Vec<String>) {
|
||||
if let Ok(Some(v)) = get_config_async("rendezvous_server", ms_timeout).await {
|
||||
v
|
||||
let mut urls = v.split(",");
|
||||
let a = urls.next().unwrap_or_default().to_owned();
|
||||
let b: Vec<String> = urls.map(|x| x.to_owned()).collect();
|
||||
(a, b)
|
||||
} else {
|
||||
Config::get_rendezvous_server()
|
||||
(
|
||||
Config::get_rendezvous_server(),
|
||||
Config::get_rendezvous_servers(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,3 +866,10 @@ pub async fn set_socks(value: config::Socks5Server) -> ResultType<()> {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn test_rendezvous_server() -> ResultType<()> {
|
||||
let mut c = connect(1000, "").await?;
|
||||
c.send(&Data::TestRendezvousServer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
291
src/lan.rs
Normal file
291
src/lan.rs
Normal file
@ -0,0 +1,291 @@
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::bail,
|
||||
config::{self, Config, RENDEZVOUS_PORT},
|
||||
log,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::*,
|
||||
tokio::{
|
||||
self,
|
||||
sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
},
|
||||
ResultType,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
type Message = RendezvousMessage;
|
||||
|
||||
pub(super) fn start_listening() -> ResultType<()> {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], get_broadcast_port()));
|
||||
let socket = std::net::UdpSocket::bind(addr)?;
|
||||
socket.set_read_timeout(Some(std::time::Duration::from_millis(1000)))?;
|
||||
log::info!("lan discovery listener started");
|
||||
loop {
|
||||
let mut buf = [0; 2048];
|
||||
if let Ok((len, addr)) = socket.recv_from(&mut buf) {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::PeerDiscovery(p)) => {
|
||||
if p.cmd == "ping" {
|
||||
if let Some(self_addr) = get_ipaddr_by_peer(&addr) {
|
||||
let mut msg_out = Message::new();
|
||||
let peer = PeerDiscovery {
|
||||
cmd: "pong".to_owned(),
|
||||
mac: get_mac(&self_addr),
|
||||
id: Config::get_id(),
|
||||
hostname: whoami::hostname(),
|
||||
username: crate::platform::get_active_username(),
|
||||
platform: whoami::platform().to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
msg_out.set_peer_discovery(peer);
|
||||
socket.send_to(&msg_out.write_to_bytes()?, addr).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn discover() -> ResultType<()> {
|
||||
let sockets = send_query()?;
|
||||
let rx = spawn_wait_responses(sockets);
|
||||
handle_received_peers(rx).await?;
|
||||
|
||||
log::info!("discover ping done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_wol(id: String) {
|
||||
let interfaces = default_net::get_interfaces();
|
||||
for peer in &config::LanPeers::load().peers {
|
||||
if peer.id == id {
|
||||
for (ip, mac) in peer.ip_mac.iter() {
|
||||
if let Ok(mac_addr) = mac.parse() {
|
||||
if let Ok(IpAddr::V4(ip)) = ip.parse() {
|
||||
for interface in &interfaces {
|
||||
for ipv4 in &interface.ipv4 {
|
||||
if (u32::from(ipv4.addr) & u32::from(ipv4.netmask))
|
||||
== (u32::from(ip) & u32::from(ipv4.netmask))
|
||||
{
|
||||
allow_err!(wol::send_wol(
|
||||
mac_addr,
|
||||
None,
|
||||
Some(IpAddr::V4(ipv4.addr))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_broadcast_port() -> u16 {
|
||||
(RENDEZVOUS_PORT + 3) as _
|
||||
}
|
||||
|
||||
fn get_mac(ip: &IpAddr) -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(mac) = get_mac_by_ip(ip) {
|
||||
mac.to_string()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
fn get_all_ipv4s() -> ResultType<Vec<Ipv4Addr>> {
|
||||
let mut ipv4s = Vec::new();
|
||||
for interface in default_net::get_interfaces() {
|
||||
for ipv4 in &interface.ipv4 {
|
||||
ipv4s.push(ipv4.addr.clone());
|
||||
}
|
||||
}
|
||||
Ok(ipv4s)
|
||||
}
|
||||
|
||||
fn get_mac_by_ip(ip: &IpAddr) -> ResultType<String> {
|
||||
for interface in default_net::get_interfaces() {
|
||||
match ip {
|
||||
IpAddr::V4(local_ipv4) => {
|
||||
if interface.ipv4.iter().any(|x| x.addr == *local_ipv4) {
|
||||
if let Some(mac_addr) = interface.mac_addr {
|
||||
return Ok(mac_addr.address());
|
||||
}
|
||||
}
|
||||
}
|
||||
IpAddr::V6(local_ipv6) => {
|
||||
if interface.ipv6.iter().any(|x| x.addr == *local_ipv6) {
|
||||
if let Some(mac_addr) = interface.mac_addr {
|
||||
return Ok(mac_addr.address());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bail!("No interface found for ip: {:?}", ip);
|
||||
}
|
||||
|
||||
// Mainly from https://github.com/shellrow/default-net/blob/cf7ca24e7e6e8e566ed32346c9cfddab3f47e2d6/src/interface/shared.rs#L4
|
||||
fn get_ipaddr_by_peer<A: ToSocketAddrs>(peer: A) -> Option<IpAddr> {
|
||||
let socket = match UdpSocket::bind("0.0.0.0:0") {
|
||||
Ok(s) => s,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
match socket.connect(peer) {
|
||||
Ok(()) => (),
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
match socket.local_addr() {
|
||||
Ok(addr) => return Some(addr.ip()),
|
||||
Err(_) => return None,
|
||||
};
|
||||
}
|
||||
|
||||
fn create_broadcast_sockets() -> ResultType<Vec<UdpSocket>> {
|
||||
let mut sockets = Vec::new();
|
||||
for v4_addr in get_all_ipv4s()? {
|
||||
if v4_addr.is_private() {
|
||||
let s = UdpSocket::bind(SocketAddr::from((v4_addr, 0)))?;
|
||||
s.set_broadcast(true)?;
|
||||
log::debug!("Bind socket to {}", &v4_addr);
|
||||
sockets.push(s)
|
||||
}
|
||||
}
|
||||
Ok(sockets)
|
||||
}
|
||||
|
||||
fn send_query() -> ResultType<Vec<UdpSocket>> {
|
||||
let sockets = create_broadcast_sockets()?;
|
||||
if sockets.is_empty() {
|
||||
bail!("Found no ipv4 addresses");
|
||||
}
|
||||
|
||||
let mut msg_out = Message::new();
|
||||
let peer = PeerDiscovery {
|
||||
cmd: "ping".to_owned(),
|
||||
..Default::default()
|
||||
};
|
||||
msg_out.set_peer_discovery(peer);
|
||||
let maddr = SocketAddr::from(([255, 255, 255, 255], get_broadcast_port()));
|
||||
for socket in &sockets {
|
||||
socket.send_to(&msg_out.write_to_bytes()?, maddr)?;
|
||||
}
|
||||
log::info!("discover ping sent");
|
||||
Ok(sockets)
|
||||
}
|
||||
|
||||
fn wait_response(
|
||||
socket: UdpSocket,
|
||||
timeout: Option<std::time::Duration>,
|
||||
tx: UnboundedSender<config::DiscoveryPeer>,
|
||||
) -> ResultType<()> {
|
||||
let mut last_recv_time = Instant::now();
|
||||
|
||||
socket.set_read_timeout(timeout)?;
|
||||
loop {
|
||||
let mut buf = [0; 2048];
|
||||
if let Ok((len, addr)) = socket.recv_from(&mut buf) {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::PeerDiscovery(p)) => {
|
||||
last_recv_time = Instant::now();
|
||||
if p.cmd == "pong" {
|
||||
let mac = if let Some(self_addr) = get_ipaddr_by_peer(&addr) {
|
||||
get_mac(&self_addr)
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
|
||||
if mac != p.mac {
|
||||
allow_err!(tx.send(config::DiscoveryPeer {
|
||||
id: p.id.clone(),
|
||||
ip_mac: HashMap::from([
|
||||
(addr.ip().to_string(), p.mac.clone(),)
|
||||
]),
|
||||
username: p.username.clone(),
|
||||
hostname: p.hostname.clone(),
|
||||
platform: p.platform.clone(),
|
||||
online: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if last_recv_time.elapsed().as_millis() > 3_000 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_wait_responses(sockets: Vec<UdpSocket>) -> UnboundedReceiver<config::DiscoveryPeer> {
|
||||
let (tx, rx) = unbounded_channel::<_>();
|
||||
for socket in sockets {
|
||||
let tx_clone = tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
allow_err!(wait_response(
|
||||
socket,
|
||||
Some(std::time::Duration::from_millis(10)),
|
||||
tx_clone
|
||||
));
|
||||
});
|
||||
}
|
||||
rx
|
||||
}
|
||||
|
||||
async fn handle_received_peers(mut rx: UnboundedReceiver<config::DiscoveryPeer>) -> ResultType<()> {
|
||||
let mut peers = config::LanPeers::load().peers;
|
||||
peers.iter_mut().for_each(|peer| {
|
||||
peer.online = false;
|
||||
});
|
||||
|
||||
let mut response_set = HashSet::new();
|
||||
let mut last_write_time = Instant::now() - std::time::Duration::from_secs(4);
|
||||
loop {
|
||||
tokio::select! {
|
||||
data = rx.recv() => match data {
|
||||
Some(mut peer) => {
|
||||
let in_response_set = !response_set.insert(peer.id.clone());
|
||||
if let Some(pos) = peers.iter().position(|x| x.is_same_peer(&peer) ) {
|
||||
let peer1 = peers.remove(pos);
|
||||
if in_response_set {
|
||||
peer.ip_mac.extend(peer1.ip_mac);
|
||||
peer.online = true;
|
||||
}
|
||||
}
|
||||
peers.insert(0, peer);
|
||||
if last_write_time.elapsed().as_millis() > 300 {
|
||||
config::LanPeers::store(&peers);
|
||||
last_write_time = Instant::now();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config::LanPeers::store(&peers);
|
||||
Ok(())
|
||||
}
|
||||
@ -8,6 +8,7 @@ mod de;
|
||||
mod en;
|
||||
mod eo;
|
||||
mod es;
|
||||
mod hu;
|
||||
mod fr;
|
||||
mod id;
|
||||
mod it;
|
||||
@ -28,6 +29,7 @@ lazy_static::lazy_static! {
|
||||
("tw", "繁體中文"),
|
||||
("pt", "Português"),
|
||||
("es", "Español"),
|
||||
("hu", "Magyar"),
|
||||
("ru", "Русский"),
|
||||
("sk", "Slovenčina"),
|
||||
("id", "Indonesia"),
|
||||
@ -68,6 +70,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
|
||||
"tw" => tw::T.deref(),
|
||||
"de" => de::T.deref(),
|
||||
"es" => es::T.deref(),
|
||||
"hu" => hu::T.deref(),
|
||||
"ru" => ru::T.deref(),
|
||||
"eo" => eo::T.deref(),
|
||||
"id" => id::T.deref(),
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "进入隐私模式"),
|
||||
("Out privacy mode", "退出隐私模式"),
|
||||
("Language", "语言"),
|
||||
("Keep RustDesk background service", "保持RustDesk后台服务"),
|
||||
("Ignore Battery Optimizations", "忽略电池优化"),
|
||||
("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的RustDesk应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"),
|
||||
("Random Password After Session", "会话结束更新随机密码"),
|
||||
("Keep", "保持"),
|
||||
("Update", "更新"),
|
||||
("Disable", "禁用"),
|
||||
("Onetime Password", "一次性口令"),
|
||||
("Verification Method", "密码验证方式"),
|
||||
("Enable security password", "启用安全密码"),
|
||||
("Enable random password", "启用随机密码"),
|
||||
("Enable onetime password", "启用一次性访问功能"),
|
||||
("Disable onetime password", "禁用一次性访问功能"),
|
||||
("Activate onetime password", "激活一次性访问功能"),
|
||||
("Set security password", "设置安全密码"),
|
||||
("Connection not allowed", "对方不允许连接"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "v režimu soukromí"),
|
||||
("Out privacy mode", "mimo režim soukromí"),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "I databeskyttelsestilstand"),
|
||||
("Out privacy mode", "Databeskyttelsestilstand fra"),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "im Datenschutzmodus"),
|
||||
("Out privacy mode", "Datenschutzmodus aus"),
|
||||
("Language", "Sprache"),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -27,5 +27,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
||||
("server_not_support", "Not yet supported by the server"),
|
||||
("android_open_battery_optimizations_tip", "If you want to disable this feature, please go to the next RustDesk application settings page, find and enter [Battery], Uncheck [Unrestricted]"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", ""),
|
||||
("Out privacy mode", ""),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "En modo de privacidad"),
|
||||
("Out privacy mode", "Fuera del modo de privacidad"),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "en mode privé"),
|
||||
("Out privacy mode", "hors mode de confidentialité"),
|
||||
("Language", "Langue"),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
304
src/lang/hu.rs
Normal file
304
src/lang/hu.rs
Normal file
@ -0,0 +1,304 @@
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Státusz"),
|
||||
("Your Desktop", "A te asztalod"),
|
||||
("desk_tip", "Az asztalod ezzel az ID-vel, és jelszóval érhető el."),
|
||||
("Password", "Jelszó"),
|
||||
("Ready", "Kész"),
|
||||
("Established", "Létrejött"),
|
||||
("connecting_status", "Kapcsolódás a RustDesk hálózatához..."),
|
||||
("Enable Service", "A szolgáltatás bekapcsolása"),
|
||||
("Start Service", "Szolgáltatás Elindítása"),
|
||||
("Service is running", "A szolgáltatás fut"),
|
||||
("Service is not running", "A szolgáltatás nem fut"),
|
||||
("not_ready_status", "A RustDesk nem áll készen. Kérlek nézd meg a hálózati beállításaidat."),
|
||||
("Control Remote Desktop", "Távoli Asztal Kontrollálása"),
|
||||
("Transfer File", "Fájl Transzfer"),
|
||||
("Connect", "Kapcsolódás"),
|
||||
("Recent Sessions", "Korábbi Sessionök"),
|
||||
("Address Book", "Címköny"),
|
||||
("Confirmation", "Megerősít"),
|
||||
("TCP Tunneling", "TCP Tunneling"),
|
||||
("Remove", "Eltávolít"),
|
||||
("Refresh random password", "Véletlenszerű jelszó frissítése"),
|
||||
("Set your own password", "Saját jelszó beállítása"),
|
||||
("Enable Keyboard/Mouse", "Billentyűzet/Egér bekapcsolása"),
|
||||
("Enable Clipboard", "Megosztott vágólap bekapcsolása"),
|
||||
("Enable File Transfer", "Fájl transzer bekapcsolása"),
|
||||
("Enable TCP Tunneling", "TCP Tunneling bekapcsolása"),
|
||||
("IP Whitelisting", "IP Fehérlista"),
|
||||
("ID/Relay Server", "ID/Relay Szerver"),
|
||||
("Stop service", "Szolgáltatás Kikapcsolása"),
|
||||
("Change ID", "ID Megváltoztatása"),
|
||||
("Website", "Weboldal"),
|
||||
("About", "Rólunk: "),
|
||||
("Mute", "Némítás"),
|
||||
("Audio Input", "Audo Bemenet"),
|
||||
("Enhancements", "Javítások"),
|
||||
("Hardware Codec", "Hardware Kodek"),
|
||||
("Adaptive Bitrate", "Adaptív Bitrate"),
|
||||
("ID Server", "ID Szerver"),
|
||||
("Relay Server", "Relay Szerver"),
|
||||
("API Server", "API Szerver"),
|
||||
("invalid_http", "A címnek mindenképpen http(s)://-el kell kezdődnie."),
|
||||
("Invalid IP", "A megadott íp cím helytelen."),
|
||||
("id_change_tip", "Csak a-z, A-Z, 0-9 csoportokba tartozó karakterek, illetve a _ karakter van engedélyezve. Az első karakternek mindenképpen a-z, A-Z csoportokba kell esnie. Az ID hosszúsága 6-tól, 16 karakter."),
|
||||
("Invalid format", "Érvénytelen formátum"),
|
||||
("server_not_support", "Még nem támogatott a szerver által"),
|
||||
("Not available", "Nem érhető el"),
|
||||
("Too frequent", "Túl gyakori"),
|
||||
("Cancel", "Mégsem"),
|
||||
("Skip", "Kihagy"),
|
||||
("Close", "Bezár"),
|
||||
("Retry", "Újrapróbálkozás"),
|
||||
("OK", "OK"),
|
||||
("Password Required", "A jelszó megadása kötelező"),
|
||||
("Please enter your password", "Kérlek írd be a jelszavad"),
|
||||
("Remember password", "Kérlek emlékezz a jelszóra"),
|
||||
("Wrong Password", "Hibás jelszó"),
|
||||
("Do you want to enter again?", "Újra szeretnéd próbálni?"),
|
||||
("Connection Error", "Kapcsolódási Hiba"),
|
||||
("Error", "Hiba"),
|
||||
("Reset by the peer", "A kapcsolatot alaphelyzetbe állt"),
|
||||
("Connecting...", "Kapcsolódás..."),
|
||||
("Connection in progress. Please wait.", "A kapcsolódás folyamatban van. Kérlek várj."),
|
||||
("Please try 1 minute later", "Kérlek próbáld újra 1 perc múlva."),
|
||||
("Login Error", "Belépési Hiba"),
|
||||
("Successful", "Sikeres"),
|
||||
("Connected, waiting for image...", "Kapcsolódva, várakozás a képre..."),
|
||||
("Name", "Név"),
|
||||
("Type", "Fajta"),
|
||||
("Modified", "Módosított"),
|
||||
("Size", "Méret"),
|
||||
("Show Hidden Files", "Rejtett Fájlok Mutatása"),
|
||||
("Receive", "Kapni"),
|
||||
("Send", "Küldeni"),
|
||||
("Refresh File", "Fájlok Frissítése"),
|
||||
("Local", "Lokális"),
|
||||
("Remote", "Távoli"),
|
||||
("Remote Computer", "Távoli Számítógép"),
|
||||
("Local Computer", "Lokális Számítógép"),
|
||||
("Confirm Delete", "Törlés Megerősítése"),
|
||||
("Delete", "Törlés"),
|
||||
("Properties", "Tulajdonságok"),
|
||||
("Multi Select", "Több fájl kiválasztása"),
|
||||
("Empty Directory", "Üres Könyvtár"),
|
||||
("Not an empty directory", "Nem egy üres könyvtár"),
|
||||
("Are you sure you want to delete this file?", "Biztosan törölni szeretnéd ezt a fájlt?"),
|
||||
("Are you sure you want to delete this empty directory?", "Biztosan törölni szeretnéd ezt az üres könyvtárat?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Biztosan törölni szeretnéd a fájlokat ebben a könyvtárban?"),
|
||||
("Do this for all conflicts", "Ezt tedd az összes konfliktussal"),
|
||||
("This is irreversible!", "Ez a folyamat visszafordíthatatlan!"),
|
||||
("Deleting", "A törlés folyamatban"),
|
||||
("files", "fájlok"),
|
||||
("Waiting", "Várunk"),
|
||||
("Finished", "Végzett"),
|
||||
("Speed", "Gyorsaság"),
|
||||
("Custom Image Quality", "Egyedi Képminőség"),
|
||||
("Privacy mode", "Inkognító mód"),
|
||||
("Block user input", "Felhasználói input blokkokolása"),
|
||||
("Unblock user input", "Felhasználói input blokkolásának feloldása"),
|
||||
("Adjust Window", "Ablakméret beállítása"),
|
||||
("Original", "Eredeti"),
|
||||
("Shrink", "Zsugorított"),
|
||||
("Stretch", "Nyújtott"),
|
||||
("Good image quality", "Jó képminőség"),
|
||||
("Balanced", "Balanszolt"),
|
||||
("Optimize reaction time", "Válaszidő optimializálása"),
|
||||
("Custom", "Egyedi"),
|
||||
("Show remote cursor", "Távoli kurzor mutatása"),
|
||||
("Show quality monitor", "Minőségi monitor mutatása"),
|
||||
("Disable clipboard", "Vágólap Kikapcsolása"),
|
||||
("Lock after session end", "Lezárás a session végén"),
|
||||
("Insert", "Beszúrás"),
|
||||
("Insert Lock", "Beszúrási Zároló"),
|
||||
("Refresh", "Frissítés"),
|
||||
("ID does not exist", "Ez az ID nem létezik"),
|
||||
("Failed to connect to rendezvous server", "A randevú szerverhez való kapcsolódás sikertelen"),
|
||||
("Please try later", "Kérlek próbád később"),
|
||||
("Remote desktop is offline", "A távoli asztal offline"),
|
||||
("Key mismatch", "Eltérés a kulcsokban"),
|
||||
("Timeout", "Időtúllépés"),
|
||||
("Failed to connect to relay server", "A relay szerverhez való kapcsolódás sikertelen"),
|
||||
("Failed to connect via rendezvous server", "A randevú szerverrel való kapcsolódás sikertelen"),
|
||||
("Failed to connect via relay server", "A relay szerverrel való kapcsolódás sikertelen"),
|
||||
("Failed to make direct connection to remote desktop", "A távoli asztalhoz való direkt kapcsolódás sikertelen"),
|
||||
("Set Password", "Jelszó Beállítása"),
|
||||
("OS Password", "Operációs Rendszer Jelszavának Beállítása"),
|
||||
("install_tip", "Az UAC (Felhasználói Fiók Felügyelet) miatt, a RustDesk nem fog rendesen funkcionálni mint távoli oldal néhány esetben. Hogy ezt kikerüld, vagy kikapcsold, kérlek nyomj rá a gombra ezalatt az üzenet alatt, hogy feltelepítsd a RustDesket a rendszerre."),
|
||||
("Click to upgrade", "Kattints a frissítés telepítéséhez"),
|
||||
("Click to download", "Kattints a letöltéshez"),
|
||||
("Click to update", "Kattints a frissítés letöltéséhez"),
|
||||
("Configure", "Beállítás"),
|
||||
("config_acc", "Ahhoz hogy a RustDesket távolról irányítani tudd, \"Elérhetőségi\" jogokat kell adnod a RustDesk-nek."),
|
||||
("config_screen", "Ahhoz hogy a RustDesket távolról irányítani tudd, \"Képernyőfelvételi\" jogokat kell adnod a RustDesk-nek."),
|
||||
("Installing ...", "Telepítés..."),
|
||||
("Install", "Telepítés"),
|
||||
("Installation", "Telepítés"),
|
||||
("Installation Path", "Telepítési útvonal"),
|
||||
("Create start menu shortcuts", "Start menu parancsikon létrehozása"),
|
||||
("Create desktop icon", "Asztali icon létrehozása"),
|
||||
("agreement_tip", "Azzal hogy elindítod a telepítést, elfogadod a licenszszerződést."),
|
||||
("Accept and Install", "Elfogadás és Telepítés"),
|
||||
("End-user license agreement", "Felhasználói licencszerződés"),
|
||||
("Generating ...", "Generálás..."),
|
||||
("Your installation is lower version.", "A jelenleg feltelepített verzió régebbi."),
|
||||
("not_close_tcp_tip", "Ne zárd be ezt az ablakot miközben a tunnelt használod"),
|
||||
("Listening ...", "Halgazózás..."),
|
||||
("Remote Host", "Távoli Host"),
|
||||
("Remote Port", "Távoli Port"),
|
||||
("Action", "Akció"),
|
||||
("Add", "Add"),
|
||||
("Local Port", "Lokális Port"),
|
||||
("setup_server_tip", "Egy gyorsabb kapcsolatért, kérlek hostolj egy saját szervert"),
|
||||
("Too short, at least 6 characters.", "Túl rövid, legalább 6 karakter"),
|
||||
("The confirmation is not identical.", "A megerősítés nem volt azonos"),
|
||||
("Permissions", "Jogok"),
|
||||
("Accept", "Elfogad"),
|
||||
("Dismiss", "Elutasít"),
|
||||
("Disconnect", "Szétkapcsolás"),
|
||||
("Allow using keyboard and mouse", "Billentyűzet és egér használatának engedélyezése"),
|
||||
("Allow using clipboard", "Vágólap használatának engedélyezése"),
|
||||
("Allow hearing sound", "Hang átvitelének engedélyezése"),
|
||||
("Allow file copy and paste", "Fájlok másolásának és beillesztésének engedélyezése"),
|
||||
("Connected", "Kapcsolódva"),
|
||||
("Direct and encrypted connection", "Direkt, és titkosított kapcsolat"),
|
||||
("Relayed and encrypted connection", "Relayelt, és titkosított kapcsolat"),
|
||||
("Direct and unencrypted connection", "Direkt, és nem titkosított kapcsolat"),
|
||||
("Relayed and unencrypted connection", "Rekayelt, és nem titkosított kapcsolat"),
|
||||
("Enter Remote ID", "Kérlek írd be a távoli ID-t"),
|
||||
("Enter your password", "Kérlek írd be a jelszavadat"),
|
||||
("Logging in...", "A belépés folyamatban..."),
|
||||
("Enable RDP session sharing", "Az RDP session megosztás engedélyezése"),
|
||||
("Auto Login", "Automatikus Login"),
|
||||
("Enable Direct IP Access", "Direkt IP elérés engedélyezése"),
|
||||
("Rename", "Átnevezés"),
|
||||
("Space", "Hely"),
|
||||
("Create Desktop Shortcut", "Asztali Parancsikon Lértehozása"),
|
||||
("Change Path", "Útvonal Megváltoztatása"),
|
||||
("Create Folder", "Mappa Készítése"),
|
||||
("Please enter the folder name", "Kérlek írd be a mappa nevét"),
|
||||
("Fix it", "Kérlek javísd meg"),
|
||||
("Warning", "Figyelem"),
|
||||
("Login screen using Wayland is not supported", "A belépési kijelzővel a Wayland használata nem támogatott"),
|
||||
("Reboot required", "Újraindítás szükséges"),
|
||||
("Unsupported display server ", "Nem támogatott kijelző szerver"),
|
||||
("x11 expected", "x11-re számítottt"),
|
||||
("Port", "Port"),
|
||||
("Settings", "Beállítások"),
|
||||
("Username", "Felhasználónév"),
|
||||
("Invalid port", "Érvénytelen port"),
|
||||
("Closed manually by the peer", "A kapcsolat manuálisan be lett zárva a másik fél álltal"),
|
||||
("Enable remote configuration modification", "Távoli konfiguráció módosítás engedélyezése"),
|
||||
("Run without install", "Futtatás feltelepítés nélkül"),
|
||||
("Always connected via relay", "Mindig relay által kapcsolódott"),
|
||||
("Always connect via relay", "Mindig relay által kapcsolódik"),
|
||||
("whitelist_tip", "Csak a fehérlistán lévő címek érhetnek el"),
|
||||
("Login", "Belépés"),
|
||||
("Logout", "Kilépés"),
|
||||
("Tags", "Tagok"),
|
||||
("Search ID", "ID keresés"),
|
||||
("Current Wayland display server is not supported", "Jelenleg a Wayland display szerver nem támogatott"),
|
||||
("whitelist_sep", "Ide jönnek a címek, vesző, pontosvessző, space, vagy új sorral elválasztva"),
|
||||
("Add ID", "ID Hozzáadása"),
|
||||
("Add Tag", "Tag Hozzáadása"),
|
||||
("Unselect all tags", "Az összes tag kiválasztásának törlése"),
|
||||
("Network error", "Hálózati hiba"),
|
||||
("Username missed", "A felhasználónév kimaradt"),
|
||||
("Password missed", "A jelszó kimaradt"),
|
||||
("Wrong credentials", "Hibás felhasználónév vagy jelszó"),
|
||||
("Edit Tag", "A tag(ok) szerkeztése"),
|
||||
("Unremember Password", "A jelszó megjegyzésének törlése"),
|
||||
("Favorites", "Kedvencek"),
|
||||
("Add to Favorites", "Hozzáadás a kedvencekhez"),
|
||||
("Remove from Favorites", "Eltávolítás a kedvencektől"),
|
||||
("Empty", "Üres"),
|
||||
("Invalid folder name", "Helytelen fájlnév"),
|
||||
("Socks5 Proxy", "Socks5-ös Proxy"),
|
||||
("Hostname", "Hostnév"),
|
||||
("Discovered", "Felfedezés"),
|
||||
("install_daemon_tip", "Ahhoz hogy a RustDesk bootkor elinduljon, telepítened kell a rendszer szolgáltatást."),
|
||||
("Remote ID", "Távoli ID"),
|
||||
("Paste", "Beillesztés"),
|
||||
("Paste here?", "Beillesztés ide?"),
|
||||
("Are you sure to close the connection?", "Biztos vagy benne hogy be szeretnéd zárni a kapcsolatot?"),
|
||||
("Download new version", "Új verzó letöltése"),
|
||||
("Touch mode", "Érintési mód bekapcsolása"),
|
||||
("Mouse mode", "Egérhasználati mód bekapcsolása"),
|
||||
("One-Finger Tap", "Egyújas érintés"),
|
||||
("Left Mouse", "Baloldali Egér"),
|
||||
("One-Long Tap", "Egy hosszú érintés"),
|
||||
("Two-Finger Tap", "Két újas érintés"),
|
||||
("Right Mouse", "Jobboldali Egér"),
|
||||
("One-Finger Move", "Egyújas mozgatás"),
|
||||
("Double Tap & Move", "Kétszeri érintés, és Mozgatás"),
|
||||
("Mouse Drag", "Egérrel való húzás"),
|
||||
("Three-Finger vertically", "Három ujj függőlegesen"),
|
||||
("Mouse Wheel", "Egérgörgő"),
|
||||
("Two-Finger Move", "Kátújas mozgatás"),
|
||||
("Canvas Move", "Nézet Mozgatása"),
|
||||
("Pinch to Zoom", "Húzd össze a nagyításhoz"),
|
||||
("Canvas Zoom", "Nézet Nagyítása"),
|
||||
("Reset canvas", "Nézet visszaállítása"),
|
||||
("No permission of file transfer", "Nincs jogod fájl transzer indításához"),
|
||||
("Note", "Megyjegyzés"),
|
||||
("Connection", "Kapcsolat"),
|
||||
("Share Screen", "Képernyőmegosztás"),
|
||||
("CLOSE", "LETILT"),
|
||||
("OPEN", "ENGEDÉLYEZ"),
|
||||
("Chat", "Chat"),
|
||||
("Total", "Összes"),
|
||||
("items", "Tárgyak"),
|
||||
("Selected", "Kiválasztott"),
|
||||
("Screen Capture", "Képernyőrögzítés"),
|
||||
("Input Control", "Input Kontrol"),
|
||||
("Audio Capture", "Audió Rögzítés"),
|
||||
("File Connection", "Fájlkapcsolat"),
|
||||
("Screen Connection", "Új Vizuális Kapcsolat"),
|
||||
("Do you accept?", "Elfogadod?"),
|
||||
("Open System Setting", "Rendszer beállítások megnyitása"),
|
||||
("How to get Android input permission?", "Hogyan állíthatok be Android input jogokat?"),
|
||||
("android_input_permission_tip1", "Ahhoz hogy egy távoli eszköz kontolálhassa az Android eszközödet egérrel vagy érintéssel, jogot kell adnod a RustDesk-nek, hogy használja az \"Elérhetőségi\" szolgáltatást."),
|
||||
("android_input_permission_tip2", "Kérlek navigálj a rendszer beállításaihoz, keresd meg vagy írd be hogy [Feltelepített Szolgáltatások], és kapcsold be a [RustDesk Input] szolgáltatást."),
|
||||
("android_new_connection_tip", "Új kontrollálási kérés érkezett, amely irányítani szeretné az eszközöded."),
|
||||
("android_service_will_start_tip", "A \"Képernyőrögzítés\" engedélyezése automatikusan elindítja majd a szolgáltatást, amely megengedi más eszközöknek hogy kérést kezdeményezzenek az eszköz felé."),
|
||||
("android_stop_service_tip", "A szolgáltatás bezárása automatikusan szétkapcsol minden létező kapcsolatot."),
|
||||
("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, kérlek frissíts legalább Android 10-re, vagy egy újabb verzióra."),
|
||||
("android_start_service_tip", "Nyomj a [Szolgáltatás Indítása] opcióra, vagy adj [Képernyőrözítési] jogot az applikációnak hogy elindítsd a képernyőmegosztó szolgáltatást."),
|
||||
("Account", "Fiók"),
|
||||
("Overwrite", "Felülírás"),
|
||||
("This file exists, skip or overwrite this file?", "Ez a fájl már létezik, skippeljünk, vagy felülírjuk ezt a fájlt?"),
|
||||
("Quit", "Kilépés"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/hu/manual/mac/#enable-permissions"),
|
||||
("Help", "Segítség"),
|
||||
("Failed", "Sikertelen"),
|
||||
("Succeeded", "Sikeres"),
|
||||
("Someone turns on privacy mode, exit", "Valaki bekacsolta a privát módot, lépj ki"),
|
||||
("Unsupported", "Nem támogatott"),
|
||||
("Peer denied", "Elutasítva a távoli fél álltal"),
|
||||
("Please install plugins", "Kérlek telepítsd a pluginokat"),
|
||||
("Peer exit", "A távoli fél kilépett"),
|
||||
("Failed to turn off", "Nem tudtuk kikapcsolni"),
|
||||
("Turned off", "Kikapcsolva"),
|
||||
("In privacy mode", "Belépés a privát módba"),
|
||||
("Out privacy mode", "Kilépés a privát módból"),
|
||||
("Language", "Nyelv"),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "Dalam mode privasi"),
|
||||
("Out privacy mode", "Keluar dari mode privasi"),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "In modalità privacy"),
|
||||
("Out privacy mode", "Fuori modalità privacy"),
|
||||
("Language", "Linguaggio"),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "No modo de privacidade"),
|
||||
("Out privacy mode", "Fora do modo de privacidade"),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -222,7 +222,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remote ID", "Удаленный идентификатор"),
|
||||
("Paste", "Вставить"),
|
||||
("Paste here?", "Вставить сюда?"),
|
||||
("Are you sure to close the connection?", "Вы уверены, что хотите закрыть соединение?"),
|
||||
("Are you sure to close the connection?", "Вы уверены, что хотите завершить подключение?"),
|
||||
("Download new version", "Скачать новую версию"),
|
||||
("Touch mode", "Сенсорный режим"),
|
||||
("Mouse mode", "Режим мыши"),
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "В режиме конфиденциальности"),
|
||||
("Out privacy mode", "Выход из режима конфиденциальности"),
|
||||
("Language", "Язык"),
|
||||
("Keep RustDesk background service", "Сохранить фоновый службу RustDesk"),
|
||||
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
|
||||
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек "),
|
||||
("Random Password After Session", "Случайный пароль после сеанса"),
|
||||
("Keep", "Оставить"),
|
||||
("Update", "Обновить"),
|
||||
("Disable", "Отключить"),
|
||||
("Onetime Password", "Одноразовый пароль"),
|
||||
("Verification Method", "Метод верификации"),
|
||||
("Enable security password", "Включить пароль безопасности"),
|
||||
("Enable random password", "Включить случайный пароль"),
|
||||
("Enable onetime password", "Включить одноразовый пароль"),
|
||||
("Disable onetime password", "Отключить одноразовый пароль"),
|
||||
("Activate onetime password", "Активировать одноразовый пароль"),
|
||||
("Set security password", "Задать пароль безопасности"),
|
||||
("Connection not allowed", "Подключение не разрешено"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "V režime súkromia"),
|
||||
("Out privacy mode", "Mimo režimu súkromia"),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", ""),
|
||||
("Out privacy mode", ""),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "Gizlilik modunda"),
|
||||
("Out privacy mode", "Gizlilik modu dışında"),
|
||||
("Language", ""),
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "開啟隱私模式"),
|
||||
("Out privacy mode", "退出隱私模式"),
|
||||
("Language", "語言"),
|
||||
("Keep RustDesk background service", "保持RustDesk後台服務"),
|
||||
("Ignore Battery Optimizations", "忽略電池優化"),
|
||||
("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
|
||||
("Random Password After Session", "會話結束更新隨機密碼"),
|
||||
("Keep", "保持"),
|
||||
("Update", "更新"),
|
||||
("Disable", "禁用"),
|
||||
("Onetime Password", "一次性口令"),
|
||||
("Verification Method", "密碼驗證方式"),
|
||||
("Enable security password", "啟用安全密碼"),
|
||||
("Enable random password", "啟用隨機密碼"),
|
||||
("Enable onetime password", "啟用一次性訪問功能"),
|
||||
("Disable onetime password", "禁用一次性訪問功能"),
|
||||
("Activate onetime password", "激活一次性訪問功能"),
|
||||
("Set security password", "設置安全密碼"),
|
||||
("Connection not allowed", "對方不允許連接"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@ -12,6 +12,8 @@ mod client;
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
mod rendezvous_mediator;
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
mod lan;
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub use self::rendezvous_mediator::*;
|
||||
/// cbindgen:ignore
|
||||
pub mod common;
|
||||
|
||||
@ -108,6 +108,10 @@ fn main() {
|
||||
args.len() > 1,
|
||||
));
|
||||
return;
|
||||
} else if args[0] == "--extract" {
|
||||
#[cfg(feature = "with_rc")]
|
||||
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if args[0] == "--remove" {
|
||||
@ -148,7 +152,7 @@ fn main() {
|
||||
return;
|
||||
} else if args[0] == "--password" {
|
||||
if args.len() == 2 {
|
||||
ipc::set_password(args[1].to_owned()).unwrap();
|
||||
ipc::set_security_password(args[1].to_owned()).unwrap();
|
||||
}
|
||||
return;
|
||||
} else if args[0] == "--check-hwcodec-config" {
|
||||
|
||||
@ -591,7 +591,6 @@ impl Connection {
|
||||
log::debug!("Exit io_loop of id={}", session.id);
|
||||
}
|
||||
Err(err) => {
|
||||
crate::common::test_rendezvous_server();
|
||||
session.msgbox("error", "Connection Error", &err.to_string());
|
||||
}
|
||||
}
|
||||
@ -600,7 +599,7 @@ impl Connection {
|
||||
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&data) {
|
||||
match msg_in.union {
|
||||
Some(message::Union::video_frame(vf)) => {
|
||||
Some(message::Union::VideoFrame(vf)) => {
|
||||
if !self.first_frame {
|
||||
self.first_frame = true;
|
||||
}
|
||||
@ -611,21 +610,21 @@ impl Connection {
|
||||
s.add(ZeroCopyBuffer(self.video_handler.rgb.clone()));
|
||||
}
|
||||
}
|
||||
Some(message::Union::hash(hash)) => {
|
||||
Some(message::Union::Hash(hash)) => {
|
||||
self.session.handle_hash(hash, peer).await;
|
||||
}
|
||||
Some(message::Union::login_response(lr)) => match lr.union {
|
||||
Some(login_response::Union::error(err)) => {
|
||||
Some(message::Union::LoginResponse(lr)) => match lr.union {
|
||||
Some(login_response::Union::Error(err)) => {
|
||||
if !self.session.handle_login_error(&err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Some(login_response::Union::peer_info(pi)) => {
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
self.session.handle_peer_info(pi);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::clipboard(cb)) => {
|
||||
Some(message::Union::Clipboard(cb)) => {
|
||||
if !self.session.lc.read().unwrap().disable_clipboard {
|
||||
let content = if cb.compress {
|
||||
decompress(&cb.content)
|
||||
@ -638,7 +637,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::cursor_data(cd)) => {
|
||||
Some(message::Union::CursorData(cd)) => {
|
||||
let colors = hbb_common::compress::decompress(&cd.colors);
|
||||
self.session.push_event(
|
||||
"cursor_data",
|
||||
@ -655,18 +654,18 @@ impl Connection {
|
||||
],
|
||||
);
|
||||
}
|
||||
Some(message::Union::cursor_id(id)) => {
|
||||
Some(message::Union::CursorId(id)) => {
|
||||
self.session
|
||||
.push_event("cursor_id", vec![("id", &id.to_string())]);
|
||||
}
|
||||
Some(message::Union::cursor_position(cp)) => {
|
||||
Some(message::Union::CursorPosition(cp)) => {
|
||||
self.session.push_event(
|
||||
"cursor_position",
|
||||
vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())],
|
||||
);
|
||||
}
|
||||
Some(message::Union::file_response(fr)) => match fr.union {
|
||||
Some(file_response::Union::dir(fd)) => {
|
||||
Some(message::Union::FileResponse(fr)) => match fr.union {
|
||||
Some(file_response::Union::Dir(fd)) => {
|
||||
let mut entries = fd.entries.to_vec();
|
||||
if self.session.peer_platform() == "Windows" {
|
||||
fs::transform_windows_path(&mut entries);
|
||||
@ -680,7 +679,7 @@ impl Connection {
|
||||
job.set_files(entries);
|
||||
}
|
||||
}
|
||||
Some(file_response::Union::block(block)) => {
|
||||
Some(file_response::Union::Block(block)) => {
|
||||
if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) {
|
||||
if let Err(_err) = job.write(block, None).await {
|
||||
// to-do: add "skip" for writing job
|
||||
@ -688,17 +687,17 @@ impl Connection {
|
||||
self.update_jobs_status();
|
||||
}
|
||||
}
|
||||
Some(file_response::Union::done(d)) => {
|
||||
Some(file_response::Union::Done(d)) => {
|
||||
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
|
||||
job.modify_time();
|
||||
fs::remove_job(d.id, &mut self.write_jobs);
|
||||
}
|
||||
self.handle_job_status(d.id, d.file_num, None);
|
||||
}
|
||||
Some(file_response::Union::error(e)) => {
|
||||
Some(file_response::Union::Error(e)) => {
|
||||
self.handle_job_status(e.id, e.file_num, Some(e.error));
|
||||
}
|
||||
Some(file_response::Union::digest(digest)) => {
|
||||
Some(file_response::Union::Digest(digest)) => {
|
||||
if digest.is_upload {
|
||||
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
|
||||
if let Some(file) = job.files().get(digest.file_num as usize) {
|
||||
@ -709,9 +708,9 @@ impl Connection {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(if overwrite {
|
||||
file_transfer_send_confirm_request::Union::offset_blk(0)
|
||||
file_transfer_send_confirm_request::Union::OffsetBlk(0)
|
||||
} else {
|
||||
file_transfer_send_confirm_request::Union::skip(
|
||||
file_transfer_send_confirm_request::Union::Skip(
|
||||
true,
|
||||
)
|
||||
}),
|
||||
@ -741,7 +740,7 @@ impl Connection {
|
||||
let msg= new_send_confirm(FileTransferSendConfirmRequest {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::skip(true)),
|
||||
union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
|
||||
..Default::default()
|
||||
});
|
||||
self.session.send_msg(msg);
|
||||
@ -753,9 +752,9 @@ impl Connection {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(if overwrite {
|
||||
file_transfer_send_confirm_request::Union::offset_blk(0)
|
||||
file_transfer_send_confirm_request::Union::OffsetBlk(0)
|
||||
} else {
|
||||
file_transfer_send_confirm_request::Union::skip(true)
|
||||
file_transfer_send_confirm_request::Union::Skip(true)
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
@ -775,7 +774,7 @@ impl Connection {
|
||||
FileTransferSendConfirmRequest {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)),
|
||||
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
@ -792,15 +791,15 @@ impl Connection {
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::misc(misc)) => match misc.union {
|
||||
Some(misc::Union::audio_format(f)) => {
|
||||
Some(message::Union::Misc(misc)) => match misc.union {
|
||||
Some(misc::Union::AudioFormat(f)) => {
|
||||
self.audio_handler.handle_format(f); //
|
||||
}
|
||||
Some(misc::Union::chat_message(c)) => {
|
||||
Some(misc::Union::ChatMessage(c)) => {
|
||||
self.session
|
||||
.push_event("chat_client_mode", vec![("text", &c.text)]);
|
||||
}
|
||||
Some(misc::Union::permission_info(p)) => {
|
||||
Some(misc::Union::PermissionInfo(p)) => {
|
||||
log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
|
||||
use permission_info::Permission;
|
||||
self.session.push_event(
|
||||
@ -816,7 +815,7 @@ impl Connection {
|
||||
)],
|
||||
);
|
||||
}
|
||||
Some(misc::Union::switch_display(s)) => {
|
||||
Some(misc::Union::SwitchDisplay(s)) => {
|
||||
self.video_handler.reset();
|
||||
self.session.push_event(
|
||||
"switch_display",
|
||||
@ -829,22 +828,22 @@ impl Connection {
|
||||
],
|
||||
);
|
||||
}
|
||||
Some(misc::Union::close_reason(c)) => {
|
||||
Some(misc::Union::CloseReason(c)) => {
|
||||
self.session.msgbox("error", "Connection Error", &c);
|
||||
return false;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::test_delay(t)) => {
|
||||
Some(message::Union::TestDelay(t)) => {
|
||||
self.session.handle_test_delay(t, peer).await;
|
||||
}
|
||||
Some(message::Union::audio_frame(frame)) => {
|
||||
Some(message::Union::AudioFrame(frame)) => {
|
||||
if !self.session.lc.read().unwrap().disable_audio {
|
||||
self.audio_handler.handle_frame(frame);
|
||||
}
|
||||
}
|
||||
Some(message::Union::file_action(action)) => match action.union {
|
||||
Some(file_action::Union::send_confirm(c)) => {
|
||||
Some(message::Union::FileAction(action)) => match action.union {
|
||||
Some(file_action::Union::SendConfirm(c)) => {
|
||||
if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) {
|
||||
job.confirm(&c);
|
||||
}
|
||||
@ -1030,9 +1029,9 @@ impl Connection {
|
||||
id,
|
||||
file_num,
|
||||
union: if need_override {
|
||||
Some(file_transfer_send_confirm_request::Union::offset_blk(0))
|
||||
Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
|
||||
} else {
|
||||
Some(file_transfer_send_confirm_request::Union::skip(true))
|
||||
Some(file_transfer_send_confirm_request::Union::Skip(true))
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
@ -1048,9 +1047,9 @@ impl Connection {
|
||||
id,
|
||||
file_num,
|
||||
union: if need_override {
|
||||
Some(file_transfer_send_confirm_request::Union::offset_blk(0))
|
||||
Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
|
||||
} else {
|
||||
Some(file_transfer_send_confirm_request::Union::skip(true))
|
||||
Some(file_transfer_send_confirm_request::Union::Skip(true))
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
@ -1452,7 +1451,7 @@ pub mod connection_manager {
|
||||
let mut req = FileTransferSendConfirmRequest {
|
||||
id,
|
||||
file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)),
|
||||
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
|
||||
..Default::default()
|
||||
};
|
||||
let digest = FileTransferDigest {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use super::{CursorData, ResultType};
|
||||
pub use hbb_common::platform::linux::*;
|
||||
use hbb_common::{allow_err, bail, log};
|
||||
use libc::{c_char, c_int, c_void};
|
||||
use std::{
|
||||
@ -8,6 +9,7 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
type Xdo = *const c_void;
|
||||
|
||||
pub const PA_SAMPLE_RATE: u32 = 48000;
|
||||
@ -143,7 +145,75 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_uinput_service() {
|
||||
use crate::server::uinput::service;
|
||||
std::thread::spawn(|| {
|
||||
service::start_service_control();
|
||||
});
|
||||
std::thread::spawn(|| {
|
||||
service::start_service_keyboard();
|
||||
});
|
||||
std::thread::spawn(|| {
|
||||
service::start_service_mouse();
|
||||
});
|
||||
}
|
||||
|
||||
fn try_start_user_service(username: &str) {
|
||||
if username == "" || username == "root" {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(mut cur_username) =
|
||||
run_cmds("ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1".to_owned())
|
||||
{
|
||||
cur_username = cur_username.trim().to_owned();
|
||||
if cur_username != "root" && cur_username != username {
|
||||
let _ = run_cmds(format!(
|
||||
"systemctl --machine={}@.host --user stop rustdesk",
|
||||
&cur_username
|
||||
));
|
||||
} else if cur_username == username {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = run_cmds(format!(
|
||||
"systemctl --machine={}@.host --user start rustdesk",
|
||||
username
|
||||
));
|
||||
}
|
||||
|
||||
fn try_stop_user_service() {
|
||||
if let Ok(mut username) =
|
||||
run_cmds("ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1".to_owned())
|
||||
{
|
||||
username = username.trim().to_owned();
|
||||
if username != "root" {
|
||||
let _ = run_cmds(format!(
|
||||
"systemctl --machine={}@.host --user stop rustdesk",
|
||||
&username
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_server(server: &mut Option<std::process::Child>) {
|
||||
if let Some(mut ps) = server.take() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_status)) => {}
|
||||
Ok(None) => {
|
||||
let _res = ps.wait();
|
||||
}
|
||||
Err(e) => log::error!("error attempting to wait: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_os_service() {
|
||||
start_uinput_service();
|
||||
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
let mut uid = "".to_owned();
|
||||
@ -157,85 +227,106 @@ pub fn start_os_service() {
|
||||
let mut cm0 = false;
|
||||
let mut last_restart = std::time::Instant::now();
|
||||
while running.load(Ordering::SeqCst) {
|
||||
let cm = get_cm();
|
||||
let tmp = get_active_userid();
|
||||
let mut start_new = false;
|
||||
if tmp != uid && !tmp.is_empty() {
|
||||
uid = tmp;
|
||||
log::info!("uid of seat0: {}", uid);
|
||||
let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
|
||||
let mut auth = get_env_tries("XAUTHORITY", &uid, 10);
|
||||
if auth.is_empty() {
|
||||
auth = if std::path::Path::new(&gdm).exists() {
|
||||
gdm
|
||||
} else {
|
||||
let username = get_active_username();
|
||||
if username == "root" {
|
||||
format!("/{}/.Xauthority", username)
|
||||
let username = get_active_username();
|
||||
let is_wayland = current_is_wayland();
|
||||
|
||||
if username == "root" || !is_wayland {
|
||||
// try stop user service
|
||||
try_stop_user_service();
|
||||
|
||||
// try start subprocess "--server"
|
||||
let cm = get_cm();
|
||||
let tmp = get_active_userid();
|
||||
let mut start_new = false;
|
||||
if tmp != uid && !tmp.is_empty() {
|
||||
uid = tmp;
|
||||
log::info!("uid of seat0: {}", uid);
|
||||
let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
|
||||
let mut auth = get_env_tries("XAUTHORITY", &uid, 10);
|
||||
if auth.is_empty() {
|
||||
auth = if std::path::Path::new(&gdm).exists() {
|
||||
gdm
|
||||
} else {
|
||||
let tmp = format!("/home/{}/.Xauthority", username);
|
||||
if std::path::Path::new(&tmp).exists() {
|
||||
tmp
|
||||
let username = get_active_username();
|
||||
if username == "root" {
|
||||
format!("/{}/.Xauthority", username)
|
||||
} else {
|
||||
format!("/var/lib/{}/.Xauthority", username)
|
||||
let tmp = format!("/home/{}/.Xauthority", username);
|
||||
if std::path::Path::new(&tmp).exists() {
|
||||
tmp
|
||||
} else {
|
||||
format!("/var/lib/{}/.Xauthority", username)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
let mut d = get_env("DISPLAY", &uid);
|
||||
if d.is_empty() {
|
||||
d = get_display();
|
||||
}
|
||||
if d.is_empty() {
|
||||
d = ":0".to_owned();
|
||||
}
|
||||
d = d.replace(&whoami::hostname(), "").replace("localhost", "");
|
||||
log::info!("DISPLAY: {}", d);
|
||||
log::info!("XAUTHORITY: {}", auth);
|
||||
std::env::set_var("XAUTHORITY", auth);
|
||||
std::env::set_var("DISPLAY", d);
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
last_restart = std::time::Instant::now();
|
||||
}
|
||||
} else if !cm
|
||||
&& ((cm0 && last_restart.elapsed().as_secs() > 60)
|
||||
|| last_restart.elapsed().as_secs() > 3600)
|
||||
{
|
||||
// restart server if new connections all closed, or every one hour,
|
||||
// as a workaround to resolve "SpotUdp" (dns resolve)
|
||||
// and x server get displays failure issue
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
last_restart = std::time::Instant::now();
|
||||
log::info!("restart server");
|
||||
}
|
||||
}
|
||||
if let Some(ps) = server.as_mut() {
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_)) => {
|
||||
server = None;
|
||||
start_new = true;
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
let mut d = get_env("DISPLAY", &uid);
|
||||
if d.is_empty() {
|
||||
d = get_display();
|
||||
}
|
||||
if d.is_empty() {
|
||||
d = ":0".to_owned();
|
||||
}
|
||||
d = d.replace(&whoami::hostname(), "").replace("localhost", "");
|
||||
log::info!("DISPLAY: {}", d);
|
||||
log::info!("XAUTHORITY: {}", auth);
|
||||
std::env::set_var("XAUTHORITY", auth);
|
||||
std::env::set_var("DISPLAY", d);
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
last_restart = std::time::Instant::now();
|
||||
}
|
||||
} else if !cm
|
||||
&& ((cm0 && last_restart.elapsed().as_secs() > 60)
|
||||
|| last_restart.elapsed().as_secs() > 3600)
|
||||
{
|
||||
// restart server if new connections all closed, or every one hour,
|
||||
// as a workaround to resolve "SpotUdp" (dns resolve)
|
||||
// and x server get displays failure issue
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
last_restart = std::time::Instant::now();
|
||||
log::info!("restart server");
|
||||
}
|
||||
}
|
||||
if let Some(ps) = server.as_mut() {
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_)) => {
|
||||
server = None;
|
||||
start_new = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
start_new = true;
|
||||
}
|
||||
if start_new {
|
||||
match crate::run_me(vec!["--server"]) {
|
||||
Ok(ps) => server = Some(ps),
|
||||
Err(err) => {
|
||||
log::error!("Failed to start server: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
cm0 = cm;
|
||||
} else if username != "" {
|
||||
if username != "gdm" {
|
||||
// try kill subprocess "--server"
|
||||
stop_server(&mut server);
|
||||
|
||||
// try start user service
|
||||
try_start_user_service(&username);
|
||||
}
|
||||
} else {
|
||||
start_new = true;
|
||||
try_stop_user_service();
|
||||
stop_server(&mut server);
|
||||
}
|
||||
if start_new {
|
||||
match crate::run_me(vec!["--server"]) {
|
||||
Ok(ps) => server = Some(ps),
|
||||
Err(err) => {
|
||||
log::error!("Failed to start server: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
cm0 = cm;
|
||||
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
}
|
||||
|
||||
try_stop_user_service();
|
||||
if let Some(ps) = server.take().as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
}
|
||||
@ -246,17 +337,6 @@ pub fn get_active_userid() -> String {
|
||||
get_value_of_seat0(1)
|
||||
}
|
||||
|
||||
fn is_active(sid: &str) -> bool {
|
||||
if let Ok(output) = std::process::Command::new("loginctl")
|
||||
.args(vec!["show-session", "-p", "State", sid])
|
||||
.output()
|
||||
{
|
||||
String::from_utf8_lossy(&output.stdout).contains("active")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cm() -> bool {
|
||||
if let Ok(output) = std::process::Command::new("ps").args(vec!["aux"]).output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
@ -312,89 +392,6 @@ fn get_display() -> String {
|
||||
last
|
||||
}
|
||||
|
||||
fn get_value_of_seat0(i: usize) -> String {
|
||||
if let Ok(output) = std::process::Command::new("loginctl").output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
if line.contains("seat0") {
|
||||
if let Some(sid) = line.split_whitespace().nth(0) {
|
||||
if is_active(sid) {
|
||||
if let Some(uid) = line.split_whitespace().nth(i) {
|
||||
return uid.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73
|
||||
if let Ok(output) = std::process::Command::new("loginctl").output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
if let Some(sid) = line.split_whitespace().nth(0) {
|
||||
let d = get_display_server_of_session(sid);
|
||||
if is_active(sid) && d != "tty" {
|
||||
if let Some(uid) = line.split_whitespace().nth(i) {
|
||||
return uid.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
pub fn get_display_server() -> String {
|
||||
let session = get_value_of_seat0(0);
|
||||
get_display_server_of_session(&session)
|
||||
}
|
||||
|
||||
fn get_display_server_of_session(session: &str) -> String {
|
||||
if let Ok(output) = std::process::Command::new("loginctl")
|
||||
.args(vec!["show-session", "-p", "Type", session])
|
||||
.output()
|
||||
// Check session type of the session
|
||||
{
|
||||
let display_server = String::from_utf8_lossy(&output.stdout)
|
||||
.replace("Type=", "")
|
||||
.trim_end()
|
||||
.into();
|
||||
if display_server == "tty" {
|
||||
// If the type is tty...
|
||||
if let Ok(output) = std::process::Command::new("loginctl")
|
||||
.args(vec!["show-session", "-p", "TTY", session])
|
||||
.output()
|
||||
// Get the tty number
|
||||
{
|
||||
let tty: String = String::from_utf8_lossy(&output.stdout)
|
||||
.replace("TTY=", "")
|
||||
.trim_end()
|
||||
.into();
|
||||
if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty))
|
||||
// And check if Xorg is running on that tty
|
||||
{
|
||||
if xorg_results.trim_end().to_string() != "" {
|
||||
// If it is, manually return "x11", otherwise return tty
|
||||
"x11".to_owned()
|
||||
} else {
|
||||
display_server
|
||||
}
|
||||
} else {
|
||||
// If any of these commands fail just fall back to the display server
|
||||
display_server
|
||||
}
|
||||
} else {
|
||||
display_server
|
||||
}
|
||||
} else {
|
||||
// If the session is not a tty, then just return the type as usual
|
||||
display_server
|
||||
}
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_login_wayland() -> bool {
|
||||
if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") {
|
||||
contents.contains("#WaylandEnable=false")
|
||||
@ -601,13 +598,6 @@ pub fn is_installed() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn run_cmds(cmds: String) -> ResultType<String> {
|
||||
let output = std::process::Command::new("sh")
|
||||
.args(vec!["-c", &cmds])
|
||||
.output()?;
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
fn get_env_tries(name: &str, uid: &str, n: usize) -> String {
|
||||
for _ in 0..n {
|
||||
let x = get_env(name, uid);
|
||||
|
||||
@ -120,21 +120,21 @@ async fn connect_and_login(
|
||||
Ok(Some(Ok(bytes))) => {
|
||||
let msg_in = Message::parse_from_bytes(&bytes)?;
|
||||
match msg_in.union {
|
||||
Some(message::Union::hash(hash)) => {
|
||||
Some(message::Union::Hash(hash)) => {
|
||||
interface.handle_hash(hash, &mut stream).await;
|
||||
}
|
||||
Some(message::Union::login_response(lr)) => match lr.union {
|
||||
Some(login_response::Union::error(err)) => {
|
||||
Some(message::Union::LoginResponse(lr)) => match lr.union {
|
||||
Some(login_response::Union::Error(err)) => {
|
||||
interface.handle_login_error(&err);
|
||||
return Ok(None);
|
||||
}
|
||||
Some(login_response::Union::peer_info(pi)) => {
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
interface.handle_peer_info(pi);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(message::Union::test_delay(t)) => {
|
||||
Some(message::Union::TestDelay(t)) => {
|
||||
interface.handle_test_delay(t, &mut stream).await;
|
||||
}
|
||||
_ => {}
|
||||
@ -183,7 +183,7 @@ async fn run_forward(forward: Framed<TcpStream, BytesCodec>, stream: Stream) ->
|
||||
},
|
||||
res = stream.next() => {
|
||||
if let Some(Ok(bytes)) = res {
|
||||
allow_err!(forward.send(bytes.into()).await);
|
||||
allow_err!(forward.send(bytes).await);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::server::{check_zombie, new as new_server, ServerPtr};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::bail,
|
||||
config::{self, Config, REG_INTERVAL, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
|
||||
config::{Config, REG_INTERVAL, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
|
||||
futures::future::join_all,
|
||||
log,
|
||||
protobuf::Message as _,
|
||||
@ -51,9 +51,12 @@ impl RendezvousMediator {
|
||||
check_zombie();
|
||||
let server = new_server();
|
||||
if Config::get_nat_type() == NatType::UNKNOWN_NAT as i32 {
|
||||
crate::common::test_nat_type();
|
||||
crate::test_nat_type();
|
||||
nat_tested = true;
|
||||
}
|
||||
if !Config::get_option("stop-service").is_empty() {
|
||||
crate::test_rendezvous_server();
|
||||
}
|
||||
let server_cloned = server.clone();
|
||||
tokio::spawn(async move {
|
||||
direct_server(server_cloned).await;
|
||||
@ -61,14 +64,14 @@ impl RendezvousMediator {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if crate::platform::is_installed() {
|
||||
std::thread::spawn(move || {
|
||||
allow_err!(lan_discovery());
|
||||
allow_err!(super::lan::start_listening());
|
||||
});
|
||||
}
|
||||
loop {
|
||||
Config::reset_online();
|
||||
if Config::get_option("stop-service").is_empty() {
|
||||
if !nat_tested {
|
||||
crate::common::test_nat_type();
|
||||
crate::test_nat_type();
|
||||
nat_tested = true;
|
||||
}
|
||||
let mut futs = Vec::new();
|
||||
@ -157,7 +160,7 @@ impl RendezvousMediator {
|
||||
Some(Ok((bytes, _))) => {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::register_peer_response(rpr)) => {
|
||||
Some(rendezvous_message::Union::RegisterPeerResponse(rpr)) => {
|
||||
update_latency();
|
||||
if rpr.request_pk {
|
||||
log::info!("request_pk received from {}", host);
|
||||
@ -165,7 +168,7 @@ impl RendezvousMediator {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Some(rendezvous_message::Union::register_pk_response(rpr)) => {
|
||||
Some(rendezvous_message::Union::RegisterPkResponse(rpr)) => {
|
||||
update_latency();
|
||||
match rpr.result.enum_value_or_default() {
|
||||
register_pk_response::Result::OK => {
|
||||
@ -179,28 +182,28 @@ impl RendezvousMediator {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some(rendezvous_message::Union::punch_hole(ph)) => {
|
||||
Some(rendezvous_message::Union::PunchHole(ph)) => {
|
||||
let rz = rz.clone();
|
||||
let server = server.clone();
|
||||
tokio::spawn(async move {
|
||||
allow_err!(rz.handle_punch_hole(ph, server).await);
|
||||
});
|
||||
}
|
||||
Some(rendezvous_message::Union::request_relay(rr)) => {
|
||||
Some(rendezvous_message::Union::RequestRelay(rr)) => {
|
||||
let rz = rz.clone();
|
||||
let server = server.clone();
|
||||
tokio::spawn(async move {
|
||||
allow_err!(rz.handle_request_relay(rr, server).await);
|
||||
});
|
||||
}
|
||||
Some(rendezvous_message::Union::fetch_local_addr(fla)) => {
|
||||
Some(rendezvous_message::Union::FetchLocalAddr(fla)) => {
|
||||
let rz = rz.clone();
|
||||
let server = server.clone();
|
||||
tokio::spawn(async move {
|
||||
allow_err!(rz.handle_intranet(fla, server).await);
|
||||
});
|
||||
}
|
||||
Some(rendezvous_message::Union::configure_update(cu)) => {
|
||||
Some(rendezvous_message::Union::ConfigureUpdate(cu)) => {
|
||||
let v0 = Config::get_rendezvous_servers();
|
||||
Config::set_option("rendezvous-servers".to_owned(), cu.rendezvous_servers.join(","));
|
||||
Config::set_serial(cu.serial);
|
||||
@ -367,7 +370,7 @@ impl RendezvousMediator {
|
||||
socket
|
||||
};
|
||||
let mut msg_out = Message::new();
|
||||
use hbb_common::protobuf::ProtobufEnum;
|
||||
use hbb_common::protobuf::Enum;
|
||||
let nat_type = NatType::from_i32(Config::get_nat_type()).unwrap_or(NatType::UNKNOWN_NAT);
|
||||
msg_out.set_punch_hole_sent(PunchHoleSent {
|
||||
socket_addr: ph.socket_addr,
|
||||
@ -386,7 +389,7 @@ impl RendezvousMediator {
|
||||
async fn register_pk(&mut self, socket: &mut FramedSocket) -> ResultType<()> {
|
||||
let mut msg_out = Message::new();
|
||||
let pk = Config::get_key_pair().1;
|
||||
let uuid = crate::get_uuid();
|
||||
let uuid = hbb_common::get_uuid();
|
||||
let id = Config::get_id();
|
||||
self.last_id_pk_registry = id.clone();
|
||||
msg_out.set_register_pk(RegisterPk {
|
||||
@ -537,103 +540,3 @@ async fn direct_server(server: ServerPtr) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_broadcast_port() -> u16 {
|
||||
(RENDEZVOUS_PORT + 3) as _
|
||||
}
|
||||
|
||||
pub fn get_mac() -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(Some(mac)) = mac_address::get_mac_address() {
|
||||
mac.to_string()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
fn lan_discovery() -> ResultType<()> {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], get_broadcast_port()));
|
||||
let socket = std::net::UdpSocket::bind(addr)?;
|
||||
socket.set_read_timeout(Some(std::time::Duration::from_millis(1000)))?;
|
||||
log::info!("lan discovery listener started");
|
||||
loop {
|
||||
let mut buf = [0; 2048];
|
||||
if let Ok((len, addr)) = socket.recv_from(&mut buf) {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::peer_discovery(p)) => {
|
||||
if p.cmd == "ping" {
|
||||
let mut msg_out = Message::new();
|
||||
let peer = PeerDiscovery {
|
||||
cmd: "pong".to_owned(),
|
||||
mac: get_mac(),
|
||||
id: Config::get_id(),
|
||||
hostname: whoami::hostname(),
|
||||
username: crate::platform::get_active_username(),
|
||||
platform: whoami::platform().to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
msg_out.set_peer_discovery(peer);
|
||||
socket.send_to(&msg_out.write_to_bytes()?, addr).ok();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn discover() -> ResultType<()> {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 0));
|
||||
let socket = std::net::UdpSocket::bind(addr)?;
|
||||
socket.set_broadcast(true)?;
|
||||
let mut msg_out = Message::new();
|
||||
let peer = PeerDiscovery {
|
||||
cmd: "ping".to_owned(),
|
||||
..Default::default()
|
||||
};
|
||||
msg_out.set_peer_discovery(peer);
|
||||
let maddr = SocketAddr::from(([255, 255, 255, 255], get_broadcast_port()));
|
||||
socket.send_to(&msg_out.write_to_bytes()?, maddr)?;
|
||||
log::info!("discover ping sent");
|
||||
let mut last_recv_time = Instant::now();
|
||||
let mut last_write_time = Instant::now();
|
||||
let mut last_write_n = 0;
|
||||
// to-do: load saved peers, and update incrementally (then we can see offline)
|
||||
let mut peers = Vec::new();
|
||||
let mac = get_mac();
|
||||
socket.set_read_timeout(Some(std::time::Duration::from_millis(10)))?;
|
||||
loop {
|
||||
let mut buf = [0; 2048];
|
||||
if let Ok((len, _)) = socket.recv_from(&mut buf) {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::peer_discovery(p)) => {
|
||||
last_recv_time = Instant::now();
|
||||
if p.cmd == "pong" {
|
||||
if p.mac != mac {
|
||||
peers.push((p.id, p.username, p.hostname, p.platform));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if last_write_time.elapsed().as_millis() > 300 && last_write_n != peers.len() {
|
||||
config::LanPeers::store(serde_json::to_string(&peers)?);
|
||||
last_write_time = Instant::now();
|
||||
last_write_n = peers.len();
|
||||
}
|
||||
if last_recv_time.elapsed().as_millis() > 3_000 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
log::info!("discover ping done");
|
||||
config::LanPeers::store(serde_json::to_string(&peers)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ use hbb_common::{
|
||||
config::{Config, Config2, CONNECT_TIMEOUT, RELAY_PORT},
|
||||
log,
|
||||
message_proto::*,
|
||||
protobuf::{Message as _, ProtobufEnum},
|
||||
protobuf::{Enum, Message as _},
|
||||
rendezvous_proto::*,
|
||||
socket_client,
|
||||
sodiumoxide::crypto::{box_, secretbox, sign},
|
||||
@ -24,6 +24,10 @@ pub mod audio_service;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(any(target_os = "android", target_os = "ios")))] {
|
||||
mod clipboard_service;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod wayland;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod uinput;
|
||||
pub mod input_service;
|
||||
} else {
|
||||
mod clipboard_service {
|
||||
@ -140,7 +144,7 @@ pub async fn create_tcp_connection(
|
||||
Some(res) => {
|
||||
let bytes = res?;
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||
if let Some(message::Union::public_key(pk)) = msg_in.union {
|
||||
if let Some(message::Union::PublicKey(pk)) = msg_in.union {
|
||||
if pk.asymmetric_value.len() == box_::PUBLICKEYBYTES {
|
||||
let nonce = box_::Nonce([0u8; box_::NONCEBYTES]);
|
||||
let mut pk_ = [0u8; box_::PUBLICKEYBYTES];
|
||||
@ -279,6 +283,8 @@ impl Drop for Server {
|
||||
for s in self.services.values() {
|
||||
s.join();
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
wayland::clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ use crate::video_service;
|
||||
use crate::{common::MOBILE_INFO2, mobile::connection_manager::start_channel};
|
||||
use crate::{ipc, VERSION};
|
||||
use hbb_common::fs::can_enable_overwrite_detection;
|
||||
use hbb_common::password_security::password;
|
||||
use hbb_common::{
|
||||
config::Config,
|
||||
fs,
|
||||
@ -35,6 +36,7 @@ pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref LOGIN_FAILURES: Arc::<Mutex<HashMap<String, (i32, i32, i32)>>> = Default::default();
|
||||
static ref SESSIONS: Arc::<Mutex<HashMap<String, Session>>> = Default::default();
|
||||
}
|
||||
pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
@ -53,6 +55,14 @@ enum MessageInput {
|
||||
BlockOff,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Session {
|
||||
name: String,
|
||||
session_id: u64,
|
||||
last_recv_time: Arc<Mutex<Instant>>,
|
||||
random_password: String,
|
||||
}
|
||||
|
||||
pub struct Connection {
|
||||
inner: ConnInner,
|
||||
stream: super::Stream,
|
||||
@ -80,6 +90,8 @@ pub struct Connection {
|
||||
video_ack_required: bool,
|
||||
peer_info: (String, String),
|
||||
api_server: String,
|
||||
lr: LoginRequest,
|
||||
last_recv_time: Arc<Mutex<Instant>>,
|
||||
}
|
||||
|
||||
impl Subscriber for ConnInner {
|
||||
@ -91,7 +103,7 @@ impl Subscriber for ConnInner {
|
||||
#[inline]
|
||||
fn send(&mut self, msg: Arc<Message>) {
|
||||
match &msg.union {
|
||||
Some(message::Union::video_frame(_)) => {
|
||||
Some(message::Union::VideoFrame(_)) => {
|
||||
self.tx_video.as_mut().map(|tx| {
|
||||
allow_err!(tx.send((Instant::now(), msg)));
|
||||
});
|
||||
@ -111,6 +123,7 @@ const H1: Duration = Duration::from_secs(3600);
|
||||
const MILLI1: Duration = Duration::from_millis(1);
|
||||
const SEND_TIMEOUT_VIDEO: u64 = 12_000;
|
||||
const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10;
|
||||
const SESSION_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
impl Connection {
|
||||
pub async fn start(
|
||||
@ -164,6 +177,8 @@ impl Connection {
|
||||
video_ack_required: false,
|
||||
peer_info: Default::default(),
|
||||
api_server: "".to_owned(),
|
||||
lr: Default::default(),
|
||||
last_recv_time: Arc::new(Mutex::new(Instant::now())),
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
tokio::spawn(async move {
|
||||
@ -222,7 +237,8 @@ impl Connection {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
conn.send(msg_out).await;
|
||||
conn.on_close("Close requested from connection manager", false);
|
||||
conn.on_close("Close requested from connection manager", false).await;
|
||||
SESSIONS.lock().unwrap().remove(&conn.lr.my_id);
|
||||
break;
|
||||
}
|
||||
ipc::Data::ChatMessage{text} => {
|
||||
@ -311,11 +327,12 @@ impl Connection {
|
||||
if let Some(res) = res {
|
||||
match res {
|
||||
Err(err) => {
|
||||
conn.on_close(&err.to_string(), true);
|
||||
conn.on_close(&err.to_string(), true).await;
|
||||
break;
|
||||
},
|
||||
Ok(bytes) => {
|
||||
last_recv_time = Instant::now();
|
||||
*conn.last_recv_time.lock().unwrap() = Instant::now();
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||
if !conn.on_message(msg_in).await {
|
||||
break;
|
||||
@ -324,14 +341,14 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
conn.on_close("Reset by the peer", true);
|
||||
conn.on_close("Reset by the peer", true).await;
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = conn.timer.tick() => {
|
||||
if !conn.read_jobs.is_empty() {
|
||||
if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await {
|
||||
conn.on_close(&err.to_string(), false);
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@ -344,7 +361,7 @@ impl Connection {
|
||||
video_service::notify_video_frame_feched(id, Some(instant.into()));
|
||||
}
|
||||
if let Err(err) = conn.stream.send(&value as &Message).await {
|
||||
conn.on_close(&err.to_string(), false);
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -354,7 +371,7 @@ impl Connection {
|
||||
|
||||
if latency > 1000 {
|
||||
match &msg.union {
|
||||
Some(message::Union::audio_frame(_)) => {
|
||||
Some(message::Union::AudioFrame(_)) => {
|
||||
// log::info!("audio frame latency {}", instant.elapsed().as_secs_f32());
|
||||
continue;
|
||||
}
|
||||
@ -362,13 +379,13 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
if let Err(err) = conn.stream.send(msg).await {
|
||||
conn.on_close(&err.to_string(), false);
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = test_delay_timer.tick() => {
|
||||
if last_recv_time.elapsed() >= SEC30 {
|
||||
conn.on_close("Timeout", true);
|
||||
conn.on_close("Timeout", true).await;
|
||||
break;
|
||||
}
|
||||
let time = crate::get_time();
|
||||
@ -398,8 +415,9 @@ impl Connection {
|
||||
video_service::notify_video_frame_feched(id, None);
|
||||
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
|
||||
video_service::VIDEO_QOS.lock().unwrap().reset();
|
||||
password::after_session(conn.authorized);
|
||||
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
|
||||
conn.on_close(&err.to_string(), false);
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
}
|
||||
|
||||
conn.post_audit(json!({
|
||||
@ -493,7 +511,7 @@ impl Connection {
|
||||
res = self.stream.next() => {
|
||||
if let Some(res) = res {
|
||||
last_recv_time = Instant::now();
|
||||
timeout(SEND_TIMEOUT_OTHER, forward.send(res?.into())).await??;
|
||||
timeout(SEND_TIMEOUT_OTHER, forward.send(res?)).await??;
|
||||
} else {
|
||||
bail!("Stream reset by the peer");
|
||||
}
|
||||
@ -572,7 +590,7 @@ impl Connection {
|
||||
let url = self.api_server.clone();
|
||||
let mut v = v;
|
||||
v["id"] = json!(Config::get_id());
|
||||
v["uuid"] = json!(base64::encode(crate::get_uuid()));
|
||||
v["uuid"] = json!(base64::encode(hbb_common::get_uuid()));
|
||||
v["Id"] = json!(self.inner.id);
|
||||
tokio::spawn(async move {
|
||||
allow_err!(Self::post_audit_async(url, v).await);
|
||||
@ -629,9 +647,9 @@ impl Connection {
|
||||
#[cfg(target_os = "linux")]
|
||||
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() {
|
||||
let dtype = crate::platform::linux::get_display_server();
|
||||
if dtype != "x11" {
|
||||
if dtype != "x11" && dtype != "wayland" {
|
||||
res.set_error(format!(
|
||||
"Unsupported display server type {}, x11 expected",
|
||||
"Unsupported display server type {}, x11 or wayland expected",
|
||||
dtype
|
||||
));
|
||||
let mut msg_out = Message::new();
|
||||
@ -667,7 +685,7 @@ impl Connection {
|
||||
res.set_peer_info(pi);
|
||||
} else {
|
||||
try_activate_screen();
|
||||
match video_service::get_displays() {
|
||||
match super::video_service::get_displays().await {
|
||||
Err(err) => {
|
||||
res.set_error(format!("X11 error: {}", err));
|
||||
}
|
||||
@ -779,8 +797,77 @@ impl Connection {
|
||||
self.tx_input.send(MessageInput::Key((msg, press))).ok();
|
||||
}
|
||||
|
||||
fn validate_one_password(&self, password: String) -> bool {
|
||||
if password.len() == 0 {
|
||||
return false;
|
||||
}
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(password);
|
||||
hasher.update(&self.hash.salt);
|
||||
let mut hasher2 = Sha256::new();
|
||||
hasher2.update(&hasher.finalize()[..]);
|
||||
hasher2.update(&self.hash.challenge);
|
||||
hasher2.finalize()[..] == self.lr.password[..]
|
||||
}
|
||||
|
||||
fn validate_password(&mut self) -> bool {
|
||||
if password::security_enabled() {
|
||||
if self.validate_one_password(Config::get_security_password()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if password::random_password_valid() {
|
||||
let password = password::random_password();
|
||||
if self.validate_one_password(password.clone()) {
|
||||
if password::onetime_password_activated() {
|
||||
password::set_onetime_password_activated(false);
|
||||
}
|
||||
SESSIONS.lock().unwrap().insert(
|
||||
self.lr.my_id.clone(),
|
||||
Session {
|
||||
name: self.lr.my_name.clone(),
|
||||
session_id: self.lr.session_id,
|
||||
last_recv_time: self.last_recv_time.clone(),
|
||||
random_password: password,
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_of_recent_session(&mut self) -> bool {
|
||||
let session = SESSIONS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&self.lr.my_id)
|
||||
.map(|s| s.to_owned());
|
||||
if let Some(session) = session {
|
||||
if session.name == self.lr.my_name
|
||||
&& session.session_id == self.lr.session_id
|
||||
&& !self.lr.password.is_empty()
|
||||
&& self.validate_one_password(session.random_password.clone())
|
||||
&& session.last_recv_time.lock().unwrap().elapsed() < SESSION_TIMEOUT
|
||||
{
|
||||
SESSIONS.lock().unwrap().insert(
|
||||
self.lr.my_id.clone(),
|
||||
Session {
|
||||
name: self.lr.my_name.clone(),
|
||||
session_id: self.lr.session_id,
|
||||
last_recv_time: self.last_recv_time.clone(),
|
||||
random_password: session.random_password,
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, msg: Message) -> bool {
|
||||
if let Some(message::Union::login_request(lr)) = msg.union {
|
||||
if let Some(message::Union::LoginRequest(lr)) = msg.union {
|
||||
self.lr = lr.clone();
|
||||
if let Some(o) = lr.option.as_ref() {
|
||||
self.update_option(o).await;
|
||||
if let Some(q) = o.video_codec_state.clone().take() {
|
||||
@ -805,7 +892,7 @@ impl Connection {
|
||||
return true;
|
||||
}
|
||||
match lr.union {
|
||||
Some(login_request::Union::file_transfer(ft)) => {
|
||||
Some(login_request::Union::FileTransfer(ft)) => {
|
||||
if !Config::get_option("enable-file-transfer").is_empty() {
|
||||
self.send_login_error("No permission of file transfer")
|
||||
.await;
|
||||
@ -814,7 +901,7 @@ impl Connection {
|
||||
}
|
||||
self.file_transfer = Some((ft.dir, ft.show_hidden));
|
||||
}
|
||||
Some(login_request::Union::port_forward(mut pf)) => {
|
||||
Some(login_request::Union::PortForward(mut pf)) => {
|
||||
if !Config::get_option("enable-tunnel").is_empty() {
|
||||
self.send_login_error("No permission of IP tunneling").await;
|
||||
sleep(1.).await;
|
||||
@ -851,15 +938,19 @@ impl Connection {
|
||||
}
|
||||
if !crate::is_ip(&lr.username) && lr.username != Config::get_id() {
|
||||
self.send_login_error("Offline").await;
|
||||
} else if self.is_of_recent_session() {
|
||||
self.try_start_cm(lr.my_id, lr.my_name, true);
|
||||
self.send_logon_response().await;
|
||||
if self.port_forward_socket.is_some() {
|
||||
return false;
|
||||
}
|
||||
} else if lr.password.is_empty() {
|
||||
self.try_start_cm(lr.my_id, lr.my_name, false);
|
||||
} else {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&Config::get_password());
|
||||
hasher.update(&self.hash.salt);
|
||||
let mut hasher2 = Sha256::new();
|
||||
hasher2.update(&hasher.finalize()[..]);
|
||||
hasher2.update(&self.hash.challenge);
|
||||
if password::passwords().len() == 0 {
|
||||
self.send_login_error("Connection not allowed").await;
|
||||
return false;
|
||||
}
|
||||
let mut failure = LOGIN_FAILURES
|
||||
.lock()
|
||||
.unwrap()
|
||||
@ -872,7 +963,7 @@ impl Connection {
|
||||
.await;
|
||||
} else if time == failure.0 && failure.1 > 6 {
|
||||
self.send_login_error("Please try 1 minute later").await;
|
||||
} else if hasher2.finalize()[..] != lr.password[..] {
|
||||
} else if !self.validate_password() {
|
||||
if failure.0 == time {
|
||||
failure.1 += 1;
|
||||
failure.2 += 1;
|
||||
@ -898,7 +989,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(message::Union::test_delay(t)) = msg.union {
|
||||
} else if let Some(message::Union::TestDelay(t)) = msg.union {
|
||||
if t.from_client {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_test_delay(t);
|
||||
@ -913,7 +1004,7 @@ impl Connection {
|
||||
}
|
||||
} else if self.authorized {
|
||||
match msg.union {
|
||||
Some(message::Union::mouse_event(me)) => {
|
||||
Some(message::Union::MouseEvent(me)) => {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
if let Err(e) = call_main_service_mouse_input(me.mask, me.x, me.y) {
|
||||
log::debug!("call_main_service_mouse_input fail:{}", e);
|
||||
@ -928,7 +1019,7 @@ impl Connection {
|
||||
self.input_mouse(me, self.inner.id());
|
||||
}
|
||||
}
|
||||
Some(message::Union::key_event(me)) => {
|
||||
Some(message::Union::KeyEvent(me)) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.keyboard {
|
||||
if is_enter(&me) {
|
||||
@ -944,8 +1035,8 @@ impl Connection {
|
||||
};
|
||||
if is_press {
|
||||
match me.union {
|
||||
Some(key_event::Union::unicode(_))
|
||||
| Some(key_event::Union::seq(_)) => {
|
||||
Some(key_event::Union::Unicode(_))
|
||||
| Some(key_event::Union::Seq(_)) => {
|
||||
self.input_key(me, false);
|
||||
}
|
||||
_ => {
|
||||
@ -957,14 +1048,14 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::clipboard(cb)) =>
|
||||
Some(message::Union::Clipboard(cb)) =>
|
||||
{
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.clipboard {
|
||||
update_clipboard(cb, None);
|
||||
}
|
||||
}
|
||||
Some(message::Union::cliprdr(_clip)) => {
|
||||
Some(message::Union::Cliprdr(_clip)) => {
|
||||
if self.file_transfer_enabled() {
|
||||
#[cfg(windows)]
|
||||
if let Some(clip) = msg_2_clip(_clip) {
|
||||
@ -972,13 +1063,13 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::file_action(fa)) => {
|
||||
Some(message::Union::FileAction(fa)) => {
|
||||
if self.file_transfer.is_some() {
|
||||
match fa.union {
|
||||
Some(file_action::Union::read_dir(rd)) => {
|
||||
Some(file_action::Union::ReadDir(rd)) => {
|
||||
self.read_dir(&rd.path, rd.include_hidden);
|
||||
}
|
||||
Some(file_action::Union::all_files(f)) => {
|
||||
Some(file_action::Union::AllFiles(f)) => {
|
||||
match fs::get_recursive_files(&f.path, f.include_hidden) {
|
||||
Err(err) => {
|
||||
self.send(fs::new_error(f.id, err, -1)).await;
|
||||
@ -988,7 +1079,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(file_action::Union::send(s)) => {
|
||||
Some(file_action::Union::Send(s)) => {
|
||||
let id = s.id;
|
||||
let od =
|
||||
can_enable_overwrite_detection(get_version_number(VERSION));
|
||||
@ -1013,7 +1104,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(file_action::Union::receive(r)) => {
|
||||
Some(file_action::Union::Receive(r)) => {
|
||||
self.send_fs(ipc::FS::NewWrite {
|
||||
path: r.path,
|
||||
id: r.id,
|
||||
@ -1026,31 +1117,31 @@ impl Connection {
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
Some(file_action::Union::remove_dir(d)) => {
|
||||
Some(file_action::Union::RemoveDir(d)) => {
|
||||
self.send_fs(ipc::FS::RemoveDir {
|
||||
path: d.path,
|
||||
id: d.id,
|
||||
recursive: d.recursive,
|
||||
});
|
||||
}
|
||||
Some(file_action::Union::remove_file(f)) => {
|
||||
Some(file_action::Union::RemoveFile(f)) => {
|
||||
self.send_fs(ipc::FS::RemoveFile {
|
||||
path: f.path,
|
||||
id: f.id,
|
||||
file_num: f.file_num,
|
||||
});
|
||||
}
|
||||
Some(file_action::Union::create(c)) => {
|
||||
Some(file_action::Union::Create(c)) => {
|
||||
self.send_fs(ipc::FS::CreateDir {
|
||||
path: c.path,
|
||||
id: c.id,
|
||||
});
|
||||
}
|
||||
Some(file_action::Union::cancel(c)) => {
|
||||
Some(file_action::Union::Cancel(c)) => {
|
||||
self.send_fs(ipc::FS::CancelWrite { id: c.id });
|
||||
fs::remove_job(c.id, &mut self.read_jobs);
|
||||
}
|
||||
Some(file_action::Union::send_confirm(r)) => {
|
||||
Some(file_action::Union::SendConfirm(r)) => {
|
||||
if let Some(job) = fs::get_job(r.id, &mut self.read_jobs) {
|
||||
job.confirm(&r);
|
||||
}
|
||||
@ -1059,8 +1150,8 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::file_response(fr)) => match fr.union {
|
||||
Some(file_response::Union::block(block)) => {
|
||||
Some(message::Union::FileResponse(fr)) => match fr.union {
|
||||
Some(file_response::Union::Block(block)) => {
|
||||
self.send_fs(ipc::FS::WriteBlock {
|
||||
id: block.id,
|
||||
file_num: block.file_num,
|
||||
@ -1068,13 +1159,13 @@ impl Connection {
|
||||
compressed: block.compressed,
|
||||
});
|
||||
}
|
||||
Some(file_response::Union::done(d)) => {
|
||||
Some(file_response::Union::Done(d)) => {
|
||||
self.send_fs(ipc::FS::WriteDone {
|
||||
id: d.id,
|
||||
file_num: d.file_num,
|
||||
});
|
||||
}
|
||||
Some(file_response::Union::digest(d)) => self.send_fs(ipc::FS::CheckDigest {
|
||||
Some(file_response::Union::Digest(d)) => self.send_fs(ipc::FS::CheckDigest {
|
||||
id: d.id,
|
||||
file_num: d.file_num,
|
||||
file_size: d.file_size,
|
||||
@ -1083,27 +1174,32 @@ impl Connection {
|
||||
}),
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::misc(misc)) => match misc.union {
|
||||
Some(misc::Union::switch_display(s)) => {
|
||||
video_service::switch_display(s.display);
|
||||
Some(message::Union::Misc(misc)) => match misc.union {
|
||||
Some(misc::Union::SwitchDisplay(s)) => {
|
||||
video_service::switch_display(s.display).await;
|
||||
}
|
||||
Some(misc::Union::chat_message(c)) => {
|
||||
Some(misc::Union::ChatMessage(c)) => {
|
||||
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
|
||||
}
|
||||
Some(misc::Union::option(o)) => {
|
||||
Some(misc::Union::Option(o)) => {
|
||||
self.update_option(&o).await;
|
||||
}
|
||||
Some(misc::Union::refresh_video(r)) => {
|
||||
Some(misc::Union::RefreshVideo(r)) => {
|
||||
if r {
|
||||
video_service::refresh();
|
||||
super::video_service::refresh();
|
||||
}
|
||||
}
|
||||
Some(misc::Union::video_received(_)) => {
|
||||
Some(misc::Union::VideoReceived(_)) => {
|
||||
video_service::notify_video_frame_feched(
|
||||
self.inner.id,
|
||||
Some(Instant::now().into()),
|
||||
);
|
||||
}
|
||||
Some(misc::Union::CloseReason(_)) => {
|
||||
self.on_close("Peer close", true).await;
|
||||
SESSIONS.lock().unwrap().remove(&self.lr.my_id);
|
||||
return false;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
@ -1258,14 +1354,14 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_close(&mut self, reason: &str, lock: bool) {
|
||||
async fn on_close(&mut self, reason: &str, lock: bool) {
|
||||
if let Some(s) = self.server.upgrade() {
|
||||
s.write().unwrap().remove_connection(&self.inner);
|
||||
}
|
||||
log::info!("#{} Connection closed: {}", self.inner.id(), reason);
|
||||
if lock && self.lock_after_session_end && self.keyboard {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
lock_screen();
|
||||
lock_screen().await;
|
||||
}
|
||||
self.tx_to_cm.send(ipc::Data::Close).ok();
|
||||
self.port_forward_socket.take();
|
||||
|
||||
@ -2,7 +2,7 @@ use super::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
use dispatch::Queue;
|
||||
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use hbb_common::{config::COMPRESS_LEVEL, protobuf::ProtobufEnumOrUnknown};
|
||||
use hbb_common::{config::COMPRESS_LEVEL, protobuf::ProtobufEnumOrUnknown, protobuf::EnumOrUnknown};
|
||||
use rdev::{simulate, EventType, Key as RdevKey};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
@ -69,7 +69,7 @@ impl Subscriber for MouseCursorSub {
|
||||
|
||||
#[inline]
|
||||
fn send(&mut self, msg: Arc<Message>) {
|
||||
if let Some(message::Union::cursor_data(cd)) = &msg.union {
|
||||
if let Some(message::Union::CursorData(cd)) = &msg.union {
|
||||
if let Some(msg) = self.cached.get(&cd.id) {
|
||||
self.inner.send(msg.clone());
|
||||
} else {
|
||||
@ -188,6 +188,26 @@ lazy_static::lazy_static! {
|
||||
static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn set_uinput() -> ResultType<()> {
|
||||
// Keyboard and mouse both open /dev/uinput
|
||||
// TODO: Make sure there's no race
|
||||
let keyboard = super::uinput::client::UInputKeyboard::new().await?;
|
||||
log::info!("UInput keyboard created");
|
||||
let mouse = super::uinput::client::UInputMouse::new().await?;
|
||||
log::info!("UInput mouse created");
|
||||
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
en.set_uinput_keyboard(Some(Box::new(keyboard)));
|
||||
en.set_uinput_mouse(Some(Box::new(mouse)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn set_uinput_resolution(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultType<()> {
|
||||
super::uinput::client::set_resolution(minx, maxx, miny, maxy).await
|
||||
}
|
||||
|
||||
pub fn is_left_up(evt: &MouseEvent) -> bool {
|
||||
let buttons = evt.mask >> 3;
|
||||
let evt_type = evt.mask & 0x7;
|
||||
@ -300,12 +320,12 @@ fn fix_key_down_timeout(force: bool) {
|
||||
// e.g. current state of ctrl is down, but ctrl not in modifier, we should change ctrl to up, to make modifier state sync between remote and local
|
||||
#[inline]
|
||||
fn fix_modifier(
|
||||
modifiers: &[ProtobufEnumOrUnknown<ControlKey>],
|
||||
modifiers: &[EnumOrUnknown<ControlKey>],
|
||||
key0: ControlKey,
|
||||
key1: Key,
|
||||
en: &mut Enigo,
|
||||
) {
|
||||
if get_modifier_state(key1, en) && !modifiers.contains(&ProtobufEnumOrUnknown::new(key0)) {
|
||||
if get_modifier_state(key1, en) && !modifiers.contains(&EnumOrUnknown::new(key0)) {
|
||||
#[cfg(windows)]
|
||||
if key0 == ControlKey::Control && get_modifier_state(Key::Alt, en) {
|
||||
// AltGr case
|
||||
@ -316,7 +336,7 @@ fn fix_modifier(
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_modifiers(modifiers: &[ProtobufEnumOrUnknown<ControlKey>], en: &mut Enigo, ck: i32) {
|
||||
fn fix_modifiers(modifiers: &[EnumOrUnknown<ControlKey>], en: &mut Enigo, ck: i32) {
|
||||
if ck != ControlKey::Shift.value() {
|
||||
fix_modifier(modifiers, ControlKey::Shift, Key::Shift, en);
|
||||
}
|
||||
@ -431,7 +451,7 @@ fn handle_mouse_(evt: &MouseEvent, conn: i32) {
|
||||
}
|
||||
|
||||
pub fn is_enter(evt: &KeyEvent) -> bool {
|
||||
if let Some(key_event::Union::control_key(ck)) = evt.union {
|
||||
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
|
||||
if ck.value() == ControlKey::Return.value() || ck.value() == ControlKey::NumpadEnter.value()
|
||||
{
|
||||
return true;
|
||||
@ -440,7 +460,7 @@ pub fn is_enter(evt: &KeyEvent) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn lock_screen() {
|
||||
pub async fn lock_screen() {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
// xdg_screensaver lock not work on Linux from our service somehow
|
||||
@ -477,7 +497,7 @@ pub fn lock_screen() {
|
||||
crate::platform::lock_screen();
|
||||
}
|
||||
}
|
||||
super::video_service::switch_to_primary();
|
||||
super::video_service::switch_to_primary().await;
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@ -556,7 +576,6 @@ lazy_static::lazy_static! {
|
||||
(ControlKey::Equals, Key::Equals),
|
||||
(ControlKey::NumpadEnter, Key::NumpadEnter),
|
||||
(ControlKey::RAlt, Key::RightAlt),
|
||||
(ControlKey::RWin, Key::RWin),
|
||||
(ControlKey::RControl, Key::RightControl),
|
||||
(ControlKey::RShift, Key::RightShift),
|
||||
].iter().map(|(a, b)| (a.value(), b.clone())).collect();
|
||||
@ -656,7 +675,7 @@ fn legacy_keyboard_map(evt: &KeyEvent) {
|
||||
#[cfg(windows)]
|
||||
let mut has_numlock = false;
|
||||
if evt.down {
|
||||
let ck = if let Some(key_event::Union::control_key(ck)) = evt.union {
|
||||
let ck = if let Some(key_event::Union::ControlKey(ck)) = evt.union {
|
||||
ck.value()
|
||||
} else {
|
||||
-1
|
||||
@ -711,7 +730,7 @@ fn legacy_keyboard_map(evt: &KeyEvent) {
|
||||
}
|
||||
}
|
||||
match evt.union {
|
||||
Some(key_event::Union::control_key(ck)) => {
|
||||
Some(key_event::Union::ControlKey(ck)) => {
|
||||
if let Some(key) = KEY_MAP.get(&ck.value()) {
|
||||
#[cfg(windows)]
|
||||
if let Some(_) = NUMPAD_KEY_MAP.get(&ck.value()) {
|
||||
@ -737,10 +756,10 @@ fn legacy_keyboard_map(evt: &KeyEvent) {
|
||||
allow_err!(send_sas());
|
||||
});
|
||||
} else if ck.value() == ControlKey::LockScreen.value() {
|
||||
lock_screen();
|
||||
lock_screen_2();
|
||||
}
|
||||
}
|
||||
Some(key_event::Union::chr(chr)) => {
|
||||
Some(key_event::Union::Chr(chr)) => {
|
||||
if evt.down {
|
||||
if en.key_down(get_layout(chr)).is_ok() {
|
||||
KEYS_DOWN
|
||||
@ -766,12 +785,12 @@ fn legacy_keyboard_map(evt: &KeyEvent) {
|
||||
.remove(&(chr as u64 + KEY_CHAR_START));
|
||||
}
|
||||
}
|
||||
Some(key_event::Union::unicode(chr)) => {
|
||||
Some(key_event::Union::Unicode(chr)) => {
|
||||
if let Ok(chr) = char::try_from(chr) {
|
||||
en.key_sequence(&chr.to_string());
|
||||
}
|
||||
}
|
||||
Some(key_event::Union::seq(ref seq)) => {
|
||||
Some(key_event::Union::Seq(ref seq)) => {
|
||||
en.key_sequence(&seq);
|
||||
}
|
||||
_ => {}
|
||||
@ -805,6 +824,11 @@ fn handle_key_(evt: &KeyEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn lock_screen_2() {
|
||||
lock_screen().await;
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send_sas() -> ResultType<()> {
|
||||
let mut stream = crate::ipc::connect(1000, crate::POSTFIX_SERVICE).await?;
|
||||
|
||||
651
src/server/uinput.rs
Normal file
651
src/server/uinput.rs
Normal file
@ -0,0 +1,651 @@
|
||||
use crate::ipc::{self, new_listener, Connection, Data, DataKeyboard, DataMouse};
|
||||
use enigo::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use evdev::{
|
||||
uinput::{VirtualDevice, VirtualDeviceBuilder},
|
||||
AttributeSet, EventType, InputEvent,
|
||||
};
|
||||
use hbb_common::{allow_err, bail, log, tokio, ResultType};
|
||||
|
||||
static IPC_CONN_TIMEOUT: u64 = 1000;
|
||||
static IPC_REQUEST_TIMEOUT: u64 = 1000;
|
||||
static IPC_POSTFIX_KEYBOARD: &str = "_uinput_keyboard";
|
||||
static IPC_POSTFIX_MOUSE: &str = "_uinput_mouse";
|
||||
static IPC_POSTFIX_CONTROL: &str = "_uinput_control";
|
||||
|
||||
pub mod client {
|
||||
use super::*;
|
||||
|
||||
pub struct UInputKeyboard {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl UInputKeyboard {
|
||||
pub async fn new() -> ResultType<Self> {
|
||||
let conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_KEYBOARD).await?;
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send(&mut self, data: Data) -> ResultType<()> {
|
||||
self.conn.send(&data).await
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send_get_key_state(&mut self, data: Data) -> ResultType<bool> {
|
||||
self.conn.send(&data).await?;
|
||||
|
||||
match self.conn.next_timeout(IPC_REQUEST_TIMEOUT).await {
|
||||
Ok(Some(Data::KeyboardResponse(ipc::DataKeyboardResponse::GetKeyState(state)))) => {
|
||||
Ok(state)
|
||||
}
|
||||
Ok(Some(resp)) => {
|
||||
// FATAL error!!!
|
||||
bail!(
|
||||
"FATAL error, wait keyboard result other response: {:?}",
|
||||
&resp
|
||||
);
|
||||
}
|
||||
Ok(None) => {
|
||||
// FATAL error!!!
|
||||
// Maybe wait later
|
||||
bail!("FATAL error, wait keyboard result, receive None",);
|
||||
}
|
||||
Err(e) => {
|
||||
// FATAL error!!!
|
||||
bail!(
|
||||
"FATAL error, wait keyboard result timeout {}, {}",
|
||||
&e,
|
||||
IPC_REQUEST_TIMEOUT
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardControllable for UInputKeyboard {
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
match self.send_get_key_state(Data::Keyboard(DataKeyboard::GetKeyState(key))) {
|
||||
Ok(state) => state,
|
||||
Err(e) => {
|
||||
// unreachable!()
|
||||
log::error!("Failed to get key state {}", &e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::Sequence(sequence.to_string()))));
|
||||
}
|
||||
|
||||
// TODO: handle error???
|
||||
fn key_down(&mut self, key: Key) -> enigo::ResultType {
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyDown(key))));
|
||||
Ok(())
|
||||
}
|
||||
fn key_up(&mut self, key: Key) {
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyUp(key))));
|
||||
}
|
||||
fn key_click(&mut self, key: Key) {
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyClick(key))));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UInputMouse {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl UInputMouse {
|
||||
pub async fn new() -> ResultType<Self> {
|
||||
let conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_MOUSE).await?;
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send(&mut self, data: Data) -> ResultType<()> {
|
||||
self.conn.send(&data).await
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseControllable for UInputMouse {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::MoveTo(x, y))));
|
||||
}
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::MoveRelative(x, y))));
|
||||
}
|
||||
// TODO: handle error???
|
||||
fn mouse_down(&mut self, button: MouseButton) -> enigo::ResultType {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::Down(button))));
|
||||
Ok(())
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::Up(button))));
|
||||
}
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::Click(button))));
|
||||
}
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::ScrollX(length))));
|
||||
}
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::ScrollY(length))));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_resolution(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultType<()> {
|
||||
let mut conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_CONTROL).await?;
|
||||
conn.send(&Data::Control(ipc::DataControl::Resolution {
|
||||
minx,
|
||||
maxx,
|
||||
miny,
|
||||
maxy,
|
||||
}))
|
||||
.await?;
|
||||
let _ = conn.next().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod service {
|
||||
use super::*;
|
||||
use hbb_common::lazy_static;
|
||||
use mouce::MouseActions;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref KEY_MAP: HashMap<enigo::Key, evdev::Key> = HashMap::from(
|
||||
[
|
||||
(enigo::Key::Alt, evdev::Key::KEY_LEFTALT),
|
||||
(enigo::Key::Backspace, evdev::Key::KEY_BACKSPACE),
|
||||
(enigo::Key::CapsLock, evdev::Key::KEY_CAPSLOCK),
|
||||
(enigo::Key::Control, evdev::Key::KEY_LEFTCTRL),
|
||||
(enigo::Key::Delete, evdev::Key::KEY_DELETE),
|
||||
(enigo::Key::DownArrow, evdev::Key::KEY_DOWN),
|
||||
(enigo::Key::End, evdev::Key::KEY_END),
|
||||
(enigo::Key::Escape, evdev::Key::KEY_ESC),
|
||||
(enigo::Key::F1, evdev::Key::KEY_F1),
|
||||
(enigo::Key::F10, evdev::Key::KEY_F10),
|
||||
(enigo::Key::F11, evdev::Key::KEY_F11),
|
||||
(enigo::Key::F12, evdev::Key::KEY_F12),
|
||||
(enigo::Key::F2, evdev::Key::KEY_F2),
|
||||
(enigo::Key::F3, evdev::Key::KEY_F3),
|
||||
(enigo::Key::F4, evdev::Key::KEY_F4),
|
||||
(enigo::Key::F5, evdev::Key::KEY_F5),
|
||||
(enigo::Key::F6, evdev::Key::KEY_F6),
|
||||
(enigo::Key::F7, evdev::Key::KEY_F7),
|
||||
(enigo::Key::F8, evdev::Key::KEY_F8),
|
||||
(enigo::Key::F9, evdev::Key::KEY_F9),
|
||||
(enigo::Key::Home, evdev::Key::KEY_HOME),
|
||||
(enigo::Key::LeftArrow, evdev::Key::KEY_LEFT),
|
||||
(enigo::Key::Meta, evdev::Key::KEY_LEFTMETA),
|
||||
(enigo::Key::Option, evdev::Key::KEY_OPTION),
|
||||
(enigo::Key::PageDown, evdev::Key::KEY_PAGEDOWN),
|
||||
(enigo::Key::PageUp, evdev::Key::KEY_PAGEUP),
|
||||
(enigo::Key::Return, evdev::Key::KEY_ENTER),
|
||||
(enigo::Key::RightArrow, evdev::Key::KEY_RIGHT),
|
||||
(enigo::Key::Shift, evdev::Key::KEY_LEFTSHIFT),
|
||||
(enigo::Key::Space, evdev::Key::KEY_SPACE),
|
||||
(enigo::Key::Tab, evdev::Key::KEY_TAB),
|
||||
(enigo::Key::UpArrow, evdev::Key::KEY_UP),
|
||||
(enigo::Key::Numpad0, evdev::Key::KEY_KP0), // check if correct?
|
||||
(enigo::Key::Numpad1, evdev::Key::KEY_KP1),
|
||||
(enigo::Key::Numpad2, evdev::Key::KEY_KP2),
|
||||
(enigo::Key::Numpad3, evdev::Key::KEY_KP3),
|
||||
(enigo::Key::Numpad4, evdev::Key::KEY_KP4),
|
||||
(enigo::Key::Numpad5, evdev::Key::KEY_KP5),
|
||||
(enigo::Key::Numpad6, evdev::Key::KEY_KP6),
|
||||
(enigo::Key::Numpad7, evdev::Key::KEY_KP7),
|
||||
(enigo::Key::Numpad8, evdev::Key::KEY_KP8),
|
||||
(enigo::Key::Numpad9, evdev::Key::KEY_KP9),
|
||||
(enigo::Key::Cancel, evdev::Key::KEY_CANCEL),
|
||||
(enigo::Key::Clear, evdev::Key::KEY_CLEAR),
|
||||
(enigo::Key::Alt, evdev::Key::KEY_LEFTALT),
|
||||
(enigo::Key::Pause, evdev::Key::KEY_PAUSE),
|
||||
(enigo::Key::Kana, evdev::Key::KEY_KATAKANA), // check if correct?
|
||||
(enigo::Key::Hangul, evdev::Key::KEY_HANGEUL), // check if correct?
|
||||
// (enigo::Key::Junja, evdev::Key::KEY_JUNJA), // map?
|
||||
// (enigo::Key::Final, evdev::Key::KEY_FINAL), // map?
|
||||
(enigo::Key::Hanja, evdev::Key::KEY_HANJA),
|
||||
// (enigo::Key::Kanji, evdev::Key::KEY_KANJI), // map?
|
||||
// (enigo::Key::Convert, evdev::Key::KEY_CONVERT),
|
||||
(enigo::Key::Select, evdev::Key::KEY_SELECT),
|
||||
(enigo::Key::Print, evdev::Key::KEY_PRINT),
|
||||
// (enigo::Key::Execute, evdev::Key::KEY_EXECUTE),
|
||||
// (enigo::Key::Snapshot, evdev::Key::KEY_SNAPSHOT),
|
||||
(enigo::Key::Insert, evdev::Key::KEY_INSERT),
|
||||
(enigo::Key::Help, evdev::Key::KEY_HELP),
|
||||
(enigo::Key::Sleep, evdev::Key::KEY_SLEEP),
|
||||
// (enigo::Key::Separator, evdev::Key::KEY_SEPARATOR),
|
||||
(enigo::Key::Scroll, evdev::Key::KEY_SCROLLLOCK),
|
||||
(enigo::Key::NumLock, evdev::Key::KEY_NUMLOCK),
|
||||
(enigo::Key::RWin, evdev::Key::KEY_RIGHTMETA),
|
||||
(enigo::Key::Apps, evdev::Key::KEY_CONTEXT_MENU),
|
||||
(enigo::Key::Multiply, evdev::Key::KEY_KPASTERISK),
|
||||
(enigo::Key::Add, evdev::Key::KEY_KPPLUS),
|
||||
(enigo::Key::Subtract, evdev::Key::KEY_KPMINUS),
|
||||
(enigo::Key::Decimal, evdev::Key::KEY_KPCOMMA), // KEY_KPDOT and KEY_KPCOMMA are exchanged?
|
||||
(enigo::Key::Divide, evdev::Key::KEY_KPSLASH),
|
||||
(enigo::Key::Equals, evdev::Key::KEY_KPEQUAL),
|
||||
(enigo::Key::NumpadEnter, evdev::Key::KEY_KPENTER),
|
||||
(enigo::Key::RightAlt, evdev::Key::KEY_RIGHTALT),
|
||||
(enigo::Key::RightControl, evdev::Key::KEY_RIGHTCTRL),
|
||||
(enigo::Key::RightShift, evdev::Key::KEY_RIGHTSHIFT),
|
||||
]);
|
||||
|
||||
static ref KEY_MAP_LAYOUT: HashMap<char, evdev::Key> = HashMap::from(
|
||||
[
|
||||
('a', evdev::Key::KEY_A),
|
||||
('b', evdev::Key::KEY_B),
|
||||
('c', evdev::Key::KEY_C),
|
||||
('d', evdev::Key::KEY_D),
|
||||
('e', evdev::Key::KEY_E),
|
||||
('f', evdev::Key::KEY_F),
|
||||
('g', evdev::Key::KEY_G),
|
||||
('h', evdev::Key::KEY_H),
|
||||
('i', evdev::Key::KEY_I),
|
||||
('j', evdev::Key::KEY_J),
|
||||
('k', evdev::Key::KEY_K),
|
||||
('l', evdev::Key::KEY_L),
|
||||
('m', evdev::Key::KEY_M),
|
||||
('n', evdev::Key::KEY_N),
|
||||
('o', evdev::Key::KEY_O),
|
||||
('p', evdev::Key::KEY_P),
|
||||
('q', evdev::Key::KEY_Q),
|
||||
('r', evdev::Key::KEY_R),
|
||||
('s', evdev::Key::KEY_S),
|
||||
('t', evdev::Key::KEY_T),
|
||||
('u', evdev::Key::KEY_U),
|
||||
('v', evdev::Key::KEY_V),
|
||||
('w', evdev::Key::KEY_W),
|
||||
('x', evdev::Key::KEY_X),
|
||||
('y', evdev::Key::KEY_Y),
|
||||
('z', evdev::Key::KEY_Z),
|
||||
('0', evdev::Key::KEY_0),
|
||||
('1', evdev::Key::KEY_1),
|
||||
('2', evdev::Key::KEY_2),
|
||||
('3', evdev::Key::KEY_3),
|
||||
('4', evdev::Key::KEY_4),
|
||||
('5', evdev::Key::KEY_5),
|
||||
('6', evdev::Key::KEY_6),
|
||||
('7', evdev::Key::KEY_7),
|
||||
('8', evdev::Key::KEY_8),
|
||||
('9', evdev::Key::KEY_9),
|
||||
('`', evdev::Key::KEY_GRAVE),
|
||||
('-', evdev::Key::KEY_MINUS),
|
||||
('=', evdev::Key::KEY_EQUAL),
|
||||
('[', evdev::Key::KEY_LEFTBRACE),
|
||||
(']', evdev::Key::KEY_RIGHTBRACE),
|
||||
('\\', evdev::Key::KEY_BACKSLASH),
|
||||
(',', evdev::Key::KEY_COMMA),
|
||||
('.', evdev::Key::KEY_DOT),
|
||||
('/', evdev::Key::KEY_SLASH),
|
||||
(';', evdev::Key::KEY_SEMICOLON),
|
||||
('\'', evdev::Key::KEY_APOSTROPHE),
|
||||
]);
|
||||
|
||||
// ((minx, maxx), (miny, maxy))
|
||||
static ref RESOLUTION: Mutex<((i32, i32), (i32, i32))> = Mutex::new(((0, 0), (0, 0)));
|
||||
}
|
||||
|
||||
fn create_uinput_keyboard() -> ResultType<VirtualDevice> {
|
||||
// TODO: ensure keys here
|
||||
let mut keys = AttributeSet::<evdev::Key>::new();
|
||||
for i in evdev::Key::KEY_ESC.code()..(evdev::Key::BTN_TRIGGER_HAPPY40.code() + 1) {
|
||||
let key = evdev::Key::new(i);
|
||||
if !format!("{:?}", &key).contains("unknown key") {
|
||||
keys.insert(key);
|
||||
}
|
||||
}
|
||||
let mut leds = AttributeSet::<evdev::LedType>::new();
|
||||
leds.insert(evdev::LedType::LED_NUML);
|
||||
leds.insert(evdev::LedType::LED_CAPSL);
|
||||
leds.insert(evdev::LedType::LED_SCROLLL);
|
||||
let mut miscs = AttributeSet::<evdev::MiscType>::new();
|
||||
miscs.insert(evdev::MiscType::MSC_SCAN);
|
||||
let keyboard = VirtualDeviceBuilder::new()?
|
||||
.name("RustDesk UInput Keyboard")
|
||||
.with_keys(&keys)?
|
||||
.with_leds(&leds)?
|
||||
.with_miscs(&miscs)?
|
||||
.build()?;
|
||||
Ok(keyboard)
|
||||
}
|
||||
|
||||
fn map_key(key: &enigo::Key) -> ResultType<evdev::Key> {
|
||||
if let Some(k) = KEY_MAP.get(&key) {
|
||||
log::trace!("mapkey {:?}, get {:?}", &key, &k);
|
||||
return Ok(k.clone());
|
||||
} else {
|
||||
match key {
|
||||
enigo::Key::Layout(c) => {
|
||||
if let Some(k) = KEY_MAP_LAYOUT.get(&c) {
|
||||
log::trace!("mapkey {:?}, get {:?}", &key, k);
|
||||
return Ok(k.clone());
|
||||
}
|
||||
}
|
||||
// enigo::Key::Raw(c) => {
|
||||
// let k = evdev::Key::new(c);
|
||||
// if !format!("{:?}", &k).contains("unknown key") {
|
||||
// return Ok(k.clone());
|
||||
// }
|
||||
// }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
bail!("Failed to map key {:?}", &key);
|
||||
}
|
||||
|
||||
async fn ipc_send_data(stream: &mut Connection, data: &Data) {
|
||||
allow_err!(stream.send(data).await);
|
||||
}
|
||||
|
||||
async fn handle_keyboard(
|
||||
stream: &mut Connection,
|
||||
keyboard: &mut VirtualDevice,
|
||||
data: &DataKeyboard,
|
||||
) {
|
||||
log::trace!("handle_keyboard {:?}", &data);
|
||||
match data {
|
||||
DataKeyboard::Sequence(_seq) => {
|
||||
// ignore
|
||||
}
|
||||
DataKeyboard::KeyDown(key) => {
|
||||
if let Ok(k) = map_key(key) {
|
||||
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
|
||||
allow_err!(keyboard.emit(&[down_event]));
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyUp(key) => {
|
||||
if let Ok(k) = map_key(key) {
|
||||
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
|
||||
allow_err!(keyboard.emit(&[up_event]));
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyClick(key) => {
|
||||
if let Ok(k) = map_key(key) {
|
||||
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
|
||||
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
|
||||
allow_err!(keyboard.emit(&[down_event, up_event]));
|
||||
}
|
||||
}
|
||||
DataKeyboard::GetKeyState(key) => {
|
||||
let key_state = if enigo::Key::CapsLock == *key {
|
||||
match keyboard.get_led_state() {
|
||||
Ok(leds) => leds.contains(evdev::LedType::LED_CAPSL),
|
||||
Err(_e) => {
|
||||
// log::debug!("Failed to get led state {}", &_e);
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match keyboard.get_key_state() {
|
||||
Ok(keys) => match key {
|
||||
enigo::Key::Shift => {
|
||||
keys.contains(evdev::Key::KEY_LEFTSHIFT)
|
||||
|| keys.contains(evdev::Key::KEY_RIGHTSHIFT)
|
||||
}
|
||||
enigo::Key::Control => {
|
||||
keys.contains(evdev::Key::KEY_LEFTCTRL)
|
||||
|| keys.contains(evdev::Key::KEY_RIGHTCTRL)
|
||||
}
|
||||
enigo::Key::Alt => {
|
||||
keys.contains(evdev::Key::KEY_LEFTALT)
|
||||
|| keys.contains(evdev::Key::KEY_RIGHTALT)
|
||||
}
|
||||
enigo::Key::NumLock => keys.contains(evdev::Key::KEY_NUMLOCK),
|
||||
enigo::Key::Meta => {
|
||||
keys.contains(evdev::Key::KEY_LEFTMETA)
|
||||
|| keys.contains(evdev::Key::KEY_RIGHTMETA)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
Err(_e) => {
|
||||
// log::debug!("Failed to get key state: {}", &_e);
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
ipc_send_data(
|
||||
stream,
|
||||
&Data::KeyboardResponse(ipc::DataKeyboardResponse::GetKeyState(key_state)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse(mouse: &mut mouce::nix::UInputMouseManager, data: &DataMouse) {
|
||||
log::trace!("handle_mouse {:?}", &data);
|
||||
match data {
|
||||
DataMouse::MoveTo(x, y) => {
|
||||
allow_err!(mouse.move_to(*x as _, *y as _))
|
||||
}
|
||||
DataMouse::MoveRelative(x, y) => {
|
||||
allow_err!(mouse.move_relative(*x, *y))
|
||||
}
|
||||
DataMouse::Down(button) => {
|
||||
let btn = match button {
|
||||
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
|
||||
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
|
||||
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
allow_err!(mouse.press_button(&btn))
|
||||
}
|
||||
DataMouse::Up(button) => {
|
||||
let btn = match button {
|
||||
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
|
||||
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
|
||||
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
allow_err!(mouse.release_button(&btn))
|
||||
}
|
||||
DataMouse::Click(button) => {
|
||||
let btn = match button {
|
||||
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
|
||||
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
|
||||
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
allow_err!(mouse.click_button(&btn))
|
||||
}
|
||||
DataMouse::ScrollX(_length) => {
|
||||
// TODO: not supported for now
|
||||
}
|
||||
DataMouse::ScrollY(length) => {
|
||||
let mut length = *length;
|
||||
|
||||
let scroll = if length < 0 {
|
||||
mouce::common::ScrollDirection::Up
|
||||
} else {
|
||||
mouce::common::ScrollDirection::Down
|
||||
};
|
||||
|
||||
if length < 0 {
|
||||
length = -length;
|
||||
}
|
||||
|
||||
for _ in 0..length {
|
||||
allow_err!(mouse.scroll_wheel(&scroll))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_keyboard_handler(mut stream: Connection) {
|
||||
tokio::spawn(async move {
|
||||
let mut keyboard = match create_uinput_keyboard() {
|
||||
Ok(keyboard) => keyboard,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create keyboard {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::info!("UInput keyboard ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Keyboard(data) => {
|
||||
handle_keyboard(&mut stream, &mut keyboard, &data).await;
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_mouse_handler(mut stream: ipc::Connection) {
|
||||
let resolution = RESOLUTION.lock().unwrap();
|
||||
if resolution.0 .0 == resolution.0 .1 || resolution.1 .0 == resolution.1 .1 {
|
||||
return;
|
||||
}
|
||||
let rng_x = resolution.0.clone();
|
||||
let rng_y = resolution.1.clone();
|
||||
tokio::spawn(async move {
|
||||
log::info!(
|
||||
"Create uinput mouce with rng_x: ({}, {}), rng_y: ({}, {})",
|
||||
rng_x.0,
|
||||
rng_x.1,
|
||||
rng_y.0,
|
||||
rng_y.1
|
||||
);
|
||||
let mut mouse = match mouce::Mouse::new_uinput(rng_x, rng_y) {
|
||||
Ok(mouse) => mouse,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create mouse, {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::info!("UInput mouse ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Mouse(data) => {
|
||||
handle_mouse(&mut mouse, &data);
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_controller_handler(mut stream: ipc::Connection) {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(_err) => {
|
||||
// log::info!("UInput controller ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Control(data) => match data {
|
||||
ipc::DataControl::Resolution{
|
||||
minx,
|
||||
maxx,
|
||||
miny,
|
||||
maxy,
|
||||
} => {
|
||||
*RESOLUTION.lock().unwrap() = ((minx, maxx), (miny, maxy));
|
||||
allow_err!(stream.send(&Data::Empty).await);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Start uinput service.
|
||||
async fn start_service<F: FnOnce(ipc::Connection) + Copy>(postfix: &str, handler: F) {
|
||||
match new_listener(postfix).await {
|
||||
Ok(mut incoming) => {
|
||||
while let Some(result) = incoming.next().await {
|
||||
match result {
|
||||
Ok(stream) => {
|
||||
log::debug!("Got new connection of uinput ipc {}", postfix);
|
||||
handler(Connection::new(stream));
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Couldn't get uinput mouse client: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start uinput mouse ipc service: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start uinput keyboard service.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_service_keyboard() {
|
||||
log::info!("start uinput keyboard service");
|
||||
start_service(IPC_POSTFIX_KEYBOARD, spawn_keyboard_handler).await;
|
||||
}
|
||||
|
||||
/// Start uinput mouse service.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_service_mouse() {
|
||||
log::info!("start uinput mouse service");
|
||||
start_service(IPC_POSTFIX_MOUSE, spawn_mouse_handler).await;
|
||||
}
|
||||
|
||||
/// Start uinput mouse service.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_service_control() {
|
||||
log::info!("start uinput control service");
|
||||
start_service(IPC_POSTFIX_CONTROL, spawn_controller_handler).await;
|
||||
}
|
||||
|
||||
pub fn stop_service_keyboard() {
|
||||
log::info!("stop uinput keyboard service");
|
||||
}
|
||||
pub fn stop_service_mouse() {
|
||||
log::info!("stop uinput mouse service");
|
||||
}
|
||||
pub fn stop_service_control() {
|
||||
log::info!("stop uinput control service");
|
||||
}
|
||||
}
|
||||
@ -147,7 +147,6 @@ impl VideoQoS {
|
||||
// handle image_quality change from peer
|
||||
pub fn update_image_quality(&mut self, image_quality: i32) {
|
||||
let image_quality = Self::convert_quality(image_quality) as _;
|
||||
log::debug!("VideoQoS update_image_quality: {}", image_quality);
|
||||
if self.current_image_quality != image_quality {
|
||||
self.current_image_quality = image_quality;
|
||||
let _ = self.generate_bitrate().ok();
|
||||
@ -171,7 +170,7 @@ impl VideoQoS {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// fix when andorid screen shrinks
|
||||
let fix = Display::fix_quality() as u32;
|
||||
let fix = scrap::Display::fix_quality() as u32;
|
||||
log::debug!("Android screen, fix quality:{}", fix);
|
||||
let base_bitrate = base_bitrate * fix;
|
||||
self.target_bitrate = base_bitrate * self.current_image_quality / 100;
|
||||
|
||||
@ -31,6 +31,7 @@ use scrap::{
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
io::{ErrorKind::WouldBlock, Result},
|
||||
ops::{Deref, DerefMut},
|
||||
time::{self, Duration, Instant},
|
||||
};
|
||||
#[cfg(windows)]
|
||||
@ -127,9 +128,11 @@ impl VideoFrameController {
|
||||
}
|
||||
}
|
||||
|
||||
trait TraitCapturer {
|
||||
pub(super) trait TraitCapturer {
|
||||
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>>;
|
||||
|
||||
fn set_use_yuv(&mut self, use_yuv: bool);
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_gdi(&self) -> bool;
|
||||
#[cfg(windows)]
|
||||
@ -141,6 +144,10 @@ impl TraitCapturer for Capturer {
|
||||
self.frame(timeout)
|
||||
}
|
||||
|
||||
fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_gdi(&self) -> bool {
|
||||
self.is_gdi()
|
||||
@ -158,6 +165,10 @@ impl TraitCapturer for scrap::CapturerMag {
|
||||
self.frame(_timeout_ms)
|
||||
}
|
||||
|
||||
fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
fn is_gdi(&self) -> bool {
|
||||
false
|
||||
}
|
||||
@ -179,6 +190,14 @@ fn check_display_changed(
|
||||
last_width: usize,
|
||||
last_hegiht: usize,
|
||||
) -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// wayland do not support changing display for now
|
||||
if !scrap::is_x11() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let displays = match try_get_displays() {
|
||||
Ok(d) => d,
|
||||
_ => return false,
|
||||
@ -293,6 +312,7 @@ fn ensure_close_virtual_device() -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This function works on privacy mode. Windows only for now.
|
||||
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
|
||||
let test_begin = Instant::now();
|
||||
while test_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||
@ -321,9 +341,38 @@ fn check_uac_switch(privacy_mode_id: i32, captuerer_privacy_mode_id: i32) -> Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(sp: GenericService) -> ResultType<()> {
|
||||
#[cfg(windows)]
|
||||
ensure_close_virtual_device()?;
|
||||
pub(super) struct CapturerInfo {
|
||||
pub origin: (i32, i32),
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub ndisplay: usize,
|
||||
pub current: usize,
|
||||
pub privacy_mode_id: i32,
|
||||
pub _captuerer_privacy_mode_id: i32,
|
||||
pub capturer: Box<dyn TraitCapturer>,
|
||||
}
|
||||
|
||||
impl Deref for CapturerInfo {
|
||||
type Target = Box<dyn TraitCapturer>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.capturer
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for CapturerInfo {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.capturer
|
||||
}
|
||||
}
|
||||
|
||||
fn get_capturer(use_yuv: bool) -> ResultType<CapturerInfo> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !scrap::is_x11() {
|
||||
return super::wayland::get_capturer();
|
||||
}
|
||||
}
|
||||
|
||||
let (ndisplay, current, display) = get_current_display()?;
|
||||
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
||||
@ -338,38 +387,6 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
num_cpus::get(),
|
||||
);
|
||||
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
|
||||
video_qos.set_size(width as _, height as _);
|
||||
let mut spf = video_qos.spf();
|
||||
let bitrate = video_qos.generate_bitrate()?;
|
||||
let abr = video_qos.check_abr_config();
|
||||
drop(video_qos);
|
||||
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
|
||||
|
||||
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
||||
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
||||
codec_name,
|
||||
width,
|
||||
height,
|
||||
bitrate: bitrate as _,
|
||||
}),
|
||||
None => EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
timebase: [1, 1000], // Output timestamp precision
|
||||
bitrate,
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut encoder;
|
||||
match Encoder::new(encoder_cfg) {
|
||||
Ok(x) => encoder = x,
|
||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||
}
|
||||
|
||||
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||
#[cfg(not(windows))]
|
||||
let captuerer_privacy_mode_id = privacy_mode_id;
|
||||
@ -389,17 +406,67 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
} else {
|
||||
log::info!("In privacy mode, the peer side cannot watch the screen");
|
||||
}
|
||||
let mut c = create_capturer(captuerer_privacy_mode_id, display, encoder.use_yuv())?;
|
||||
let capturer = create_capturer(captuerer_privacy_mode_id, display, use_yuv)?;
|
||||
Ok(CapturerInfo {
|
||||
origin,
|
||||
width,
|
||||
height,
|
||||
ndisplay,
|
||||
current,
|
||||
privacy_mode_id,
|
||||
_captuerer_privacy_mode_id: captuerer_privacy_mode_id,
|
||||
capturer,
|
||||
})
|
||||
}
|
||||
|
||||
fn run(sp: GenericService) -> ResultType<()> {
|
||||
#[cfg(windows)]
|
||||
ensure_close_virtual_device()?;
|
||||
|
||||
let mut c = get_capturer(true)?;
|
||||
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
|
||||
video_qos.set_size(c.width as _, c.height as _);
|
||||
let mut spf = video_qos.spf();
|
||||
let bitrate = video_qos.generate_bitrate()?;
|
||||
let abr = video_qos.check_abr_config();
|
||||
drop(video_qos);
|
||||
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
|
||||
|
||||
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
||||
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
||||
codec_name,
|
||||
width: c.width,
|
||||
height: c.height,
|
||||
bitrate: bitrate as _,
|
||||
}),
|
||||
None => EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: c.width as _,
|
||||
height: c.height as _,
|
||||
timebase: [1, 1000], // Output timestamp precision
|
||||
bitrate,
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut encoder;
|
||||
match Encoder::new(encoder_cfg) {
|
||||
Ok(x) => encoder = x,
|
||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||
}
|
||||
c.set_use_yuv(encoder.use_yuv());
|
||||
|
||||
if *SWITCH.lock().unwrap() {
|
||||
log::debug!("Broadcasting display switch");
|
||||
let mut misc = Misc::new();
|
||||
misc.set_switch_display(SwitchDisplay {
|
||||
display: current as _,
|
||||
x: origin.0 as _,
|
||||
y: origin.1 as _,
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
display: c.current as _,
|
||||
x: c.origin.0 as _,
|
||||
y: c.origin.1 as _,
|
||||
width: c.width as _,
|
||||
height: c.height as _,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
@ -419,7 +486,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
|
||||
while sp.ok() {
|
||||
#[cfg(windows)]
|
||||
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
|
||||
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
|
||||
|
||||
{
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
@ -437,11 +504,11 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
if *SWITCH.lock().unwrap() {
|
||||
bail!("SWITCH");
|
||||
}
|
||||
if current != *CURRENT_DISPLAY.lock().unwrap() {
|
||||
if c.current != *CURRENT_DISPLAY.lock().unwrap() {
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
}
|
||||
check_privacy_mode_changed(&sp, privacy_mode_id)?;
|
||||
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if crate::platform::windows::desktop_changed() {
|
||||
@ -451,7 +518,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let now = time::Instant::now();
|
||||
if last_check_displays.elapsed().as_millis() > 1000 {
|
||||
last_check_displays = now;
|
||||
if ndisplay != get_display_num() {
|
||||
if c.ndisplay != get_display_num() {
|
||||
log::info!("Displays changed");
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
@ -515,7 +582,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if check_display_changed(ndisplay, current, width, height) {
|
||||
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
|
||||
log::info!("Displays changed");
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
@ -537,9 +604,9 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let timeout_millis = 3_000u64;
|
||||
let wait_begin = Instant::now();
|
||||
while wait_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||
check_privacy_mode_changed(&sp, privacy_mode_id)?;
|
||||
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
|
||||
#[cfg(windows)]
|
||||
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
|
||||
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
|
||||
frame_controller.try_wait_next(&mut fetched_conn_ids, 300);
|
||||
// break if all connections have received current frame
|
||||
if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() {
|
||||
@ -633,6 +700,17 @@ pub fn handle_one_frame_encoded(
|
||||
}
|
||||
|
||||
fn get_display_num() -> usize {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !scrap::is_x11() {
|
||||
return if let Ok(n) = super::wayland::get_display_num() {
|
||||
n
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(d) = try_get_displays() {
|
||||
d.len()
|
||||
} else {
|
||||
@ -640,14 +718,10 @@ fn get_display_num() -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
// switch to primary display if long time (30 seconds) no users
|
||||
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
|
||||
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
|
||||
}
|
||||
pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
|
||||
let mut displays = Vec::new();
|
||||
let mut primary = 0;
|
||||
for (i, d) in try_get_displays()?.iter().enumerate() {
|
||||
for (i, d) in all.iter().enumerate() {
|
||||
if d.is_primary() {
|
||||
primary = i;
|
||||
}
|
||||
@ -665,12 +739,26 @@ pub fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
if *lock >= displays.len() {
|
||||
*lock = primary
|
||||
}
|
||||
Ok((*lock, displays))
|
||||
(*lock, displays)
|
||||
}
|
||||
|
||||
pub fn switch_display(i: i32) {
|
||||
pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !scrap::is_x11() {
|
||||
return super::wayland::get_displays().await;
|
||||
}
|
||||
}
|
||||
// switch to primary display if long time (30 seconds) no users
|
||||
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
|
||||
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
|
||||
}
|
||||
Ok(get_displays_2(&try_get_displays()?))
|
||||
}
|
||||
|
||||
pub async fn switch_display(i: i32) {
|
||||
let i = i as usize;
|
||||
if let Ok((_, displays)) = get_displays() {
|
||||
if let Ok((_, displays)) = get_displays().await {
|
||||
if i < displays.len() {
|
||||
*CURRENT_DISPLAY.lock().unwrap() = i;
|
||||
}
|
||||
@ -684,6 +772,16 @@ pub fn refresh() {
|
||||
}
|
||||
|
||||
fn get_primary() -> usize {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !scrap::is_x11() {
|
||||
return match super::wayland::get_primary() {
|
||||
Ok(n) => n,
|
||||
Err(_) => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(all) = try_get_displays() {
|
||||
for (i, d) in all.iter().enumerate() {
|
||||
if d.is_primary() {
|
||||
@ -694,8 +792,8 @@ fn get_primary() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn switch_to_primary() {
|
||||
switch_display(get_primary() as _);
|
||||
pub async fn switch_to_primary() {
|
||||
switch_display(get_primary() as _).await;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
@ -733,16 +831,15 @@ fn try_get_displays() -> ResultType<Vec<Display>> {
|
||||
Ok(displays)
|
||||
}
|
||||
|
||||
fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
pub(super) fn get_current_display_2(mut all: Vec<Display>) -> ResultType<(usize, usize, Display)> {
|
||||
let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize;
|
||||
let mut displays = try_get_displays()?;
|
||||
if displays.len() == 0 {
|
||||
if all.len() == 0 {
|
||||
bail!("No displays");
|
||||
}
|
||||
let n = displays.len();
|
||||
let n = all.len();
|
||||
if current >= n {
|
||||
current = 0;
|
||||
for (i, d) in displays.iter().enumerate() {
|
||||
for (i, d) in all.iter().enumerate() {
|
||||
if d.is_primary() {
|
||||
current = i;
|
||||
break;
|
||||
@ -750,5 +847,9 @@ fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
}
|
||||
*CURRENT_DISPLAY.lock().unwrap() = current;
|
||||
}
|
||||
return Ok((n, current, displays.remove(current)));
|
||||
return Ok((n, current, all.remove(current)));
|
||||
}
|
||||
|
||||
fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
get_current_display_2(try_get_displays()?)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user