mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-18 15:53:00 +08:00
Merge branch 'master' into lan_discovery
This commit is contained in:
commit
dfeb9a29c1
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__
|
||||
src/version.rs
|
||||
*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
|
||||
|
||||
[dependencies]
|
||||
whoami = "1.1"
|
||||
whoami = "1.2"
|
||||
scrap = { path = "libs/scrap" }
|
||||
hbb_common = { path = "libs/hbb_common" }
|
||||
socket_cs = { path = "libs/socket_cs" }
|
||||
enigo = { path = "libs/enigo" }
|
||||
sys-locale = "0.1"
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
cfg-if = "1.0"
|
||||
lazy_static = "1.4"
|
||||
sha2 = "0.9"
|
||||
sha2 = "0.10"
|
||||
repng = "0.2"
|
||||
libc = "0.2"
|
||||
parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" }
|
||||
flexi_logger = "0.17"
|
||||
flexi_logger = "0.22"
|
||||
runas = "0.2"
|
||||
magnum-opus = { git = "https://github.com/open-trade/magnum-opus" }
|
||||
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 }
|
||||
async-trait = "0.1"
|
||||
crc32fast = "1.2"
|
||||
crc32fast = "1.3"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
clap = "2.33"
|
||||
clap = "2.34"
|
||||
rpassword = "5.0"
|
||||
base64 = "0.13"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android")))'.dependencies]
|
||||
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" }
|
||||
ctrlc = "3.2"
|
||||
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]
|
||||
#systray = { git = "https://github.com/open-trade/systray-rs" }
|
||||
systray = { git = "https://github.com/liyue201/systray-rs" }
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
winreg = "0.7"
|
||||
windows-service = { git = 'https://github.com/mullvad/windows-service-rs.git' }
|
||||
winreg = "0.10"
|
||||
windows-service = "0.4"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2"
|
||||
@ -68,15 +72,15 @@ core-foundation = "0.9"
|
||||
core-graphics = "0.22"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libpulse-simple-binding = "2.16"
|
||||
libpulse-binding = "2.16"
|
||||
libpulse-simple-binding = "2.24"
|
||||
libpulse-binding = "2.25"
|
||||
rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" }
|
||||
|
||||
[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]
|
||||
android_logger = "0.9"
|
||||
android_logger = "0.10"
|
||||
|
||||
[workspace]
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
|
@ -6,7 +6,7 @@
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-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>
|
||||
|
||||
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
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
|
@ -6,7 +6,7 @@ fn main() {
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
let mut enigo = Enigo::new();
|
||||
|
||||
enigo.key_down(Key::Layout('a'));
|
||||
enigo.key_down(Key::Layout('a')).ok();
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
enigo.key_up(Key::Layout('a'));
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ fn main() {
|
||||
enigo.key_sequence("Hello World! here is a lot of text ❤️");
|
||||
|
||||
// select all
|
||||
enigo.key_down(Key::Control);
|
||||
enigo.key_down(Key::Control).ok();
|
||||
enigo.key_click(Key::Layout('a'));
|
||||
enigo.key_up(Key::Control);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ fn main() {
|
||||
enigo.mouse_move_to(500, 200);
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_down(MouseButton::Left);
|
||||
enigo.mouse_down(MouseButton::Left).ok();
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_move_relative(100, 100);
|
||||
|
@ -16,7 +16,7 @@ fn main() {
|
||||
println!("{:?}", time);
|
||||
|
||||
// select all
|
||||
enigo.key_down(Key::Control);
|
||||
enigo.key_down(Key::Control).ok();
|
||||
enigo.key_click(Key::Layout('a'));
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
protobuf = { version = "3.0.0-pre", git = "https://github.com/stepancheg/rust-protobuf" }
|
||||
tokio = { version = "1.10", features = ["full"] }
|
||||
protobuf = "3.0.0-alpha.2"
|
||||
tokio = { version = "1.15", features = ["full"] }
|
||||
tokio-util = { version = "0.6", features = ["full"] }
|
||||
futures = "0.3"
|
||||
bytes = "1.0"
|
||||
bytes = "1.1"
|
||||
log = "0.4"
|
||||
env_logger = "0.9"
|
||||
socket2 = { version = "0.3", features = ["reuseport"] }
|
||||
zstd = "0.9"
|
||||
quinn = {version = "0.6", optional = true }
|
||||
quinn = {version = "0.8", optional = true }
|
||||
anyhow = "1.0"
|
||||
futures-util = "0.3"
|
||||
directories-next = "2.0"
|
||||
@ -28,6 +28,8 @@ confy = { git = "https://github.com/open-trade/confy" }
|
||||
dirs-next = "2.0"
|
||||
filetime = "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]
|
||||
mac_address = "1.1"
|
||||
@ -36,7 +38,7 @@ mac_address = "1.1"
|
||||
quic = ["quinn"]
|
||||
|
||||
[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]
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
|
@ -66,6 +66,7 @@ message MouseEvent {
|
||||
}
|
||||
|
||||
enum ControlKey {
|
||||
Unknown = 0;
|
||||
Alt = 1;
|
||||
Backspace = 2;
|
||||
CapsLock = 3;
|
||||
@ -342,7 +343,6 @@ message PublicKey {
|
||||
|
||||
message SignedId {
|
||||
bytes id = 1;
|
||||
bytes pk = 2;
|
||||
}
|
||||
|
||||
message AudioFormat {
|
||||
|
@ -6,14 +6,13 @@ use sodiumoxide::crypto::sign;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
net::SocketAddr,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
pub const APP_NAME: &str = "RustDesk";
|
||||
pub const BIND_INTERFACE: &str = "0.0.0.0";
|
||||
pub const RENDEZVOUS_TIMEOUT: u64 = 12_000;
|
||||
pub const CONNECT_TIMEOUT: u64 = 18_000;
|
||||
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 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)]
|
||||
pub struct Config {
|
||||
@ -73,6 +76,16 @@ pub struct Config {
|
||||
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
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct Config2 {
|
||||
@ -87,6 +100,9 @@ pub struct Config2 {
|
||||
#[serde(default)]
|
||||
serial: i32,
|
||||
|
||||
#[serde(default)]
|
||||
socks: Option<Socks5Server>,
|
||||
|
||||
// the other scalar value must before this
|
||||
#[serde(default)]
|
||||
pub options: HashMap<String, String>,
|
||||
@ -276,8 +292,8 @@ impl Config {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
pub fn log_path() -> PathBuf {
|
||||
#[allow(unreachable_code)]
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(path) = dirs_next::home_dir().as_mut() {
|
||||
@ -329,10 +345,10 @@ impl Config {
|
||||
|
||||
#[inline]
|
||||
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");
|
||||
if rendezvous_server.is_empty() {
|
||||
rendezvous_server = CONFIG2.write().unwrap().rendezvous_server.clone();
|
||||
@ -346,11 +362,7 @@ impl Config {
|
||||
if !rendezvous_server.contains(":") {
|
||||
rendezvous_server = format!("{}:{}", rendezvous_server, RENDEZVOUS_PORT);
|
||||
}
|
||||
if let Ok(addr) = crate::to_socket_addr(&rendezvous_server) {
|
||||
addr
|
||||
} else {
|
||||
Self::get_any_listen_addr()
|
||||
}
|
||||
rendezvous_server
|
||||
}
|
||||
|
||||
pub fn get_rendezvous_servers() -> Vec<String> {
|
||||
@ -490,6 +502,9 @@ impl Config {
|
||||
|
||||
pub fn set_key_pair(pair: (Vec<u8>, Vec<u8>)) {
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if config.key_pair == pair {
|
||||
return;
|
||||
}
|
||||
config.key_pair = pair;
|
||||
config.store();
|
||||
}
|
||||
@ -522,6 +537,9 @@ impl Config {
|
||||
|
||||
pub fn set_options(v: HashMap<String, String>) {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
if config.options == v {
|
||||
return;
|
||||
}
|
||||
config.options = v;
|
||||
config.store();
|
||||
}
|
||||
@ -621,6 +639,26 @@ impl Config {
|
||||
pub fn get_remote_id() -> String {
|
||||
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";
|
||||
@ -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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -13,12 +13,13 @@ pub use protobuf;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufRead},
|
||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs},
|
||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
path::Path,
|
||||
time::{self, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
pub use tokio;
|
||||
pub use tokio_util;
|
||||
pub mod socket_client;
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
||||
pub use env_logger;
|
||||
@ -30,7 +31,11 @@ pub use anyhow::{self, bail};
|
||||
pub use futures_util;
|
||||
pub mod config;
|
||||
pub mod fs;
|
||||
pub use regex;
|
||||
pub use sodiumoxide;
|
||||
pub use tokio_socks;
|
||||
pub use tokio_socks::IntoTargetAddr;
|
||||
pub use tokio_socks::TargetAddr;
|
||||
|
||||
#[cfg(feature = "quic")]
|
||||
pub type Stream = quic::Connection;
|
||||
@ -151,14 +156,6 @@ pub fn get_version_from_url(url: &str) -> String {
|
||||
"".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() {
|
||||
let mut file = File::create("./src/version.rs").unwrap();
|
||||
for line in read_lines("Cargo.toml").unwrap() {
|
||||
@ -183,6 +180,14 @@ where
|
||||
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)]
|
||||
mod tests {
|
||||
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 sodiumoxide::crypto::secretbox::{self, Key, Nonce};
|
||||
use std::{
|
||||
io::{Error, ErrorKind},
|
||||
io::{self, Error, ErrorKind},
|
||||
net::SocketAddr,
|
||||
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;
|
||||
|
||||
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 {
|
||||
type Target = Framed<TcpStream, BytesCodec>;
|
||||
type Target = Framed<DynTcpStream, BytesCodec>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&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> {
|
||||
let socket = match addr {
|
||||
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 {
|
||||
pub async fn new<T: ToSocketAddrs, T2: ToSocketAddrs>(
|
||||
remote_addr: T,
|
||||
pub async fn new<T1: ToSocketAddrs, T2: ToSocketAddrs>(
|
||||
remote_addr: T1,
|
||||
local_addr: T2,
|
||||
ms_timeout: u64,
|
||||
) -> ResultType<Self> {
|
||||
@ -56,23 +85,86 @@ impl FramedStream {
|
||||
new_socket(local_addr, true)?.connect(remote_addr),
|
||||
)
|
||||
.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");
|
||||
}
|
||||
|
||||
pub fn from(stream: TcpStream) -> Self {
|
||||
Self(Framed::new(stream, BytesCodec::new()), None)
|
||||
pub async fn connect<'a, 't, P, T1, T2>(
|
||||
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) {
|
||||
self.0.codec_mut().set_raw();
|
||||
self.1 = None;
|
||||
self.2 = None;
|
||||
}
|
||||
|
||||
pub fn is_secured(&self) -> bool {
|
||||
self.1.is_some()
|
||||
self.2.is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -83,24 +175,29 @@ impl FramedStream {
|
||||
#[inline]
|
||||
pub async fn send_raw(&mut self, msg: Vec<u8>) -> ResultType<()> {
|
||||
let mut msg = msg;
|
||||
if let Some(key) = self.1.as_mut() {
|
||||
if let Some(key) = self.2.as_mut() {
|
||||
key.1 += 1;
|
||||
let nonce = Self::get_nonce(key.1);
|
||||
msg = secretbox::seal(&msg, &nonce, &key.0);
|
||||
}
|
||||
self.0.send(bytes::Bytes::from(msg)).await?;
|
||||
self.send_bytes(bytes::Bytes::from(msg)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn next(&mut self) -> Option<Result<BytesMut, Error>> {
|
||||
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() {
|
||||
key.2 += 1;
|
||||
let nonce = Self::get_nonce(key.2);
|
||||
@ -128,7 +225,7 @@ impl FramedStream {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -152,3 +249,35 @@ pub async fn new_listener<T: ToSocketAddrs>(addr: T, reuse: bool) -> ResultType<
|
||||
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 bytes::BytesMut;
|
||||
use anyhow::anyhow;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use protobuf::Message;
|
||||
use socket2::{Domain, Socket, Type};
|
||||
use std::{
|
||||
io::Error,
|
||||
net::SocketAddr,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use tokio::{net::ToSocketAddrs, net::UdpSocket};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::{ToSocketAddrs, UdpSocket};
|
||||
use tokio_socks::{udp::Socks5UdpFramed, IntoTargetAddr, TargetAddr, ToProxyAddrs};
|
||||
use tokio_util::{codec::BytesCodec, udp::UdpFramed};
|
||||
|
||||
pub struct FramedSocket(UdpFramed<BytesCodec>);
|
||||
|
||||
impl Deref for FramedSocket {
|
||||
type Target = UdpFramed<BytesCodec>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
pub enum FramedSocket {
|
||||
Direct(UdpFramed<BytesCodec>),
|
||||
ProxySocks(Socks5UdpFramed),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
impl DerefMut for FramedSocket {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FramedSocket {
|
||||
pub async fn new<T: ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
||||
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)]
|
||||
pub async fn new_reuse<T: std::net::ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
return Ok(Self(UdpFramed::new(
|
||||
UdpSocket::from_std(new_socket(addr, true)?.into_udp_socket())?,
|
||||
let socket = new_socket(addr, true)?.into_udp_socket();
|
||||
return Ok(Self::Direct(UdpFramed::new(
|
||||
UdpSocket::from_std(socket)?,
|
||||
BytesCodec::new(),
|
||||
)));
|
||||
}
|
||||
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]
|
||||
pub async fn send(&mut self, msg: &impl Message, addr: SocketAddr) -> ResultType<()> {
|
||||
self.0
|
||||
.send((bytes::Bytes::from(msg.write_to_bytes().unwrap()), addr))
|
||||
.await?;
|
||||
pub async fn send(
|
||||
&mut self,
|
||||
msg: &impl Message,
|
||||
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(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn send_raw(&mut self, msg: &'static [u8], addr: SocketAddr) -> ResultType<()> {
|
||||
self.0.send((bytes::Bytes::from(msg), addr)).await?;
|
||||
Ok(())
|
||||
pub async fn next(&mut self) -> Option<ResultType<(BytesMut, TargetAddr<'static>)>> {
|
||||
match self {
|
||||
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]
|
||||
pub async fn next(&mut self) -> Option<Result<(BytesMut, SocketAddr), Error>> {
|
||||
self.0.next().await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn next_timeout(&mut self, ms: u64) -> Option<Result<(BytesMut, SocketAddr), Error>> {
|
||||
pub async fn next_timeout(
|
||||
&mut self,
|
||||
ms: u64,
|
||||
) -> Option<ResultType<(BytesMut, TargetAddr<'static>)>> {
|
||||
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
|
||||
} else {
|
||||
|
@ -43,7 +43,6 @@ struct Args {
|
||||
flag_time: Option<u64>,
|
||||
flag_fps: u64,
|
||||
flag_bv: u32,
|
||||
flag_ba: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
|
@ -9,8 +9,8 @@ extern "C" {
|
||||
src_stride_argb: c_int,
|
||||
dst_argb: *mut u8,
|
||||
dst_stride_argb: c_int,
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
src_width: c_int,
|
||||
src_height: c_int,
|
||||
mode: c_int,
|
||||
) -> c_int;
|
||||
|
||||
|
@ -3,32 +3,20 @@ pub mod gdi;
|
||||
pub use gdi::CapturerGDI;
|
||||
|
||||
use winapi::{
|
||||
shared::dxgi::{
|
||||
CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIResource, IDXGISurface,
|
||||
IID_IDXGIFactory1, IID_IDXGISurface, DXGI_MAP_READ, DXGI_OUTPUT_DESC,
|
||||
DXGI_RESOURCE_PRIORITY_MAXIMUM,
|
||||
shared::{
|
||||
dxgi::*,
|
||||
dxgi1_2::*,
|
||||
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,
|
||||
// shared::dxgiformat::{DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_420_OPAQUE},
|
||||
shared::dxgi1_2::{IDXGIOutput1, IID_IDXGIOutput1},
|
||||
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::*, d3dcommon::D3D_DRIVER_TYPE_UNKNOWN, unknwnbase::IUnknown, wingdi::*,
|
||||
winnt::HRESULT, winuser::*,
|
||||
},
|
||||
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);
|
||||
@ -54,12 +42,11 @@ pub struct Capturer {
|
||||
duplication: ComPtr<IDXGIOutputDuplication>,
|
||||
fastlane: bool,
|
||||
surface: ComPtr<IDXGISurface>,
|
||||
data: *const u8,
|
||||
len: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
use_yuv: bool,
|
||||
yuv: Vec<u8>,
|
||||
rotated: Vec<u8>,
|
||||
gdi_capturer: Option<CapturerGDI>,
|
||||
gdi_buffer: Vec<u8>,
|
||||
}
|
||||
@ -158,10 +145,9 @@ impl Capturer {
|
||||
width: display.width() as usize,
|
||||
height: display.height() as usize,
|
||||
display,
|
||||
data: ptr::null(),
|
||||
len: 0,
|
||||
use_yuv,
|
||||
yuv: Vec::new(),
|
||||
rotated: Vec::new(),
|
||||
gdi_capturer,
|
||||
gdi_buffer: Vec::new(),
|
||||
})
|
||||
@ -181,10 +167,9 @@ impl Capturer {
|
||||
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 info = mem::MaybeUninit::uninit().assume_init();
|
||||
self.data = ptr::null();
|
||||
|
||||
wrap_hresult((*self.duplication.0).AcquireNextFrame(timeout, &mut info, &mut frame))?;
|
||||
let frame = ComPtr(frame);
|
||||
@ -200,9 +185,7 @@ impl Capturer {
|
||||
self.surface = ComPtr(self.ohgodwhat(frame.0)?);
|
||||
wrap_hresult((*self.surface.0).Map(&mut rect, DXGI_MAP_READ))?;
|
||||
}
|
||||
self.data = rect.pBits;
|
||||
self.len = self.height * rect.Pitch as usize;
|
||||
Ok(())
|
||||
Ok((rect.pBits, rect.Pitch))
|
||||
}
|
||||
|
||||
// copy from GPU memory to system memory
|
||||
@ -257,8 +240,42 @@ impl Capturer {
|
||||
}
|
||||
} else {
|
||||
self.unmap();
|
||||
self.load_frame(timeout)?;
|
||||
slice::from_raw_parts(self.data, self.len)
|
||||
let r = self.load_frame(timeout)?;
|
||||
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({
|
||||
@ -478,7 +495,10 @@ pub struct Display {
|
||||
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 {
|
||||
pub fn width(&self) -> LONG {
|
||||
|
130
src/client.rs
130
src/client.rs
@ -14,8 +14,8 @@ use hbb_common::{
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::*,
|
||||
socket_client,
|
||||
sodiumoxide::crypto::{box_, secretbox, sign},
|
||||
tcp::FramedStream,
|
||||
timeout,
|
||||
tokio::time::Duration,
|
||||
AddrMangle, ResultType, Stream,
|
||||
@ -35,6 +35,8 @@ pub const SEC30: Duration = Duration::from_secs(30);
|
||||
|
||||
pub struct Client;
|
||||
|
||||
pub use super::lang::*;
|
||||
|
||||
#[cfg(not(any(target_os = "android")))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref AUDIO_HOST: Host = cpal::default_host();
|
||||
@ -102,14 +104,39 @@ impl Drop for OboePlayer {
|
||||
|
||||
impl Client {
|
||||
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
|
||||
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;
|
||||
log::info!("rendezvous server: {}", rendezvous_server);
|
||||
let mut socket = FramedStream::new(rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT)
|
||||
.await
|
||||
.with_context(|| "Failed to connect to rendezvous server")?;
|
||||
let my_addr = socket.get_ref().local_addr()?;
|
||||
|
||||
let mut socket =
|
||||
socket_client::connect_tcp(&*rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT).await?;
|
||||
let my_addr = socket.local_addr();
|
||||
let mut pk = Vec::new();
|
||||
let mut relay_server = "".to_owned();
|
||||
|
||||
@ -204,7 +231,7 @@ impl Client {
|
||||
peer,
|
||||
pk,
|
||||
&relay_server,
|
||||
rendezvous_server,
|
||||
&rendezvous_server,
|
||||
time_used,
|
||||
peer_nat_type,
|
||||
my_nat_type,
|
||||
@ -220,7 +247,7 @@ impl Client {
|
||||
peer_id: &str,
|
||||
pk: Vec<u8>,
|
||||
relay_server: &str,
|
||||
rendezvous_server: SocketAddr,
|
||||
rendezvous_server: &str,
|
||||
punch_time_used: u64,
|
||||
peer_nat_type: NatType,
|
||||
my_nat_type: i32,
|
||||
@ -261,7 +288,8 @@ impl Client {
|
||||
}
|
||||
log::info!("peer address: {}, timeout: {}", peer, connect_timeout);
|
||||
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();
|
||||
if conn.is_err() {
|
||||
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<()> {
|
||||
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 {
|
||||
// send an empty message out in case server is setting up secure and waiting for first message
|
||||
conn.send(&Message::new()).await?;
|
||||
return Ok(());
|
||||
}
|
||||
let mut pk_ = [0u8; sign::PUBLICKEYBYTES];
|
||||
pk_[..].copy_from_slice(&pk);
|
||||
let pk = sign::PublicKey(pk_);
|
||||
let mut tmp = [0u8; sign::PUBLICKEYBYTES];
|
||||
tmp[..].copy_from_slice(&pk);
|
||||
let sign_pk = sign::PublicKey(tmp);
|
||||
match timeout(CONNECT_TIMEOUT, conn.next()).await? {
|
||||
Some(res) => {
|
||||
let bytes = res?;
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||
if let Some(message::Union::signed_id(si)) = msg_in.union {
|
||||
let their_pk_b = if si.pk.len() == box_::PUBLICKEYBYTES {
|
||||
let mut pk_ = [0u8; box_::PUBLICKEYBYTES];
|
||||
pk_[..].copy_from_slice(&si.pk);
|
||||
box_::PublicKey(pk_)
|
||||
} else {
|
||||
bail!("Handshake failed: invalid public box key length from peer");
|
||||
};
|
||||
if let Ok(id) = sign::verify(&si.id, &pk) {
|
||||
if id == peer_id.as_bytes() {
|
||||
if let Ok(data) = sign::verify(&si.id, &sign_pk) {
|
||||
let s = String::from_utf8_lossy(&data);
|
||||
let mut it = s.split("\0");
|
||||
let id = it.next().unwrap_or_default();
|
||||
let pk =
|
||||
base64::decode(it.next().unwrap_or_default()).unwrap_or_default();
|
||||
let their_pk_b = if pk.len() == box_::PUBLICKEYBYTES {
|
||||
let mut pk_ = [0u8; box_::PUBLICKEYBYTES];
|
||||
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 key = secretbox::gen_key();
|
||||
let nonce = box_::Nonce([0u8; box_::NONCEBYTES]);
|
||||
@ -331,7 +382,8 @@ impl Client {
|
||||
timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??;
|
||||
conn.set_key(key);
|
||||
} else {
|
||||
bail!("Handshake failed: sign failure");
|
||||
log::error!("Handshake failed: sign failure");
|
||||
conn.send(&Message::new()).await?;
|
||||
}
|
||||
} else {
|
||||
// fall back to non-secure connection in case pk mismatch
|
||||
@ -341,10 +393,12 @@ impl Client {
|
||||
timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??;
|
||||
}
|
||||
} else {
|
||||
bail!("Handshake failed: invalid message type");
|
||||
log::error!("Handshake failed: invalid message type");
|
||||
conn.send(&Message::new()).await?;
|
||||
}
|
||||
} else {
|
||||
bail!("Handshake failed: invalid message format");
|
||||
log::error!("Handshake failed: invalid message format");
|
||||
conn.send(&Message::new()).await?;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@ -357,7 +411,7 @@ impl Client {
|
||||
async fn request_relay(
|
||||
peer: &str,
|
||||
relay_server: String,
|
||||
rendezvous_server: SocketAddr,
|
||||
rendezvous_server: &str,
|
||||
secure: bool,
|
||||
conn_type: ConnType,
|
||||
) -> ResultType<Stream> {
|
||||
@ -366,9 +420,11 @@ impl Client {
|
||||
let mut uuid = "".to_owned();
|
||||
for i in 1..=3 {
|
||||
// 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)
|
||||
.await
|
||||
.with_context(|| "Failed to connect to rendezvous server")?;
|
||||
let mut socket =
|
||||
socket_client::connect_tcp(rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT)
|
||||
.await
|
||||
.with_context(|| "Failed to connect to rendezvous server")?;
|
||||
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
uuid = Uuid::new_v4().to_string();
|
||||
log::info!(
|
||||
@ -411,7 +467,7 @@ impl Client {
|
||||
relay_server: String,
|
||||
conn_type: ConnType,
|
||||
) -> ResultType<Stream> {
|
||||
let mut conn = FramedStream::new(
|
||||
let mut conn = socket_client::connect_tcp(
|
||||
crate::check_port(relay_server, RELAY_PORT),
|
||||
Config::get_any_listen_addr(),
|
||||
CONNECT_TIMEOUT,
|
||||
@ -530,7 +586,10 @@ impl AudioHandler {
|
||||
);
|
||||
audio_buffer.lock().unwrap().extend(buffer);
|
||||
} else {
|
||||
audio_buffer.lock().unwrap().extend(buffer.iter().cloned());
|
||||
audio_buffer
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend(buffer[0..n].iter().cloned());
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "android"))]
|
||||
@ -548,7 +607,8 @@ impl AudioHandler {
|
||||
device: &Device,
|
||||
) -> ResultType<()> {
|
||||
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 stream = device.build_output_stream(
|
||||
@ -583,7 +643,7 @@ pub struct VideoHandler {
|
||||
impl VideoHandler {
|
||||
pub fn new() -> Self {
|
||||
VideoHandler {
|
||||
decoder: Decoder::new(VideoCodecId::VP9, 1).unwrap(),
|
||||
decoder: Decoder::new(VideoCodecId::VP9, 0).unwrap(),
|
||||
rgb: Default::default(),
|
||||
}
|
||||
}
|
||||
@ -625,6 +685,7 @@ pub struct LoginConfigHandler {
|
||||
pub port_forward: (String, i32),
|
||||
pub support_press: bool,
|
||||
pub support_refresh: bool,
|
||||
pub version: i64,
|
||||
}
|
||||
|
||||
impl Deref for LoginConfigHandler {
|
||||
@ -659,6 +720,12 @@ impl LoginConfigHandler {
|
||||
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) {
|
||||
let mut config = self.load_config();
|
||||
config.view_style = value;
|
||||
@ -875,6 +942,7 @@ impl LoginConfigHandler {
|
||||
if !pi.version.is_empty() {
|
||||
self.support_press = true;
|
||||
self.support_refresh = true;
|
||||
self.version = crate::get_version_number(&pi.version);
|
||||
}
|
||||
let serde = PeerInfoSerde {
|
||||
username,
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub use arboard::Clipboard as ClipboardContext;
|
||||
use hbb_common::{
|
||||
allow_err, bail,
|
||||
allow_err,
|
||||
anyhow::bail,
|
||||
compress::{compress as compress_func, decompress},
|
||||
config::{Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT},
|
||||
log,
|
||||
@ -8,9 +9,7 @@ use hbb_common::{
|
||||
protobuf::Message as _,
|
||||
protobuf::ProtobufEnum,
|
||||
rendezvous_proto::*,
|
||||
sleep,
|
||||
tcp::FramedStream,
|
||||
tokio, ResultType,
|
||||
sleep, socket_client, tokio, ResultType,
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
|
||||
@ -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 {
|
||||
let bytes = content.into_bytes();
|
||||
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 old = if let Some(old) = old { old } else { &CONTENT };
|
||||
*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);
|
||||
}
|
||||
Err(err) => {
|
||||
@ -236,15 +229,19 @@ pub fn test_nat_type() {
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn test_nat_type_() -> ResultType<bool> {
|
||||
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 rendezvous_server = get_rendezvous_server(100).await;
|
||||
let rendezvous_server = get_rendezvous_server(1_000).await;
|
||||
let server1 = rendezvous_server;
|
||||
let mut server2 = server1;
|
||||
if server1.port() == 0 { // offline
|
||||
// avoid overflow crash
|
||||
bail!("Offline");
|
||||
let tmp: Vec<&str> = server1.split(":").collect();
|
||||
if tmp.len() != 2 {
|
||||
bail!("Invalid server address: {}", server1);
|
||||
}
|
||||
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 serial = Config::get_serial();
|
||||
msg_out.set_test_nat_request(TestNatRequest {
|
||||
@ -253,15 +250,24 @@ async fn test_nat_type_() -> ResultType<bool> {
|
||||
});
|
||||
let mut port1 = 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();
|
||||
for i in 0..2 {
|
||||
let mut socket = FramedStream::new(
|
||||
if i == 0 { &server1 } else { &server2 },
|
||||
let mut socket = socket_client::connect_tcp(
|
||||
if i == 0 {
|
||||
server1.clone()
|
||||
} else {
|
||||
server2.clone()
|
||||
},
|
||||
addr,
|
||||
RENDEZVOUS_TIMEOUT,
|
||||
)
|
||||
.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?;
|
||||
if let Some(Ok(bytes)) = socket.next_timeout(3000).await {
|
||||
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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
@ -326,8 +332,8 @@ async fn test_rendezvous_server_() {
|
||||
for host in servers {
|
||||
futs.push(tokio::spawn(async move {
|
||||
let tm = std::time::Instant::now();
|
||||
if FramedStream::new(
|
||||
&crate::check_port(&host, RENDEZVOUS_PORT),
|
||||
if socket_client::connect_tcp(
|
||||
crate::check_port(&host, RENDEZVOUS_PORT),
|
||||
Config::get_any_listen_addr(),
|
||||
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 {
|
||||
let mut n = 0;
|
||||
for x in v.split(".") {
|
||||
@ -433,8 +428,11 @@ pub fn check_software_update() {
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn _check_software_update() -> hbb_common::ResultType<()> {
|
||||
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();
|
||||
msg_out.set_software_update(SoftwareUpdate {
|
||||
url: crate::VERSION.to_owned(),
|
||||
@ -454,3 +452,9 @@ async fn _check_software_update() -> hbb_common::ResultType<()> {
|
||||
}
|
||||
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,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, net::SocketAddr};
|
||||
use std::collections::HashMap;
|
||||
#[cfg(not(windows))]
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
@ -89,6 +89,7 @@ pub enum Data {
|
||||
NatType(Option<i32>),
|
||||
ConfirmedKey(Option<(Vec<u8>, Vec<u8>)>),
|
||||
RawMessage(Vec<u8>),
|
||||
Socks(Option<config::Socks5Server>),
|
||||
FS(FS),
|
||||
SessionsUpdated,
|
||||
Test,
|
||||
@ -193,6 +194,20 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
};
|
||||
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 {
|
||||
None => {
|
||||
let value;
|
||||
@ -203,7 +218,9 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
} else if name == "salt" {
|
||||
value = Some(Config::get_salt());
|
||||
} 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 {
|
||||
value = None;
|
||||
}
|
||||
@ -211,6 +228,7 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
}
|
||||
Some(value) => {
|
||||
if name == "id" {
|
||||
Config::set_key_confirmed(false);
|
||||
Config::set_id(&value);
|
||||
} else if name == "password" {
|
||||
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(v) = v.parse() {
|
||||
return v;
|
||||
}
|
||||
v
|
||||
} else {
|
||||
Config::get_rendezvous_server()
|
||||
}
|
||||
return Config::get_rendezvous_server();
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
|
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;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod port_forward;
|
||||
mod lang;
|
||||
|
22
src/main.rs
22
src/main.rs
@ -43,16 +43,18 @@ fn main() {
|
||||
}
|
||||
}
|
||||
use flexi_logger::*;
|
||||
Logger::with_env_or_str("debug")
|
||||
.log_to_file()
|
||||
.format(opt_format)
|
||||
.rotate(
|
||||
Criterion::Age(Age::Day),
|
||||
Naming::Timestamps,
|
||||
Cleanup::KeepLogFiles(6),
|
||||
)
|
||||
.directory(path)
|
||||
.start()
|
||||
Logger::try_with_env_or_str("debug")
|
||||
.map(|x| {
|
||||
x.log_to_file(FileSpec::default().directory(path))
|
||||
.format(opt_format)
|
||||
.rotate(
|
||||
Criterion::Age(Age::Day),
|
||||
Naming::Timestamps,
|
||||
Cleanup::KeepLogFiles(6),
|
||||
)
|
||||
.start()
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
if args.is_empty() {
|
||||
|
@ -11,7 +11,7 @@ use std::{
|
||||
};
|
||||
type Xdo = *const c_void;
|
||||
|
||||
pub const PA_SAMPLE_RATE: u32 = 24000;
|
||||
pub const PA_SAMPLE_RATE: u32 = 48000;
|
||||
static mut UNMODIFIED: bool = true;
|
||||
|
||||
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 {
|
||||
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);
|
||||
if let Ok(Some(x)) = run_cmds(cmd) {
|
||||
x.trim_end().to_string()
|
||||
|
@ -1,23 +1,26 @@
|
||||
use crate::server::{check_zombie, new as new_server, ServerPtr};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::bail,
|
||||
config::{Config, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
|
||||
futures::future::join_all,
|
||||
log,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::*,
|
||||
sleep,
|
||||
tcp::FramedStream,
|
||||
sleep, socket_client,
|
||||
tokio::{
|
||||
self, select,
|
||||
time::{interval, Duration},
|
||||
},
|
||||
udp::FramedSocket,
|
||||
AddrMangle, ResultType,
|
||||
AddrMangle, IntoTargetAddr, ResultType, TargetAddr,
|
||||
};
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::SystemTime,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
@ -25,12 +28,14 @@ use uuid::Uuid;
|
||||
type Message = RendezvousMessage;
|
||||
|
||||
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)]
|
||||
pub struct RendezvousMediator {
|
||||
addr: SocketAddr,
|
||||
addr: TargetAddr<'static>,
|
||||
host: String,
|
||||
host_prefix: String,
|
||||
rendezvous_servers: Vec<String>,
|
||||
@ -38,6 +43,10 @@ pub struct RendezvousMediator {
|
||||
}
|
||||
|
||||
impl RendezvousMediator {
|
||||
pub fn restart() {
|
||||
SHOULD_EXIT.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub async fn start_all() {
|
||||
let mut nat_tested = false;
|
||||
check_zombie();
|
||||
@ -46,6 +55,10 @@ impl RendezvousMediator {
|
||||
crate::common::test_nat_type();
|
||||
nat_tested = true;
|
||||
}
|
||||
let server_cloned = server.clone();
|
||||
tokio::spawn(async move {
|
||||
allow_err!(direct_server(server_cloned).await);
|
||||
});
|
||||
loop {
|
||||
Config::reset_online();
|
||||
if Config::get_option("stop-service").is_empty() {
|
||||
@ -55,11 +68,14 @@ impl RendezvousMediator {
|
||||
}
|
||||
let mut futs = Vec::new();
|
||||
let servers = Config::get_rendezvous_servers();
|
||||
SHOULD_EXIT.store(false, Ordering::SeqCst);
|
||||
for host in servers.clone() {
|
||||
let server = server.clone();
|
||||
let servers = servers.clone();
|
||||
futs.push(tokio::spawn(async move {
|
||||
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;
|
||||
@ -86,18 +102,20 @@ impl RendezvousMediator {
|
||||
})
|
||||
.unwrap_or(host.to_owned());
|
||||
let mut rz = Self {
|
||||
addr: Config::get_any_listen_addr(),
|
||||
addr: Config::get_any_listen_addr().into_target_addr()?,
|
||||
host: host.clone(),
|
||||
host_prefix,
|
||||
rendezvous_servers,
|
||||
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);
|
||||
let mut timer = interval(TIMER_OUT);
|
||||
let mut last_timer = SystemTime::UNIX_EPOCH;
|
||||
const REG_INTERVAL: i64 = 12_000;
|
||||
const REG_TIMEOUT: i64 = 3_000;
|
||||
const MAX_FAILS1: i64 = 3;
|
||||
const MAX_FAILS2: i64 = 6;
|
||||
@ -136,60 +154,68 @@ impl RendezvousMediator {
|
||||
}
|
||||
};
|
||||
select! {
|
||||
Some(Ok((bytes, _))) = socket.next() => {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::register_peer_response(rpr)) => {
|
||||
update_latency();
|
||||
if rpr.request_pk {
|
||||
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();
|
||||
n = socket.next() => {
|
||||
match n {
|
||||
Some(Ok((bytes, _))) => {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::register_peer_response(rpr)) => {
|
||||
update_latency();
|
||||
if rpr.request_pk {
|
||||
log::info!("request_pk received from {}", host);
|
||||
allow_err!(rz.register_pk(&mut socket).await);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
register_pk_response::Result::UUID_MISMATCH => {
|
||||
allow_err!(rz.handle_uuid_mismatch(&mut socket).await);
|
||||
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 => {
|
||||
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();
|
||||
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(Err(e)) => bail!("Failed to receive next {}", e), // maybe socks5 tcp disconnected
|
||||
None => {
|
||||
// unreachable!()
|
||||
},
|
||||
}
|
||||
},
|
||||
_ = timer.tick() => {
|
||||
@ -199,15 +225,8 @@ impl RendezvousMediator {
|
||||
if !Config::get_option("stop-service").is_empty() {
|
||||
break;
|
||||
}
|
||||
if rz.addr.port() == 0 {
|
||||
allow_err!(rz.dns_check());
|
||||
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?;
|
||||
}
|
||||
if SHOULD_EXIT.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
let now = SystemTime::now();
|
||||
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);
|
||||
old_latency = 0;
|
||||
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() {
|
||||
// in some case of network reconnect (dial IP network),
|
||||
// old UDP socket not work any more after network recover
|
||||
socket = FramedSocket::new(Config::get_any_listen_addr()).await?;
|
||||
rz.addr = socket_client::get_target_addr(&crate::check_port(&host, RENDEZVOUS_PORT))?;
|
||||
// in some case of network reconnect (dial IP network),
|
||||
// old UDP socket not work any more after network recover
|
||||
if let Some(s) = socket_client::rebind_udp(any_addr).await? {
|
||||
socket = s;
|
||||
}
|
||||
last_dns_check = now;
|
||||
}
|
||||
@ -245,12 +265,6 @@ impl RendezvousMediator {
|
||||
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<()> {
|
||||
self.create_relay(
|
||||
rr.socket_addr,
|
||||
@ -280,8 +294,14 @@ impl RendezvousMediator {
|
||||
uuid,
|
||||
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 rr = RelayResponse {
|
||||
socket_addr,
|
||||
@ -303,15 +323,15 @@ impl RendezvousMediator {
|
||||
async fn handle_intranet(&self, fla: FetchLocalAddr, server: ServerPtr) -> ResultType<()> {
|
||||
let peer_addr = AddrMangle::decode(&fla.socket_addr);
|
||||
log::debug!("Handle intranet from {:?}", peer_addr);
|
||||
let (mut socket, port) = {
|
||||
let socket =
|
||||
FramedStream::new(self.addr, Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT)
|
||||
.await?;
|
||||
let port = socket.get_ref().local_addr()?.port();
|
||||
(socket, port)
|
||||
};
|
||||
let local_addr = socket.get_ref().local_addr()?;
|
||||
let local_addr: SocketAddr = format!("{}:{}", local_addr.ip(), port).parse()?;
|
||||
let mut socket = socket_client::connect_tcp(
|
||||
self.addr.to_owned(),
|
||||
Config::get_any_listen_addr(),
|
||||
RENDEZVOUS_TIMEOUT,
|
||||
)
|
||||
.await?;
|
||||
let local_addr = socket.local_addr();
|
||||
let local_addr: SocketAddr =
|
||||
format!("{}:{}", local_addr.ip(), local_addr.port()).parse()?;
|
||||
let mut msg_out = Message::new();
|
||||
let mut relay_server = Config::get_option("relay-server");
|
||||
if relay_server.is_empty() {
|
||||
@ -347,10 +367,14 @@ impl RendezvousMediator {
|
||||
let peer_addr = AddrMangle::decode(&ph.socket_addr);
|
||||
log::debug!("Punch hole to {:?}", peer_addr);
|
||||
let mut socket = {
|
||||
let socket =
|
||||
FramedStream::new(self.addr, Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT)
|
||||
.await?;
|
||||
allow_err!(FramedStream::new(peer_addr, socket.get_ref().local_addr()?, 300).await);
|
||||
let socket = socket_client::connect_tcp(
|
||||
self.addr.to_owned(),
|
||||
Config::get_any_listen_addr(),
|
||||
RENDEZVOUS_TIMEOUT,
|
||||
)
|
||||
.await?;
|
||||
let local_addr = socket.local_addr();
|
||||
allow_err!(socket_client::connect_tcp(peer_addr, local_addr, 300).await);
|
||||
socket
|
||||
};
|
||||
let mut msg_out = Message::new();
|
||||
@ -387,7 +411,7 @@ impl RendezvousMediator {
|
||||
pk,
|
||||
..Default::default()
|
||||
});
|
||||
socket.send(&msg_out, self.addr).await?;
|
||||
socket.send(&msg_out, self.addr.to_owned()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -433,7 +457,47 @@ impl RendezvousMediator {
|
||||
serial,
|
||||
..Default::default()
|
||||
});
|
||||
socket.send(&msg_out, self.addr).await?;
|
||||
socket.send(&msg_out, self.addr.to_owned()).await?;
|
||||
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::*,
|
||||
sleep,
|
||||
sodiumoxide::crypto::{box_, secretbox, sign},
|
||||
tcp::FramedStream,
|
||||
timeout, tokio, ResultType, Stream,
|
||||
socket_client,
|
||||
};
|
||||
use service::{GenericService, Service, ServiceTmpl, Subscriber};
|
||||
use std::{
|
||||
@ -63,7 +63,7 @@ pub fn new() -> ServerPtr {
|
||||
}
|
||||
|
||||
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);
|
||||
// even we drop socket, below still may fail if not use reuse_addr,
|
||||
// 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?;
|
||||
log::info!("Server listening on: {}", &listener.local_addr()?);
|
||||
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(())
|
||||
}
|
||||
|
||||
async fn create_tcp_connection_(
|
||||
pub async fn create_tcp_connection(
|
||||
server: ServerPtr,
|
||||
stream: Stream,
|
||||
addr: SocketAddr,
|
||||
@ -94,11 +95,13 @@ async fn create_tcp_connection_(
|
||||
sk_[..].copy_from_slice(&sk);
|
||||
let sk = sign::SecretKey(sk_);
|
||||
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 signed_id = sign::sign(
|
||||
format!("{}\0{}", Config::get_id(), base64::encode(our_pk_b.0)).as_bytes(),
|
||||
&sk,
|
||||
);
|
||||
msg_out.set_signed_id(SignedId {
|
||||
id: signed_id,
|
||||
pk: our_pk_b.0.into(),
|
||||
..Default::default()
|
||||
});
|
||||
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;
|
||||
@ -124,8 +127,8 @@ async fn create_tcp_connection_(
|
||||
key[..].copy_from_slice(&symmetric_key);
|
||||
stream.set_key(secretbox::Key(key));
|
||||
} else if pk.asymmetric_value.is_empty() {
|
||||
// force a trial to update_pk to rendezvous server
|
||||
Config::set_key_confirmed(false);
|
||||
log::info!("Force to update pk");
|
||||
} else {
|
||||
bail!("Handshake failed: invalid public sign key length from peer");
|
||||
}
|
||||
@ -183,8 +186,8 @@ async fn create_relay_connection_(
|
||||
peer_addr: SocketAddr,
|
||||
secure: bool,
|
||||
) -> ResultType<()> {
|
||||
let mut stream = FramedStream::new(
|
||||
&crate::check_port(relay_server, RELAY_PORT),
|
||||
let mut stream = socket_client::connect_tcp(
|
||||
crate::check_port(relay_server, RELAY_PORT),
|
||||
Config::get_any_listen_addr(),
|
||||
CONNECT_TIMEOUT,
|
||||
)
|
||||
@ -195,7 +198,7 @@ async fn create_relay_connection_(
|
||||
..Default::default()
|
||||
});
|
||||
stream.send(&msg_out).await?;
|
||||
create_tcp_connection_(server, stream, peer_addr, secure).await?;
|
||||
create_tcp_connection(server, stream, peer_addr, secure).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -36,35 +36,34 @@ mod pa_impl {
|
||||
use super::*;
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn run(sp: GenericService) -> ResultType<()> {
|
||||
if let Ok(mut stream) = crate::ipc::connect(1000, "_pa").await {
|
||||
let mut encoder =
|
||||
Encoder::new(crate::platform::linux::PA_SAMPLE_RATE, Stereo, LowDelay)?;
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&crate::ipc::Data::Config((
|
||||
"audio-input".to_owned(),
|
||||
Some(Config::get_option("audio-input"))
|
||||
)))
|
||||
.await
|
||||
);
|
||||
while sp.ok() {
|
||||
sp.snapshot(|sps| {
|
||||
sps.send(create_format_msg(crate::platform::linux::PA_SAMPLE_RATE, 2));
|
||||
Ok(())
|
||||
})?;
|
||||
if let Some(data) = stream.next_timeout2(1000).await {
|
||||
match data? {
|
||||
Some(crate::ipc::Data::RawMessage(bytes)) => {
|
||||
let data = unsafe {
|
||||
std::slice::from_raw_parts::<f32>(
|
||||
bytes.as_ptr() as _,
|
||||
bytes.len() / 4,
|
||||
)
|
||||
};
|
||||
send_f32(data, &mut encoder, &sp);
|
||||
}
|
||||
_ => {}
|
||||
hbb_common::sleep(0.1).await; // one moment to wait for _pa ipc
|
||||
let mut stream = crate::ipc::connect(1000, "_pa").await?;
|
||||
unsafe {
|
||||
AUDIO_ZERO_COUNT = 0;
|
||||
}
|
||||
let mut encoder = Encoder::new(crate::platform::linux::PA_SAMPLE_RATE, Stereo, LowDelay)?;
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&crate::ipc::Data::Config((
|
||||
"audio-input".to_owned(),
|
||||
Some(Config::get_option("audio-input"))
|
||||
)))
|
||||
.await
|
||||
);
|
||||
while sp.ok() {
|
||||
sp.snapshot(|sps| {
|
||||
sps.send(create_format_msg(crate::platform::linux::PA_SAMPLE_RATE, 2));
|
||||
Ok(())
|
||||
})?;
|
||||
if let Some(data) = stream.next_timeout2(1000).await {
|
||||
match data? {
|
||||
Some(crate::ipc::Data::RawMessage(bytes)) => {
|
||||
let data = unsafe {
|
||||
std::slice::from_raw_parts::<f32>(bytes.as_ptr() as _, bytes.len() / 4)
|
||||
};
|
||||
send_f32(data, &mut encoder, &sp);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,9 +118,6 @@ mod cpal_impl {
|
||||
encoder: &mut Encoder,
|
||||
sp: &GenericService,
|
||||
) {
|
||||
if data.iter().filter(|x| **x != 0.).next().is_none() {
|
||||
return;
|
||||
}
|
||||
let buffer;
|
||||
let data = if sample_rate0 != sample_rate {
|
||||
buffer = crate::common::resample_channels(data, sample_rate0, sample_rate, channels);
|
||||
@ -195,10 +191,10 @@ mod cpal_impl {
|
||||
let (device, config) = get_device()?;
|
||||
let sp = sp.clone();
|
||||
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.
|
||||
// Note: somehow 48000 not work
|
||||
let sample_rate_0 = config.sample_rate().0;
|
||||
let sample_rate = if sample_rate_0 < 12000 {
|
||||
8000
|
||||
@ -206,9 +202,15 @@ mod cpal_impl {
|
||||
12000
|
||||
} else if sample_rate_0 < 24000 {
|
||||
16000
|
||||
} else {
|
||||
} else if sample_rate_0 < 48000 {
|
||||
24000
|
||||
} else {
|
||||
48000
|
||||
};
|
||||
log::debug!("Audio sample rate : {}", sample_rate);
|
||||
unsafe {
|
||||
AUDIO_ZERO_COUNT = 0;
|
||||
}
|
||||
let mut encoder = Encoder::new(
|
||||
sample_rate,
|
||||
if config.channels() > 1 { Stereo } else { Mono },
|
||||
@ -282,19 +284,39 @@ fn create_format_msg(sample_rate: u32, channels: u16) -> Message {
|
||||
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) {
|
||||
if data.iter().filter(|x| **x != 0.).next().is_some() {
|
||||
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(_) => {}
|
||||
unsafe {
|
||||
AUDIO_ZERO_COUNT = 0;
|
||||
}
|
||||
} 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) {
|
||||
let _ = match ctx.get_text() {
|
||||
Ok(text) => ctx.set_text(text),
|
||||
Err(_) => ctx.set_text(Default::default()),
|
||||
Ok(text) => {
|
||||
if !text.is_empty() {
|
||||
ctx.set_text(text).ok();
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -62,15 +62,18 @@ impl Subscriber for ConnInner {
|
||||
|
||||
#[inline]
|
||||
fn send(&mut self, msg: Arc<Message>) {
|
||||
self.tx.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)));
|
||||
});
|
||||
match &msg.union {
|
||||
Some(message::Union::video_frame(_)) => {
|
||||
self.tx_video.as_mut().map(|tx| {
|
||||
allow_err!(tx.send((Instant::now(), 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 H1: Duration = Duration::from_secs(3600);
|
||||
const MILLI1: Duration = Duration::from_millis(1);
|
||||
const SEND_TIMEOUT_VIDEO: u64 = 12_000;
|
||||
const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10;
|
||||
|
||||
impl Connection {
|
||||
pub async fn start(
|
||||
@ -144,9 +149,17 @@ impl Connection {
|
||||
time::interval_at(Instant::now() + TEST_DELAY_TIMEOUT, TEST_DELAY_TIMEOUT);
|
||||
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 {
|
||||
tokio::select! {
|
||||
biased;
|
||||
biased; // video has higher priority
|
||||
|
||||
Some(data) = rx_from_cm.recv() => {
|
||||
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_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");
|
||||
conn.stream.set_raw();
|
||||
self.stream.set_raw();
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(data) = rx_from_cm.recv() => {
|
||||
match data {
|
||||
ipc::Data::Close => {
|
||||
conn.on_close("Close requested from connection manager", false);
|
||||
break;
|
||||
bail!("Close requested from selfection manager");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
res = forward.next() => {
|
||||
if let Some(res) = res {
|
||||
match res {
|
||||
Err(err) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
last_recv_time = Instant::now();
|
||||
self.stream.send_bytes(res?.into()).await?;
|
||||
} else {
|
||||
conn.on_close("Forward reset by the peer", false);
|
||||
break;
|
||||
bail!("Forward reset by the peer");
|
||||
}
|
||||
},
|
||||
res = conn.stream.next() => {
|
||||
res = self.stream.next() => {
|
||||
if let Some(res) = res {
|
||||
match res {
|
||||
Err(err) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
last_recv_time = Instant::now();
|
||||
timeout(SEND_TIMEOUT_OTHER, forward.send(res?.into())).await??;
|
||||
} else {
|
||||
conn.on_close("Stream reset by the peer", false);
|
||||
break;
|
||||
bail!("Stream reset by the peer");
|
||||
}
|
||||
},
|
||||
_ = conn.timer.tick() => {
|
||||
_ = self.timer.tick() => {
|
||||
if last_recv_time.elapsed() >= H1 {
|
||||
conn.on_close("Timeout", false);
|
||||
break;
|
||||
bail!("Timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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;
|
||||
} else if lr.password.is_empty() {
|
||||
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>,
|
||||
tx_from_cm: mpsc::UnboundedSender<ipc::Data>,
|
||||
) -> ResultType<()> {
|
||||
loop {
|
||||
if !crate::platform::is_prelogin() {
|
||||
break;
|
||||
}
|
||||
sleep(1.).await;
|
||||
if crate::platform::is_prelogin() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut stream = None;
|
||||
if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
|
||||
stream = Some(s);
|
||||
|
@ -79,8 +79,6 @@ impl Subscriber for MouseCursorSub {
|
||||
self.inner.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn send_video_frame(&mut self, _: std::time::Instant, _: Arc<Message>) {}
|
||||
}
|
||||
|
||||
pub const NAME_CURSOR: &'static str = "mouse_cursor";
|
||||
@ -197,15 +195,20 @@ fn modifier_sleep() {
|
||||
std::thread::sleep(std::time::Duration::from_nanos(1));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[inline]
|
||||
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());
|
||||
match key {
|
||||
Key::Shift => x || en.get_key_state(Key::RightShift),
|
||||
Key::Control => x || en.get_key_state(Key::RightControl),
|
||||
Key::Alt => x || en.get_key_state(Key::RightAlt),
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -264,7 +267,7 @@ fn fix_key_down_timeout(force: bool) {
|
||||
if let Some(key) = key {
|
||||
let func = move || {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
if en.get_key_state(key) {
|
||||
if get_modifier_state(key, &mut en) {
|
||||
en.key_up(key);
|
||||
log::debug!("Fixed {:?} timeout", key);
|
||||
}
|
||||
@ -286,7 +289,7 @@ fn fix_modifier(
|
||||
key1: Key,
|
||||
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);
|
||||
log::debug!("Fixed {:?}", key1);
|
||||
}
|
||||
@ -577,11 +580,9 @@ fn handle_key_(evt: &KeyEvent) {
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
if crate::common::valid_for_capslock(evt) {
|
||||
if has_cap != en.get_key_state(Key::CapsLock) {
|
||||
en.key_down(Key::CapsLock).ok();
|
||||
en.key_up(Key::CapsLock);
|
||||
}
|
||||
if has_cap != en.get_key_state(Key::CapsLock) {
|
||||
en.key_down(Key::CapsLock).ok();
|
||||
en.key_up(Key::CapsLock);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if crate::common::valid_for_numlock(evt) {
|
||||
|
@ -16,7 +16,6 @@ pub trait Service: Send + Sync {
|
||||
pub trait Subscriber: Default + Send + Sync + 'static {
|
||||
fn id(&self) -> i32;
|
||||
fn send(&mut self, msg: Arc<Message>);
|
||||
fn send_video_frame(&mut self, tm: time::Instant, msg: Arc<Message>);
|
||||
}
|
||||
|
||||
#[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> {
|
||||
self.send_video_frame_shared(tm, Arc::new(msg))
|
||||
pub fn send_video_frame(&self, msg: Message) -> HashSet<i32> {
|
||||
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 lock = self.0.write().unwrap();
|
||||
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
|
||||
|
@ -108,7 +108,7 @@ impl VideoFrameController {
|
||||
fetched_conn_ids.insert(id);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@ -188,11 +188,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
speed,
|
||||
};
|
||||
let mut vpx;
|
||||
let mut n = ((width * height) as f64 / (1920 * 1080) as f64).round() as u32;
|
||||
if n < 1 {
|
||||
n = 1;
|
||||
}
|
||||
match Encoder::new(&cfg, n) {
|
||||
match Encoder::new(&cfg, 0) {
|
||||
Ok(x) => vpx = x,
|
||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||
}
|
||||
@ -220,7 +216,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let start = time::Instant::now();
|
||||
let mut last_check_displays = time::Instant::now();
|
||||
#[cfg(windows)]
|
||||
let mut try_gdi = true;
|
||||
let mut try_gdi = 1;
|
||||
#[cfg(windows)]
|
||||
log::info!("gdi: {}", c.is_gdi());
|
||||
while sp.ok() {
|
||||
@ -257,11 +253,11 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
Ok(frame) => {
|
||||
let time = now - start;
|
||||
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);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
try_gdi = false;
|
||||
try_gdi = 0;
|
||||
}
|
||||
}
|
||||
Err(ref e) if e.kind() == WouldBlock => {
|
||||
@ -271,10 +267,13 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
wait = 0
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if try_gdi && !c.is_gdi() {
|
||||
c.set_gdi();
|
||||
try_gdi = false;
|
||||
log::info!("No image, fall back to gdi");
|
||||
if try_gdi > 0 && !c.is_gdi() {
|
||||
if try_gdi > 3 {
|
||||
c.set_gdi();
|
||||
try_gdi = 0;
|
||||
log::info!("No image, fall back to gdi");
|
||||
}
|
||||
try_gdi += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -327,7 +326,6 @@ fn create_frame(frame: &EncodeFrame) -> VP9 {
|
||||
#[inline]
|
||||
fn handle_one_frame(
|
||||
sp: &GenericService,
|
||||
now: Instant,
|
||||
frame: &[u8],
|
||||
ms: i64,
|
||||
crc: &mut (u32, u32),
|
||||
@ -365,7 +363,7 @@ fn handle_one_frame(
|
||||
|
||||
// to-do: flush periodically, e.g. 1 second
|
||||
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)
|
||||
|
110
src/ui.rs
110
src/ui.rs
@ -8,7 +8,7 @@ use crate::common::SOFTWARE_UPDATE_URL;
|
||||
use crate::ipc;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::{Config, PeerConfig, APP_NAME, ICON},
|
||||
config::{self, Config, Fav, PeerConfig, APP_NAME, ICON},
|
||||
log, sleep,
|
||||
tokio::{self, time},
|
||||
};
|
||||
@ -147,7 +147,6 @@ pub fn start(args: &mut [String]) {
|
||||
|
||||
#[cfg(windows)]
|
||||
fn start_tray() -> hbb_common::ResultType<()> {
|
||||
/*
|
||||
let mut app = systray::Application::new()?;
|
||||
let icon = include_bytes!("./tray-icon.ico");
|
||||
app.set_icon_from_buffer(icon, 32, 32).unwrap();
|
||||
@ -186,7 +185,7 @@ fn start_tray() -> hbb_common::ResultType<()> {
|
||||
Ok::<_, systray::Error>(())
|
||||
})?;
|
||||
allow_err!(app.wait_for_message());
|
||||
*/
|
||||
|
||||
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 {
|
||||
let c = PeerConfig::load(&id);
|
||||
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 {
|
||||
crate::common::test_if_valid_server(host)
|
||||
hbb_common::socket_client::test_if_valid_server(&host)
|
||||
}
|
||||
|
||||
fn get_sound_inputs(&self) -> Value {
|
||||
@ -361,6 +374,29 @@ impl UI {
|
||||
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 {
|
||||
crate::platform::is_installed()
|
||||
}
|
||||
@ -371,8 +407,8 @@ impl UI {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let installed_version = crate::platform::windows::get_installed_version();
|
||||
let a = crate::common::get_version_number(crate::VERSION);
|
||||
let b = crate::common::get_version_number(&installed_version);
|
||||
let a = hbb_common::get_version_number(crate::VERSION);
|
||||
let b = hbb_common::get_version_number(&installed_version);
|
||||
return a > b;
|
||||
}
|
||||
}
|
||||
@ -400,22 +436,43 @@ impl UI {
|
||||
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 {
|
||||
let peers: Vec<Value> = PeerConfig::peers()
|
||||
.iter()
|
||||
.map(|p| {
|
||||
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)
|
||||
})
|
||||
.drain(..)
|
||||
.map(|p| Self::get_peer_value(p.0, p.2))
|
||||
.collect();
|
||||
Value::from_iter(peers)
|
||||
}
|
||||
@ -564,6 +621,10 @@ impl UI {
|
||||
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 {
|
||||
crate::platform::is_xfce()
|
||||
}
|
||||
@ -575,6 +636,7 @@ impl UI {
|
||||
|
||||
impl sciter::EventHandler for UI {
|
||||
sciter::dispatch_script_call! {
|
||||
fn t(String);
|
||||
fn is_xfce();
|
||||
fn get_id();
|
||||
fn get_password();
|
||||
@ -587,11 +649,16 @@ impl sciter::EventHandler for UI {
|
||||
fn remove_peer(String);
|
||||
fn get_connect_status();
|
||||
fn get_recent_sessions();
|
||||
fn get_peer(String);
|
||||
fn get_fav();
|
||||
fn store_fav(Value);
|
||||
fn recent_sessions_updated();
|
||||
fn get_icon();
|
||||
fn get_msgbox();
|
||||
fn install_me(String);
|
||||
fn is_installed();
|
||||
fn set_socks(String, String, String);
|
||||
fn get_socks();
|
||||
fn is_installed_lower_version();
|
||||
fn install_path();
|
||||
fn goto_install();
|
||||
@ -604,7 +671,10 @@ impl sciter::EventHandler for UI {
|
||||
fn modify_default_login();
|
||||
fn get_options();
|
||||
fn get_option(String);
|
||||
fn get_local_option(String);
|
||||
fn get_peer_option(String, String);
|
||||
fn peer_has_password(String);
|
||||
fn forget_password(String);
|
||||
fn set_peer_option(String, String, String);
|
||||
fn test_if_valid_server(String);
|
||||
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) {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn t(&self, name: String) -> String {
|
||||
crate::client::translate(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl sciter::EventHandler for ConnectionManager {
|
||||
@ -308,6 +312,7 @@ impl sciter::EventHandler for ConnectionManager {
|
||||
}
|
||||
|
||||
sciter::dispatch_script_call! {
|
||||
fn t(String);
|
||||
fn get_icon();
|
||||
fn close(i32);
|
||||
fn authorize(i32);
|
||||
@ -405,12 +410,14 @@ async fn start_pa() {
|
||||
break;
|
||||
}
|
||||
let spec = pulse::sample::Spec {
|
||||
format: pulse::sample::Format::F32be,
|
||||
format: pulse::sample::Format::F32le,
|
||||
channels: 2,
|
||||
rate: crate::platform::linux::PA_SAMPLE_RATE,
|
||||
};
|
||||
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
|
||||
APP_NAME, // Our application’s name
|
||||
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 buffering attributes
|
||||
) {
|
||||
loop {
|
||||
Ok(s) => loop {
|
||||
if let Some(Err(_)) = stream.next_timeout2(1).await {
|
||||
break;
|
||||
}
|
||||
let mut out: Vec<u8> = Vec::with_capacity(480 * 4);
|
||||
unsafe {
|
||||
out.set_len(out.capacity());
|
||||
}
|
||||
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);
|
||||
}
|
||||
if let Ok(_) = s.read(&mut buf) {
|
||||
allow_err!(
|
||||
stream.send(&Data::RawMessage(buf.clone())).await
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("Could not create simple pulse: {}", err);
|
||||
}
|
||||
} else {
|
||||
log::error!("Could not create simple pulse");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -33,22 +33,22 @@ class Body: Reactor.Component
|
||||
<div>
|
||||
<div .id style="font-weight: bold; font-size: 1.2em;">{c.name}</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 />
|
||||
{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>
|
||||
<div class={!c.keyboard ? "disabled" : ""} title="Allow using keyboard and mouse"><icon .keyboard /></div>
|
||||
<div class={!c.clipboard ? "disabled" : ""} title="Allow using clipboard"><icon .clipboard /></div>
|
||||
<div class={!c.audio ? "disabled" : ""} title="Allow hearing sound"><icon .audio /></div>
|
||||
<div class={!c.keyboard ? "disabled" : ""} title={translate('Allow using keyboard and mouse')}><icon .keyboard /></div>
|
||||
<div class={!c.clipboard ? "disabled" : ""} title={translate('Allow using clipboard')}><icon .clipboard /></div>
|
||||
<div class={!c.audio ? "disabled" : ""} title={translate('Allow hearing sound')}><icon .audio /></div>
|
||||
</div>}
|
||||
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
|
||||
<div style="size:*"/>
|
||||
<div .buttons>
|
||||
{auth ? "" : <button .button tabindex="-1" #accept>Accept</button>}
|
||||
{auth ? "" : <button .button tabindex="-1" .outline #dismiss>Dismiss</button>}
|
||||
{auth ? <button .button tabindex="-1" #disconnect>Disconnect</button> : ""}
|
||||
{auth ? "" : <button .button tabindex="-1" #accept>{translate('Accept')}</button>}
|
||||
{auth ? "" : <button .button tabindex="-1" .outline #dismiss>{translate('Dismiss')}</button>}
|
||||
{auth ? <button .button tabindex="-1" #disconnect>{translate('Disconnect')}</button> : ""}
|
||||
</div>
|
||||
{c.is_file_transfer || c.port_forward ? "" : <div .chaticon>{svg_chat}</div>}
|
||||
</div>
|
||||
@ -101,6 +101,9 @@ class Body: Reactor.Component
|
||||
connection.authorized = true;
|
||||
body.update();
|
||||
handler.authorize(cid);
|
||||
self.timer(30ms, function() {
|
||||
view.windowState = View.WINDOW_MINIMIZED;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ html {
|
||||
var(gray-bg): #eee;
|
||||
var(bg): white;
|
||||
var(border): #ccc;
|
||||
var(hover-border): #999;
|
||||
var(text): #222;
|
||||
var(placeholder): #aaa;
|
||||
var(lighter-text): #888;
|
||||
@ -52,6 +53,10 @@ button.button:active, button.active {
|
||||
border-color: color(accent);
|
||||
}
|
||||
|
||||
button.button:hover, button.outline:hover {
|
||||
border-color: color(hover-border);
|
||||
}
|
||||
|
||||
input[type=text], input[type=password], input[type=number] {
|
||||
width: *;
|
||||
font-size: 1.5em;
|
||||
@ -156,9 +161,7 @@ header caption {
|
||||
top: 0;
|
||||
padding: 0 10px;
|
||||
width: 22px;
|
||||
height: *;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
color: black;
|
||||
border: none;
|
||||
background: none;
|
||||
@ -321,5 +324,5 @@ menu li.selected span {
|
||||
|
||||
menu li.line-through {
|
||||
text-decoration-line: line-through;
|
||||
color: grey;
|
||||
color: red;
|
||||
}
|
||||
|
@ -10,6 +10,15 @@ var is_file_transfer;
|
||||
var is_xfce = false;
|
||||
try { is_xfce = handler.is_xfce(); } catch(e) {}
|
||||
|
||||
|
||||
function translate(name) {
|
||||
try {
|
||||
return handler.t(name);
|
||||
} catch(_) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
function hashCode(str) {
|
||||
var hash = 160 << 16 + 114 << 8 + 91;
|
||||
for (var i = 0; i < str.length; i += 1) {
|
||||
@ -207,7 +216,19 @@ function getMsgboxParams() {
|
||||
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;
|
||||
if (!has_msgbox && !type) return;
|
||||
var remember = false;
|
||||
@ -217,7 +238,8 @@ function msgbox(type, title, text, callback, height, width, retry=0) {
|
||||
msgbox_params = {
|
||||
remember: remember, type: type, text: text, title: title,
|
||||
getParams: getMsgboxParams,
|
||||
callback: callback, retry: retry,
|
||||
callback: callback, translate: translate,
|
||||
retry: retry, contentStyle: contentStyle,
|
||||
};
|
||||
if (has_msgbox) return;
|
||||
var dialog = {
|
||||
@ -239,8 +261,8 @@ function msgbox(type, title, text, callback, height, width, retry=0) {
|
||||
} else if (res == "!alive") {
|
||||
// do nothing
|
||||
} else if (res.type == "input-password") {
|
||||
if (!is_port_forward) connecting();
|
||||
handler.login(res.password, res.remember);
|
||||
if (!is_port_forward) msgbox("connecting", "Connecting...", "Logging in...");
|
||||
} else if (res.reconnect) {
|
||||
if (!is_port_forward) connecting();
|
||||
handler.reconnect();
|
||||
@ -251,19 +273,13 @@ function connecting() {
|
||||
handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait.");
|
||||
}
|
||||
|
||||
handler.msgbox = function(type, title, text, callback=null, height=180, width=500, retry=0) {
|
||||
// directly call view.Dialog from native may crash, add timer here, seem safe
|
||||
// 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);
|
||||
handler.msgbox = function(type, title, text, retry=0) {
|
||||
self.timer(30ms, function() { msgbox(type, title, text, null, 180, 500, retry); });
|
||||
}
|
||||
|
||||
var reconnectTimeout = 1;
|
||||
handler.msgbox_retry = function(type, title, text, hasRetry, callback=null, height=180, width=500) {
|
||||
handler.msgbox(type, title, text, callback, height, width, hasRetry ? reconnectTimeout : 0);
|
||||
handler.msgbox_retry = function(type, title, text, hasRetry) {
|
||||
handler.msgbox(type, title, text, hasRetry ? reconnectTimeout : 0);
|
||||
if (hasRetry) {
|
||||
reconnectTimeout *= 2;
|
||||
} else {
|
||||
@ -311,3 +327,52 @@ function Progress()
|
||||
|
||||
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) {
|
||||
if (!job.entries) return "Waiting";
|
||||
if (!job.entries) return translate("Waiting");
|
||||
var i = job.file_num + 1;
|
||||
var n = job.num_entries || job.entries.length;
|
||||
if (i > n) i = n;
|
||||
var res = i + ' / ' + n + " files";
|
||||
var res = i + ' / ' + n + " " + translate("files");
|
||||
if (job.total_size > 0) {
|
||||
var s = getSize(0, job.finished_size);
|
||||
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();
|
||||
if (job.finished) percent = '100';
|
||||
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";
|
||||
return res;
|
||||
}
|
||||
@ -250,7 +250,7 @@ class FolderView : Reactor.Component {
|
||||
return <div .title>
|
||||
{svg_computer}
|
||||
<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>
|
||||
}
|
||||
|
||||
@ -273,7 +273,7 @@ class FolderView : Reactor.Component {
|
||||
function renderOpBar() {
|
||||
if (this.is_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 .add-folder .button>{svg_add_folder}</div>
|
||||
<div .trash .button>{svg_trash}</div>
|
||||
@ -283,7 +283,7 @@ class FolderView : Reactor.Component {
|
||||
<div .add-folder .button>{svg_add_folder}</div>
|
||||
<div .trash .button>{svg_trash}</div>
|
||||
<div .spacer></div>
|
||||
<div .send .button><span>Send</span>{svg_send}</div>
|
||||
<div .send .button><span>{translate('Send')}</span>{svg_send}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@ -308,14 +308,14 @@ class FolderView : Reactor.Component {
|
||||
var id = (this.is_remote ? "remote" : "local") + "-folder-view";
|
||||
return <table @{this.table} .folder-view .has_current id={id}>
|
||||
<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>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
<popup>
|
||||
<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>
|
||||
</popup>
|
||||
</table>;
|
||||
@ -431,8 +431,8 @@ class FolderView : Reactor.Component {
|
||||
|
||||
event click $(.add-folder) () {
|
||||
var me = this;
|
||||
handler.msgbox("custom", "Create Folder", "<div .form> \
|
||||
<div>Please enter the folder name:</div> \
|
||||
msgbox("custom", translate("Create Folder"), "<div .form> \
|
||||
<div>" + translate("Please enter the folder name") + ":</div> \
|
||||
<div><input|text(name) .outline-focus /></div> \
|
||||
</div>", function(res=null) {
|
||||
if (!res) return;
|
||||
@ -523,7 +523,7 @@ class FolderView : Reactor.Component {
|
||||
var file_transfer;
|
||||
|
||||
class FileTransfer: Reactor.Component {
|
||||
function this(params) {
|
||||
function this() {
|
||||
file_transfer = this;
|
||||
}
|
||||
|
||||
@ -576,12 +576,12 @@ handler.jobDone = function(id, file_num = -1) {
|
||||
handler.jobError = function(id, err, file_num = -1) {
|
||||
var job = deleting_single_file_jobs[id];
|
||||
if (job) {
|
||||
handler.msgbox("custom-error", "Delete File", err);
|
||||
msgbox("custom-error", "Delete File", err);
|
||||
return;
|
||||
}
|
||||
job = create_dir_jobs[id];
|
||||
if (job) {
|
||||
handler.msgbox("custom-error", "Create Folder", err);
|
||||
msgbox("custom-error", "Create Folder", err);
|
||||
return;
|
||||
}
|
||||
if (file_num < 0) {
|
||||
@ -599,8 +599,8 @@ var deleting_single_file_jobs = {};
|
||||
var create_dir_jobs = {}
|
||||
|
||||
function confirmDelete(path, is_remote) {
|
||||
handler.block_msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
||||
<div>Are you sure you want to delete this file?</div> \
|
||||
msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
||||
<div>" + translate('Are you sure you want to delete this file?') + "</div> \
|
||||
<div.ellipsis style=\"font-weight: bold;\">" + path + "</div> \
|
||||
</div>", function(res=null) {
|
||||
if (res) {
|
||||
@ -619,11 +619,11 @@ handler.confirmDeleteFiles = function(id, i, name) {
|
||||
if (i >= n) return;
|
||||
var file_path = job.path;
|
||||
if (name) file_path += handler.get_path_sep(job.is_remote) + name;
|
||||
handler.block_msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
||||
<div>Deleting #" + (i + 1) + " of " + n + " files.</div> \
|
||||
<div>Are you sure you want to delete this file?</div> \
|
||||
msgbox("custom-skip", "Confirm Delete", "<div .form> \
|
||||
<div>" + translate('Deleting') + " #" + (i + 1) + " / " + n + " " + translate('files') + ".</div> \
|
||||
<div>" + translate('Are you sure you want to delete this file?') + "</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) {
|
||||
if (!res) {
|
||||
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 {
|
||||
const TABLE_HEADER_CLICK = 0x81;
|
||||
const TABLE_ROW_CLICK = 0x82;
|
||||
@ -144,7 +153,7 @@ class Grid: Behavior {
|
||||
|
||||
function onKey(evt)
|
||||
{
|
||||
|
||||
last_key_time = getTime();
|
||||
if (evt.type != Event.KEY_DOWN)
|
||||
return false;
|
||||
|
||||
|
@ -42,6 +42,24 @@ header .remote-id {
|
||||
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 {
|
||||
color: black;
|
||||
background: color(gray-bg);
|
||||
|
@ -31,6 +31,10 @@ if (is_linux) {
|
||||
}
|
||||
}
|
||||
|
||||
function get_id() {
|
||||
return handler.get_option('alias') || handler.get_id()
|
||||
}
|
||||
|
||||
function stateChanged() {
|
||||
stdout.println('state changed from ' + cur_window_state + ' -> ' + view.windowState);
|
||||
cur_window_state = view.windowState;
|
||||
@ -58,7 +62,7 @@ var old_window_state = View.WINDOW_SHOWN;
|
||||
var input_blocked;
|
||||
|
||||
class Header: Reactor.Component {
|
||||
function this(params) {
|
||||
function this() {
|
||||
header = this;
|
||||
}
|
||||
|
||||
@ -67,18 +71,18 @@ class Header: Reactor.Component {
|
||||
var title_conn;
|
||||
if (this.secure_connection && this.direct_connection) {
|
||||
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) {
|
||||
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) {
|
||||
icon_conn = svg_insecure;
|
||||
title_conn = "Direct and insecure connection";
|
||||
title_conn = translate("Direct and unencrypted connection");
|
||||
} else {
|
||||
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.displays || []).length == 0) {
|
||||
return <div .ellipsis style="size:*;text-align:center;margin:*;">{title}</div>;
|
||||
@ -89,14 +93,14 @@ class Header: Reactor.Component {
|
||||
</div>;
|
||||
});
|
||||
updateWindowToolbarPosition();
|
||||
var style = "flow: horizontal;";
|
||||
if (is_osx) style += "margin: *";
|
||||
var style = "flow:horizontal;";
|
||||
if (is_osx) style += "margin:*";
|
||||
self.timer(1ms, toggleMenuState);
|
||||
return <div style={style}>
|
||||
{is_osx || is_xfce ? "" : <span #fullscreen>{svg_fullscreen}</span>}
|
||||
<div #screens>
|
||||
<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>
|
||||
{this.renderGlobalScreens()}
|
||||
</div>
|
||||
@ -111,22 +115,22 @@ class Header: Reactor.Component {
|
||||
function renderDisplayPop() {
|
||||
return <popup>
|
||||
<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"/>
|
||||
<li #original type="view-style"><span>{svg_checkmark}</span>Original</li>
|
||||
<li #shrink type="view-style"><span>{svg_checkmark}</span>Shrink</li>
|
||||
<li #stretch type="view-style"><span>{svg_checkmark}</span>Stretch</li>
|
||||
<li #original type="view-style"><span>{svg_checkmark}</span>{translate('Original')}</li>
|
||||
<li #shrink type="view-style"><span>{svg_checkmark}</span>{translate('Shrink')}</li>
|
||||
<li #stretch type="view-style"><span>{svg_checkmark}</span>{translate('Stretch')}</li>
|
||||
<div .separator />
|
||||
<li #best type="image-quality"><span>{svg_checkmark}</span>Good image quality</li>
|
||||
<li #balanced type="image-quality"><span>{svg_checkmark}</span>Balanced</li>
|
||||
<li #low type="image-quality"><span>{svg_checkmark}</span>Optimize reaction time</li>
|
||||
<li #custom type="image-quality"><span>{svg_checkmark}</span>Custom</li>
|
||||
<li #best type="image-quality"><span>{svg_checkmark}</span>{translate('Good image quality')}</li>
|
||||
<li #balanced type="image-quality"><span>{svg_checkmark}</span>{translate('Balanced')}</li>
|
||||
<li #low type="image-quality"><span>{svg_checkmark}</span>{translate('Optimize reaction time')}</li>
|
||||
<li #custom type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li>
|
||||
<div .separator />
|
||||
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>Show remote cursor</li>
|
||||
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>Mute</li> : ""}
|
||||
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>Disable clipboard</li> : ""}
|
||||
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>Lock after session end</li> : ""}
|
||||
{false && pi.platform == "Windows" ? <li #privacy-mode .toggle-option><span>{svg_checkmark}</span>Privacy mode</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>{translate('Mute')}</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>{translate('Lock after session end')}</li> : ""}
|
||||
{false && pi.platform == "Windows" ? <li #privacy-mode .toggle-option><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
|
||||
</menu>
|
||||
</popup>;
|
||||
}
|
||||
@ -134,23 +138,20 @@ class Header: Reactor.Component {
|
||||
function renderActionPop() {
|
||||
return <popup>
|
||||
<menu.context #action-options>
|
||||
<li #transfer-file>Transfer File</li>
|
||||
<li #tunnel>TCP Tunneling</li>
|
||||
<li #transfer-file>{translate('Transfer File')}</li>
|
||||
<li #tunnel>{translate('TCP Tunneling')}</li>
|
||||
<div .separator />
|
||||
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>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>}
|
||||
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>{translate('Insert')} Ctrl + Alt + Del</li> : ""}
|
||||
<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> : ""}
|
||||
{handler.support_refresh() ? <li #refresh>Refresh</li> : ""}
|
||||
{handler.support_refresh() ? <li #refresh>{translate('Refresh')}</li> : ""}
|
||||
</menu>
|
||||
</popup>;
|
||||
}
|
||||
|
||||
function renderGlobalScreens() {
|
||||
if (pi.displays.length < 2) return "";
|
||||
if (pi.displays.length < 3) return "";
|
||||
var x0 = 9999999;
|
||||
var y0 = 9999999;
|
||||
var x = -9999999;
|
||||
@ -233,18 +234,6 @@ class Header: Reactor.Component {
|
||||
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) {
|
||||
handler.lock_screen();
|
||||
}
|
||||
@ -288,9 +277,9 @@ function handle_custom_image_quality() {
|
||||
var tmp = handler.get_custom_image_quality();
|
||||
var bitrate0 = tmp[0] || 50;
|
||||
var quantizer0 = tmp.length > 1 ? tmp[1] : 100;
|
||||
handler.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: 66%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
|
||||
msgbox("custom", "Custom Image Quality", "<div .form> \
|
||||
<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: 50%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
|
||||
</div>", function(res=null) {
|
||||
if (!res) return;
|
||||
if (!res.bitrate) return;
|
||||
@ -408,7 +397,7 @@ function startChat() {
|
||||
height: h,
|
||||
client: true,
|
||||
parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon },
|
||||
caption: handler.get_id(),
|
||||
caption: get_id(),
|
||||
};
|
||||
var html = handler.get_chatbox();
|
||||
if (html) params.html = html;
|
||||
|
127
src/ui/index.css
127
src/ui/index.css
@ -39,6 +39,77 @@ body {
|
||||
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 {
|
||||
border-spacing: 0.5em;
|
||||
border-left: color(accent) solid 2px;
|
||||
@ -132,13 +203,6 @@ div.recent-sessions-content {
|
||||
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 {
|
||||
border-radius: 1em;
|
||||
height: 140px;
|
||||
@ -148,7 +212,7 @@ div.remote-session {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.remote-session:hover {
|
||||
div.remote-session:hover, div.remote-session-list:hover {
|
||||
outline: color(button) solid 2px -2px;
|
||||
}
|
||||
|
||||
@ -176,6 +240,41 @@ div.remote-session .platform svg {
|
||||
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 {
|
||||
background: white;
|
||||
position: absolute;
|
||||
@ -200,20 +299,22 @@ svg#menu {
|
||||
color: color(light-text);
|
||||
}
|
||||
|
||||
svg#menu:active {
|
||||
.remote-session-list svg#menu {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
svg#menu:hover {
|
||||
color: black;
|
||||
border-radius: 1em;
|
||||
background: color(gray-bg);
|
||||
}
|
||||
|
||||
svg#edit:active {
|
||||
opacity: 0.5;
|
||||
svg#edit:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
svg#edit {
|
||||
display: inline-block;
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.install-me, div.trust-me {
|
||||
|
@ -7,6 +7,7 @@
|
||||
</style>
|
||||
<script type="text/tiscript">
|
||||
include "common.tis";
|
||||
include "ab.tis";
|
||||
include "index.tis";
|
||||
</script>
|
||||
</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"/>
|
||||
</svg>;
|
||||
|
||||
var my_id = "";
|
||||
function get_id() {
|
||||
my_id = handler.get_id();
|
||||
return my_id;
|
||||
}
|
||||
|
||||
class ConnectStatus: Reactor.Component {
|
||||
function render() {
|
||||
return
|
||||
<div .connect-status>
|
||||
<span class={"connect-status-icon connect-status" + (service_stopped ? 0 : connect_status)} />
|
||||
{this.getConnectStatusStr()}
|
||||
{service_stopped ? <span class="link">Start Service</span> : ""}
|
||||
{service_stopped ? <span .link #start-service>{translate('Start Service')}</span> : ""}
|
||||
</div>;
|
||||
}
|
||||
|
||||
function getConnectStatusStr() {
|
||||
if (service_stopped) {
|
||||
return "Service is not running";
|
||||
return translate("Service is not running");
|
||||
} else if (connect_status == -1) {
|
||||
return "Not ready. Please check your connection";
|
||||
return translate('not_ready_status');
|
||||
} 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) () {
|
||||
handler.set_option("stop-service", "");
|
||||
}
|
||||
}
|
||||
|
||||
class RecentSessions: Reactor.Component {
|
||||
function render() {
|
||||
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;
|
||||
});
|
||||
event click $(#start-service) () {
|
||||
handler.set_option("stop-service", "");
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,14 +58,32 @@ function createNewConnect(id, type) {
|
||||
id = id.replace(/\s/g, "");
|
||||
app.remote_id.value = formatId(id);
|
||||
if (!id) return;
|
||||
if (id == handler.get_id()) {
|
||||
handler.msgbox("custom-error", "Error", "You cannot connect to your own computer");
|
||||
if (id == my_id) {
|
||||
msgbox("custom-error", "Error", "You cannot connect to your own computer");
|
||||
return;
|
||||
}
|
||||
handler.set_remote_id(id);
|
||||
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 audioInputMenu;
|
||||
class AudioInputs: Reactor.Component {
|
||||
@ -154,10 +99,10 @@ class AudioInputs: Reactor.Component {
|
||||
inputs = ["Mute"].concat(inputs);
|
||||
var me = this;
|
||||
self.timer(1ms, function() { me.toggleMenuState() });
|
||||
return <li>Audio Input
|
||||
return <li>{translate('Audio Input')}
|
||||
<menu #audio-input key={inputs.length}>
|
||||
{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>
|
||||
</li>;
|
||||
@ -195,7 +140,6 @@ class MyIdMenu: Reactor.Component {
|
||||
}
|
||||
|
||||
function render() {
|
||||
var me = this;
|
||||
return <div #myid>
|
||||
{this.renderPop()}
|
||||
ID{svg_menu}
|
||||
@ -205,19 +149,20 @@ class MyIdMenu: Reactor.Component {
|
||||
function renderPop() {
|
||||
return <popup>
|
||||
<menu.context #config-options>
|
||||
<li #enable-keyboard><span>{svg_checkmark}</span>Enable Keyboard/Mouse</li>
|
||||
<li #enable-clipboard><span>{svg_checkmark}</span>Enable Clipboard</li>
|
||||
<li #enable-file-transfer><span>{svg_checkmark}</span>Enable File Transfer</li>
|
||||
<li #enable-tunnel><span>{svg_checkmark}</span>Enable TCP Tunneling</li>
|
||||
<li #enable-keyboard><span>{svg_checkmark}</span>{translate('Enable Keyboard/Mouse')}</li>
|
||||
<li #enable-clipboard><span>{svg_checkmark}</span>{translate('Enable Clipboard')}</li>
|
||||
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable File Transfer')}</li>
|
||||
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
|
||||
<AudioInputs />
|
||||
<div .separator />
|
||||
<li #whitelist title="Only whitelisted IP can access me">IP Whitelisting</li>
|
||||
<li #custom-server>ID/Relay Server</li>
|
||||
<li #whitelist title={translate('whitelist_tip')}>{translate('IP Whitelisting')}</li>
|
||||
<li #custom-server>{translate('ID/Relay Server')}</li>
|
||||
<li #socks5-server>{translate('Socks5 Proxy')}</li>
|
||||
<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 />
|
||||
<li #forum>Forum</li>
|
||||
<li #about>About {handler.get_app_name()}</li>
|
||||
<li #about>{translate('About')} {" "} {handler.get_app_name()}</li>
|
||||
</menu>
|
||||
</popup>;
|
||||
}
|
||||
@ -225,6 +170,7 @@ class MyIdMenu: Reactor.Component {
|
||||
event click $(svg#menu) (_, me) {
|
||||
audioInputMenu.update({ show: true });
|
||||
this.toggleMenuState();
|
||||
if (direct_server) direct_server.update();
|
||||
var menu = $(menu#config-options);
|
||||
me.popup(menu);
|
||||
}
|
||||
@ -245,8 +191,9 @@ class MyIdMenu: Reactor.Component {
|
||||
}
|
||||
if (me.id == "whitelist") {
|
||||
var old_value = handler.get_option("whitelist").split(",").join("\n");
|
||||
handler.msgbox("custom-whitelist", "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>\
|
||||
msgbox("custom-whitelist", translate("IP Whitelisting"), "<div .form> \
|
||||
<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> \
|
||||
", function(res=null) {
|
||||
if (!res) return;
|
||||
@ -255,7 +202,7 @@ class MyIdMenu: Reactor.Component {
|
||||
var values = value.split(/[\s,;\n]+/g);
|
||||
for (var ip in values) {
|
||||
if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
||||
return "Invalid ip: " + ip;
|
||||
return translate("Invalid IP") + ": " + ip;
|
||||
}
|
||||
}
|
||||
value = values.join("\n");
|
||||
@ -265,12 +212,12 @@ class MyIdMenu: Reactor.Component {
|
||||
handler.set_option("whitelist", value.replace("\n", ","));
|
||||
}, 300);
|
||||
} else if (me.id == "custom-server") {
|
||||
var configOptions = handler.get_options();
|
||||
var configOptions = handler.get_options();
|
||||
var old_relay = configOptions["relay-server"] || "";
|
||||
var old_id = configOptions["custom-rendezvous-server"] || "";
|
||||
handler.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'>Relay Server: </span><input style='width: 250px' name='relay' value='" + old_relay + "' /></div> \
|
||||
msgbox("custom-server", "ID/Relay Server", "<div .form> \
|
||||
<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'>" + translate("Relay Server") + ": </span><input style='width: 250px' name='relay' value='" + old_relay + "' /></div> \
|
||||
</div> \
|
||||
", function(res=null) {
|
||||
if (!res) return;
|
||||
@ -279,26 +226,46 @@ class MyIdMenu: Reactor.Component {
|
||||
if (id == old_id && relay == old_relay) return;
|
||||
if (id) {
|
||||
var err = handler.test_if_valid_server(id);
|
||||
if (err) return "ID Server: " + err;
|
||||
if (err) return translate("ID Server") + ": " + err;
|
||||
}
|
||||
if (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["relay-server"] = relay;
|
||||
handler.set_options(configOptions);
|
||||
});
|
||||
} else if (me.id == "forum") {
|
||||
handler.open_url("https:://forum.rustdesk.com");
|
||||
}, 240);
|
||||
} else if (me.id == "socks5-server") {
|
||||
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") {
|
||||
handler.set_option("stop-service", service_stopped ? "" : "Y");
|
||||
} else if (me.id == "about") {
|
||||
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 .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 \
|
||||
<br /> Author: Carrie \
|
||||
<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);
|
||||
return
|
||||
<div .app>
|
||||
<popup>
|
||||
<menu.context #remote-context>
|
||||
<li #connect>Connect</li>
|
||||
<li #transfer>Transfer File</li>
|
||||
<li #tunnel>TCP Tunneling</li>
|
||||
<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>
|
||||
<popup><menu.context #edit-password-context>
|
||||
<li #refresh-password>{translate('Refresh random password')}</li>
|
||||
<li #set-password>{translate('Set your own password')}</li>
|
||||
</menu></popup>
|
||||
<div .left-pane>
|
||||
<div>
|
||||
<div .title>Your Desktop</div>
|
||||
<div .lighter-text>Your desktop can be accessed with this ID and password.</div>
|
||||
<div .title>{translate('Your Desktop')}</div>
|
||||
<div .lighter-text>{translate('desk_tip')}</div>
|
||||
<div .your-desktop>
|
||||
<MyIdMenu />
|
||||
{key_confirmed ? <input type="text" readonly value={formatId(handler.get_id())}/> : "Generating ..."}
|
||||
<MyIdMenu />
|
||||
{key_confirmed ? <input type="text" readonly value={formatId(get_id())}/> : translate("Generating ...")}
|
||||
</div>
|
||||
<div .your-desktop>
|
||||
<div>Password</div>
|
||||
<div>{translate('Password')}</div>
|
||||
<Password />
|
||||
</div>
|
||||
</div>
|
||||
{handler.is_installed() ? "": <InstalllMe />}
|
||||
{handler.is_installed() ? "": <InstallMe />}
|
||||
{handler.is_installed() && software_update_url ? <UpdateMe /> : ""}
|
||||
{handler.is_installed() && !software_update_url && handler.is_installed_lower_version() ? <UpgradeMe /> : ""}
|
||||
{is_can_screen_recording ? "": <CanScreenRecording />}
|
||||
@ -364,14 +318,14 @@ class App: Reactor.Component
|
||||
<div .right-pane>
|
||||
<div .right-content>
|
||||
<div .card-connect>
|
||||
<div .title>Control Remote Desktop</div>
|
||||
<div .title>{translate('Control Remote Desktop')}</div>
|
||||
<ID @{this.remote_id} />
|
||||
<div .right-buttons>
|
||||
<button .button .outline #file-transfer>Transfer File</button>
|
||||
<button .button #connect>Connect</button>
|
||||
<button .button .outline #file-transfer>{translate('Transfer File')}</button>
|
||||
<button .button #connect>{translate('Connect')}</button>
|
||||
</div>
|
||||
</div>
|
||||
<RecentSessions @{this.recent_sessions} />
|
||||
<MultipleSessions @{this.multipleSessions} />
|
||||
</div>
|
||||
<ConnectStatus @{this.connect_status} />
|
||||
</div>
|
||||
@ -391,11 +345,12 @@ class App: Reactor.Component
|
||||
}
|
||||
}
|
||||
|
||||
class InstalllMe: Reactor.Component {
|
||||
class InstallMe: Reactor.Component {
|
||||
function render() {
|
||||
return <div .install-me>
|
||||
<div>Install RustDesk</div>
|
||||
<div #install-me .link>Install RustDesk on this computer ...</div>
|
||||
<span />
|
||||
<div>{translate('install_tip')}</div>
|
||||
<div style="text-align: center; margin-top: 1em;"><button #install-me .button>{translate('Install')}</button></div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@ -450,9 +405,9 @@ class UpgradeMe: Reactor.Component {
|
||||
function render() {
|
||||
var update_or_download = is_osx ? "download" : "update";
|
||||
return <div .install-me>
|
||||
<div>{handler.get_app_name()} Status</div>
|
||||
<div>An update is available for RustDesk.</div>
|
||||
<div #install-me .link style="padding-top: 1em">Click to upgrade</div>
|
||||
<div>{translate('Status')}</div>
|
||||
<div>{translate('Your installation is lower version.')}</div>
|
||||
<div #install-me .link style="padding-top: 1em">{translate('Click to upgrade')}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@ -463,9 +418,9 @@ class UpgradeMe: Reactor.Component {
|
||||
|
||||
class UpdateMe: Reactor.Component {
|
||||
function render() {
|
||||
var update_or_download = is_osx ? "download" : "update";
|
||||
var update_or_download = "download"; // !is_win ? "download" : "update";
|
||||
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 #install-me .link style="padding-top: 1em">Click to {update_or_download}</div>
|
||||
<div #download-percent style="display:hidden; padding-top: 1em;" />
|
||||
@ -473,18 +428,20 @@ class UpdateMe: Reactor.Component {
|
||||
}
|
||||
|
||||
event click $(#install-me) {
|
||||
if (is_osx) {
|
||||
handler.open_url("https://rustdesk.com");
|
||||
return;
|
||||
if (!is_win) {
|
||||
handler.open_url("https://rustdesk.com");
|
||||
return;
|
||||
}
|
||||
var url = software_update_url + '.' + handler.get_software_ext();
|
||||
var path = handler.get_software_store_path();
|
||||
var onsuccess = function(md5) {
|
||||
$(#download-percent).content("Installing ...");
|
||||
$(#download-percent).content(translate("Installing ..."));
|
||||
handler.update_me(path);
|
||||
};
|
||||
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) {
|
||||
if (!total) total = 5 * 1024 * 1024;
|
||||
@ -511,9 +468,9 @@ class SystemError: Reactor.Component {
|
||||
class TrustMe: Reactor.Component {
|
||||
function render() {
|
||||
return <div .trust-me>
|
||||
<div>Configuration Permissions</div>
|
||||
<div>In order to control your Desktop remotely, you need to grant RustDesk "Accessibility" permissions</div>
|
||||
<div #trust-me .link>Configure</div>
|
||||
<div>{translate('Configuration Permissions')}</div>
|
||||
<div>{translate('config_acc')}</div>
|
||||
<div #trust-me .link>{translate('Configure')}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@ -526,9 +483,9 @@ class TrustMe: Reactor.Component {
|
||||
class CanScreenRecording: Reactor.Component {
|
||||
function render() {
|
||||
return <div .trust-me>
|
||||
<div>Configuration Permissions</div>
|
||||
<div>In order to access your Desktop remotely, you need to grant RustDesk "Screen Recording" permissions</div>
|
||||
<div #screen-recording .link>Configure</div>
|
||||
<div>{translate('Configuration Permissions')}</div>
|
||||
<div>{translate('config_screen')}</div>
|
||||
<div #screen-recording .link>{translate('Configure')}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@ -541,9 +498,10 @@ class CanScreenRecording: Reactor.Component {
|
||||
class FixWayland: Reactor.Component {
|
||||
function render() {
|
||||
return <div .trust-me>
|
||||
<div>Warning</div>
|
||||
<div>Login screen using Wayland is not supported</div>
|
||||
<div #fix-wayland .link>Fix it</div>
|
||||
<div>{translate('Warning')}</div>
|
||||
<div>{translate('Login screen using Wayland is not supported')}</div>
|
||||
<div #fix-wayland .link>{translate('Fix it')}</div>
|
||||
<div style="text-align: center">({translate('Reboot required')})</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@ -556,15 +514,16 @@ class FixWayland: Reactor.Component {
|
||||
class ModifyDefaultLogin: Reactor.Component {
|
||||
function render() {
|
||||
return <div .trust-me>
|
||||
<div>Warning</div>
|
||||
<div>Current Wayland display server is not supported</div>
|
||||
<div #modify-default-login .link>Fix it(re-login required)</div>
|
||||
<div>{translate('Warning')}</div>
|
||||
<div>{translate('Current Wayland display server is not supported')}</div>
|
||||
<div #modify-default-login .link>{translate('Fix it')}</div>
|
||||
<div style="text-align: center">({translate('Reboot required')})</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
event click $(#modify-default-login) {
|
||||
if (var r = handler.modify_default_login()) {
|
||||
handler.msgbox("custom-error", "Error", r);
|
||||
msgbox("custom-error", "Error", r);
|
||||
}
|
||||
app.update();
|
||||
}
|
||||
@ -627,19 +586,19 @@ class Password: Reactor.Component {
|
||||
|
||||
event click $(li#set-password) {
|
||||
var me = this;
|
||||
handler.msgbox("custom-password", "Set Password", "<div .form .set-password> \
|
||||
<div><span>Password:</span><input|password(password) /></div> \
|
||||
<div><span>Confirmation:</span><input|password(confirmation) /></div> \
|
||||
msgbox("custom-password", translate("Set Password"), "<div .form .set-password> \
|
||||
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus /></div> \
|
||||
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) /></div> \
|
||||
</div> \
|
||||
", function(res=null) {
|
||||
if (!res) return;
|
||||
var p0 = (res.password || "").trim();
|
||||
var p1 = (res.confirmation || "").trim();
|
||||
if (p0.length < 6) {
|
||||
return "Too short, at least 6 characters.";
|
||||
return translate("Too short, at least 6 characters.");
|
||||
}
|
||||
if (p0 != p1) {
|
||||
return "The confirmation is not identical.";
|
||||
return translate("The confirmation is not identical.");
|
||||
}
|
||||
handler.update_password(p0);
|
||||
me.update();
|
||||
@ -649,7 +608,7 @@ class Password: Reactor.Component {
|
||||
|
||||
class ID: Reactor.Component {
|
||||
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())} />;
|
||||
}
|
||||
|
||||
@ -713,10 +672,10 @@ function self.closing() {
|
||||
|
||||
function self.ready() {
|
||||
var r = handler.get_size();
|
||||
if (r[2] == 0) {
|
||||
centerize(800, 600);
|
||||
} else {
|
||||
if (isReasonableSize(r) && r[2] > 0) {
|
||||
view.move(r[0], r[1], r[2], r[3]);
|
||||
} else {
|
||||
centerize(800, 600);
|
||||
}
|
||||
if (!handler.get_remote_id()) {
|
||||
view.focus = $(#remote_id);
|
||||
@ -740,6 +699,10 @@ function checkConnectStatus() {
|
||||
key_confirmed = tmp[1];
|
||||
app.update();
|
||||
}
|
||||
if (tmp[2] && tmp[2] != my_id) {
|
||||
stdout.println("id updated");
|
||||
app.update();
|
||||
}
|
||||
tmp = handler.get_error();
|
||||
if (system_error != tmp) {
|
||||
system_error = tmp;
|
||||
@ -752,7 +715,7 @@ function checkConnectStatus() {
|
||||
}
|
||||
if (handler.recent_sessions_updated()) {
|
||||
stdout.println("recent sessions updated");
|
||||
app.recent_sessions.update();
|
||||
app.update();
|
||||
}
|
||||
checkConnectStatus();
|
||||
});
|
||||
|
@ -5,17 +5,17 @@ function self.ready() {
|
||||
class Install: Reactor.Component {
|
||||
function render() {
|
||||
return <div .content>
|
||||
<div style="font-size: 2em;">Installation</div>
|
||||
<div style="margin: 2em 0;">Installation Path: <input|text disabled value={view.install_path()} /></div>
|
||||
<div><button|checkbox #startmenu checked>Create start menu shortcuts</button></div>
|
||||
<div><button|checkbox #desktopicon checked>Create desktop icon</button></div>
|
||||
<div #aggrement .link style="margin-top: 2em;">End-user license agreement</div>
|
||||
<div>By starting the installation, you accept the license agreement.</div>
|
||||
<div style="font-size: 2em;">{translate('Installation')}</div>
|
||||
<div style="margin: 2em 0;">{translate('Installation Path')} {": "}<input|text disabled value={view.install_path()} /></div>
|
||||
<div><button|checkbox #startmenu checked>{translate('Create start menu shortcuts')}</button></div>
|
||||
<div><button|checkbox #desktopicon checked>{translate('Create desktop icon')}</button></div>
|
||||
<div #aggrement .link style="margin-top: 2em;">{translate('End-user license agreement')}</div>
|
||||
<div>{translate('agreement_tip')}</div>
|
||||
<div style="height: 1px; background: gray; margin-top: 1em" />
|
||||
<div style="text-align: right;">
|
||||
<progress style={"color:" + color} style="display: none" />
|
||||
<button .button id="cancel" .outline style="margin-right: 2em;">Cancel</button>
|
||||
<button .button id="submit">Accept and Install</button>
|
||||
<button .button id="cancel" .outline style="margin-right: 2em;">{translate('Cancel')}</button>
|
||||
<button .button id="submit">{translate('Accept and Install')}</button>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
@ -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) {
|
||||
type = params.type;
|
||||
@ -7,7 +8,10 @@ function updateParams(params) {
|
||||
getParams = params.getParams;
|
||||
remember = params.remember;
|
||||
callback = params.callback;
|
||||
my_translate = params.translate;
|
||||
retry = params.retry;
|
||||
contentStyle = params.contentStyle;
|
||||
try { text = translate_text(text); } catch (e) {}
|
||||
if (retry > 0) {
|
||||
self.timer(retry * 1000, function() {
|
||||
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;
|
||||
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;
|
||||
|
||||
class Body: Reactor.Component {
|
||||
@ -74,9 +59,9 @@ class Body: Reactor.Component {
|
||||
function getInputPasswordContent() {
|
||||
var ts = remember ? { checked: true } : {};
|
||||
return <div .form>
|
||||
<div>Please enter your password</div>
|
||||
<Password />
|
||||
<div><button|checkbox(remember) {ts}>Remember password</button></div>
|
||||
<div>{my_translate('Please enter your password')}</div>
|
||||
<PasswordComponent />
|
||||
<div><button|checkbox(remember) {ts}>{my_translate('Remember password')}</button></div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@ -116,27 +101,27 @@ class Body: Reactor.Component {
|
||||
var me = this;
|
||||
self.timer(1ms, function() {
|
||||
if (typeof content == "string")
|
||||
me.$(#content).html = content;
|
||||
me.$(#content).html = my_translate(content);
|
||||
else
|
||||
me.$(#content).content(content);
|
||||
});
|
||||
return (
|
||||
<div style="size: *">
|
||||
<header style={"height: 2em; background: " + color}>
|
||||
<caption role="window-caption">{title}</caption>
|
||||
<caption role="window-caption">{my_translate(title)}</caption>
|
||||
</header>
|
||||
<div style="padding: 1em 2em; size: *;">
|
||||
<div style="height: *; flow: horizontal">
|
||||
{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 style="text-align: right;">
|
||||
<span #error />
|
||||
{show_progress ? <progress style={"color:" + color} /> : ""}
|
||||
{hasCancel || hasRetry ? <button .button #cancel .outline>{hasRetry ? "OK" : "Cancel"}</button> : ""}
|
||||
{this.hasSkip() ? <button .button #skip .outline>Skip</button> : ""}
|
||||
{hasOk || hasRetry ? <button .button #submit>{hasRetry ? "Retry" : "OK"}</button> : ""}
|
||||
{hasClose ? <button .button #cancel .outline>Close</button> : ""}
|
||||
<span style="display:inline-block; max-width: 260px; font-size:12px;" #error />
|
||||
<progress #progress style={"color:" + color + "; display: " + (show_progress ? "inline-block" : "none")} />
|
||||
{hasCancel || hasRetry ? <button .button #cancel .outline>{my_translate(hasRetry ? "OK" : "Cancel")}</button> : ""}
|
||||
{this.hasSkip() ? <button .button #skip .outline>{my_translate('Skip')}</button> : ""}
|
||||
{hasOk || hasRetry ? <button .button #submit>{my_translate(hasRetry ? "Retry" : "OK")}</button> : ""}
|
||||
{hasClose ? <button .button #cancel .outline>{my_translate('Close')}</button> : ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
@ -149,6 +134,17 @@ class Body: Reactor.Component {
|
||||
|
||||
$(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() {
|
||||
if ($(button#submit)) {
|
||||
$(button#submit).sendEvent("click");
|
||||
@ -211,9 +207,12 @@ event click $(button#submit) {
|
||||
}
|
||||
var values = getValues();
|
||||
if (callback) {
|
||||
var err = callback(values);
|
||||
var err = callback(values, show_progress);
|
||||
if (err && !err.trim()) {
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
$(#error).text = err;
|
||||
show_progress(false, err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -235,7 +234,7 @@ event keydown (evt) {
|
||||
|
||||
function set_outline_focus() {
|
||||
self.timer(30ms, function() {
|
||||
var el = $(input.outline-focus);
|
||||
var el = $(.outline-focus);
|
||||
if (el) view.focus = el;
|
||||
else {
|
||||
el = $(#submit);
|
||||
|
@ -21,17 +21,17 @@ class PortForward: Reactor.Component {
|
||||
});
|
||||
return <div #file-transfer><section>
|
||||
{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: 0.8em; color: #ddd">Don't close this window while you are using the tunnel</span>
|
||||
<span style="font-size: 1.2em">{translate('Listening ...')}</span><br/>
|
||||
<span style="font-size: 0.8em; color: #ddd">{translate('not_close_tcp_tip')}</span>
|
||||
</div> : ""}
|
||||
<table #port-forward>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Local Port</th>
|
||||
<th>{translate('Local Port')}</th>
|
||||
<th style="width: 1em" />
|
||||
<th>Remote Host</th>
|
||||
<th>Remote Port</th>
|
||||
{args.length ? "" : <th style="width: 6em">Action</th>}
|
||||
<th>{translate('Remote Host')}</th>
|
||||
<th>{translate('Remote Port')}</th>
|
||||
{args.length ? "" : <th style="width: 6em">{translate('Action')}</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody key={pfs.length}>
|
||||
@ -41,7 +41,7 @@ class PortForward: Reactor.Component {
|
||||
<td .right-arrow style="text-align: center">{svg_arrow}</td>
|
||||
<td><input|text #remote-host novalue="localhost" /></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>
|
||||
}
|
||||
{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)
|
||||
}
|
||||
|
||||
static mut IS_IN: bool = false;
|
||||
static mut KEYBOARD_HOOKED: bool = false;
|
||||
static mut KEYBOARD_ENABLED: bool = true;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HandlerInner {
|
||||
element: Option<Element>,
|
||||
sender: Option<mpsc::UnboundedSender<Data>>,
|
||||
thread: Option<std::thread::JoinHandle<()>>,
|
||||
close_state: HashMap<String, String>,
|
||||
last_down_key: Option<(String, i32, bool)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@ -65,7 +68,6 @@ pub struct Handler {
|
||||
id: String,
|
||||
args: Vec<String>,
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
super_on: bool,
|
||||
}
|
||||
|
||||
impl Deref for Handler {
|
||||
@ -147,6 +149,8 @@ impl sciter::EventHandler for Handler {
|
||||
fn get_id();
|
||||
fn get_default_pi();
|
||||
fn get_option(String);
|
||||
fn t(String);
|
||||
fn set_option(String, String);
|
||||
fn save_close_state(String, String);
|
||||
fn is_file_transfer();
|
||||
fn is_port_forward();
|
||||
@ -154,11 +158,9 @@ impl sciter::EventHandler for Handler {
|
||||
fn login(String, bool);
|
||||
fn new_rdp();
|
||||
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_space();
|
||||
fn alt_tab();
|
||||
fn super_x();
|
||||
fn transfer_file();
|
||||
fn tunnel();
|
||||
fn lock_screen();
|
||||
@ -218,6 +220,139 @@ impl Handler {
|
||||
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 {
|
||||
return self.lc.read().unwrap().view_style.clone();
|
||||
}
|
||||
@ -287,6 +422,10 @@ impl Handler {
|
||||
self.lc.read().unwrap().remember
|
||||
}
|
||||
|
||||
fn t(&self, name: String) -> String {
|
||||
crate::client::translate(name)
|
||||
}
|
||||
|
||||
fn is_xfce(&self) -> bool {
|
||||
crate::platform::is_xfce()
|
||||
}
|
||||
@ -409,6 +548,10 @@ impl Handler {
|
||||
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) {
|
||||
self.write().unwrap().close_state.insert(k, v);
|
||||
}
|
||||
@ -535,7 +678,6 @@ impl Handler {
|
||||
fn reconnect(&mut self) {
|
||||
let cloned = self.clone();
|
||||
let mut lock = self.write().unwrap();
|
||||
lock.last_down_key.take();
|
||||
lock.thread.take().map(|t| t.join());
|
||||
lock.thread = Some(std::thread::spawn(move || {
|
||||
io_loop(cloned);
|
||||
@ -629,6 +771,18 @@ impl Handler {
|
||||
self.send(Data::NewRDP);
|
||||
}
|
||||
|
||||
fn enter(&mut self) {
|
||||
unsafe {
|
||||
IS_IN = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn leave(&mut self) {
|
||||
unsafe {
|
||||
IS_IN = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn send_mouse(
|
||||
&mut self,
|
||||
mask: i32,
|
||||
@ -668,7 +822,6 @@ impl Handler {
|
||||
let evt_type = mask & 0x7;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -754,6 +907,7 @@ impl Handler {
|
||||
65300 => key_event.set_control_key(ControlKey::Scroll),
|
||||
65421 => key_event.set_control_key(ControlKey::NumpadEnter), // numpad enter
|
||||
65407 => key_event.set_control_key(ControlKey::NumLock),
|
||||
65515 => key_event.set_control_key(ControlKey::Meta),
|
||||
65516 => key_event.set_control_key(ControlKey::RWin),
|
||||
65513 => key_event.set_control_key(ControlKey::Alt),
|
||||
65514 => key_event.set_control_key(ControlKey::RAlt),
|
||||
@ -811,32 +965,20 @@ impl Handler {
|
||||
|
||||
fn ctrl_alt_del(&mut self) {
|
||||
if self.peer_platform() == "Windows" {
|
||||
let del = "CTRL_ALT_DEL".to_owned();
|
||||
self.key_down_or_up(1, del, 0, false, false, false, false, false);
|
||||
let mut key_event = KeyEvent::new();
|
||||
key_event.set_control_key(ControlKey::CtrlAltDel);
|
||||
self.key_down_or_up(1, key_event, false, false, false, false);
|
||||
} else {
|
||||
let del = "VK_DELETE".to_owned();
|
||||
self.key_down_or_up(1, del.clone(), 0, true, true, false, false, false);
|
||||
self.key_down_or_up(0, del, 0, true, true, false, false, false);
|
||||
let mut key_event = KeyEvent::new();
|
||||
key_event.set_control_key(ControlKey::Delete);
|
||||
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) {
|
||||
let lock = "LOCK_SCREEN".to_owned();
|
||||
self.key_down_or_up(1, lock, 0, false, false, false, false, false);
|
||||
let mut key_event = KeyEvent::new();
|
||||
key_event.set_control_key(ControlKey::LockScreen);
|
||||
self.key_down_or_up(1, key_event, false, false, false, false);
|
||||
}
|
||||
|
||||
fn transfer_file(&mut self) {
|
||||
@ -858,104 +1000,55 @@ impl Handler {
|
||||
fn key_down_or_up(
|
||||
&mut self,
|
||||
down_or_up: i32,
|
||||
name: String,
|
||||
code: i32,
|
||||
evt: KeyEvent,
|
||||
alt: bool,
|
||||
ctrl: bool,
|
||||
shift: 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
|
||||
// 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 key_event = evt;
|
||||
|
||||
let mut command = command;
|
||||
if self.super_on {
|
||||
command = true;
|
||||
if alt
|
||||
&& !crate::is_control_key(&key_event, &ControlKey::Alt)
|
||||
&& !crate::is_control_key(&key_event, &ControlKey::RAlt)
|
||||
{
|
||||
key_event.modifiers.push(ControlKey::Alt.into());
|
||||
}
|
||||
|
||||
if down_or_up == 0 {
|
||||
self.super_on = false;
|
||||
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());
|
||||
}
|
||||
|
||||
let mut name = name;
|
||||
#[cfg(target_os = "linux")]
|
||||
if code == 65383 {
|
||||
// VK_MENU
|
||||
name = "Apps".to_owned();
|
||||
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 extended {
|
||||
match name.as_ref() {
|
||||
"VK_CONTROL" => name = "RControl".to_owned(),
|
||||
"VK_MENU" => name = "RAlt".to_owned(),
|
||||
"VK_SHIFT" => name = "RShift".to_owned(),
|
||||
_ => {}
|
||||
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 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 let Some(mut key_event) = self.get_key_event(down_or_up, &name, code) {
|
||||
if alt
|
||||
&& !crate::is_control_key(&key_event, &ControlKey::Alt)
|
||||
&& !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));
|
||||
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));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -1132,6 +1225,9 @@ impl Remote {
|
||||
};
|
||||
match Client::start(&self.handler.id, conn_type).await {
|
||||
Ok((mut peer, direct)) => {
|
||||
unsafe {
|
||||
KEYBOARD_ENABLED = true;
|
||||
}
|
||||
self.handler
|
||||
.call("setConnectionType", &make_args!(peer.is_secured(), direct));
|
||||
loop {
|
||||
@ -1191,6 +1287,9 @@ impl Remote {
|
||||
if let Some(stop) = stop_clipboard {
|
||||
stop.send(()).ok();
|
||||
}
|
||||
unsafe {
|
||||
KEYBOARD_ENABLED = false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
match p.permission.enum_value_or_default() {
|
||||
Permission::Keyboard => {
|
||||
unsafe {
|
||||
KEYBOARD_ENABLED = p.enabled;
|
||||
}
|
||||
self.handler
|
||||
.call("setPermission", &make_args!("keyboard", p.enabled));
|
||||
}
|
||||
@ -1738,6 +1840,7 @@ impl Interface for Handler {
|
||||
crate::platform::windows::add_recent_document(&path);
|
||||
}
|
||||
}
|
||||
self.start_keyboard_hook();
|
||||
}
|
||||
|
||||
async fn handle_hash(&mut self, hash: Hash, peer: &mut Stream) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
var cursor_img = $(img#cursor);
|
||||
var last_key_time = 0;
|
||||
is_file_transfer = handler.is_file_transfer();
|
||||
var is_port_forward = handler.is_port_forward();
|
||||
var display_width = 0;
|
||||
@ -72,46 +71,14 @@ function adaptDisplay() {
|
||||
// https://sciter.com/docs/content/sciter/Event.htm
|
||||
|
||||
var entered = false;
|
||||
|
||||
var keymap = {};
|
||||
for (var (k, v) in Event) {
|
||||
k = k + ""
|
||||
if (k[0] == "V" && k[1] == "K") {
|
||||
keymap[v] = k;
|
||||
if (!is_file_transfer && !is_port_forward) {
|
||||
self.onKey = function(evt) {
|
||||
if (!entered) return false;
|
||||
// so that arrow key not move scrollbar
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 last_mouse_mask;
|
||||
var acc_wheel_delta_x = 0;
|
||||
@ -173,10 +140,14 @@ function handler.onMouse(evt)
|
||||
wait_window_toolbar = true;
|
||||
self.timer(300ms, function() {
|
||||
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) {
|
||||
$(header).style.set {
|
||||
display: "block",
|
||||
padding: (2 * workarea_offset) + "px 0 0 0",
|
||||
padding: (2 * workarea_offset + extra) + "px 0 0 0",
|
||||
};
|
||||
}
|
||||
wait_window_toolbar = false;
|
||||
@ -280,10 +251,12 @@ function handler.onMouse(evt)
|
||||
case Event.MOUSE_ENTER:
|
||||
entered = true;
|
||||
stdout.println("enter");
|
||||
handler.enter();
|
||||
return keyboard_enabled;
|
||||
case Event.MOUSE_LEAVE:
|
||||
entered = false;
|
||||
stdout.println("leave");
|
||||
handler.leave();
|
||||
return keyboard_enabled;
|
||||
default:
|
||||
return false;
|
||||
@ -396,7 +369,7 @@ function self.ready() {
|
||||
var h = 640;
|
||||
if (is_file_transfer || is_port_forward) {
|
||||
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]);
|
||||
} else {
|
||||
centerize(w, h);
|
||||
@ -418,7 +391,7 @@ handler.adaptSize = function() {
|
||||
var (fx, fy, fw, fh) = view.screenBox(#frame, #rectw);
|
||||
if (is_osx) workarea_offset = sy;
|
||||
var r = handler.get_size();
|
||||
if (r[2] > 0) {
|
||||
if (isReasonableSize(r) && r[2] > 0) {
|
||||
if (r[2] >= fw && r[3] >= fh && !is_linux) {
|
||||
view.windowState = View.WINDOW_FULL_SCREEN;
|
||||
stdout.println("Initialize to full screen");
|
||||
|
Loading…
Reference in New Issue
Block a user