mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-20 09:23:20 +03:00
Merge branch 'master' into lan_discovery
This commit is contained in:
4
.cargo/config.toml
Normal file
4
.cargo/config.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[target.'cfg(target_os="macos")']
|
||||||
|
rustflags = [
|
||||||
|
"-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null",
|
||||||
|
]
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ extractor
|
|||||||
__pycache__
|
__pycache__
|
||||||
src/version.rs
|
src/version.rs
|
||||||
*dmg
|
*dmg
|
||||||
|
sciter.dll
|
||||||
|
|||||||
BIN
128x128.png
Normal file
BIN
128x128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
BIN
128x128@2x.png
Normal file
BIN
128x128@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
1555
Cargo.lock
generated
1555
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
32
Cargo.toml
32
Cargo.toml
@@ -17,31 +17,33 @@ default = ["use_dasp"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
whoami = "1.1"
|
whoami = "1.2"
|
||||||
scrap = { path = "libs/scrap" }
|
scrap = { path = "libs/scrap" }
|
||||||
hbb_common = { path = "libs/hbb_common" }
|
hbb_common = { path = "libs/hbb_common" }
|
||||||
socket_cs = { path = "libs/socket_cs" }
|
socket_cs = { path = "libs/socket_cs" }
|
||||||
enigo = { path = "libs/enigo" }
|
enigo = { path = "libs/enigo" }
|
||||||
|
sys-locale = "0.1"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
sha2 = "0.9"
|
sha2 = "0.10"
|
||||||
repng = "0.2"
|
repng = "0.2"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" }
|
parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" }
|
||||||
flexi_logger = "0.17"
|
flexi_logger = "0.22"
|
||||||
runas = "0.2"
|
runas = "0.2"
|
||||||
magnum-opus = { git = "https://github.com/open-trade/magnum-opus" }
|
magnum-opus = { git = "https://github.com/open-trade/magnum-opus" }
|
||||||
dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpolate"], optional = true }
|
dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpolate"], optional = true }
|
||||||
rubato = { version = "0.8", optional = true }
|
rubato = { version = "0.10", optional = true }
|
||||||
samplerate = { version = "0.2", optional = true }
|
samplerate = { version = "0.2", optional = true }
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
crc32fast = "1.2"
|
crc32fast = "1.3"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
clap = "2.33"
|
clap = "2.34"
|
||||||
rpassword = "5.0"
|
rpassword = "5.0"
|
||||||
|
base64 = "0.13"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android")))'.dependencies]
|
||||||
cpal = { git = "https://github.com/open-trade/cpal" }
|
cpal = { git = "https://github.com/open-trade/cpal" }
|
||||||
@@ -52,13 +54,15 @@ mac_address = "1.1"
|
|||||||
sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" }
|
sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" }
|
||||||
ctrlc = "3.2"
|
ctrlc = "3.2"
|
||||||
arboard = "2.0"
|
arboard = "2.0"
|
||||||
clipboard-master = "3"
|
clipboard-master = "3.1"
|
||||||
|
#rdev = { path = "../rdev" }
|
||||||
|
rdev = { git = "https://github.com/open-trade/rdev" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
#systray = { git = "https://github.com/open-trade/systray-rs" }
|
systray = { git = "https://github.com/liyue201/systray-rs" }
|
||||||
winapi = { version = "0.3", features = ["winuser"] }
|
winapi = { version = "0.3", features = ["winuser"] }
|
||||||
winreg = "0.7"
|
winreg = "0.10"
|
||||||
windows-service = { git = 'https://github.com/mullvad/windows-service-rs.git' }
|
windows-service = "0.4"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
objc = "0.2"
|
objc = "0.2"
|
||||||
@@ -68,15 +72,15 @@ core-foundation = "0.9"
|
|||||||
core-graphics = "0.22"
|
core-graphics = "0.22"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
libpulse-simple-binding = "2.16"
|
libpulse-simple-binding = "2.24"
|
||||||
libpulse-binding = "2.16"
|
libpulse-binding = "2.25"
|
||||||
rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" }
|
rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" }
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "windows", target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "windows", target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
psutil = "3.2"
|
psutil = { version = "3.2", features = [ "process" ], git = "https://github.com/open-trade/rust-psutil" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
android_logger = "0.9"
|
android_logger = "0.10"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/socket_cs"]
|
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/socket_cs"]
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<a href="#file-structure">Structure</a> •
|
<a href="#file-structure">Structure</a> •
|
||||||
<a href="#snapshot">Snapshot</a><br>
|
<a href="#snapshot">Snapshot</a><br>
|
||||||
[<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-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-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
[<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-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-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||||
<b>We need your help to translate this README to your native language</b>
|
<b>We need your help to translate this README and <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> to your native language</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||||
@@ -72,7 +72,7 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
cd vcpkg
|
cd vcpkg
|
||||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
git checkout 2021.12.01
|
||||||
cd ..
|
cd ..
|
||||||
vcpkg/bootstrap-vcpkg.sh
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
export VCPKG_ROOT=$HOME/vcpkg
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ fn main() {
|
|||||||
thread::sleep(Duration::from_secs(2));
|
thread::sleep(Duration::from_secs(2));
|
||||||
let mut enigo = Enigo::new();
|
let mut enigo = Enigo::new();
|
||||||
|
|
||||||
enigo.key_down(Key::Layout('a'));
|
enigo.key_down(Key::Layout('a')).ok();
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(1));
|
||||||
enigo.key_up(Key::Layout('a'));
|
enigo.key_up(Key::Layout('a'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ fn main() {
|
|||||||
enigo.key_sequence("Hello World! here is a lot of text ❤️");
|
enigo.key_sequence("Hello World! here is a lot of text ❤️");
|
||||||
|
|
||||||
// select all
|
// select all
|
||||||
enigo.key_down(Key::Control);
|
enigo.key_down(Key::Control).ok();
|
||||||
enigo.key_click(Key::Layout('a'));
|
enigo.key_click(Key::Layout('a'));
|
||||||
enigo.key_up(Key::Control);
|
enigo.key_up(Key::Control);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ fn main() {
|
|||||||
enigo.mouse_move_to(500, 200);
|
enigo.mouse_move_to(500, 200);
|
||||||
thread::sleep(wait_time);
|
thread::sleep(wait_time);
|
||||||
|
|
||||||
enigo.mouse_down(MouseButton::Left);
|
enigo.mouse_down(MouseButton::Left).ok();
|
||||||
thread::sleep(wait_time);
|
thread::sleep(wait_time);
|
||||||
|
|
||||||
enigo.mouse_move_relative(100, 100);
|
enigo.mouse_move_relative(100, 100);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ fn main() {
|
|||||||
println!("{:?}", time);
|
println!("{:?}", time);
|
||||||
|
|
||||||
// select all
|
// select all
|
||||||
enigo.key_down(Key::Control);
|
enigo.key_down(Key::Control).ok();
|
||||||
enigo.key_click(Key::Layout('a'));
|
enigo.key_click(Key::Layout('a'));
|
||||||
enigo.key_up(Key::Control);
|
enigo.key_up(Key::Control);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
protobuf = { version = "3.0.0-pre", git = "https://github.com/stepancheg/rust-protobuf" }
|
protobuf = "3.0.0-alpha.2"
|
||||||
tokio = { version = "1.10", features = ["full"] }
|
tokio = { version = "1.15", features = ["full"] }
|
||||||
tokio-util = { version = "0.6", features = ["full"] }
|
tokio-util = { version = "0.6", features = ["full"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
bytes = "1.0"
|
bytes = "1.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
socket2 = { version = "0.3", features = ["reuseport"] }
|
socket2 = { version = "0.3", features = ["reuseport"] }
|
||||||
zstd = "0.9"
|
zstd = "0.9"
|
||||||
quinn = {version = "0.6", optional = true }
|
quinn = {version = "0.8", optional = true }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
directories-next = "2.0"
|
directories-next = "2.0"
|
||||||
@@ -28,6 +28,8 @@ confy = { git = "https://github.com/open-trade/confy" }
|
|||||||
dirs-next = "2.0"
|
dirs-next = "2.0"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
sodiumoxide = "0.2"
|
sodiumoxide = "0.2"
|
||||||
|
regex = "1.4"
|
||||||
|
tokio-socks = { git = "https://github.com/fufesou/tokio-socks" }
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
mac_address = "1.1"
|
mac_address = "1.1"
|
||||||
@@ -36,7 +38,7 @@ mac_address = "1.1"
|
|||||||
quic = ["quinn"]
|
quic = ["quinn"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
protobuf-codegen-pure = { version = "3.0.0-pre", git = "https://github.com/stepancheg/rust-protobuf" }
|
protobuf-codegen-pure = "3.0.0-alpha.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["winuser"] }
|
winapi = { version = "0.3", features = ["winuser"] }
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ message MouseEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ControlKey {
|
enum ControlKey {
|
||||||
|
Unknown = 0;
|
||||||
Alt = 1;
|
Alt = 1;
|
||||||
Backspace = 2;
|
Backspace = 2;
|
||||||
CapsLock = 3;
|
CapsLock = 3;
|
||||||
@@ -342,7 +343,6 @@ message PublicKey {
|
|||||||
|
|
||||||
message SignedId {
|
message SignedId {
|
||||||
bytes id = 1;
|
bytes id = 1;
|
||||||
bytes pk = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message AudioFormat {
|
message AudioFormat {
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ use sodiumoxide::crypto::sign;
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs,
|
fs,
|
||||||
net::SocketAddr,
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{Arc, Mutex, RwLock},
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const APP_NAME: &str = "RustDesk";
|
pub const APP_NAME: &str = "RustDesk";
|
||||||
pub const BIND_INTERFACE: &str = "0.0.0.0";
|
|
||||||
pub const RENDEZVOUS_TIMEOUT: u64 = 12_000;
|
pub const RENDEZVOUS_TIMEOUT: u64 = 12_000;
|
||||||
pub const CONNECT_TIMEOUT: u64 = 18_000;
|
pub const CONNECT_TIMEOUT: u64 = 18_000;
|
||||||
pub const COMPRESS_LEVEL: i32 = 3;
|
pub const COMPRESS_LEVEL: i32 = 3;
|
||||||
@@ -55,7 +54,11 @@ pub const RENDEZVOUS_SERVERS: &'static [&'static str] = &[
|
|||||||
pub const RENDEZVOUS_PORT: i32 = 21116;
|
pub const RENDEZVOUS_PORT: i32 = 21116;
|
||||||
pub const RELAY_PORT: i32 = 21117;
|
pub const RELAY_PORT: i32 = 21117;
|
||||||
|
|
||||||
pub const SERVER_UDP_PORT: u16 = 21001; // udp
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum NetworkType {
|
||||||
|
Direct,
|
||||||
|
ProxySocks,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@@ -73,6 +76,16 @@ pub struct Config {
|
|||||||
keys_confirmed: HashMap<String, bool>,
|
keys_confirmed: HashMap<String, bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Socks5Server {
|
||||||
|
#[serde(default)]
|
||||||
|
pub proxy: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub username: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
// more variable configs
|
// more variable configs
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Config2 {
|
pub struct Config2 {
|
||||||
@@ -87,6 +100,9 @@ pub struct Config2 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
serial: i32,
|
serial: i32,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
socks: Option<Socks5Server>,
|
||||||
|
|
||||||
// the other scalar value must before this
|
// the other scalar value must before this
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub options: HashMap<String, String>,
|
pub options: HashMap<String, String>,
|
||||||
@@ -276,8 +292,8 @@ impl Config {
|
|||||||
return "".into();
|
return "".into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
pub fn log_path() -> PathBuf {
|
pub fn log_path() -> PathBuf {
|
||||||
#[allow(unreachable_code)]
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
if let Some(path) = dirs_next::home_dir().as_mut() {
|
if let Some(path) = dirs_next::home_dir().as_mut() {
|
||||||
@@ -329,10 +345,10 @@ impl Config {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_any_listen_addr() -> SocketAddr {
|
pub fn get_any_listen_addr() -> SocketAddr {
|
||||||
format!("{}:0", BIND_INTERFACE).parse().unwrap()
|
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rendezvous_server() -> SocketAddr {
|
pub fn get_rendezvous_server() -> String {
|
||||||
let mut rendezvous_server = Self::get_option("custom-rendezvous-server");
|
let mut rendezvous_server = Self::get_option("custom-rendezvous-server");
|
||||||
if rendezvous_server.is_empty() {
|
if rendezvous_server.is_empty() {
|
||||||
rendezvous_server = CONFIG2.write().unwrap().rendezvous_server.clone();
|
rendezvous_server = CONFIG2.write().unwrap().rendezvous_server.clone();
|
||||||
@@ -346,11 +362,7 @@ impl Config {
|
|||||||
if !rendezvous_server.contains(":") {
|
if !rendezvous_server.contains(":") {
|
||||||
rendezvous_server = format!("{}:{}", rendezvous_server, RENDEZVOUS_PORT);
|
rendezvous_server = format!("{}:{}", rendezvous_server, RENDEZVOUS_PORT);
|
||||||
}
|
}
|
||||||
if let Ok(addr) = crate::to_socket_addr(&rendezvous_server) {
|
rendezvous_server
|
||||||
addr
|
|
||||||
} else {
|
|
||||||
Self::get_any_listen_addr()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rendezvous_servers() -> Vec<String> {
|
pub fn get_rendezvous_servers() -> Vec<String> {
|
||||||
@@ -490,6 +502,9 @@ impl Config {
|
|||||||
|
|
||||||
pub fn set_key_pair(pair: (Vec<u8>, Vec<u8>)) {
|
pub fn set_key_pair(pair: (Vec<u8>, Vec<u8>)) {
|
||||||
let mut config = CONFIG.write().unwrap();
|
let mut config = CONFIG.write().unwrap();
|
||||||
|
if config.key_pair == pair {
|
||||||
|
return;
|
||||||
|
}
|
||||||
config.key_pair = pair;
|
config.key_pair = pair;
|
||||||
config.store();
|
config.store();
|
||||||
}
|
}
|
||||||
@@ -522,6 +537,9 @@ impl Config {
|
|||||||
|
|
||||||
pub fn set_options(v: HashMap<String, String>) {
|
pub fn set_options(v: HashMap<String, String>) {
|
||||||
let mut config = CONFIG2.write().unwrap();
|
let mut config = CONFIG2.write().unwrap();
|
||||||
|
if config.options == v {
|
||||||
|
return;
|
||||||
|
}
|
||||||
config.options = v;
|
config.options = v;
|
||||||
config.store();
|
config.store();
|
||||||
}
|
}
|
||||||
@@ -621,6 +639,26 @@ impl Config {
|
|||||||
pub fn get_remote_id() -> String {
|
pub fn get_remote_id() -> String {
|
||||||
CONFIG2.read().unwrap().remote_id.clone()
|
CONFIG2.read().unwrap().remote_id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_socks(socks: Option<Socks5Server>) {
|
||||||
|
let mut config = CONFIG2.write().unwrap();
|
||||||
|
if config.socks == socks {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
config.socks = socks;
|
||||||
|
config.store();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_socks() -> Option<Socks5Server> {
|
||||||
|
CONFIG2.read().unwrap().socks.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_network_type() -> NetworkType {
|
||||||
|
match &CONFIG2.read().unwrap().socks {
|
||||||
|
None => NetworkType::Direct,
|
||||||
|
Some(_) => NetworkType::ProxySocks,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PEERS: &str = "peers";
|
const PEERS: &str = "peers";
|
||||||
@@ -690,6 +728,32 @@ impl PeerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Fav {
|
||||||
|
#[serde(default)]
|
||||||
|
pub peers: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fav {
|
||||||
|
pub fn load() -> Fav {
|
||||||
|
let _ = CONFIG.read().unwrap(); // for lock
|
||||||
|
match confy::load_path(&Config::file_("_fav")) {
|
||||||
|
Ok(fav) => fav,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to load fav: {}", err);
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(peers: Vec<String>) {
|
||||||
|
let f = Fav { peers };
|
||||||
|
if let Err(err) = confy::store_path(Config::file_("_fav"), f) {
|
||||||
|
log::error!("Failed to store fav: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ pub use protobuf;
|
|||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, BufRead},
|
io::{self, BufRead},
|
||||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs},
|
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||||
path::Path,
|
path::Path,
|
||||||
time::{self, SystemTime, UNIX_EPOCH},
|
time::{self, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
pub use tokio;
|
pub use tokio;
|
||||||
pub use tokio_util;
|
pub use tokio_util;
|
||||||
|
pub mod socket_client;
|
||||||
pub mod tcp;
|
pub mod tcp;
|
||||||
pub mod udp;
|
pub mod udp;
|
||||||
pub use env_logger;
|
pub use env_logger;
|
||||||
@@ -30,7 +31,11 @@ pub use anyhow::{self, bail};
|
|||||||
pub use futures_util;
|
pub use futures_util;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
pub use regex;
|
||||||
pub use sodiumoxide;
|
pub use sodiumoxide;
|
||||||
|
pub use tokio_socks;
|
||||||
|
pub use tokio_socks::IntoTargetAddr;
|
||||||
|
pub use tokio_socks::TargetAddr;
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
pub type Stream = quic::Connection;
|
pub type Stream = quic::Connection;
|
||||||
@@ -151,14 +156,6 @@ pub fn get_version_from_url(url: &str) -> String {
|
|||||||
"".to_owned()
|
"".to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_socket_addr(host: &str) -> ResultType<SocketAddr> {
|
|
||||||
let addrs: Vec<SocketAddr> = host.to_socket_addrs()?.collect();
|
|
||||||
if addrs.is_empty() {
|
|
||||||
bail!("Failed to solve {}", host);
|
|
||||||
}
|
|
||||||
Ok(addrs[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gen_version() {
|
pub fn gen_version() {
|
||||||
let mut file = File::create("./src/version.rs").unwrap();
|
let mut file = File::create("./src/version.rs").unwrap();
|
||||||
for line in read_lines("Cargo.toml").unwrap() {
|
for line in read_lines("Cargo.toml").unwrap() {
|
||||||
@@ -183,6 +180,14 @@ where
|
|||||||
Ok(io::BufReader::new(file).lines())
|
Ok(io::BufReader::new(file).lines())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_version_number(v: &str) -> i64 {
|
||||||
|
let mut n = 0;
|
||||||
|
for x in v.split(".") {
|
||||||
|
n = n * 1000 + x.parse::<i64>().unwrap_or(0);
|
||||||
|
}
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
91
libs/hbb_common/src/socket_client.rs
Normal file
91
libs/hbb_common/src/socket_client.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use crate::{
|
||||||
|
config::{Config, NetworkType},
|
||||||
|
tcp::FramedStream,
|
||||||
|
udp::FramedSocket,
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
use anyhow::Context;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use tokio::net::ToSocketAddrs;
|
||||||
|
use tokio_socks::{IntoTargetAddr, TargetAddr};
|
||||||
|
|
||||||
|
fn to_socket_addr(host: &str) -> ResultType<SocketAddr> {
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
|
host.to_socket_addrs()?.next().context("Failed to solve")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_target_addr(host: &str) -> ResultType<TargetAddr<'static>> {
|
||||||
|
let addr = match Config::get_network_type() {
|
||||||
|
NetworkType::Direct => to_socket_addr(&host)?.into_target_addr()?,
|
||||||
|
NetworkType::ProxySocks => host.into_target_addr()?,
|
||||||
|
}
|
||||||
|
.to_owned();
|
||||||
|
Ok(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_if_valid_server(host: &str) -> String {
|
||||||
|
let mut host = host.to_owned();
|
||||||
|
if !host.contains(":") {
|
||||||
|
host = format!("{}:{}", host, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
match Config::get_network_type() {
|
||||||
|
NetworkType::Direct => match to_socket_addr(&host) {
|
||||||
|
Err(err) => err.to_string(),
|
||||||
|
Ok(_) => "".to_owned(),
|
||||||
|
},
|
||||||
|
NetworkType::ProxySocks => match &host.into_target_addr() {
|
||||||
|
Err(err) => err.to_string(),
|
||||||
|
Ok(_) => "".to_owned(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_tcp<'t, T: IntoTargetAddr<'t>>(
|
||||||
|
target: T,
|
||||||
|
local: SocketAddr,
|
||||||
|
ms_timeout: u64,
|
||||||
|
) -> ResultType<FramedStream> {
|
||||||
|
let target_addr = target.into_target_addr()?;
|
||||||
|
|
||||||
|
if let Some(conf) = Config::get_socks() {
|
||||||
|
FramedStream::connect(
|
||||||
|
conf.proxy.as_str(),
|
||||||
|
target_addr,
|
||||||
|
local,
|
||||||
|
conf.username.as_str(),
|
||||||
|
conf.password.as_str(),
|
||||||
|
ms_timeout,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
let addr = std::net::ToSocketAddrs::to_socket_addrs(&target_addr)?
|
||||||
|
.next()
|
||||||
|
.context("Invalid target addr")?;
|
||||||
|
Ok(FramedStream::new(addr, local, ms_timeout).await?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_udp<T: ToSocketAddrs>(local: T, ms_timeout: u64) -> ResultType<FramedSocket> {
|
||||||
|
match Config::get_socks() {
|
||||||
|
None => Ok(FramedSocket::new(local).await?),
|
||||||
|
Some(conf) => {
|
||||||
|
let socket = FramedSocket::new_proxy(
|
||||||
|
conf.proxy.as_str(),
|
||||||
|
local,
|
||||||
|
conf.username.as_str(),
|
||||||
|
conf.password.as_str(),
|
||||||
|
ms_timeout,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(socket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rebind_udp<T: ToSocketAddrs>(local: T) -> ResultType<Option<FramedSocket>> {
|
||||||
|
match Config::get_network_type() {
|
||||||
|
NetworkType::Direct => Ok(Some(FramedSocket::new(local).await?)),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,16 +4,31 @@ use futures::{SinkExt, StreamExt};
|
|||||||
use protobuf::Message;
|
use protobuf::Message;
|
||||||
use sodiumoxide::crypto::secretbox::{self, Key, Nonce};
|
use sodiumoxide::crypto::secretbox::{self, Key, Nonce};
|
||||||
use std::{
|
use std::{
|
||||||
io::{Error, ErrorKind},
|
io::{self, Error, ErrorKind},
|
||||||
|
net::SocketAddr,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
use tokio::net::{lookup_host, TcpListener, TcpSocket, TcpStream, ToSocketAddrs};
|
use tokio::{
|
||||||
|
io::{AsyncRead, AsyncWrite, ReadBuf},
|
||||||
|
net::{lookup_host, TcpListener, TcpSocket, ToSocketAddrs},
|
||||||
|
};
|
||||||
|
use tokio_socks::{tcp::Socks5Stream, IntoTargetAddr, ToProxyAddrs};
|
||||||
use tokio_util::codec::Framed;
|
use tokio_util::codec::Framed;
|
||||||
|
|
||||||
pub struct FramedStream(Framed<TcpStream, BytesCodec>, Option<(Key, u64, u64)>);
|
pub trait TcpStreamTrait: AsyncRead + AsyncWrite + Unpin {}
|
||||||
|
pub struct DynTcpStream(Box<dyn TcpStreamTrait + Send>);
|
||||||
|
|
||||||
|
pub struct FramedStream(
|
||||||
|
Framed<DynTcpStream, BytesCodec>,
|
||||||
|
SocketAddr,
|
||||||
|
Option<(Key, u64, u64)>,
|
||||||
|
u64,
|
||||||
|
);
|
||||||
|
|
||||||
impl Deref for FramedStream {
|
impl Deref for FramedStream {
|
||||||
type Target = Framed<TcpStream, BytesCodec>;
|
type Target = Framed<DynTcpStream, BytesCodec>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
@@ -26,6 +41,20 @@ impl DerefMut for FramedStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for DynTcpStream {
|
||||||
|
type Target = Box<dyn TcpStreamTrait + Send>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for DynTcpStream {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std::io::Error> {
|
fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std::io::Error> {
|
||||||
let socket = match addr {
|
let socket = match addr {
|
||||||
std::net::SocketAddr::V4(..) => TcpSocket::new_v4()?,
|
std::net::SocketAddr::V4(..) => TcpSocket::new_v4()?,
|
||||||
@@ -44,8 +73,8 @@ fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std:
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FramedStream {
|
impl FramedStream {
|
||||||
pub async fn new<T: ToSocketAddrs, T2: ToSocketAddrs>(
|
pub async fn new<T1: ToSocketAddrs, T2: ToSocketAddrs>(
|
||||||
remote_addr: T,
|
remote_addr: T1,
|
||||||
local_addr: T2,
|
local_addr: T2,
|
||||||
ms_timeout: u64,
|
ms_timeout: u64,
|
||||||
) -> ResultType<Self> {
|
) -> ResultType<Self> {
|
||||||
@@ -56,23 +85,86 @@ impl FramedStream {
|
|||||||
new_socket(local_addr, true)?.connect(remote_addr),
|
new_socket(local_addr, true)?.connect(remote_addr),
|
||||||
)
|
)
|
||||||
.await??;
|
.await??;
|
||||||
return Ok(Self(Framed::new(stream, BytesCodec::new()), None));
|
let addr = stream.local_addr()?;
|
||||||
|
return Ok(Self(
|
||||||
|
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||||
|
addr,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bail!("could not resolve to any address");
|
bail!("could not resolve to any address");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from(stream: TcpStream) -> Self {
|
pub async fn connect<'a, 't, P, T1, T2>(
|
||||||
Self(Framed::new(stream, BytesCodec::new()), None)
|
proxy: P,
|
||||||
|
target: T1,
|
||||||
|
local: T2,
|
||||||
|
username: &'a str,
|
||||||
|
password: &'a str,
|
||||||
|
ms_timeout: u64,
|
||||||
|
) -> ResultType<Self>
|
||||||
|
where
|
||||||
|
P: ToProxyAddrs,
|
||||||
|
T1: IntoTargetAddr<'t>,
|
||||||
|
T2: ToSocketAddrs,
|
||||||
|
{
|
||||||
|
if let Some(local) = lookup_host(&local).await?.next() {
|
||||||
|
if let Some(proxy) = proxy.to_proxy_addrs().next().await {
|
||||||
|
let stream =
|
||||||
|
super::timeout(ms_timeout, new_socket(local, true)?.connect(proxy?)).await??;
|
||||||
|
let stream = if username.trim().is_empty() {
|
||||||
|
super::timeout(
|
||||||
|
ms_timeout,
|
||||||
|
Socks5Stream::connect_with_socket(stream, target),
|
||||||
|
)
|
||||||
|
.await??
|
||||||
|
} else {
|
||||||
|
super::timeout(
|
||||||
|
ms_timeout,
|
||||||
|
Socks5Stream::connect_with_password_and_socket(
|
||||||
|
stream, target, username, password,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await??
|
||||||
|
};
|
||||||
|
let addr = stream.local_addr()?;
|
||||||
|
return Ok(Self(
|
||||||
|
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||||
|
addr,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
bail!("could not resolve to any address");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_addr(&self) -> SocketAddr {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_send_timeout(&mut self, ms: u64) {
|
||||||
|
self.3 = ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(stream: impl TcpStreamTrait + Send + 'static, addr: SocketAddr) -> Self {
|
||||||
|
Self(
|
||||||
|
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||||
|
addr,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_raw(&mut self) {
|
pub fn set_raw(&mut self) {
|
||||||
self.0.codec_mut().set_raw();
|
self.0.codec_mut().set_raw();
|
||||||
self.1 = None;
|
self.2 = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_secured(&self) -> bool {
|
pub fn is_secured(&self) -> bool {
|
||||||
self.1.is_some()
|
self.2.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -83,24 +175,29 @@ impl FramedStream {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub async fn send_raw(&mut self, msg: Vec<u8>) -> ResultType<()> {
|
pub async fn send_raw(&mut self, msg: Vec<u8>) -> ResultType<()> {
|
||||||
let mut msg = msg;
|
let mut msg = msg;
|
||||||
if let Some(key) = self.1.as_mut() {
|
if let Some(key) = self.2.as_mut() {
|
||||||
key.1 += 1;
|
key.1 += 1;
|
||||||
let nonce = Self::get_nonce(key.1);
|
let nonce = Self::get_nonce(key.1);
|
||||||
msg = secretbox::seal(&msg, &nonce, &key.0);
|
msg = secretbox::seal(&msg, &nonce, &key.0);
|
||||||
}
|
}
|
||||||
self.0.send(bytes::Bytes::from(msg)).await?;
|
self.send_bytes(bytes::Bytes::from(msg)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub async fn send_bytes(&mut self, bytes: Bytes) -> ResultType<()> {
|
pub async fn send_bytes(&mut self, bytes: Bytes) -> ResultType<()> {
|
||||||
self.0.send(bytes).await?;
|
if self.3 > 0 {
|
||||||
|
super::timeout(self.3, self.0.send(bytes)).await??;
|
||||||
|
} else {
|
||||||
|
self.0.send(bytes).await?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn next(&mut self) -> Option<Result<BytesMut, Error>> {
|
pub async fn next(&mut self) -> Option<Result<BytesMut, Error>> {
|
||||||
let mut res = self.0.next().await;
|
let mut res = self.0.next().await;
|
||||||
if let Some(key) = self.1.as_mut() {
|
if let Some(key) = self.2.as_mut() {
|
||||||
if let Some(Ok(bytes)) = res.as_mut() {
|
if let Some(Ok(bytes)) = res.as_mut() {
|
||||||
key.2 += 1;
|
key.2 += 1;
|
||||||
let nonce = Self::get_nonce(key.2);
|
let nonce = Self::get_nonce(key.2);
|
||||||
@@ -128,7 +225,7 @@ impl FramedStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_key(&mut self, key: Key) {
|
pub fn set_key(&mut self, key: Key) {
|
||||||
self.1 = Some((key, 0, 0));
|
self.2 = Some((key, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nonce(seqnum: u64) -> Nonce {
|
fn get_nonce(seqnum: u64) -> Nonce {
|
||||||
@@ -152,3 +249,35 @@ pub async fn new_listener<T: ToSocketAddrs>(addr: T, reuse: bool) -> ResultType<
|
|||||||
bail!("could not resolve to any address");
|
bail!("could not resolve to any address");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Unpin for DynTcpStream {}
|
||||||
|
|
||||||
|
impl AsyncRead for DynTcpStream {
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<io::Result<()>> {
|
||||||
|
AsyncRead::poll_read(Pin::new(&mut self.0), cx, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for DynTcpStream {
|
||||||
|
fn poll_write(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
AsyncWrite::poll_flush(Pin::new(&mut self.0), cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: AsyncRead + AsyncWrite + Unpin> TcpStreamTrait for R {}
|
||||||
|
|||||||
@@ -1,24 +1,17 @@
|
|||||||
use crate::{bail, ResultType};
|
use crate::{bail, ResultType};
|
||||||
use bytes::BytesMut;
|
use anyhow::anyhow;
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use protobuf::Message;
|
use protobuf::Message;
|
||||||
use socket2::{Domain, Socket, Type};
|
use socket2::{Domain, Socket, Type};
|
||||||
use std::{
|
use std::net::SocketAddr;
|
||||||
io::Error,
|
use tokio::net::{ToSocketAddrs, UdpSocket};
|
||||||
net::SocketAddr,
|
use tokio_socks::{udp::Socks5UdpFramed, IntoTargetAddr, TargetAddr, ToProxyAddrs};
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
};
|
|
||||||
use tokio::{net::ToSocketAddrs, net::UdpSocket};
|
|
||||||
use tokio_util::{codec::BytesCodec, udp::UdpFramed};
|
use tokio_util::{codec::BytesCodec, udp::UdpFramed};
|
||||||
|
|
||||||
pub struct FramedSocket(UdpFramed<BytesCodec>);
|
pub enum FramedSocket {
|
||||||
|
Direct(UdpFramed<BytesCodec>),
|
||||||
impl Deref for FramedSocket {
|
ProxySocks(Socks5UdpFramed),
|
||||||
type Target = UdpFramed<BytesCodec>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_socket(addr: SocketAddr, reuse: bool) -> Result<Socket, std::io::Error> {
|
fn new_socket(addr: SocketAddr, reuse: bool) -> Result<Socket, std::io::Error> {
|
||||||
@@ -38,52 +31,110 @@ fn new_socket(addr: SocketAddr, reuse: bool) -> Result<Socket, std::io::Error> {
|
|||||||
Ok(socket)
|
Ok(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for FramedSocket {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FramedSocket {
|
impl FramedSocket {
|
||||||
pub async fn new<T: ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
pub async fn new<T: ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
||||||
let socket = UdpSocket::bind(addr).await?;
|
let socket = UdpSocket::bind(addr).await?;
|
||||||
Ok(Self(UdpFramed::new(socket, BytesCodec::new())))
|
Ok(Self::Direct(UdpFramed::new(socket, BytesCodec::new())))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::never_loop)]
|
#[allow(clippy::never_loop)]
|
||||||
pub async fn new_reuse<T: std::net::ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
pub async fn new_reuse<T: std::net::ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
||||||
for addr in addr.to_socket_addrs()? {
|
for addr in addr.to_socket_addrs()? {
|
||||||
return Ok(Self(UdpFramed::new(
|
let socket = new_socket(addr, true)?.into_udp_socket();
|
||||||
UdpSocket::from_std(new_socket(addr, true)?.into_udp_socket())?,
|
return Ok(Self::Direct(UdpFramed::new(
|
||||||
|
UdpSocket::from_std(socket)?,
|
||||||
BytesCodec::new(),
|
BytesCodec::new(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
bail!("could not resolve to any address");
|
bail!("could not resolve to any address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn new_proxy<'a, 't, P: ToProxyAddrs, T: ToSocketAddrs>(
|
||||||
|
proxy: P,
|
||||||
|
local: T,
|
||||||
|
username: &'a str,
|
||||||
|
password: &'a str,
|
||||||
|
ms_timeout: u64,
|
||||||
|
) -> ResultType<Self> {
|
||||||
|
let framed = if username.trim().is_empty() {
|
||||||
|
super::timeout(ms_timeout, Socks5UdpFramed::connect(proxy, Some(local))).await??
|
||||||
|
} else {
|
||||||
|
super::timeout(
|
||||||
|
ms_timeout,
|
||||||
|
Socks5UdpFramed::connect_with_password(proxy, Some(local), username, password),
|
||||||
|
)
|
||||||
|
.await??
|
||||||
|
};
|
||||||
|
log::trace!(
|
||||||
|
"Socks5 udp connected, local addr: {:?}, target addr: {}",
|
||||||
|
framed.local_addr(),
|
||||||
|
framed.socks_addr()
|
||||||
|
);
|
||||||
|
Ok(Self::ProxySocks(framed))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn send(&mut self, msg: &impl Message, addr: SocketAddr) -> ResultType<()> {
|
pub async fn send(
|
||||||
self.0
|
&mut self,
|
||||||
.send((bytes::Bytes::from(msg.write_to_bytes().unwrap()), addr))
|
msg: &impl Message,
|
||||||
.await?;
|
addr: impl IntoTargetAddr<'_>,
|
||||||
|
) -> ResultType<()> {
|
||||||
|
let addr = addr.into_target_addr()?.to_owned();
|
||||||
|
let send_data = Bytes::from(msg.write_to_bytes()?);
|
||||||
|
let _ = match self {
|
||||||
|
Self::Direct(f) => match addr {
|
||||||
|
TargetAddr::Ip(addr) => f.send((send_data, addr)).await?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Self::ProxySocks(f) => f.send((send_data, addr)).await?,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/68733302/1926020
|
||||||
|
#[inline]
|
||||||
|
pub async fn send_raw(
|
||||||
|
&mut self,
|
||||||
|
msg: &'static [u8],
|
||||||
|
addr: impl IntoTargetAddr<'static>,
|
||||||
|
) -> ResultType<()> {
|
||||||
|
let addr = addr.into_target_addr()?.to_owned();
|
||||||
|
|
||||||
|
let _ = match self {
|
||||||
|
Self::Direct(f) => match addr {
|
||||||
|
TargetAddr::Ip(addr) => f.send((Bytes::from(msg), addr)).await?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Self::ProxySocks(f) => f.send((Bytes::from(msg), addr)).await?,
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn send_raw(&mut self, msg: &'static [u8], addr: SocketAddr) -> ResultType<()> {
|
pub async fn next(&mut self) -> Option<ResultType<(BytesMut, TargetAddr<'static>)>> {
|
||||||
self.0.send((bytes::Bytes::from(msg), addr)).await?;
|
match self {
|
||||||
Ok(())
|
Self::Direct(f) => match f.next().await {
|
||||||
|
Some(Ok((data, addr))) => {
|
||||||
|
Some(Ok((data, addr.into_target_addr().ok()?.to_owned())))
|
||||||
|
}
|
||||||
|
Some(Err(e)) => Some(Err(anyhow!(e))),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
Self::ProxySocks(f) => match f.next().await {
|
||||||
|
Some(Ok((data, _))) => Some(Ok((data.data, data.dst_addr))),
|
||||||
|
Some(Err(e)) => Some(Err(anyhow!(e))),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn next(&mut self) -> Option<Result<(BytesMut, SocketAddr), Error>> {
|
pub async fn next_timeout(
|
||||||
self.0.next().await
|
&mut self,
|
||||||
}
|
ms: u64,
|
||||||
|
) -> Option<ResultType<(BytesMut, TargetAddr<'static>)>> {
|
||||||
#[inline]
|
|
||||||
pub async fn next_timeout(&mut self, ms: u64) -> Option<Result<(BytesMut, SocketAddr), Error>> {
|
|
||||||
if let Ok(res) =
|
if let Ok(res) =
|
||||||
tokio::time::timeout(std::time::Duration::from_millis(ms), self.0.next()).await
|
tokio::time::timeout(std::time::Duration::from_millis(ms), self.next()).await
|
||||||
{
|
{
|
||||||
res
|
res
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ struct Args {
|
|||||||
flag_time: Option<u64>,
|
flag_time: Option<u64>,
|
||||||
flag_fps: u64,
|
flag_fps: u64,
|
||||||
flag_bv: u32,
|
flag_bv: u32,
|
||||||
flag_ba: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ extern "C" {
|
|||||||
src_stride_argb: c_int,
|
src_stride_argb: c_int,
|
||||||
dst_argb: *mut u8,
|
dst_argb: *mut u8,
|
||||||
dst_stride_argb: c_int,
|
dst_stride_argb: c_int,
|
||||||
width: c_int,
|
src_width: c_int,
|
||||||
height: c_int,
|
src_height: c_int,
|
||||||
mode: c_int,
|
mode: c_int,
|
||||||
) -> c_int;
|
) -> c_int;
|
||||||
|
|
||||||
|
|||||||
@@ -3,32 +3,20 @@ pub mod gdi;
|
|||||||
pub use gdi::CapturerGDI;
|
pub use gdi::CapturerGDI;
|
||||||
|
|
||||||
use winapi::{
|
use winapi::{
|
||||||
shared::dxgi::{
|
shared::{
|
||||||
CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIResource, IDXGISurface,
|
dxgi::*,
|
||||||
IID_IDXGIFactory1, IID_IDXGISurface, DXGI_MAP_READ, DXGI_OUTPUT_DESC,
|
dxgi1_2::*,
|
||||||
DXGI_RESOURCE_PRIORITY_MAXIMUM,
|
dxgitype::*,
|
||||||
|
minwindef::{DWORD, FALSE, TRUE, UINT},
|
||||||
|
ntdef::LONG,
|
||||||
|
windef::HMONITOR,
|
||||||
|
winerror::*,
|
||||||
|
// dxgiformat::{DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_420_OPAQUE},
|
||||||
},
|
},
|
||||||
shared::dxgi1_2::IDXGIOutputDuplication,
|
um::{
|
||||||
// shared::dxgiformat::{DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_420_OPAQUE},
|
d3d11::*, d3dcommon::D3D_DRIVER_TYPE_UNKNOWN, unknwnbase::IUnknown, wingdi::*,
|
||||||
shared::dxgi1_2::{IDXGIOutput1, IID_IDXGIOutput1},
|
winnt::HRESULT, winuser::*,
|
||||||
shared::dxgitype::DXGI_MODE_ROTATION,
|
|
||||||
shared::minwindef::{DWORD, FALSE, TRUE, UINT},
|
|
||||||
shared::ntdef::LONG,
|
|
||||||
shared::windef::HMONITOR,
|
|
||||||
shared::winerror::{
|
|
||||||
DXGI_ERROR_ACCESS_LOST, DXGI_ERROR_INVALID_CALL, DXGI_ERROR_NOT_CURRENTLY_AVAILABLE,
|
|
||||||
DXGI_ERROR_SESSION_DISCONNECTED, DXGI_ERROR_UNSUPPORTED, DXGI_ERROR_WAIT_TIMEOUT,
|
|
||||||
E_ACCESSDENIED, E_INVALIDARG, S_OK,
|
|
||||||
},
|
},
|
||||||
um::d3d11::{
|
|
||||||
D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, IID_ID3D11Texture2D,
|
|
||||||
D3D11_CPU_ACCESS_READ, D3D11_SDK_VERSION, D3D11_USAGE_STAGING,
|
|
||||||
},
|
|
||||||
um::d3dcommon::D3D_DRIVER_TYPE_UNKNOWN,
|
|
||||||
um::unknwnbase::IUnknown,
|
|
||||||
um::wingdi::*,
|
|
||||||
um::winnt::HRESULT,
|
|
||||||
um::winuser::*,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ComPtr<T>(*mut T);
|
pub struct ComPtr<T>(*mut T);
|
||||||
@@ -54,12 +42,11 @@ pub struct Capturer {
|
|||||||
duplication: ComPtr<IDXGIOutputDuplication>,
|
duplication: ComPtr<IDXGIOutputDuplication>,
|
||||||
fastlane: bool,
|
fastlane: bool,
|
||||||
surface: ComPtr<IDXGISurface>,
|
surface: ComPtr<IDXGISurface>,
|
||||||
data: *const u8,
|
|
||||||
len: usize,
|
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
use_yuv: bool,
|
use_yuv: bool,
|
||||||
yuv: Vec<u8>,
|
yuv: Vec<u8>,
|
||||||
|
rotated: Vec<u8>,
|
||||||
gdi_capturer: Option<CapturerGDI>,
|
gdi_capturer: Option<CapturerGDI>,
|
||||||
gdi_buffer: Vec<u8>,
|
gdi_buffer: Vec<u8>,
|
||||||
}
|
}
|
||||||
@@ -158,10 +145,9 @@ impl Capturer {
|
|||||||
width: display.width() as usize,
|
width: display.width() as usize,
|
||||||
height: display.height() as usize,
|
height: display.height() as usize,
|
||||||
display,
|
display,
|
||||||
data: ptr::null(),
|
|
||||||
len: 0,
|
|
||||||
use_yuv,
|
use_yuv,
|
||||||
yuv: Vec::new(),
|
yuv: Vec::new(),
|
||||||
|
rotated: Vec::new(),
|
||||||
gdi_capturer,
|
gdi_capturer,
|
||||||
gdi_buffer: Vec::new(),
|
gdi_buffer: Vec::new(),
|
||||||
})
|
})
|
||||||
@@ -181,10 +167,9 @@ impl Capturer {
|
|||||||
self.gdi_capturer.take();
|
self.gdi_capturer.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn load_frame(&mut self, timeout: UINT) -> io::Result<()> {
|
unsafe fn load_frame(&mut self, timeout: UINT) -> io::Result<(*const u8, i32)> {
|
||||||
let mut frame = ptr::null_mut();
|
let mut frame = ptr::null_mut();
|
||||||
let mut info = mem::MaybeUninit::uninit().assume_init();
|
let mut info = mem::MaybeUninit::uninit().assume_init();
|
||||||
self.data = ptr::null();
|
|
||||||
|
|
||||||
wrap_hresult((*self.duplication.0).AcquireNextFrame(timeout, &mut info, &mut frame))?;
|
wrap_hresult((*self.duplication.0).AcquireNextFrame(timeout, &mut info, &mut frame))?;
|
||||||
let frame = ComPtr(frame);
|
let frame = ComPtr(frame);
|
||||||
@@ -200,9 +185,7 @@ impl Capturer {
|
|||||||
self.surface = ComPtr(self.ohgodwhat(frame.0)?);
|
self.surface = ComPtr(self.ohgodwhat(frame.0)?);
|
||||||
wrap_hresult((*self.surface.0).Map(&mut rect, DXGI_MAP_READ))?;
|
wrap_hresult((*self.surface.0).Map(&mut rect, DXGI_MAP_READ))?;
|
||||||
}
|
}
|
||||||
self.data = rect.pBits;
|
Ok((rect.pBits, rect.Pitch))
|
||||||
self.len = self.height * rect.Pitch as usize;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy from GPU memory to system memory
|
// copy from GPU memory to system memory
|
||||||
@@ -257,8 +240,42 @@ impl Capturer {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.unmap();
|
self.unmap();
|
||||||
self.load_frame(timeout)?;
|
let r = self.load_frame(timeout)?;
|
||||||
slice::from_raw_parts(self.data, self.len)
|
let rotate = match self.display.rotation() {
|
||||||
|
DXGI_MODE_ROTATION_IDENTITY | DXGI_MODE_ROTATION_UNSPECIFIED => 0,
|
||||||
|
DXGI_MODE_ROTATION_ROTATE90 => 90,
|
||||||
|
DXGI_MODE_ROTATION_ROTATE180 => 180,
|
||||||
|
DXGI_MODE_ROTATION_ROTATE270 => 270,
|
||||||
|
_ => {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Unknown roration".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if rotate == 0 {
|
||||||
|
slice::from_raw_parts(r.0, r.1 as usize * self.height)
|
||||||
|
} else {
|
||||||
|
self.rotated.resize(self.width * self.height * 4, 0);
|
||||||
|
crate::common::ARGBRotate(
|
||||||
|
r.0,
|
||||||
|
r.1,
|
||||||
|
self.rotated.as_mut_ptr(),
|
||||||
|
4 * self.width as i32,
|
||||||
|
if rotate == 180 {
|
||||||
|
self.width
|
||||||
|
} else {
|
||||||
|
self.height
|
||||||
|
} as _,
|
||||||
|
if rotate != 180 {
|
||||||
|
self.width
|
||||||
|
} else {
|
||||||
|
self.height
|
||||||
|
} as _,
|
||||||
|
rotate,
|
||||||
|
);
|
||||||
|
&self.rotated[..]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok({
|
Ok({
|
||||||
@@ -478,7 +495,10 @@ pub struct Display {
|
|||||||
gdi: bool,
|
gdi: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/dchapyshev/aspia/blob/59233c5d01a4d03ed6de19b03ce77d61a6facf79/source/base/desktop/win/screen_capture_utils.cc
|
// optimized for updated region
|
||||||
|
// https://github.com/dchapyshev/aspia/blob/master/source/base/desktop/win/dxgi_output_duplicator.cc
|
||||||
|
// rotation
|
||||||
|
// https://github.com/bryal/dxgcap-rs/blob/master/src/lib.rs
|
||||||
|
|
||||||
impl Display {
|
impl Display {
|
||||||
pub fn width(&self) -> LONG {
|
pub fn width(&self) -> LONG {
|
||||||
|
|||||||
130
src/client.rs
130
src/client.rs
@@ -14,8 +14,8 @@ use hbb_common::{
|
|||||||
message_proto::*,
|
message_proto::*,
|
||||||
protobuf::Message as _,
|
protobuf::Message as _,
|
||||||
rendezvous_proto::*,
|
rendezvous_proto::*,
|
||||||
|
socket_client,
|
||||||
sodiumoxide::crypto::{box_, secretbox, sign},
|
sodiumoxide::crypto::{box_, secretbox, sign},
|
||||||
tcp::FramedStream,
|
|
||||||
timeout,
|
timeout,
|
||||||
tokio::time::Duration,
|
tokio::time::Duration,
|
||||||
AddrMangle, ResultType, Stream,
|
AddrMangle, ResultType, Stream,
|
||||||
@@ -35,6 +35,8 @@ pub const SEC30: Duration = Duration::from_secs(30);
|
|||||||
|
|
||||||
pub struct Client;
|
pub struct Client;
|
||||||
|
|
||||||
|
pub use super::lang::*;
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android")))]
|
#[cfg(not(any(target_os = "android")))]
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref AUDIO_HOST: Host = cpal::default_host();
|
static ref AUDIO_HOST: Host = cpal::default_host();
|
||||||
@@ -102,14 +104,39 @@ impl Drop for OboePlayer {
|
|||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub async fn start(peer: &str, conn_type: ConnType) -> ResultType<(Stream, bool)> {
|
pub async fn start(peer: &str, conn_type: ConnType) -> ResultType<(Stream, bool)> {
|
||||||
|
match Self::_start(peer, conn_type).await {
|
||||||
|
Err(err) => {
|
||||||
|
let err_str = err.to_string();
|
||||||
|
if err_str.starts_with("Failed") {
|
||||||
|
bail!(err_str + ": Please try later");
|
||||||
|
} else {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(x) => Ok(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _start(peer: &str, conn_type: ConnType) -> ResultType<(Stream, bool)> {
|
||||||
// to-do: remember the port for each peer, so that we can retry easier
|
// to-do: remember the port for each peer, so that we can retry easier
|
||||||
let any_addr = Config::get_any_listen_addr();
|
let any_addr = Config::get_any_listen_addr();
|
||||||
|
if crate::is_ip(peer) {
|
||||||
|
return Ok((
|
||||||
|
socket_client::connect_tcp(
|
||||||
|
crate::check_port(peer, RELAY_PORT + 1),
|
||||||
|
any_addr,
|
||||||
|
RENDEZVOUS_TIMEOUT,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
}
|
||||||
let rendezvous_server = crate::get_rendezvous_server(1_000).await;
|
let rendezvous_server = crate::get_rendezvous_server(1_000).await;
|
||||||
log::info!("rendezvous server: {}", rendezvous_server);
|
log::info!("rendezvous server: {}", rendezvous_server);
|
||||||
let mut socket = FramedStream::new(rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT)
|
|
||||||
.await
|
let mut socket =
|
||||||
.with_context(|| "Failed to connect to rendezvous server")?;
|
socket_client::connect_tcp(&*rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT).await?;
|
||||||
let my_addr = socket.get_ref().local_addr()?;
|
let my_addr = socket.local_addr();
|
||||||
let mut pk = Vec::new();
|
let mut pk = Vec::new();
|
||||||
let mut relay_server = "".to_owned();
|
let mut relay_server = "".to_owned();
|
||||||
|
|
||||||
@@ -204,7 +231,7 @@ impl Client {
|
|||||||
peer,
|
peer,
|
||||||
pk,
|
pk,
|
||||||
&relay_server,
|
&relay_server,
|
||||||
rendezvous_server,
|
&rendezvous_server,
|
||||||
time_used,
|
time_used,
|
||||||
peer_nat_type,
|
peer_nat_type,
|
||||||
my_nat_type,
|
my_nat_type,
|
||||||
@@ -220,7 +247,7 @@ impl Client {
|
|||||||
peer_id: &str,
|
peer_id: &str,
|
||||||
pk: Vec<u8>,
|
pk: Vec<u8>,
|
||||||
relay_server: &str,
|
relay_server: &str,
|
||||||
rendezvous_server: SocketAddr,
|
rendezvous_server: &str,
|
||||||
punch_time_used: u64,
|
punch_time_used: u64,
|
||||||
peer_nat_type: NatType,
|
peer_nat_type: NatType,
|
||||||
my_nat_type: i32,
|
my_nat_type: i32,
|
||||||
@@ -261,7 +288,8 @@ impl Client {
|
|||||||
}
|
}
|
||||||
log::info!("peer address: {}, timeout: {}", peer, connect_timeout);
|
log::info!("peer address: {}, timeout: {}", peer, connect_timeout);
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let mut conn = FramedStream::new(peer, local_addr, connect_timeout).await;
|
// NOTICE: Socks5 is be used event in intranet. Which may be not a good way.
|
||||||
|
let mut conn = socket_client::connect_tcp(peer, local_addr, connect_timeout).await;
|
||||||
let direct = !conn.is_err();
|
let direct = !conn.is_err();
|
||||||
if conn.is_err() {
|
if conn.is_err() {
|
||||||
if !relay_server.is_empty() {
|
if !relay_server.is_empty() {
|
||||||
@@ -296,28 +324,51 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn secure_connection(peer_id: &str, pk: Vec<u8>, conn: &mut Stream) -> ResultType<()> {
|
async fn secure_connection(peer_id: &str, pk: Vec<u8>, conn: &mut Stream) -> ResultType<()> {
|
||||||
|
let mut pk = pk;
|
||||||
|
const RS_PK: &[u8; 32] = &[
|
||||||
|
177, 155, 15, 73, 116, 147, 172, 11, 55, 38, 92, 168, 30, 116, 213, 196, 12, 134, 130,
|
||||||
|
170, 181, 161, 192, 176, 132, 229, 139, 178, 17, 165, 150, 51,
|
||||||
|
];
|
||||||
|
if !pk.is_empty() {
|
||||||
|
let tmp = sign::PublicKey(*RS_PK);
|
||||||
|
if let Ok(data) = sign::verify(&pk, &tmp) {
|
||||||
|
pk = data;
|
||||||
|
} else {
|
||||||
|
log::error!("Handshake failed: invalid public key from rendezvous server");
|
||||||
|
pk.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
if pk.len() != sign::PUBLICKEYBYTES {
|
if pk.len() != sign::PUBLICKEYBYTES {
|
||||||
// send an empty message out in case server is setting up secure and waiting for first message
|
// send an empty message out in case server is setting up secure and waiting for first message
|
||||||
conn.send(&Message::new()).await?;
|
conn.send(&Message::new()).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut pk_ = [0u8; sign::PUBLICKEYBYTES];
|
let mut tmp = [0u8; sign::PUBLICKEYBYTES];
|
||||||
pk_[..].copy_from_slice(&pk);
|
tmp[..].copy_from_slice(&pk);
|
||||||
let pk = sign::PublicKey(pk_);
|
let sign_pk = sign::PublicKey(tmp);
|
||||||
match timeout(CONNECT_TIMEOUT, conn.next()).await? {
|
match timeout(CONNECT_TIMEOUT, conn.next()).await? {
|
||||||
Some(res) => {
|
Some(res) => {
|
||||||
let bytes = res?;
|
let bytes = res?;
|
||||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
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::signed_id(si)) = msg_in.union {
|
||||||
let their_pk_b = if si.pk.len() == box_::PUBLICKEYBYTES {
|
if let Ok(data) = sign::verify(&si.id, &sign_pk) {
|
||||||
let mut pk_ = [0u8; box_::PUBLICKEYBYTES];
|
let s = String::from_utf8_lossy(&data);
|
||||||
pk_[..].copy_from_slice(&si.pk);
|
let mut it = s.split("\0");
|
||||||
box_::PublicKey(pk_)
|
let id = it.next().unwrap_or_default();
|
||||||
} else {
|
let pk =
|
||||||
bail!("Handshake failed: invalid public box key length from peer");
|
base64::decode(it.next().unwrap_or_default()).unwrap_or_default();
|
||||||
};
|
let their_pk_b = if pk.len() == box_::PUBLICKEYBYTES {
|
||||||
if let Ok(id) = sign::verify(&si.id, &pk) {
|
let mut pk_ = [0u8; box_::PUBLICKEYBYTES];
|
||||||
if id == peer_id.as_bytes() {
|
pk_[..].copy_from_slice(&pk);
|
||||||
|
box_::PublicKey(pk_)
|
||||||
|
} else {
|
||||||
|
log::error!(
|
||||||
|
"Handshake failed: invalid public box key length from peer"
|
||||||
|
);
|
||||||
|
conn.send(&Message::new()).await?;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if id == peer_id {
|
||||||
let (our_pk_b, out_sk_b) = box_::gen_keypair();
|
let (our_pk_b, out_sk_b) = box_::gen_keypair();
|
||||||
let key = secretbox::gen_key();
|
let key = secretbox::gen_key();
|
||||||
let nonce = box_::Nonce([0u8; box_::NONCEBYTES]);
|
let nonce = box_::Nonce([0u8; box_::NONCEBYTES]);
|
||||||
@@ -331,7 +382,8 @@ impl Client {
|
|||||||
timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??;
|
timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??;
|
||||||
conn.set_key(key);
|
conn.set_key(key);
|
||||||
} else {
|
} else {
|
||||||
bail!("Handshake failed: sign failure");
|
log::error!("Handshake failed: sign failure");
|
||||||
|
conn.send(&Message::new()).await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// fall back to non-secure connection in case pk mismatch
|
// fall back to non-secure connection in case pk mismatch
|
||||||
@@ -341,10 +393,12 @@ impl Client {
|
|||||||
timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??;
|
timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bail!("Handshake failed: invalid message type");
|
log::error!("Handshake failed: invalid message type");
|
||||||
|
conn.send(&Message::new()).await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bail!("Handshake failed: invalid message format");
|
log::error!("Handshake failed: invalid message format");
|
||||||
|
conn.send(&Message::new()).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@@ -357,7 +411,7 @@ impl Client {
|
|||||||
async fn request_relay(
|
async fn request_relay(
|
||||||
peer: &str,
|
peer: &str,
|
||||||
relay_server: String,
|
relay_server: String,
|
||||||
rendezvous_server: SocketAddr,
|
rendezvous_server: &str,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
conn_type: ConnType,
|
conn_type: ConnType,
|
||||||
) -> ResultType<Stream> {
|
) -> ResultType<Stream> {
|
||||||
@@ -366,9 +420,11 @@ impl Client {
|
|||||||
let mut uuid = "".to_owned();
|
let mut uuid = "".to_owned();
|
||||||
for i in 1..=3 {
|
for i in 1..=3 {
|
||||||
// use different socket due to current hbbs implement requiring different nat address for each attempt
|
// use different socket due to current hbbs implement requiring different nat address for each attempt
|
||||||
let mut socket = FramedStream::new(rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT)
|
let mut socket =
|
||||||
.await
|
socket_client::connect_tcp(rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT)
|
||||||
.with_context(|| "Failed to connect to rendezvous server")?;
|
.await
|
||||||
|
.with_context(|| "Failed to connect to rendezvous server")?;
|
||||||
|
|
||||||
let mut msg_out = RendezvousMessage::new();
|
let mut msg_out = RendezvousMessage::new();
|
||||||
uuid = Uuid::new_v4().to_string();
|
uuid = Uuid::new_v4().to_string();
|
||||||
log::info!(
|
log::info!(
|
||||||
@@ -411,7 +467,7 @@ impl Client {
|
|||||||
relay_server: String,
|
relay_server: String,
|
||||||
conn_type: ConnType,
|
conn_type: ConnType,
|
||||||
) -> ResultType<Stream> {
|
) -> ResultType<Stream> {
|
||||||
let mut conn = FramedStream::new(
|
let mut conn = socket_client::connect_tcp(
|
||||||
crate::check_port(relay_server, RELAY_PORT),
|
crate::check_port(relay_server, RELAY_PORT),
|
||||||
Config::get_any_listen_addr(),
|
Config::get_any_listen_addr(),
|
||||||
CONNECT_TIMEOUT,
|
CONNECT_TIMEOUT,
|
||||||
@@ -530,7 +586,10 @@ impl AudioHandler {
|
|||||||
);
|
);
|
||||||
audio_buffer.lock().unwrap().extend(buffer);
|
audio_buffer.lock().unwrap().extend(buffer);
|
||||||
} else {
|
} else {
|
||||||
audio_buffer.lock().unwrap().extend(buffer.iter().cloned());
|
audio_buffer
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.extend(buffer[0..n].iter().cloned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(any(target_os = "android"))]
|
#[cfg(any(target_os = "android"))]
|
||||||
@@ -548,7 +607,8 @@ impl AudioHandler {
|
|||||||
device: &Device,
|
device: &Device,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
let err_fn = move |err| {
|
let err_fn = move |err| {
|
||||||
log::error!("an error occurred on stream: {}", err);
|
// too many errors, will improve later
|
||||||
|
log::trace!("an error occurred on stream: {}", err);
|
||||||
};
|
};
|
||||||
let audio_buffer = self.audio_buffer.clone();
|
let audio_buffer = self.audio_buffer.clone();
|
||||||
let stream = device.build_output_stream(
|
let stream = device.build_output_stream(
|
||||||
@@ -583,7 +643,7 @@ pub struct VideoHandler {
|
|||||||
impl VideoHandler {
|
impl VideoHandler {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
VideoHandler {
|
VideoHandler {
|
||||||
decoder: Decoder::new(VideoCodecId::VP9, 1).unwrap(),
|
decoder: Decoder::new(VideoCodecId::VP9, 0).unwrap(),
|
||||||
rgb: Default::default(),
|
rgb: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -625,6 +685,7 @@ pub struct LoginConfigHandler {
|
|||||||
pub port_forward: (String, i32),
|
pub port_forward: (String, i32),
|
||||||
pub support_press: bool,
|
pub support_press: bool,
|
||||||
pub support_refresh: bool,
|
pub support_refresh: bool,
|
||||||
|
pub version: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for LoginConfigHandler {
|
impl Deref for LoginConfigHandler {
|
||||||
@@ -659,6 +720,12 @@ impl LoginConfigHandler {
|
|||||||
self.config = config;
|
self.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_option(&mut self, k: String, v: String) {
|
||||||
|
let mut config = self.load_config();
|
||||||
|
config.options.insert(k, v);
|
||||||
|
self.save_config(config);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_view_style(&mut self, value: String) {
|
pub fn save_view_style(&mut self, value: String) {
|
||||||
let mut config = self.load_config();
|
let mut config = self.load_config();
|
||||||
config.view_style = value;
|
config.view_style = value;
|
||||||
@@ -875,6 +942,7 @@ impl LoginConfigHandler {
|
|||||||
if !pi.version.is_empty() {
|
if !pi.version.is_empty() {
|
||||||
self.support_press = true;
|
self.support_press = true;
|
||||||
self.support_refresh = true;
|
self.support_refresh = true;
|
||||||
|
self.version = crate::get_version_number(&pi.version);
|
||||||
}
|
}
|
||||||
let serde = PeerInfoSerde {
|
let serde = PeerInfoSerde {
|
||||||
username,
|
username,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub use arboard::Clipboard as ClipboardContext;
|
pub use arboard::Clipboard as ClipboardContext;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err, bail,
|
allow_err,
|
||||||
|
anyhow::bail,
|
||||||
compress::{compress as compress_func, decompress},
|
compress::{compress as compress_func, decompress},
|
||||||
config::{Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT},
|
config::{Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT},
|
||||||
log,
|
log,
|
||||||
@@ -8,9 +9,7 @@ use hbb_common::{
|
|||||||
protobuf::Message as _,
|
protobuf::Message as _,
|
||||||
protobuf::ProtobufEnum,
|
protobuf::ProtobufEnum,
|
||||||
rendezvous_proto::*,
|
rendezvous_proto::*,
|
||||||
sleep,
|
sleep, socket_client, tokio, ResultType,
|
||||||
tcp::FramedStream,
|
|
||||||
tokio, ResultType,
|
|
||||||
};
|
};
|
||||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||||
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
|
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
|
||||||
@@ -41,15 +40,6 @@ pub fn valid_for_numlock(evt: &KeyEvent) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn valid_for_capslock(evt: &KeyEvent) -> bool {
|
|
||||||
if let Some(key_event::Union::chr(ch)) = evt.union {
|
|
||||||
ch >= 'a' as u32 && ch <= 'z' as u32
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_clipboard_msg(content: String) -> Message {
|
pub fn create_clipboard_msg(content: String) -> Message {
|
||||||
let bytes = content.into_bytes();
|
let bytes = content.into_bytes();
|
||||||
let compressed = compress_func(&bytes, COMPRESS_LEVEL);
|
let compressed = compress_func(&bytes, COMPRESS_LEVEL);
|
||||||
@@ -95,7 +85,10 @@ pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc<Mutex<String>>>)
|
|||||||
let side = if old.is_none() { "host" } else { "client" };
|
let side = if old.is_none() { "host" } else { "client" };
|
||||||
let old = if let Some(old) = old { old } else { &CONTENT };
|
let old = if let Some(old) = old { old } else { &CONTENT };
|
||||||
*old.lock().unwrap() = content.clone();
|
*old.lock().unwrap() = content.clone();
|
||||||
allow_err!(ctx.set_text(content));
|
if !content.is_empty() {
|
||||||
|
// empty content make ctx.set_text crash
|
||||||
|
allow_err!(ctx.set_text(content));
|
||||||
|
}
|
||||||
log::debug!("{} updated on {}", CLIPBOARD_NAME, side);
|
log::debug!("{} updated on {}", CLIPBOARD_NAME, side);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -236,15 +229,19 @@ pub fn test_nat_type() {
|
|||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn test_nat_type_() -> ResultType<bool> {
|
async fn test_nat_type_() -> ResultType<bool> {
|
||||||
log::info!("Testing nat ...");
|
log::info!("Testing nat ...");
|
||||||
|
let is_direct = crate::ipc::get_socks_async(1_000).await.is_none(); // sync socks BTW
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let rendezvous_server = get_rendezvous_server(100).await;
|
let rendezvous_server = get_rendezvous_server(1_000).await;
|
||||||
let server1 = rendezvous_server;
|
let server1 = rendezvous_server;
|
||||||
let mut server2 = server1;
|
let tmp: Vec<&str> = server1.split(":").collect();
|
||||||
if server1.port() == 0 { // offline
|
if tmp.len() != 2 {
|
||||||
// avoid overflow crash
|
bail!("Invalid server address: {}", server1);
|
||||||
bail!("Offline");
|
|
||||||
}
|
}
|
||||||
server2.set_port(server1.port() - 1);
|
let port: u16 = tmp[1].parse()?;
|
||||||
|
if port == 0 {
|
||||||
|
bail!("Invalid server address: {}", server1);
|
||||||
|
}
|
||||||
|
let server2 = format!("{}:{}", tmp[0], port - 1);
|
||||||
let mut msg_out = RendezvousMessage::new();
|
let mut msg_out = RendezvousMessage::new();
|
||||||
let serial = Config::get_serial();
|
let serial = Config::get_serial();
|
||||||
msg_out.set_test_nat_request(TestNatRequest {
|
msg_out.set_test_nat_request(TestNatRequest {
|
||||||
@@ -253,15 +250,24 @@ async fn test_nat_type_() -> ResultType<bool> {
|
|||||||
});
|
});
|
||||||
let mut port1 = 0;
|
let mut port1 = 0;
|
||||||
let mut port2 = 0;
|
let mut port2 = 0;
|
||||||
|
let server1 = socket_client::get_target_addr(&server1)?;
|
||||||
|
let server2 = socket_client::get_target_addr(&server2)?;
|
||||||
let mut addr = Config::get_any_listen_addr();
|
let mut addr = Config::get_any_listen_addr();
|
||||||
for i in 0..2 {
|
for i in 0..2 {
|
||||||
let mut socket = FramedStream::new(
|
let mut socket = socket_client::connect_tcp(
|
||||||
if i == 0 { &server1 } else { &server2 },
|
if i == 0 {
|
||||||
|
server1.clone()
|
||||||
|
} else {
|
||||||
|
server2.clone()
|
||||||
|
},
|
||||||
addr,
|
addr,
|
||||||
RENDEZVOUS_TIMEOUT,
|
RENDEZVOUS_TIMEOUT,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
addr = socket.get_ref().local_addr()?;
|
if is_direct {
|
||||||
|
// to-do: should set NatType::UNKNOWN for proxy
|
||||||
|
addr = socket.local_addr();
|
||||||
|
}
|
||||||
socket.send(&msg_out).await?;
|
socket.send(&msg_out).await?;
|
||||||
if let Some(Ok(bytes)) = socket.next_timeout(3000).await {
|
if let Some(Ok(bytes)) = socket.next_timeout(3000).await {
|
||||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||||
@@ -298,12 +304,12 @@ async fn test_nat_type_() -> ResultType<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
pub async fn get_rendezvous_server(_ms_timeout: u64) -> std::net::SocketAddr {
|
pub async fn get_rendezvous_server(_ms_timeout: u64) -> String {
|
||||||
Config::get_rendezvous_server()
|
Config::get_rendezvous_server()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub async fn get_rendezvous_server(ms_timeout: u64) -> std::net::SocketAddr {
|
pub async fn get_rendezvous_server(ms_timeout: u64) -> String {
|
||||||
crate::ipc::get_rendezvous_server(ms_timeout).await
|
crate::ipc::get_rendezvous_server(ms_timeout).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,8 +332,8 @@ async fn test_rendezvous_server_() {
|
|||||||
for host in servers {
|
for host in servers {
|
||||||
futs.push(tokio::spawn(async move {
|
futs.push(tokio::spawn(async move {
|
||||||
let tm = std::time::Instant::now();
|
let tm = std::time::Instant::now();
|
||||||
if FramedStream::new(
|
if socket_client::connect_tcp(
|
||||||
&crate::check_port(&host, RENDEZVOUS_PORT),
|
crate::check_port(&host, RENDEZVOUS_PORT),
|
||||||
Config::get_any_listen_addr(),
|
Config::get_any_listen_addr(),
|
||||||
RENDEZVOUS_TIMEOUT,
|
RENDEZVOUS_TIMEOUT,
|
||||||
)
|
)
|
||||||
@@ -407,17 +413,6 @@ pub fn is_modifier(evt: &KeyEvent) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_if_valid_server(host: String) -> String {
|
|
||||||
let mut host = host;
|
|
||||||
if !host.contains(":") {
|
|
||||||
host = format!("{}:{}", host, 0);
|
|
||||||
}
|
|
||||||
match hbb_common::to_socket_addr(&host) {
|
|
||||||
Err(err) => err.to_string(),
|
|
||||||
Ok(_) => "".to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_version_number(v: &str) -> i64 {
|
pub fn get_version_number(v: &str) -> i64 {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
for x in v.split(".") {
|
for x in v.split(".") {
|
||||||
@@ -433,8 +428,11 @@ pub fn check_software_update() {
|
|||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn _check_software_update() -> hbb_common::ResultType<()> {
|
async fn _check_software_update() -> hbb_common::ResultType<()> {
|
||||||
sleep(3.).await;
|
sleep(3.).await;
|
||||||
let rendezvous_server = get_rendezvous_server(1_000).await;
|
|
||||||
let mut socket = hbb_common::udp::FramedSocket::new(Config::get_any_listen_addr()).await?;
|
let rendezvous_server = socket_client::get_target_addr(&get_rendezvous_server(1_000).await)?;
|
||||||
|
let mut socket =
|
||||||
|
socket_client::new_udp(Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT).await?;
|
||||||
|
|
||||||
let mut msg_out = RendezvousMessage::new();
|
let mut msg_out = RendezvousMessage::new();
|
||||||
msg_out.set_software_update(SoftwareUpdate {
|
msg_out.set_software_update(SoftwareUpdate {
|
||||||
url: crate::VERSION.to_owned(),
|
url: crate::VERSION.to_owned(),
|
||||||
@@ -454,3 +452,9 @@ async fn _check_software_update() -> hbb_common::ResultType<()> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_ip(id: &str) -> bool {
|
||||||
|
hbb_common::regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+$")
|
||||||
|
.unwrap()
|
||||||
|
.is_match(id)
|
||||||
|
}
|
||||||
|
|||||||
66
src/ipc.rs
66
src/ipc.rs
@@ -13,7 +13,7 @@ use parity_tokio_ipc::{
|
|||||||
Connection as Conn, ConnectionClient as ConnClient, Endpoint, Incoming, SecurityAttributes,
|
Connection as Conn, ConnectionClient as ConnClient, Endpoint, Incoming, SecurityAttributes,
|
||||||
};
|
};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, net::SocketAddr};
|
use std::collections::HashMap;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::{fs::File, io::prelude::*};
|
use std::{fs::File, io::prelude::*};
|
||||||
|
|
||||||
@@ -89,6 +89,7 @@ pub enum Data {
|
|||||||
NatType(Option<i32>),
|
NatType(Option<i32>),
|
||||||
ConfirmedKey(Option<(Vec<u8>, Vec<u8>)>),
|
ConfirmedKey(Option<(Vec<u8>, Vec<u8>)>),
|
||||||
RawMessage(Vec<u8>),
|
RawMessage(Vec<u8>),
|
||||||
|
Socks(Option<config::Socks5Server>),
|
||||||
FS(FS),
|
FS(FS),
|
||||||
SessionsUpdated,
|
SessionsUpdated,
|
||||||
Test,
|
Test,
|
||||||
@@ -193,6 +194,20 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
};
|
};
|
||||||
allow_err!(stream.send(&Data::ConfirmedKey(out)).await);
|
allow_err!(stream.send(&Data::ConfirmedKey(out)).await);
|
||||||
}
|
}
|
||||||
|
Data::Socks(s) => match s {
|
||||||
|
None => {
|
||||||
|
allow_err!(stream.send(&Data::Socks(Config::get_socks())).await);
|
||||||
|
}
|
||||||
|
Some(data) => {
|
||||||
|
if data.proxy.is_empty() {
|
||||||
|
Config::set_socks(None);
|
||||||
|
} else {
|
||||||
|
Config::set_socks(Some(data));
|
||||||
|
}
|
||||||
|
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||||
|
log::info!("socks updated");
|
||||||
|
}
|
||||||
|
},
|
||||||
Data::Config((name, value)) => match value {
|
Data::Config((name, value)) => match value {
|
||||||
None => {
|
None => {
|
||||||
let value;
|
let value;
|
||||||
@@ -203,7 +218,9 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
} else if name == "salt" {
|
} else if name == "salt" {
|
||||||
value = Some(Config::get_salt());
|
value = Some(Config::get_salt());
|
||||||
} else if name == "rendezvous_server" {
|
} else if name == "rendezvous_server" {
|
||||||
value = Some(Config::get_rendezvous_server().to_string());
|
value = Some(Config::get_rendezvous_server());
|
||||||
|
} else if name == "rendezvous_servers" {
|
||||||
|
value = Some(Config::get_rendezvous_servers().join(","));
|
||||||
} else {
|
} else {
|
||||||
value = None;
|
value = None;
|
||||||
}
|
}
|
||||||
@@ -211,6 +228,7 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
}
|
}
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
if name == "id" {
|
if name == "id" {
|
||||||
|
Config::set_key_confirmed(false);
|
||||||
Config::set_id(&value);
|
Config::set_id(&value);
|
||||||
} else if name == "password" {
|
} else if name == "password" {
|
||||||
Config::set_password(&value);
|
Config::set_password(&value);
|
||||||
@@ -414,13 +432,12 @@ pub fn get_password() -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_rendezvous_server(ms_timeout: u64) -> SocketAddr {
|
pub async fn get_rendezvous_server(ms_timeout: u64) -> String {
|
||||||
if let Ok(Some(v)) = get_config_async("rendezvous_server", ms_timeout).await {
|
if let Ok(Some(v)) = get_config_async("rendezvous_server", ms_timeout).await {
|
||||||
if let Ok(v) = v.parse() {
|
v
|
||||||
return v;
|
} else {
|
||||||
}
|
Config::get_rendezvous_server()
|
||||||
}
|
}
|
||||||
return Config::get_rendezvous_server();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_options_(ms_timeout: u64) -> ResultType<HashMap<String, String>> {
|
async fn get_options_(ms_timeout: u64) -> ResultType<HashMap<String, String>> {
|
||||||
@@ -485,6 +502,41 @@ pub async fn get_nat_type(ms_timeout: u64) -> i32 {
|
|||||||
.unwrap_or(Config::get_nat_type())
|
.unwrap_or(Config::get_nat_type())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn get_socks_(ms_timeout: u64) -> ResultType<Option<config::Socks5Server>> {
|
||||||
|
let mut c = connect(ms_timeout, "").await?;
|
||||||
|
c.send(&Data::Socks(None)).await?;
|
||||||
|
if let Some(Data::Socks(value)) = c.next_timeout(ms_timeout).await? {
|
||||||
|
Config::set_socks(value.clone());
|
||||||
|
Ok(value)
|
||||||
|
} else {
|
||||||
|
Ok(Config::get_socks())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_socks_async(ms_timeout: u64) -> Option<config::Socks5Server> {
|
||||||
|
get_socks_(ms_timeout).await.unwrap_or(Config::get_socks())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
pub async fn get_socks() -> Option<config::Socks5Server> {
|
||||||
|
get_socks_async(1_000).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
pub async fn set_socks(value: config::Socks5Server) -> ResultType<()> {
|
||||||
|
Config::set_socks(if value.proxy.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(value.clone())
|
||||||
|
});
|
||||||
|
connect(1_000, "")
|
||||||
|
.await?
|
||||||
|
.send(&Data::Socks(Some(value)))
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
static mut SHARED_MEMORY: *mut i64 = std::ptr::null_mut();
|
static mut SHARED_MEMORY: *mut i64 = std::ptr::null_mut();
|
||||||
|
|
||||||
|
|||||||
37
src/lang.rs
Normal file
37
src/lang.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use hbb_common::{config::Config, log};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
mod cn;
|
||||||
|
mod en;
|
||||||
|
mod fr;
|
||||||
|
mod it;
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
pub fn translate(name: String) -> String {
|
||||||
|
let locale = sys_locale::get_locale().unwrap_or_default().to_lowercase();
|
||||||
|
log::debug!("The current locale is {}", locale);
|
||||||
|
translate_locale(name, &locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translate_locale(name: String, locale: &str) -> String {
|
||||||
|
let mut lang = Config::get_option("lang");
|
||||||
|
if lang.is_empty() {
|
||||||
|
lang = locale
|
||||||
|
.split("-")
|
||||||
|
.last()
|
||||||
|
.map(|x| x.split("_").last().unwrap_or_default())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_owned();
|
||||||
|
}
|
||||||
|
let m = match lang.to_lowercase().as_str() {
|
||||||
|
"fr" => fr::T.deref(),
|
||||||
|
"cn" => cn::T.deref(),
|
||||||
|
"it" => it::T.deref(),
|
||||||
|
_ => en::T.deref(),
|
||||||
|
};
|
||||||
|
if let Some(v) = m.get(&name as &str) {
|
||||||
|
v.to_string()
|
||||||
|
} else {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
198
src/lang/cn.rs
Normal file
198
src/lang/cn.rs
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||||
|
[
|
||||||
|
("Status", "状态"),
|
||||||
|
("Your Desktop", "你的桌面"),
|
||||||
|
("desk_tip", "你的桌面可以通过下面的 ID和密码访问。"),
|
||||||
|
("Password", "密码"),
|
||||||
|
("Ready", "就绪"),
|
||||||
|
("connecting_status", "正在接入RustDesk网络..."),
|
||||||
|
("Enable Service", "允许服务"),
|
||||||
|
("Start Service", "启动服务"),
|
||||||
|
("Service is not running", "服务没有启动"),
|
||||||
|
("not_ready_status", "未就绪,请检查网络连接"),
|
||||||
|
("Control Remote Desktop", "控制远程桌面"),
|
||||||
|
("Transfer File", "传输文件"),
|
||||||
|
("Connect", "连接"),
|
||||||
|
("Recent Sessions", "最近访问过"),
|
||||||
|
("Address Book", "地址簿"),
|
||||||
|
("Confirmation", "确认"),
|
||||||
|
("TCP Tunneling", "TCP隧道"),
|
||||||
|
("Remove", "删除"),
|
||||||
|
("Refresh random password", "刷新随机密码"),
|
||||||
|
("Set your own password", "设置密码"),
|
||||||
|
("Enable Keyboard/Mouse", "允许控制键盘/鼠标"),
|
||||||
|
("Enable Clipboard", "允许同步剪贴板"),
|
||||||
|
("Enable File Transfer", "允许传输文件"),
|
||||||
|
("Enable TCP Tunneling", "允许建立TCP隧道"),
|
||||||
|
("IP Whitelisting", "IP白名单"),
|
||||||
|
("ID/Relay Server", "ID/中继服务器"),
|
||||||
|
("Stop service", "停止服务"),
|
||||||
|
("Change ID", "改变ID"),
|
||||||
|
("Website", "网站"),
|
||||||
|
("About", "关于"),
|
||||||
|
("Mute", "静音"),
|
||||||
|
("Audio Input", "音频输入"),
|
||||||
|
("ID Server", "ID服务器"),
|
||||||
|
("Relay Server", "中继服务器"),
|
||||||
|
("API Server", "API服务器"),
|
||||||
|
("invalid_http", "必须以http://或者https://开头"),
|
||||||
|
("Invalid IP", "无效IP"),
|
||||||
|
("id_change_tip", "只可以使用字母a-z, A-Z, 0-9, _ (下划线)。首字母必须是a-z, A-Z。长度在6与16之间。"),
|
||||||
|
("Invalid format", "无效格式"),
|
||||||
|
("This function is turned off by the server", "服务器关闭了此功能"),
|
||||||
|
("Not available", "已被占用"),
|
||||||
|
("Too frequent", "修改太频繁,请稍后再试"),
|
||||||
|
("Cancel", "取消"),
|
||||||
|
("Skip", "跳过"),
|
||||||
|
("Close", "关闭"),
|
||||||
|
("Retry", "再试"),
|
||||||
|
("OK", "确认"),
|
||||||
|
("Password Required", "需要密码"),
|
||||||
|
("Please enter your password", "请输入密码"),
|
||||||
|
("Remember password", "记住密码"),
|
||||||
|
("Wrong Password", "密码错误"),
|
||||||
|
("Do you want to enter again?", "还想输入一次吗?"),
|
||||||
|
("Connection Error", "连接错误"),
|
||||||
|
("Error", "错误"),
|
||||||
|
("Reset by the peer", "连接被对方关闭"),
|
||||||
|
("Connecting...", "正在连接..."),
|
||||||
|
("Connection in progress. Please wait.", "连接进行中,请稍等。"),
|
||||||
|
("Please try 1 minute later", "一分钟后再试"),
|
||||||
|
("Login Error", "登录错误"),
|
||||||
|
("Successful", "成功"),
|
||||||
|
("Connected, waiting for image...", "已连接,等待画面传输..."),
|
||||||
|
("Name", "文件名"),
|
||||||
|
("Modified", "修改时间"),
|
||||||
|
("Size", "大小"),
|
||||||
|
("Show Hidden Files", "显示隐藏文件"),
|
||||||
|
("Receive", "接受"),
|
||||||
|
("Send", "发送"),
|
||||||
|
("Remote Computer", "远程电脑"),
|
||||||
|
("Local Computer", "本地电脑"),
|
||||||
|
("Confirm Delete", "确认删除"),
|
||||||
|
("Are you sure you want to delete this file?", "是否删除此文件?"),
|
||||||
|
("Do this for all conflicts", "应用于其它冲突"),
|
||||||
|
("Deleting", "正在删除"),
|
||||||
|
("files", "文件"),
|
||||||
|
("Waiting", "等待..."),
|
||||||
|
("Finished", "完成"),
|
||||||
|
("Custom Image Quality", "设置画面质量"),
|
||||||
|
("Privacy mode", "隐私模式"),
|
||||||
|
("Adjust Window", "调节窗口"),
|
||||||
|
("Original", "原始比例"),
|
||||||
|
("Shrink", "收缩"),
|
||||||
|
("Stretch", "伸展"),
|
||||||
|
("Good image quality", "好画质"),
|
||||||
|
("Balanced", "一般画质"),
|
||||||
|
("Optimize reaction time", "优化反应时间"),
|
||||||
|
("Custom", "自定义画质"),
|
||||||
|
("Show remote cursor", "显示远程光标"),
|
||||||
|
("Disable clipboard", "禁止剪贴板"),
|
||||||
|
("Lock after session end", "断开后锁定远程电脑"),
|
||||||
|
("Insert", "插入"),
|
||||||
|
("Insert Lock", "锁定远程电脑"),
|
||||||
|
("Refresh", "刷新画面"),
|
||||||
|
("ID does not exist", "ID不存在"),
|
||||||
|
("Failed to connect to rendezvous server", "连接注册服务器失败"),
|
||||||
|
("Please try later", "请稍后再试"),
|
||||||
|
("Remote desktop is offline", "远程电脑不在线"),
|
||||||
|
("Key mismatch", "Key不匹配"),
|
||||||
|
("Timeout", "连接超时"),
|
||||||
|
("Failed to connect to relay server", "无法连接到中继服务器"),
|
||||||
|
("Failed to connect via rendezvous server", "无法通过注册服务器建立连接"),
|
||||||
|
("Failed to connect via relay server", "无法通过中继服务器建立连接"),
|
||||||
|
("Failed to make direct connection to remote desktop", "无法建立直接连接"),
|
||||||
|
("Set Password", "设置密码"),
|
||||||
|
("OS Password", "操作系统密码"),
|
||||||
|
("install_tip", "你正在运行未安装版本,由于UAC限制,作为被控端,会在某些情况下无法控制鼠标键盘,或者录制屏幕,请点击下面的按钮将RustDesk安装到系统,从而规避上述问题。"),
|
||||||
|
("Click to upgrade", "点击这里升级"),
|
||||||
|
("Configuration Permissions", "配置权限"),
|
||||||
|
("Configure", "配置"),
|
||||||
|
("config_acc", "为了能够远程控制你的桌面, 请给予RustDesk\"辅助功能\" 权限。"),
|
||||||
|
("config_screen", "为了能够远程访问你的桌面, 请给予RustDesk\"屏幕录制\" 权限。"),
|
||||||
|
("Installing ...", "安装 ..."),
|
||||||
|
("Install", "安装"),
|
||||||
|
("Installation", "安装"),
|
||||||
|
("Installation Path", "安装路径"),
|
||||||
|
("Create start menu shortcuts", "创建启动菜单快捷方式"),
|
||||||
|
("Create desktop icon", "创建桌面图标"),
|
||||||
|
("agreement_tip", "开始安装即表示接受许可协议。"),
|
||||||
|
("Accept and Install", "同意并安装"),
|
||||||
|
("End-user license agreement", "用户协议"),
|
||||||
|
("Generating ...", "正在产生 ..."),
|
||||||
|
("Your installation is lower version.", "你安装的版本比当前运行的低。"),
|
||||||
|
("not_close_tcp_tip", "请在使用隧道的时候,不要关闭本窗口"),
|
||||||
|
("Listening ...", "正在等待隧道连接 ..."),
|
||||||
|
("Remote Host", "远程主机"),
|
||||||
|
("Remote Port", "远程端口"),
|
||||||
|
("Action", "动作"),
|
||||||
|
("Add", "添加"),
|
||||||
|
("Local Port", "本地端口"),
|
||||||
|
("setup_server_tip", "如果需要更快连接速度,你可以选择自建服务器"),
|
||||||
|
("Too short, at least 6 characters.", "太短了,至少6个字符"),
|
||||||
|
("The confirmation is not identical.", "两次输入不匹配"),
|
||||||
|
("Permissions", "权限"),
|
||||||
|
("Accept", "接受"),
|
||||||
|
("Dismiss", "拒绝"),
|
||||||
|
("Disconnect", "断开连接"),
|
||||||
|
("Allow using keyboard and mouse", "允许使用键盘鼠标"),
|
||||||
|
("Allow using clipboard", "允许使用剪贴板"),
|
||||||
|
("Allow hearing sound", "允许听到声音"),
|
||||||
|
("Connected", "已经连接"),
|
||||||
|
("Direct and encrypted connection", "加密直连"),
|
||||||
|
("Relayed and encrypted connection", "加密中继连接"),
|
||||||
|
("Direct and unencrypted connection", "非加密直连"),
|
||||||
|
("Relayed and unencrypted connection", "非加密中继连接"),
|
||||||
|
("Enter Remote ID", "输入对方ID"),
|
||||||
|
("Enter your password", "输入密码"),
|
||||||
|
("Logging in...", "正在登录..."),
|
||||||
|
("Enable RDP session sharing", "允许RDP会话共享"),
|
||||||
|
("Auto Login", "自动登录(设置断开后锁定才有效)"),
|
||||||
|
("Enable Direct IP Access", "允许IP直接访问"),
|
||||||
|
("Rename", "改名"),
|
||||||
|
("Space", "空格"),
|
||||||
|
("Create Desktop Shortcut", "创建桌面快捷方式"),
|
||||||
|
("Change Path", "改变路径"),
|
||||||
|
("Create Folder", "创建文件夹"),
|
||||||
|
("Please enter the folder name", "请输入文件夹名称"),
|
||||||
|
("Fix it", "修复"),
|
||||||
|
("Warning", "警告"),
|
||||||
|
("Login screen using Wayland is not supported", "不支持使用 Wayland 登录界面"),
|
||||||
|
("Reboot required", "重启后才能生效"),
|
||||||
|
("Unsupported display server ", "不支持当前显示服务器"),
|
||||||
|
("x11 expected", "请切换到 x11"),
|
||||||
|
("Port", "端口"),
|
||||||
|
("Settings", "设置"),
|
||||||
|
("Username", " 用户名"),
|
||||||
|
("Invalid port", "无效端口"),
|
||||||
|
("Closed manually by the peer", "被对方手动关闭"),
|
||||||
|
("Enable remote configuration modification", "允许远程修改配置"),
|
||||||
|
("Run without install", "无安装运行"),
|
||||||
|
("Always connected via relay", "强制走中继连接"),
|
||||||
|
("Always connect via relay", "强制走中继连接"),
|
||||||
|
("whitelist_tip", "只有白名单里的ip才能访问我"),
|
||||||
|
("Login", "登录"),
|
||||||
|
("Logout", "登出"),
|
||||||
|
("Tags", "标签"),
|
||||||
|
("Search ID", "查找ID"),
|
||||||
|
("Current Wayland display server is not supported", "不支持 Wayland 显示服务器"),
|
||||||
|
("whitelist_sep", "可以使用逗号,分号,空格或者换行符作为分隔符"),
|
||||||
|
("Add ID", "增加ID"),
|
||||||
|
("Add Tag", "增加标签"),
|
||||||
|
("Unselect all tags", "取消选择所有标签"),
|
||||||
|
("Network error", "网络错误"),
|
||||||
|
("Username missed", "用户名没有填写"),
|
||||||
|
("Password missed", "密码没有填写"),
|
||||||
|
("Wrong credentials", "用户名或者密码错误"),
|
||||||
|
("Edit Tag", "修改标签"),
|
||||||
|
("Unremember Password", "忘掉密码"),
|
||||||
|
("Favorites", "收藏"),
|
||||||
|
("Add to Favorites", "加入到收藏"),
|
||||||
|
("Remove from Favorites", "从收藏中删除"),
|
||||||
|
("Empty", "空空如也"),
|
||||||
|
("Invalid folder name", "无效文件夹名称"),
|
||||||
|
("Socks5 Proxy", "Socks5 代理"),
|
||||||
|
("Hostname", "主机名"),
|
||||||
|
].iter().cloned().collect();
|
||||||
|
}
|
||||||
20
src/lang/en.rs
Normal file
20
src/lang/en.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||||
|
[
|
||||||
|
("desk_tip", "Your desktop can be accessed with this ID and password."),
|
||||||
|
("connecting_status", "Connecting to the RustDesk network..."),
|
||||||
|
("not_ready_status", "Not ready. Please check your connection"),
|
||||||
|
("id_change_tip", "Only a-z, A-Z, 0-9 and _ (underscore) characters allowed. The first letter must be a-z, A-Z. Length between 6 and 16."),
|
||||||
|
("install_tip", "Due to UAC, RustDesk can not work properly as the remote side in some cases. To avoid UAC, please click the button below to install RustDesk to the system."),
|
||||||
|
("config_acc", "In order to control your Desktop remotely, you need to grant RustDesk \"Accessibility\" permissions."),
|
||||||
|
("config_screen", "In order to access your Desktop remotely, you need to grant RustDesk \"Screen Recording\" permissions."),
|
||||||
|
("agreement_tip", "By starting the installation, you accept the license agreement."),
|
||||||
|
("not_close_tcp_tip", "Don't close this window while you are using the tunnel"),
|
||||||
|
("setup_server_tip", "For faster connection, please set up your own server"),
|
||||||
|
("Auto Login", "Auto Login (Only valid if you set \"Lock after session end\")"),
|
||||||
|
("whitelist_tip", "Only whitelisted IP can access me"),
|
||||||
|
("whitelist_sep", "Seperated by comma, semicolon, spaces or new line"),
|
||||||
|
("Wrong credentials", "Wrong username or password"),
|
||||||
|
("invalid_http", "must start with http:// or https://"),
|
||||||
|
].iter().cloned().collect();
|
||||||
|
}
|
||||||
192
src/lang/fr.rs
Normal file
192
src/lang/fr.rs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||||
|
[
|
||||||
|
("Status", "Statut"),
|
||||||
|
("Your Desktop", "Votre bureau"),
|
||||||
|
("desk_tip", "Votre bureau est accessible via l'identifiant et le mot de passe ci-dessous."),
|
||||||
|
("Password", "Mot de passe"),
|
||||||
|
("Ready", "Prêt"),
|
||||||
|
("connecting_status", "Connexion au réseau RustDesk..."),
|
||||||
|
("Enable Service", "Autoriser le service"),
|
||||||
|
("Start Service", "Démarrer le service"),
|
||||||
|
("Service is not running", "Le service ne fonctionne pas"),
|
||||||
|
("not_ready_status", "Pas prêt, veuillez vérifier la connexion réseau"),
|
||||||
|
("Control Remote Desktop", "Contrôler le bureau à distance"),
|
||||||
|
("Transfer File", "Transférer le fichier"),
|
||||||
|
("Connect", "Connecter"),
|
||||||
|
("Recent Sessions", "Sessions récentes"),
|
||||||
|
("Address Book", "Carnet d'adresses"),
|
||||||
|
("Confirmation", "Confirmation"),
|
||||||
|
("TCP Tunneling", "Tunneling TCP"),
|
||||||
|
("Remove", "Supprimer"),
|
||||||
|
("Refresh random password", "Actualiser le mot de passe aléatoire"),
|
||||||
|
("Set your own password", "Définir votre propre mot de passe"),
|
||||||
|
("Enable Keyboard/Mouse", "Activer le contrôle clavier/souris"),
|
||||||
|
("Enable Clipboard", "Activer la synchronisation du presse-papiers"),
|
||||||
|
("Enable File Transfer", "Activer le transfert de fichiers"),
|
||||||
|
("Enable TCP Tunneling", "Activer le tunneling TCP"),
|
||||||
|
("IP Whitelisting", "Liste blanche IP"),
|
||||||
|
("ID/Relay Server", "ID/Serveur Relais"),
|
||||||
|
("Stop service", "Arrêter service"),
|
||||||
|
("Change ID", "Changer d'ID"),
|
||||||
|
("Website", "Site Web"),
|
||||||
|
("About", "Sur"),
|
||||||
|
("Mute", "Muet"),
|
||||||
|
("Audio Input", "Entrée audio"),
|
||||||
|
("ID Server", "Serveur ID"),
|
||||||
|
("Relay Server", "Serveur Relais"),
|
||||||
|
("API Server", "Serveur API"),
|
||||||
|
("invalid_http", "Doit commencer par http:// ou https://"),
|
||||||
|
("Invalid IP", "IP invalide"),
|
||||||
|
("id_change_tip", "Seules les lettres a-z, A-Z, 0-9, _ (trait de soulignement) peuvent être utilisées. La première lettre doit être a-z, A-Z. La longueur est comprise entre 6 et 16."),
|
||||||
|
("Invalid format", "Format invalide"),
|
||||||
|
("This function is turned off by the server", "Cette fonction est désactivée par le serveur"),
|
||||||
|
("Not available", "Indisponible"),
|
||||||
|
("Too frequent", "Modifier trop fréquemment, veuillez réessayer plus tard"),
|
||||||
|
("Cancel", "Annuler"),
|
||||||
|
("Skip", "Ignorer"),
|
||||||
|
("Close", "Fermer"),
|
||||||
|
("Retry", "Réessayer"),
|
||||||
|
("OK", "Confirmer"),
|
||||||
|
("Password Required", "Mot de passe requis"),
|
||||||
|
("Please enter your password", "Veuillez saisir votre mot de passe"),
|
||||||
|
("Remember password", "Mémoriser le mot de passe"),
|
||||||
|
("Wrong Password", "Mauvais mot de passe"),
|
||||||
|
("Do you want to enter again?", "Voulez-vous participer à nouveau ?"),
|
||||||
|
("Connection Error", "Erreur de connexion"),
|
||||||
|
("Error", "Erreur"),
|
||||||
|
("Reset by the peer", "La connexion a été fermée par le pair"),
|
||||||
|
("Connecting...", "Connexion..."),
|
||||||
|
("Connection in progress. Please wait.", "Connexion en cours. Veuillez patienter."),
|
||||||
|
("Please try 1 minute later", "Réessayez dans une minute"),
|
||||||
|
("Login Error", "Erreur de connexion"),
|
||||||
|
("Successful", "Succès"),
|
||||||
|
("Connected, waiting for image...", "Connecté, en attente de transmission d'image..."),
|
||||||
|
("Name", "Nom du fichier"),
|
||||||
|
("Modified", "Modifié"),
|
||||||
|
("Size", "Taille"),
|
||||||
|
("Show Hidden Files", "Afficher les fichiers cachés"),
|
||||||
|
("Receive", "Accepter"),
|
||||||
|
("Send", "Envoyer"),
|
||||||
|
("Remote Computer", "Ordinateur distant"),
|
||||||
|
("Local Computer", "Ordinateur local"),
|
||||||
|
("Confirm Delete", "Confirmer la suppression"),
|
||||||
|
("Are you sure you want to delete this file?", "Voulez-vous vraiment supprimer ce fichier ?"),
|
||||||
|
("Do this for all conflicts", "Appliquer à d'autres conflits"),
|
||||||
|
("Deleting", "Suppression"),
|
||||||
|
("files", "fichier"),
|
||||||
|
("Waiting", "En attente en attente..."),
|
||||||
|
("Finished", "Terminé"),
|
||||||
|
("Custom Image Quality", "Définir la qualité d'image"),
|
||||||
|
("Privacy mode", "Mode privé"),
|
||||||
|
("Adjust Window", "Ajuster la fenêtre"),
|
||||||
|
("Original", "Ratio d'origine"),
|
||||||
|
("Shrink", "Rétréci"),
|
||||||
|
("Stretch", "Étirer"),
|
||||||
|
("Good image quality", "Bonne qualité d'image"),
|
||||||
|
("Balanced", "Qualité d'image normale"),
|
||||||
|
("Optimize reaction time", "Optimiser le temps de réaction"),
|
||||||
|
("Custom", "Qualité d'image personnalisée"),
|
||||||
|
("Show remote cursor", "Afficher le curseur distant"),
|
||||||
|
("Disable clipboard", "Désactiver le presse-papiers"),
|
||||||
|
("Lock after session end", "Verrouiller l'ordinateur distant après la déconnexion"),
|
||||||
|
("Insert", "Insérer"),
|
||||||
|
("Insert Lock", "Verrouiller l'ordinateur distant"),
|
||||||
|
("Refresh", "Rafraîchir l'écran"),
|
||||||
|
("ID does not exist", "L'ID n'existe pas"),
|
||||||
|
("Failed to connect to rendezvous server", "Échec de la connexion au serveur de rendez-vous"),
|
||||||
|
("Please try later", "Veuillez essayer plus tard"),
|
||||||
|
("Remote desktop is offline", "Le bureau à distance est hors ligne"),
|
||||||
|
("Key mismatch", "Discordance de clé"),
|
||||||
|
("Timeout", "Connexion expirée"),
|
||||||
|
("Failed to connect to relay server", "Échec de la connexion au serveur relais"),
|
||||||
|
("Failed to connect via rendezvous server", "Échec de l'établissement d'une connexion via le serveur de rendez-vous"),
|
||||||
|
("Failed to connect via relay server", "Impossible d'établir une connexion via le serveur relais"),
|
||||||
|
("Failed to make direct connection to remote desktop", "Impossible d'établir une connexion directe"),
|
||||||
|
("Set Password", "Définir le mot de passe"),
|
||||||
|
("OS Password", "Mot de passe du système d'exploitation"),
|
||||||
|
("install_tip", "Vous utilisez une version désinstallée. En raison des restrictions UAC, en tant que terminal contrôlé, dans certains cas, il ne sera pas en mesure de contrôler la souris et le clavier ou d'enregistrer l'écran. Veuillez cliquer sur le bouton ci-dessous pour installer RustDesk au système pour éviter la question ci-dessus."),
|
||||||
|
("Click to upgrade", "Cliquez pour mettre à niveau"),
|
||||||
|
("Configuration Permissions", "Autorisations de configuration"),
|
||||||
|
("Configure", "Configurer"),
|
||||||
|
("config_acc", "Afin de pouvoir contrôler votre bureau à distance, veuillez donner l'autorisation\"accessibilité\" à RustDesk."),
|
||||||
|
("config_screen", "Afin de pouvoir accéder à votre bureau à distance, veuillez donner l'autorisation à RustDesk\"enregistrement d'écran\"."),
|
||||||
|
("Installing ...", "Installation ..."),
|
||||||
|
("Install", "Installer"),
|
||||||
|
("Installation", "Installation"),
|
||||||
|
("Installation Path", "Chemin d'installation"),
|
||||||
|
("Create start menu shortcuts", "Créer des raccourcis dans le menu démarrer"),
|
||||||
|
("Create desktop icon", "Créer une icône sur le bureau"),
|
||||||
|
("agreement_tip", "Démarrer l'installation signifie accepter le contrat de licence."),
|
||||||
|
("Accept and Install", "Accepter et installer"),
|
||||||
|
("End-user license agreement", "Contrat d'utilisateur"),
|
||||||
|
("Generating ...", "Génération ..."),
|
||||||
|
("Your installation is lower version.", "La version que vous avez installée est inférieure à la version en cours d'exécution."),
|
||||||
|
("not_close_tcp_tip", "Veuillez ne pas fermer cette fenêtre lors de l'utilisation du tunnel"),
|
||||||
|
("Listening ...", "En attente de connexion tunnel..."),
|
||||||
|
("Remote Host", "Hôte distant"),
|
||||||
|
("Remote Port", "Port distant"),
|
||||||
|
("Action", "Action"),
|
||||||
|
("Add", "Ajouter"),
|
||||||
|
("Local Port", "Port local"),
|
||||||
|
("setup_server_tip", "Si vous avez besoin d'une vitesse de connexion plus rapide, vous pouvez choisir de créer votre propre serveur"),
|
||||||
|
("Too short, at least 6 characters.", "Trop court, au moins 6 caractères."),
|
||||||
|
("The confirmation is not identical.", "Les deux entrées ne correspondent pas"),
|
||||||
|
("Permissions", "Autorisations"),
|
||||||
|
("Accept", "Accepter"),
|
||||||
|
("Dismiss", "Rejeter"),
|
||||||
|
("Disconnect", "Déconnecter"),
|
||||||
|
("Allow using keyboard and mouse", "Autoriser l'utilisation du clavier et de la souris"),
|
||||||
|
("Allow using clipboard", "Autoriser l'utilisation du presse-papiers"),
|
||||||
|
("Allow hearing sound", "Autoriser l'audition du son"),
|
||||||
|
("Connected", "Connecté"),
|
||||||
|
("Direct and encrypted connection", "Connexion directe cryptée"),
|
||||||
|
("Relayed and encrypted connection", "Connexion relais cryptée"),
|
||||||
|
("Direct and unencrypted connection", "Connexion directe non cryptée"),
|
||||||
|
("Relayed and unencrypted connection", "Connexion relais non cryptée"),
|
||||||
|
("Enter Remote ID", "Entrez l'ID à distance"),
|
||||||
|
("Enter your password", "Entrez votre mot de passe"),
|
||||||
|
("Logging in...", "Se connecter..."),
|
||||||
|
("Enable RDP session sharing", "Activer le partage de session RDP"),
|
||||||
|
("Auto Login", "Connexion automatique (le verrouillage ne sera effectif qu'après la déconnexion du paramètre)"),
|
||||||
|
("Enable Direct IP Access", "Autoriser l'accès direct IP"),
|
||||||
|
("Rename", "Renommer"),
|
||||||
|
("Space", "Espace"),
|
||||||
|
("Create Desktop Shortcut", "Créer un raccourci sur le bureau"),
|
||||||
|
("Change Path", "Changer de chemin"),
|
||||||
|
("Create Folder", "Créer un dossier"),
|
||||||
|
("Please enter the folder name", "Veuillez saisir le nom du dossier"),
|
||||||
|
("Fix it", "Réparez-le"),
|
||||||
|
("Warning", "Avertissement"),
|
||||||
|
("Login screen using Wayland is not supported", "L'écran de connexion utilisant Wayland n'est pas pris en charge"),
|
||||||
|
("Reboot required", "Redémarrage pour prendre effet"),
|
||||||
|
("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"),
|
||||||
|
("x11 expected", "Veuillez passer à x11"),
|
||||||
|
("Port", "Port"),
|
||||||
|
("Settings", "Paramètres"),
|
||||||
|
("Username", " Nom d'utilisateur"),
|
||||||
|
("Invalid port", "Port invalide"),
|
||||||
|
("Closed manually by the peer", "Fermé manuellement par le pair"),
|
||||||
|
("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"),
|
||||||
|
("Run without install", "Exécuter sans installer"),
|
||||||
|
("Always connected via relay", "Forcer la connexion relais"),
|
||||||
|
("Always connect via relay", "Forcer la connexion relais"),
|
||||||
|
("whitelist_tip", "Seul l'ip dans la liste blanche peut m'accéder"),
|
||||||
|
("Login", "Connexion"),
|
||||||
|
("Logout", "Déconnexion"),
|
||||||
|
("Tags", "Étiqueter"),
|
||||||
|
("Search ID", "Identifiant de recherche"),
|
||||||
|
("Current Wayland display server is not supported", "Le serveur d'affichage Wayland n'est pas pris en charge"),
|
||||||
|
("whitelist_sep", "Vous pouvez utiliser une virgule, un point-virgule, un espace ou une nouvelle ligne comme séparateur"),
|
||||||
|
("Add ID", "Ajouter ID"),
|
||||||
|
("Add Tag", "Ajouter une balise"),
|
||||||
|
("Unselect all tags", "Désélectionner toutes les balises"),
|
||||||
|
("Network error", "Erreur réseau"),
|
||||||
|
("Username missed", "Nom d'utilisateur manqué"),
|
||||||
|
("Password missed", "Mot de passe manqué"),
|
||||||
|
("Wrong credentials", "Identifiant ou mot de passe erroné"),
|
||||||
|
("Edit Tag", "Modifier la balise"),
|
||||||
|
("Invalid folder name", "Nom de dossier invalide"),
|
||||||
|
("Hostname", "nom d'hôte"),
|
||||||
|
].iter().cloned().collect();
|
||||||
|
}
|
||||||
193
src/lang/it.rs
Normal file
193
src/lang/it.rs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||||
|
[
|
||||||
|
("Status", "Stato"),
|
||||||
|
("Your Desktop", "Il tuo desktop"),
|
||||||
|
("desk_tip", "Puoi accedere al tuo desktop usando l'ID e la password riportati qui."),
|
||||||
|
("Password", "Password"),
|
||||||
|
("Ready", "Pronto"),
|
||||||
|
("connecting_status", "Connessione alla rete RustDesk in corso..."),
|
||||||
|
("Enable Service", "Abilita servizio"),
|
||||||
|
("Start Service", "Avvia servizio"),
|
||||||
|
("Service is not running", "Il servizio non è in esecuzione"),
|
||||||
|
("not_ready_status", "Non pronto. Verifica la tua connessione"),
|
||||||
|
("Control Remote Desktop", "Controlla una scrivania remota"),
|
||||||
|
("Transfer File", "Trasferisci file"),
|
||||||
|
("Connect", "Connetti"),
|
||||||
|
("Recent Sessions", "Sessioni recenti"),
|
||||||
|
("Address Book", "Rubrica"),
|
||||||
|
("Confirmation", "Conferma"),
|
||||||
|
("TCP Tunneling", "Tunnel TCP"),
|
||||||
|
("Remove", "Rimuovi"),
|
||||||
|
("Refresh random password", "Nuova password casuale"),
|
||||||
|
("Set your own password", "Imposta la tua password"),
|
||||||
|
("Enable Keyboard/Mouse", "Abilita tastiera/mouse"),
|
||||||
|
("Enable Clipboard", "Abilita appunti"),
|
||||||
|
("Enable File Transfer", "Abilita trasferimento file"),
|
||||||
|
("Enable TCP Tunneling", "Abilita tunnel TCP"),
|
||||||
|
("IP Whitelisting", "IP autorizzati"),
|
||||||
|
("ID/Relay Server", "Server ID/Relay"),
|
||||||
|
("Stop service", "Arresta servizio"),
|
||||||
|
("Change ID", "Cambia ID"),
|
||||||
|
("Website", "Sito web"),
|
||||||
|
("About", "Informazioni"),
|
||||||
|
("Mute", "Silenzia"),
|
||||||
|
("Audio Input", "Input audio"),
|
||||||
|
("ID Server", "ID server"),
|
||||||
|
("Relay Server", "Server relay"),
|
||||||
|
("API Server", "Server API"),
|
||||||
|
("invalid_http", "deve iniziare con http:// o https://"),
|
||||||
|
("Invalid IP", "Indirizzo IP non valido"),
|
||||||
|
("id_change_tip", "Puoi usare solo i caratteri a-z, A-Z, 0-9 e _ (underscore). Il primo carattere deve essere a-z o A-Z. La lunghezza deve essere fra 6 e 16 caratteri."),
|
||||||
|
("Invalid format", "Formato non valido"),
|
||||||
|
("This function is turned off by the server", "Questa funzione è disabilitata sul server"),
|
||||||
|
("Not available", "Non disponibile"),
|
||||||
|
("Too frequent", "Troppo frequente"),
|
||||||
|
("Cancel", "Annulla"),
|
||||||
|
("Skip", "Ignora"),
|
||||||
|
("Close", "Chiudi"),
|
||||||
|
("Retry", "Riprova"),
|
||||||
|
("OK", "OK"),
|
||||||
|
("Password Required", "Password richiesta"),
|
||||||
|
("Please enter your password", "Inserisci la tua password"),
|
||||||
|
("Remember password", "Ricorda password"),
|
||||||
|
("Wrong Password", "Password errata"),
|
||||||
|
("Do you want to enter again?", "Vuoi riprovare?"),
|
||||||
|
("Connection Error", "Errore di connessione"),
|
||||||
|
("Error", "Errore"),
|
||||||
|
("Reset by the peer", "Reimpostata dal peer"),
|
||||||
|
("Connecting...", "Connessione..."),
|
||||||
|
("Connection in progress. Please wait.", "Connessione in corso. Attendi."),
|
||||||
|
("Please try 1 minute later", "Per favore riprova fra 1 minuto"),
|
||||||
|
("Login Error", "Errore di login"),
|
||||||
|
("Successful", "Successo"),
|
||||||
|
("Connected, waiting for image...", "Connesso, in attesa dell'immagine..."),
|
||||||
|
("Name", "Nome"),
|
||||||
|
("Modified", "Modificato"),
|
||||||
|
("Size", "Dimensione"),
|
||||||
|
("Show Hidden Files", "Mostra file nascosti"),
|
||||||
|
("Receive", "Ricevi"),
|
||||||
|
("Send", "Invia"),
|
||||||
|
("Remote Computer", "Computer remoto"),
|
||||||
|
("Local Computer", "Computer locale"),
|
||||||
|
("Confirm Delete", "Conferma cancellazione"),
|
||||||
|
("Are you sure you want to delete this file?", "Vuoi davvero eliminare questo file?"),
|
||||||
|
("Do this for all conflicts", "Ricorca questa scelta per tutti i conflitti"),
|
||||||
|
("Deleting", "Cancellazione di"),
|
||||||
|
("files", "file"),
|
||||||
|
("Waiting", "In attesa"),
|
||||||
|
("Finished", "Terminato"),
|
||||||
|
("Custom Image Quality", "Qualità immagine personalizzata"),
|
||||||
|
("Privacy mode", "Modalità privacy"),
|
||||||
|
("Adjust Window", "Adatta la finestra"),
|
||||||
|
("Original", "Originale"),
|
||||||
|
("Shrink", "Restringi"),
|
||||||
|
("Stretch", "Allarga"),
|
||||||
|
("Good image quality", "Buona qualità immagine"),
|
||||||
|
("Balanced", "Bilanciato"),
|
||||||
|
("Optimize reaction time", "Ottimizza il tempo di reazione"),
|
||||||
|
("Custom", "Personalizzato"),
|
||||||
|
("Show remote cursor", "Mostra il cursore remoto"),
|
||||||
|
("Disable clipboard", "Disabilita appunti"),
|
||||||
|
("Lock after session end", "Blocca al termine della sessione"),
|
||||||
|
("Insert", "Inserisci"),
|
||||||
|
("Insert Lock", "Blocco inserimento"),
|
||||||
|
("Refresh", "Aggiorna"),
|
||||||
|
("ID does not exist", "L'ID non esiste"),
|
||||||
|
("Failed to connect to rendezvous server", "Errore di connessione al server rendezvous"),
|
||||||
|
("Please try later", "Riprova più tardi"),
|
||||||
|
("Remote desktop is offline", "Il desktop remoto è offline"),
|
||||||
|
("Key mismatch", "La chiave non corrisponde"),
|
||||||
|
("Timeout", "Timeout"),
|
||||||
|
("Failed to connect to relay server", "Errore di connessione al server relay"),
|
||||||
|
("Failed to connect via rendezvous server", "Errore di connessione tramite il server rendezvous"),
|
||||||
|
("Failed to connect via relay server", "Errore di connessione tramite il server relay"),
|
||||||
|
("Failed to make direct connection to remote desktop", "Impossibile connettersi direttamente al desktop remoto"),
|
||||||
|
("Set Password", "Imposta password"),
|
||||||
|
("OS Password", "Password del sistema operativo"),
|
||||||
|
("install_tip", "A causa del Controllo Account Utente, RustDesk potrebbe non funzionare correttamente come desktop remoto. Per evitare questo problema, fai click sul tasto qui sotto per installare RustDesk a livello di sistema."),
|
||||||
|
("Click to upgrade", "Fai click per aggiornare"),
|
||||||
|
("Configuration Permissions", "Permessi di configurazione"),
|
||||||
|
("Configure", "Configura"),
|
||||||
|
("config_acc", "Per controllare il tuo desktop dall'esterno, devi fornire a RustDesk il permesso \"Accessibilità\"."),
|
||||||
|
("config_screen", "Per controllare il tuo desktop dall'esterno, devi fornire a RustDesk il permesso \"Registrazione schermo\"."),
|
||||||
|
("Installing ...", "Installazione ..."),
|
||||||
|
("Install", "Installa"),
|
||||||
|
("Installation", "Installazione"),
|
||||||
|
("Installation Path", "Percorso di installazione"),
|
||||||
|
("Create start menu shortcuts", "Crea i collegamenti nel menu di avvio"),
|
||||||
|
("Create desktop icon", "Crea un'icona sul desktop"),
|
||||||
|
("agreement_tip", "Avviando l'installazione, accetti i termini del contratto di licenza."),
|
||||||
|
("Accept and Install", "Accetta e installa"),
|
||||||
|
("End-user license agreement", "Contratto di licenza con l'utente finale"),
|
||||||
|
("Generating ...", "Generazione ..."),
|
||||||
|
("Your installation is lower version.", "La tua installazione non è aggiornata."),
|
||||||
|
("not_close_tcp_tip", "Non chiudere questa finestra mentre stai usando il tunnel"),
|
||||||
|
("Listening ...", "In ascolto ..."),
|
||||||
|
("Remote Host", "Host remoto"),
|
||||||
|
("Remote Port", "Porta remota"),
|
||||||
|
("Action", "Azione"),
|
||||||
|
("Add", "Aggiungi"),
|
||||||
|
("Local Port", "Porta locale"),
|
||||||
|
("setup_server_tip", "Per una connessione più veloce, configura un tuo server"),
|
||||||
|
("Too short, at least 6 characters.", "Troppo breve, almeno 6 caratteri"),
|
||||||
|
("The confirmation is not identical.", "La conferma non corrisponde"),
|
||||||
|
("Permissions", "Permessi"),
|
||||||
|
("Accept", "Accetta"),
|
||||||
|
("Dismiss", "Rifiuta"),
|
||||||
|
("Disconnect", "Disconnetti"),
|
||||||
|
("Allow using keyboard and mouse", "Consenti l'uso di tastiera e mouse"),
|
||||||
|
("Allow using clipboard", "Consenti l'uso degli appunti"),
|
||||||
|
("Allow hearing sound", "Consenti la riproduzione dell'audio"),
|
||||||
|
("Connected", "Connesso"),
|
||||||
|
("Direct and encrypted connection", "Connessione diretta e cifrata"),
|
||||||
|
("Relayed and encrypted connection", "Connessione tramite relay e cifrata"),
|
||||||
|
("Direct and unencrypted connection", "Connessione diretta e non cifrata"),
|
||||||
|
("Relayed and unencrypted connection", "Connessione tramite relay e non cifrata"),
|
||||||
|
("Enter Remote ID", "Inserisci l'ID remoto"),
|
||||||
|
("Enter your password", "Inserisci la tua password"),
|
||||||
|
("Logging in...", "Autenticazione..."),
|
||||||
|
("Enable RDP session sharing", "Abilita la condivisione della sessione RDP"),
|
||||||
|
("Auto Login", "Login automatico"),
|
||||||
|
("Enable Direct IP Access", "Abilita l'accesso diretto tramite IP"),
|
||||||
|
("Rename", "Rinomina"),
|
||||||
|
("Space", "Spazio"),
|
||||||
|
("Create Desktop Shortcut", "Crea collegamento sul desktop"),
|
||||||
|
("Change Path", "Cambia percorso"),
|
||||||
|
("Create Folder", "Crea cartella"),
|
||||||
|
("Please enter the folder name", "Inserisci il nome della cartella"),
|
||||||
|
("Fix it", "Risolvi"),
|
||||||
|
("Warning", "Avviso"),
|
||||||
|
("Login screen using Wayland is not supported", "La schermata di login non è supportata utilizzando Wayland"),
|
||||||
|
("Reboot required", "Riavvio necessario"),
|
||||||
|
("Unsupported display server ", "Display server non supportato"),
|
||||||
|
("x11 expected", "x11 necessario"),
|
||||||
|
("Port", "Porta"),
|
||||||
|
("Settings", "Impostazioni"),
|
||||||
|
("Username", " Nome utente"),
|
||||||
|
("Invalid port", "Porta non valida"),
|
||||||
|
("Closed manually by the peer", "Chiuso manualmente dal peer"),
|
||||||
|
("Enable remote configuration modification", "Abilita la modifica remota della configurazione"),
|
||||||
|
("Run without install", "Avvia senza installare"),
|
||||||
|
("Always connected via relay", "Connesso sempre tramite relay"),
|
||||||
|
("Always connect via relay", "Connetti sempre tramite relay"),
|
||||||
|
("whitelist_tip", "Solo gli indirizzi IP autorizzati possono connettersi a questo desktop"),
|
||||||
|
("Login", "Accedi"),
|
||||||
|
("Logout", "Esci"),
|
||||||
|
("Tags", "Tag"),
|
||||||
|
("Search ID", "Cerca ID"),
|
||||||
|
("Current Wayland display server is not supported", "Questo display server Wayland non è supportato"),
|
||||||
|
("whitelist_sep", "Separati da virgola, punto e virgola, spazio o a capo"),
|
||||||
|
("Add ID", "Aggiungi ID"),
|
||||||
|
("Add Tag", "Aggiungi tag"),
|
||||||
|
("Unselect all tags", "Deseleziona tutti i tag"),
|
||||||
|
("Network error", "Errore di rete"),
|
||||||
|
("Username missed", "Nome utente dimenticato"),
|
||||||
|
("Password missed", "Password dimenticata"),
|
||||||
|
("Wrong credentials", "Credenziali errate"),
|
||||||
|
("Edit Tag", "Modifica tag"),
|
||||||
|
("Invalid folder name", "Nome della cartella non valido"),
|
||||||
|
("Hostname", "Nome host"),
|
||||||
|
].iter().cloned().collect();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -27,3 +27,4 @@ use common::*;
|
|||||||
pub mod cli;
|
pub mod cli;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
mod port_forward;
|
mod port_forward;
|
||||||
|
mod lang;
|
||||||
|
|||||||
22
src/main.rs
22
src/main.rs
@@ -43,16 +43,18 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
use flexi_logger::*;
|
use flexi_logger::*;
|
||||||
Logger::with_env_or_str("debug")
|
Logger::try_with_env_or_str("debug")
|
||||||
.log_to_file()
|
.map(|x| {
|
||||||
.format(opt_format)
|
x.log_to_file(FileSpec::default().directory(path))
|
||||||
.rotate(
|
.format(opt_format)
|
||||||
Criterion::Age(Age::Day),
|
.rotate(
|
||||||
Naming::Timestamps,
|
Criterion::Age(Age::Day),
|
||||||
Cleanup::KeepLogFiles(6),
|
Naming::Timestamps,
|
||||||
)
|
Cleanup::KeepLogFiles(6),
|
||||||
.directory(path)
|
)
|
||||||
.start()
|
.start()
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
type Xdo = *const c_void;
|
type Xdo = *const c_void;
|
||||||
|
|
||||||
pub const PA_SAMPLE_RATE: u32 = 24000;
|
pub const PA_SAMPLE_RATE: u32 = 48000;
|
||||||
static mut UNMODIFIED: bool = true;
|
static mut UNMODIFIED: bool = true;
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
@@ -644,7 +644,7 @@ fn get_env_tries(name: &str, uid: &str, n: usize) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_env(name: &str, uid: &str) -> String {
|
fn get_env(name: &str, uid: &str) -> String {
|
||||||
let cmd = format!("ps -u {} -o pid= | xargs -I__ cat /proc/__/environ 2>/dev/null | tr '\\0' '\\n' | grep -m1 '^{}=' | sed 's/{}=//g'", uid, name, name);
|
let cmd = format!("ps -u {} -o pid= | xargs -I__ cat /proc/__/environ 2>/dev/null | tr '\\0' '\\n' | grep '^{}=' | tail -1 | sed 's/{}=//g'", uid, name, name);
|
||||||
log::debug!("Run: {}", &cmd);
|
log::debug!("Run: {}", &cmd);
|
||||||
if let Ok(Some(x)) = run_cmds(cmd) {
|
if let Ok(Some(x)) = run_cmds(cmd) {
|
||||||
x.trim_end().to_string()
|
x.trim_end().to_string()
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
use crate::server::{check_zombie, new as new_server, ServerPtr};
|
use crate::server::{check_zombie, new as new_server, ServerPtr};
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
|
anyhow::bail,
|
||||||
config::{Config, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
|
config::{Config, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
|
||||||
futures::future::join_all,
|
futures::future::join_all,
|
||||||
log,
|
log,
|
||||||
protobuf::Message as _,
|
protobuf::Message as _,
|
||||||
rendezvous_proto::*,
|
rendezvous_proto::*,
|
||||||
sleep,
|
sleep, socket_client,
|
||||||
tcp::FramedStream,
|
|
||||||
tokio::{
|
tokio::{
|
||||||
self, select,
|
self, select,
|
||||||
time::{interval, Duration},
|
time::{interval, Duration},
|
||||||
},
|
},
|
||||||
udp::FramedSocket,
|
udp::FramedSocket,
|
||||||
AddrMangle, ResultType,
|
AddrMangle, IntoTargetAddr, ResultType, TargetAddr,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
sync::{Arc, Mutex},
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc, Mutex,
|
||||||
|
},
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@@ -25,12 +28,14 @@ use uuid::Uuid;
|
|||||||
type Message = RendezvousMessage;
|
type Message = RendezvousMessage;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref SOLVING_PK_MISMATCH: Arc<Mutex<String>> = Default::default();
|
static ref SOLVING_PK_MISMATCH: Arc<Mutex<String>> = Default::default();
|
||||||
}
|
}
|
||||||
|
static SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
|
||||||
|
const REG_INTERVAL: i64 = 12_000;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RendezvousMediator {
|
pub struct RendezvousMediator {
|
||||||
addr: SocketAddr,
|
addr: TargetAddr<'static>,
|
||||||
host: String,
|
host: String,
|
||||||
host_prefix: String,
|
host_prefix: String,
|
||||||
rendezvous_servers: Vec<String>,
|
rendezvous_servers: Vec<String>,
|
||||||
@@ -38,6 +43,10 @@ pub struct RendezvousMediator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RendezvousMediator {
|
impl RendezvousMediator {
|
||||||
|
pub fn restart() {
|
||||||
|
SHOULD_EXIT.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start_all() {
|
pub async fn start_all() {
|
||||||
let mut nat_tested = false;
|
let mut nat_tested = false;
|
||||||
check_zombie();
|
check_zombie();
|
||||||
@@ -46,6 +55,10 @@ impl RendezvousMediator {
|
|||||||
crate::common::test_nat_type();
|
crate::common::test_nat_type();
|
||||||
nat_tested = true;
|
nat_tested = true;
|
||||||
}
|
}
|
||||||
|
let server_cloned = server.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
allow_err!(direct_server(server_cloned).await);
|
||||||
|
});
|
||||||
loop {
|
loop {
|
||||||
Config::reset_online();
|
Config::reset_online();
|
||||||
if Config::get_option("stop-service").is_empty() {
|
if Config::get_option("stop-service").is_empty() {
|
||||||
@@ -55,11 +68,14 @@ impl RendezvousMediator {
|
|||||||
}
|
}
|
||||||
let mut futs = Vec::new();
|
let mut futs = Vec::new();
|
||||||
let servers = Config::get_rendezvous_servers();
|
let servers = Config::get_rendezvous_servers();
|
||||||
|
SHOULD_EXIT.store(false, Ordering::SeqCst);
|
||||||
for host in servers.clone() {
|
for host in servers.clone() {
|
||||||
let server = server.clone();
|
let server = server.clone();
|
||||||
let servers = servers.clone();
|
let servers = servers.clone();
|
||||||
futs.push(tokio::spawn(async move {
|
futs.push(tokio::spawn(async move {
|
||||||
allow_err!(Self::start(server, host, servers).await);
|
allow_err!(Self::start(server, host, servers).await);
|
||||||
|
// SHOULD_EXIT here is to ensure once one exits, the others also exit.
|
||||||
|
SHOULD_EXIT.store(true, Ordering::SeqCst);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
join_all(futs).await;
|
join_all(futs).await;
|
||||||
@@ -86,18 +102,20 @@ impl RendezvousMediator {
|
|||||||
})
|
})
|
||||||
.unwrap_or(host.to_owned());
|
.unwrap_or(host.to_owned());
|
||||||
let mut rz = Self {
|
let mut rz = Self {
|
||||||
addr: Config::get_any_listen_addr(),
|
addr: Config::get_any_listen_addr().into_target_addr()?,
|
||||||
host: host.clone(),
|
host: host.clone(),
|
||||||
host_prefix,
|
host_prefix,
|
||||||
rendezvous_servers,
|
rendezvous_servers,
|
||||||
last_id_pk_registry: "".to_owned(),
|
last_id_pk_registry: "".to_owned(),
|
||||||
};
|
};
|
||||||
allow_err!(rz.dns_check());
|
|
||||||
let mut socket = FramedSocket::new(Config::get_any_listen_addr()).await?;
|
rz.addr = socket_client::get_target_addr(&crate::check_port(&host, RENDEZVOUS_PORT))?;
|
||||||
|
let any_addr = Config::get_any_listen_addr();
|
||||||
|
let mut socket = socket_client::new_udp(any_addr, RENDEZVOUS_TIMEOUT).await?;
|
||||||
|
|
||||||
const TIMER_OUT: Duration = Duration::from_secs(1);
|
const TIMER_OUT: Duration = Duration::from_secs(1);
|
||||||
let mut timer = interval(TIMER_OUT);
|
let mut timer = interval(TIMER_OUT);
|
||||||
let mut last_timer = SystemTime::UNIX_EPOCH;
|
let mut last_timer = SystemTime::UNIX_EPOCH;
|
||||||
const REG_INTERVAL: i64 = 12_000;
|
|
||||||
const REG_TIMEOUT: i64 = 3_000;
|
const REG_TIMEOUT: i64 = 3_000;
|
||||||
const MAX_FAILS1: i64 = 3;
|
const MAX_FAILS1: i64 = 3;
|
||||||
const MAX_FAILS2: i64 = 6;
|
const MAX_FAILS2: i64 = 6;
|
||||||
@@ -136,60 +154,68 @@ impl RendezvousMediator {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
select! {
|
select! {
|
||||||
Some(Ok((bytes, _))) = socket.next() => {
|
n = socket.next() => {
|
||||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
match n {
|
||||||
match msg_in.union {
|
Some(Ok((bytes, _))) => {
|
||||||
Some(rendezvous_message::Union::register_peer_response(rpr)) => {
|
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||||
update_latency();
|
match msg_in.union {
|
||||||
if rpr.request_pk {
|
Some(rendezvous_message::Union::register_peer_response(rpr)) => {
|
||||||
log::info!("request_pk received from {}", host);
|
update_latency();
|
||||||
allow_err!(rz.register_pk(&mut socket).await);
|
if rpr.request_pk {
|
||||||
continue;
|
log::info!("request_pk received from {}", host);
|
||||||
}
|
allow_err!(rz.register_pk(&mut socket).await);
|
||||||
}
|
continue;
|
||||||
Some(rendezvous_message::Union::register_pk_response(rpr)) => {
|
}
|
||||||
update_latency();
|
|
||||||
match rpr.result.enum_value_or_default() {
|
|
||||||
register_pk_response::Result::OK => {
|
|
||||||
Config::set_key_confirmed(true);
|
|
||||||
Config::set_host_key_confirmed(&rz.host_prefix, true);
|
|
||||||
*SOLVING_PK_MISMATCH.lock().unwrap() = "".to_owned();
|
|
||||||
}
|
}
|
||||||
register_pk_response::Result::UUID_MISMATCH => {
|
Some(rendezvous_message::Union::register_pk_response(rpr)) => {
|
||||||
allow_err!(rz.handle_uuid_mismatch(&mut socket).await);
|
update_latency();
|
||||||
|
match rpr.result.enum_value_or_default() {
|
||||||
|
register_pk_response::Result::OK => {
|
||||||
|
Config::set_key_confirmed(true);
|
||||||
|
Config::set_host_key_confirmed(&rz.host_prefix, true);
|
||||||
|
*SOLVING_PK_MISMATCH.lock().unwrap() = "".to_owned();
|
||||||
|
}
|
||||||
|
register_pk_response::Result::UUID_MISMATCH => {
|
||||||
|
allow_err!(rz.handle_uuid_mismatch(&mut socket).await);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(rendezvous_message::Union::punch_hole(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)) => {
|
||||||
|
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)) => {
|
||||||
|
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)) => {
|
||||||
|
Config::set_option("rendezvous-servers".to_owned(), cu.rendezvous_servers.join(","));
|
||||||
|
Config::set_serial(cu.serial);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log::debug!("Non-protobuf message bytes received: {:?}", bytes);
|
||||||
}
|
}
|
||||||
Some(rendezvous_message::Union::punch_hole(ph)) => {
|
},
|
||||||
let rz = rz.clone();
|
Some(Err(e)) => bail!("Failed to receive next {}", e), // maybe socks5 tcp disconnected
|
||||||
let server = server.clone();
|
None => {
|
||||||
tokio::spawn(async move {
|
// unreachable!()
|
||||||
allow_err!(rz.handle_punch_hole(ph, server).await);
|
},
|
||||||
});
|
|
||||||
}
|
|
||||||
Some(rendezvous_message::Union::request_relay(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)) => {
|
|
||||||
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)) => {
|
|
||||||
Config::set_option("rendezvous-servers".to_owned(), cu.rendezvous_servers.join(","));
|
|
||||||
Config::set_serial(cu.serial);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::debug!("Non-protobuf message bytes received: {:?}", bytes);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = timer.tick() => {
|
_ = timer.tick() => {
|
||||||
@@ -199,15 +225,8 @@ impl RendezvousMediator {
|
|||||||
if !Config::get_option("stop-service").is_empty() {
|
if !Config::get_option("stop-service").is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if rz.addr.port() == 0 {
|
if SHOULD_EXIT.load(Ordering::SeqCst) {
|
||||||
allow_err!(rz.dns_check());
|
break;
|
||||||
if rz.addr.port() == 0 {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// have to do this for osx, to avoid "Can't assign requested address"
|
|
||||||
// when socket created before OS network ready
|
|
||||||
socket = FramedSocket::new(Config::get_any_listen_addr()).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
if now.duration_since(last_timer).map(|d| d < TIMER_OUT).unwrap_or(false) {
|
if now.duration_since(last_timer).map(|d| d < TIMER_OUT).unwrap_or(false) {
|
||||||
@@ -226,10 +245,11 @@ impl RendezvousMediator {
|
|||||||
Config::update_latency(&host, -1);
|
Config::update_latency(&host, -1);
|
||||||
old_latency = 0;
|
old_latency = 0;
|
||||||
if now.duration_since(last_dns_check).map(|d| d.as_millis() as i64).unwrap_or(0) > DNS_INTERVAL {
|
if now.duration_since(last_dns_check).map(|d| d.as_millis() as i64).unwrap_or(0) > DNS_INTERVAL {
|
||||||
if let Ok(_) = rz.dns_check() {
|
rz.addr = socket_client::get_target_addr(&crate::check_port(&host, RENDEZVOUS_PORT))?;
|
||||||
// in some case of network reconnect (dial IP network),
|
// in some case of network reconnect (dial IP network),
|
||||||
// old UDP socket not work any more after network recover
|
// old UDP socket not work any more after network recover
|
||||||
socket = FramedSocket::new(Config::get_any_listen_addr()).await?;
|
if let Some(s) = socket_client::rebind_udp(any_addr).await? {
|
||||||
|
socket = s;
|
||||||
}
|
}
|
||||||
last_dns_check = now;
|
last_dns_check = now;
|
||||||
}
|
}
|
||||||
@@ -245,12 +265,6 @@ impl RendezvousMediator {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dns_check(&mut self) -> ResultType<()> {
|
|
||||||
self.addr = hbb_common::to_socket_addr(&crate::check_port(&self.host, RENDEZVOUS_PORT))?;
|
|
||||||
log::debug!("Lookup dns of {}", self.host);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_request_relay(&self, rr: RequestRelay, server: ServerPtr) -> ResultType<()> {
|
async fn handle_request_relay(&self, rr: RequestRelay, server: ServerPtr) -> ResultType<()> {
|
||||||
self.create_relay(
|
self.create_relay(
|
||||||
rr.socket_addr,
|
rr.socket_addr,
|
||||||
@@ -280,8 +294,14 @@ impl RendezvousMediator {
|
|||||||
uuid,
|
uuid,
|
||||||
secure,
|
secure,
|
||||||
);
|
);
|
||||||
let mut socket =
|
|
||||||
FramedStream::new(self.addr, Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT).await?;
|
let mut socket = socket_client::connect_tcp(
|
||||||
|
self.addr.to_owned(),
|
||||||
|
Config::get_any_listen_addr(),
|
||||||
|
RENDEZVOUS_TIMEOUT,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
let mut rr = RelayResponse {
|
let mut rr = RelayResponse {
|
||||||
socket_addr,
|
socket_addr,
|
||||||
@@ -303,15 +323,15 @@ impl RendezvousMediator {
|
|||||||
async fn handle_intranet(&self, fla: FetchLocalAddr, server: ServerPtr) -> ResultType<()> {
|
async fn handle_intranet(&self, fla: FetchLocalAddr, server: ServerPtr) -> ResultType<()> {
|
||||||
let peer_addr = AddrMangle::decode(&fla.socket_addr);
|
let peer_addr = AddrMangle::decode(&fla.socket_addr);
|
||||||
log::debug!("Handle intranet from {:?}", peer_addr);
|
log::debug!("Handle intranet from {:?}", peer_addr);
|
||||||
let (mut socket, port) = {
|
let mut socket = socket_client::connect_tcp(
|
||||||
let socket =
|
self.addr.to_owned(),
|
||||||
FramedStream::new(self.addr, Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT)
|
Config::get_any_listen_addr(),
|
||||||
.await?;
|
RENDEZVOUS_TIMEOUT,
|
||||||
let port = socket.get_ref().local_addr()?.port();
|
)
|
||||||
(socket, port)
|
.await?;
|
||||||
};
|
let local_addr = socket.local_addr();
|
||||||
let local_addr = socket.get_ref().local_addr()?;
|
let local_addr: SocketAddr =
|
||||||
let local_addr: SocketAddr = format!("{}:{}", local_addr.ip(), port).parse()?;
|
format!("{}:{}", local_addr.ip(), local_addr.port()).parse()?;
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
let mut relay_server = Config::get_option("relay-server");
|
let mut relay_server = Config::get_option("relay-server");
|
||||||
if relay_server.is_empty() {
|
if relay_server.is_empty() {
|
||||||
@@ -347,10 +367,14 @@ impl RendezvousMediator {
|
|||||||
let peer_addr = AddrMangle::decode(&ph.socket_addr);
|
let peer_addr = AddrMangle::decode(&ph.socket_addr);
|
||||||
log::debug!("Punch hole to {:?}", peer_addr);
|
log::debug!("Punch hole to {:?}", peer_addr);
|
||||||
let mut socket = {
|
let mut socket = {
|
||||||
let socket =
|
let socket = socket_client::connect_tcp(
|
||||||
FramedStream::new(self.addr, Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT)
|
self.addr.to_owned(),
|
||||||
.await?;
|
Config::get_any_listen_addr(),
|
||||||
allow_err!(FramedStream::new(peer_addr, socket.get_ref().local_addr()?, 300).await);
|
RENDEZVOUS_TIMEOUT,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let local_addr = socket.local_addr();
|
||||||
|
allow_err!(socket_client::connect_tcp(peer_addr, local_addr, 300).await);
|
||||||
socket
|
socket
|
||||||
};
|
};
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
@@ -387,7 +411,7 @@ impl RendezvousMediator {
|
|||||||
pk,
|
pk,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
socket.send(&msg_out, self.addr).await?;
|
socket.send(&msg_out, self.addr.to_owned()).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +457,47 @@ impl RendezvousMediator {
|
|||||||
serial,
|
serial,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
socket.send(&msg_out, self.addr).await?;
|
socket.send(&msg_out, self.addr.to_owned()).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn direct_server(server: ServerPtr) -> ResultType<()> {
|
||||||
|
let port = RENDEZVOUS_PORT + 2;
|
||||||
|
let addr = format!("0.0.0.0:{}", port);
|
||||||
|
let mut listener = None;
|
||||||
|
loop {
|
||||||
|
if !Config::get_option("direct-server").is_empty() && listener.is_none() {
|
||||||
|
listener = Some(hbb_common::tcp::new_listener(&addr, false).await?);
|
||||||
|
log::info!(
|
||||||
|
"Direct server listening on: {}",
|
||||||
|
&listener.as_ref().unwrap().local_addr()?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(l) = listener.as_mut() {
|
||||||
|
if let Ok(Ok((stream, addr))) = hbb_common::timeout(1000, l.accept()).await {
|
||||||
|
if Config::get_option("direct-server").is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log::info!("direct access from {}", addr);
|
||||||
|
let local_addr = stream.local_addr()?;
|
||||||
|
let server = server.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
allow_err!(
|
||||||
|
crate::server::create_tcp_connection(
|
||||||
|
server,
|
||||||
|
hbb_common::Stream::from(stream, local_addr),
|
||||||
|
addr,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sleep(0.1).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sleep(1.).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ use hbb_common::{
|
|||||||
rendezvous_proto::*,
|
rendezvous_proto::*,
|
||||||
sleep,
|
sleep,
|
||||||
sodiumoxide::crypto::{box_, secretbox, sign},
|
sodiumoxide::crypto::{box_, secretbox, sign},
|
||||||
tcp::FramedStream,
|
|
||||||
timeout, tokio, ResultType, Stream,
|
timeout, tokio, ResultType, Stream,
|
||||||
|
socket_client,
|
||||||
};
|
};
|
||||||
use service::{GenericService, Service, ServiceTmpl, Subscriber};
|
use service::{GenericService, Service, ServiceTmpl, Subscriber};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -63,7 +63,7 @@ pub fn new() -> ServerPtr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn accept_connection_(server: ServerPtr, socket: Stream, secure: bool) -> ResultType<()> {
|
async fn accept_connection_(server: ServerPtr, socket: Stream, secure: bool) -> ResultType<()> {
|
||||||
let local_addr = socket.get_ref().local_addr()?;
|
let local_addr = socket.local_addr();
|
||||||
drop(socket);
|
drop(socket);
|
||||||
// even we drop socket, below still may fail if not use reuse_addr,
|
// even we drop socket, below still may fail if not use reuse_addr,
|
||||||
// there is TIME_WAIT before socket really released, so sometimes we
|
// there is TIME_WAIT before socket really released, so sometimes we
|
||||||
@@ -71,12 +71,13 @@ async fn accept_connection_(server: ServerPtr, socket: Stream, secure: bool) ->
|
|||||||
let listener = new_listener(local_addr, true).await?;
|
let listener = new_listener(local_addr, true).await?;
|
||||||
log::info!("Server listening on: {}", &listener.local_addr()?);
|
log::info!("Server listening on: {}", &listener.local_addr()?);
|
||||||
if let Ok((stream, addr)) = timeout(CONNECT_TIMEOUT, listener.accept()).await? {
|
if let Ok((stream, addr)) = timeout(CONNECT_TIMEOUT, listener.accept()).await? {
|
||||||
create_tcp_connection_(server, Stream::from(stream), addr, secure).await?;
|
let stream_addr = stream.local_addr()?;
|
||||||
|
create_tcp_connection(server, Stream::from(stream, stream_addr), addr, secure).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_tcp_connection_(
|
pub async fn create_tcp_connection(
|
||||||
server: ServerPtr,
|
server: ServerPtr,
|
||||||
stream: Stream,
|
stream: Stream,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
@@ -94,11 +95,13 @@ async fn create_tcp_connection_(
|
|||||||
sk_[..].copy_from_slice(&sk);
|
sk_[..].copy_from_slice(&sk);
|
||||||
let sk = sign::SecretKey(sk_);
|
let sk = sign::SecretKey(sk_);
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
let signed_id = sign::sign(Config::get_id().as_bytes(), &sk);
|
|
||||||
let (our_pk_b, our_sk_b) = box_::gen_keypair();
|
let (our_pk_b, our_sk_b) = box_::gen_keypair();
|
||||||
|
let signed_id = sign::sign(
|
||||||
|
format!("{}\0{}", Config::get_id(), base64::encode(our_pk_b.0)).as_bytes(),
|
||||||
|
&sk,
|
||||||
|
);
|
||||||
msg_out.set_signed_id(SignedId {
|
msg_out.set_signed_id(SignedId {
|
||||||
id: signed_id,
|
id: signed_id,
|
||||||
pk: our_pk_b.0.into(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;
|
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;
|
||||||
@@ -124,8 +127,8 @@ async fn create_tcp_connection_(
|
|||||||
key[..].copy_from_slice(&symmetric_key);
|
key[..].copy_from_slice(&symmetric_key);
|
||||||
stream.set_key(secretbox::Key(key));
|
stream.set_key(secretbox::Key(key));
|
||||||
} else if pk.asymmetric_value.is_empty() {
|
} else if pk.asymmetric_value.is_empty() {
|
||||||
// force a trial to update_pk to rendezvous server
|
|
||||||
Config::set_key_confirmed(false);
|
Config::set_key_confirmed(false);
|
||||||
|
log::info!("Force to update pk");
|
||||||
} else {
|
} else {
|
||||||
bail!("Handshake failed: invalid public sign key length from peer");
|
bail!("Handshake failed: invalid public sign key length from peer");
|
||||||
}
|
}
|
||||||
@@ -183,8 +186,8 @@ async fn create_relay_connection_(
|
|||||||
peer_addr: SocketAddr,
|
peer_addr: SocketAddr,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
let mut stream = FramedStream::new(
|
let mut stream = socket_client::connect_tcp(
|
||||||
&crate::check_port(relay_server, RELAY_PORT),
|
crate::check_port(relay_server, RELAY_PORT),
|
||||||
Config::get_any_listen_addr(),
|
Config::get_any_listen_addr(),
|
||||||
CONNECT_TIMEOUT,
|
CONNECT_TIMEOUT,
|
||||||
)
|
)
|
||||||
@@ -195,7 +198,7 @@ async fn create_relay_connection_(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
stream.send(&msg_out).await?;
|
stream.send(&msg_out).await?;
|
||||||
create_tcp_connection_(server, stream, peer_addr, secure).await?;
|
create_tcp_connection(server, stream, peer_addr, secure).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,35 +36,34 @@ mod pa_impl {
|
|||||||
use super::*;
|
use super::*;
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
pub async fn run(sp: GenericService) -> ResultType<()> {
|
pub async fn run(sp: GenericService) -> ResultType<()> {
|
||||||
if let Ok(mut stream) = crate::ipc::connect(1000, "_pa").await {
|
hbb_common::sleep(0.1).await; // one moment to wait for _pa ipc
|
||||||
let mut encoder =
|
let mut stream = crate::ipc::connect(1000, "_pa").await?;
|
||||||
Encoder::new(crate::platform::linux::PA_SAMPLE_RATE, Stereo, LowDelay)?;
|
unsafe {
|
||||||
allow_err!(
|
AUDIO_ZERO_COUNT = 0;
|
||||||
stream
|
}
|
||||||
.send(&crate::ipc::Data::Config((
|
let mut encoder = Encoder::new(crate::platform::linux::PA_SAMPLE_RATE, Stereo, LowDelay)?;
|
||||||
"audio-input".to_owned(),
|
allow_err!(
|
||||||
Some(Config::get_option("audio-input"))
|
stream
|
||||||
)))
|
.send(&crate::ipc::Data::Config((
|
||||||
.await
|
"audio-input".to_owned(),
|
||||||
);
|
Some(Config::get_option("audio-input"))
|
||||||
while sp.ok() {
|
)))
|
||||||
sp.snapshot(|sps| {
|
.await
|
||||||
sps.send(create_format_msg(crate::platform::linux::PA_SAMPLE_RATE, 2));
|
);
|
||||||
Ok(())
|
while sp.ok() {
|
||||||
})?;
|
sp.snapshot(|sps| {
|
||||||
if let Some(data) = stream.next_timeout2(1000).await {
|
sps.send(create_format_msg(crate::platform::linux::PA_SAMPLE_RATE, 2));
|
||||||
match data? {
|
Ok(())
|
||||||
Some(crate::ipc::Data::RawMessage(bytes)) => {
|
})?;
|
||||||
let data = unsafe {
|
if let Some(data) = stream.next_timeout2(1000).await {
|
||||||
std::slice::from_raw_parts::<f32>(
|
match data? {
|
||||||
bytes.as_ptr() as _,
|
Some(crate::ipc::Data::RawMessage(bytes)) => {
|
||||||
bytes.len() / 4,
|
let data = unsafe {
|
||||||
)
|
std::slice::from_raw_parts::<f32>(bytes.as_ptr() as _, bytes.len() / 4)
|
||||||
};
|
};
|
||||||
send_f32(data, &mut encoder, &sp);
|
send_f32(data, &mut encoder, &sp);
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,9 +118,6 @@ mod cpal_impl {
|
|||||||
encoder: &mut Encoder,
|
encoder: &mut Encoder,
|
||||||
sp: &GenericService,
|
sp: &GenericService,
|
||||||
) {
|
) {
|
||||||
if data.iter().filter(|x| **x != 0.).next().is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let buffer;
|
let buffer;
|
||||||
let data = if sample_rate0 != sample_rate {
|
let data = if sample_rate0 != sample_rate {
|
||||||
buffer = crate::common::resample_channels(data, sample_rate0, sample_rate, channels);
|
buffer = crate::common::resample_channels(data, sample_rate0, sample_rate, channels);
|
||||||
@@ -195,10 +191,10 @@ mod cpal_impl {
|
|||||||
let (device, config) = get_device()?;
|
let (device, config) = get_device()?;
|
||||||
let sp = sp.clone();
|
let sp = sp.clone();
|
||||||
let err_fn = move |err| {
|
let err_fn = move |err| {
|
||||||
log::error!("an error occurred on stream: {}", err);
|
// too many UnknownErrno, will improve later
|
||||||
|
log::trace!("an error occurred on stream: {}", err);
|
||||||
};
|
};
|
||||||
// Sample rate must be one of 8000, 12000, 16000, 24000, or 48000.
|
// Sample rate must be one of 8000, 12000, 16000, 24000, or 48000.
|
||||||
// Note: somehow 48000 not work
|
|
||||||
let sample_rate_0 = config.sample_rate().0;
|
let sample_rate_0 = config.sample_rate().0;
|
||||||
let sample_rate = if sample_rate_0 < 12000 {
|
let sample_rate = if sample_rate_0 < 12000 {
|
||||||
8000
|
8000
|
||||||
@@ -206,9 +202,15 @@ mod cpal_impl {
|
|||||||
12000
|
12000
|
||||||
} else if sample_rate_0 < 24000 {
|
} else if sample_rate_0 < 24000 {
|
||||||
16000
|
16000
|
||||||
} else {
|
} else if sample_rate_0 < 48000 {
|
||||||
24000
|
24000
|
||||||
|
} else {
|
||||||
|
48000
|
||||||
};
|
};
|
||||||
|
log::debug!("Audio sample rate : {}", sample_rate);
|
||||||
|
unsafe {
|
||||||
|
AUDIO_ZERO_COUNT = 0;
|
||||||
|
}
|
||||||
let mut encoder = Encoder::new(
|
let mut encoder = Encoder::new(
|
||||||
sample_rate,
|
sample_rate,
|
||||||
if config.channels() > 1 { Stereo } else { Mono },
|
if config.channels() > 1 { Stereo } else { Mono },
|
||||||
@@ -282,19 +284,39 @@ fn create_format_msg(sample_rate: u32, channels: u16) -> Message {
|
|||||||
msg
|
msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use AUDIO_ZERO_COUNT for the Noise(Zero) Gate Attack Time
|
||||||
|
// every audio data length is set to 480
|
||||||
|
// MAX_AUDIO_ZERO_COUNT=800 is similar as Gate Attack Time 3~5s(Linux) || 6~8s(Windows)
|
||||||
|
const MAX_AUDIO_ZERO_COUNT: u16 = 800;
|
||||||
|
static mut AUDIO_ZERO_COUNT: u16 = 0;
|
||||||
|
|
||||||
fn send_f32(data: &[f32], encoder: &mut Encoder, sp: &GenericService) {
|
fn send_f32(data: &[f32], encoder: &mut Encoder, sp: &GenericService) {
|
||||||
if data.iter().filter(|x| **x != 0.).next().is_some() {
|
if data.iter().filter(|x| **x != 0.).next().is_some() {
|
||||||
match encoder.encode_vec_float(data, data.len() * 6) {
|
unsafe {
|
||||||
Ok(data) => {
|
AUDIO_ZERO_COUNT = 0;
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_audio_frame(AudioFrame {
|
|
||||||
data,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
sp.send(msg_out);
|
|
||||||
}
|
|
||||||
Err(_) => {}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
if AUDIO_ZERO_COUNT > MAX_AUDIO_ZERO_COUNT {
|
||||||
|
if AUDIO_ZERO_COUNT == MAX_AUDIO_ZERO_COUNT + 1 {
|
||||||
|
log::debug!("Audio Zero Gate Attack");
|
||||||
|
AUDIO_ZERO_COUNT += 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AUDIO_ZERO_COUNT += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match encoder.encode_vec_float(data, data.len() * 6) {
|
||||||
|
Ok(data) => {
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
msg_out.set_audio_frame(AudioFrame {
|
||||||
|
data,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
sp.send(msg_out);
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,8 +97,12 @@ mod listen {
|
|||||||
|
|
||||||
fn trigger(ctx: &mut ClipboardContext) {
|
fn trigger(ctx: &mut ClipboardContext) {
|
||||||
let _ = match ctx.get_text() {
|
let _ = match ctx.get_text() {
|
||||||
Ok(text) => ctx.set_text(text),
|
Ok(text) => {
|
||||||
Err(_) => ctx.set_text(Default::default()),
|
if !text.is_empty() {
|
||||||
|
ctx.set_text(text).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,15 +62,18 @@ impl Subscriber for ConnInner {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn send(&mut self, msg: Arc<Message>) {
|
fn send(&mut self, msg: Arc<Message>) {
|
||||||
self.tx.as_mut().map(|tx| {
|
match &msg.union {
|
||||||
allow_err!(tx.send((Instant::now(), msg)));
|
Some(message::Union::video_frame(_)) => {
|
||||||
});
|
self.tx_video.as_mut().map(|tx| {
|
||||||
}
|
allow_err!(tx.send((Instant::now(), msg)));
|
||||||
|
});
|
||||||
fn send_video_frame(&mut self, tm: std::time::Instant, msg: Arc<Message>) {
|
}
|
||||||
self.tx_video.as_mut().map(|tx| {
|
_ => {
|
||||||
allow_err!(tx.send((tm.into(), msg)));
|
self.tx.as_mut().map(|tx| {
|
||||||
});
|
allow_err!(tx.send((Instant::now(), msg)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +81,8 @@ const TEST_DELAY_TIMEOUT: Duration = Duration::from_secs(3);
|
|||||||
const SEC30: Duration = Duration::from_secs(30);
|
const SEC30: Duration = Duration::from_secs(30);
|
||||||
const H1: Duration = Duration::from_secs(3600);
|
const H1: Duration = Duration::from_secs(3600);
|
||||||
const MILLI1: Duration = Duration::from_millis(1);
|
const MILLI1: Duration = Duration::from_millis(1);
|
||||||
|
const SEND_TIMEOUT_VIDEO: u64 = 12_000;
|
||||||
|
const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10;
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
pub async fn start(
|
pub async fn start(
|
||||||
@@ -144,9 +149,17 @@ impl Connection {
|
|||||||
time::interval_at(Instant::now() + TEST_DELAY_TIMEOUT, TEST_DELAY_TIMEOUT);
|
time::interval_at(Instant::now() + TEST_DELAY_TIMEOUT, TEST_DELAY_TIMEOUT);
|
||||||
let mut last_recv_time = Instant::now();
|
let mut last_recv_time = Instant::now();
|
||||||
|
|
||||||
|
conn.stream.set_send_timeout(
|
||||||
|
if conn.file_transfer.is_some() || conn.port_forward_socket.is_some() {
|
||||||
|
SEND_TIMEOUT_OTHER
|
||||||
|
} else {
|
||||||
|
SEND_TIMEOUT_VIDEO
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
biased;
|
biased; // video has higher priority
|
||||||
|
|
||||||
Some(data) = rx_from_cm.recv() => {
|
Some(data) = rx_from_cm.recv() => {
|
||||||
match data {
|
match data {
|
||||||
@@ -283,73 +296,58 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
video_service::notify_video_frame_feched(id, None);
|
|
||||||
|
|
||||||
|
video_service::notify_video_frame_feched(id, None);
|
||||||
super::video_service::update_test_latency(id, 0);
|
super::video_service::update_test_latency(id, 0);
|
||||||
super::video_service::update_image_quality(id, None);
|
super::video_service::update_image_quality(id, None);
|
||||||
if let Some(forward) = conn.port_forward_socket.as_mut() {
|
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
|
||||||
|
conn.on_close(&err.to_string(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_port_forward_loop(
|
||||||
|
&mut self,
|
||||||
|
rx_from_cm: &mut mpsc::UnboundedReceiver<Data>,
|
||||||
|
) -> ResultType<()> {
|
||||||
|
let mut last_recv_time = Instant::now();
|
||||||
|
if let Some(forward) = self.port_forward_socket.as_mut() {
|
||||||
log::info!("Running port forwarding loop");
|
log::info!("Running port forwarding loop");
|
||||||
conn.stream.set_raw();
|
self.stream.set_raw();
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Some(data) = rx_from_cm.recv() => {
|
Some(data) = rx_from_cm.recv() => {
|
||||||
match data {
|
match data {
|
||||||
ipc::Data::Close => {
|
ipc::Data::Close => {
|
||||||
conn.on_close("Close requested from connection manager", false);
|
bail!("Close requested from selfection manager");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res = forward.next() => {
|
res = forward.next() => {
|
||||||
if let Some(res) = res {
|
if let Some(res) = res {
|
||||||
match res {
|
last_recv_time = Instant::now();
|
||||||
Err(err) => {
|
self.stream.send_bytes(res?.into()).await?;
|
||||||
conn.on_close(&err.to_string(), false);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
Ok(bytes) => {
|
|
||||||
last_recv_time = Instant::now();
|
|
||||||
if let Err(err) = conn.stream.send_bytes(bytes.into()).await {
|
|
||||||
conn.on_close(&err.to_string(), false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
conn.on_close("Forward reset by the peer", false);
|
bail!("Forward reset by the peer");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
res = conn.stream.next() => {
|
res = self.stream.next() => {
|
||||||
if let Some(res) = res {
|
if let Some(res) = res {
|
||||||
match res {
|
last_recv_time = Instant::now();
|
||||||
Err(err) => {
|
timeout(SEND_TIMEOUT_OTHER, forward.send(res?.into())).await??;
|
||||||
conn.on_close(&err.to_string(), false);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
Ok(bytes) => {
|
|
||||||
last_recv_time = Instant::now();
|
|
||||||
if let Err(err) = forward.send(bytes.into()).await {
|
|
||||||
conn.on_close(&err.to_string(), false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
conn.on_close("Stream reset by the peer", false);
|
bail!("Stream reset by the peer");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = conn.timer.tick() => {
|
_ = self.timer.tick() => {
|
||||||
if last_recv_time.elapsed() >= H1 {
|
if last_recv_time.elapsed() >= H1 {
|
||||||
conn.on_close("Timeout", false);
|
bail!("Timeout");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_permission(&mut self, permission: Permission, enabled: bool) {
|
async fn send_permission(&mut self, permission: Permission, enabled: bool) {
|
||||||
@@ -591,7 +589,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
if lr.username != Config::get_id() {
|
if !crate::is_ip(&lr.username) && lr.username != Config::get_id() {
|
||||||
self.send_login_error("Offline").await;
|
self.send_login_error("Offline").await;
|
||||||
} else if lr.password.is_empty() {
|
} else if lr.password.is_empty() {
|
||||||
self.try_start_cm(lr.my_id, lr.my_name, false).await;
|
self.try_start_cm(lr.my_id, lr.my_name, false).await;
|
||||||
@@ -906,12 +904,10 @@ async fn start_ipc(
|
|||||||
mut rx_to_cm: mpsc::UnboundedReceiver<ipc::Data>,
|
mut rx_to_cm: mpsc::UnboundedReceiver<ipc::Data>,
|
||||||
tx_from_cm: mpsc::UnboundedSender<ipc::Data>,
|
tx_from_cm: mpsc::UnboundedSender<ipc::Data>,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
loop {
|
if crate::platform::is_prelogin() {
|
||||||
if !crate::platform::is_prelogin() {
|
return Ok(());
|
||||||
break;
|
|
||||||
}
|
|
||||||
sleep(1.).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut stream = None;
|
let mut stream = None;
|
||||||
if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
|
if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
|
||||||
stream = Some(s);
|
stream = Some(s);
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ impl Subscriber for MouseCursorSub {
|
|||||||
self.inner.send(msg);
|
self.inner.send(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_video_frame(&mut self, _: std::time::Instant, _: Arc<Message>) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const NAME_CURSOR: &'static str = "mouse_cursor";
|
pub const NAME_CURSOR: &'static str = "mouse_cursor";
|
||||||
@@ -197,15 +195,20 @@ fn modifier_sleep() {
|
|||||||
std::thread::sleep(std::time::Duration::from_nanos(1));
|
std::thread::sleep(std::time::Duration::from_nanos(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_modifier_state(key: Key, en: &mut Enigo) -> bool {
|
fn get_modifier_state(key: Key, en: &mut Enigo) -> bool {
|
||||||
|
// on Linux, if RightAlt is down, RightAlt status is false, Alt status is true
|
||||||
|
// but on Windows, both are true
|
||||||
let x = en.get_key_state(key.clone());
|
let x = en.get_key_state(key.clone());
|
||||||
match key {
|
match key {
|
||||||
Key::Shift => x || en.get_key_state(Key::RightShift),
|
Key::Shift => x || en.get_key_state(Key::RightShift),
|
||||||
Key::Control => x || en.get_key_state(Key::RightControl),
|
Key::Control => x || en.get_key_state(Key::RightControl),
|
||||||
Key::Alt => x || en.get_key_state(Key::RightAlt),
|
Key::Alt => x || en.get_key_state(Key::RightAlt),
|
||||||
Key::Meta => x || en.get_key_state(Key::RWin),
|
Key::Meta => x || en.get_key_state(Key::RWin),
|
||||||
|
Key::RightShift => x || en.get_key_state(Key::Shift),
|
||||||
|
Key::RightControl => x || en.get_key_state(Key::Control),
|
||||||
|
Key::RightAlt => x || en.get_key_state(Key::Alt),
|
||||||
|
Key::RWin => x || en.get_key_state(Key::Meta),
|
||||||
_ => x,
|
_ => x,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,7 +267,7 @@ fn fix_key_down_timeout(force: bool) {
|
|||||||
if let Some(key) = key {
|
if let Some(key) = key {
|
||||||
let func = move || {
|
let func = move || {
|
||||||
let mut en = ENIGO.lock().unwrap();
|
let mut en = ENIGO.lock().unwrap();
|
||||||
if en.get_key_state(key) {
|
if get_modifier_state(key, &mut en) {
|
||||||
en.key_up(key);
|
en.key_up(key);
|
||||||
log::debug!("Fixed {:?} timeout", key);
|
log::debug!("Fixed {:?} timeout", key);
|
||||||
}
|
}
|
||||||
@@ -286,7 +289,7 @@ fn fix_modifier(
|
|||||||
key1: Key,
|
key1: Key,
|
||||||
en: &mut Enigo,
|
en: &mut Enigo,
|
||||||
) {
|
) {
|
||||||
if en.get_key_state(key1) && !modifiers.contains(&ProtobufEnumOrUnknown::new(key0)) {
|
if get_modifier_state(key1, en) && !modifiers.contains(&ProtobufEnumOrUnknown::new(key0)) {
|
||||||
en.key_up(key1);
|
en.key_up(key1);
|
||||||
log::debug!("Fixed {:?}", key1);
|
log::debug!("Fixed {:?}", key1);
|
||||||
}
|
}
|
||||||
@@ -577,11 +580,9 @@ fn handle_key_(evt: &KeyEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
if crate::common::valid_for_capslock(evt) {
|
if has_cap != en.get_key_state(Key::CapsLock) {
|
||||||
if has_cap != en.get_key_state(Key::CapsLock) {
|
en.key_down(Key::CapsLock).ok();
|
||||||
en.key_down(Key::CapsLock).ok();
|
en.key_up(Key::CapsLock);
|
||||||
en.key_up(Key::CapsLock);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if crate::common::valid_for_numlock(evt) {
|
if crate::common::valid_for_numlock(evt) {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ pub trait Service: Send + Sync {
|
|||||||
pub trait Subscriber: Default + Send + Sync + 'static {
|
pub trait Subscriber: Default + Send + Sync + 'static {
|
||||||
fn id(&self) -> i32;
|
fn id(&self) -> i32;
|
||||||
fn send(&mut self, msg: Arc<Message>);
|
fn send(&mut self, msg: Arc<Message>);
|
||||||
fn send_video_frame(&mut self, tm: time::Instant, msg: Arc<Message>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -145,15 +144,15 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_video_frame(&self, tm: time::Instant, msg: Message) -> HashSet<i32> {
|
pub fn send_video_frame(&self, msg: Message) -> HashSet<i32> {
|
||||||
self.send_video_frame_shared(tm, Arc::new(msg))
|
self.send_video_frame_shared(Arc::new(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_video_frame_shared(&self, tm: time::Instant, msg: Arc<Message>) -> HashSet<i32> {
|
pub fn send_video_frame_shared(&self, msg: Arc<Message>) -> HashSet<i32> {
|
||||||
let mut conn_ids = HashSet::new();
|
let mut conn_ids = HashSet::new();
|
||||||
let mut lock = self.0.write().unwrap();
|
let mut lock = self.0.write().unwrap();
|
||||||
for s in lock.subscribes.values_mut() {
|
for s in lock.subscribes.values_mut() {
|
||||||
s.send_video_frame(tm, msg.clone());
|
s.send(msg.clone());
|
||||||
conn_ids.insert(s.id());
|
conn_ids.insert(s.id());
|
||||||
}
|
}
|
||||||
conn_ids
|
conn_ids
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ impl VideoFrameController {
|
|||||||
fetched_conn_ids.insert(id);
|
fetched_conn_ids.insert(id);
|
||||||
|
|
||||||
// break if all connections have received current frame
|
// break if all connections have received current frame
|
||||||
if fetched_conn_ids.is_superset(&send_conn_ids) {
|
if fetched_conn_ids.len() >= send_conn_ids.len() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,11 +188,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
speed,
|
speed,
|
||||||
};
|
};
|
||||||
let mut vpx;
|
let mut vpx;
|
||||||
let mut n = ((width * height) as f64 / (1920 * 1080) as f64).round() as u32;
|
match Encoder::new(&cfg, 0) {
|
||||||
if n < 1 {
|
|
||||||
n = 1;
|
|
||||||
}
|
|
||||||
match Encoder::new(&cfg, n) {
|
|
||||||
Ok(x) => vpx = x,
|
Ok(x) => vpx = x,
|
||||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||||
}
|
}
|
||||||
@@ -220,7 +216,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
let start = time::Instant::now();
|
let start = time::Instant::now();
|
||||||
let mut last_check_displays = time::Instant::now();
|
let mut last_check_displays = time::Instant::now();
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let mut try_gdi = true;
|
let mut try_gdi = 1;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
log::info!("gdi: {}", c.is_gdi());
|
log::info!("gdi: {}", c.is_gdi());
|
||||||
while sp.ok() {
|
while sp.ok() {
|
||||||
@@ -257,11 +253,11 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let time = now - start;
|
let time = now - start;
|
||||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||||
let send_conn_ids = handle_one_frame(&sp, now, &frame, ms, &mut crc, &mut vpx)?;
|
let send_conn_ids = handle_one_frame(&sp, &frame, ms, &mut crc, &mut vpx)?;
|
||||||
frame_controller.set_send(now, send_conn_ids);
|
frame_controller.set_send(now, send_conn_ids);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
try_gdi = false;
|
try_gdi = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ref e) if e.kind() == WouldBlock => {
|
Err(ref e) if e.kind() == WouldBlock => {
|
||||||
@@ -271,10 +267,13 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
wait = 0
|
wait = 0
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if try_gdi && !c.is_gdi() {
|
if try_gdi > 0 && !c.is_gdi() {
|
||||||
c.set_gdi();
|
if try_gdi > 3 {
|
||||||
try_gdi = false;
|
c.set_gdi();
|
||||||
log::info!("No image, fall back to gdi");
|
try_gdi = 0;
|
||||||
|
log::info!("No image, fall back to gdi");
|
||||||
|
}
|
||||||
|
try_gdi += 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -327,7 +326,6 @@ fn create_frame(frame: &EncodeFrame) -> VP9 {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn handle_one_frame(
|
fn handle_one_frame(
|
||||||
sp: &GenericService,
|
sp: &GenericService,
|
||||||
now: Instant,
|
|
||||||
frame: &[u8],
|
frame: &[u8],
|
||||||
ms: i64,
|
ms: i64,
|
||||||
crc: &mut (u32, u32),
|
crc: &mut (u32, u32),
|
||||||
@@ -365,7 +363,7 @@ fn handle_one_frame(
|
|||||||
|
|
||||||
// to-do: flush periodically, e.g. 1 second
|
// to-do: flush periodically, e.g. 1 second
|
||||||
if frames.len() > 0 {
|
if frames.len() > 0 {
|
||||||
send_conn_ids = sp.send_video_frame(now, create_msg(frames));
|
send_conn_ids = sp.send_video_frame(create_msg(frames));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(send_conn_ids)
|
Ok(send_conn_ids)
|
||||||
|
|||||||
110
src/ui.rs
110
src/ui.rs
@@ -8,7 +8,7 @@ use crate::common::SOFTWARE_UPDATE_URL;
|
|||||||
use crate::ipc;
|
use crate::ipc;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
config::{Config, PeerConfig, APP_NAME, ICON},
|
config::{self, Config, Fav, PeerConfig, APP_NAME, ICON},
|
||||||
log, sleep,
|
log, sleep,
|
||||||
tokio::{self, time},
|
tokio::{self, time},
|
||||||
};
|
};
|
||||||
@@ -147,7 +147,6 @@ pub fn start(args: &mut [String]) {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn start_tray() -> hbb_common::ResultType<()> {
|
fn start_tray() -> hbb_common::ResultType<()> {
|
||||||
/*
|
|
||||||
let mut app = systray::Application::new()?;
|
let mut app = systray::Application::new()?;
|
||||||
let icon = include_bytes!("./tray-icon.ico");
|
let icon = include_bytes!("./tray-icon.ico");
|
||||||
app.set_icon_from_buffer(icon, 32, 32).unwrap();
|
app.set_icon_from_buffer(icon, 32, 32).unwrap();
|
||||||
@@ -186,7 +185,7 @@ fn start_tray() -> hbb_common::ResultType<()> {
|
|||||||
Ok::<_, systray::Error>(())
|
Ok::<_, systray::Error>(())
|
||||||
})?;
|
})?;
|
||||||
allow_err!(app.wait_for_message());
|
allow_err!(app.wait_for_message());
|
||||||
*/
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +279,20 @@ impl UI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_local_option(&self, key: String) -> String {
|
||||||
|
Config::get_option(&key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peer_has_password(&self, id: String) -> bool {
|
||||||
|
!PeerConfig::load(&id).password.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forget_password(&self, id: String) {
|
||||||
|
let mut c = PeerConfig::load(&id);
|
||||||
|
c.password.clear();
|
||||||
|
c.store(&id);
|
||||||
|
}
|
||||||
|
|
||||||
fn get_peer_option(&self, id: String, name: String) -> String {
|
fn get_peer_option(&self, id: String, name: String) -> String {
|
||||||
let c = PeerConfig::load(&id);
|
let c = PeerConfig::load(&id);
|
||||||
c.options.get(&name).unwrap_or(&"".to_owned()).to_owned()
|
c.options.get(&name).unwrap_or(&"".to_owned()).to_owned()
|
||||||
@@ -304,7 +317,7 @@ impl UI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_if_valid_server(&self, host: String) -> String {
|
fn test_if_valid_server(&self, host: String) -> String {
|
||||||
crate::common::test_if_valid_server(host)
|
hbb_common::socket_client::test_if_valid_server(&host)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sound_inputs(&self) -> Value {
|
fn get_sound_inputs(&self) -> Value {
|
||||||
@@ -361,6 +374,29 @@ impl UI {
|
|||||||
return "".to_owned();
|
return "".to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_socks(&self) -> Value {
|
||||||
|
let s = ipc::get_socks();
|
||||||
|
match s {
|
||||||
|
None => Value::null(),
|
||||||
|
Some(s) => {
|
||||||
|
let mut v = Value::array(0);
|
||||||
|
v.push(s.proxy);
|
||||||
|
v.push(s.username);
|
||||||
|
v.push(s.password);
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_socks(&self, proxy: String, username: String, password: String) {
|
||||||
|
ipc::set_socks(config::Socks5Server {
|
||||||
|
proxy,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
fn is_installed(&mut self) -> bool {
|
fn is_installed(&mut self) -> bool {
|
||||||
crate::platform::is_installed()
|
crate::platform::is_installed()
|
||||||
}
|
}
|
||||||
@@ -371,8 +407,8 @@ impl UI {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let installed_version = crate::platform::windows::get_installed_version();
|
let installed_version = crate::platform::windows::get_installed_version();
|
||||||
let a = crate::common::get_version_number(crate::VERSION);
|
let a = hbb_common::get_version_number(crate::VERSION);
|
||||||
let b = crate::common::get_version_number(&installed_version);
|
let b = hbb_common::get_version_number(&installed_version);
|
||||||
return a > b;
|
return a > b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,22 +436,43 @@ impl UI {
|
|||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_peer_value(id: String, p: PeerConfig) -> Value {
|
||||||
|
let values = vec![
|
||||||
|
id,
|
||||||
|
p.info.username.clone(),
|
||||||
|
p.info.hostname.clone(),
|
||||||
|
p.info.platform.clone(),
|
||||||
|
p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(),
|
||||||
|
];
|
||||||
|
Value::from_iter(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_peer(&self, id: String) -> Value {
|
||||||
|
let c = PeerConfig::load(&id);
|
||||||
|
Self::get_peer_value(id, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fav(&self) -> Value {
|
||||||
|
Value::from_iter(Fav::load().peers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_fav(&self, fav: Value) {
|
||||||
|
let mut tmp = vec![];
|
||||||
|
fav.values().for_each(|v| {
|
||||||
|
if let Some(v) = v.as_string() {
|
||||||
|
if !v.is_empty() {
|
||||||
|
tmp.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Fav::store(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
fn get_recent_sessions(&mut self) -> Value {
|
fn get_recent_sessions(&mut self) -> Value {
|
||||||
let peers: Vec<Value> = PeerConfig::peers()
|
let peers: Vec<Value> = PeerConfig::peers()
|
||||||
.iter()
|
.drain(..)
|
||||||
.map(|p| {
|
.map(|p| Self::get_peer_value(p.0, p.2))
|
||||||
let values = vec![
|
|
||||||
p.0.clone(),
|
|
||||||
p.2.info.username.clone(),
|
|
||||||
p.2.info.hostname.clone(),
|
|
||||||
p.2.info.platform.clone(),
|
|
||||||
p.2.options
|
|
||||||
.get("alias")
|
|
||||||
.unwrap_or(&"".to_owned())
|
|
||||||
.to_owned(),
|
|
||||||
];
|
|
||||||
Value::from_iter(values)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
Value::from_iter(peers)
|
Value::from_iter(peers)
|
||||||
}
|
}
|
||||||
@@ -564,6 +621,10 @@ impl UI {
|
|||||||
allow_err!(std::process::Command::new(p).arg(url).spawn());
|
allow_err!(std::process::Command::new(p).arg(url).spawn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn t(&self, name: String) -> String {
|
||||||
|
crate::client::translate(name)
|
||||||
|
}
|
||||||
|
|
||||||
fn is_xfce(&self) -> bool {
|
fn is_xfce(&self) -> bool {
|
||||||
crate::platform::is_xfce()
|
crate::platform::is_xfce()
|
||||||
}
|
}
|
||||||
@@ -575,6 +636,7 @@ impl UI {
|
|||||||
|
|
||||||
impl sciter::EventHandler for UI {
|
impl sciter::EventHandler for UI {
|
||||||
sciter::dispatch_script_call! {
|
sciter::dispatch_script_call! {
|
||||||
|
fn t(String);
|
||||||
fn is_xfce();
|
fn is_xfce();
|
||||||
fn get_id();
|
fn get_id();
|
||||||
fn get_password();
|
fn get_password();
|
||||||
@@ -587,11 +649,16 @@ impl sciter::EventHandler for UI {
|
|||||||
fn remove_peer(String);
|
fn remove_peer(String);
|
||||||
fn get_connect_status();
|
fn get_connect_status();
|
||||||
fn get_recent_sessions();
|
fn get_recent_sessions();
|
||||||
|
fn get_peer(String);
|
||||||
|
fn get_fav();
|
||||||
|
fn store_fav(Value);
|
||||||
fn recent_sessions_updated();
|
fn recent_sessions_updated();
|
||||||
fn get_icon();
|
fn get_icon();
|
||||||
fn get_msgbox();
|
fn get_msgbox();
|
||||||
fn install_me(String);
|
fn install_me(String);
|
||||||
fn is_installed();
|
fn is_installed();
|
||||||
|
fn set_socks(String, String, String);
|
||||||
|
fn get_socks();
|
||||||
fn is_installed_lower_version();
|
fn is_installed_lower_version();
|
||||||
fn install_path();
|
fn install_path();
|
||||||
fn goto_install();
|
fn goto_install();
|
||||||
@@ -604,7 +671,10 @@ impl sciter::EventHandler for UI {
|
|||||||
fn modify_default_login();
|
fn modify_default_login();
|
||||||
fn get_options();
|
fn get_options();
|
||||||
fn get_option(String);
|
fn get_option(String);
|
||||||
|
fn get_local_option(String);
|
||||||
fn get_peer_option(String, String);
|
fn get_peer_option(String, String);
|
||||||
|
fn peer_has_password(String);
|
||||||
|
fn forget_password(String);
|
||||||
fn set_peer_option(String, String, String);
|
fn set_peer_option(String, String, String);
|
||||||
fn test_if_valid_server(String);
|
fn test_if_valid_server(String);
|
||||||
fn get_sound_inputs();
|
fn get_sound_inputs();
|
||||||
|
|||||||
290
src/ui/ab.tis
Normal file
290
src/ui/ab.tis
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
var svg_tile = <svg #session-tile viewBox="0 0 158.6 158.6"><path style="stroke-width:.309756" d="M5.4 157.7c-1-.3-2-1-3.2-2.1-2.8-2.8-2.6-1-2.5-32 0-26.7 0-27 .7-28.3a9.3 9.3 0 0 1 4-4.2c1.2-.6 2.3-.6 29-.7 27.5 0 27.6 0 29.1.6.8.4 2 1.2 2.7 2 2.4 2.5 2.3.7 2.2 31.6-.1 26.5-.1 27.6-.7 28.8a9.3 9.3 0 0 1-4.2 4c-1.4.6-1.6.6-28.5.7a235 235 0 0 1-28.6-.4zm91 0a8.5 8.5 0 0 1-5.7-5.4c-.2-.7-.3-8.3-.3-28.3V96.7l.7-1.6a8.9 8.9 0 0 1 4.6-4.3c1.2-.4 3.8-.5 28.9-.4 26.6.1 27.6.1 28.8.7 1.6.8 3.2 2.5 4 4.2.7 1.4.7 1.6.7 28.3.1 31 .3 29.2-2.5 32-2.8 2.7-1 2.6-31.4 2.6-21.4 0-26.8-.1-27.9-.5zM5.3 67a8.7 8.7 0 0 1-4-3C-.5 61.6-.5 62.3-.5 33.6-.4 3.2-.5 5 2.2 2.2 5-.6 3.2-.4 34.2-.3c26.7 0 27 0 28.3.7 1.7.8 3.4 2.4 4.2 4 .6 1.2.6 2.2.7 28.8 0 25.1 0 27.7-.4 29a9 9 0 0 1-4.3 4.5l-1.6.7H33.7c-20.2 0-27.7-.1-28.4-.4Zm89.8-.3a9 9 0 0 1-4.3-4.6c-.5-1.2-.5-3.8-.5-28.9.1-26.6.2-27.6.7-28.8a9.3 9.3 0 0 1 4.2-4c1.4-.7 1.6-.7 28.3-.7 31-.1 29.2-.3 32 2.5 2.8 2.8 2.6 1 2.5 32 0 26.7 0 26.9-.7 28.3a9.3 9.3 0 0 1-4 4.2c-1.2.5-2.2.6-29 .6l-27.7.1z" transform="translate(.4 .4)"/></svg>;
|
||||||
|
var svg_list = <svg #session-list viewBox="0 0 246.8 185.8"><path style="stroke-width:.482473" d="M-69.2 102.7A16.5 16.5 0 0 1-67 70.4c7.3-1 15 4 17.3 11 1 3 1 8 0 10.8a16.7 16.7 0 0 1-19.5 10.5zm53-3.4a12.3 12.3 0 0 1-7-16.8c1.3-3 3.1-4.7 6-6 2.2-1 2.8-1 87.2-1 92.4 0 87-.2 90.6 2.6.9.7 2.2 2.4 3 3.7 1.2 2.2 1.4 3.1 1.4 6 0 4.8-2.3 8.6-6.8 11l-1.9 1-85.2.1c-71.9 0-85.5 0-87.3-.6zm-53.5-73c-4.7-1.5-8.6-5-10.6-9.1-1.8-4-1.8-9.8 0-13.7 1.6-3.3 4.4-6.2 7.8-8 2.2-1.2 3-1.3 7.1-1.3 4 0 5 .1 7.3 1.3a16.6 16.6 0 0 1 0 29.6c-2 1-3.4 1.4-6.5 1.5-2.2 0-4.5 0-5.1-.3zm52.3-4.8c-2.4-1.1-5.3-4-6.2-6.5-1-2.4-1-7.3.1-9.7.5-1.1 1.8-2.8 2.8-3.8 3.7-3.5-4-3.2 91-3.2h85.5l2.5 1.1a12 12 0 0 1 0 21.8l-2.5 1.2H70.2c-82.5 0-85.7 0-87.6-1zm-52.1-71.6a18 18 0 0 1-10-7.7 17 17 0 0 1-.7-15c2.3-5 5.8-7.9 11.4-9.3 9-2.3 18.3 4 19.8 13.4a16.4 16.4 0 0 1-15.2 19c-2.1.1-4.1 0-5.3-.4zm52.1-5.9c-1.3-.6-3-1.7-3.7-2.5-4.7-5-4.2-13.7 1-18 3.7-3.1-1.8-3 91.5-2.8l84.9.1 2 1a12 12 0 0 1 6.7 11c0 3-.2 3.9-1.4 6-.8 1.4-2.1 3-3 3.8-3.7 2.7 1.8 2.6-90.6 2.6h-85l-2.4-1.2z" transform="translate(81.7 82.6)"/></svg>;
|
||||||
|
var search_icon = <svg viewBox="0 0 655.278 655.024"><g transform="translate(-24.497 -195.01)"><path d="m649.96 847.92c-2.9592-1.3629-27.183-24.243-63.36-59.846-32.213-31.702-70.814-69.663-85.78-84.357l-27.21-26.717-4.7897 3.5287c-66.337 48.872-145.32 66.878-224.31 51.138-72.966-14.539-136.58-58.184-178.47-122.44-15.945-24.462-30.723-61.471-36.413-91.191-8.9404-46.696-6.2422-90.39 8.3388-135.04 13.39-41.003 34.756-75.42 66.479-107.09 74.506-74.377 183.71-99.89 284.22-66.397 62.352 20.777 117.67 65.579 150.79 122.12 38.716 66.101 46.59 147.55 21.43 221.66-9.9038 29.171-29.788 63.725-49.916 86.743l-7.0583 8.0717 3.0992 2.919c1.7046 1.6054 40.675 39.928 86.602 85.161 89.007 87.664 87.558 86.034 85.619 96.293-1.2888 6.8209-5.2313 12.041-11.321 14.989-6.7901 3.287-11.55 3.4093-17.952 0.46117zm-316.64-154.63c32.373-5.0481 61.075-15.115 86.553-30.358 47.942-28.683 83.505-72.09 100.89-123.14 35.043-102.91-6.4362-214.07-100.89-270.37-52.514-31.302-117.76-40.564-178.06-25.277-81.183 20.579-145.19 82.918-166.86 162.52-5.5757 20.478-7.445 35.423-7.445 59.52s1.8693 39.042 7.445 59.52c21.409 78.63 85.366 141.52 164.81 162.05 29.22 7.5511 66.493 9.756 93.564 5.5347z" stroke-width="1.28"/></g></svg>;
|
||||||
|
var clear_icon = <svg viewBox="0 0 478.94 479.03"><path d="M217.488 478.45c-30.264-3.146-55.348-10.265-82.714-23.477C62.54 420.1 14.214 353.763 1.824 272.463c-2.412-15.82-2.434-50.027-.043-66.058 16.004-107.32 97.008-188.28 204.71-204.6 14.33-2.172 49.054-2.447 63-.498C323.95 8.915 371.3 32.2 409.03 69.927c37.697 37.698 61.125 85.349 68.605 139.54 1.943 14.08 1.68 48.804-.478 63-6.616 43.533-24.01 83.859-50.468 117-37.556 47.046-92.812 78.608-153.26 87.54-12.553 1.855-44.144 2.671-55.936 1.445zm42.144-32.045c15.649-1.602 29.895-4.63 44.856-9.531 78.146-25.604 133.49-94.718 141.94-177.26 6.245-60.993-16.1-123.3-59.94-167.14-55.797-55.797-139.4-75.365-213.52-49.98-77.69 26.609-131.51 94.14-140.42 176.19-4.761 43.843 6.392 91.899 30.274 130.44 41.468 66.926 119.01 105.26 196.82 97.29zm-138.69-80.346c-4.096-1.784-8.225-6.874-9.022-11.123-1.676-8.935-3.495-6.761 52.877-63.221l52.17-52.25-52.17-52.25c-56.544-56.632-54.56-54.249-52.834-63.451.924-4.923 6.905-10.904 11.828-11.828 9.201-1.726 6.819-3.71 63.451 52.834l52.25 52.169 52.25-52.169c56.632-56.544 54.25-54.56 63.451-52.834 4.923.923 10.904 6.905 11.828 11.828 1.726 9.201 3.71 6.818-52.834 63.451l-52.169 52.25 52.17 52.25c56.543 56.632 54.56 54.249 52.833 63.451-.923 4.923-6.905 10.904-11.828 11.828-9.201 1.726-6.818 3.71-63.455-52.838l-52.255-52.173-51.745 51.696c-28.496 28.469-53.01 52.166-54.56 52.742-3.766 1.4-8.515 1.26-12.234-.36z"/></svg>;
|
||||||
|
|
||||||
|
function getSessionsStyleOption(type) {
|
||||||
|
return (type || "recent") + "-sessions-style";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessionsStyle(type) {
|
||||||
|
var v = handler.get_local_option(getSessionsStyleOption(type));
|
||||||
|
if (!v) v = type == "ab" ? "list" : "tile";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchPatterns = {};
|
||||||
|
|
||||||
|
class SearchBar: Reactor.Component {
|
||||||
|
this var type = "";
|
||||||
|
|
||||||
|
function this(params) {
|
||||||
|
this.type = (params || {}).type || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
var value = searchPatterns[this.type] || "";
|
||||||
|
var me = this;
|
||||||
|
self.timer(1ms, function() { me.search_id.value = value; });
|
||||||
|
return <div .search-id>
|
||||||
|
<span .search-icon>{search_icon}</span>
|
||||||
|
<input|text @{this.search_id} novalue={translate("Search ID")} />
|
||||||
|
{value && <span .clear-input>{clear_icon}</span>}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
event click $(span.clear-input) {
|
||||||
|
this.onChange('');
|
||||||
|
}
|
||||||
|
|
||||||
|
event change $(input) (_, el) {
|
||||||
|
this.onChange(el.value.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(v) {
|
||||||
|
searchPatterns[this.type] = v;
|
||||||
|
app.multipleSessions.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionStyle: Reactor.Component {
|
||||||
|
this var type = "";
|
||||||
|
|
||||||
|
function this(params) {
|
||||||
|
this.type = (params || {}).type || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
var sessionsStyle = getSessionsStyle(this.type);
|
||||||
|
return <div .sessions-tab style="margin-left: 0.5em;">
|
||||||
|
<span class={sessionsStyle == "tile" ? "active" : "inactive"}>{svg_tile}</span>
|
||||||
|
<span class={sessionsStyle != "tile" ? "active" : "inactive"}>{svg_list}</span>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
event click $(span.inactive) {
|
||||||
|
var option = getSessionsStyleOption(this.type);
|
||||||
|
var sessionsStyle = getSessionsStyle(this.type);
|
||||||
|
handler.set_option(option, sessionsStyle == "tile" ? "list" : "tile");
|
||||||
|
app.multipleSessions.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionList: Reactor.Component {
|
||||||
|
this var sessions = [];
|
||||||
|
this var type = "";
|
||||||
|
this var style;
|
||||||
|
|
||||||
|
function this(params) {
|
||||||
|
this.sessions = params.sessions;
|
||||||
|
this.type = params.type || "";
|
||||||
|
this.style = getSessionsStyle(this.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessions() {
|
||||||
|
var p = searchPatterns[this.type];
|
||||||
|
if (!p) return this.sessions;
|
||||||
|
var tmp = [];
|
||||||
|
this.sessions.map(function(s) {
|
||||||
|
var name = s[4] || s.alias || s[0] || s.id || "";
|
||||||
|
if (name.indexOf(p) >= 0) tmp.push(s);
|
||||||
|
});
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
var sessions = this.getSessions();
|
||||||
|
if (sessions.length == 0) {
|
||||||
|
return <div style="margin: *; font-size: 1.6em;">{translate("Empty")}</div>;
|
||||||
|
}
|
||||||
|
var me = this;
|
||||||
|
sessions = sessions.map(function(x) { return me.getSession(x); });
|
||||||
|
return <div .recent-sessions-content key={sessions.length}>
|
||||||
|
<popup>
|
||||||
|
<menu.context #remote-context>
|
||||||
|
<li #connect>{translate('Connect')}</li>
|
||||||
|
<li #transfer>{translate('Transfer File')}</li>
|
||||||
|
<li #tunnel>{translate('TCP Tunneling')}</li>
|
||||||
|
<li #rdp>RDP<EditRdpPort /></li>
|
||||||
|
<div .separator />
|
||||||
|
<li #rename>{translate('Rename')}</li>
|
||||||
|
{this.type != "fav" && <li #remove>{translate('Remove')}</li>}
|
||||||
|
{is_win && <li #shortcut>{translate('Create Desktop Shortcut')}</li>}
|
||||||
|
<li #forget-password>{translate('Unremember Password')}</li>
|
||||||
|
{(!this.type || this.type == "fav") && <li #add-fav>{translate('Add to Favorites')}</li>}
|
||||||
|
{(!this.type || this.type == "fav") && <li #remove-fav>{translate('Remove from Favorites')}</li>}
|
||||||
|
</menu>
|
||||||
|
</popup>
|
||||||
|
{sessions}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSession(s) {
|
||||||
|
var id = s[0] || s.id || "";
|
||||||
|
var username = s[1] || s.username || "";
|
||||||
|
var hostname = s[2] || s.hostname || "";
|
||||||
|
var platform = s[3] || s.platform || "";
|
||||||
|
var alias = s[4] || s.alias || "";
|
||||||
|
if (this.style == "list") {
|
||||||
|
return <div .remote-session-link .remote-session-list id={id} platform={platform} title={alias ? "ID: " + id : ""}>
|
||||||
|
<div .platform style={"background:"+string2RGB(id+platform, 0.5)}>
|
||||||
|
{platform && platformSvg(platform, "white")}
|
||||||
|
</div>
|
||||||
|
<div .name>
|
||||||
|
<div>
|
||||||
|
<div #alias .ellipsis>{alias ? alias : formatId(id)}</div>
|
||||||
|
<div .username .ellipsis>{username}@{hostname}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{svg_menu}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
return <div .remote-session-link .remote-session id={id} platform={platform} title={alias ? "ID: " + id : ""} style={"background:"+string2RGB(id+platform, 0.5)}>
|
||||||
|
<div .platform>
|
||||||
|
{platform && platformSvg(platform, "white")}
|
||||||
|
<div .username .ellipsis>{username}@{hostname}</div>
|
||||||
|
</div>
|
||||||
|
<div .text>
|
||||||
|
<div #alias .ellipsis>{alias ? alias : formatId(id)}</div>
|
||||||
|
{svg_menu}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
event dblclick $(div.remote-session-link) (evt, me) {
|
||||||
|
createNewConnect(me.id, "connect");
|
||||||
|
}
|
||||||
|
|
||||||
|
event click $(#menu) (_, me) {
|
||||||
|
var id = me.parent.parent.id;
|
||||||
|
var platform = me.parent.parent.attributes["platform"];
|
||||||
|
this.$(#rdp).style.set{
|
||||||
|
display: (platform == "Windows" && is_win) ? "block" : "none",
|
||||||
|
};
|
||||||
|
this.$(#forget-password).style.set{
|
||||||
|
display: handler.peer_has_password(id) ? "block" : "none",
|
||||||
|
};
|
||||||
|
if (!this.type || this.type == "fav") {
|
||||||
|
var in_fav = handler.get_fav().indexOf(id) >= 0;
|
||||||
|
this.$(#add-fav).style.set{
|
||||||
|
display: in_fav ? "none" : "block",
|
||||||
|
};
|
||||||
|
this.$(#remove-fav).style.set{
|
||||||
|
display: in_fav ? "block" : "none",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// https://sciter.com/forums/topic/replacecustomize-context-menu/
|
||||||
|
var menu = this.$(menu#remote-context);
|
||||||
|
menu.attributes["remote-id"] = id;
|
||||||
|
me.popup(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
event click $(menu#remote-context li) (evt, me) {
|
||||||
|
var action = me.id;
|
||||||
|
var id = me.parent.attributes["remote-id"];
|
||||||
|
if (action == "connect") {
|
||||||
|
createNewConnect(id, "connect");
|
||||||
|
} else if (action == "transfer") {
|
||||||
|
createNewConnect(id, "file-transfer");
|
||||||
|
} else if (action == "remove") {
|
||||||
|
if (!this.type) {
|
||||||
|
handler.remove_peer(id);
|
||||||
|
app.update();
|
||||||
|
}
|
||||||
|
} else if (action == "forget-password") {
|
||||||
|
handler.forget_password(id);
|
||||||
|
} else if (action == "shortcut") {
|
||||||
|
handler.create_shortcut(id);
|
||||||
|
} else if (action == "rdp") {
|
||||||
|
createNewConnect(id, "rdp");
|
||||||
|
} else if (action == "add-fav") {
|
||||||
|
var favs = handler.get_fav();
|
||||||
|
if (favs.indexOf(id) < 0) {
|
||||||
|
favs = [id].concat(favs);
|
||||||
|
handler.store_fav(favs);
|
||||||
|
}
|
||||||
|
app.multipleSessions.update();
|
||||||
|
app.update();
|
||||||
|
} else if (action == "remove-fav") {
|
||||||
|
var favs = handler.get_fav();
|
||||||
|
var i = favs.indexOf(id);
|
||||||
|
favs.splice(i, 1);
|
||||||
|
handler.store_fav(favs);
|
||||||
|
app.multipleSessions.update();
|
||||||
|
} else if (action == "tunnel") {
|
||||||
|
createNewConnect(id, "port-forward");
|
||||||
|
} else if (action == "rename") {
|
||||||
|
var old_name = handler.get_peer_option(id, "alias");
|
||||||
|
msgbox("custom-rename", "Rename", "<div .form> \
|
||||||
|
<div><input name='name' .outline-focus style='width: *; height: 23px', value='" + old_name + "' /></div> \
|
||||||
|
</div> \
|
||||||
|
", function(res=null) {
|
||||||
|
if (!res) return;
|
||||||
|
var name = (res.name || "").trim();
|
||||||
|
if (name != old_name) {
|
||||||
|
handler.set_peer_option(id, "alias", name);
|
||||||
|
}
|
||||||
|
app.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessionsType() {
|
||||||
|
return handler.get_local_option("show-sessions-type");
|
||||||
|
}
|
||||||
|
|
||||||
|
class Favorites: Reactor.Component {
|
||||||
|
function render() {
|
||||||
|
var sessions = handler.get_fav().map(function(f) {
|
||||||
|
return handler.get_peer(f);
|
||||||
|
});
|
||||||
|
return <SessionList sessions={sessions} type="fav" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MultipleSessions: Reactor.Component {
|
||||||
|
function render() {
|
||||||
|
var type = getSessionsType();
|
||||||
|
return <div style="size: *">
|
||||||
|
<div .sessions-bar>
|
||||||
|
<div style="width:*" .sessions-tab #sessions-type>
|
||||||
|
<span class={!type ? 'active' : 'inactive'}>{translate('Recent Sessions')}</span>
|
||||||
|
<span #fav class={type == "fav" ? 'active' : 'inactive'}>{translate('Favorites')}</span>
|
||||||
|
</div>
|
||||||
|
{!this.hidden && <SearchBar type={type} />}
|
||||||
|
{!this.hidden && <SessionStyle type={type} />}
|
||||||
|
</div>
|
||||||
|
{!this.hidden &&
|
||||||
|
((type == "fav" && <Favorites />) ||
|
||||||
|
<SessionList sessions={handler.get_recent_sessions()} />)}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stupidUpdate() {
|
||||||
|
/* hidden is workaround of stupid sciter bug */
|
||||||
|
this.hidden = true;
|
||||||
|
this.update();
|
||||||
|
var me = this;
|
||||||
|
self.timer(60ms, function() {
|
||||||
|
me.hidden = false;
|
||||||
|
me.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
event click $(div#sessions-type span.inactive) (_, el) {
|
||||||
|
handler.set_option('show-sessions-type', el.id || "");
|
||||||
|
this.stupidUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSize() {
|
||||||
|
var w = this.$(.sessions-bar).box(#width) - 220;
|
||||||
|
this.$(#sessions-type span).style.set{
|
||||||
|
"max-width": (w / 2) + "px",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); });
|
||||||
30
src/ui/cm.rs
30
src/ui/cm.rs
@@ -300,6 +300,10 @@ impl ConnectionManager {
|
|||||||
fn exit(&self) {
|
fn exit(&self) {
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn t(&self, name: String) -> String {
|
||||||
|
crate::client::translate(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sciter::EventHandler for ConnectionManager {
|
impl sciter::EventHandler for ConnectionManager {
|
||||||
@@ -308,6 +312,7 @@ impl sciter::EventHandler for ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sciter::dispatch_script_call! {
|
sciter::dispatch_script_call! {
|
||||||
|
fn t(String);
|
||||||
fn get_icon();
|
fn get_icon();
|
||||||
fn close(i32);
|
fn close(i32);
|
||||||
fn authorize(i32);
|
fn authorize(i32);
|
||||||
@@ -405,12 +410,14 @@ async fn start_pa() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let spec = pulse::sample::Spec {
|
let spec = pulse::sample::Spec {
|
||||||
format: pulse::sample::Format::F32be,
|
format: pulse::sample::Format::F32le,
|
||||||
channels: 2,
|
channels: 2,
|
||||||
rate: crate::platform::linux::PA_SAMPLE_RATE,
|
rate: crate::platform::linux::PA_SAMPLE_RATE,
|
||||||
};
|
};
|
||||||
log::info!("pa monitor: {:?}", device);
|
log::info!("pa monitor: {:?}", device);
|
||||||
if let Ok(s) = psimple::Simple::new(
|
// systemctl --user status pulseaudio.service
|
||||||
|
let mut buf: Vec<u8> = vec![0; 480 * 4];
|
||||||
|
match psimple::Simple::new(
|
||||||
None, // Use the default server
|
None, // Use the default server
|
||||||
APP_NAME, // Our application’s name
|
APP_NAME, // Our application’s name
|
||||||
pulse::stream::Direction::Record, // We want a record stream
|
pulse::stream::Direction::Record, // We want a record stream
|
||||||
@@ -420,22 +427,19 @@ async fn start_pa() {
|
|||||||
None, // Use default channel map
|
None, // Use default channel map
|
||||||
None, // Use default buffering attributes
|
None, // Use default buffering attributes
|
||||||
) {
|
) {
|
||||||
loop {
|
Ok(s) => loop {
|
||||||
if let Some(Err(_)) = stream.next_timeout2(1).await {
|
if let Some(Err(_)) = stream.next_timeout2(1).await {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let mut out: Vec<u8> = Vec::with_capacity(480 * 4);
|
if let Ok(_) = s.read(&mut buf) {
|
||||||
unsafe {
|
allow_err!(
|
||||||
out.set_len(out.capacity());
|
stream.send(&Data::RawMessage(buf.clone())).await
|
||||||
}
|
);
|
||||||
if let Ok(_) = s.read(&mut out) {
|
|
||||||
if out.iter().filter(|x| **x != 0).next().is_some() {
|
|
||||||
allow_err!(stream.send(&Data::RawMessage(out)).await);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Could not create simple pulse: {}", err);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log::error!("Could not create simple pulse");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -33,22 +33,22 @@ class Body: Reactor.Component
|
|||||||
<div>
|
<div>
|
||||||
<div .id style="font-weight: bold; font-size: 1.2em;">{c.name}</div>
|
<div .id style="font-weight: bold; font-size: 1.2em;">{c.name}</div>
|
||||||
<div .id>({c.peer_id})</div>
|
<div .id>({c.peer_id})</div>
|
||||||
<div style="margin-top: 1.2em">Connected <span #time>{getElaspsed(c.time)}</span></div>
|
<div style="margin-top: 1.2em">{translate('Connected')} {" "} <span #time>{getElaspsed(c.time)}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <div>Permissions</div>}
|
{c.is_file_transfer || c.port_forward ? "" : <div>{translate('Permissions')}</div>}
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <div .permissions>
|
{c.is_file_transfer || c.port_forward ? "" : <div .permissions>
|
||||||
<div class={!c.keyboard ? "disabled" : ""} title="Allow using keyboard and mouse"><icon .keyboard /></div>
|
<div class={!c.keyboard ? "disabled" : ""} title={translate('Allow using keyboard and mouse')}><icon .keyboard /></div>
|
||||||
<div class={!c.clipboard ? "disabled" : ""} title="Allow using clipboard"><icon .clipboard /></div>
|
<div class={!c.clipboard ? "disabled" : ""} title={translate('Allow using clipboard')}><icon .clipboard /></div>
|
||||||
<div class={!c.audio ? "disabled" : ""} title="Allow hearing sound"><icon .audio /></div>
|
<div class={!c.audio ? "disabled" : ""} title={translate('Allow hearing sound')}><icon .audio /></div>
|
||||||
</div>}
|
</div>}
|
||||||
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
|
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
|
||||||
<div style="size:*"/>
|
<div style="size:*"/>
|
||||||
<div .buttons>
|
<div .buttons>
|
||||||
{auth ? "" : <button .button tabindex="-1" #accept>Accept</button>}
|
{auth ? "" : <button .button tabindex="-1" #accept>{translate('Accept')}</button>}
|
||||||
{auth ? "" : <button .button tabindex="-1" .outline #dismiss>Dismiss</button>}
|
{auth ? "" : <button .button tabindex="-1" .outline #dismiss>{translate('Dismiss')}</button>}
|
||||||
{auth ? <button .button tabindex="-1" #disconnect>Disconnect</button> : ""}
|
{auth ? <button .button tabindex="-1" #disconnect>{translate('Disconnect')}</button> : ""}
|
||||||
</div>
|
</div>
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <div .chaticon>{svg_chat}</div>}
|
{c.is_file_transfer || c.port_forward ? "" : <div .chaticon>{svg_chat}</div>}
|
||||||
</div>
|
</div>
|
||||||
@@ -101,6 +101,9 @@ class Body: Reactor.Component
|
|||||||
connection.authorized = true;
|
connection.authorized = true;
|
||||||
body.update();
|
body.update();
|
||||||
handler.authorize(cid);
|
handler.authorize(cid);
|
||||||
|
self.timer(30ms, function() {
|
||||||
|
view.windowState = View.WINDOW_MINIMIZED;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ html {
|
|||||||
var(gray-bg): #eee;
|
var(gray-bg): #eee;
|
||||||
var(bg): white;
|
var(bg): white;
|
||||||
var(border): #ccc;
|
var(border): #ccc;
|
||||||
|
var(hover-border): #999;
|
||||||
var(text): #222;
|
var(text): #222;
|
||||||
var(placeholder): #aaa;
|
var(placeholder): #aaa;
|
||||||
var(lighter-text): #888;
|
var(lighter-text): #888;
|
||||||
@@ -52,6 +53,10 @@ button.button:active, button.active {
|
|||||||
border-color: color(accent);
|
border-color: color(accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.button:hover, button.outline:hover {
|
||||||
|
border-color: color(hover-border);
|
||||||
|
}
|
||||||
|
|
||||||
input[type=text], input[type=password], input[type=number] {
|
input[type=text], input[type=password], input[type=number] {
|
||||||
width: *;
|
width: *;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
@@ -156,9 +161,7 @@ header caption {
|
|||||||
top: 0;
|
top: 0;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: *;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: 0;
|
|
||||||
color: black;
|
color: black;
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
@@ -321,5 +324,5 @@ menu li.selected span {
|
|||||||
|
|
||||||
menu li.line-through {
|
menu li.line-through {
|
||||||
text-decoration-line: line-through;
|
text-decoration-line: line-through;
|
||||||
color: grey;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ var is_file_transfer;
|
|||||||
var is_xfce = false;
|
var is_xfce = false;
|
||||||
try { is_xfce = handler.is_xfce(); } catch(e) {}
|
try { is_xfce = handler.is_xfce(); } catch(e) {}
|
||||||
|
|
||||||
|
|
||||||
|
function translate(name) {
|
||||||
|
try {
|
||||||
|
return handler.t(name);
|
||||||
|
} catch(_) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hashCode(str) {
|
function hashCode(str) {
|
||||||
var hash = 160 << 16 + 114 << 8 + 91;
|
var hash = 160 << 16 + 114 << 8 + 91;
|
||||||
for (var i = 0; i < str.length; i += 1) {
|
for (var i = 0; i < str.length; i += 1) {
|
||||||
@@ -207,7 +216,19 @@ function getMsgboxParams() {
|
|||||||
return msgbox_params;
|
return msgbox_params;
|
||||||
}
|
}
|
||||||
|
|
||||||
function msgbox(type, title, text, callback, height, width, retry=0) {
|
// tmp workaround https://sciter.com/forums/topic/menu-not-be-hidden-when-open-dialog-on-linux/
|
||||||
|
function msgbox(type, title, text, callback=null, height=180, width=500, retry=0, contentStyle="") {
|
||||||
|
if (is_linux) { // fix menu not hidden issue
|
||||||
|
self.timer(1ms,
|
||||||
|
function() {
|
||||||
|
msgbox_(type, title, text, callback, height, width, retry, contentStyle);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
msgbox_(type, title, text, callback, height, width, retry, contentStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function msgbox_(type, title, text, callback, height, width, retry, contentStyle) {
|
||||||
var has_msgbox = msgbox_params != null;
|
var has_msgbox = msgbox_params != null;
|
||||||
if (!has_msgbox && !type) return;
|
if (!has_msgbox && !type) return;
|
||||||
var remember = false;
|
var remember = false;
|
||||||
@@ -217,7 +238,8 @@ function msgbox(type, title, text, callback, height, width, retry=0) {
|
|||||||
msgbox_params = {
|
msgbox_params = {
|
||||||
remember: remember, type: type, text: text, title: title,
|
remember: remember, type: type, text: text, title: title,
|
||||||
getParams: getMsgboxParams,
|
getParams: getMsgboxParams,
|
||||||
callback: callback, retry: retry,
|
callback: callback, translate: translate,
|
||||||
|
retry: retry, contentStyle: contentStyle,
|
||||||
};
|
};
|
||||||
if (has_msgbox) return;
|
if (has_msgbox) return;
|
||||||
var dialog = {
|
var dialog = {
|
||||||
@@ -239,8 +261,8 @@ function msgbox(type, title, text, callback, height, width, retry=0) {
|
|||||||
} else if (res == "!alive") {
|
} else if (res == "!alive") {
|
||||||
// do nothing
|
// do nothing
|
||||||
} else if (res.type == "input-password") {
|
} else if (res.type == "input-password") {
|
||||||
if (!is_port_forward) connecting();
|
|
||||||
handler.login(res.password, res.remember);
|
handler.login(res.password, res.remember);
|
||||||
|
if (!is_port_forward) msgbox("connecting", "Connecting...", "Logging in...");
|
||||||
} else if (res.reconnect) {
|
} else if (res.reconnect) {
|
||||||
if (!is_port_forward) connecting();
|
if (!is_port_forward) connecting();
|
||||||
handler.reconnect();
|
handler.reconnect();
|
||||||
@@ -251,19 +273,13 @@ function connecting() {
|
|||||||
handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait.");
|
handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait.");
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.msgbox = function(type, title, text, callback=null, height=180, width=500, retry=0) {
|
handler.msgbox = function(type, title, text, retry=0) {
|
||||||
// directly call view.Dialog from native may crash, add timer here, seem safe
|
self.timer(30ms, function() { msgbox(type, title, text, null, 180, 500, retry); });
|
||||||
// too short time, msgbox won't get focus, per my test, 150 is almost minimun
|
|
||||||
self.timer(150ms, function() { msgbox(type, title, text, callback, height, width, retry); });
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.block_msgbox = function(type, title, text, callback=null, height=180, width=500, retry=0) {
|
|
||||||
msgbox(type, title, text, callback, height, width, retry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var reconnectTimeout = 1;
|
var reconnectTimeout = 1;
|
||||||
handler.msgbox_retry = function(type, title, text, hasRetry, callback=null, height=180, width=500) {
|
handler.msgbox_retry = function(type, title, text, hasRetry) {
|
||||||
handler.msgbox(type, title, text, callback, height, width, hasRetry ? reconnectTimeout : 0);
|
handler.msgbox(type, title, text, hasRetry ? reconnectTimeout : 0);
|
||||||
if (hasRetry) {
|
if (hasRetry) {
|
||||||
reconnectTimeout *= 2;
|
reconnectTimeout *= 2;
|
||||||
} else {
|
} else {
|
||||||
@@ -311,3 +327,52 @@ function Progress()
|
|||||||
|
|
||||||
this.value = "";
|
this.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var svg_eye_cross = <svg viewBox="0 -21 511.96 511">
|
||||||
|
<path d="m506.68 261.88c7.043-16.984 7.043-36.461 0-53.461-41.621-100.4-140.03-165.27-250.71-165.27-46.484 0-90.797 11.453-129.64 32.191l-68.605-68.609c-8.3438-8.3398-21.824-8.3398-30.168 0-8.3398 8.3398-8.3398 21.824 0 30.164l271.49 271.49 86.484 86.488 68.676 68.672c4.1797 4.1797 9.6406 6.2695 15.102 6.2695 5.4609 0 10.922-2.0898 15.082-6.25 8.3438-8.3398 8.3438-21.824 0-30.164l-62.145-62.145c36.633-27.883 66.094-65.109 84.438-109.38zm-293.91-100.1c12.648-7.5742 27.391-11.969 43.199-11.969 47.062 0 85.332 38.273 85.332 85.336 0 15.805-4.3945 30.547-11.969 43.199z"/>
|
||||||
|
<path d="m255.97 320.48c-47.062 0-85.336-38.273-85.336-85.332 0-3.0938 0.59766-6.0195 0.91797-9.0039l-106.15-106.16c-25.344 24.707-46.059 54.465-60.117 88.43-7.043 16.98-7.043 36.457 0 53.461 41.598 100.39 140.01 165.27 250.69 165.27 34.496 0 67.797-6.3164 98.559-18.027l-89.559-89.559c-2.9844 0.32031-5.9062 0.91797-9 0.91797z"/>
|
||||||
|
</svg>;
|
||||||
|
|
||||||
|
class PasswordComponent: Reactor.Component {
|
||||||
|
this var visible = false;
|
||||||
|
this var value = '';
|
||||||
|
this var name = 'password';
|
||||||
|
|
||||||
|
function this(params) {
|
||||||
|
if (params && params.value) {
|
||||||
|
this.value = params.value;
|
||||||
|
}
|
||||||
|
if (params && params.name) {
|
||||||
|
this.name = params.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
return <div .password>
|
||||||
|
<input name={this.name} value={this.value} type={this.visible ? "text" : "password"} .outline-focus />
|
||||||
|
{this.visible ? svg_eye_cross : svg_eye}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
event click $(svg) {
|
||||||
|
var el = this.$(input);
|
||||||
|
var value = el.value;
|
||||||
|
var start = el.xcall(#selectionStart) || 0;
|
||||||
|
var end = el.xcall(#selectionEnd);
|
||||||
|
this.update({ visible: !this.visible });
|
||||||
|
var me = this;
|
||||||
|
self.timer(30ms, function() {
|
||||||
|
var el = me.$(input);
|
||||||
|
view.focus = el;
|
||||||
|
el.value = value;
|
||||||
|
el.xcall(#setSelection, start, end);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReasonableSize(r) {
|
||||||
|
var x = r[0];
|
||||||
|
var y = r[1];
|
||||||
|
return !(x < -3200 || x > 3200 || y < -3200 || y > 3200);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,11 +141,11 @@ class JobTable: Reactor.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getStatus(job) {
|
function getStatus(job) {
|
||||||
if (!job.entries) return "Waiting";
|
if (!job.entries) return translate("Waiting");
|
||||||
var i = job.file_num + 1;
|
var i = job.file_num + 1;
|
||||||
var n = job.num_entries || job.entries.length;
|
var n = job.num_entries || job.entries.length;
|
||||||
if (i > n) i = n;
|
if (i > n) i = n;
|
||||||
var res = i + ' / ' + n + " files";
|
var res = i + ' / ' + n + " " + translate("files");
|
||||||
if (job.total_size > 0) {
|
if (job.total_size > 0) {
|
||||||
var s = getSize(0, job.finished_size);
|
var s = getSize(0, job.finished_size);
|
||||||
if (s) s += " / ";
|
if (s) s += " / ";
|
||||||
@@ -155,7 +155,7 @@ class JobTable: Reactor.Component {
|
|||||||
var percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger();
|
var percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger();
|
||||||
if (job.finished) percent = '100';
|
if (job.finished) percent = '100';
|
||||||
if (percent) res += ", " + percent + "%";
|
if (percent) res += ", " + percent + "%";
|
||||||
if (job.finished) res = "Finished " + res;
|
if (job.finished) res = translate("Finished") + " " + res;
|
||||||
if (job.speed) res += ", " + getSize(0, job.speed) + "/s";
|
if (job.speed) res += ", " + getSize(0, job.speed) + "/s";
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@ class FolderView : Reactor.Component {
|
|||||||
return <div .title>
|
return <div .title>
|
||||||
{svg_computer}
|
{svg_computer}
|
||||||
<div .platform>{platformSvg(handler.get_platform(this.is_remote), "white")}</div>
|
<div .platform>{platformSvg(handler.get_platform(this.is_remote), "white")}</div>
|
||||||
<div><span>{this.is_remote ? "Remote Computer" : "Local Computer"}</span></div>
|
<div><span>{translate(this.is_remote ? "Remote Computer" : "Local Computer")}</span></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ class FolderView : Reactor.Component {
|
|||||||
function renderOpBar() {
|
function renderOpBar() {
|
||||||
if (this.is_remote) {
|
if (this.is_remote) {
|
||||||
return <div .toolbar .remote>
|
return <div .toolbar .remote>
|
||||||
<div .send .button>{svg_send}<span>Receive</span></div>
|
<div .send .button>{svg_send}<span>{translate('Receive')}</span></div>
|
||||||
<div .spacer></div>
|
<div .spacer></div>
|
||||||
<div .add-folder .button>{svg_add_folder}</div>
|
<div .add-folder .button>{svg_add_folder}</div>
|
||||||
<div .trash .button>{svg_trash}</div>
|
<div .trash .button>{svg_trash}</div>
|
||||||
@@ -283,7 +283,7 @@ class FolderView : Reactor.Component {
|
|||||||
<div .add-folder .button>{svg_add_folder}</div>
|
<div .add-folder .button>{svg_add_folder}</div>
|
||||||
<div .trash .button>{svg_trash}</div>
|
<div .trash .button>{svg_trash}</div>
|
||||||
<div .spacer></div>
|
<div .spacer></div>
|
||||||
<div .send .button><span>Send</span>{svg_send}</div>
|
<div .send .button><span>{translate('Send')}</span>{svg_send}</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,14 +308,14 @@ class FolderView : Reactor.Component {
|
|||||||
var id = (this.is_remote ? "remote" : "local") + "-folder-view";
|
var id = (this.is_remote ? "remote" : "local") + "-folder-view";
|
||||||
return <table @{this.table} .folder-view .has_current id={id}>
|
return <table @{this.table} .folder-view .has_current id={id}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th></th><th .sortable>Name</th><th .sortable>Modified</th><th .sortable>Size</th></tr>
|
<tr><th></th><th .sortable>{translate('Name')}</th><th .sortable>{translate('Modified')}</th><th .sortable>{translate('Size')}</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
</tbody>
|
</tbody>
|
||||||
<popup>
|
<popup>
|
||||||
<menu.context id={id}>
|
<menu.context id={id}>
|
||||||
<li #switch-hidden class={this.show_hidden ? "selected" : ""}><span>{svg_checkmark}</span>Show Hidden Files</li>
|
<li #switch-hidden class={this.show_hidden ? "selected" : ""}><span>{svg_checkmark}</span>{translate('Show Hidden Files')}</li>
|
||||||
</menu>
|
</menu>
|
||||||
</popup>
|
</popup>
|
||||||
</table>;
|
</table>;
|
||||||
@@ -431,8 +431,8 @@ class FolderView : Reactor.Component {
|
|||||||
|
|
||||||
event click $(.add-folder) () {
|
event click $(.add-folder) () {
|
||||||
var me = this;
|
var me = this;
|
||||||
handler.msgbox("custom", "Create Folder", "<div .form> \
|
msgbox("custom", translate("Create Folder"), "<div .form> \
|
||||||
<div>Please enter the folder name:</div> \
|
<div>" + translate("Please enter the folder name") + ":</div> \
|
||||||
<div><input|text(name) .outline-focus /></div> \
|
<div><input|text(name) .outline-focus /></div> \
|
||||||
</div>", function(res=null) {
|
</div>", function(res=null) {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
@@ -523,7 +523,7 @@ class FolderView : Reactor.Component {
|
|||||||
var file_transfer;
|
var file_transfer;
|
||||||
|
|
||||||
class FileTransfer: Reactor.Component {
|
class FileTransfer: Reactor.Component {
|
||||||
function this(params) {
|
function this() {
|
||||||
file_transfer = this;
|
file_transfer = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,12 +576,12 @@ handler.jobDone = function(id, file_num = -1) {
|
|||||||
handler.jobError = function(id, err, file_num = -1) {
|
handler.jobError = function(id, err, file_num = -1) {
|
||||||
var job = deleting_single_file_jobs[id];
|
var job = deleting_single_file_jobs[id];
|
||||||
if (job) {
|
if (job) {
|
||||||
handler.msgbox("custom-error", "Delete File", err);
|
msgbox("custom-error", "Delete File", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
job = create_dir_jobs[id];
|
job = create_dir_jobs[id];
|
||||||
if (job) {
|
if (job) {
|
||||||
handler.msgbox("custom-error", "Create Folder", err);
|
msgbox("custom-error", "Create Folder", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file_num < 0) {
|
if (file_num < 0) {
|
||||||
@@ -599,8 +599,8 @@ var deleting_single_file_jobs = {};
|
|||||||
var create_dir_jobs = {}
|
var create_dir_jobs = {}
|
||||||
|
|
||||||
function confirmDelete(path, is_remote) {
|
function confirmDelete(path, is_remote) {
|
||||||
handler.block_msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
||||||
<div>Are you sure you want to delete this file?</div> \
|
<div>" + translate('Are you sure you want to delete this file?') + "</div> \
|
||||||
<div.ellipsis style=\"font-weight: bold;\">" + path + "</div> \
|
<div.ellipsis style=\"font-weight: bold;\">" + path + "</div> \
|
||||||
</div>", function(res=null) {
|
</div>", function(res=null) {
|
||||||
if (res) {
|
if (res) {
|
||||||
@@ -619,11 +619,11 @@ handler.confirmDeleteFiles = function(id, i, name) {
|
|||||||
if (i >= n) return;
|
if (i >= n) return;
|
||||||
var file_path = job.path;
|
var file_path = job.path;
|
||||||
if (name) file_path += handler.get_path_sep(job.is_remote) + name;
|
if (name) file_path += handler.get_path_sep(job.is_remote) + name;
|
||||||
handler.block_msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
||||||
<div>Deleting #" + (i + 1) + " of " + n + " files.</div> \
|
<div>" + translate('Deleting') + " #" + (i + 1) + " / " + n + " " + translate('files') + ".</div> \
|
||||||
<div>Are you sure you want to delete this file?</div> \
|
<div>" + translate('Are you sure you want to delete this file?') + "</div> \
|
||||||
<div.ellipsis style=\"font-weight: bold;\" .text>" + name + "</div> \
|
<div.ellipsis style=\"font-weight: bold;\" .text>" + name + "</div> \
|
||||||
<div><button|checkbox(remember) {ts}>Do this for all conflicts</button></div> \
|
<div><button|checkbox(remember) {ts}>" + translate('Do this for all conflicts') + "</button></div> \
|
||||||
</div>", function(res=null) {
|
</div>", function(res=null) {
|
||||||
if (!res) {
|
if (!res) {
|
||||||
jt.updateJobStatus(id, i - 1, "cancel");
|
jt.updateJobStatus(id, i - 1, "cancel");
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
var last_key_time = 0;
|
||||||
|
var keymap = {};
|
||||||
|
for (var (k, v) in Event) {
|
||||||
|
k = k + ""
|
||||||
|
if (k[0] == "V" && k[1] == "K") {
|
||||||
|
keymap[v] = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Grid: Behavior {
|
class Grid: Behavior {
|
||||||
const TABLE_HEADER_CLICK = 0x81;
|
const TABLE_HEADER_CLICK = 0x81;
|
||||||
const TABLE_ROW_CLICK = 0x82;
|
const TABLE_ROW_CLICK = 0x82;
|
||||||
@@ -144,7 +153,7 @@ class Grid: Behavior {
|
|||||||
|
|
||||||
function onKey(evt)
|
function onKey(evt)
|
||||||
{
|
{
|
||||||
|
last_key_time = getTime();
|
||||||
if (evt.type != Event.KEY_DOWN)
|
if (evt.type != Event.KEY_DOWN)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,24 @@ header .remote-id {
|
|||||||
margin: * 0;
|
margin: * 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header span:hover {
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media platform != "OSX" {
|
||||||
|
header span:hover {
|
||||||
|
background: #d9d9d9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header #screen:hover {
|
||||||
|
background: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
header #secure:hover {
|
||||||
|
background: unset;
|
||||||
|
}
|
||||||
|
|
||||||
header span:active, header #screen:active {
|
header span:active, header #screen:active {
|
||||||
color: black;
|
color: black;
|
||||||
background: color(gray-bg);
|
background: color(gray-bg);
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ if (is_linux) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function get_id() {
|
||||||
|
return handler.get_option('alias') || handler.get_id()
|
||||||
|
}
|
||||||
|
|
||||||
function stateChanged() {
|
function stateChanged() {
|
||||||
stdout.println('state changed from ' + cur_window_state + ' -> ' + view.windowState);
|
stdout.println('state changed from ' + cur_window_state + ' -> ' + view.windowState);
|
||||||
cur_window_state = view.windowState;
|
cur_window_state = view.windowState;
|
||||||
@@ -58,7 +62,7 @@ var old_window_state = View.WINDOW_SHOWN;
|
|||||||
var input_blocked;
|
var input_blocked;
|
||||||
|
|
||||||
class Header: Reactor.Component {
|
class Header: Reactor.Component {
|
||||||
function this(params) {
|
function this() {
|
||||||
header = this;
|
header = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,18 +71,18 @@ class Header: Reactor.Component {
|
|||||||
var title_conn;
|
var title_conn;
|
||||||
if (this.secure_connection && this.direct_connection) {
|
if (this.secure_connection && this.direct_connection) {
|
||||||
icon_conn = svg_secure;
|
icon_conn = svg_secure;
|
||||||
title_conn = "Direct and secure connection";
|
title_conn = translate("Direct and encrypted connection");
|
||||||
} else if (this.secure_connection && !this.direct_connection) {
|
} else if (this.secure_connection && !this.direct_connection) {
|
||||||
icon_conn = svg_secure_relay;
|
icon_conn = svg_secure_relay;
|
||||||
title_conn = "Relayed and secure connection";
|
title_conn = translate("Relayed and encrypted connection");
|
||||||
} else if (!this.secure_connection && this.direct_connection) {
|
} else if (!this.secure_connection && this.direct_connection) {
|
||||||
icon_conn = svg_insecure;
|
icon_conn = svg_insecure;
|
||||||
title_conn = "Direct and insecure connection";
|
title_conn = translate("Direct and unencrypted connection");
|
||||||
} else {
|
} else {
|
||||||
icon_conn = svg_insecure_relay;
|
icon_conn = svg_insecure_relay;
|
||||||
title_conn = "Relayed and insecure connection";
|
title_conn = translate("Relayed and unencrypted connection");
|
||||||
}
|
}
|
||||||
var title = handler.get_id();
|
var title = get_id();
|
||||||
if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")";
|
if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")";
|
||||||
if ((pi.displays || []).length == 0) {
|
if ((pi.displays || []).length == 0) {
|
||||||
return <div .ellipsis style="size:*;text-align:center;margin:*;">{title}</div>;
|
return <div .ellipsis style="size:*;text-align:center;margin:*;">{title}</div>;
|
||||||
@@ -89,14 +93,14 @@ class Header: Reactor.Component {
|
|||||||
</div>;
|
</div>;
|
||||||
});
|
});
|
||||||
updateWindowToolbarPosition();
|
updateWindowToolbarPosition();
|
||||||
var style = "flow: horizontal;";
|
var style = "flow:horizontal;";
|
||||||
if (is_osx) style += "margin: *";
|
if (is_osx) style += "margin:*";
|
||||||
self.timer(1ms, toggleMenuState);
|
self.timer(1ms, toggleMenuState);
|
||||||
return <div style={style}>
|
return <div style={style}>
|
||||||
{is_osx || is_xfce ? "" : <span #fullscreen>{svg_fullscreen}</span>}
|
{is_osx || is_xfce ? "" : <span #fullscreen>{svg_fullscreen}</span>}
|
||||||
<div #screens>
|
<div #screens>
|
||||||
<span #secure title={title_conn}>{icon_conn}</span>
|
<span #secure title={title_conn}>{icon_conn}</span>
|
||||||
<div .remote-id>{handler.get_id()}</div>
|
<div .remote-id>{get_id()}</div>
|
||||||
<div style="flow:horizontal;border-spacing: 0.5em;">{screens}</div>
|
<div style="flow:horizontal;border-spacing: 0.5em;">{screens}</div>
|
||||||
{this.renderGlobalScreens()}
|
{this.renderGlobalScreens()}
|
||||||
</div>
|
</div>
|
||||||
@@ -111,22 +115,22 @@ class Header: Reactor.Component {
|
|||||||
function renderDisplayPop() {
|
function renderDisplayPop() {
|
||||||
return <popup>
|
return <popup>
|
||||||
<menu.context #display-options>
|
<menu.context #display-options>
|
||||||
<li #adjust-window style="display:none">Adjust Window</li>
|
<li #adjust-window style="display:none">{translate('Adjust Window')}</li>
|
||||||
<div #adjust-window .separator style="display:none"/>
|
<div #adjust-window .separator style="display:none"/>
|
||||||
<li #original type="view-style"><span>{svg_checkmark}</span>Original</li>
|
<li #original type="view-style"><span>{svg_checkmark}</span>{translate('Original')}</li>
|
||||||
<li #shrink type="view-style"><span>{svg_checkmark}</span>Shrink</li>
|
<li #shrink type="view-style"><span>{svg_checkmark}</span>{translate('Shrink')}</li>
|
||||||
<li #stretch type="view-style"><span>{svg_checkmark}</span>Stretch</li>
|
<li #stretch type="view-style"><span>{svg_checkmark}</span>{translate('Stretch')}</li>
|
||||||
<div .separator />
|
<div .separator />
|
||||||
<li #best type="image-quality"><span>{svg_checkmark}</span>Good image quality</li>
|
<li #best type="image-quality"><span>{svg_checkmark}</span>{translate('Good image quality')}</li>
|
||||||
<li #balanced type="image-quality"><span>{svg_checkmark}</span>Balanced</li>
|
<li #balanced type="image-quality"><span>{svg_checkmark}</span>{translate('Balanced')}</li>
|
||||||
<li #low type="image-quality"><span>{svg_checkmark}</span>Optimize reaction time</li>
|
<li #low type="image-quality"><span>{svg_checkmark}</span>{translate('Optimize reaction time')}</li>
|
||||||
<li #custom type="image-quality"><span>{svg_checkmark}</span>Custom</li>
|
<li #custom type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li>
|
||||||
<div .separator />
|
<div .separator />
|
||||||
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>Show remote cursor</li>
|
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>
|
||||||
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>Mute</li> : ""}
|
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
|
||||||
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>Disable clipboard</li> : ""}
|
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
||||||
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>Lock after session end</li> : ""}
|
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
|
||||||
{false && pi.platform == "Windows" ? <li #privacy-mode .toggle-option><span>{svg_checkmark}</span>Privacy mode</li> : ""}
|
{false && pi.platform == "Windows" ? <li #privacy-mode .toggle-option><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
|
||||||
</menu>
|
</menu>
|
||||||
</popup>;
|
</popup>;
|
||||||
}
|
}
|
||||||
@@ -134,23 +138,20 @@ class Header: Reactor.Component {
|
|||||||
function renderActionPop() {
|
function renderActionPop() {
|
||||||
return <popup>
|
return <popup>
|
||||||
<menu.context #action-options>
|
<menu.context #action-options>
|
||||||
<li #transfer-file>Transfer File</li>
|
<li #transfer-file>{translate('Transfer File')}</li>
|
||||||
<li #tunnel>TCP Tunneling</li>
|
<li #tunnel>{translate('TCP Tunneling')}</li>
|
||||||
<div .separator />
|
<div .separator />
|
||||||
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>Insert Ctrl + Alt + Del</li> : ""}
|
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>{translate('Insert')} Ctrl + Alt + Del</li> : ""}
|
||||||
<li #ctrl-space>Insert Ctrl + Space</li>
|
|
||||||
<li #alt-tab>Insert Alt + Tab</li>
|
|
||||||
{false && <li #super-x>Insert Win/Super + ...</li>}
|
|
||||||
<div .separator />
|
<div .separator />
|
||||||
{keyboard_enabled ? <li #lock-screen>Insert Lock</li> : ""}
|
{keyboard_enabled ? <li #lock-screen>{translate('Insert Lock')}</li> : ""}
|
||||||
{false && pi.platform == "Windows" ? <li #block-input>Block user input </li> : ""}
|
{false && pi.platform == "Windows" ? <li #block-input>Block user input </li> : ""}
|
||||||
{handler.support_refresh() ? <li #refresh>Refresh</li> : ""}
|
{handler.support_refresh() ? <li #refresh>{translate('Refresh')}</li> : ""}
|
||||||
</menu>
|
</menu>
|
||||||
</popup>;
|
</popup>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderGlobalScreens() {
|
function renderGlobalScreens() {
|
||||||
if (pi.displays.length < 2) return "";
|
if (pi.displays.length < 3) return "";
|
||||||
var x0 = 9999999;
|
var x0 = 9999999;
|
||||||
var y0 = 9999999;
|
var y0 = 9999999;
|
||||||
var x = -9999999;
|
var x = -9999999;
|
||||||
@@ -232,18 +233,6 @@ class Header: Reactor.Component {
|
|||||||
event click $(#ctrl-alt-del) {
|
event click $(#ctrl-alt-del) {
|
||||||
handler.ctrl_alt_del();
|
handler.ctrl_alt_del();
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(#alt-tab) {
|
|
||||||
handler.alt_tab();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#ctrl-space) {
|
|
||||||
handler.ctrl_space();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#super-x) {
|
|
||||||
handler.super_x();
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#lock-screen) {
|
event click $(#lock-screen) {
|
||||||
handler.lock_screen();
|
handler.lock_screen();
|
||||||
@@ -288,9 +277,9 @@ function handle_custom_image_quality() {
|
|||||||
var tmp = handler.get_custom_image_quality();
|
var tmp = handler.get_custom_image_quality();
|
||||||
var bitrate0 = tmp[0] || 50;
|
var bitrate0 = tmp[0] || 50;
|
||||||
var quantizer0 = tmp.length > 1 ? tmp[1] : 100;
|
var quantizer0 = tmp.length > 1 ? tmp[1] : 100;
|
||||||
handler.msgbox("custom", "Custom Image Quality", "<div .form> \
|
msgbox("custom", "Custom Image Quality", "<div .form> \
|
||||||
<div><input type=\"hslider\" style=\"width: 66%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate0 + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% bitrate</div> \
|
<div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate0 + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% bitrate</div> \
|
||||||
<div><input type=\"hslider\" style=\"width: 66%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
|
<div><input type=\"hslider\" style=\"width: 50%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
|
||||||
</div>", function(res=null) {
|
</div>", function(res=null) {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
if (!res.bitrate) return;
|
if (!res.bitrate) return;
|
||||||
@@ -408,7 +397,7 @@ function startChat() {
|
|||||||
height: h,
|
height: h,
|
||||||
client: true,
|
client: true,
|
||||||
parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon },
|
parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon },
|
||||||
caption: handler.get_id(),
|
caption: get_id(),
|
||||||
};
|
};
|
||||||
var html = handler.get_chatbox();
|
var html = handler.get_chatbox();
|
||||||
if (html) params.html = html;
|
if (html) params.html = html;
|
||||||
|
|||||||
127
src/ui/index.css
127
src/ui/index.css
@@ -39,6 +39,77 @@ body {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.sessions-bar {
|
||||||
|
color: color(light-text);
|
||||||
|
padding-top: 0.5em;
|
||||||
|
border-top: color(border) solid 1px;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
position: relative;
|
||||||
|
flow: horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sessions-tab span {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sessions-tab svg {
|
||||||
|
size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sessions-tab span.active {
|
||||||
|
cursor: default;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: white;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.search-id {
|
||||||
|
width: 120px;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.search-id input {
|
||||||
|
font-size: 1em;
|
||||||
|
height: 20px;
|
||||||
|
border: none;
|
||||||
|
padding-left: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.search-id span {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
padding: 6px;
|
||||||
|
color: color(border);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.search-id svg {
|
||||||
|
size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.search-icon {
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.clear-input {
|
||||||
|
display: none;
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.search-id:hover span.clear-input {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.clear-input:hover {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
.your-desktop {
|
.your-desktop {
|
||||||
border-spacing: 0.5em;
|
border-spacing: 0.5em;
|
||||||
border-left: color(accent) solid 2px;
|
border-left: color(accent) solid 2px;
|
||||||
@@ -132,13 +203,6 @@ div.recent-sessions-content {
|
|||||||
flow: horizontal-flow;
|
flow: horizontal-flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.recent-sessions-title {
|
|
||||||
color: color(light-text);
|
|
||||||
padding-top: 0.5em;
|
|
||||||
border-top: color(border) solid 1px;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.remote-session {
|
div.remote-session {
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
height: 140px;
|
height: 140px;
|
||||||
@@ -148,7 +212,7 @@ div.remote-session {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.remote-session:hover {
|
div.remote-session:hover, div.remote-session-list:hover {
|
||||||
outline: color(button) solid 2px -2px;
|
outline: color(button) solid 2px -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +240,41 @@ div.remote-session .platform svg {
|
|||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.remote-session-list {
|
||||||
|
background: white;
|
||||||
|
width: 220px;
|
||||||
|
flow: horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.remote-session-list .platform {
|
||||||
|
size: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.remote-session-list .platform svg {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: none;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.remote-session-list .name {
|
||||||
|
size: *;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.remote-session-list .name >div {
|
||||||
|
margin-top: *;
|
||||||
|
margin-bottom: *;
|
||||||
|
width: *;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.remote-session-list .name .username {
|
||||||
|
margin-top: 3px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: color(lighter-text);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
div.remote-session .text {
|
div.remote-session .text {
|
||||||
background: white;
|
background: white;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -200,20 +299,22 @@ svg#menu {
|
|||||||
color: color(light-text);
|
color: color(light-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg#menu:active {
|
.remote-session-list svg#menu {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg#menu:hover {
|
||||||
color: black;
|
color: black;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
background: color(gray-bg);
|
background: color(gray-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg#edit:active {
|
svg#edit:hover {
|
||||||
opacity: 0.5;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg#edit {
|
svg#edit {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 0.25em;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.install-me, div.trust-me {
|
div.install-me, div.trust-me {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</style>
|
</style>
|
||||||
<script type="text/tiscript">
|
<script type="text/tiscript">
|
||||||
include "common.tis";
|
include "common.tis";
|
||||||
|
include "ab.tis";
|
||||||
include "index.tis";
|
include "index.tis";
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
315
src/ui/index.tis
315
src/ui/index.tis
@@ -18,112 +18,39 @@ var svg_menu = <svg #menu viewBox="0 0 512 512">
|
|||||||
<circle cx="256" cy="64" r="64"/>
|
<circle cx="256" cy="64" r="64"/>
|
||||||
</svg>;
|
</svg>;
|
||||||
|
|
||||||
|
var my_id = "";
|
||||||
|
function get_id() {
|
||||||
|
my_id = handler.get_id();
|
||||||
|
return my_id;
|
||||||
|
}
|
||||||
|
|
||||||
class ConnectStatus: Reactor.Component {
|
class ConnectStatus: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return
|
return
|
||||||
<div .connect-status>
|
<div .connect-status>
|
||||||
<span class={"connect-status-icon connect-status" + (service_stopped ? 0 : connect_status)} />
|
<span class={"connect-status-icon connect-status" + (service_stopped ? 0 : connect_status)} />
|
||||||
{this.getConnectStatusStr()}
|
{this.getConnectStatusStr()}
|
||||||
{service_stopped ? <span class="link">Start Service</span> : ""}
|
{service_stopped ? <span .link #start-service>{translate('Start Service')}</span> : ""}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConnectStatusStr() {
|
function getConnectStatusStr() {
|
||||||
if (service_stopped) {
|
if (service_stopped) {
|
||||||
return "Service is not running";
|
return translate("Service is not running");
|
||||||
} else if (connect_status == -1) {
|
} else if (connect_status == -1) {
|
||||||
return "Not ready. Please check your connection";
|
return translate('not_ready_status');
|
||||||
} else if (connect_status == 0) {
|
} else if (connect_status == 0) {
|
||||||
return "Connecting to the RustDesk network...";
|
return translate('connecting_status');
|
||||||
}
|
}
|
||||||
return "Ready";
|
return translate("Ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(.connect-status .link) () {
|
event click $(.connect-status .link) () {
|
||||||
handler.set_option("stop-service", "");
|
handler.set_option("stop-service", "");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class RecentSessions: Reactor.Component {
|
event click $(#start-service) () {
|
||||||
function render() {
|
handler.set_option("stop-service", "");
|
||||||
var sessions = handler.get_recent_sessions();
|
|
||||||
if (sessions.length == 0) return <span />;
|
|
||||||
sessions = sessions.map(this.getSession);
|
|
||||||
return <div style="width: *">
|
|
||||||
<div .recent-sessions-title>RECENT SESSIONS</div>
|
|
||||||
{ false && <button .button #discover>DISCOVER</button>}
|
|
||||||
<div .recent-sessions-content key={sessions.length}>
|
|
||||||
{sessions}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(button#discover) {
|
|
||||||
handler.lan_discover();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSession(s) {
|
|
||||||
var id = s[0];
|
|
||||||
var username = s[1];
|
|
||||||
var hostname = s[2];
|
|
||||||
var platform = s[3];
|
|
||||||
var alias = s[4];
|
|
||||||
return <div .remote-session id={id} platform={platform} style={"background:"+string2RGB(id+platform, 0.5)}>
|
|
||||||
<div .platform>
|
|
||||||
{platformSvg(platform, "white")}
|
|
||||||
<div .username>{username}@{hostname}</div>
|
|
||||||
</div>
|
|
||||||
<div .text>
|
|
||||||
<div #alias>{alias ? alias : formatId(id)}</div>
|
|
||||||
{svg_menu}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event dblclick $(div.remote-session) (evt, me) {
|
|
||||||
createNewConnect(me.id, "connect");
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(#menu) (_, me) {
|
|
||||||
var id = me.parent.parent.id;
|
|
||||||
var platform = me.parent.parent.attributes["platform"];
|
|
||||||
$(#rdp).style.set{
|
|
||||||
display: (platform == "Windows" && is_win) ? "block" : "none",
|
|
||||||
};
|
|
||||||
// https://sciter.com/forums/topic/replacecustomize-context-menu/
|
|
||||||
var menu = $(menu#remote-context);
|
|
||||||
menu.attributes["remote-id"] = id;
|
|
||||||
me.popup(menu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(menu#remote-context li) (evt, me) {
|
|
||||||
var action = me.id;
|
|
||||||
var id = me.parent.attributes["remote-id"];
|
|
||||||
if (action == "connect") {
|
|
||||||
createNewConnect(id, "connect");
|
|
||||||
} else if (action == "transfer") {
|
|
||||||
createNewConnect(id, "file-transfer");
|
|
||||||
} else if (action == "remove") {
|
|
||||||
handler.remove_peer(id);
|
|
||||||
app.recent_sessions.update();
|
|
||||||
} else if (action == "shortcut") {
|
|
||||||
handler.create_shortcut(id);
|
|
||||||
} else if (action == "rdp") {
|
|
||||||
createNewConnect(id, "rdp");
|
|
||||||
} else if (action == "tunnel") {
|
|
||||||
createNewConnect(id, "port-forward");
|
|
||||||
} else if (action == "rename") {
|
|
||||||
var old_name = handler.get_peer_option(id, "alias");
|
|
||||||
handler.msgbox("custom-rename", "Rename", "<div .form> \
|
|
||||||
<div><input name='name' style='width: *; height: 23px', value='" + old_name + "' /></div> \
|
|
||||||
</div> \
|
|
||||||
", function(res=null) {
|
|
||||||
if (!res) return;
|
|
||||||
var name = (res.name || "").trim();
|
|
||||||
if (name != old_name) handler.set_peer_option(id, "alias", name);
|
|
||||||
self.select('#' + id).select('#alias').text = name || id;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,14 +58,32 @@ function createNewConnect(id, type) {
|
|||||||
id = id.replace(/\s/g, "");
|
id = id.replace(/\s/g, "");
|
||||||
app.remote_id.value = formatId(id);
|
app.remote_id.value = formatId(id);
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
if (id == handler.get_id()) {
|
if (id == my_id) {
|
||||||
handler.msgbox("custom-error", "Error", "You cannot connect to your own computer");
|
msgbox("custom-error", "Error", "You cannot connect to your own computer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handler.set_remote_id(id);
|
handler.set_remote_id(id);
|
||||||
handler.new_remote(id, type);
|
handler.new_remote(id, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var direct_server;
|
||||||
|
class DirectServer: Reactor.Component {
|
||||||
|
function this() {
|
||||||
|
direct_server = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
var text = translate("Enable Direct IP Access");
|
||||||
|
var cls = handler.get_option("direct-server") == "Y" ? "selected" : "line-through";
|
||||||
|
return <li class={cls}><span>{svg_checkmark}</span>{text}</li>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
handler.set_option("direct-server", handler.get_option("direct-server") == "Y" ? "" : "Y");
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var myIdMenu;
|
var myIdMenu;
|
||||||
var audioInputMenu;
|
var audioInputMenu;
|
||||||
class AudioInputs: Reactor.Component {
|
class AudioInputs: Reactor.Component {
|
||||||
@@ -154,10 +99,10 @@ class AudioInputs: Reactor.Component {
|
|||||||
inputs = ["Mute"].concat(inputs);
|
inputs = ["Mute"].concat(inputs);
|
||||||
var me = this;
|
var me = this;
|
||||||
self.timer(1ms, function() { me.toggleMenuState() });
|
self.timer(1ms, function() { me.toggleMenuState() });
|
||||||
return <li>Audio Input
|
return <li>{translate('Audio Input')}
|
||||||
<menu #audio-input key={inputs.length}>
|
<menu #audio-input key={inputs.length}>
|
||||||
{inputs.map(function(name) {
|
{inputs.map(function(name) {
|
||||||
return <li id={name}><span>{svg_checkmark}</span>{name}</li>;
|
return <li id={name}><span>{svg_checkmark}</span>{translate(name)}</li>;
|
||||||
})}
|
})}
|
||||||
</menu>
|
</menu>
|
||||||
</li>;
|
</li>;
|
||||||
@@ -195,7 +140,6 @@ class MyIdMenu: Reactor.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
var me = this;
|
|
||||||
return <div #myid>
|
return <div #myid>
|
||||||
{this.renderPop()}
|
{this.renderPop()}
|
||||||
ID{svg_menu}
|
ID{svg_menu}
|
||||||
@@ -205,19 +149,20 @@ class MyIdMenu: Reactor.Component {
|
|||||||
function renderPop() {
|
function renderPop() {
|
||||||
return <popup>
|
return <popup>
|
||||||
<menu.context #config-options>
|
<menu.context #config-options>
|
||||||
<li #enable-keyboard><span>{svg_checkmark}</span>Enable Keyboard/Mouse</li>
|
<li #enable-keyboard><span>{svg_checkmark}</span>{translate('Enable Keyboard/Mouse')}</li>
|
||||||
<li #enable-clipboard><span>{svg_checkmark}</span>Enable Clipboard</li>
|
<li #enable-clipboard><span>{svg_checkmark}</span>{translate('Enable Clipboard')}</li>
|
||||||
<li #enable-file-transfer><span>{svg_checkmark}</span>Enable File Transfer</li>
|
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable File Transfer')}</li>
|
||||||
<li #enable-tunnel><span>{svg_checkmark}</span>Enable TCP Tunneling</li>
|
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
|
||||||
<AudioInputs />
|
<AudioInputs />
|
||||||
<div .separator />
|
<div .separator />
|
||||||
<li #whitelist title="Only whitelisted IP can access me">IP Whitelisting</li>
|
<li #whitelist title={translate('whitelist_tip')}>{translate('IP Whitelisting')}</li>
|
||||||
<li #custom-server>ID/Relay Server</li>
|
<li #custom-server>{translate('ID/Relay Server')}</li>
|
||||||
|
<li #socks5-server>{translate('Socks5 Proxy')}</li>
|
||||||
<div .separator />
|
<div .separator />
|
||||||
<li #stop-service class={service_stopped ? "line-through" : "selected"}><span>{svg_checkmark}</span>Enable service</li>
|
<li #stop-service class={service_stopped ? "line-through" : "selected"}><span>{svg_checkmark}</span>{translate("Enable Service")}</li>
|
||||||
|
<DirectServer />
|
||||||
<div .separator />
|
<div .separator />
|
||||||
<li #forum>Forum</li>
|
<li #about>{translate('About')} {" "} {handler.get_app_name()}</li>
|
||||||
<li #about>About {handler.get_app_name()}</li>
|
|
||||||
</menu>
|
</menu>
|
||||||
</popup>;
|
</popup>;
|
||||||
}
|
}
|
||||||
@@ -225,6 +170,7 @@ class MyIdMenu: Reactor.Component {
|
|||||||
event click $(svg#menu) (_, me) {
|
event click $(svg#menu) (_, me) {
|
||||||
audioInputMenu.update({ show: true });
|
audioInputMenu.update({ show: true });
|
||||||
this.toggleMenuState();
|
this.toggleMenuState();
|
||||||
|
if (direct_server) direct_server.update();
|
||||||
var menu = $(menu#config-options);
|
var menu = $(menu#config-options);
|
||||||
me.popup(menu);
|
me.popup(menu);
|
||||||
}
|
}
|
||||||
@@ -245,8 +191,9 @@ class MyIdMenu: Reactor.Component {
|
|||||||
}
|
}
|
||||||
if (me.id == "whitelist") {
|
if (me.id == "whitelist") {
|
||||||
var old_value = handler.get_option("whitelist").split(",").join("\n");
|
var old_value = handler.get_option("whitelist").split(",").join("\n");
|
||||||
handler.msgbox("custom-whitelist", "IP Whitelisting", "<div .form> \
|
msgbox("custom-whitelist", translate("IP Whitelisting"), "<div .form> \
|
||||||
<textarea spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; height: 160px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
|
<div>" + translate("whitelist_sep") + "</div> \
|
||||||
|
<textarea spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
|
||||||
</div> \
|
</div> \
|
||||||
", function(res=null) {
|
", function(res=null) {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
@@ -255,7 +202,7 @@ class MyIdMenu: Reactor.Component {
|
|||||||
var values = value.split(/[\s,;\n]+/g);
|
var values = value.split(/[\s,;\n]+/g);
|
||||||
for (var ip in values) {
|
for (var ip in values) {
|
||||||
if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
||||||
return "Invalid ip: " + ip;
|
return translate("Invalid IP") + ": " + ip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value = values.join("\n");
|
value = values.join("\n");
|
||||||
@@ -265,12 +212,12 @@ class MyIdMenu: Reactor.Component {
|
|||||||
handler.set_option("whitelist", value.replace("\n", ","));
|
handler.set_option("whitelist", value.replace("\n", ","));
|
||||||
}, 300);
|
}, 300);
|
||||||
} else if (me.id == "custom-server") {
|
} else if (me.id == "custom-server") {
|
||||||
var configOptions = handler.get_options();
|
var configOptions = handler.get_options();
|
||||||
var old_relay = configOptions["relay-server"] || "";
|
var old_relay = configOptions["relay-server"] || "";
|
||||||
var old_id = configOptions["custom-rendezvous-server"] || "";
|
var old_id = configOptions["custom-rendezvous-server"] || "";
|
||||||
handler.msgbox("custom-server", "ID/Relay Server", "<div .form> \
|
msgbox("custom-server", "ID/Relay Server", "<div .form> \
|
||||||
<div><span style='width: 100px; display:inline-block'>ID Server: </span><input style='width: 250px' name='id' value='" + old_id + "' /></div> \
|
<div><span style='width: 100px; display:inline-block'>" + translate("ID Server") + ": </span><input .outline-focus style='width: 250px' name='id' value='" + old_id + "' /></div> \
|
||||||
<div><span style='width: 100px; display:inline-block'>Relay Server: </span><input style='width: 250px' name='relay' value='" + old_relay + "' /></div> \
|
<div><span style='width: 100px; display:inline-block'>" + translate("Relay Server") + ": </span><input style='width: 250px' name='relay' value='" + old_relay + "' /></div> \
|
||||||
</div> \
|
</div> \
|
||||||
", function(res=null) {
|
", function(res=null) {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
@@ -279,26 +226,46 @@ class MyIdMenu: Reactor.Component {
|
|||||||
if (id == old_id && relay == old_relay) return;
|
if (id == old_id && relay == old_relay) return;
|
||||||
if (id) {
|
if (id) {
|
||||||
var err = handler.test_if_valid_server(id);
|
var err = handler.test_if_valid_server(id);
|
||||||
if (err) return "ID Server: " + err;
|
if (err) return translate("ID Server") + ": " + err;
|
||||||
}
|
}
|
||||||
if (relay) {
|
if (relay) {
|
||||||
var err = handler.test_if_valid_server(relay);
|
var err = handler.test_if_valid_server(relay);
|
||||||
if (err) return "Relay Server: " + err;
|
if (err) return translate("Relay Server") + ": " + err;
|
||||||
}
|
}
|
||||||
configOptions["custom-rendezvous-server"] = id;
|
configOptions["custom-rendezvous-server"] = id;
|
||||||
configOptions["relay-server"] = relay;
|
configOptions["relay-server"] = relay;
|
||||||
handler.set_options(configOptions);
|
handler.set_options(configOptions);
|
||||||
});
|
}, 240);
|
||||||
} else if (me.id == "forum") {
|
} else if (me.id == "socks5-server") {
|
||||||
handler.open_url("https:://forum.rustdesk.com");
|
var socks5 = handler.get_socks() || {};
|
||||||
|
var old_proxy = socks5[0] || "";
|
||||||
|
var old_username = socks5[1] || "";
|
||||||
|
var old_password = socks5[2] || "";
|
||||||
|
msgbox("custom-server", "Socks5 Proxy", <div .form .set-password>
|
||||||
|
<div><span>{translate("Hostname")}</span><input .outline-focus style='width: *' name='proxy' value={old_proxy} /></div>
|
||||||
|
<div><span>{translate("Username")}</span><input style='width: *' name='username' value={old_username} /></div>
|
||||||
|
<div><span>{translate("Password")}</span><PasswordComponent value={old_password} /></div>
|
||||||
|
</div>
|
||||||
|
, function(res=null) {
|
||||||
|
if (!res) return;
|
||||||
|
var proxy = (res.proxy || "").trim();
|
||||||
|
var username = (res.username || "").trim();
|
||||||
|
var password = (res.password || "").trim();
|
||||||
|
if (proxy == old_proxy && username == old_username && password == old_password) return;
|
||||||
|
if (proxy) {
|
||||||
|
var err = handler.test_if_valid_server(proxy);
|
||||||
|
if (err) return translate("Server") + ": " + err;
|
||||||
|
}
|
||||||
|
handler.set_socks(proxy, username, password);
|
||||||
|
}, 240);
|
||||||
} else if (me.id == "stop-service") {
|
} else if (me.id == "stop-service") {
|
||||||
handler.set_option("stop-service", service_stopped ? "" : "Y");
|
handler.set_option("stop-service", service_stopped ? "" : "Y");
|
||||||
} else if (me.id == "about") {
|
} else if (me.id == "about") {
|
||||||
var name = handler.get_app_name();
|
var name = handler.get_app_name();
|
||||||
handler.msgbox("custom-nocancel-nook-hasclose", "About " + name, "<div style='line-height: 2em'> \
|
msgbox("custom-nocancel-nook-hasclose", "About " + name, "<div style='line-height: 2em'> \
|
||||||
<div>Version: " + handler.get_version() + " \
|
<div>Version: " + handler.get_version() + " \
|
||||||
<div .link .custom-event url='http://rustdesk.com/privacy'>Privacy Statement</div> \
|
<div .link .custom-event url='http://rustdesk.com/privacy'>Privacy Statement</div> \
|
||||||
<div .link .custom-event url='http://forum.rustdesk.com'>Forum</div> \
|
<div .link .custom-event url='http://rustdesk.com'>Website</div> \
|
||||||
<div style='background: #2c8cff; color: white; padding: 1em; margin-top: 1em;'>Copyright © 2020 CarrieZ Studio \
|
<div style='background: #2c8cff; color: white; padding: 1em; margin-top: 1em;'>Copyright © 2020 CarrieZ Studio \
|
||||||
<br /> Author: Carrie \
|
<br /> Author: Carrie \
|
||||||
<p style='font-weight: bold'>Made with heart in this chaotic world!</p>\
|
<p style='font-weight: bold'>Made with heart in this chaotic world!</p>\
|
||||||
@@ -322,37 +289,24 @@ class App: Reactor.Component
|
|||||||
var is_can_screen_recording = handler.is_can_screen_recording(false);
|
var is_can_screen_recording = handler.is_can_screen_recording(false);
|
||||||
return
|
return
|
||||||
<div .app>
|
<div .app>
|
||||||
<popup>
|
<popup><menu.context #edit-password-context>
|
||||||
<menu.context #remote-context>
|
<li #refresh-password>{translate('Refresh random password')}</li>
|
||||||
<li #connect>Connect</li>
|
<li #set-password>{translate('Set your own password')}</li>
|
||||||
<li #transfer>Transfer File</li>
|
</menu></popup>
|
||||||
<li #tunnel>TCP Tunneling</li>
|
<div .left-pane>
|
||||||
<li #rdp>RDP</li>
|
|
||||||
<li #rename>Rename</li>
|
|
||||||
<li #remove>Remove</li>
|
|
||||||
{is_win && <li #shortcut>Create Desktop Shortcut</li>}
|
|
||||||
</menu>
|
|
||||||
</popup>
|
|
||||||
<popup>
|
|
||||||
<menu.context #edit-password-context>
|
|
||||||
<li #refresh-password>Refresh random password</li>
|
|
||||||
<li #set-password>Set your own password</li>
|
|
||||||
</menu>
|
|
||||||
</popup>
|
|
||||||
<div .left-pane>
|
|
||||||
<div>
|
<div>
|
||||||
<div .title>Your Desktop</div>
|
<div .title>{translate('Your Desktop')}</div>
|
||||||
<div .lighter-text>Your desktop can be accessed with this ID and password.</div>
|
<div .lighter-text>{translate('desk_tip')}</div>
|
||||||
<div .your-desktop>
|
<div .your-desktop>
|
||||||
<MyIdMenu />
|
<MyIdMenu />
|
||||||
{key_confirmed ? <input type="text" readonly value={formatId(handler.get_id())}/> : "Generating ..."}
|
{key_confirmed ? <input type="text" readonly value={formatId(get_id())}/> : translate("Generating ...")}
|
||||||
</div>
|
</div>
|
||||||
<div .your-desktop>
|
<div .your-desktop>
|
||||||
<div>Password</div>
|
<div>{translate('Password')}</div>
|
||||||
<Password />
|
<Password />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{handler.is_installed() ? "": <InstalllMe />}
|
{handler.is_installed() ? "": <InstallMe />}
|
||||||
{handler.is_installed() && software_update_url ? <UpdateMe /> : ""}
|
{handler.is_installed() && software_update_url ? <UpdateMe /> : ""}
|
||||||
{handler.is_installed() && !software_update_url && handler.is_installed_lower_version() ? <UpgradeMe /> : ""}
|
{handler.is_installed() && !software_update_url && handler.is_installed_lower_version() ? <UpgradeMe /> : ""}
|
||||||
{is_can_screen_recording ? "": <CanScreenRecording />}
|
{is_can_screen_recording ? "": <CanScreenRecording />}
|
||||||
@@ -364,14 +318,14 @@ class App: Reactor.Component
|
|||||||
<div .right-pane>
|
<div .right-pane>
|
||||||
<div .right-content>
|
<div .right-content>
|
||||||
<div .card-connect>
|
<div .card-connect>
|
||||||
<div .title>Control Remote Desktop</div>
|
<div .title>{translate('Control Remote Desktop')}</div>
|
||||||
<ID @{this.remote_id} />
|
<ID @{this.remote_id} />
|
||||||
<div .right-buttons>
|
<div .right-buttons>
|
||||||
<button .button .outline #file-transfer>Transfer File</button>
|
<button .button .outline #file-transfer>{translate('Transfer File')}</button>
|
||||||
<button .button #connect>Connect</button>
|
<button .button #connect>{translate('Connect')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<RecentSessions @{this.recent_sessions} />
|
<MultipleSessions @{this.multipleSessions} />
|
||||||
</div>
|
</div>
|
||||||
<ConnectStatus @{this.connect_status} />
|
<ConnectStatus @{this.connect_status} />
|
||||||
</div>
|
</div>
|
||||||
@@ -391,11 +345,12 @@ class App: Reactor.Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InstalllMe: Reactor.Component {
|
class InstallMe: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return <div .install-me>
|
return <div .install-me>
|
||||||
<div>Install RustDesk</div>
|
<span />
|
||||||
<div #install-me .link>Install RustDesk on this computer ...</div>
|
<div>{translate('install_tip')}</div>
|
||||||
|
<div style="text-align: center; margin-top: 1em;"><button #install-me .button>{translate('Install')}</button></div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,9 +405,9 @@ class UpgradeMe: Reactor.Component {
|
|||||||
function render() {
|
function render() {
|
||||||
var update_or_download = is_osx ? "download" : "update";
|
var update_or_download = is_osx ? "download" : "update";
|
||||||
return <div .install-me>
|
return <div .install-me>
|
||||||
<div>{handler.get_app_name()} Status</div>
|
<div>{translate('Status')}</div>
|
||||||
<div>An update is available for RustDesk.</div>
|
<div>{translate('Your installation is lower version.')}</div>
|
||||||
<div #install-me .link style="padding-top: 1em">Click to upgrade</div>
|
<div #install-me .link style="padding-top: 1em">{translate('Click to upgrade')}</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,9 +418,9 @@ class UpgradeMe: Reactor.Component {
|
|||||||
|
|
||||||
class UpdateMe: Reactor.Component {
|
class UpdateMe: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
var update_or_download = is_osx ? "download" : "update";
|
var update_or_download = "download"; // !is_win ? "download" : "update";
|
||||||
return <div .install-me>
|
return <div .install-me>
|
||||||
<div>{handler.get_app_name()} Status</div>
|
<div>{translate('Status')}</div>
|
||||||
<div>There is a newer version of {handler.get_app_name()} ({handler.get_new_version()}) available.</div>
|
<div>There is a newer version of {handler.get_app_name()} ({handler.get_new_version()}) available.</div>
|
||||||
<div #install-me .link style="padding-top: 1em">Click to {update_or_download}</div>
|
<div #install-me .link style="padding-top: 1em">Click to {update_or_download}</div>
|
||||||
<div #download-percent style="display:hidden; padding-top: 1em;" />
|
<div #download-percent style="display:hidden; padding-top: 1em;" />
|
||||||
@@ -473,18 +428,20 @@ class UpdateMe: Reactor.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event click $(#install-me) {
|
event click $(#install-me) {
|
||||||
if (is_osx) {
|
handler.open_url("https://rustdesk.com");
|
||||||
|
return;
|
||||||
|
if (!is_win) {
|
||||||
handler.open_url("https://rustdesk.com");
|
handler.open_url("https://rustdesk.com");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var url = software_update_url + '.' + handler.get_software_ext();
|
var url = software_update_url + '.' + handler.get_software_ext();
|
||||||
var path = handler.get_software_store_path();
|
var path = handler.get_software_store_path();
|
||||||
var onsuccess = function(md5) {
|
var onsuccess = function(md5) {
|
||||||
$(#download-percent).content("Installing ...");
|
$(#download-percent).content(translate("Installing ..."));
|
||||||
handler.update_me(path);
|
handler.update_me(path);
|
||||||
};
|
};
|
||||||
var onerror = function(err) {
|
var onerror = function(err) {
|
||||||
handler.msgbox("custom-error", "Download Error", "Failed to download");
|
msgbox("custom-error", "Download Error", "Failed to download");
|
||||||
};
|
};
|
||||||
var onprogress = function(loaded, total) {
|
var onprogress = function(loaded, total) {
|
||||||
if (!total) total = 5 * 1024 * 1024;
|
if (!total) total = 5 * 1024 * 1024;
|
||||||
@@ -511,9 +468,9 @@ class SystemError: Reactor.Component {
|
|||||||
class TrustMe: Reactor.Component {
|
class TrustMe: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return <div .trust-me>
|
return <div .trust-me>
|
||||||
<div>Configuration Permissions</div>
|
<div>{translate('Configuration Permissions')}</div>
|
||||||
<div>In order to control your Desktop remotely, you need to grant RustDesk "Accessibility" permissions</div>
|
<div>{translate('config_acc')}</div>
|
||||||
<div #trust-me .link>Configure</div>
|
<div #trust-me .link>{translate('Configure')}</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,9 +483,9 @@ class TrustMe: Reactor.Component {
|
|||||||
class CanScreenRecording: Reactor.Component {
|
class CanScreenRecording: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return <div .trust-me>
|
return <div .trust-me>
|
||||||
<div>Configuration Permissions</div>
|
<div>{translate('Configuration Permissions')}</div>
|
||||||
<div>In order to access your Desktop remotely, you need to grant RustDesk "Screen Recording" permissions</div>
|
<div>{translate('config_screen')}</div>
|
||||||
<div #screen-recording .link>Configure</div>
|
<div #screen-recording .link>{translate('Configure')}</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,9 +498,10 @@ class CanScreenRecording: Reactor.Component {
|
|||||||
class FixWayland: Reactor.Component {
|
class FixWayland: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return <div .trust-me>
|
return <div .trust-me>
|
||||||
<div>Warning</div>
|
<div>{translate('Warning')}</div>
|
||||||
<div>Login screen using Wayland is not supported</div>
|
<div>{translate('Login screen using Wayland is not supported')}</div>
|
||||||
<div #fix-wayland .link>Fix it</div>
|
<div #fix-wayland .link>{translate('Fix it')}</div>
|
||||||
|
<div style="text-align: center">({translate('Reboot required')})</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,15 +514,16 @@ class FixWayland: Reactor.Component {
|
|||||||
class ModifyDefaultLogin: Reactor.Component {
|
class ModifyDefaultLogin: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return <div .trust-me>
|
return <div .trust-me>
|
||||||
<div>Warning</div>
|
<div>{translate('Warning')}</div>
|
||||||
<div>Current Wayland display server is not supported</div>
|
<div>{translate('Current Wayland display server is not supported')}</div>
|
||||||
<div #modify-default-login .link>Fix it(re-login required)</div>
|
<div #modify-default-login .link>{translate('Fix it')}</div>
|
||||||
|
<div style="text-align: center">({translate('Reboot required')})</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(#modify-default-login) {
|
event click $(#modify-default-login) {
|
||||||
if (var r = handler.modify_default_login()) {
|
if (var r = handler.modify_default_login()) {
|
||||||
handler.msgbox("custom-error", "Error", r);
|
msgbox("custom-error", "Error", r);
|
||||||
}
|
}
|
||||||
app.update();
|
app.update();
|
||||||
}
|
}
|
||||||
@@ -627,19 +586,19 @@ class Password: Reactor.Component {
|
|||||||
|
|
||||||
event click $(li#set-password) {
|
event click $(li#set-password) {
|
||||||
var me = this;
|
var me = this;
|
||||||
handler.msgbox("custom-password", "Set Password", "<div .form .set-password> \
|
msgbox("custom-password", translate("Set Password"), "<div .form .set-password> \
|
||||||
<div><span>Password:</span><input|password(password) /></div> \
|
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus /></div> \
|
||||||
<div><span>Confirmation:</span><input|password(confirmation) /></div> \
|
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) /></div> \
|
||||||
</div> \
|
</div> \
|
||||||
", function(res=null) {
|
", function(res=null) {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
var p0 = (res.password || "").trim();
|
var p0 = (res.password || "").trim();
|
||||||
var p1 = (res.confirmation || "").trim();
|
var p1 = (res.confirmation || "").trim();
|
||||||
if (p0.length < 6) {
|
if (p0.length < 6) {
|
||||||
return "Too short, at least 6 characters.";
|
return translate("Too short, at least 6 characters.");
|
||||||
}
|
}
|
||||||
if (p0 != p1) {
|
if (p0 != p1) {
|
||||||
return "The confirmation is not identical.";
|
return translate("The confirmation is not identical.");
|
||||||
}
|
}
|
||||||
handler.update_password(p0);
|
handler.update_password(p0);
|
||||||
me.update();
|
me.update();
|
||||||
@@ -649,7 +608,7 @@ class Password: Reactor.Component {
|
|||||||
|
|
||||||
class ID: Reactor.Component {
|
class ID: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return <input type="text" #remote_id .outline-focus novalue="Enter Remote ID" maxlength="13"
|
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")} maxlength="15"
|
||||||
value={formatId(handler.get_remote_id())} />;
|
value={formatId(handler.get_remote_id())} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,10 +672,10 @@ function self.closing() {
|
|||||||
|
|
||||||
function self.ready() {
|
function self.ready() {
|
||||||
var r = handler.get_size();
|
var r = handler.get_size();
|
||||||
if (r[2] == 0) {
|
if (isReasonableSize(r) && r[2] > 0) {
|
||||||
centerize(800, 600);
|
|
||||||
} else {
|
|
||||||
view.move(r[0], r[1], r[2], r[3]);
|
view.move(r[0], r[1], r[2], r[3]);
|
||||||
|
} else {
|
||||||
|
centerize(800, 600);
|
||||||
}
|
}
|
||||||
if (!handler.get_remote_id()) {
|
if (!handler.get_remote_id()) {
|
||||||
view.focus = $(#remote_id);
|
view.focus = $(#remote_id);
|
||||||
@@ -740,6 +699,10 @@ function checkConnectStatus() {
|
|||||||
key_confirmed = tmp[1];
|
key_confirmed = tmp[1];
|
||||||
app.update();
|
app.update();
|
||||||
}
|
}
|
||||||
|
if (tmp[2] && tmp[2] != my_id) {
|
||||||
|
stdout.println("id updated");
|
||||||
|
app.update();
|
||||||
|
}
|
||||||
tmp = handler.get_error();
|
tmp = handler.get_error();
|
||||||
if (system_error != tmp) {
|
if (system_error != tmp) {
|
||||||
system_error = tmp;
|
system_error = tmp;
|
||||||
@@ -752,7 +715,7 @@ function checkConnectStatus() {
|
|||||||
}
|
}
|
||||||
if (handler.recent_sessions_updated()) {
|
if (handler.recent_sessions_updated()) {
|
||||||
stdout.println("recent sessions updated");
|
stdout.println("recent sessions updated");
|
||||||
app.recent_sessions.update();
|
app.update();
|
||||||
}
|
}
|
||||||
checkConnectStatus();
|
checkConnectStatus();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ function self.ready() {
|
|||||||
class Install: Reactor.Component {
|
class Install: Reactor.Component {
|
||||||
function render() {
|
function render() {
|
||||||
return <div .content>
|
return <div .content>
|
||||||
<div style="font-size: 2em;">Installation</div>
|
<div style="font-size: 2em;">{translate('Installation')}</div>
|
||||||
<div style="margin: 2em 0;">Installation Path: <input|text disabled value={view.install_path()} /></div>
|
<div style="margin: 2em 0;">{translate('Installation Path')} {": "}<input|text disabled value={view.install_path()} /></div>
|
||||||
<div><button|checkbox #startmenu checked>Create start menu shortcuts</button></div>
|
<div><button|checkbox #startmenu checked>{translate('Create start menu shortcuts')}</button></div>
|
||||||
<div><button|checkbox #desktopicon checked>Create desktop icon</button></div>
|
<div><button|checkbox #desktopicon checked>{translate('Create desktop icon')}</button></div>
|
||||||
<div #aggrement .link style="margin-top: 2em;">End-user license agreement</div>
|
<div #aggrement .link style="margin-top: 2em;">{translate('End-user license agreement')}</div>
|
||||||
<div>By starting the installation, you accept the license agreement.</div>
|
<div>{translate('agreement_tip')}</div>
|
||||||
<div style="height: 1px; background: gray; margin-top: 1em" />
|
<div style="height: 1px; background: gray; margin-top: 1em" />
|
||||||
<div style="text-align: right;">
|
<div style="text-align: right;">
|
||||||
<progress style={"color:" + color} style="display: none" />
|
<progress style={"color:" + color} style="display: none" />
|
||||||
<button .button id="cancel" .outline style="margin-right: 2em;">Cancel</button>
|
<button .button id="cancel" .outline style="margin-right: 2em;">{translate('Cancel')}</button>
|
||||||
<button .button id="submit">Accept and Install</button>
|
<button .button id="submit">{translate('Accept and Install')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
@@ -42,4 +42,4 @@ class Install: Reactor.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(body).content(<Install />);
|
$(body).content(<Install />);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
var type, title, text, getParams, remember, retry, callback;
|
var type, title, text, getParams, remember, retry, callback, contentStyle;
|
||||||
|
var my_translate;
|
||||||
|
|
||||||
function updateParams(params) {
|
function updateParams(params) {
|
||||||
type = params.type;
|
type = params.type;
|
||||||
@@ -7,7 +8,10 @@ function updateParams(params) {
|
|||||||
getParams = params.getParams;
|
getParams = params.getParams;
|
||||||
remember = params.remember;
|
remember = params.remember;
|
||||||
callback = params.callback;
|
callback = params.callback;
|
||||||
|
my_translate = params.translate;
|
||||||
retry = params.retry;
|
retry = params.retry;
|
||||||
|
contentStyle = params.contentStyle;
|
||||||
|
try { text = translate_text(text); } catch (e) {}
|
||||||
if (retry > 0) {
|
if (retry > 0) {
|
||||||
self.timer(retry * 1000, function() {
|
self.timer(retry * 1000, function() {
|
||||||
view.close({ reconnect: true });
|
view.close({ reconnect: true });
|
||||||
@@ -15,39 +19,20 @@ function updateParams(params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function translate_text(text) {
|
||||||
|
if (text.indexOf('Failed') == 0 && text.indexOf(': ') > 0) {
|
||||||
|
var fds = text.split(': ');
|
||||||
|
for (var i = 0; i < fds.length; ++i) {
|
||||||
|
fds[i] = my_translate(fds[i]);
|
||||||
|
}
|
||||||
|
text = fds.join(': ');
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
var params = view.parameters;
|
var params = view.parameters;
|
||||||
updateParams(params);
|
updateParams(params);
|
||||||
|
|
||||||
var svg_eye_cross = <svg viewBox="0 -21 511.96 511">
|
|
||||||
<path d="m506.68 261.88c7.043-16.984 7.043-36.461 0-53.461-41.621-100.4-140.03-165.27-250.71-165.27-46.484 0-90.797 11.453-129.64 32.191l-68.605-68.609c-8.3438-8.3398-21.824-8.3398-30.168 0-8.3398 8.3398-8.3398 21.824 0 30.164l271.49 271.49 86.484 86.488 68.676 68.672c4.1797 4.1797 9.6406 6.2695 15.102 6.2695 5.4609 0 10.922-2.0898 15.082-6.25 8.3438-8.3398 8.3438-21.824 0-30.164l-62.145-62.145c36.633-27.883 66.094-65.109 84.438-109.38zm-293.91-100.1c12.648-7.5742 27.391-11.969 43.199-11.969 47.062 0 85.332 38.273 85.332 85.336 0 15.805-4.3945 30.547-11.969 43.199z"/>
|
|
||||||
<path d="m255.97 320.48c-47.062 0-85.336-38.273-85.336-85.332 0-3.0938 0.59766-6.0195 0.91797-9.0039l-106.15-106.16c-25.344 24.707-46.059 54.465-60.117 88.43-7.043 16.98-7.043 36.457 0 53.461 41.598 100.39 140.01 165.27 250.69 165.27 34.496 0 67.797-6.3164 98.559-18.027l-89.559-89.559c-2.9844 0.32031-5.9062 0.91797-9 0.91797z"/>
|
|
||||||
</svg>;
|
|
||||||
|
|
||||||
class Password: Reactor.Component {
|
|
||||||
this var visible = false;
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
return <div .password>
|
|
||||||
<input name="password" type={this.visible ? "text" : "password"} .outline-focus />
|
|
||||||
{this.visible ? svg_eye_cross : svg_eye}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
event click $(svg) {
|
|
||||||
var el = this.$(input);
|
|
||||||
var value = el.value;
|
|
||||||
var start = el.xcall(#selectionStart) || 0;
|
|
||||||
var end = el.xcall(#selectionEnd);
|
|
||||||
this.update({ visible: !this.visible });
|
|
||||||
self.timer(30ms, function() {
|
|
||||||
var el = this.$(input);
|
|
||||||
view.focus = el;
|
|
||||||
el.value = value;
|
|
||||||
el.xcall(#setSelection, start, end);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body;
|
var body;
|
||||||
|
|
||||||
class Body: Reactor.Component {
|
class Body: Reactor.Component {
|
||||||
@@ -74,9 +59,9 @@ class Body: Reactor.Component {
|
|||||||
function getInputPasswordContent() {
|
function getInputPasswordContent() {
|
||||||
var ts = remember ? { checked: true } : {};
|
var ts = remember ? { checked: true } : {};
|
||||||
return <div .form>
|
return <div .form>
|
||||||
<div>Please enter your password</div>
|
<div>{my_translate('Please enter your password')}</div>
|
||||||
<Password />
|
<PasswordComponent />
|
||||||
<div><button|checkbox(remember) {ts}>Remember password</button></div>
|
<div><button|checkbox(remember) {ts}>{my_translate('Remember password')}</button></div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,27 +101,27 @@ class Body: Reactor.Component {
|
|||||||
var me = this;
|
var me = this;
|
||||||
self.timer(1ms, function() {
|
self.timer(1ms, function() {
|
||||||
if (typeof content == "string")
|
if (typeof content == "string")
|
||||||
me.$(#content).html = content;
|
me.$(#content).html = my_translate(content);
|
||||||
else
|
else
|
||||||
me.$(#content).content(content);
|
me.$(#content).content(content);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div style="size: *">
|
<div style="size: *">
|
||||||
<header style={"height: 2em; background: " + color}>
|
<header style={"height: 2em; background: " + color}>
|
||||||
<caption role="window-caption">{title}</caption>
|
<caption role="window-caption">{my_translate(title)}</caption>
|
||||||
</header>
|
</header>
|
||||||
<div style="padding: 1em 2em; size: *;">
|
<div style="padding: 1em 2em; size: *;">
|
||||||
<div style="height: *; flow: horizontal">
|
<div style="height: *; flow: horizontal">
|
||||||
{icon && <div style="height: *; margin: * 0; padding-right: 2em;">{icon}</div>}
|
{icon && <div style="height: *; margin: * 0; padding-right: 2em;">{icon}</div>}
|
||||||
<div style="size: *; margin: * 0;" #content />
|
<div style={contentStyle || "size: *; margin: * 0;"} #content />
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: right;">
|
<div style="text-align: right;">
|
||||||
<span #error />
|
<span style="display:inline-block; max-width: 260px; font-size:12px;" #error />
|
||||||
{show_progress ? <progress style={"color:" + color} /> : ""}
|
<progress #progress style={"color:" + color + "; display: " + (show_progress ? "inline-block" : "none")} />
|
||||||
{hasCancel || hasRetry ? <button .button #cancel .outline>{hasRetry ? "OK" : "Cancel"}</button> : ""}
|
{hasCancel || hasRetry ? <button .button #cancel .outline>{my_translate(hasRetry ? "OK" : "Cancel")}</button> : ""}
|
||||||
{this.hasSkip() ? <button .button #skip .outline>Skip</button> : ""}
|
{this.hasSkip() ? <button .button #skip .outline>{my_translate('Skip')}</button> : ""}
|
||||||
{hasOk || hasRetry ? <button .button #submit>{hasRetry ? "Retry" : "OK"}</button> : ""}
|
{hasOk || hasRetry ? <button .button #submit>{my_translate(hasRetry ? "Retry" : "OK")}</button> : ""}
|
||||||
{hasClose ? <button .button #cancel .outline>Close</button> : ""}
|
{hasClose ? <button .button #cancel .outline>{my_translate('Close')}</button> : ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
@@ -149,6 +134,17 @@ class Body: Reactor.Component {
|
|||||||
|
|
||||||
$(body).content(<Body />);
|
$(body).content(<Body />);
|
||||||
|
|
||||||
|
function show_progress(show=1, err="") {
|
||||||
|
if (show == -1) {
|
||||||
|
view.close()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(#progress).style.set {
|
||||||
|
display: show ? "inline-block" : "none"
|
||||||
|
};
|
||||||
|
$(#error).text = err;
|
||||||
|
}
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
if ($(button#submit)) {
|
if ($(button#submit)) {
|
||||||
$(button#submit).sendEvent("click");
|
$(button#submit).sendEvent("click");
|
||||||
@@ -211,9 +207,12 @@ event click $(button#submit) {
|
|||||||
}
|
}
|
||||||
var values = getValues();
|
var values = getValues();
|
||||||
if (callback) {
|
if (callback) {
|
||||||
var err = callback(values);
|
var err = callback(values, show_progress);
|
||||||
|
if (err && !err.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
$(#error).text = err;
|
show_progress(false, err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,7 +234,7 @@ event keydown (evt) {
|
|||||||
|
|
||||||
function set_outline_focus() {
|
function set_outline_focus() {
|
||||||
self.timer(30ms, function() {
|
self.timer(30ms, function() {
|
||||||
var el = $(input.outline-focus);
|
var el = $(.outline-focus);
|
||||||
if (el) view.focus = el;
|
if (el) view.focus = el;
|
||||||
else {
|
else {
|
||||||
el = $(#submit);
|
el = $(#submit);
|
||||||
|
|||||||
@@ -21,17 +21,17 @@ class PortForward: Reactor.Component {
|
|||||||
});
|
});
|
||||||
return <div #file-transfer><section>
|
return <div #file-transfer><section>
|
||||||
{pfs.length ? <div style="background: green; color: white; text-align: center; padding: 0.5em;">
|
{pfs.length ? <div style="background: green; color: white; text-align: center; padding: 0.5em;">
|
||||||
<span style="font-size: 1.2em">Listening ...</span><br/>
|
<span style="font-size: 1.2em">{translate('Listening ...')}</span><br/>
|
||||||
<span style="font-size: 0.8em; color: #ddd">Don't close this window while you are using the tunnel</span>
|
<span style="font-size: 0.8em; color: #ddd">{translate('not_close_tcp_tip')}</span>
|
||||||
</div> : ""}
|
</div> : ""}
|
||||||
<table #port-forward>
|
<table #port-forward>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Local Port</th>
|
<th>{translate('Local Port')}</th>
|
||||||
<th style="width: 1em" />
|
<th style="width: 1em" />
|
||||||
<th>Remote Host</th>
|
<th>{translate('Remote Host')}</th>
|
||||||
<th>Remote Port</th>
|
<th>{translate('Remote Port')}</th>
|
||||||
{args.length ? "" : <th style="width: 6em">Action</th>}
|
{args.length ? "" : <th style="width: 6em">{translate('Action')}</th>}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody key={pfs.length}>
|
<tbody key={pfs.length}>
|
||||||
@@ -41,7 +41,7 @@ class PortForward: Reactor.Component {
|
|||||||
<td .right-arrow style="text-align: center">{svg_arrow}</td>
|
<td .right-arrow style="text-align: center">{svg_arrow}</td>
|
||||||
<td><input|text #remote-host novalue="localhost" /></td>
|
<td><input|text #remote-host novalue="localhost" /></td>
|
||||||
<td><input|number #remote-port /></td>
|
<td><input|number #remote-port /></td>
|
||||||
<td style="margin:0;"><button .button #add>Add</button></td>
|
<td style="margin:0;"><button .button #add>{translate('Add')}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
{pfs}
|
{pfs}
|
||||||
|
|||||||
333
src/ui/remote.rs
333
src/ui/remote.rs
@@ -49,13 +49,16 @@ fn get_key_state(key: enigo::Key) -> bool {
|
|||||||
ENIGO.lock().unwrap().get_key_state(key)
|
ENIGO.lock().unwrap().get_key_state(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static mut IS_IN: bool = false;
|
||||||
|
static mut KEYBOARD_HOOKED: bool = false;
|
||||||
|
static mut KEYBOARD_ENABLED: bool = true;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct HandlerInner {
|
pub struct HandlerInner {
|
||||||
element: Option<Element>,
|
element: Option<Element>,
|
||||||
sender: Option<mpsc::UnboundedSender<Data>>,
|
sender: Option<mpsc::UnboundedSender<Data>>,
|
||||||
thread: Option<std::thread::JoinHandle<()>>,
|
thread: Option<std::thread::JoinHandle<()>>,
|
||||||
close_state: HashMap<String, String>,
|
close_state: HashMap<String, String>,
|
||||||
last_down_key: Option<(String, i32, bool)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
@@ -65,7 +68,6 @@ pub struct Handler {
|
|||||||
id: String,
|
id: String,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||||
super_on: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Handler {
|
impl Deref for Handler {
|
||||||
@@ -147,6 +149,8 @@ impl sciter::EventHandler for Handler {
|
|||||||
fn get_id();
|
fn get_id();
|
||||||
fn get_default_pi();
|
fn get_default_pi();
|
||||||
fn get_option(String);
|
fn get_option(String);
|
||||||
|
fn t(String);
|
||||||
|
fn set_option(String, String);
|
||||||
fn save_close_state(String, String);
|
fn save_close_state(String, String);
|
||||||
fn is_file_transfer();
|
fn is_file_transfer();
|
||||||
fn is_port_forward();
|
fn is_port_forward();
|
||||||
@@ -154,11 +158,9 @@ impl sciter::EventHandler for Handler {
|
|||||||
fn login(String, bool);
|
fn login(String, bool);
|
||||||
fn new_rdp();
|
fn new_rdp();
|
||||||
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
|
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
|
||||||
fn key_down_or_up(bool, String, i32, bool, bool, bool, bool, bool);
|
fn enter();
|
||||||
|
fn leave();
|
||||||
fn ctrl_alt_del();
|
fn ctrl_alt_del();
|
||||||
fn ctrl_space();
|
|
||||||
fn alt_tab();
|
|
||||||
fn super_x();
|
|
||||||
fn transfer_file();
|
fn transfer_file();
|
||||||
fn tunnel();
|
fn tunnel();
|
||||||
fn lock_screen();
|
fn lock_screen();
|
||||||
@@ -218,6 +220,139 @@ impl Handler {
|
|||||||
me
|
me
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_keyboard_hook(&self) {
|
||||||
|
if self.is_port_forward() || self.is_file_transfer() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if unsafe { KEYBOARD_HOOKED } {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
KEYBOARD_HOOKED = true;
|
||||||
|
}
|
||||||
|
log::info!("keyboard hooked");
|
||||||
|
let mut me = self.clone();
|
||||||
|
let peer = self.peer_platform();
|
||||||
|
let is_win = peer == "Windows";
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
// This will block.
|
||||||
|
std::env::set_var("KEYBOARD_ONLY", "y"); // pass to rdev
|
||||||
|
use rdev::{EventType::*, *};
|
||||||
|
let func = move |evt: Event| {
|
||||||
|
if unsafe { !IS_IN || !KEYBOARD_ENABLED } {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (key, down) = match evt.event_type {
|
||||||
|
KeyPress(k) => (k, 1),
|
||||||
|
KeyRelease(k) => (k, 0),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let alt = get_key_state(enigo::Key::Alt);
|
||||||
|
let ctrl = get_key_state(enigo::Key::Control);
|
||||||
|
let shift = get_key_state(enigo::Key::Shift);
|
||||||
|
let command = get_key_state(enigo::Key::Meta);
|
||||||
|
let control_key = match key {
|
||||||
|
Key::Alt => Some(ControlKey::Alt),
|
||||||
|
Key::AltGr => Some(ControlKey::RAlt),
|
||||||
|
Key::Backspace => Some(ControlKey::Backspace),
|
||||||
|
Key::ControlLeft => Some(ControlKey::Control),
|
||||||
|
Key::ControlRight => Some(ControlKey::RControl),
|
||||||
|
Key::DownArrow => Some(ControlKey::DownArrow),
|
||||||
|
Key::Escape => Some(ControlKey::Escape),
|
||||||
|
Key::F1 => Some(ControlKey::F1),
|
||||||
|
Key::F10 => Some(ControlKey::F10),
|
||||||
|
Key::F11 => Some(ControlKey::F11),
|
||||||
|
Key::F12 => Some(ControlKey::F12),
|
||||||
|
Key::F2 => Some(ControlKey::F2),
|
||||||
|
Key::F3 => Some(ControlKey::F3),
|
||||||
|
Key::F4 => Some(ControlKey::F4),
|
||||||
|
Key::F5 => Some(ControlKey::F5),
|
||||||
|
Key::F6 => Some(ControlKey::F6),
|
||||||
|
Key::F7 => Some(ControlKey::F7),
|
||||||
|
Key::F8 => Some(ControlKey::F8),
|
||||||
|
Key::F9 => Some(ControlKey::F9),
|
||||||
|
Key::LeftArrow => Some(ControlKey::LeftArrow),
|
||||||
|
Key::MetaLeft => Some(ControlKey::Meta),
|
||||||
|
Key::MetaRight => Some(ControlKey::RWin),
|
||||||
|
Key::Return => Some(ControlKey::Return),
|
||||||
|
Key::RightArrow => Some(ControlKey::RightArrow),
|
||||||
|
Key::ShiftLeft => Some(ControlKey::Shift),
|
||||||
|
Key::ShiftRight => Some(ControlKey::RShift),
|
||||||
|
Key::Space => Some(ControlKey::Space),
|
||||||
|
Key::Tab => Some(ControlKey::Tab),
|
||||||
|
Key::UpArrow => Some(ControlKey::UpArrow),
|
||||||
|
Key::Delete => {
|
||||||
|
if is_win && ctrl && alt {
|
||||||
|
me.ctrl_alt_del();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(ControlKey::Delete)
|
||||||
|
}
|
||||||
|
Key::Apps => Some(ControlKey::Apps),
|
||||||
|
Key::Cancel => Some(ControlKey::Cancel),
|
||||||
|
Key::Clear => Some(ControlKey::Clear),
|
||||||
|
Key::Kana => Some(ControlKey::Kana),
|
||||||
|
Key::Hangul => Some(ControlKey::Hangul),
|
||||||
|
Key::Junja => Some(ControlKey::Junja),
|
||||||
|
Key::Final => Some(ControlKey::Final),
|
||||||
|
Key::Hanja => Some(ControlKey::Hanja),
|
||||||
|
Key::Hanji => Some(ControlKey::Hanja),
|
||||||
|
Key::Convert => Some(ControlKey::Convert),
|
||||||
|
Key::Print => Some(ControlKey::Print),
|
||||||
|
Key::Select => Some(ControlKey::Select),
|
||||||
|
Key::Execute => Some(ControlKey::Execute),
|
||||||
|
Key::PrintScreen => Some(ControlKey::Snapshot),
|
||||||
|
Key::Help => Some(ControlKey::Help),
|
||||||
|
Key::Sleep => Some(ControlKey::Sleep),
|
||||||
|
Key::Separator => Some(ControlKey::Separator),
|
||||||
|
Key::KpReturn => Some(ControlKey::NumpadEnter),
|
||||||
|
Key::Kp0 => Some(ControlKey::Numpad0),
|
||||||
|
Key::Kp1 => Some(ControlKey::Numpad1),
|
||||||
|
Key::Kp2 => Some(ControlKey::Numpad2),
|
||||||
|
Key::Kp3 => Some(ControlKey::Numpad3),
|
||||||
|
Key::Kp4 => Some(ControlKey::Numpad4),
|
||||||
|
Key::Kp5 => Some(ControlKey::Numpad5),
|
||||||
|
Key::Kp6 => Some(ControlKey::Numpad6),
|
||||||
|
Key::Kp7 => Some(ControlKey::Numpad7),
|
||||||
|
Key::Kp8 => Some(ControlKey::Numpad8),
|
||||||
|
Key::Kp9 => Some(ControlKey::Numpad9),
|
||||||
|
Key::KpDivide => Some(ControlKey::Divide),
|
||||||
|
Key::KpMultiply => Some(ControlKey::Subtract),
|
||||||
|
Key::KpDecimal => Some(ControlKey::Decimal),
|
||||||
|
Key::KpMinus => Some(ControlKey::Subtract),
|
||||||
|
Key::KpPlus => Some(ControlKey::Add),
|
||||||
|
Key::CapsLock | Key::NumLock | Key::ScrollLock => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let mut key_event = KeyEvent::new();
|
||||||
|
if let Some(k) = control_key {
|
||||||
|
key_event.set_control_key(k);
|
||||||
|
} else {
|
||||||
|
let chr = match evt.name {
|
||||||
|
Some(ref s) => s.chars().next().unwrap_or('\0'),
|
||||||
|
_ => '\0',
|
||||||
|
};
|
||||||
|
if chr != '\0' {
|
||||||
|
if chr == 'l' && is_win && command {
|
||||||
|
me.lock_screen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
key_event.set_chr(chr as _);
|
||||||
|
} else {
|
||||||
|
log::error!("Unknown key {:?}", evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
me.key_down_or_up(down, key_event, alt, ctrl, shift, command);
|
||||||
|
};
|
||||||
|
if let Err(error) = rdev::listen(func) {
|
||||||
|
log::error!("rdev: {:?}", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn get_view_style(&mut self) -> String {
|
fn get_view_style(&mut self) -> String {
|
||||||
return self.lc.read().unwrap().view_style.clone();
|
return self.lc.read().unwrap().view_style.clone();
|
||||||
}
|
}
|
||||||
@@ -287,6 +422,10 @@ impl Handler {
|
|||||||
self.lc.read().unwrap().remember
|
self.lc.read().unwrap().remember
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn t(&self, name: String) -> String {
|
||||||
|
crate::client::translate(name)
|
||||||
|
}
|
||||||
|
|
||||||
fn is_xfce(&self) -> bool {
|
fn is_xfce(&self) -> bool {
|
||||||
crate::platform::is_xfce()
|
crate::platform::is_xfce()
|
||||||
}
|
}
|
||||||
@@ -409,6 +548,10 @@ impl Handler {
|
|||||||
self.lc.read().unwrap().get_option(&k)
|
self.lc.read().unwrap().get_option(&k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_option(&self, k: String, v: String) {
|
||||||
|
self.lc.write().unwrap().set_option(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
fn save_close_state(&self, k: String, v: String) {
|
fn save_close_state(&self, k: String, v: String) {
|
||||||
self.write().unwrap().close_state.insert(k, v);
|
self.write().unwrap().close_state.insert(k, v);
|
||||||
}
|
}
|
||||||
@@ -535,7 +678,6 @@ impl Handler {
|
|||||||
fn reconnect(&mut self) {
|
fn reconnect(&mut self) {
|
||||||
let cloned = self.clone();
|
let cloned = self.clone();
|
||||||
let mut lock = self.write().unwrap();
|
let mut lock = self.write().unwrap();
|
||||||
lock.last_down_key.take();
|
|
||||||
lock.thread.take().map(|t| t.join());
|
lock.thread.take().map(|t| t.join());
|
||||||
lock.thread = Some(std::thread::spawn(move || {
|
lock.thread = Some(std::thread::spawn(move || {
|
||||||
io_loop(cloned);
|
io_loop(cloned);
|
||||||
@@ -629,6 +771,18 @@ impl Handler {
|
|||||||
self.send(Data::NewRDP);
|
self.send(Data::NewRDP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enter(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
IS_IN = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leave(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
IS_IN = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn send_mouse(
|
fn send_mouse(
|
||||||
&mut self,
|
&mut self,
|
||||||
mask: i32,
|
mask: i32,
|
||||||
@@ -668,7 +822,6 @@ impl Handler {
|
|||||||
let evt_type = mask & 0x7;
|
let evt_type = mask & 0x7;
|
||||||
if buttons == 1 && evt_type == 1 && ctrl && self.peer_platform() != "Mac OS" {
|
if buttons == 1 && evt_type == 1 && ctrl && self.peer_platform() != "Mac OS" {
|
||||||
self.send_mouse((1 << 3 | 2) as _, x, y, alt, ctrl, shift, command);
|
self.send_mouse((1 << 3 | 2) as _, x, y, alt, ctrl, shift, command);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -754,6 +907,7 @@ impl Handler {
|
|||||||
65300 => key_event.set_control_key(ControlKey::Scroll),
|
65300 => key_event.set_control_key(ControlKey::Scroll),
|
||||||
65421 => key_event.set_control_key(ControlKey::NumpadEnter), // numpad enter
|
65421 => key_event.set_control_key(ControlKey::NumpadEnter), // numpad enter
|
||||||
65407 => key_event.set_control_key(ControlKey::NumLock),
|
65407 => key_event.set_control_key(ControlKey::NumLock),
|
||||||
|
65515 => key_event.set_control_key(ControlKey::Meta),
|
||||||
65516 => key_event.set_control_key(ControlKey::RWin),
|
65516 => key_event.set_control_key(ControlKey::RWin),
|
||||||
65513 => key_event.set_control_key(ControlKey::Alt),
|
65513 => key_event.set_control_key(ControlKey::Alt),
|
||||||
65514 => key_event.set_control_key(ControlKey::RAlt),
|
65514 => key_event.set_control_key(ControlKey::RAlt),
|
||||||
@@ -811,32 +965,20 @@ impl Handler {
|
|||||||
|
|
||||||
fn ctrl_alt_del(&mut self) {
|
fn ctrl_alt_del(&mut self) {
|
||||||
if self.peer_platform() == "Windows" {
|
if self.peer_platform() == "Windows" {
|
||||||
let del = "CTRL_ALT_DEL".to_owned();
|
let mut key_event = KeyEvent::new();
|
||||||
self.key_down_or_up(1, del, 0, false, false, false, false, false);
|
key_event.set_control_key(ControlKey::CtrlAltDel);
|
||||||
|
self.key_down_or_up(1, key_event, false, false, false, false);
|
||||||
} else {
|
} else {
|
||||||
let del = "VK_DELETE".to_owned();
|
let mut key_event = KeyEvent::new();
|
||||||
self.key_down_or_up(1, del.clone(), 0, true, true, false, false, false);
|
key_event.set_control_key(ControlKey::Delete);
|
||||||
self.key_down_or_up(0, del, 0, true, true, false, false, false);
|
self.key_down_or_up(3, key_event, true, true, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn super_x(&mut self) {
|
|
||||||
self.super_on = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ctrl_space(&mut self) {
|
|
||||||
let key = "VK_SPACE".to_owned();
|
|
||||||
self.key_down_or_up(3, key, 0, false, true, false, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alt_tab(&mut self) {
|
|
||||||
let key = "VK_TAB".to_owned();
|
|
||||||
self.key_down_or_up(3, key, 0, true, false, false, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lock_screen(&mut self) {
|
fn lock_screen(&mut self) {
|
||||||
let lock = "LOCK_SCREEN".to_owned();
|
let mut key_event = KeyEvent::new();
|
||||||
self.key_down_or_up(1, lock, 0, false, false, false, false, false);
|
key_event.set_control_key(ControlKey::LockScreen);
|
||||||
|
self.key_down_or_up(1, key_event, false, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer_file(&mut self) {
|
fn transfer_file(&mut self) {
|
||||||
@@ -858,104 +1000,55 @@ impl Handler {
|
|||||||
fn key_down_or_up(
|
fn key_down_or_up(
|
||||||
&mut self,
|
&mut self,
|
||||||
down_or_up: i32,
|
down_or_up: i32,
|
||||||
name: String,
|
evt: KeyEvent,
|
||||||
code: i32,
|
|
||||||
alt: bool,
|
alt: bool,
|
||||||
ctrl: bool,
|
ctrl: bool,
|
||||||
shift: bool,
|
shift: bool,
|
||||||
command: bool,
|
command: bool,
|
||||||
extended: bool,
|
|
||||||
) {
|
) {
|
||||||
// extended: e.g. ctrl key on right side, https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-keybd_event
|
let mut key_event = evt;
|
||||||
// not found api of osx and xdo
|
|
||||||
log::debug!(
|
|
||||||
"{:?} {} {} {} {} {} {} {} {}",
|
|
||||||
std::time::SystemTime::now(),
|
|
||||||
down_or_up,
|
|
||||||
name,
|
|
||||||
code,
|
|
||||||
alt,
|
|
||||||
ctrl,
|
|
||||||
shift,
|
|
||||||
command,
|
|
||||||
extended,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut command = command;
|
if alt
|
||||||
if self.super_on {
|
&& !crate::is_control_key(&key_event, &ControlKey::Alt)
|
||||||
command = true;
|
&& !crate::is_control_key(&key_event, &ControlKey::RAlt)
|
||||||
|
{
|
||||||
|
key_event.modifiers.push(ControlKey::Alt.into());
|
||||||
}
|
}
|
||||||
|
if shift
|
||||||
if down_or_up == 0 {
|
&& !crate::is_control_key(&key_event, &ControlKey::Shift)
|
||||||
self.super_on = false;
|
&& !crate::is_control_key(&key_event, &ControlKey::RShift)
|
||||||
|
{
|
||||||
|
key_event.modifiers.push(ControlKey::Shift.into());
|
||||||
}
|
}
|
||||||
|
if ctrl
|
||||||
let mut name = name;
|
&& !crate::is_control_key(&key_event, &ControlKey::Control)
|
||||||
#[cfg(target_os = "linux")]
|
&& !crate::is_control_key(&key_event, &ControlKey::RControl)
|
||||||
if code == 65383 {
|
{
|
||||||
// VK_MENU
|
key_event.modifiers.push(ControlKey::Control.into());
|
||||||
name = "Apps".to_owned();
|
|
||||||
}
|
}
|
||||||
|
if command
|
||||||
if extended {
|
&& !crate::is_control_key(&key_event, &ControlKey::Meta)
|
||||||
match name.as_ref() {
|
&& !crate::is_control_key(&key_event, &ControlKey::RWin)
|
||||||
"VK_CONTROL" => name = "RControl".to_owned(),
|
{
|
||||||
"VK_MENU" => name = "RAlt".to_owned(),
|
key_event.modifiers.push(ControlKey::Meta.into());
|
||||||
"VK_SHIFT" => name = "RShift".to_owned(),
|
}
|
||||||
_ => {}
|
if get_key_state(enigo::Key::CapsLock) {
|
||||||
|
key_event.modifiers.push(ControlKey::CapsLock.into());
|
||||||
|
}
|
||||||
|
if self.peer_platform() != "Mac OS" {
|
||||||
|
if get_key_state(enigo::Key::NumLock) && common::valid_for_numlock(&key_event) {
|
||||||
|
key_event.modifiers.push(ControlKey::NumLock.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if down_or_up == 1 {
|
||||||
if let Some(mut key_event) = self.get_key_event(down_or_up, &name, code) {
|
key_event.down = true;
|
||||||
if alt
|
} else if down_or_up == 3 {
|
||||||
&& !crate::is_control_key(&key_event, &ControlKey::Alt)
|
key_event.press = true;
|
||||||
&& !crate::is_control_key(&key_event, &ControlKey::RAlt)
|
|
||||||
{
|
|
||||||
key_event.modifiers.push(ControlKey::Alt.into());
|
|
||||||
}
|
|
||||||
if shift
|
|
||||||
&& !crate::is_control_key(&key_event, &ControlKey::Shift)
|
|
||||||
&& !crate::is_control_key(&key_event, &ControlKey::RShift)
|
|
||||||
{
|
|
||||||
key_event.modifiers.push(ControlKey::Shift.into());
|
|
||||||
}
|
|
||||||
if ctrl
|
|
||||||
&& !crate::is_control_key(&key_event, &ControlKey::Control)
|
|
||||||
&& !crate::is_control_key(&key_event, &ControlKey::RControl)
|
|
||||||
{
|
|
||||||
key_event.modifiers.push(ControlKey::Control.into());
|
|
||||||
}
|
|
||||||
if command
|
|
||||||
&& !crate::is_control_key(&key_event, &ControlKey::Meta)
|
|
||||||
&& !crate::is_control_key(&key_event, &ControlKey::RWin)
|
|
||||||
{
|
|
||||||
key_event.modifiers.push(ControlKey::Meta.into());
|
|
||||||
}
|
|
||||||
if crate::is_control_key(&key_event, &ControlKey::CapsLock) {
|
|
||||||
return;
|
|
||||||
} else if get_key_state(enigo::Key::CapsLock) && common::valid_for_capslock(&key_event)
|
|
||||||
{
|
|
||||||
key_event.modifiers.push(ControlKey::CapsLock.into());
|
|
||||||
}
|
|
||||||
if self.peer_platform() != "Mac OS" {
|
|
||||||
if crate::is_control_key(&key_event, &ControlKey::NumLock) {
|
|
||||||
return;
|
|
||||||
} else if get_key_state(enigo::Key::NumLock)
|
|
||||||
&& common::valid_for_numlock(&key_event)
|
|
||||||
{
|
|
||||||
key_event.modifiers.push(ControlKey::NumLock.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if down_or_up == 1 {
|
|
||||||
key_event.down = true;
|
|
||||||
} else if down_or_up == 3 {
|
|
||||||
key_event.press = true;
|
|
||||||
}
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_key_event(key_event);
|
|
||||||
log::debug!("{:?}", msg_out);
|
|
||||||
self.send(Data::Message(msg_out));
|
|
||||||
}
|
}
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
msg_out.set_key_event(key_event);
|
||||||
|
log::debug!("{:?}", msg_out);
|
||||||
|
self.send(Data::Message(msg_out));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -1132,6 +1225,9 @@ impl Remote {
|
|||||||
};
|
};
|
||||||
match Client::start(&self.handler.id, conn_type).await {
|
match Client::start(&self.handler.id, conn_type).await {
|
||||||
Ok((mut peer, direct)) => {
|
Ok((mut peer, direct)) => {
|
||||||
|
unsafe {
|
||||||
|
KEYBOARD_ENABLED = true;
|
||||||
|
}
|
||||||
self.handler
|
self.handler
|
||||||
.call("setConnectionType", &make_args!(peer.is_secured(), direct));
|
.call("setConnectionType", &make_args!(peer.is_secured(), direct));
|
||||||
loop {
|
loop {
|
||||||
@@ -1191,6 +1287,9 @@ impl Remote {
|
|||||||
if let Some(stop) = stop_clipboard {
|
if let Some(stop) = stop_clipboard {
|
||||||
stop.send(()).ok();
|
stop.send(()).ok();
|
||||||
}
|
}
|
||||||
|
unsafe {
|
||||||
|
KEYBOARD_ENABLED = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_job_status(&mut self, id: i32, file_num: i32, err: Option<String>) {
|
fn handle_job_status(&mut self, id: i32, file_num: i32, err: Option<String>) {
|
||||||
@@ -1586,6 +1685,9 @@ impl Remote {
|
|||||||
log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
|
log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
|
||||||
match p.permission.enum_value_or_default() {
|
match p.permission.enum_value_or_default() {
|
||||||
Permission::Keyboard => {
|
Permission::Keyboard => {
|
||||||
|
unsafe {
|
||||||
|
KEYBOARD_ENABLED = p.enabled;
|
||||||
|
}
|
||||||
self.handler
|
self.handler
|
||||||
.call("setPermission", &make_args!("keyboard", p.enabled));
|
.call("setPermission", &make_args!("keyboard", p.enabled));
|
||||||
}
|
}
|
||||||
@@ -1738,6 +1840,7 @@ impl Interface for Handler {
|
|||||||
crate::platform::windows::add_recent_document(&path);
|
crate::platform::windows::add_recent_document(&path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.start_keyboard_hook();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_hash(&mut self, hash: Hash, peer: &mut Stream) {
|
async fn handle_hash(&mut self, hash: Hash, peer: &mut Stream) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
var cursor_img = $(img#cursor);
|
var cursor_img = $(img#cursor);
|
||||||
var last_key_time = 0;
|
|
||||||
is_file_transfer = handler.is_file_transfer();
|
is_file_transfer = handler.is_file_transfer();
|
||||||
var is_port_forward = handler.is_port_forward();
|
var is_port_forward = handler.is_port_forward();
|
||||||
var display_width = 0;
|
var display_width = 0;
|
||||||
@@ -72,46 +71,14 @@ function adaptDisplay() {
|
|||||||
// https://sciter.com/docs/content/sciter/Event.htm
|
// https://sciter.com/docs/content/sciter/Event.htm
|
||||||
|
|
||||||
var entered = false;
|
var entered = false;
|
||||||
|
if (!is_file_transfer && !is_port_forward) {
|
||||||
var keymap = {};
|
self.onKey = function(evt) {
|
||||||
for (var (k, v) in Event) {
|
if (!entered) return false;
|
||||||
k = k + ""
|
// so that arrow key not move scrollbar
|
||||||
if (k[0] == "V" && k[1] == "K") {
|
return true;
|
||||||
keymap[v] = k;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VK_ENTER = VK_RETURN
|
|
||||||
// somehow, handler.onKey and view.onKey not working
|
|
||||||
function self.onKey(evt) {
|
|
||||||
last_key_time = getTime();
|
|
||||||
if (is_file_transfer || is_port_forward) return false;
|
|
||||||
if (!entered) return false;
|
|
||||||
if (!keyboard_enabled) return false;
|
|
||||||
switch (evt.type) {
|
|
||||||
case Event.KEY_DOWN:
|
|
||||||
handler.key_down_or_up(1, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
|
||||||
if (is_osx && evt.commandKey) {
|
|
||||||
handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Event.KEY_UP:
|
|
||||||
handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
|
||||||
break;
|
|
||||||
case Event.KEY_CHAR:
|
|
||||||
// the keypress event is fired when the element receives character value. Event.keyCode is a UNICODE code point of the character
|
|
||||||
handler.key_down_or_up(2, "", evt.keyCode, evt.altKey,
|
|
||||||
evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var wait_window_toolbar = false;
|
var wait_window_toolbar = false;
|
||||||
var last_mouse_mask;
|
var last_mouse_mask;
|
||||||
var acc_wheel_delta_x = 0;
|
var acc_wheel_delta_x = 0;
|
||||||
@@ -173,10 +140,14 @@ function handler.onMouse(evt)
|
|||||||
wait_window_toolbar = true;
|
wait_window_toolbar = true;
|
||||||
self.timer(300ms, function() {
|
self.timer(300ms, function() {
|
||||||
if (!wait_window_toolbar) return;
|
if (!wait_window_toolbar) return;
|
||||||
|
var extra = 0;
|
||||||
|
// workaround for stupid Sciter, without this, click
|
||||||
|
// event not triggered on top part of buttons on toolbar
|
||||||
|
if (is_osx) extra = 10;
|
||||||
if (view.windowState == View.WINDOW_FULL_SCREEN) {
|
if (view.windowState == View.WINDOW_FULL_SCREEN) {
|
||||||
$(header).style.set {
|
$(header).style.set {
|
||||||
display: "block",
|
display: "block",
|
||||||
padding: (2 * workarea_offset) + "px 0 0 0",
|
padding: (2 * workarea_offset + extra) + "px 0 0 0",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
wait_window_toolbar = false;
|
wait_window_toolbar = false;
|
||||||
@@ -280,10 +251,12 @@ function handler.onMouse(evt)
|
|||||||
case Event.MOUSE_ENTER:
|
case Event.MOUSE_ENTER:
|
||||||
entered = true;
|
entered = true;
|
||||||
stdout.println("enter");
|
stdout.println("enter");
|
||||||
|
handler.enter();
|
||||||
return keyboard_enabled;
|
return keyboard_enabled;
|
||||||
case Event.MOUSE_LEAVE:
|
case Event.MOUSE_LEAVE:
|
||||||
entered = false;
|
entered = false;
|
||||||
stdout.println("leave");
|
stdout.println("leave");
|
||||||
|
handler.leave();
|
||||||
return keyboard_enabled;
|
return keyboard_enabled;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -396,7 +369,7 @@ function self.ready() {
|
|||||||
var h = 640;
|
var h = 640;
|
||||||
if (is_file_transfer || is_port_forward) {
|
if (is_file_transfer || is_port_forward) {
|
||||||
var r = handler.get_size();
|
var r = handler.get_size();
|
||||||
if (r[0] > 0) {
|
if (isReasonableSize(r) && r[2] > 0) {
|
||||||
view.move(r[0], r[1], r[2], r[3]);
|
view.move(r[0], r[1], r[2], r[3]);
|
||||||
} else {
|
} else {
|
||||||
centerize(w, h);
|
centerize(w, h);
|
||||||
@@ -418,7 +391,7 @@ handler.adaptSize = function() {
|
|||||||
var (fx, fy, fw, fh) = view.screenBox(#frame, #rectw);
|
var (fx, fy, fw, fh) = view.screenBox(#frame, #rectw);
|
||||||
if (is_osx) workarea_offset = sy;
|
if (is_osx) workarea_offset = sy;
|
||||||
var r = handler.get_size();
|
var r = handler.get_size();
|
||||||
if (r[2] > 0) {
|
if (isReasonableSize(r) && r[2] > 0) {
|
||||||
if (r[2] >= fw && r[3] >= fh && !is_linux) {
|
if (r[2] >= fw && r[3] >= fh && !is_linux) {
|
||||||
view.windowState = View.WINDOW_FULL_SCREEN;
|
view.windowState = View.WINDOW_FULL_SCREEN;
|
||||||
stdout.println("Initialize to full screen");
|
stdout.println("Initialize to full screen");
|
||||||
|
|||||||
Reference in New Issue
Block a user