Merge branch 'master' of https://github.com/rustdesk/rustdesk into master

This commit is contained in:
rustdesk 2021-06-14 17:52:33 +08:00
commit e06217536b
34 changed files with 952 additions and 110 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
github: [rustdesk]
ko_fi: rustdesk

49
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,49 @@
# Contributing to RustDesk
RustDesk welcomes contribution from everyone. Here are the guidelines if you are
thinking of helping us:
## Contributions
Contributions to RustDesk or its dependencies should be made in the form of GitHub
pull requests. Each pull request will be reviewed by a core contributor
(someone with permission to land patches) and either landed in the main tree or
given feedback for changes that would be required. All contributions should
follow this format, even those from core contributors.
Should you wish to work on an issue, please claim it first by commenting on
the GitHub issue that you want to work on it. This is to prevent duplicated
efforts from contributors on the same issue.
## Pull Request Checklist
- Branch from the master branch and, if needed, rebase to the current master
branch before submitting your pull request. If it doesn't merge cleanly with
master you may be asked to rebase your changes.
- Commits should be as small as possible, while ensuring that each commit is
correct independently (i.e., each commit should compile and pass tests).
- Commits should be accompanied by a Developer Certificate of Origin
(http://developercertificate.org) sign-off, which indicates that you (and
your employer if applicable) agree to be bound by the terms of the
[project license](LICENSE.md). In git, this is the `-s` option to `git commit`
- If your patch is not getting reviewed or you need a specific person to review
it, you can @-reply a reviewer asking for a review in the pull request or a
comment, or you can ask for a review via [email](mailto:info@rustdesk.com).
- Add tests relevant to the fixed bug or new feature.
For specific git instructions, see [GitHub workflow 101](https://github.com/servo/servo/wiki/Github-workflow).
## Conduct
We follow the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct).
## Communication
RustDesk contributors frequent the [Discord](https://discord.gg/nDceKgxnkV).

30
Cargo.lock generated
View File

@ -461,10 +461,11 @@ dependencies = [
[[package]]
name = "confy"
version = "0.4.1"
source = "git+https://github.com/open-trade/confy#27fa12941291b44ccd856aef4a5452c1eb646047"
dependencies = [
"directories",
"serde 1.0.125",
"serde_derive",
"serde_yaml",
"toml",
]
@ -1763,6 +1764,12 @@ dependencies = [
"num-traits 0.2.14",
]
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "lock_api"
version = "0.3.4"
@ -3196,6 +3203,18 @@ dependencies = [
"serde 1.0.125",
]
[[package]]
name = "serde_yaml"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
dependencies = [
"dtoa",
"linked-hash-map",
"serde 1.0.125",
"yaml-rust",
]
[[package]]
name = "sha2"
version = "0.9.3"
@ -4115,6 +4134,15 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "yansi"
version = "0.5.0"

View File

@ -1,6 +1,12 @@
# RustDesk | Your Remote Desktop Software
The best open-source remote desktop client software, written in Rust. Works out of the box, no configuration required. Great alternative to TeamViewer and AnyDesk! You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/blog/id-relay-set/), or write your own rendezvous/relay server.
Chat with us: [Discord](https://discord.gg/nDceKgxnkV)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
An open-source remote desktop client software, written in Rust. Works out of the box, no configuration required. Great alternative to TeamViewer and AnyDesk! You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/blog/id-relay-set/), or [write your own rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo).
RustDesk welcomes contribution from everyone. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for help getting started.
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
@ -8,10 +14,9 @@ The best open-source remote desktop client software, written in Rust. Works out
Desktop versions use [sciter](https://sciter.com/) for GUI, please download sciter dynamic library yourself.
[Windows](https://github.com/c-smile/sciter-sdk/blob/dc65744b66389cd5a0ff6bdb7c63a8b7b05a708b/bin.win/x64/sciter.dll)
[Linux](https://github.com/c-smile/sciter-sdk/raw/dc65744b66389cd5a0ff6bdb7c63a8b7b05a708b/bin.lnx/x64/libsciter-gtk.so)
[Osx](https://github.com/c-smile/sciter-sdk/raw/dc65744b66389cd5a0ff6bdb7c63a8b7b05a708b/bin.osx/sciter-osx-64.dylib)
[Windows](https://github.com/c-smile/sciter-sdk/blob/dc65744b66389cd5a0ff6bdb7c63a8b7b05a708b/bin.win/x64/sciter.dll) |
[Linux](https://github.com/c-smile/sciter-sdk/raw/dc65744b66389cd5a0ff6bdb7c63a8b7b05a708b/bin.lnx/x64/libsciter-gtk.so) |
[macOS](https://github.com/c-smile/sciter-sdk/raw/dc65744b66389cd5a0ff6bdb7c63a8b7b05a708b/bin.osx/sciter-osx-64.dylib)
## Raw steps to build
* Prepare your Rust development env and C++ build env
@ -37,7 +42,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
### Arch (Manjaro)
```
sudo pacman -Syu unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### Install vcpkg
@ -94,5 +99,3 @@ RustDesk does not support Wayland. Check [this](https://docs.fedoraproject.org/e
![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png)
![image](https://user-images.githubusercontent.com/71636191/113112990-65e2fd80-923c-11eb-840e-349b4d6e340d.png)

14
libs/confy/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
/target
**/*.rs.bk
Cargo.lock

26
libs/confy/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "confy"
version = "0.4.1"
authors = ["Katharina Fey <kookie@spacekookie.de>"]
description = "Boilerplate-free configuration management"
license = "MIT/X11 OR Apache-2.0"
documentation = "https://docs.rs/confy"
repository = "https://github.com/rust-clique/confy"
edition = "2018"
[dependencies]
serde = "^1.0"
toml = { version = "^0.5", optional = true }
directories = "^2.0"
serde_yaml = { version = "0.8", optional = true }
[features]
default = ["toml_conf"]
toml_conf = ["toml"]
yaml_conf = ["serde_yaml"]
[[example]]
name = "simple"
[dev-dependencies]
serde_derive = "^1.0"

21
libs/confy/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 rust-clique
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

39
libs/confy/README.md Normal file
View File

@ -0,0 +1,39 @@
# confy
Chat with us: [Discord](https://discord.gg/dwq4Zme)
Zero-boilerplate configuration management.
Focus on storing the right data, instead of worrying about how or where to store it.
```rust
use serde_derive::{Serialize, Deserialize};
#[derive(Default, Debug, Serialize, Deserialize)]
struct MyConfig {
version: u8,
api_key: String,
}
fn main() -> Result<(), ::std::io::Error> {
let cfg: MyConfig = confy::load("my-app-name")?;
dbg!(cfg);
Ok(())
}
```
## Using yaml
Enabling the `yaml_conf` feature while disabling the default `toml_conf`
feature causes confy to use a YAML config file instead of TOML.
```
[dependencies.confy]
features = ["yaml_conf"]
default-features = false
```
## Breakings changes
Starting with version 0.4.0 the configuration file are stored in the expected place for your system. See the [`directories`] crates for more information.
Before version 0.4.0, the configuration file was written in the current directory.
[`directories`]: https://crates.io/crates/directories

View File

@ -0,0 +1,29 @@
//! The most simplest examples of how to use confy
extern crate confy;
#[macro_use]
extern crate serde_derive;
#[derive(Debug, Serialize, Deserialize)]
struct ConfyConfig {
name: String,
comfy: bool,
foo: i64,
}
impl Default for ConfyConfig {
fn default() -> Self {
ConfyConfig {
name: "Unknown".to_string(),
comfy: true,
foo: 42,
}
}
}
fn main() -> Result<(), confy::ConfyError> {
let cfg: ConfyConfig = confy::load("confy_simple_app")?;
println!("{:#?}", cfg);
Ok(())
}

300
libs/confy/src/lib.rs Normal file
View File

@ -0,0 +1,300 @@
//! Zero-boilerplate configuration management
//!
//! ## Why?
//!
//! There are a lot of different requirements when
//! selecting, loading and writing a config,
//! depending on the operating system and other
//! environment factors.
//!
//! In many applications this burden is left to you,
//! the developer of an application, to figure out
//! where to place the configuration files.
//!
//! This is where `confy` comes in.
//!
//! ## Idea
//!
//! `confy` takes care of figuring out operating system
//! specific and environment paths before reading and
//! writing a configuration.
//!
//! It gives you easy access to a configuration file
//! which is mirrored into a Rust `struct` via [serde].
//! This way you only need to worry about the layout of
//! your configuration, not where and how to store it.
//!
//! [serde]: https://docs.rs/crates/serde
//!
//! `confy` uses the [`Default`] trait in Rust to automatically
//! create a new configuration, if none is available to read
//! from yet.
//! This means that you can simply assume your application
//! to have a configuration, which will be created with
//! default values of your choosing, without requiring
//! any special logic to handle creation.
//!
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
//!
//! ```rust,no_run
//! use serde_derive::{Serialize, Deserialize};
//!
//! #[derive(Serialize, Deserialize)]
//! struct MyConfig {
//! version: u8,
//! api_key: String,
//! }
//!
//! /// `MyConfig` implements `Default`
//! impl ::std::default::Default for MyConfig {
//! fn default() -> Self { Self { version: 0, api_key: "".into() } }
//! }
//!
//! fn main() -> Result<(), confy::ConfyError> {
//! let cfg = confy::load("my-app-name")?;
//! Ok(())
//! }
//! ```
//!
//! Updating the configuration is then done via the [`store`] function.
//!
//! [`store`]: fn.store.html
//!
mod utils;
use utils::*;
use directories::ProjectDirs;
use serde::{de::DeserializeOwned, Serialize};
use std::error::Error;
use std::fmt;
use std::fs::{self, File, OpenOptions};
use std::io::{ErrorKind::NotFound, Write};
use std::path::{Path, PathBuf};
#[cfg(not(any(feature = "toml_conf", feature = "yaml_conf")))]
compile_error!("Exactly one config language feature must be enabled to use \
confy. Please enable one of either the `toml_conf` or `yaml_conf` \
features.");
#[cfg(all(feature = "toml_conf", feature = "yaml_conf"))]
compile_error!("Exactly one config language feature must be enabled to compile \
confy. Please disable one of either the `toml_conf` or `yaml_conf` features. \
NOTE: `toml_conf` is a default feature, so disabling it might mean switching off \
default features for confy in your Cargo.toml");
#[cfg(all(feature = "toml_conf", not(feature = "yaml_conf")))]
const EXTENSION: &str = "toml";
#[cfg(feature = "yaml_conf")]
const EXTENSION: &str = "yml";
/// Load an application configuration from disk
///
/// A new configuration file is created with default values if none
/// exists.
///
/// Errors that are returned from this function are I/O related,
/// for example if the writing of the new configuration fails
/// or `confy` encounters an operating system or environment
/// that it does not support.
///
/// **Note:** The type of configuration needs to be declared in some way
/// that is inferrable by the compiler. Also note that your
/// configuration needs to implement `Default`.
///
/// ```rust,no_run
/// # use confy::ConfyError;
/// # use serde_derive::{Serialize, Deserialize};
/// # fn main() -> Result<(), ConfyError> {
/// #[derive(Default, Serialize, Deserialize)]
/// struct MyConfig {}
///
/// let cfg: MyConfig = confy::load("my-app-name")?;
/// # Ok(())
/// # }
/// ```
pub fn load<T: Serialize + DeserializeOwned + Default>(name: &str) -> Result<T, ConfyError> {
let project = ProjectDirs::from("rs", "", name).ok_or(ConfyError::BadConfigDirectoryStr)?;
let config_dir_str = get_configuration_directory_str(&project)?;
let path: PathBuf = [config_dir_str, &format!("{}.{}", name, EXTENSION)].iter().collect();
load_path(path)
}
/// Load an application configuration from a specified path.
///
/// This is an alternate version of [`load`] that allows the specification of
/// an aritrary path instead of a system one. For more information on errors
/// and behavior, see [`load`]'s documentation.
///
/// [`load`]: fn.load.html
pub fn load_path<T: Serialize + DeserializeOwned + Default>(path: impl AsRef<Path>) -> Result<T, ConfyError> {
match File::open(&path) {
Ok(mut cfg) => {
let cfg_string = cfg
.get_string()
.map_err(ConfyError::ReadConfigurationFileError)?;
#[cfg(feature = "toml_conf")] {
let cfg_data = toml::from_str(&cfg_string);
cfg_data.map_err(ConfyError::BadTomlData)
}
#[cfg(feature = "yaml_conf")] {
let cfg_data = serde_yaml::from_str(&cfg_string);
cfg_data.map_err(ConfyError::BadYamlData)
}
}
Err(ref e) if e.kind() == NotFound => {
if let Some(parent) = path.as_ref().parent() {
fs::create_dir_all(parent)
.map_err(ConfyError::DirectoryCreationFailed)?;
}
store_path(path, T::default())?;
Ok(T::default())
}
Err(e) => Err(ConfyError::GeneralLoadError(e)),
}
}
/// The errors the confy crate can encounter.
#[derive(Debug)]
pub enum ConfyError {
#[cfg(feature = "toml_conf")]
BadTomlData(toml::de::Error),
#[cfg(feature = "yaml_conf")]
BadYamlData(serde_yaml::Error),
DirectoryCreationFailed(std::io::Error),
GeneralLoadError(std::io::Error),
BadConfigDirectoryStr,
#[cfg(feature = "toml_conf")]
SerializeTomlError(toml::ser::Error),
#[cfg(feature = "yaml_conf")]
SerializeYamlError(serde_yaml::Error),
WriteConfigurationFileError(std::io::Error),
ReadConfigurationFileError(std::io::Error),
OpenConfigurationFileError(std::io::Error),
}
impl fmt::Display for ConfyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
#[cfg(feature = "toml_conf")]
ConfyError::BadTomlData(e) => write!(f, "Bad TOML data: {}", e),
#[cfg(feature = "toml_conf")]
ConfyError::SerializeTomlError(_) => write!(f, "Failed to serialize configuration data into TOML."),
#[cfg(feature = "yaml_conf")]
ConfyError::BadYamlData(e) => write!(f, "Bad YAML data: {}", e),
#[cfg(feature = "yaml_conf")]
ConfyError::SerializeYamlError(_) => write!(f, "Failed to serialize configuration data into YAML."),
ConfyError::DirectoryCreationFailed(e) => write!(f, "Failed to create directory: {}", e),
ConfyError::GeneralLoadError(_) => write!(f, "Failed to load configuration file."),
ConfyError::BadConfigDirectoryStr => write!(f, "Failed to convert directory name to str."),
ConfyError::WriteConfigurationFileError(_) => write!(f, "Failed to write configuration file."),
ConfyError::ReadConfigurationFileError(_) => write!(f, "Failed to read configuration file."),
ConfyError::OpenConfigurationFileError(_) => write!(f, "Failed to open configuration file."),
}
}
}
impl Error for ConfyError {}
/// Save changes made to a configuration object
///
/// This function will update a configuration,
/// with the provided values, and create a new one,
/// if none exists.
///
/// You can also use this function to create a new configuration
/// with different initial values than which are provided
/// by your `Default` trait implementation, or if your
/// configuration structure _can't_ implement `Default`.
///
/// ```rust,no_run
/// # use serde_derive::{Serialize, Deserialize};
/// # use confy::ConfyError;
/// # fn main() -> Result<(), ConfyError> {
/// #[derive(Serialize, Deserialize)]
/// struct MyConf {}
///
/// let my_cfg = MyConf {};
/// confy::store("my-app-name", my_cfg)?;
/// # Ok(())
/// # }
/// ```
///
/// Errors returned are I/O errors related to not being
/// able to write the configuration file or if `confy`
/// encounters an operating system or environment it does
/// not support.
pub fn store<T: Serialize>(name: &str, cfg: T) -> Result<(), ConfyError> {
let project = ProjectDirs::from("rs", "", name).ok_or(ConfyError::BadConfigDirectoryStr)?;
fs::create_dir_all(project.config_dir()).map_err(ConfyError::DirectoryCreationFailed)?;
let config_dir_str = get_configuration_directory_str(&project)?;
let path: PathBuf = [config_dir_str, &format!("{}.{}", name, EXTENSION)].iter().collect();
store_path(path, cfg)
}
/// Save changes made to a configuration object at a specified path
///
/// This is an alternate version of [`store`] that allows the specification of
/// an aritrary path instead of a system one. For more information on errors
/// and behavior, see [`store`]'s documentation.
///
/// [`store`]: fn.store.html
pub fn store_path<T: Serialize>(path: impl AsRef<Path>, cfg: T) -> Result<(), ConfyError> {
let path = path.as_ref();
let mut path_tmp = path.to_path_buf();
use std::time::{SystemTime, UNIX_EPOCH};
let mut i = 0;
loop {
i += 1;
path_tmp.set_extension(SystemTime::now().duration_since(UNIX_EPOCH).map(|x| x.as_nanos()).unwrap_or(i).to_string());
if !path_tmp.exists() {
break;
}
}
let mut f = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path_tmp)
.map_err(ConfyError::OpenConfigurationFileError)?;
let s;
#[cfg(feature = "toml_conf")] {
s = toml::to_string(&cfg).map_err(ConfyError::SerializeTomlError)?;
}
#[cfg(feature = "yaml_conf")] {
s = serde_yaml::to_string(&cfg).map_err(ConfyError::SerializeYamlError)?;
}
f.write_all(s.as_bytes())
.map_err(ConfyError::WriteConfigurationFileError)?;
std::fs::rename(path_tmp, path)
.map_err(ConfyError::WriteConfigurationFileError)?;
Ok(())
}
fn get_configuration_directory_str(project: &ProjectDirs) -> Result<&str, ConfyError> {
let config_dir_option = project.config_dir().to_str();
match config_dir_option {
Some(x) => Ok(x),
None => Err(ConfyError::BadConfigDirectoryStr),
}
}

