mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-18 07:43:01 +08:00
linux_wayland_support: init merge, windows build
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
6533b30cac
commit
aae6e2b16b
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
.vscode
|
||||
.idea
|
||||
.DS_Store
|
||||
libsciter-gtk.so
|
||||
src/ui/inline.rs
|
||||
extractor
|
||||
__pycache__
|
||||
|
59
Cargo.lock
generated
59
Cargo.lock
generated
@ -317,6 +317,18 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
@ -1323,6 +1335,16 @@ dependencies = [
|
||||
"str-buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evdev"
|
||||
version = "0.11.5"
|
||||
source = "git+https://github.com/fufesou/evdev#cec616e37790293d2cd2aa54a96601ed6b1b35a9"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"libc",
|
||||
"nix 0.23.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.2"
|
||||
@ -1498,6 +1520,12 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.21"
|
||||
@ -2741,6 +2769,14 @@ dependencies = [
|
||||
"windows-sys 0.28.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mouce"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/fufesou/mouce.git#7da9d9b6597f4c4461881deb4ed49da2385e3cac"
|
||||
dependencies = [
|
||||
"glob",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "muldiv"
|
||||
version = "0.2.1"
|
||||
@ -3527,6 +3563,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.5"
|
||||
@ -3933,6 +3975,7 @@ dependencies = [
|
||||
"default-net",
|
||||
"dispatch",
|
||||
"enigo",
|
||||
"evdev",
|
||||
"flexi_logger",
|
||||
"flutter_rust_bridge",
|
||||
"flutter_rust_bridge_codegen",
|
||||
@ -3947,6 +3990,7 @@ dependencies = [
|
||||
"mac_address",
|
||||
"machine-uid",
|
||||
"magnum-opus",
|
||||
"mouce",
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"parity-tokio-ipc",
|
||||
@ -4587,6 +4631,12 @@ dependencies = [
|
||||
"version-compare 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "target_build_utils"
|
||||
version = "0.3.1"
|
||||
@ -5542,6 +5592,15 @@ dependencies = [
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11"
|
||||
version = "2.19.1"
|
||||
|
@ -31,7 +31,7 @@ default = ["use_dasp"]
|
||||
|
||||
[dependencies]
|
||||
whoami = "1.2"
|
||||
scrap = { path = "libs/scrap" }
|
||||
scrap = { path = "libs/scrap", features = ["wayland"] }
|
||||
hbb_common = { path = "libs/hbb_common" }
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
@ -69,7 +69,7 @@ machine-uid = "0.2"
|
||||
mac_address = "1.1"
|
||||
sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" }
|
||||
sys-locale = "0.2"
|
||||
enigo = { path = "libs/enigo" }
|
||||
enigo = { path = "libs/enigo", features = [ "with_serde" ] }
|
||||
clipboard = { path = "libs/clipboard" }
|
||||
rdev = { git = "https://github.com/open-trade/rdev" }
|
||||
ctrlc = "3.2"
|
||||
@ -99,6 +99,8 @@ psimple = { package = "libpulse-simple-binding", version = "2.25" }
|
||||
pulse = { package = "libpulse-binding", version = "2.26" }
|
||||
rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" }
|
||||
async-process = "1.3"
|
||||
mouce = { git="https://github.com/fufesou/mouce.git" }
|
||||
evdev = { git="https://github.com/fufesou/evdev" }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.11"
|
||||
|
@ -8,16 +8,20 @@ if [ "$1" = configure ]; then
|
||||
|
||||
if [ "systemd" == "$INITSYS" ]; then
|
||||
if [ -e /etc/systemd/system/rustdesk.service ]; then
|
||||
rm /etc/systemd/system/rustdesk.service
|
||||
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service /usr/lib/systemd/user/rustdesk.service >/dev/null 2>&1
|
||||
fi
|
||||
version=$(python3 -V 2>&1 | grep -Po '(?<=Python )(.+)')
|
||||
parsedVersion=$(echo "${version//./}")
|
||||
if [[ "$parsedVersion" -gt "360" ]]; then
|
||||
sudo -H pip3 install pynput
|
||||
fi
|
||||
cp /usr/share/rustdesk/files/systemd/rustdesk.service /etc/systemd/system/rustdesk.service
|
||||
cp /usr/share/rustdesk/files/systemd/rustdesk.service /usr/lib/systemd/system/rustdesk.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
|
||||
cp /usr/share/rustdesk/files/systemd/rustdesk.service.user /usr/lib/systemd/user/rustdesk.service
|
||||
curUser=$(who | awk '{print $1}' | head -1)
|
||||
systemctl --machine=${curUser}@.host --user daemon-reload
|
||||
fi
|
||||
fi
|
||||
|
@ -7,6 +7,13 @@ case $1 in
|
||||
INITSYS=$(ls -al /proc/1/exe | awk -F' ' '{print $NF}' | awk -F'/' '{print $NF}')
|
||||
if [ "systemd" == "${INITSYS}" ]; then
|
||||
service rustdesk stop || true
|
||||
|
||||
serverUser=$(ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1)
|
||||
if [ "$serverUser" != "" ] && [ "$serverUser" != "root" ]
|
||||
then
|
||||
systemctl --machine=${serverUser}@.host --user stop rustdesk || true
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
rm -rf /usr/bin/libsciter-gtk.so
|
||||
fi
|
||||
|
@ -8,7 +8,14 @@ case $1 in
|
||||
if [ "systemd" == "${INITSYS}" ]; then
|
||||
systemctl stop rustdesk || true
|
||||
systemctl disable rustdesk || true
|
||||
rm /etc/systemd/system/rustdesk.service || true
|
||||
|
||||
serverUser=$(ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1)
|
||||
if [ "$serverUser" != "" ] && [ "$serverUser" != "root" ]
|
||||
then
|
||||
systemctl --machine=${serverUser}@.host --user stop rustdesk || true
|
||||
fi
|
||||
|
||||
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service /usr/lib/systemd/user/rustdesk.service || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
5
build.py
5
build.py
@ -209,12 +209,15 @@ rcodesign notarize --api-issuer 69a6de7d-2907-47e3-e053-5b8c7c11a4d1 --api-key 9
|
||||
os.system('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||
os.system(
|
||||
'cp rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||
os.system(
|
||||
'cp rustdesk.service.user tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||
os.system('cp pynput_service.py tmpdeb/usr/share/rustdesk/files/')
|
||||
os.system('cp DEBIAN/* tmpdeb/DEBIAN/')
|
||||
os.system('cp -a DEBIAN/* tmpdeb/DEBIAN/')
|
||||
os.system('strip tmpdeb/usr/bin/rustdesk')
|
||||
os.system('mkdir -p tmpdeb/usr/lib/rustdesk')
|
||||
os.system('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
|
||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service.user')
|
||||
md5_file('usr/share/rustdesk/files/pynput_service.py')
|
||||
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
|
||||
os.system('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
|
||||
|
@ -74,7 +74,7 @@ pub use macos::Enigo;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use crate::linux::Enigo;
|
||||
pub use crate::linux::{is_x11, Enigo};
|
||||
|
||||
/// DSL parser module
|
||||
pub mod dsl;
|
||||
@ -249,7 +249,7 @@ pub trait MouseControllable {
|
||||
/// For alphabetical keys, use Key::Layout for a system independent key.
|
||||
/// If a key is missing, you can use the raw keycode with Key::Raw.
|
||||
#[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Key {
|
||||
/// alt key on Linux and Windows (option key on macOS)
|
||||
Alt,
|
||||
|
37
libs/enigo/src/linux/mod.rs
Normal file
37
libs/enigo/src/linux/mod.rs
Normal file
@ -0,0 +1,37 @@
|
||||
mod nix_impl;
|
||||
mod pynput;
|
||||
mod xdo;
|
||||
|
||||
pub use self::nix_impl::Enigo;
|
||||
|
||||
/// Check if display manager is x11.
|
||||
pub fn is_x11() -> bool {
|
||||
let stdout =
|
||||
match std::process::Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("loginctl show-session $(loginctl | awk '/tty/ {print $1}') -p Type | awk -F= '{print $2}'")
|
||||
.output() {
|
||||
Ok(output) => {
|
||||
output.stdout
|
||||
},
|
||||
Err(_) => {
|
||||
match std::process::Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("echo $XDG_SESSION_TYPE")
|
||||
.output() {
|
||||
Ok(output) => {
|
||||
output.stdout
|
||||
},
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(display_manager) = std::str::from_utf8(&stdout) {
|
||||
display_manager.trim() == "x11"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
178
libs/enigo/src/linux/nix_impl.rs
Normal file
178
libs/enigo/src/linux/nix_impl.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use super::{pynput::EnigoPynput, xdo::EnigoXdo};
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
// #[derive(Default)]
|
||||
pub struct Enigo {
|
||||
xdo: EnigoXdo,
|
||||
pynput: EnigoPynput,
|
||||
is_x11: bool,
|
||||
uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
|
||||
uinput_mouse: Option<Box<dyn MouseControllable + Send>>,
|
||||
}
|
||||
|
||||
impl Enigo {
|
||||
/// Get delay of xdo implementation.
|
||||
pub fn delay(&self) -> u64 {
|
||||
self.xdo.delay()
|
||||
}
|
||||
/// Set delay of xdo implemetation.
|
||||
pub fn set_delay(&mut self, delay: u64) {
|
||||
self.xdo.set_delay(delay)
|
||||
}
|
||||
/// Reset pynput.
|
||||
pub fn reset(&mut self) {
|
||||
self.pynput.reset();
|
||||
}
|
||||
/// Set uinput keyboard.
|
||||
pub fn set_uinput_keyboard(
|
||||
&mut self,
|
||||
uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
|
||||
) {
|
||||
self.uinput_keyboard = uinput_keyboard
|
||||
}
|
||||
/// Set uinput mouse.
|
||||
pub fn set_uinput_mouse(&mut self, uinput_mouse: Option<Box<dyn MouseControllable + Send>>) {
|
||||
self.uinput_mouse = uinput_mouse
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Enigo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_x11: crate::linux::is_x11(),
|
||||
uinput_keyboard: None,
|
||||
uinput_mouse: None,
|
||||
xdo: EnigoXdo::default(),
|
||||
pynput: EnigoPynput::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseControllable for Enigo {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_move_to(x, y);
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_move_to(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_move_relative(x, y);
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_move_relative(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_down(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_down(button)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_up(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_up(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_click(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_click(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_scroll_x(length)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_scroll_x(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_scroll_y(length)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_scroll_y(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardControllable for Enigo {
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
if self.is_x11 {
|
||||
self.xdo.get_key_state(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.get_key_state(key)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
if self.is_x11 {
|
||||
self.xdo.key_sequence(sequence)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_sequence(sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
if self.is_x11 {
|
||||
if self.pynput.send_pynput(&key, true) {
|
||||
return Ok(());
|
||||
}
|
||||
self.xdo.key_down(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_down(key)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_up(&mut self, key: Key) {
|
||||
if self.is_x11 {
|
||||
if self.pynput.send_pynput(&key, false) {
|
||||
return;
|
||||
}
|
||||
self.xdo.key_up(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_up(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_click(&mut self, key: Key) {
|
||||
if self.is_x11 {
|
||||
self.xdo.key_click(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_click(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
280
libs/enigo/src/linux/pynput.rs
Normal file
280
libs/enigo/src/linux/pynput.rs
Normal file
@ -0,0 +1,280 @@
|
||||
use crate::Key;
|
||||
use std::{io::prelude::*, sync::mpsc};
|
||||
|
||||
enum PyMsg {
|
||||
Char(char),
|
||||
Str(&'static str),
|
||||
}
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub(super) struct EnigoPynput {
|
||||
tx: mpsc::Sender<(PyMsg, bool)>,
|
||||
}
|
||||
|
||||
impl Default for EnigoPynput {
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
start_pynput_service(rx);
|
||||
Self { tx }
|
||||
}
|
||||
}
|
||||
impl EnigoPynput {
|
||||
pub(super) fn reset(&mut self) {
|
||||
self.tx.send((PyMsg::Char('\0'), true)).ok();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn send_pynput(&mut self, key: &Key, is_press: bool) -> bool {
|
||||
if unsafe { PYNPUT_EXIT || !PYNPUT_REDAY } {
|
||||
return false;
|
||||
}
|
||||
if let Key::Layout(c) = key {
|
||||
return self.tx.send((PyMsg::Char(*c), is_press)).is_ok();
|
||||
}
|
||||
if let Key::Raw(_) = key {
|
||||
return false;
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
let s = match key {
|
||||
Key::Alt => "Alt_L",
|
||||
Key::Backspace => "BackSpace",
|
||||
Key::CapsLock => "Caps_Lock",
|
||||
Key::Control => "Control_L",
|
||||
Key::Delete => "Delete",
|
||||
Key::DownArrow => "Down",
|
||||
Key::End => "End",
|
||||
Key::Escape => "Escape",
|
||||
Key::F1 => "F1",
|
||||
Key::F10 => "F10",
|
||||
Key::F11 => "F11",
|
||||
Key::F12 => "F12",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F6 => "F6",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
Key::F9 => "F9",
|
||||
Key::Home => "Home",
|
||||
Key::LeftArrow => "Left",
|
||||
Key::Option => "Option",
|
||||
Key::PageDown => "Page_Down",
|
||||
Key::PageUp => "Page_Up",
|
||||
Key::Return => "Return",
|
||||
Key::RightArrow => "Right",
|
||||
Key::Shift => "Shift_L",
|
||||
Key::Space => "space",
|
||||
Key::Tab => "Tab",
|
||||
Key::UpArrow => "Up",
|
||||
Key::Numpad0 => "0",
|
||||
Key::Numpad1 => "1",
|
||||
Key::Numpad2 => "2",
|
||||
Key::Numpad3 => "3",
|
||||
Key::Numpad4 => "4",
|
||||
Key::Numpad5 => "5",
|
||||
Key::Numpad6 => "6",
|
||||
Key::Numpad7 => "7",
|
||||
Key::Numpad8 => "8",
|
||||
Key::Numpad9 => "9",
|
||||
Key::Decimal => "KP_Decimal",
|
||||
Key::Cancel => "Cancel",
|
||||
Key::Clear => "Clear",
|
||||
Key::Pause => "Pause",
|
||||
Key::Kana => "Kana",
|
||||
Key::Hangul => "Hangul",
|
||||
Key::Hanja => "Hanja",
|
||||
Key::Kanji => "Kanji",
|
||||
Key::Select => "Select",
|
||||
Key::Print => "Print",
|
||||
Key::Execute => "Execute",
|
||||
Key::Snapshot => "3270_PrintScreen",
|
||||
Key::Insert => "Insert",
|
||||
Key::Help => "Help",
|
||||
Key::Separator => "KP_Separator",
|
||||
Key::Scroll => "Scroll_Lock",
|
||||
Key::NumLock => "Num_Lock",
|
||||
Key::RWin => "Super_R",
|
||||
Key::Apps => "Menu",
|
||||
Key::Multiply => "KP_Multiply",
|
||||
Key::Add => "KP_Add",
|
||||
Key::Subtract => "KP_Subtract",
|
||||
Key::Divide => "KP_Divide",
|
||||
Key::Equals => "KP_Equal",
|
||||
Key::NumpadEnter => "KP_Enter",
|
||||
Key::RightShift => "Shift_R",
|
||||
Key::RightControl => "Control_R",
|
||||
Key::RightAlt => "Mode_switch",
|
||||
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super_L",
|
||||
_ => {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
log::info!("send pynput: {:?}", &s);
|
||||
return self.tx.send((PyMsg::Str(s), is_press)).is_ok();
|
||||
}
|
||||
}
|
||||
|
||||
// impl MouseControllable for EnigoPynput {
|
||||
// fn mouse_move_to(&mut self, _x: i32, _y: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_move_relative(&mut self, _x: i32, _y: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_down(&mut self, _button: MouseButton) -> crate::ResultType {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_up(&mut self, _button: MouseButton) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_click(&mut self, _button: MouseButton) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_scroll_x(&mut self, _length: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_scroll_y(&mut self, _length: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl KeyboardControllable for EnigoPynput {
|
||||
// fn get_key_state(&mut self, _key: Key) -> bool {
|
||||
// unimplemented!()
|
||||
// }
|
||||
|
||||
// fn key_sequence(&mut self, _sequence: &str) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
// let _ = self.send_pynput(&key, true);
|
||||
// Ok(())
|
||||
// }
|
||||
// fn key_up(&mut self, key: Key) {
|
||||
// let _ = self.send_pynput(&key, false);
|
||||
// }
|
||||
// fn key_click(&mut self, _key: Key) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// }
|
||||
|
||||
static mut PYNPUT_EXIT: bool = false;
|
||||
static mut PYNPUT_REDAY: bool = false;
|
||||
static IPC_FILE: &'static str = "/tmp/RustDesk/pynput_service";
|
||||
|
||||
fn start_pynput_service(rx: mpsc::Receiver<(PyMsg, bool)>) {
|
||||
let mut py = "./pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/share/rustdesk/files/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/lib/rustdesk/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
log::error!("{} not exits", py);
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("pynput service: {}", py);
|
||||
std::thread::spawn(move || {
|
||||
let username = std::env::var("PYNPUT_USERNAME").unwrap_or("".to_owned());
|
||||
let userid = std::env::var("PYNPUT_USERID").unwrap_or("".to_owned());
|
||||
let status = if username.is_empty() {
|
||||
std::process::Command::new("python3")
|
||||
.arg(&py)
|
||||
.arg(IPC_FILE)
|
||||
.status()
|
||||
.map(|x| x.success())
|
||||
} else {
|
||||
let mut status = Ok(true);
|
||||
for i in 0..100 {
|
||||
if i % 10 == 0 {
|
||||
log::info!("#{} try to start pynput server", i);
|
||||
}
|
||||
status = std::process::Command::new("sudo")
|
||||
.args(vec![
|
||||
"-E",
|
||||
&format!("XDG_RUNTIME_DIR=/run/user/{}", userid) as &str,
|
||||
"-u",
|
||||
&username,
|
||||
"python3",
|
||||
&py,
|
||||
IPC_FILE,
|
||||
])
|
||||
.status()
|
||||
.map(|x| x.success());
|
||||
match status {
|
||||
Ok(true) => break,
|
||||
_ => {}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
status
|
||||
};
|
||||
log::info!(
|
||||
"pynput server exit with username/id {}/{}: {:?}",
|
||||
username,
|
||||
userid,
|
||||
status
|
||||
);
|
||||
unsafe {
|
||||
PYNPUT_EXIT = true;
|
||||
}
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
for i in 0..300 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
let mut conn = match std::os::unix::net::UnixStream::connect(IPC_FILE) {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
if i % 15 == 0 {
|
||||
log::warn!("Failed to connect to {}: {}", IPC_FILE, err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) = conn.set_nonblocking(true) {
|
||||
log::error!("Failed to set ipc nonblocking: {}", err);
|
||||
return;
|
||||
}
|
||||
log::info!("Conntected to pynput server");
|
||||
let d = std::time::Duration::from_millis(30);
|
||||
unsafe {
|
||||
PYNPUT_REDAY = true;
|
||||
}
|
||||
let mut buf = [0u8; 1024];
|
||||
loop {
|
||||
if unsafe { PYNPUT_EXIT } {
|
||||
break;
|
||||
}
|
||||
match rx.recv_timeout(d) {
|
||||
Ok((msg, is_press)) => {
|
||||
let msg = match msg {
|
||||
PyMsg::Char(chr) => {
|
||||
format!("{}{}", if is_press { 'p' } else { 'r' }, chr)
|
||||
}
|
||||
PyMsg::Str(s) => format!("{}{}", if is_press { 'p' } else { 'r' }, s),
|
||||
};
|
||||
let n = msg.len();
|
||||
buf[0] = n as _;
|
||||
buf[1..(n + 1)].copy_from_slice(msg.as_bytes());
|
||||
if let Err(err) = conn.write_all(&buf[..n + 1]) {
|
||||
log::error!("Failed to write to ipc: {}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
mpsc::RecvTimeoutError::Disconnected => {
|
||||
log::error!("pynput sender disconnecte");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
PYNPUT_REDAY = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
@ -3,7 +3,7 @@ use libc;
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
|
||||
use self::libc::{c_char, c_int, c_void, useconds_t};
|
||||
use std::{borrow::Cow, ffi::CString, io::prelude::*, ptr, sync::mpsc};
|
||||
use std::{borrow::Cow, ffi::CString, ptr};
|
||||
|
||||
const CURRENT_WINDOW: c_int = 0;
|
||||
const DEFAULT_DELAY: u64 = 12000;
|
||||
@ -60,34 +60,25 @@ fn mousebutton(button: MouseButton) -> c_int {
|
||||
}
|
||||
}
|
||||
|
||||
enum PyMsg {
|
||||
Char(char),
|
||||
Str(&'static str),
|
||||
}
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub struct Enigo {
|
||||
pub(super) struct EnigoXdo {
|
||||
xdo: Xdo,
|
||||
delay: u64,
|
||||
tx: mpsc::Sender<(PyMsg, bool)>,
|
||||
}
|
||||
// This is safe, we have a unique pointer.
|
||||
// TODO: use Unique<c_char> once stable.
|
||||
unsafe impl Send for Enigo {}
|
||||
unsafe impl Send for EnigoXdo {}
|
||||
|
||||
impl Default for Enigo {
|
||||
/// Create a new Enigo instance
|
||||
impl Default for EnigoXdo {
|
||||
/// Create a new EnigoXdo instance
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
start_pynput_service(rx);
|
||||
Self {
|
||||
xdo: unsafe { xdo_new(ptr::null()) },
|
||||
delay: DEFAULT_DELAY,
|
||||
tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Enigo {
|
||||
impl EnigoXdo {
|
||||
/// Get the delay per keypress.
|
||||
/// Default value is 12000.
|
||||
/// This is Linux-specific.
|
||||
@ -99,101 +90,8 @@ impl Enigo {
|
||||
pub fn set_delay(&mut self, delay: u64) {
|
||||
self.delay = delay;
|
||||
}
|
||||
///
|
||||
pub fn reset(&mut self) {
|
||||
self.tx.send((PyMsg::Char('\0'), true)).ok();
|
||||
}
|
||||
#[inline]
|
||||
fn send_pynput(&mut self, key: &Key, is_press: bool) -> bool {
|
||||
if unsafe { PYNPUT_EXIT || !PYNPUT_REDAY } {
|
||||
return false;
|
||||
}
|
||||
if let Key::Layout(c) = key {
|
||||
return self.tx.send((PyMsg::Char(*c), is_press)).is_ok();
|
||||
}
|
||||
if let Key::Raw(_) = key {
|
||||
return false;
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
let s = match key {
|
||||
Key::Alt => "Alt_L",
|
||||
Key::Backspace => "BackSpace",
|
||||
Key::CapsLock => "Caps_Lock",
|
||||
Key::Control => "Control_L",
|
||||
Key::Delete => "Delete",
|
||||
Key::DownArrow => "Down",
|
||||
Key::End => "End",
|
||||
Key::Escape => "Escape",
|
||||
Key::F1 => "F1",
|
||||
Key::F10 => "F10",
|
||||
Key::F11 => "F11",
|
||||
Key::F12 => "F12",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F6 => "F6",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
Key::F9 => "F9",
|
||||
Key::Home => "Home",
|
||||
Key::LeftArrow => "Left",
|
||||
Key::Option => "Option",
|
||||
Key::PageDown => "Page_Down",
|
||||
Key::PageUp => "Page_Up",
|
||||
Key::Return => "Return",
|
||||
Key::RightArrow => "Right",
|
||||
Key::Shift => "Shift_L",
|
||||
Key::Space => "space",
|
||||
Key::Tab => "Tab",
|
||||
Key::UpArrow => "Up",
|
||||
Key::Numpad0 => "0",
|
||||
Key::Numpad1 => "1",
|
||||
Key::Numpad2 => "2",
|
||||
Key::Numpad3 => "3",
|
||||
Key::Numpad4 => "4",
|
||||
Key::Numpad5 => "5",
|
||||
Key::Numpad6 => "6",
|
||||
Key::Numpad7 => "7",
|
||||
Key::Numpad8 => "8",
|
||||
Key::Numpad9 => "9",
|
||||
Key::Decimal => "KP_Decimal",
|
||||
Key::Cancel => "Cancel",
|
||||
Key::Clear => "Clear",
|
||||
Key::Pause => "Pause",
|
||||
Key::Kana => "Kana",
|
||||
Key::Hangul => "Hangul",
|
||||
Key::Hanja => "Hanja",
|
||||
Key::Kanji => "Kanji",
|
||||
Key::Select => "Select",
|
||||
Key::Print => "Print",
|
||||
Key::Execute => "Execute",
|
||||
Key::Snapshot => "3270_PrintScreen",
|
||||
Key::Insert => "Insert",
|
||||
Key::Help => "Help",
|
||||
Key::Separator => "KP_Separator",
|
||||
Key::Scroll => "Scroll_Lock",
|
||||
Key::NumLock => "Num_Lock",
|
||||
Key::RWin => "Super_R",
|
||||
Key::Apps => "Menu",
|
||||
Key::Multiply => "KP_Multiply",
|
||||
Key::Add => "KP_Add",
|
||||
Key::Subtract => "KP_Subtract",
|
||||
Key::Divide => "KP_Divide",
|
||||
Key::Equals => "KP_Equal",
|
||||
Key::NumpadEnter => "KP_Enter",
|
||||
Key::RightShift => "Shift_R",
|
||||
Key::RightControl => "Control_R",
|
||||
Key::RightAlt => "Mode_switch",
|
||||
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super_L",
|
||||
_ => {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return self.tx.send((PyMsg::Str(s), is_press)).is_ok();
|
||||
}
|
||||
}
|
||||
impl Drop for Enigo {
|
||||
impl Drop for EnigoXdo {
|
||||
fn drop(&mut self) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
@ -203,7 +101,7 @@ impl Drop for Enigo {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MouseControllable for Enigo {
|
||||
impl MouseControllable for EnigoXdo {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
@ -378,7 +276,7 @@ fn keysequence<'a>(key: Key) -> Cow<'a, str> {
|
||||
_ => "",
|
||||
})
|
||||
}
|
||||
impl KeyboardControllable for Enigo {
|
||||
impl KeyboardControllable for EnigoXdo {
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
if self.xdo.is_null() {
|
||||
return false;
|
||||
@ -431,9 +329,6 @@ impl KeyboardControllable for Enigo {
|
||||
if self.xdo.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
if self.send_pynput(&key, true) {
|
||||
return Ok(());
|
||||
}
|
||||
let string = CString::new(&*keysequence(key))?;
|
||||
unsafe {
|
||||
xdo_send_keysequence_window_down(
|
||||
@ -449,9 +344,6 @@ impl KeyboardControllable for Enigo {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
if self.send_pynput(&key, false) {
|
||||
return;
|
||||
}
|
||||
if let Ok(string) = CString::new(&*keysequence(key)) {
|
||||
unsafe {
|
||||
xdo_send_keysequence_window_up(
|
||||
@ -479,127 +371,3 @@ impl KeyboardControllable for Enigo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut PYNPUT_EXIT: bool = false;
|
||||
static mut PYNPUT_REDAY: bool = false;
|
||||
static IPC_FILE: &'static str = "/tmp/RustDesk/pynput_service";
|
||||
|
||||
fn start_pynput_service(rx: mpsc::Receiver<(PyMsg, bool)>) {
|
||||
let mut py = "./pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/share/rustdesk/files/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/lib/rustdesk/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
// enigo libs, not rustdesk root project, so skip using appimage features
|
||||
py = std::env::var("APPDIR").unwrap_or("".to_string()) + "/usr/lib/rustdesk/pynput_service.py";
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
log::error!("{} not exists", py);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("pynput service: {}", py);
|
||||
std::thread::spawn(move || {
|
||||
let username = std::env::var("PYNPUT_USERNAME").unwrap_or("".to_owned());
|
||||
let userid = std::env::var("PYNPUT_USERID").unwrap_or("".to_owned());
|
||||
let status = if username.is_empty() {
|
||||
std::process::Command::new("python3")
|
||||
.arg(&py)
|
||||
.arg(IPC_FILE)
|
||||
.status()
|
||||
.map(|x| x.success())
|
||||
} else {
|
||||
let mut status = Ok(true);
|
||||
for i in 0..100 {
|
||||
if i % 10 == 0 {
|
||||
log::info!("#{} try to start pynput server", i);
|
||||
}
|
||||
status = std::process::Command::new("sudo")
|
||||
.args(vec![
|
||||
"-E",
|
||||
&format!("XDG_RUNTIME_DIR=/run/user/{}", userid) as &str,
|
||||
"-u",
|
||||
&username,
|
||||
"python3",
|
||||
&py,
|
||||
IPC_FILE,
|
||||
])
|
||||
.status()
|
||||
.map(|x| x.success());
|
||||
match status {
|
||||
Ok(true) => break,
|
||||
_ => {}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
status
|
||||
};
|
||||
log::info!(
|
||||
"pynput server exit with username/id {}/{}: {:?}",
|
||||
username,
|
||||
userid,
|
||||
status
|
||||
);
|
||||
unsafe {
|
||||
PYNPUT_EXIT = true;
|
||||
}
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
for i in 0..300 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
let mut conn = match std::os::unix::net::UnixStream::connect(IPC_FILE) {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
if i % 15 == 0 {
|
||||
log::warn!("Failed to connect to {}: {}", IPC_FILE, err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) = conn.set_nonblocking(true) {
|
||||
log::error!("Failed to set ipc nonblocking: {}", err);
|
||||
return;
|
||||
}
|
||||
log::info!("Conntected to pynput server");
|
||||
let d = std::time::Duration::from_millis(30);
|
||||
unsafe {
|
||||
PYNPUT_REDAY = true;
|
||||
}
|
||||
let mut buf = [0u8; 1024];
|
||||
loop {
|
||||
if unsafe { PYNPUT_EXIT } {
|
||||
break;
|
||||
}
|
||||
match rx.recv_timeout(d) {
|
||||
Ok((msg, is_press)) => {
|
||||
let msg = match msg {
|
||||
PyMsg::Char(chr) => {
|
||||
format!("{}{}", if is_press { 'p' } else { 'r' }, chr)
|
||||
}
|
||||
PyMsg::Str(s) => format!("{}{}", if is_press { 'p' } else { 'r' }, s),
|
||||
};
|
||||
let n = msg.len();
|
||||
buf[0] = n as _;
|
||||
buf[1..(n + 1)].copy_from_slice(msg.as_bytes());
|
||||
if let Err(err) = conn.write_all(&buf[..n + 1]) {
|
||||
log::error!("Failed to write to ipc: {}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
mpsc::RecvTimeoutError::Disconnected => {
|
||||
log::error!("pynput sender disconnecte");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
PYNPUT_REDAY = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
@ -21,6 +21,10 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.inner.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
pub fn is_gdi(&self) -> bool {
|
||||
self.inner.is_gdi()
|
||||
}
|
||||
@ -41,8 +45,8 @@ impl Capturer {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.inner.frame(timeout_ms.as_millis() as _) {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.inner.frame(timeout.as_millis() as _) {
|
||||
Ok(frame) => Ok(Frame(frame)),
|
||||
Err(ref error) if error.kind() == TimedOut => Err(WouldBlock.into()),
|
||||
Err(error) => Err(error),
|
||||
@ -129,6 +133,11 @@ impl CapturerMag {
|
||||
data: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.inner.set_use_yuv(use_yuv)
|
||||
}
|
||||
|
||||
pub fn exclude(&mut self, cls: &str, name: &str) -> io::Result<bool> {
|
||||
self.inner.exclude(cls, name)
|
||||
}
|
||||
|
@ -17,6 +17,13 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
match self {
|
||||
Capturer::X11(d) => d.set_use_yuv(use_yuv),
|
||||
Capturer::WAYLAND(d) => d.set_use_yuv(use_yuv),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
match self {
|
||||
Capturer::X11(d) => d.width(),
|
||||
@ -31,10 +38,10 @@ impl Capturer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self {
|
||||
Capturer::X11(d) => d.frame(timeout_ms),
|
||||
Capturer::WAYLAND(d) => d.frame(timeout_ms),
|
||||
Capturer::X11(d) => d.frame(timeout),
|
||||
Capturer::WAYLAND(d) => d.frame(timeout),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,7 +52,7 @@ pub enum Display {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_wayland() -> bool {
|
||||
pub fn is_wayland() -> bool {
|
||||
std::env::var("IS_WAYLAND").is_ok()
|
||||
|| std::env::var("XDG_SESSION_TYPE") == Ok("wayland".to_owned())
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ cfg_if! {
|
||||
mod wayland;
|
||||
mod x11;
|
||||
pub use self::linux::*;
|
||||
pub use self::x11::Frame;
|
||||
} else {
|
||||
mod x11;
|
||||
pub use self::x11::*;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::common::x11::Frame;
|
||||
use crate::wayland::{capturable::*, *};
|
||||
use std::io;
|
||||
use std::{io, time::Duration};
|
||||
|
||||
pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>);
|
||||
|
||||
@ -14,6 +14,10 @@ impl Capturer {
|
||||
Ok(Capturer(display, r, yuv, Default::default()))
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.2 = use_yuv;
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.width()
|
||||
}
|
||||
@ -22,8 +26,8 @@ impl Capturer {
|
||||
self.0.height()
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
match self.1.capture(timeout_ms as _).map_err(map_err)? {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.1.capture(timeout.as_millis() as _).map_err(map_err)? {
|
||||
PixelProvider::BGR0(w, h, x) => Ok(Frame(if self.2 {
|
||||
crate::common::bgra_to_i420(w as _, h as _, &x, &mut self.3);
|
||||
&self.3[..]
|
||||
|
@ -8,6 +8,10 @@ impl Capturer {
|
||||
x11::Capturer::new(display.0, yuv).map(Capturer)
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.0.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.display().rect().w as usize
|
||||
}
|
||||
|
@ -446,6 +446,10 @@ impl CapturerMag {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub(crate) fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
|
||||
let name_c = CString::new(name).unwrap();
|
||||
unsafe {
|
||||
|
@ -156,6 +156,10 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub fn is_gdi(&self) -> bool {
|
||||
self.gdi_capturer.is_some()
|
||||
}
|
||||
|
@ -74,6 +74,10 @@ impl Capturer {
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &Display {
|
||||
&self.display
|
||||
}
|
||||
|
@ -1,236 +0,0 @@
|
||||
from pynput.keyboard import Key, Controller
|
||||
from pynput.keyboard._xorg import KeyCode
|
||||
from pynput._util.xorg import display_manager
|
||||
import Xlib
|
||||
from pynput._util.xorg import *
|
||||
import Xlib
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
|
||||
KeyCode._from_symbol("\0") # test
|
||||
|
||||
DEAD_KEYS = {
|
||||
'`': 65104,
|
||||
'´': 65105,
|
||||
'^': 65106,
|
||||
'~': 65107,
|
||||
'¯': 65108,
|
||||
'˘': 65109,
|
||||
'˙': 65110,
|
||||
'¨': 65111,
|
||||
'˚': 65112,
|
||||
'˝': 65113,
|
||||
'ˇ': 65114,
|
||||
'¸': 65115,
|
||||
'˛': 65116,
|
||||
'℩': 65117, # ?
|
||||
'゛': 65118, # ?
|
||||
'゚ ': 65119,
|
||||
'ٜ': 65120,
|
||||
'↪': 65121,
|
||||
' ̛': 65122,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def my_keyboard_mapping(display):
|
||||
"""Generates a mapping from *keysyms* to *key codes* and required
|
||||
modifier shift states.
|
||||
|
||||
:param Xlib.display.Display display: The display for which to retrieve the
|
||||
keyboard mapping.
|
||||
|
||||
:return: the keyboard mapping
|
||||
"""
|
||||
mapping = {}
|
||||
|
||||
shift_mask = 1 << 0
|
||||
group_mask = alt_gr_mask(display)
|
||||
|
||||
# Iterate over all keysym lists in the keyboard mapping
|
||||
min_keycode = display.display.info.min_keycode
|
||||
keycode_count = display.display.info.max_keycode - min_keycode + 1
|
||||
for index, keysyms in enumerate(display.get_keyboard_mapping(
|
||||
min_keycode, keycode_count)):
|
||||
key_code = index + min_keycode
|
||||
|
||||
# Normalise the keysym list to yield a tuple containing the two groups
|
||||
normalized = keysym_normalize(keysyms)
|
||||
if not normalized:
|
||||
continue
|
||||
|
||||
# Iterate over the groups to extract the shift and modifier state
|
||||
for groups, group in zip(normalized, (False, True)):
|
||||
for keysym, shift in zip(groups, (False, True)):
|
||||
|
||||
if not keysym:
|
||||
continue
|
||||
shift_state = 0 \
|
||||
| (shift_mask if shift else 0) \
|
||||
| (group_mask if group else 0)
|
||||
|
||||
# !!!: Save all keycode combinations of keysym
|
||||
if keysym in mapping:
|
||||
mapping[keysym].append((key_code, shift_state))
|
||||
else:
|
||||
mapping[keysym] = [(key_code, shift_state)]
|
||||
return mapping
|
||||
|
||||
|
||||
class MyController(Controller):
|
||||
def _update_keyboard_mapping(self):
|
||||
"""Updates the keyboard mapping.
|
||||
"""
|
||||
with display_manager(self._display) as dm:
|
||||
self._keyboard_mapping = my_keyboard_mapping(dm)
|
||||
|
||||
def send_event(self, event, keycode, shift_state):
|
||||
with display_manager(self._display) as dm, self.modifiers as modifiers:
|
||||
# Under certain cimcumstances, such as when running under Xephyr,
|
||||
# the value returned by dm.get_input_focus is an int
|
||||
window = dm.get_input_focus().focus
|
||||
send_event = getattr(
|
||||
window,
|
||||
'send_event',
|
||||
lambda event: dm.send_event(window, event))
|
||||
send_event(event(
|
||||
detail=keycode,
|
||||
state=shift_state | self._shift_mask(modifiers),
|
||||
time=0,
|
||||
root=dm.screen().root,
|
||||
window=window,
|
||||
same_screen=0,
|
||||
child=Xlib.X.NONE,
|
||||
root_x=0, root_y=0, event_x=0, event_y=0))
|
||||
|
||||
def fake_input(self, keycode, is_press):
|
||||
with display_manager(self._display) as dm:
|
||||
Xlib.ext.xtest.fake_input(
|
||||
dm,
|
||||
Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease,
|
||||
keycode)
|
||||
|
||||
def _handle(self, key, is_press):
|
||||
"""Resolves a key identifier and sends a keyboard event.
|
||||
:param event: The *X* keyboard event.
|
||||
:param int keysym: The keysym to handle.
|
||||
"""
|
||||
event = Xlib.display.event.KeyPress if is_press \
|
||||
else Xlib.display.event.KeyRelease
|
||||
keysym = self._keysym(key)
|
||||
|
||||
if key.vk is not None:
|
||||
keycode = self._display.keysym_to_keycode(key.vk)
|
||||
self.fake_input(keycode, is_press)
|
||||
# Otherwise use XSendEvent; we need to use this in the general case to
|
||||
# work around problems with keyboard layouts
|
||||
self._emit('_on_fake_event', key, is_press)
|
||||
return
|
||||
|
||||
# Make sure to verify that the key was resolved
|
||||
if keysym is None:
|
||||
raise self.InvalidKeyException(key)
|
||||
|
||||
# There may be multiple keycodes for keysym in keyboard_mapping
|
||||
keycode_flag = len(self.keyboard_mapping[keysym]) == 1
|
||||
if keycode_flag:
|
||||
keycode, shift_state = self.keyboard_mapping[keysym][0]
|
||||
else:
|
||||
keycode, shift_state = self._display.keysym_to_keycode(keysym), 0
|
||||
|
||||
keycode_set = set(map(lambda x: x[0], self.keyboard_mapping[keysym]))
|
||||
# The keycode of the dead key is inconsistent, The keysym has multiple combinations of a keycode.
|
||||
if keycode != self._display.keysym_to_keycode(keysym) \
|
||||
or (keycode_flag == False and keycode == list(keycode_set)[0] and len(keycode_set) == 1):
|
||||
deakkey_chr = str(key).replace("'", '')
|
||||
keysym = DEAD_KEYS[deakkey_chr]
|
||||
keycode, shift_state = self.keyboard_mapping[keysym][0]
|
||||
|
||||
# If the key has a virtual key code, use that immediately with
|
||||
# fake_input; fake input,being an X server extension, has access to
|
||||
# more internal state that we do
|
||||
|
||||
try:
|
||||
with self.modifiers as modifiers:
|
||||
alt_gr = Key.alt_gr in modifiers
|
||||
# !!!: Send_event can't support lock screen, this condition cann't be modified
|
||||
if alt_gr:
|
||||
self.send_event(
|
||||
event, keycode, shift_state)
|
||||
else:
|
||||
self.fake_input(keycode, is_press)
|
||||
except KeyError:
|
||||
with self._borrow_lock:
|
||||
keycode, index, count = self._borrows[keysym]
|
||||
self._send_key(
|
||||
event,
|
||||
keycode,
|
||||
index_to_shift(self._display, index))
|
||||
count += 1 if is_press else -1
|
||||
self._borrows[keysym] = (keycode, index, count)
|
||||
|
||||
# Notify any running listeners
|
||||
self._emit('_on_fake_event', key, is_press)
|
||||
|
||||
|
||||
keyboard = MyController()
|
||||
|
||||
server_address = sys.argv[1]
|
||||
if not os.path.exists(os.path.dirname(server_address)):
|
||||
os.makedirs(os.path.dirname(server_address))
|
||||
|
||||
try:
|
||||
os.unlink(server_address)
|
||||
except OSError:
|
||||
if os.path.exists(server_address):
|
||||
raise
|
||||
|
||||
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
server.bind(server_address)
|
||||
server.listen(1)
|
||||
clientsocket, address = server.accept()
|
||||
os.system('chmod a+rw %s' % server_address)
|
||||
print("Got pynput connection")
|
||||
|
||||
|
||||
def loop():
|
||||
global keyboard
|
||||
buf = []
|
||||
while True:
|
||||
data = clientsocket.recv(1024)
|
||||
if not data:
|
||||
print("Connection broken")
|
||||
break
|
||||
buf.extend(data)
|
||||
while buf:
|
||||
n = buf[0]
|
||||
n = n + 1
|
||||
if len(buf) < n:
|
||||
break
|
||||
msg = bytearray(buf[1:n]).decode("utf-8")
|
||||
buf = buf[n:]
|
||||
if len(msg) < 2:
|
||||
continue
|
||||
if msg[1] == "\0":
|
||||
keyboard = MyController()
|
||||
print("Keyboard reset")
|
||||
continue
|
||||
if len(msg) == 2:
|
||||
name = msg[1]
|
||||
else:
|
||||
name = KeyCode._from_symbol(msg[1:])
|
||||
if str(name) == "<0>":
|
||||
continue
|
||||
try:
|
||||
if msg[0] == "p":
|
||||
keyboard.press(name)
|
||||
else:
|
||||
keyboard.release(name)
|
||||
except Exception as e:
|
||||
print('[x] error key',e)
|
||||
|
||||
|
||||
loop()
|
||||
clientsocket.close()
|
||||
server.close()
|
@ -6,7 +6,7 @@ After=systemd-user-sessions.service
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/rustdesk --service
|
||||
PIDFile=/var/run/rustdesk.pid
|
||||
PIDFile=/run/rustdesk.pid
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=30
|
||||
User=root
|
||||
|
13
rustdesk.service.user
Normal file
13
rustdesk.service.user
Normal file
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=RustDesk
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/rustdesk --server
|
||||
PIDFile=/run/rustdesk.user.pid
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=30
|
||||
LimitNOFILE=100000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
44
src/ipc.rs
44
src/ipc.rs
@ -96,6 +96,45 @@ pub enum FS {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum DataKeyboard {
|
||||
Sequence(String),
|
||||
KeyDown(enigo::Key),
|
||||
KeyUp(enigo::Key),
|
||||
KeyClick(enigo::Key),
|
||||
GetKeyState(enigo::Key),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum DataKeyboardResponse {
|
||||
GetKeyState(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum DataMouse {
|
||||
MoveTo(i32, i32),
|
||||
MoveRelative(i32, i32),
|
||||
Down(enigo::MouseButton),
|
||||
Up(enigo::MouseButton),
|
||||
Click(enigo::MouseButton),
|
||||
ScrollX(i32),
|
||||
ScrollY(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum DataControl {
|
||||
Resolution {
|
||||
minx: i32,
|
||||
maxx: i32,
|
||||
miny: i32,
|
||||
maxy: i32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum Data {
|
||||
@ -141,6 +180,11 @@ pub enum Data {
|
||||
PrivacyModeState((i32, PrivacyModeState)),
|
||||
TestRendezvousServer,
|
||||
Bool((String, Option<bool>)),
|
||||
Keyboard(DataKeyboard),
|
||||
KeyboardResponse(DataKeyboardResponse),
|
||||
Mouse(DataMouse),
|
||||
Control(DataControl),
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
|
@ -108,6 +108,10 @@ fn main() {
|
||||
args.len() > 1,
|
||||
));
|
||||
return;
|
||||
} else if args[0] == "--extract" {
|
||||
#[cfg(feature = "with_rc")]
|
||||
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if args[0] == "--remove" {
|
||||
|
@ -143,7 +143,75 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_uinput_service() {
|
||||
use crate::server::input_service::uinput::service;
|
||||
std::thread::spawn(|| {
|
||||
service::start_service_control();
|
||||
});
|
||||
std::thread::spawn(|| {
|
||||
service::start_service_keyboard();
|
||||
});
|
||||
std::thread::spawn(|| {
|
||||
service::start_service_mouse();
|
||||
});
|
||||
}
|
||||
|
||||
fn try_start_user_service(username: &str) {
|
||||
if username == "" || username == "root" {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(mut cur_username) =
|
||||
run_cmds("ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1".to_owned())
|
||||
{
|
||||
cur_username = cur_username.trim().to_owned();
|
||||
if cur_username != "root" && cur_username != username {
|
||||
let _ = run_cmds(format!(
|
||||
"systemctl --machine={}@.host --user stop rustdesk",
|
||||
&cur_username
|
||||
));
|
||||
} else if cur_username == username {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = run_cmds(format!(
|
||||
"systemctl --machine={}@.host --user start rustdesk",
|
||||
username
|
||||
));
|
||||
}
|
||||
|
||||
fn try_stop_user_service() {
|
||||
if let Ok(mut username) =
|
||||
run_cmds("ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1".to_owned())
|
||||
{
|
||||
username = username.trim().to_owned();
|
||||
if username != "root" {
|
||||
let _ = run_cmds(format!(
|
||||
"systemctl --machine={}@.host --user stop rustdesk",
|
||||
&username
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_server(server: &mut Option<std::process::Child>) {
|
||||
if let Some(mut ps) = server.take() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_status)) => {}
|
||||
Ok(None) => {
|
||||
let _res = ps.wait();
|
||||
}
|
||||
Err(e) => log::error!("error attempting to wait: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_os_service() {
|
||||
start_uinput_service();
|
||||
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
let mut uid = "".to_owned();
|
||||
@ -157,85 +225,106 @@ pub fn start_os_service() {
|
||||
let mut cm0 = false;
|
||||
let mut last_restart = std::time::Instant::now();
|
||||
while running.load(Ordering::SeqCst) {
|
||||
let cm = get_cm();
|
||||
let tmp = get_active_userid();
|
||||
let mut start_new = false;
|
||||
if tmp != uid && !tmp.is_empty() {
|
||||
uid = tmp;
|
||||
log::info!("uid of seat0: {}", uid);
|
||||
let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
|
||||
let mut auth = get_env_tries("XAUTHORITY", &uid, 10);
|
||||
if auth.is_empty() {
|
||||
auth = if std::path::Path::new(&gdm).exists() {
|
||||
gdm
|
||||
} else {
|
||||
let username = get_active_username();
|
||||
if username == "root" {
|
||||
format!("/{}/.Xauthority", username)
|
||||
let username = get_active_username();
|
||||
let is_wayland = current_is_wayland();
|
||||
|
||||
if username == "root" || !is_wayland {
|
||||
// try stop user service
|
||||
try_stop_user_service();
|
||||
|
||||
// try start subprocess "--server"
|
||||
let cm = get_cm();
|
||||
let tmp = get_active_userid();
|
||||
let mut start_new = false;
|
||||
if tmp != uid && !tmp.is_empty() {
|
||||
uid = tmp;
|
||||
log::info!("uid of seat0: {}", uid);
|
||||
let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
|
||||
let mut auth = get_env_tries("XAUTHORITY", &uid, 10);
|
||||
if auth.is_empty() {
|
||||
auth = if std::path::Path::new(&gdm).exists() {
|
||||
gdm
|
||||
} else {
|
||||
let tmp = format!("/home/{}/.Xauthority", username);
|
||||
if std::path::Path::new(&tmp).exists() {
|
||||
tmp
|
||||
let username = get_active_username();
|
||||
if username == "root" {
|
||||
format!("/{}/.Xauthority", username)
|
||||
} else {
|
||||
format!("/var/lib/{}/.Xauthority", username)
|
||||
let tmp = format!("/home/{}/.Xauthority", username);
|
||||
if std::path::Path::new(&tmp).exists() {
|
||||
tmp
|
||||
} else {
|
||||
format!("/var/lib/{}/.Xauthority", username)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
let mut d = get_env("DISPLAY", &uid);
|
||||
if d.is_empty() {
|
||||
d = get_display();
|
||||
}
|
||||
if d.is_empty() {
|
||||
d = ":0".to_owned();
|
||||
}
|
||||
d = d.replace(&whoami::hostname(), "").replace("localhost", "");
|
||||
log::info!("DISPLAY: {}", d);
|
||||
log::info!("XAUTHORITY: {}", auth);
|
||||
std::env::set_var("XAUTHORITY", auth);
|
||||
std::env::set_var("DISPLAY", d);
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
last_restart = std::time::Instant::now();
|
||||
}
|
||||
} else if !cm
|
||||
&& ((cm0 && last_restart.elapsed().as_secs() > 60)
|
||||
|| last_restart.elapsed().as_secs() > 3600)
|
||||
{
|
||||
// restart server if new connections all closed, or every one hour,
|
||||
// as a workaround to resolve "SpotUdp" (dns resolve)
|
||||
// and x server get displays failure issue
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
last_restart = std::time::Instant::now();
|
||||
log::info!("restart server");
|
||||
}
|
||||
}
|
||||
if let Some(ps) = server.as_mut() {
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_)) => {
|
||||
server = None;
|
||||
start_new = true;
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
let mut d = get_env("DISPLAY", &uid);
|
||||
if d.is_empty() {
|
||||
d = get_display();
|
||||
}
|
||||
if d.is_empty() {
|
||||
d = ":0".to_owned();
|
||||
}
|
||||
d = d.replace(&whoami::hostname(), "").replace("localhost", "");
|
||||
log::info!("DISPLAY: {}", d);
|
||||
log::info!("XAUTHORITY: {}", auth);
|
||||
std::env::set_var("XAUTHORITY", auth);
|
||||
std::env::set_var("DISPLAY", d);
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
last_restart = std::time::Instant::now();
|
||||
}
|
||||
} else if !cm
|
||||
&& ((cm0 && last_restart.elapsed().as_secs() > 60)
|
||||
|| last_restart.elapsed().as_secs() > 3600)
|
||||
{
|
||||
// restart server if new connections all closed, or every one hour,
|
||||
// as a workaround to resolve "SpotUdp" (dns resolve)
|
||||
// and x server get displays failure issue
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
last_restart = std::time::Instant::now();
|
||||
log::info!("restart server");
|
||||
}
|
||||
}
|
||||
if let Some(ps) = server.as_mut() {
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_)) => {
|
||||
server = None;
|
||||
start_new = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
start_new = true;
|
||||
}
|
||||
if start_new {
|
||||
match crate::run_me(vec!["--server"]) {
|
||||
Ok(ps) => server = Some(ps),
|
||||
Err(err) => {
|
||||
log::error!("Failed to start server: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
cm0 = cm;
|
||||
} else if username != "" {
|
||||
if username != "gdm" {
|
||||
// try kill subprocess "--server"
|
||||
stop_server(&mut server);
|
||||
|
||||
// try start user service
|
||||
try_start_user_service(&username);
|
||||
}
|
||||
} else {
|
||||
start_new = true;
|
||||
try_stop_user_service();
|
||||
stop_server(&mut server);
|
||||
}
|
||||
if start_new {
|
||||
match crate::run_me(vec!["--server"]) {
|
||||
Ok(ps) => server = Some(ps),
|
||||
Err(err) => {
|
||||
log::error!("Failed to start server: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
cm0 = cm;
|
||||
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
}
|
||||
|
||||
try_stop_user_service();
|
||||
if let Some(ps) = server.take().as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
}
|
||||
|
@ -279,6 +279,8 @@ impl Drop for Server {
|
||||
for s in self.services.values() {
|
||||
s.join();
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
video_service::wayland_support::clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,7 +237,7 @@ impl Connection {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
conn.send(msg_out).await;
|
||||
conn.on_close("Close requested from connection manager", false);
|
||||
conn.on_close("Close requested from connection manager", false).await;
|
||||
SESSIONS.lock().unwrap().remove(&conn.lr.my_id);
|
||||
break;
|
||||
}
|
||||
@ -327,7 +327,7 @@ impl Connection {
|
||||
if let Some(res) = res {
|
||||
match res {
|
||||
Err(err) => {
|
||||
conn.on_close(&err.to_string(), true);
|
||||
conn.on_close(&err.to_string(), true).await;
|
||||
break;
|
||||
},
|
||||
Ok(bytes) => {
|
||||
@ -341,14 +341,14 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
conn.on_close("Reset by the peer", true);
|
||||
conn.on_close("Reset by the peer", true).await;
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = conn.timer.tick() => {
|
||||
if !conn.read_jobs.is_empty() {
|
||||
if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await {
|
||||
conn.on_close(&err.to_string(), false);
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@ -361,7 +361,7 @@ impl Connection {
|
||||
video_service::notify_video_frame_feched(id, Some(instant.into()));
|
||||
}
|
||||
if let Err(err) = conn.stream.send(&value as &Message).await {
|
||||
conn.on_close(&err.to_string(), false);
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -379,13 +379,13 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
if let Err(err) = conn.stream.send(msg).await {
|
||||
conn.on_close(&err.to_string(), false);
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = test_delay_timer.tick() => {
|
||||
if last_recv_time.elapsed() >= SEC30 {
|
||||
conn.on_close("Timeout", true);
|
||||
conn.on_close("Timeout", true).await;
|
||||
break;
|
||||
}
|
||||
let time = crate::get_time();
|
||||
@ -417,7 +417,7 @@ impl Connection {
|
||||
video_service::VIDEO_QOS.lock().unwrap().reset();
|
||||
password::after_session(conn.authorized);
|
||||
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
|
||||
conn.on_close(&err.to_string(), false);
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
}
|
||||
|
||||
conn.post_audit(json!({
|
||||
@ -646,9 +646,9 @@ impl Connection {
|
||||
#[cfg(target_os = "linux")]
|
||||
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() {
|
||||
let dtype = crate::platform::linux::get_display_server();
|
||||
if dtype != "x11" {
|
||||
if dtype != "x11" && dtype != "wayland" {
|
||||
res.set_error(format!(
|
||||
"Unsupported display server type {}, x11 expected",
|
||||
"Unsupported display server type {}, x11 or wayland expected",
|
||||
dtype
|
||||
));
|
||||
let mut msg_out = Message::new();
|
||||
@ -684,7 +684,7 @@ impl Connection {
|
||||
res.set_peer_info(pi);
|
||||
} else {
|
||||
try_activate_screen();
|
||||
match video_service::get_displays() {
|
||||
match super::video_service::get_displays().await {
|
||||
Err(err) => {
|
||||
res.set_error(format!("X11 error: {}", err));
|
||||
}
|
||||
@ -1175,7 +1175,7 @@ impl Connection {
|
||||
},
|
||||
Some(message::Union::Misc(misc)) => match misc.union {
|
||||
Some(misc::Union::SwitchDisplay(s)) => {
|
||||
video_service::switch_display(s.display);
|
||||
video_service::switch_display(s.display).await;
|
||||
}
|
||||
Some(misc::Union::ChatMessage(c)) => {
|
||||
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
|
||||
@ -1185,7 +1185,7 @@ impl Connection {
|
||||
}
|
||||
Some(misc::Union::RefreshVideo(r)) => {
|
||||
if r {
|
||||
video_service::refresh();
|
||||
super::video_service::refresh();
|
||||
}
|
||||
}
|
||||
Some(misc::Union::VideoReceived(_)) => {
|
||||
@ -1195,7 +1195,7 @@ impl Connection {
|
||||
);
|
||||
}
|
||||
Some(misc::Union::CloseReason(_)) => {
|
||||
self.on_close("Peer close", true);
|
||||
self.on_close("Peer close", true).await;
|
||||
SESSIONS.lock().unwrap().remove(&self.lr.my_id);
|
||||
return false;
|
||||
}
|
||||
@ -1353,14 +1353,14 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_close(&mut self, reason: &str, lock: bool) {
|
||||
async fn on_close(&mut self, reason: &str, lock: bool) {
|
||||
if let Some(s) = self.server.upgrade() {
|
||||
s.write().unwrap().remove_connection(&self.inner);
|
||||
}
|
||||
log::info!("#{} Connection closed: {}", self.inner.id(), reason);
|
||||
if lock && self.lock_after_session_end && self.keyboard {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
lock_screen();
|
||||
lock_screen().await;
|
||||
}
|
||||
self.tx_to_cm.send(ipc::Data::Close).ok();
|
||||
self.port_forward_socket.take();
|
||||
|
@ -187,6 +187,26 @@ lazy_static::lazy_static! {
|
||||
static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn set_uinput() -> ResultType<()> {
|
||||
// Keyboard and mouse both open /dev/uinput
|
||||
// TODO: Make sure there's no race
|
||||
let keyboard = self::uinput::client::UInputKeyboard::new().await?;
|
||||
log::info!("UInput keyboard created");
|
||||
let mouse = self::uinput::client::UInputMouse::new().await?;
|
||||
log::info!("UInput mouse created");
|
||||
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
en.set_uinput_keyboard(Some(Box::new(keyboard)));
|
||||
en.set_uinput_mouse(Some(Box::new(mouse)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn set_uinput_resolution(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultType<()> {
|
||||
self::uinput::client::set_resolution(minx, maxx, miny, maxy).await
|
||||
}
|
||||
|
||||
pub fn is_left_up(evt: &MouseEvent) -> bool {
|
||||
let buttons = evt.mask >> 3;
|
||||
let evt_type = evt.mask & 0x7;
|
||||
@ -439,7 +459,7 @@ pub fn is_enter(evt: &KeyEvent) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn lock_screen() {
|
||||
pub async fn lock_screen() {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
// xdg_screensaver lock not work on Linux from our service somehow
|
||||
@ -469,7 +489,7 @@ pub fn lock_screen() {
|
||||
crate::platform::lock_screen();
|
||||
}
|
||||
}
|
||||
super::video_service::switch_to_primary();
|
||||
super::video_service::switch_to_primary().await;
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@ -548,7 +568,6 @@ lazy_static::lazy_static! {
|
||||
(ControlKey::Equals, Key::Equals),
|
||||
(ControlKey::NumpadEnter, Key::NumpadEnter),
|
||||
(ControlKey::RAlt, Key::RightAlt),
|
||||
(ControlKey::RWin, Key::RWin),
|
||||
(ControlKey::RControl, Key::RightControl),
|
||||
(ControlKey::RShift, Key::RightShift),
|
||||
].iter().map(|(a, b)| (a.value(), b.clone())).collect();
|
||||
@ -679,7 +698,7 @@ fn handle_key_(evt: &KeyEvent) {
|
||||
allow_err!(send_sas());
|
||||
});
|
||||
} else if ck.value() == ControlKey::LockScreen.value() {
|
||||
lock_screen();
|
||||
lock_screen_2();
|
||||
}
|
||||
}
|
||||
Some(key_event::Union::Chr(chr)) => {
|
||||
@ -729,9 +748,669 @@ fn handle_key_(evt: &KeyEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn lock_screen_2() {
|
||||
lock_screen().await;
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send_sas() -> ResultType<()> {
|
||||
let mut stream = crate::ipc::connect(1000, crate::POSTFIX_SERVICE).await?;
|
||||
timeout(1000, stream.send(&crate::ipc::Data::SAS)).await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod uinput {
|
||||
use crate::ipc::{self, new_listener, Connection, Data, DataKeyboard, DataMouse};
|
||||
use enigo::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use evdev::{
|
||||
uinput::{VirtualDevice, VirtualDeviceBuilder},
|
||||
AttributeSet, EventType, InputEvent,
|
||||
};
|
||||
use hbb_common::{allow_err, bail, log, tokio, ResultType};
|
||||
|
||||
static IPC_CONN_TIMEOUT: u64 = 1000;
|
||||
static IPC_REQUEST_TIMEOUT: u64 = 1000;
|
||||
static IPC_POSTFIX_KEYBOARD: &str = "_uinput_keyboard";
|
||||
static IPC_POSTFIX_MOUSE: &str = "_uinput_mouse";
|
||||
static IPC_POSTFIX_CONTROL: &str = "_uinput_control";
|
||||
|
||||
pub mod client {
|
||||
use super::*;
|
||||
|
||||
pub struct UInputKeyboard {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl UInputKeyboard {
|
||||
pub async fn new() -> ResultType<Self> {
|
||||
let conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_KEYBOARD).await?;
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send(&mut self, data: Data) -> ResultType<()> {
|
||||
self.conn.send(&data).await
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send_get_key_state(&mut self, data: Data) -> ResultType<bool> {
|
||||
self.conn.send(&data).await?;
|
||||
|
||||
match self.conn.next_timeout(IPC_REQUEST_TIMEOUT).await {
|
||||
Ok(Some(Data::KeyboardResponse(ipc::DataKeyboardResponse::GetKeyState(
|
||||
state,
|
||||
)))) => Ok(state),
|
||||
Ok(Some(resp)) => {
|
||||
// FATAL error!!!
|
||||
bail!(
|
||||
"FATAL error, wait keyboard result other response: {:?}",
|
||||
&resp
|
||||
);
|
||||
}
|
||||
Ok(None) => {
|
||||
// FATAL error!!!
|
||||
// Maybe wait later
|
||||
bail!("FATAL error, wait keyboard result, receive None",);
|
||||
}
|
||||
Err(e) => {
|
||||
// FATAL error!!!
|
||||
bail!(
|
||||
"FATAL error, wait keyboard result timeout {}, {}",
|
||||
&e,
|
||||
IPC_REQUEST_TIMEOUT
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardControllable for UInputKeyboard {
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
match self.send_get_key_state(Data::Keyboard(DataKeyboard::GetKeyState(key))) {
|
||||
Ok(state) => state,
|
||||
Err(e) => {
|
||||
// unreachable!()
|
||||
log::error!("Failed to get key state {}", &e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::Sequence(sequence.to_string()))));
|
||||
}
|
||||
|
||||
// TODO: handle error???
|
||||
fn key_down(&mut self, key: Key) -> enigo::ResultType {
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyDown(key))));
|
||||
Ok(())
|
||||
}
|
||||
fn key_up(&mut self, key: Key) {
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyUp(key))));
|
||||
}
|
||||
fn key_click(&mut self, key: Key) {
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyClick(key))));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UInputMouse {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl UInputMouse {
|
||||
pub async fn new() -> ResultType<Self> {
|
||||
let conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_MOUSE).await?;
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send(&mut self, data: Data) -> ResultType<()> {
|
||||
self.conn.send(&data).await
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseControllable for UInputMouse {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::MoveTo(x, y))));
|
||||
}
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::MoveRelative(x, y))));
|
||||
}
|
||||
// TODO: handle error???
|
||||
fn mouse_down(&mut self, button: MouseButton) -> enigo::ResultType {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::Down(button))));
|
||||
Ok(())
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::Up(button))));
|
||||
}
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::Click(button))));
|
||||
}
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::ScrollX(length))));
|
||||
}
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
allow_err!(self.send(Data::Mouse(DataMouse::ScrollY(length))));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_resolution(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultType<()> {
|
||||
let mut conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_CONTROL).await?;
|
||||
conn.send(&Data::Control(ipc::DataControl::Resolution {
|
||||
minx,
|
||||
maxx,
|
||||
miny,
|
||||
maxy,
|
||||
}))
|
||||
.await?;
|
||||
let _ = conn.next().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod service {
|
||||
use super::*;
|
||||
use hbb_common::lazy_static;
|
||||
use mouce::MouseActions;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref KEY_MAP: HashMap<enigo::Key, evdev::Key> = HashMap::from(
|
||||
[
|
||||
(enigo::Key::Alt, evdev::Key::KEY_LEFTALT),
|
||||
(enigo::Key::Backspace, evdev::Key::KEY_BACKSPACE),
|
||||
(enigo::Key::CapsLock, evdev::Key::KEY_CAPSLOCK),
|
||||
(enigo::Key::Control, evdev::Key::KEY_LEFTCTRL),
|
||||
(enigo::Key::Delete, evdev::Key::KEY_DELETE),
|
||||
(enigo::Key::DownArrow, evdev::Key::KEY_DOWN),
|
||||
(enigo::Key::End, evdev::Key::KEY_END),
|
||||
(enigo::Key::Escape, evdev::Key::KEY_ESC),
|
||||
(enigo::Key::F1, evdev::Key::KEY_F1),
|
||||
(enigo::Key::F10, evdev::Key::KEY_F10),
|
||||
(enigo::Key::F11, evdev::Key::KEY_F11),
|
||||
(enigo::Key::F12, evdev::Key::KEY_F12),
|
||||
(enigo::Key::F2, evdev::Key::KEY_F2),
|
||||
(enigo::Key::F3, evdev::Key::KEY_F3),
|
||||
(enigo::Key::F4, evdev::Key::KEY_F4),
|
||||
(enigo::Key::F5, evdev::Key::KEY_F5),
|
||||
(enigo::Key::F6, evdev::Key::KEY_F6),
|
||||
(enigo::Key::F7, evdev::Key::KEY_F7),
|
||||
(enigo::Key::F8, evdev::Key::KEY_F8),
|
||||
(enigo::Key::F9, evdev::Key::KEY_F9),
|
||||
(enigo::Key::Home, evdev::Key::KEY_HOME),
|
||||
(enigo::Key::LeftArrow, evdev::Key::KEY_LEFT),
|
||||
(enigo::Key::Meta, evdev::Key::KEY_LEFTMETA),
|
||||
(enigo::Key::Option, evdev::Key::KEY_OPTION),
|
||||
(enigo::Key::PageDown, evdev::Key::KEY_PAGEDOWN),
|
||||
(enigo::Key::PageUp, evdev::Key::KEY_PAGEUP),
|
||||
(enigo::Key::Return, evdev::Key::KEY_ENTER),
|
||||
(enigo::Key::RightArrow, evdev::Key::KEY_RIGHT),
|
||||
(enigo::Key::Shift, evdev::Key::KEY_LEFTSHIFT),
|
||||
(enigo::Key::Space, evdev::Key::KEY_SPACE),
|
||||
(enigo::Key::Tab, evdev::Key::KEY_TAB),
|
||||
(enigo::Key::UpArrow, evdev::Key::KEY_UP),
|
||||
(enigo::Key::Numpad0, evdev::Key::KEY_KP0), // check if correct?
|
||||
(enigo::Key::Numpad1, evdev::Key::KEY_KP1),
|
||||
(enigo::Key::Numpad2, evdev::Key::KEY_KP2),
|
||||
(enigo::Key::Numpad3, evdev::Key::KEY_KP3),
|
||||
(enigo::Key::Numpad4, evdev::Key::KEY_KP4),
|
||||
(enigo::Key::Numpad5, evdev::Key::KEY_KP5),
|
||||
(enigo::Key::Numpad6, evdev::Key::KEY_KP6),
|
||||
(enigo::Key::Numpad7, evdev::Key::KEY_KP7),
|
||||
(enigo::Key::Numpad8, evdev::Key::KEY_KP8),
|
||||
(enigo::Key::Numpad9, evdev::Key::KEY_KP9),
|
||||
(enigo::Key::Cancel, evdev::Key::KEY_CANCEL),
|
||||
(enigo::Key::Clear, evdev::Key::KEY_CLEAR),
|
||||
(enigo::Key::Alt, evdev::Key::KEY_LEFTALT),
|
||||
(enigo::Key::Pause, evdev::Key::KEY_PAUSE),
|
||||
(enigo::Key::Kana, evdev::Key::KEY_KATAKANA), // check if correct?
|
||||
(enigo::Key::Hangul, evdev::Key::KEY_HANGEUL), // check if correct?
|
||||
// (enigo::Key::Junja, evdev::Key::KEY_JUNJA), // map?
|
||||
// (enigo::Key::Final, evdev::Key::KEY_FINAL), // map?
|
||||
(enigo::Key::Hanja, evdev::Key::KEY_HANJA),
|
||||
// (enigo::Key::Kanji, evdev::Key::KEY_KANJI), // map?
|
||||
// (enigo::Key::Convert, evdev::Key::KEY_CONVERT),
|
||||
(enigo::Key::Select, evdev::Key::KEY_SELECT),
|
||||
(enigo::Key::Print, evdev::Key::KEY_PRINT),
|
||||
// (enigo::Key::Execute, evdev::Key::KEY_EXECUTE),
|
||||
// (enigo::Key::Snapshot, evdev::Key::KEY_SNAPSHOT),
|
||||
(enigo::Key::Insert, evdev::Key::KEY_INSERT),
|
||||
(enigo::Key::Help, evdev::Key::KEY_HELP),
|
||||
(enigo::Key::Sleep, evdev::Key::KEY_SLEEP),
|
||||
// (enigo::Key::Separator, evdev::Key::KEY_SEPARATOR),
|
||||
(enigo::Key::Scroll, evdev::Key::KEY_SCROLLLOCK),
|
||||
(enigo::Key::NumLock, evdev::Key::KEY_NUMLOCK),
|
||||
(enigo::Key::RWin, evdev::Key::KEY_RIGHTMETA),
|
||||
(enigo::Key::Apps, evdev::Key::KEY_CONTEXT_MENU),
|
||||
(enigo::Key::Multiply, evdev::Key::KEY_KPASTERISK),
|
||||
(enigo::Key::Add, evdev::Key::KEY_KPPLUS),
|
||||
(enigo::Key::Subtract, evdev::Key::KEY_KPMINUS),
|
||||
(enigo::Key::Decimal, evdev::Key::KEY_KPCOMMA), // KEY_KPDOT and KEY_KPCOMMA are exchanged?
|
||||
(enigo::Key::Divide, evdev::Key::KEY_KPSLASH),
|
||||
(enigo::Key::Equals, evdev::Key::KEY_KPEQUAL),
|
||||
(enigo::Key::NumpadEnter, evdev::Key::KEY_KPENTER),
|
||||
(enigo::Key::RightAlt, evdev::Key::KEY_RIGHTALT),
|
||||
(enigo::Key::RightControl, evdev::Key::KEY_RIGHTCTRL),
|
||||
(enigo::Key::RightShift, evdev::Key::KEY_RIGHTSHIFT),
|
||||
]);
|
||||
|
||||
static ref KEY_MAP_LAYOUT: HashMap<char, evdev::Key> = HashMap::from(
|
||||
[
|
||||
('a', evdev::Key::KEY_A),
|
||||
('b', evdev::Key::KEY_B),
|
||||
('c', evdev::Key::KEY_C),
|
||||
('d', evdev::Key::KEY_D),
|
||||
('e', evdev::Key::KEY_E),
|
||||
('f', evdev::Key::KEY_F),
|
||||
('g', evdev::Key::KEY_G),
|
||||
('h', evdev::Key::KEY_H),
|
||||
('i', evdev::Key::KEY_I),
|
||||
('j', evdev::Key::KEY_J),
|
||||
('k', evdev::Key::KEY_K),
|
||||
('l', evdev::Key::KEY_L),
|
||||
('m', evdev::Key::KEY_M),
|
||||
('n', evdev::Key::KEY_N),
|
||||
('o', evdev::Key::KEY_O),
|
||||
('p', evdev::Key::KEY_P),
|
||||
('q', evdev::Key::KEY_Q),
|
||||
('r', evdev::Key::KEY_R),
|
||||
('s', evdev::Key::KEY_S),
|
||||
('t', evdev::Key::KEY_T),
|
||||
('u', evdev::Key::KEY_U),
|
||||
('v', evdev::Key::KEY_V),
|
||||
('w', evdev::Key::KEY_W),
|
||||
('x', evdev::Key::KEY_X),
|
||||
('y', evdev::Key::KEY_Y),
|
||||
('z', evdev::Key::KEY_Z),
|
||||
('0', evdev::Key::KEY_0),
|
||||
('1', evdev::Key::KEY_1),
|
||||
('2', evdev::Key::KEY_2),
|
||||
('3', evdev::Key::KEY_3),
|
||||
('4', evdev::Key::KEY_4),
|
||||
('5', evdev::Key::KEY_5),
|
||||
('6', evdev::Key::KEY_6),
|
||||
('7', evdev::Key::KEY_7),
|
||||
('8', evdev::Key::KEY_8),
|
||||
('9', evdev::Key::KEY_9),
|
||||
('`', evdev::Key::KEY_GRAVE),
|
||||
('-', evdev::Key::KEY_MINUS),
|
||||
('=', evdev::Key::KEY_EQUAL),
|
||||
('[', evdev::Key::KEY_LEFTBRACE),
|
||||
(']', evdev::Key::KEY_RIGHTBRACE),
|
||||
('\\', evdev::Key::KEY_BACKSLASH),
|
||||
(',', evdev::Key::KEY_COMMA),
|
||||
('.', evdev::Key::KEY_DOT),
|
||||
('/', evdev::Key::KEY_SLASH),
|
||||
(';', evdev::Key::KEY_SEMICOLON),
|
||||
('\'', evdev::Key::KEY_APOSTROPHE),
|
||||
]);
|
||||
|
||||
// ((minx, maxx), (miny, maxy))
|
||||
static ref RESOLUTION: Mutex<((i32, i32), (i32, i32))> = Mutex::new(((0, 0), (0, 0)));
|
||||
}
|
||||
|
||||
fn create_uinput_keyboard() -> ResultType<VirtualDevice> {
|
||||
// TODO: ensure keys here
|
||||
let mut keys = AttributeSet::<evdev::Key>::new();
|
||||
for i in evdev::Key::KEY_ESC.code()..(evdev::Key::BTN_TRIGGER_HAPPY40.code() + 1) {
|
||||
let key = evdev::Key::new(i);
|
||||
if !format!("{:?}", &key).contains("unknown key") {
|
||||
keys.insert(key);
|
||||
}
|
||||
}
|
||||
let mut leds = AttributeSet::<evdev::LedType>::new();
|
||||
leds.insert(evdev::LedType::LED_NUML);
|
||||
leds.insert(evdev::LedType::LED_CAPSL);
|
||||
leds.insert(evdev::LedType::LED_SCROLLL);
|
||||
let mut miscs = AttributeSet::<evdev::MiscType>::new();
|
||||
miscs.insert(evdev::MiscType::MSC_SCAN);
|
||||
let keyboard = VirtualDeviceBuilder::new()?
|
||||
.name("RustDesk UInput Keyboard")
|
||||
.with_keys(&keys)?
|
||||
.with_leds(&leds)?
|
||||
.with_miscs(&miscs)?
|
||||
.build()?;
|
||||
Ok(keyboard)
|
||||
}
|
||||
|
||||
fn map_key(key: &enigo::Key) -> ResultType<evdev::Key> {
|
||||
if let Some(k) = KEY_MAP.get(&key) {
|
||||
log::trace!("mapkey {:?}, get {:?}", &key, &k);
|
||||
return Ok(k.clone());
|
||||
} else {
|
||||
match key {
|
||||
enigo::Key::Layout(c) => {
|
||||
if let Some(k) = KEY_MAP_LAYOUT.get(&c) {
|
||||
log::trace!("mapkey {:?}, get {:?}", &key, k);
|
||||
return Ok(k.clone());
|
||||
}
|
||||
}
|
||||
// enigo::Key::Raw(c) => {
|
||||
// let k = evdev::Key::new(c);
|
||||
// if !format!("{:?}", &k).contains("unknown key") {
|
||||
// return Ok(k.clone());
|
||||
// }
|
||||
// }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
bail!("Failed to map key {:?}", &key);
|
||||
}
|
||||
|
||||
async fn ipc_send_data(stream: &mut Connection, data: &Data) {
|
||||
allow_err!(stream.send(data).await);
|
||||
}
|
||||
|
||||
async fn handle_keyboard(
|
||||
stream: &mut Connection,
|
||||
keyboard: &mut VirtualDevice,
|
||||
data: &DataKeyboard,
|
||||
) {
|
||||
log::trace!("handle_keyboard {:?}", &data);
|
||||
match data {
|
||||
DataKeyboard::Sequence(_seq) => {
|
||||
// ignore
|
||||
}
|
||||
DataKeyboard::KeyDown(key) => {
|
||||
if let Ok(k) = map_key(key) {
|
||||
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
|
||||
allow_err!(keyboard.emit(&[down_event]));
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyUp(key) => {
|
||||
if let Ok(k) = map_key(key) {
|
||||
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
|
||||
allow_err!(keyboard.emit(&[up_event]));
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyClick(key) => {
|
||||
if let Ok(k) = map_key(key) {
|
||||
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
|
||||
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
|
||||
allow_err!(keyboard.emit(&[down_event, up_event]));
|
||||
}
|
||||
}
|
||||
DataKeyboard::GetKeyState(key) => {
|
||||
let key_state = if enigo::Key::CapsLock == *key {
|
||||
match keyboard.get_led_state() {
|
||||
Ok(leds) => leds.contains(evdev::LedType::LED_CAPSL),
|
||||
Err(_e) => {
|
||||
// log::debug!("Failed to get led state {}", &_e);
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match keyboard.get_key_state() {
|
||||
Ok(keys) => match key {
|
||||
enigo::Key::Shift => {
|
||||
keys.contains(evdev::Key::KEY_LEFTSHIFT)
|
||||
|| keys.contains(evdev::Key::KEY_RIGHTSHIFT)
|
||||
}
|
||||
enigo::Key::Control => {
|
||||
keys.contains(evdev::Key::KEY_LEFTCTRL)
|
||||
|| keys.contains(evdev::Key::KEY_RIGHTCTRL)
|
||||
}
|
||||
enigo::Key::Alt => {
|
||||
keys.contains(evdev::Key::KEY_LEFTALT)
|
||||
|| keys.contains(evdev::Key::KEY_RIGHTALT)
|
||||
}
|
||||
enigo::Key::NumLock => keys.contains(evdev::Key::KEY_NUMLOCK),
|
||||
enigo::Key::Meta => {
|
||||
keys.contains(evdev::Key::KEY_LEFTMETA)
|
||||
|| keys.contains(evdev::Key::KEY_RIGHTMETA)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
Err(_e) => {
|
||||
// log::debug!("Failed to get key state: {}", &_e);
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
ipc_send_data(
|
||||
stream,
|
||||
&Data::KeyboardResponse(ipc::DataKeyboardResponse::GetKeyState(key_state)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse(mouse: &mut mouce::nix::UInputMouseManager, data: &DataMouse) {
|
||||
log::trace!("handle_mouse {:?}", &data);
|
||||
match data {
|
||||
DataMouse::MoveTo(x, y) => {
|
||||
allow_err!(mouse.move_to(*x as _, *y as _))
|
||||
}
|
||||
DataMouse::MoveRelative(x, y) => {
|
||||
allow_err!(mouse.move_relative(*x, *y))
|
||||
}
|
||||
DataMouse::Down(button) => {
|
||||
let btn = match button {
|
||||
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
|
||||
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
|
||||
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
allow_err!(mouse.press_button(&btn))
|
||||
}
|
||||
DataMouse::Up(button) => {
|
||||
let btn = match button {
|
||||
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
|
||||
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
|
||||
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
allow_err!(mouse.release_button(&btn))
|
||||
}
|
||||
DataMouse::Click(button) => {
|
||||
let btn = match button {
|
||||
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
|
||||
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
|
||||
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
allow_err!(mouse.click_button(&btn))
|
||||
}
|
||||
DataMouse::ScrollX(_length) => {
|
||||
// TODO: not supported for now
|
||||
}
|
||||
DataMouse::ScrollY(length) => {
|
||||
let mut length = *length;
|
||||
|
||||
let scroll = if length < 0 {
|
||||
mouce::common::ScrollDirection::Up
|
||||
} else {
|
||||
mouce::common::ScrollDirection::Down
|
||||
};
|
||||
|
||||
if length < 0 {
|
||||
length = -length;
|
||||
}
|
||||
|
||||
for _ in 0..length {
|
||||
allow_err!(mouse.scroll_wheel(&scroll))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_keyboard_handler(mut stream: Connection) {
|
||||
tokio::spawn(async move {
|
||||
let mut keyboard = match create_uinput_keyboard() {
|
||||
Ok(keyboard) => keyboard,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create keyboard {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::info!("UInput keyboard ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Keyboard(data) => {
|
||||
handle_keyboard(&mut stream, &mut keyboard, &data).await;
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_mouse_handler(mut stream: ipc::Connection) {
|
||||
let resolution = RESOLUTION.lock().unwrap();
|
||||
if resolution.0 .0 == resolution.0 .1 || resolution.1 .0 == resolution.1 .1 {
|
||||
return;
|
||||
}
|
||||
let rng_x = resolution.0.clone();
|
||||
let rng_y = resolution.1.clone();
|
||||
tokio::spawn(async move {
|
||||
log::info!(
|
||||
"Create uinput mouce with rng_x: ({}, {}), rng_y: ({}, {})",
|
||||
rng_x.0,
|
||||
rng_x.1,
|
||||
rng_y.0,
|
||||
rng_y.1
|
||||
);
|
||||
let mut mouse = match mouce::Mouse::new_uinput(rng_x, rng_y) {
|
||||
Ok(mouse) => mouse,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create mouse, {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::info!("UInput mouse ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Mouse(data) => {
|
||||
handle_mouse(&mut mouse, &data);
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_controller_handler(mut stream: ipc::Connection) {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(_err) => {
|
||||
// log::info!("UInput controller ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Control(data) => match data {
|
||||
ipc::DataControl::Resolution{
|
||||
minx,
|
||||
maxx,
|
||||
miny,
|
||||
maxy,
|
||||
} => {
|
||||
*RESOLUTION.lock().unwrap() = ((minx, maxx), (miny, maxy));
|
||||
allow_err!(stream.send(&Data::Empty).await);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Start uinput service.
|
||||
async fn start_service<F: FnOnce(ipc::Connection) + Copy>(postfix: &str, handler: F) {
|
||||
match new_listener(postfix).await {
|
||||
Ok(mut incoming) => {
|
||||
while let Some(result) = incoming.next().await {
|
||||
match result {
|
||||
Ok(stream) => {
|
||||
log::debug!("Got new connection of uinput ipc {}", postfix);
|
||||
handler(Connection::new(stream));
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Couldn't get uinput mouse client: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start uinput mouse ipc service: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start uinput keyboard service.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_service_keyboard() {
|
||||
log::info!("start uinput keyboard service");
|
||||
start_service(IPC_POSTFIX_KEYBOARD, spawn_keyboard_handler).await;
|
||||
}
|
||||
|
||||
/// Start uinput mouse service.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_service_mouse() {
|
||||
log::info!("start uinput mouse service");
|
||||
start_service(IPC_POSTFIX_MOUSE, spawn_mouse_handler).await;
|
||||
}
|
||||
|
||||
/// Start uinput mouse service.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_service_control() {
|
||||
log::info!("start uinput control service");
|
||||
start_service(IPC_POSTFIX_CONTROL, spawn_controller_handler).await;
|
||||
}
|
||||
|
||||
pub fn stop_service_keyboard() {
|
||||
log::info!("stop uinput keyboard service");
|
||||
}
|
||||
pub fn stop_service_mouse() {
|
||||
log::info!("stop uinput mouse service");
|
||||
}
|
||||
pub fn stop_service_control() {
|
||||
log::info!("stop uinput control service");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +130,8 @@ impl VideoFrameController {
|
||||
trait TraitCapturer {
|
||||
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>>;
|
||||
|
||||
fn set_use_yuv(&mut self, use_yuv: bool);
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_gdi(&self) -> bool;
|
||||
#[cfg(windows)]
|
||||
@ -141,6 +143,10 @@ impl TraitCapturer for Capturer {
|
||||
self.frame(timeout)
|
||||
}
|
||||
|
||||
fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_gdi(&self) -> bool {
|
||||
self.is_gdi()
|
||||
@ -158,6 +164,10 @@ impl TraitCapturer for scrap::CapturerMag {
|
||||
self.frame(_timeout_ms)
|
||||
}
|
||||
|
||||
fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
fn is_gdi(&self) -> bool {
|
||||
false
|
||||
}
|
||||
@ -179,6 +189,14 @@ fn check_display_changed(
|
||||
last_width: usize,
|
||||
last_hegiht: usize,
|
||||
) -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// wayland do not support changing display for now
|
||||
if scrap::is_wayland() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let displays = match try_get_displays() {
|
||||
Ok(d) => d,
|
||||
_ => return false,
|
||||
@ -293,6 +311,7 @@ fn ensure_close_virtual_device() -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This function works on privacy mode. Windows only for now.
|
||||
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
|
||||
let test_begin = Instant::now();
|
||||
while test_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||
@ -321,9 +340,24 @@ fn check_uac_switch(privacy_mode_id: i32, captuerer_privacy_mode_id: i32) -> Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(sp: GenericService) -> ResultType<()> {
|
||||
#[cfg(windows)]
|
||||
ensure_close_virtual_device()?;
|
||||
struct CapturerInfo {
|
||||
origin: (i32, i32),
|
||||
width: usize,
|
||||
height: usize,
|
||||
ndisplay: usize,
|
||||
current: usize,
|
||||
privacy_mode_id: i32,
|
||||
_captuerer_privacy_mode_id: i32,
|
||||
capturer: Box<dyn TraitCapturer>,
|
||||
}
|
||||
|
||||
fn get_capturer(use_yuv: bool) -> ResultType<CapturerInfo> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if scrap::is_wayland() {
|
||||
return wayland_support::get_capturer();
|
||||
}
|
||||
}
|
||||
|
||||
let (ndisplay, current, display) = get_current_display()?;
|
||||
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
||||
@ -338,38 +372,6 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
num_cpus::get(),
|
||||
);
|
||||
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
|
||||
video_qos.set_size(width as _, height as _);
|
||||
let mut spf = video_qos.spf();
|
||||
let bitrate = video_qos.generate_bitrate()?;
|
||||
let abr = video_qos.check_abr_config();
|
||||
drop(video_qos);
|
||||
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
|
||||
|
||||
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
||||
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
||||
codec_name,
|
||||
width,
|
||||
height,
|
||||
bitrate: bitrate as _,
|
||||
}),
|
||||
None => EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
timebase: [1, 1000], // Output timestamp precision
|
||||
bitrate,
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut encoder;
|
||||
match Encoder::new(encoder_cfg) {
|
||||
Ok(x) => encoder = x,
|
||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||
}
|
||||
|
||||
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||
#[cfg(not(windows))]
|
||||
let captuerer_privacy_mode_id = privacy_mode_id;
|
||||
@ -389,17 +391,67 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
} else {
|
||||
log::info!("In privacy mode, the peer side cannot watch the screen");
|
||||
}
|
||||
let mut c = create_capturer(captuerer_privacy_mode_id, display, encoder.use_yuv())?;
|
||||
let capturer = create_capturer(captuerer_privacy_mode_id, display, use_yuv)?;
|
||||
Ok(CapturerInfo {
|
||||
origin,
|
||||
width,
|
||||
height,
|
||||
ndisplay,
|
||||
current,
|
||||
privacy_mode_id,
|
||||
_captuerer_privacy_mode_id: captuerer_privacy_mode_id,
|
||||
capturer,
|
||||
})
|
||||
}
|
||||
|
||||
fn run(sp: GenericService) -> ResultType<()> {
|
||||
#[cfg(windows)]
|
||||
ensure_close_virtual_device()?;
|
||||
|
||||
let mut c = get_capturer(true)?;
|
||||
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
|
||||
video_qos.set_size(c.width as _, c.height as _);
|
||||
let mut spf = video_qos.spf();
|
||||
let bitrate = video_qos.generate_bitrate()?;
|
||||
let abr = video_qos.check_abr_config();
|
||||
drop(video_qos);
|
||||
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
|
||||
|
||||
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
||||
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
||||
codec_name,
|
||||
width: c.width,
|
||||
height: c.height,
|
||||
bitrate: bitrate as _,
|
||||
}),
|
||||
None => EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: c.width as _,
|
||||
height: c.height as _,
|
||||
timebase: [1, 1000], // Output timestamp precision
|
||||
bitrate,
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut encoder;
|
||||
match Encoder::new(encoder_cfg) {
|
||||
Ok(x) => encoder = x,
|
||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||
}
|
||||
c.capturer.set_use_yuv(encoder.use_yuv());
|
||||
|
||||
if *SWITCH.lock().unwrap() {
|
||||
log::debug!("Broadcasting display switch");
|
||||
let mut misc = Misc::new();
|
||||
misc.set_switch_display(SwitchDisplay {
|
||||
display: current as _,
|
||||
x: origin.0 as _,
|
||||
y: origin.1 as _,
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
display: c.current as _,
|
||||
x: c.origin.0 as _,
|
||||
y: c.origin.1 as _,
|
||||
width: c.width as _,
|
||||
height: c.height as _,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
@ -415,11 +467,11 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
#[cfg(windows)]
|
||||
let mut try_gdi = 1;
|
||||
#[cfg(windows)]
|
||||
log::info!("gdi: {}", c.is_gdi());
|
||||
log::info!("gdi: {}", c.capturer.is_gdi());
|
||||
|
||||
while sp.ok() {
|
||||
#[cfg(windows)]
|
||||
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
|
||||
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
|
||||
|
||||
{
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
@ -437,11 +489,11 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
if *SWITCH.lock().unwrap() {
|
||||
bail!("SWITCH");
|
||||
}
|
||||
if current != *CURRENT_DISPLAY.lock().unwrap() {
|
||||
if c.current != *CURRENT_DISPLAY.lock().unwrap() {
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
}
|
||||
check_privacy_mode_changed(&sp, privacy_mode_id)?;
|
||||
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if crate::platform::windows::desktop_changed() {
|
||||
@ -451,7 +503,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let now = time::Instant::now();
|
||||
if last_check_displays.elapsed().as_millis() > 1000 {
|
||||
last_check_displays = now;
|
||||
if ndisplay != get_display_num() {
|
||||
if c.ndisplay != get_display_num() {
|
||||
log::info!("Displays changed");
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
@ -463,7 +515,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
frame_controller.reset();
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let res = match c.frame(spf) {
|
||||
let res = match (*c.capturer).frame(spf) {
|
||||
Ok(frame) => {
|
||||
let time = now - start;
|
||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||
@ -486,7 +538,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let res = match c.frame(spf) {
|
||||
let res = match (*c.capturer).frame(spf) {
|
||||
Ok(frame) => {
|
||||
let time = now - start;
|
||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||
@ -505,9 +557,9 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
Err(ref e) if e.kind() == WouldBlock =>
|
||||
{
|
||||
#[cfg(windows)]
|
||||
if try_gdi > 0 && !c.is_gdi() {
|
||||
if try_gdi > 0 && !c.capturer.is_gdi() {
|
||||
if try_gdi > 3 {
|
||||
c.set_gdi();
|
||||
c.capturer.set_gdi();
|
||||
try_gdi = 0;
|
||||
log::info!("No image, fall back to gdi");
|
||||
}
|
||||
@ -515,15 +567,15 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if check_display_changed(ndisplay, current, width, height) {
|
||||
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
|
||||
log::info!("Displays changed");
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if !c.is_gdi() {
|
||||
c.set_gdi();
|
||||
if !c.capturer.is_gdi() {
|
||||
c.capturer.set_gdi();
|
||||
log::info!("dxgi error, fall back to gdi: {:?}", err);
|
||||
continue;
|
||||
}
|
||||
@ -537,9 +589,9 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let timeout_millis = 3_000u64;
|
||||
let wait_begin = Instant::now();
|
||||
while wait_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||
check_privacy_mode_changed(&sp, privacy_mode_id)?;
|
||||
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
|
||||
#[cfg(windows)]
|
||||
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
|
||||
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
|
||||
frame_controller.try_wait_next(&mut fetched_conn_ids, 300);
|
||||
// break if all connections have received current frame
|
||||
if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() {
|
||||
@ -633,6 +685,17 @@ pub fn handle_one_frame_encoded(
|
||||
}
|
||||
|
||||
fn get_display_num() -> usize {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if scrap::is_wayland() {
|
||||
return if let Ok(n) = wayland_support::get_display_num() {
|
||||
n
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(d) = try_get_displays() {
|
||||
d.len()
|
||||
} else {
|
||||
@ -640,14 +703,10 @@ fn get_display_num() -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
// switch to primary display if long time (30 seconds) no users
|
||||
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
|
||||
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
|
||||
}
|
||||
fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
|
||||
let mut displays = Vec::new();
|
||||
let mut primary = 0;
|
||||
for (i, d) in try_get_displays()?.iter().enumerate() {
|
||||
for (i, d) in all.iter().enumerate() {
|
||||
if d.is_primary() {
|
||||
primary = i;
|
||||
}
|
||||
@ -665,12 +724,26 @@ pub fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
if *lock >= displays.len() {
|
||||
*lock = primary
|
||||
}
|
||||
Ok((*lock, displays))
|
||||
(*lock, displays)
|
||||
}
|
||||
|
||||
pub fn switch_display(i: i32) {
|
||||
pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if scrap::is_wayland() {
|
||||
return wayland_support::get_displays().await;
|
||||
}
|
||||
}
|
||||
// switch to primary display if long time (30 seconds) no users
|
||||
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
|
||||
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
|
||||
}
|
||||
Ok(get_displays_2(&try_get_displays()?))
|
||||
}
|
||||
|
||||
pub async fn switch_display(i: i32) {
|
||||
let i = i as usize;
|
||||
if let Ok((_, displays)) = get_displays() {
|
||||
if let Ok((_, displays)) = get_displays().await {
|
||||
if i < displays.len() {
|
||||
*CURRENT_DISPLAY.lock().unwrap() = i;
|
||||
}
|
||||
@ -684,6 +757,16 @@ pub fn refresh() {
|
||||
}
|
||||
|
||||
fn get_primary() -> usize {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if scrap::is_wayland() {
|
||||
return match wayland_support::get_primary() {
|
||||
Ok(n) => n,
|
||||
Err(_) => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(all) = try_get_displays() {
|
||||
for (i, d) in all.iter().enumerate() {
|
||||
if d.is_primary() {
|
||||
@ -694,8 +777,8 @@ fn get_primary() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn switch_to_primary() {
|
||||
switch_display(get_primary() as _);
|
||||
pub async fn switch_to_primary() {
|
||||
switch_display(get_primary() as _).await;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
@ -733,16 +816,15 @@ fn try_get_displays() -> ResultType<Vec<Display>> {
|
||||
Ok(displays)
|
||||
}
|
||||
|
||||
fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
fn get_current_display_2(mut all: Vec<Display>) -> ResultType<(usize, usize, Display)> {
|
||||
let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize;
|
||||
let mut displays = try_get_displays()?;
|
||||
if displays.len() == 0 {
|
||||
if all.len() == 0 {
|
||||
bail!("No displays");
|
||||
}
|
||||
let n = displays.len();
|
||||
let n = all.len();
|
||||
if current >= n {
|
||||
current = 0;
|
||||
for (i, d) in displays.iter().enumerate() {
|
||||
for (i, d) in all.iter().enumerate() {
|
||||
if d.is_primary() {
|
||||
current = i;
|
||||
break;
|
||||
@ -750,5 +832,191 @@ fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
}
|
||||
*CURRENT_DISPLAY.lock().unwrap() = current;
|
||||
}
|
||||
return Ok((n, current, displays.remove(current)));
|
||||
return Ok((n, current, all.remove(current)));
|
||||
}
|
||||
|
||||
fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
get_current_display_2(try_get_displays()?)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod wayland_support {
|
||||
use super::*;
|
||||
use hbb_common::allow_err;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CAP_DISPLAY_INFO: RwLock<u64> = RwLock::new(0);
|
||||
}
|
||||
struct CapDisplayInfo {
|
||||
rects: Vec<((i32, i32), usize, usize)>,
|
||||
displays: Vec<DisplayInfo>,
|
||||
num: usize,
|
||||
primary: usize,
|
||||
current: usize,
|
||||
capturer: *mut Capturer,
|
||||
}
|
||||
|
||||
impl TraitCapturer for *mut Capturer {
|
||||
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> {
|
||||
unsafe { (**self).frame(timeout) }
|
||||
}
|
||||
|
||||
fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
unsafe {
|
||||
(**self).set_use_yuv(use_yuv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_init() -> ResultType<()> {
|
||||
if scrap::is_wayland() {
|
||||
let mut minx = 0;
|
||||
let mut maxx = 0;
|
||||
let mut miny = 0;
|
||||
let mut maxy = 0;
|
||||
|
||||
if *CAP_DISPLAY_INFO.read().unwrap() == 0 {
|
||||
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
|
||||
if *lock == 0 {
|
||||
let all = Display::all()?;
|
||||
let num = all.len();
|
||||
let (primary, displays) = get_displays_2(&all);
|
||||
|
||||
let mut rects: Vec<((i32, i32), usize, usize)> = Vec::new();
|
||||
for d in &all {
|
||||
rects.push((d.origin(), d.width(), d.height()));
|
||||
}
|
||||
|
||||
let (ndisplay, current, display) = get_current_display_2(all)?;
|
||||
let (origin, width, height) =
|
||||
(display.origin(), display.width(), display.height());
|
||||
log::debug!(
|
||||
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
|
||||
ndisplay,
|
||||
current,
|
||||
&origin,
|
||||
width,
|
||||
height,
|
||||
num_cpus::get_physical(),
|
||||
num_cpus::get(),
|
||||
);
|
||||
|
||||
minx = origin.0;
|
||||
maxx = origin.0 + width as i32;
|
||||
miny = origin.1;
|
||||
maxy = origin.1 + height as i32;
|
||||
|
||||
let capturer = Box::into_raw(Box::new(
|
||||
Capturer::new(display, true)
|
||||
.with_context(|| "Failed to create capturer")?,
|
||||
));
|
||||
let cap_display_info = Box::into_raw(Box::new(CapDisplayInfo {
|
||||
rects,
|
||||
displays,
|
||||
num,
|
||||
primary,
|
||||
current,
|
||||
capturer,
|
||||
}));
|
||||
*lock = cap_display_info as _;
|
||||
}
|
||||
}
|
||||
|
||||
if minx != maxx && miny != maxy {
|
||||
log::info!(
|
||||
"send uinput resolution: ({}, {}), ({}, {})",
|
||||
minx,
|
||||
maxx,
|
||||
miny,
|
||||
maxy
|
||||
);
|
||||
allow_err!(input_service::set_uinput_resolution(minx, maxx, miny, maxy).await);
|
||||
allow_err!(input_service::set_uinput().await);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear() {
|
||||
if !scrap::is_wayland() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
|
||||
if *lock != 0 {
|
||||
unsafe {
|
||||
let cap_display_info = Box::from_raw(*lock as *mut CapDisplayInfo);
|
||||
let _ = Box::from_raw(cap_display_info.capturer);
|
||||
}
|
||||
*lock = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
||||
check_init().await?;
|
||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
||||
if addr != 0 {
|
||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
||||
unsafe {
|
||||
let cap_display_info = &*cap_display_info;
|
||||
let primary = cap_display_info.primary;
|
||||
let displays = cap_display_info.displays.clone();
|
||||
Ok((primary, displays))
|
||||
}
|
||||
} else {
|
||||
bail!("Failed to get capturer display info");
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_primary() -> ResultType<usize> {
|
||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
||||
if addr != 0 {
|
||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
||||
unsafe {
|
||||
let cap_display_info = &*cap_display_info;
|
||||
Ok(cap_display_info.primary)
|
||||
}
|
||||
} else {
|
||||
bail!("Failed to get capturer display info");
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_display_num() -> ResultType<usize> {
|
||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
||||
if addr != 0 {
|
||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
||||
unsafe {
|
||||
let cap_display_info = &*cap_display_info;
|
||||
Ok(cap_display_info.num)
|
||||
}
|
||||
} else {
|
||||
bail!("Failed to get capturer display info");
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_capturer() -> ResultType<CapturerInfo> {
|
||||
if !scrap::is_wayland() {
|
||||
bail!("Do not call this function if not wayland");
|
||||
}
|
||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
||||
if addr != 0 {
|
||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
||||
unsafe {
|
||||
let cap_display_info = &*cap_display_info;
|
||||
let rect = cap_display_info.rects[cap_display_info.current];
|
||||
Ok(CapturerInfo {
|
||||
origin: rect.0,
|
||||
width: rect.1,
|
||||
height: rect.2,
|
||||
ndisplay: cap_display_info.num,
|
||||
current: cap_display_info.current,
|
||||
privacy_mode_id: 0,
|
||||
_captuerer_privacy_mode_id: 0,
|
||||
capturer: Box::new(cap_display_info.capturer),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
bail!("Failed to get capturer display info");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user