16
libs/confy/src/utils.rs Normal file
View File

@ -0,0 +1,16 @@
//! Some storage utilities
use std::fs::File;
use std::io::{Error as IoError, Read};
pub trait CheckedStringRead {
fn get_string(&mut self) -> Result<String, IoError>;
}
impl CheckedStringRead for File {
fn get_string(&mut self) -> Result<String, IoError> {
let mut s = String::new();
self.read_to_string(&mut s)?;
Ok(s)
}
}

View File

@ -347,7 +347,7 @@ pub enum Key {
///
Clear,
///
Menu,
Menu, // deprecated, use alt instead
///
Pause,
///
@ -409,6 +409,12 @@ pub enum Key {
///
NumpadEnter,
///
RightShift,
///
RightControl,
///
RightAlt,
///
/// Function, /// mac
/// keyboard layout dependent key
Layout(char),
@ -485,7 +491,7 @@ impl Enigo {
/// ```
pub fn new() -> Self {
#[cfg(any(target_os = "android", target_os = "ios"))]
return Enigo{};
return Enigo {};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Self::default()
}

View File

@ -260,7 +260,7 @@ fn keysequence<'a>(key: Key) -> Cow<'a, str> {
Key::Mute => "",
Key::Scroll => "Scroll_Lock",
Key::NumLock => "Num_Lock",
Key::RWin => "",
Key::RWin => "Super_R",
Key::Apps => "",
Key::Multiply => "KP_Multiply",
Key::Add => "KP_Add",
@ -268,6 +268,9 @@ fn keysequence<'a>(key: Key) -> Cow<'a, str> {
Key::Divide => "KP_Divide",
Key::Equals => "KP_Equal",
Key::NumpadEnter => "KP_Enter",
Key::RightShift => "Shift_R",
Key::RightControl => "Control_R",
Key::RightAlt => "Alt_R",
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super",

View File

@ -70,3 +70,4 @@ pub const kVK_ANSI_KeypadDivide: u16 = 0x4B;
pub const kVK_ANSI_KeypadEnter: u16 = 0x4C;
pub const kVK_ANSI_KeypadMinus: u16 = 0x4E;
pub const kVK_ANSI_KeypadEquals: u16 = 0x51;
pub const kVK_RIGHT_COMMAND: u16 = 0x36;

View File

@ -142,6 +142,8 @@ pub const kCFStringEncodingUTF8: u32 = 134_217_984;
#[link(name = "Carbon", kind = "framework")]
extern "C" {
fn TISCopyCurrentKeyboardInputSource() -> TISInputSourceRef;
fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
fn TISCopyCurrentASCIICapableKeyboardLayoutInputSource() -> TISInputSourceRef;
// extern void *
// TISGetInputSourceProperty(
@ -583,6 +585,10 @@ impl Enigo {
Key::Subtract => kVK_ANSI_KeypadMinus,
Key::Equals => kVK_ANSI_KeypadEquals,
Key::NumLock => kVK_ANSI_KeypadClear,
Key::RWin => kVK_RIGHT_COMMAND,
Key::RightShift => kVK_RightShift,
Key::RightControl => kVK_RightControl,
Key::RightAlt => kVK_RightOption,
Key::Raw(raw_keycode) => raw_keycode,
Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
@ -600,6 +606,7 @@ impl Enigo {
}
fn init_map(&mut self) {
println!("init_map");
self.keycode_to_string_map.insert("".to_owned(), 0);
// loop through every keycode (0 - 127)
for keycode in 0..128 {
@ -649,14 +656,44 @@ impl Enigo {
fn create_string_for_key(&self, keycode: u16, modifier: u32) -> CFStringRef {
let current_keyboard = unsafe { TISCopyCurrentKeyboardInputSource() };
let layout_data = unsafe {
let mut layout_data = std::ptr::null_mut();
if !current_keyboard.is_null() {
layout_data = unsafe {
TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData)
};
}
if layout_data.is_null() {
// https://github.com/microsoft/vscode/issues/23833
let current_keyboard = unsafe { TISCopyCurrentKeyboardLayoutInputSource() };
if !current_keyboard.is_null() {
layout_data = unsafe {
TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData)
};
}
}
if layout_data.is_null() {
let current_keyboard = unsafe { TISCopyCurrentASCIICapableKeyboardLayoutInputSource() };
if !current_keyboard.is_null() {
layout_data = unsafe {
TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData)
};
}
}
if layout_data.is_null() {
// to-do: try out manual mapping in https://github.com/stweil/OSXvnc
// we do see crash like this, also not easy to reproduce, no sure if it related
/*
0 rustdesk 0x000000010f921bc9 std::collections::hash::map::HashMap$LT$K$C$V$C$S$GT$::insert::h84e28c51a3292e7a + 473
1 rustdesk 0x000000010f921884 enigo::macos::macos_impl::Enigo::key_to_keycode::h85ead82e9b1075ae + 1428
2 rustdesk 0x000000010f922a8c _$LT$enigo..macos..macos_impl..Enigo$u20$as$u20$enigo..KeyboardControllable$GT$::key_down::h54f24da6d274b948 + 44
*/
return std::ptr::null() as _;
}
let keyboard_layout = unsafe { CFDataGetBytePtr(layout_data) };
let mut keys_down: UInt32 = 0;
// let mut chars: *mut c_void;//[UniChar; 4];
let mut chars: u16 = 0;
// let mut chars: *mut c_void;//[UniChar; 4];
let mut real_length: UniCharCount = 0;
unsafe {
UCKeyTranslate(

View File

@ -7,9 +7,14 @@ pub const EVK_BACK: u16 = 0x08;
pub const EVK_ESCAPE: u16 = 0x1b;
pub const EVK_LWIN: u16 = 0x5b;
pub const EVK_SHIFT: u16 = 0x10;
//pub const EVK_LSHIFT: u16 = 0xa0;
pub const EVK_RSHIFT: u16 = 0xa1;
//pub const EVK_LMENU: u16 = 0xa4;
pub const EVK_RMENU: u16 = 0xa5;
pub const EVK_CAPITAL: u16 = 0x14;
pub const EVK_MENU: u16 = 0x12;
pub const EVK_LCONTROL: u16 = 0xa2;
pub const EVK_RCONTROL: u16 = 0xa3;
pub const EVK_HOME: u16 = 0x24;
pub const EVK_PRIOR: u16 = 0x21;
pub const EVK_NEXT: u16 = 0x22;

View File

@ -338,10 +338,14 @@ impl Enigo {
Key::Divide => EVK_DIVIDE,
Key::NumpadEnter => EVK_RETURN,
Key::Equals => '=' as _,
Key::RightShift => EVK_RSHIFT,
Key::RightControl => EVK_RCONTROL,
Key::RightAlt => EVK_RMENU,
Key::Raw(raw_keycode) => raw_keycode,
Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
Key::Super | Key::Command | Key::Windows | Key::Meta => EVK_LWIN,
_ => 0,
}
}

View File

@ -24,7 +24,7 @@ rand = "0.7"
serde_derive = "1.0"
serde = "1.0"
lazy_static = "1.4"
confy = { git = "https://github.com/open-trade/confy" }
confy = { path = "../confy" }
dirs-next = "2.0"
filetime = "0.2"
sodiumoxide = "0.2"

View File

@ -129,7 +129,7 @@ enum ControlKey {
Numpad9 = 42;
Cancel = 43;
Clear = 44;
Menu = 45;
Menu = 45; // deprecated, use Alt instead
Pause = 46;
Kana = 47;
Hangul = 48;
@ -157,6 +157,9 @@ enum ControlKey {
Divide = 70;
Equals = 71;
NumpadEnter = 72;
RShift= 73;
RControl = 74;
RAlt = 75;
CtrlAltDel = 100;
LockScreen = 101;
}

View File

@ -135,7 +135,7 @@ impl Client {
if ph.socket_addr.is_empty() {
match ph.failure.enum_value_or_default() {
punch_hole_response::Failure::ID_NOT_EXIST => {
bail!("ID not exist");
bail!("ID does not exist");
}
punch_hole_response::Failure::OFFLINE => {
bail!("Remote desktop is offline");
@ -318,6 +318,7 @@ impl Client {
}
} else {
// fall back to non-secure connection in case pk mismatch
// to-do: pop up a warning dialog to let user choose if continue
let mut msg_out = Message::new();
msg_out.set_public_key(PublicKey::new());
timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??;
@ -1068,7 +1069,7 @@ lazy_static::lazy_static! {
("VK_RETURN", Key::ControlKey(ControlKey::Return)),
("VK_SHIFT", Key::ControlKey(ControlKey::Shift)),
("VK_CONTROL", Key::ControlKey(ControlKey::Control)),
("VK_MENU", Key::ControlKey(ControlKey::Menu)),
("VK_MENU", Key::ControlKey(ControlKey::Alt)),
("VK_PAUSE", Key::ControlKey(ControlKey::Pause)),
("VK_CAPITAL", Key::ControlKey(ControlKey::CapsLock)),
("VK_KANA", Key::ControlKey(ControlKey::Kana)),
@ -1107,6 +1108,10 @@ lazy_static::lazy_static! {
("VK_NUMPAD7", Key::ControlKey(ControlKey::Numpad7)),
("VK_NUMPAD8", Key::ControlKey(ControlKey::Numpad8)),
("VK_NUMPAD9", Key::ControlKey(ControlKey::Numpad9)),
("RAlt", Key::ControlKey(ControlKey::RAlt)),
("RWin", Key::ControlKey(ControlKey::RWin)),
("RControl", Key::ControlKey(ControlKey::RControl)),
("RShift", Key::ControlKey(ControlKey::RShift)),
("CTRL_ALT_DEL", Key::ControlKey(ControlKey::CtrlAltDel)),
("LOCK_SCREEN", Key::ControlKey(ControlKey::LockScreen)),
].iter().cloned().collect();

View File

@ -50,6 +50,20 @@ pub fn valid_for_capslock(evt: &KeyEvent) -> bool {
}
}
pub fn create_clipboard_msg(content: String) -> Message {
let bytes = content.into_bytes();
let compressed = compress_func(&bytes, COMPRESS_LEVEL);
let compress = compressed.len() < bytes.len();
let content = if compress { compressed } else { bytes };
let mut msg = Message::new();
msg.set_clipboard(Clipboard {
compress,
content,
..Default::default()
});
msg
}
pub fn check_clipboard(
ctx: &mut ClipboardContext,
old: Option<&Arc<Mutex<String>>>,
@ -61,18 +75,8 @@ pub fn check_clipboard(
let changed = content != *old.lock().unwrap();
if changed {
log::info!("{} update found on {}", CLIPBOARD_NAME, side);
let bytes = content.clone().into_bytes();
*old.lock().unwrap() = content;
let compressed = compress_func(&bytes, COMPRESS_LEVEL);
let compress = compressed.len() < bytes.len();
let content = if compress { compressed } else { bytes };
let mut msg = Message::new();
msg.set_clipboard(Clipboard {
compress,
content,
..Default::default()
});
return Some(msg);
*old.lock().unwrap() = content.clone();
return Some(create_clipboard_msg(content));
}
}
}

View File

@ -164,7 +164,7 @@ pub fn start_os_service() {
uid = tmp;
log::info!("uid of seat0: {}", uid);
let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
let mut auth = get_env("XAUTHORITY", &uid);
let mut auth = get_env_tries("XAUTHORITY", &uid, 10);
if auth.is_empty() {
auth = if std::path::Path::new(&gdm).exists() {
gdm
@ -187,8 +187,9 @@ pub fn start_os_service() {
d = get_display();
}
if d.is_empty() {
d = ":0".to_owned()
d = ":0".to_owned();
}
d = d.replace(&whoami::hostname(), "").replace("localhost", "");
log::info!("DISPLAY: {}", d);
log::info!("XAUTHORITY: {}", auth);
std::env::set_var("XAUTHORITY", auth);
@ -289,6 +290,7 @@ fn get_display() -> String {
}
// above not work for gdm user
log::debug!("ls -l /tmp/.X11-unix/");
let mut last = "".to_owned();
if let Ok(output) = std::process::Command::new("ls")
.args(vec!["-l", "/tmp/.X11-unix/"])
.output()
@ -296,16 +298,18 @@ fn get_display() -> String {
for line in String::from_utf8_lossy(&output.stdout).lines() {
log::debug!(" {}", line);
let mut iter = line.split_whitespace();
if iter.nth(2) == Some(&user) {
let user_field = iter.nth(2);
if let Some(x) = iter.last() {
if x.starts_with("X") {
return x.replace("X", ":").to_owned();
last = x.replace("X", ":").to_owned();
if user_field == Some(&user) {
return last;
}
}
}
}
}
"".to_owned()
last
}
fn get_value_of_seat0(i: usize) -> String {
@ -322,13 +326,32 @@ fn get_value_of_seat0(i: usize) -> String {
}
}
}
// some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73
if let Ok(output) = std::process::Command::new("loginctl").output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if let Some(sid) = line.split_whitespace().nth(0) {
let d = get_display_server_of_session(sid);
if is_active(sid) && d != "tty" {
if let Some(uid) = line.split_whitespace().nth(i) {
return uid.to_owned();
}
}
}
}
}
return "".to_owned();
}
pub fn get_display_server() -> String {
let session = get_value_of_seat0(0);
get_display_server_of_session(&session)
}
fn get_display_server_of_session(session: &str) -> String {
if let Ok(output) = std::process::Command::new("loginctl")
.args(vec!["show-session", "-p", "Type", &session])
.args(vec!["show-session", "-p", "Type", session])
.output()
{
String::from_utf8_lossy(&output.stdout)
@ -343,18 +366,24 @@ pub fn get_display_server() -> String {
pub fn is_login_wayland() -> bool {
if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") {
contents.contains("#WaylandEnable=false")
} else if let Ok(contents) = std::fs::read_to_string("/etc/gdm/custom.conf") {
contents.contains("#WaylandEnable=false")
} else {
false
}
}
pub fn fix_login_wayland() {
let mut file = "/etc/gdm3/custom.conf".to_owned();
if !std::path::Path::new(&file).exists() {
file = "/etc/gdm/custom.conf".to_owned();
}
match std::process::Command::new("pkexec")
.args(vec![
"sed",
"-i",
"s/#WaylandEnable=false/WaylandEnable=false/g",
"/etc/gdm3/custom.conf",
&file
])
.output()
{
@ -495,6 +524,17 @@ fn run_cmds(cmds: String) -> ResultType<Option<String>> {
}
}
fn get_env_tries(name: &str, uid: &str, n: usize) -> String {
for _ in 0..n {
let x = get_env(name, uid);
if !x.is_empty() {
return x;
}
std::thread::sleep(std::time::Duration::from_millis(300));
}
"".to_owned()
}
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);
log::debug!("Run: {}", &cmd);

View File

@ -1037,3 +1037,33 @@ pub fn get_installed_version() -> String {
}
"".to_owned()
}
pub fn create_shortcut(id: &str) -> ResultType<()> {
let exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned();
let shortcut = write_cmds(
format!(
"
Set oWS = WScript.CreateObject(\"WScript.Shell\")
strDesktop = oWS.SpecialFolders(\"Desktop\")
Set objFSO = CreateObject(\"Scripting.FileSystemObject\")
sLinkFile = objFSO.BuildPath(strDesktop, \"{id}.lnk\")
Set oLink = oWS.CreateShortcut(sLinkFile)
oLink.TargetPath = \"{exe}\"
oLink.Arguments = \"--connect {id}\"
oLink.Save
",
exe = exe,
id = id,
),
"vbs",
)?
.to_str()
.unwrap_or("")
.to_owned();
std::process::Command::new("cscript")
.arg(&shortcut)
.output()?;
allow_err!(std::fs::remove_file(shortcut));
Ok(())
}

View File

@ -109,18 +109,12 @@ impl RendezvousMediator {
let mut old_latency = 0;
let mut ema_latency = 0;
loop {
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)) => {
if rpr.request_pk {
log::info!("request_pk received from {}", host);
allow_err!(rz.register_pk(&mut socket).await);
continue;
}
let mut update_latency = || {
last_register_resp = SystemTime::now();
let mut latency = last_register_resp.duration_since(last_register_sent).map(|d| d.as_micros() as i64).unwrap_or(0);
let mut latency = last_register_resp
.duration_since(last_register_sent)
.map(|d| d.as_micros() as i64)
.unwrap_or(0);
if ema_latency == 0 {
ema_latency = latency;
} else {
@ -137,18 +131,26 @@ impl RendezvousMediator {
old_latency = latency;
}
fails = 0;
};
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();
last_register_resp = SystemTime::now();
let latency = last_register_resp.duration_since(last_register_sent).map(|d| d.as_micros() as i64).unwrap_or(0);
Config::update_latency(&host, latency);
log::debug!("Latency of {}: {}ms", host, latency as f64 / 1000.);
fails = 0;
}
register_pk_response::Result::UUID_MISMATCH => {
allow_err!(rz.handle_uuid_mismatch(&mut socket).await);

View File

@ -121,7 +121,10 @@ async fn create_tcp_connection_(
let mut key = [0u8; secretbox::KEYBYTES];
key[..].copy_from_slice(&symmetric_key);
stream.set_key(secretbox::Key(key));
} else if !pk.asymmetric_value.is_empty() {
} else if pk.asymmetric_value.is_empty() {
// force a trial to update_pk to rendezvous server
Config::set_key_confirmed(false);
} else {
bail!("Handshake failed: invalid public sign key length from peer");
}
} else {

View File

@ -6,7 +6,6 @@ pub use crate::common::{
struct State {
ctx: Option<ClipboardContext>,
initialized: bool,
}
impl Default for State {
@ -18,22 +17,18 @@ impl Default for State {
None
}
};
Self {
ctx,
initialized: false,
}
Self { ctx }
}
}
impl super::service::Reset for State {
fn reset(&mut self) {
*CONTENT.lock().unwrap() = Default::default();
self.initialized = false;
}
}
pub fn new() -> GenericService {
let sp = GenericService::new(NAME, false);
let sp = GenericService::new(NAME, true);
sp.repeat::<State, _>(INTERVAL, run);
sp
}
@ -41,13 +36,16 @@ pub fn new() -> GenericService {
fn run(sp: GenericService, state: &mut State) -> ResultType<()> {
if let Some(ctx) = state.ctx.as_mut() {
if let Some(msg) = check_clipboard(ctx, None) {
if !state.initialized {
state.initialized = true;
// ignore clipboard update before service start
return Ok(());
}
sp.send(msg);
}
sp.snapshot(|sps| {
let txt = crate::CONTENT.lock().unwrap().clone();
if !txt.is_empty() {
let msg_out = crate::create_clipboard_msg(txt);
sps.send_shared(Arc::new(msg_out));
}
Ok(())
})?;
}
Ok(())
}

View File

@ -174,7 +174,6 @@ pub fn is_left_up(evt: &MouseEvent) -> bool {
#[cfg(windows)]
pub fn mouse_move_relative(x: i32, y: i32) {
#[cfg(windows)]
crate::platform::windows::try_change_desktop();
let mut en = ENIGO.lock().unwrap();
en.mouse_move_relative(x, y);
@ -187,6 +186,19 @@ fn modifier_sleep() {
std::thread::sleep(std::time::Duration::from_nanos(1));
}
#[cfg(not(target_os = "macos"))]
#[inline]
fn get_modifier_state(key: enigo::Key, en: &mut Enigo) -> bool {
let x = en.get_key_state(key.clone());
match key {
enigo::Key::Shift => x || en.get_key_state(enigo::Key::RightShift),
enigo::Key::Control => x || en.get_key_state(enigo::Key::RightControl),
enigo::Key::Alt => x || en.get_key_state(enigo::Key::RightAlt),
enigo::Key::Meta => x || en.get_key_state(enigo::Key::RWin),
_ => x,
}
}
pub fn handle_mouse(evt: &MouseEvent, conn: i32) {
#[cfg(target_os = "macos")]
if !*IS_SERVER {
@ -221,7 +233,7 @@ fn handle_mouse_(evt: &MouseEvent, conn: i32) {
en.add_flag(key);
#[cfg(not(target_os = "macos"))]
if key != &enigo::Key::CapsLock && key != &enigo::Key::NumLock {
if !en.get_key_state(key.clone()) {
if !get_modifier_state(key.clone(), &mut en) {
en.key_down(key.clone()).ok();
modifier_sleep();
to_release.push(key);
@ -340,7 +352,7 @@ lazy_static::lazy_static! {
(ControlKey::Numpad9, enigo::Key::Numpad9),
(ControlKey::Cancel, enigo::Key::Cancel),
(ControlKey::Clear, enigo::Key::Clear),
(ControlKey::Menu, enigo::Key::Menu),
(ControlKey::Menu, enigo::Key::Alt),
(ControlKey::Pause, enigo::Key::Pause),
(ControlKey::Kana, enigo::Key::Kana),
(ControlKey::Hangul, enigo::Key::Hangul),
@ -368,6 +380,10 @@ lazy_static::lazy_static! {
(ControlKey::Divide, enigo::Key::Divide),
(ControlKey::Equals, enigo::Key::Equals),
(ControlKey::NumpadEnter, enigo::Key::NumpadEnter),
(ControlKey::RAlt, enigo::Key::RightAlt),
(ControlKey::RWin, enigo::Key::RWin),
(ControlKey::RControl, enigo::Key::RightControl),
(ControlKey::RShift, enigo::Key::RightShift),
].iter().map(|(a, b)| (a.value(), b.clone())).collect();
static ref NUMPAD_KEY_MAP: HashMap<i32, bool> =
[
@ -425,7 +441,7 @@ fn handle_key_(evt: &KeyEvent) {
has_numlock = true;
}
} else {
if !en.get_key_state(key.clone()) {
if !get_modifier_state(key.clone(), &mut en) {
en.key_down(key.clone()).ok();
modifier_sleep();
to_release.push(key);

View File

@ -46,11 +46,11 @@ pub fn new() -> GenericService {
fn run(sp: GenericService) -> ResultType<()> {
let fps = 30;
let spf = time::Duration::from_secs_f32(1. / (fps as f32));
let (n, current, display) = get_current_display()?;
let (ndisplay, current, display) = get_current_display()?;
let (origin, width, height) = (display.origin(), display.width(), display.height());
log::debug!(
"#displays={}, current={}, origin: {:?}, width={}, height={}",
n,
ndisplay,
current,
&origin,
width,
@ -74,7 +74,11 @@ fn run(sp: GenericService) -> ResultType<()> {
speed,
};
let mut vpx;
match Encoder::new(&cfg, 1) {
let mut n = ((width * height) as f64 / (1920 * 1080) as f64).round() as u32;
if n < 1 {
n = 1;
}
match Encoder::new(&cfg, n) {
Ok(x) => vpx = x,
Err(err) => bail!("Failed to create encoder: {}", err),
}
@ -113,6 +117,7 @@ fn run(sp: GenericService) -> ResultType<()> {
let start = time::Instant::now();
let mut crc = (0, 0);
let mut last_sent = time::Instant::now();
let mut last_check_displays = time::Instant::now();
while sp.ok() {
if *SWITCH.lock().unwrap() {
bail!("SWITCH");
@ -131,6 +136,14 @@ fn run(sp: GenericService) -> ResultType<()> {
}
}
let now = time::Instant::now();
if last_check_displays.elapsed().as_millis() > 1000 {
last_check_displays = now;
if ndisplay != get_display_num() {
log::info!("Displays changed");
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
}
*LAST_ACTIVE.lock().unwrap() = now;
if get_latency() < 1000 || last_sent.elapsed().as_millis() > 1000 {
match c.frame(wait as _) {
@ -228,6 +241,14 @@ fn handle_one_frame(
Ok(())
}
fn get_display_num() -> usize {
if let Ok(d) = Display::all() {
d.len()
} else {
0
}
}
pub fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
// switch to primary display if long time (30 seconds) no users
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {

View File

@ -486,6 +486,11 @@ impl UI {
format!("{}.{}", p.to_string_lossy(), self.get_software_ext())
}
fn create_shortcut(&self, id: String) {
#[cfg(windows)]
crate::platform::windows::create_shortcut(&id).ok();
}
fn open_url(&self, url: String) {
#[cfg(windows)]
let p = "explorer";
@ -541,6 +546,7 @@ impl sciter::EventHandler for UI {
fn get_software_store_path();
fn get_software_ext();
fn open_url(String);
fn create_shortcut(String);
}
}

View File

@ -36,7 +36,9 @@ function stateChanged() {
cur_window_state = view.windowState;
adjustBorder();
adaptDisplay();
if (!is_linux) view.focus = handler; // this cause windows always topmost on linux
if (cur_window_state != View.WINDOW_MINIMIZED) {
view.focus = handler; // to make focus away from restore/maximize button, so that enter key work
}
var fs = view.windowState == View.WINDOW_FULL_SCREEN;
var el = $(#fullscreen);
if (el) el.attributes.toggleClass("active", fs);
@ -134,7 +136,12 @@ class Header: Reactor.Component {
<menu.context #action-options>
<li #transfer-file>Transfer File</li>
<li #tunnel>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>}
<div .separator />
{keyboard_enabled ? <li #lock-screen>Insert Lock</li> : ""}
{false && pi.platform == "Windows" ? <li #block-input>Block user input </li> : ""}
{handler.support_refresh() ? <li #refresh>Refresh</li> : ""}
@ -222,6 +229,18 @@ 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();
}

View File

@ -9,19 +9,6 @@
include "common.tis";
include "index.tis";
</script>
<popup>
<menu.context #remote-context>
<li #connect>Connect</li>
<li #transfer>Transfer File</li>
<li #tunnel>TCP Tunneling</li>
<li #rdp>RDP</li>
<li #remove>Remove</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>
</head>
<body>

View File

@ -103,6 +103,8 @@ event click $(menu#remote-context li) (evt, me) {
} 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") {
@ -115,7 +117,7 @@ function createNewConnect(id, type) {
app.remote_id.value = formatId(id);
if (!id) return;
if (id == handler.get_id()) {
handler.msgbox("custom-error", "Error", "Sorry, it is yourself");
handler.msgbox("custom-error", "Error", "You cannot connect to your own computer");
return;
}
handler.set_remote_id(id);
@ -310,6 +312,22 @@ 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 #remove>Remove</li>
{is_win && <li #shortcut>Create Desktop Shortcut</li>}
</menu>
</popup>
<popup>
<menu.context #edit-password-context>
<li #refresh-password>Refresh random password</li>
<li #set-password>Set your own password</li>
</menu>
</popup>
<div .left-pane>
<div>
<div .title>Your Desktop</div>
@ -421,7 +439,7 @@ class UpgradeMe: Reactor.Component {
var update_or_download = is_osx ? "download" : "update";
return <div .install-me>
<div>{handler.get_app_name()} Status</div>
<div>Your installation is lower version.</div>
<div>An update is available for RustDesk.</div>
<div #install-me .link style="padding-top: 1em">Click to upgrade</div>
</div>;
}
@ -444,7 +462,7 @@ class UpdateMe: Reactor.Component {
event click $(#install-me) {
if (is_osx) {
handler.open_url("http://rustdesk.com");
handler.open_url("https://rustdesk.com");
return;
}
var url = software_update_url + '.' + handler.get_software_ext();

View File

@ -22,7 +22,7 @@ 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 your are using tunnel</span>
<span style="font-size: 0.8em; color: #ddd">Don't close this window while you are using the tunnel</span>
</div> : ""}
<table #port-forward>
<thead>

View File

@ -9,6 +9,7 @@ use hbb_common::{
fs, log,
message_proto::*,
protobuf::Message as _,
sleep,
tokio::{
self,
sync::mpsc,
@ -62,6 +63,7 @@ pub struct Handler {
id: String,
args: Vec<String>,
lc: Arc<RwLock<LoginConfigHandler>>,
super_on: bool,
}
impl Deref for Handler {
@ -152,6 +154,9 @@ impl sciter::EventHandler for Handler {
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
fn key_down_or_up(bool, String, i32, bool, bool, bool, bool, bool);
fn ctrl_alt_del();
fn ctrl_space();
fn alt_tab();
fn super_x();
fn transfer_file();
fn tunnel();
fn lock_screen();
@ -710,6 +715,7 @@ impl Handler {
0x4C => key_event.set_control_key(ControlKey::NumpadEnter), // numpad enter
0x69 => key_event.set_control_key(ControlKey::Snapshot),
0x72 => key_event.set_control_key(ControlKey::Help),
0x6E => key_event.set_control_key(ControlKey::Apps),
0x47 => {
key_event.set_control_key(if self.peer_platform() == "Mac OS" {
ControlKey::Clear
@ -731,6 +737,7 @@ impl Handler {
0x91 => key_event.set_control_key(ControlKey::Scroll),
0x90 => key_event.set_control_key(ControlKey::NumLock),
0x5C => key_event.set_control_key(ControlKey::RWin),
0x5B => key_event.set_control_key(ControlKey::Meta),
0x5D => key_event.set_control_key(ControlKey::Apps),
0xBE => key_event.set_chr('.' as _),
0xC0 => key_event.set_chr('`' as _),
@ -739,6 +746,44 @@ impl Handler {
return None;
}
}
} else if cfg!(target_os = "linux") {
match code {
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),
65516 => key_event.set_control_key(ControlKey::RWin),
65513 => key_event.set_control_key(ControlKey::Alt),
65514 => key_event.set_control_key(ControlKey::RAlt),
65508 => key_event.set_control_key(ControlKey::RControl),
65506 => key_event.set_control_key(ControlKey::RShift),
96 => key_event.set_chr('`' as _),
46 => key_event.set_chr('.' as _),
126 => key_event.set_chr('`' as _),
33 => key_event.set_chr('1' as _),
64 => key_event.set_chr('2' as _),
35 => key_event.set_chr('3' as _),
36 => key_event.set_chr('4' as _),
37 => key_event.set_chr('5' as _),
94 => key_event.set_chr('6' as _),
38 => key_event.set_chr('7' as _),
42 => key_event.set_chr('8' as _),
40 => key_event.set_chr('9' as _),
41 => key_event.set_chr('0' as _),
95 => key_event.set_chr('-' as _),
43 => key_event.set_chr('=' as _),
123 => key_event.set_chr('[' as _),
125 => key_event.set_chr(']' as _),
124 => key_event.set_chr('\\' as _),
58 => key_event.set_chr(';' as _),
34 => key_event.set_chr('\'' as _),
60 => key_event.set_chr(',' as _),
62 => key_event.set_chr('.' as _),
63 => key_event.set_chr('/' as _),
_ => {
log::error!("Unknown key code {}", code);
return None;
}
}
} else {
log::error!("Unknown key code {}", code);
return None;
@ -772,6 +817,20 @@ impl Handler {
}
}
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);
@ -819,6 +878,26 @@ impl Handler {
extended,
);
let mut command = command;
if self.super_on {
command = true;
}
if down_or_up == 0 {
self.super_on = false;
}
let mut name = name;
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 let Some(mut key_event) = self.get_key_event(down_or_up, &name, code) {
// Linux has different repeated key down handling from mac and windows
/* // below cause hang some time, not find why, so disable. so shift + repeat char not work for mac->linux, win->linux works fine, linux->linux not test yet
@ -847,16 +926,28 @@ impl Handler {
}
}
*/
if alt && !crate::is_control_key(&key_event, &ControlKey::Alt) {
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) {
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) {
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) {
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) {
@ -1145,7 +1236,7 @@ impl Remote {
}
fn start_clipboard(&mut self) -> Option<std::sync::mpsc::Sender<()>> {
if self.handler.is_file_transfer() {
if self.handler.is_file_transfer() || self.handler.is_port_forward() {
return None;
}
let (tx, rx) = std::sync::mpsc::channel();
@ -1430,6 +1521,23 @@ impl Remote {
}
Some(login_response::Union::peer_info(pi)) => {
self.handler.handle_peer_info(pi);
if !(self.handler.is_file_transfer()
|| self.handler.is_port_forward()
|| !*self.clipboard.read().unwrap()
|| !*self.keyboard.read().unwrap()
|| self.handler.lc.read().unwrap().disable_clipboard)
{
let txt = self.old_clipboard.lock().unwrap().clone();
if !txt.is_empty() {
let msg_out = crate::create_clipboard_msg(txt);
let sender = self.sender.clone();
tokio::spawn(async move {
// due to clipboard service interval time
sleep(common::CLIPBOARD_INTERVAL as f32 / 1_000.).await;
sender.send(Data::Message(msg_out)).ok();
});
}
}
}
_ => {}
},
@ -1587,7 +1695,7 @@ impl Interface for Handler {
pi_sciter.set_item("sas_enabled", pi.sas_enabled);
if self.is_file_transfer() {
if pi.username.is_empty() {
self.on_error("No active user logged on, please connect and logon first.");
self.on_error("No active console user logged on, please connect and logon first.");
return;
}
} else if !self.is_port_forward() {