From aae6e2b16bb47a2b32bcbbd7ee7e18501d71a728 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 7 Jul 2022 01:27:21 +0800 Subject: [PATCH 1/9] linux_wayland_support: init merge, windows build Signed-off-by: fufesou --- .gitignore | 1 + Cargo.lock | 59 ++ Cargo.toml | 6 +- DEBIAN/postinst | 8 +- DEBIAN/preinst | 7 + DEBIAN/prerm | 9 +- build.py | 5 +- libs/enigo/src/lib.rs | 4 +- libs/enigo/src/linux/mod.rs | 37 ++ libs/enigo/src/linux/nix_impl.rs | 178 ++++++ libs/enigo/src/linux/pynput.rs | 280 +++++++++ libs/enigo/src/{linux.rs => linux/xdo.rs} | 250 +------- libs/scrap/src/common/dxgi.rs | 13 +- libs/scrap/src/common/linux.rs | 15 +- libs/scrap/src/common/mod.rs | 1 + libs/scrap/src/common/wayland.rs | 10 +- libs/scrap/src/common/x11.rs | 4 + libs/scrap/src/dxgi/mag.rs | 4 + libs/scrap/src/dxgi/mod.rs | 4 + libs/scrap/src/x11/capturer.rs | 4 + pynput_service.py | 236 -------- rustdesk.service | 2 +- rustdesk.service.user | 13 + src/ipc.rs | 44 ++ src/main.rs | 4 + src/platform/linux.rs | 227 ++++--- src/server.rs | 2 + src/server/connection.rs | 32 +- src/server/input_service.rs | 687 +++++++++++++++++++++- src/server/video_service.rs | 412 ++++++++++--- 30 files changed, 1902 insertions(+), 656 deletions(-) create mode 100644 libs/enigo/src/linux/mod.rs create mode 100644 libs/enigo/src/linux/nix_impl.rs create mode 100644 libs/enigo/src/linux/pynput.rs rename libs/enigo/src/{linux.rs => linux/xdo.rs} (54%) delete mode 100644 pynput_service.py create mode 100644 rustdesk.service.user diff --git a/.gitignore b/.gitignore index 5b26711c5..53bd9cf94 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .vscode .idea .DS_Store +libsciter-gtk.so src/ui/inline.rs extractor __pycache__ diff --git a/Cargo.lock b/Cargo.lock index bbd8edfba..743f0258c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 87fc0f169..2b7f4391c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ default = ["use_dasp"] [dependencies] whoami = "1.2" -scrap = { path = "libs/scrap" } +scrap = { path = "libs/scrap", features = ["wayland"] } hbb_common = { path = "libs/hbb_common" } serde_derive = "1.0" serde = "1.0" @@ -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" diff --git a/DEBIAN/postinst b/DEBIAN/postinst index 5899bd4df..1c7697acc 100644 --- a/DEBIAN/postinst +++ b/DEBIAN/postinst @@ -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 diff --git a/DEBIAN/preinst b/DEBIAN/preinst index 8b73e9962..7fbedca4a 100644 --- a/DEBIAN/preinst +++ b/DEBIAN/preinst @@ -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 diff --git a/DEBIAN/prerm b/DEBIAN/prerm index 865b689ab..3bb453198 100644 --- a/DEBIAN/prerm +++ b/DEBIAN/prerm @@ -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 diff --git a/build.py b/build.py index 2b7cd3f27..efa6f7831 100644 --- a/build.py +++ b/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/') diff --git a/libs/enigo/src/lib.rs b/libs/enigo/src/lib.rs index 10cde9cbe..40ba1c1db 100644 --- a/libs/enigo/src/lib.rs +++ b/libs/enigo/src/lib.rs @@ -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, diff --git a/libs/enigo/src/linux/mod.rs b/libs/enigo/src/linux/mod.rs new file mode 100644 index 000000000..3c0784660 --- /dev/null +++ b/libs/enigo/src/linux/mod.rs @@ -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 + } +} diff --git a/libs/enigo/src/linux/nix_impl.rs b/libs/enigo/src/linux/nix_impl.rs new file mode 100644 index 000000000..332b6d16e --- /dev/null +++ b/libs/enigo/src/linux/nix_impl.rs @@ -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>, + uinput_mouse: Option>, +} + +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>, + ) { + self.uinput_keyboard = uinput_keyboard + } + /// Set uinput mouse. + pub fn set_uinput_mouse(&mut self, uinput_mouse: Option>) { + 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) + } + } + } +} diff --git a/libs/enigo/src/linux/pynput.rs b/libs/enigo/src/linux/pynput.rs new file mode 100644 index 000000000..748b30105 --- /dev/null +++ b/libs/enigo/src/linux/pynput.rs @@ -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; + } + }); +} diff --git a/libs/enigo/src/linux.rs b/libs/enigo/src/linux/xdo.rs similarity index 54% rename from libs/enigo/src/linux.rs rename to libs/enigo/src/linux/xdo.rs index adfe9507c..0e3b79ab3 100644 --- a/libs/enigo/src/linux.rs +++ b/libs/enigo/src/linux/xdo.rs @@ -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 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; - } - }); -} diff --git a/libs/scrap/src/common/dxgi.rs b/libs/scrap/src/common/dxgi.rs index 1a8c39885..855ac7ac3 100644 --- a/libs/scrap/src/common/dxgi.rs +++ b/libs/scrap/src/common/dxgi.rs @@ -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> { - match self.inner.frame(timeout_ms.as_millis() as _) { + pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result> { + 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 { self.inner.exclude(cls, name) } diff --git a/libs/scrap/src/common/linux.rs b/libs/scrap/src/common/linux.rs index 50bab092c..06a4ed9e0 100644 --- a/libs/scrap/src/common/linux.rs +++ b/libs/scrap/src/common/linux.rs @@ -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> { + pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result> { 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()) } diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index 792ea14e1..9115bfd3a 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -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::*; diff --git a/libs/scrap/src/common/wayland.rs b/libs/scrap/src/common/wayland.rs index ff6bf8022..05bb08744 100644 --- a/libs/scrap/src/common/wayland.rs +++ b/libs/scrap/src/common/wayland.rs @@ -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, bool, Vec); @@ -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> { - match self.1.capture(timeout_ms as _).map_err(map_err)? { + pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result> { + 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[..] diff --git a/libs/scrap/src/common/x11.rs b/libs/scrap/src/common/x11.rs index 255819902..c1a25c8d6 100644 --- a/libs/scrap/src/common/x11.rs +++ b/libs/scrap/src/common/x11.rs @@ -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 } diff --git a/libs/scrap/src/dxgi/mag.rs b/libs/scrap/src/dxgi/mag.rs index 9adf26cdb..78f14194c 100644 --- a/libs/scrap/src/dxgi/mag.rs +++ b/libs/scrap/src/dxgi/mag.rs @@ -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 { let name_c = CString::new(name).unwrap(); unsafe { diff --git a/libs/scrap/src/dxgi/mod.rs b/libs/scrap/src/dxgi/mod.rs index 46692535d..6b60b256d 100644 --- a/libs/scrap/src/dxgi/mod.rs +++ b/libs/scrap/src/dxgi/mod.rs @@ -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() } diff --git a/libs/scrap/src/x11/capturer.rs b/libs/scrap/src/x11/capturer.rs index 890b9db63..ed424c35a 100644 --- a/libs/scrap/src/x11/capturer.rs +++ b/libs/scrap/src/x11/capturer.rs @@ -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 } diff --git a/pynput_service.py b/pynput_service.py deleted file mode 100644 index c51e9a524..000000000 --- a/pynput_service.py +++ /dev/null @@ -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() diff --git a/rustdesk.service b/rustdesk.service index af4a3c411..e703b056f 100644 --- a/rustdesk.service +++ b/rustdesk.service @@ -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 diff --git a/rustdesk.service.user b/rustdesk.service.user new file mode 100644 index 000000000..0756ad35e --- /dev/null +++ b/rustdesk.service.user @@ -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 diff --git a/src/ipc.rs b/src/ipc.rs index 5f2f83b89..a5615ce6a 100644 --- a/src/ipc.rs +++ b/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)), + Keyboard(DataKeyboard), + KeyboardResponse(DataKeyboardResponse), + Mouse(DataMouse), + Control(DataControl), + Empty, } #[tokio::main(flavor = "current_thread")] diff --git a/src/main.rs b/src/main.rs index 2f30f4b4d..c8f76cbd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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" { diff --git a/src/platform/linux.rs b/src/platform/linux.rs index efd6476b6..b08793d12 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -143,7 +143,75 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType { } } +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) { + 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()); } diff --git a/src/server.rs b/src/server.rs index d437ce6d4..f1171e61d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -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(); } } diff --git a/src/server/connection.rs b/src/server/connection.rs index 04b070cfe..869df4196 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -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(); diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 3ec9c0f70..5d0ebe452 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -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 { + 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 { + 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 { + 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 = 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 = 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 { + // TODO: ensure keys here + let mut keys = AttributeSet::::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::::new(); + leds.insert(evdev::LedType::LED_NUML); + leds.insert(evdev::LedType::LED_CAPSL); + leds.insert(evdev::LedType::LED_SCROLLL); + let mut miscs = AttributeSet::::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 { + 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(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"); + } + } +} diff --git a/src/server/video_service.rs b/src/server/video_service.rs index e64ddd807..c8b628015 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -130,6 +130,8 @@ impl VideoFrameController { trait TraitCapturer { fn frame<'a>(&'a mut self, timeout: Duration) -> Result>; + 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, +} + +fn get_capturer(use_yuv: bool) -> ResultType { + #[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)> { - // 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) -> (usize, Vec) { 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)> { 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)> { + #[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> { Ok(displays) } -fn get_current_display() -> ResultType<(usize, usize, Display)> { +fn get_current_display_2(mut all: Vec) -> 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 = RwLock::new(0); + } + struct CapDisplayInfo { + rects: Vec<((i32, i32), usize, usize)>, + displays: Vec, + num: usize, + primary: usize, + current: usize, + capturer: *mut Capturer, + } + + impl TraitCapturer for *mut Capturer { + fn frame<'a>(&'a mut self, timeout: Duration) -> Result> { + 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)> { + 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 { + 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 { + 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 { + 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"); + } + } } From 05b264c96842107ccb797e32a758fedbfaceae41 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 6 Jul 2022 10:49:50 -0700 Subject: [PATCH 2/9] linux_wayland_support: build linux Signed-off-by: fufesou --- DEBIAN/postinst | 0 DEBIAN/postrm | 0 DEBIAN/preinst | 0 DEBIAN/prerm | 0 libs/scrap/src/common/linux.rs | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 DEBIAN/postinst mode change 100644 => 100755 DEBIAN/postrm mode change 100644 => 100755 DEBIAN/preinst mode change 100644 => 100755 DEBIAN/prerm diff --git a/DEBIAN/postinst b/DEBIAN/postinst old mode 100644 new mode 100755 diff --git a/DEBIAN/postrm b/DEBIAN/postrm old mode 100644 new mode 100755 diff --git a/DEBIAN/preinst b/DEBIAN/preinst old mode 100644 new mode 100755 diff --git a/DEBIAN/prerm b/DEBIAN/prerm old mode 100644 new mode 100755 diff --git a/libs/scrap/src/common/linux.rs b/libs/scrap/src/common/linux.rs index 06a4ed9e0..2a921477e 100644 --- a/libs/scrap/src/common/linux.rs +++ b/libs/scrap/src/common/linux.rs @@ -2,7 +2,7 @@ use crate::common::{ wayland, x11::{self, Frame}, }; -use std::io; +use std::{io, time::Duration}; pub enum Capturer { X11(x11::Capturer), From 8e121eacd9919e1299624168b34c9b044a7386b6 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 7 Jul 2022 02:05:32 +0800 Subject: [PATCH 3/9] linux_wayland_support: pynput_service.py Signed-off-by: fufesou --- pynput_service.py | 236 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 pynput_service.py diff --git a/pynput_service.py b/pynput_service.py new file mode 100644 index 000000000..c51e9a524 --- /dev/null +++ b/pynput_service.py @@ -0,0 +1,236 @@ +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() From 8d72dec0881b185ff747ca3bb6829431862fd013 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 6 Jul 2022 19:40:20 -0700 Subject: [PATCH 4/9] linux_wayland_support: debug wayland Signed-off-by: fufesou --- rustdesk.service.user | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rustdesk.service.user b/rustdesk.service.user index 0756ad35e..f6c7454c9 100644 --- a/rustdesk.service.user +++ b/rustdesk.service.user @@ -1,5 +1,5 @@ [Unit] -Description=RustDesk +Description=RustDesk user service (--server) [Service] Type=simple @@ -8,6 +8,8 @@ PIDFile=/run/rustdesk.user.pid KillMode=mixed TimeoutStopSec=30 LimitNOFILE=100000 +Restart=on-failure +RestartSec=3 [Install] WantedBy=multi-user.target From 01bbfcad8e678f7e3d937a534047468a0cae9fca Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 6 Jul 2022 23:16:32 -0700 Subject: [PATCH 5/9] linux_wayland_support: debug multi user login Signed-off-by: fufesou --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 743f0258c..160765a04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "mouce" version = "0.2.1" -source = "git+https://github.com/fufesou/mouce.git#7da9d9b6597f4c4461881deb4ed49da2385e3cac" +source = "git+https://github.com/fufesou/mouce.git#f503ca2b01c50f46c2a0f61d37cd23a1732f7b55" dependencies = [ "glob", ] From 6721d729e9c7069483613f2ecc08f6737cd3291d Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 7 Jul 2022 20:23:57 +0800 Subject: [PATCH 6/9] linux_wayland_support: gstreamer-1.0 deps Signed-off-by: fufesou --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd8282187..aa1f5595b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: shell: bash run: | case ${{ matrix.job.target }} in - x86_64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake ;; + x86_64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev ;; # arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; # aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; esac From 613b1a858248e4a26ded629825a0171c9a0a1428 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 19 Jul 2022 07:36:13 -0700 Subject: [PATCH 7/9] linux_wayland_support: update crate mouce Signed-off-by: fufesou --- Cargo.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 160765a04..70c76a9cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2772,9 +2772,10 @@ dependencies = [ [[package]] name = "mouce" version = "0.2.1" -source = "git+https://github.com/fufesou/mouce.git#f503ca2b01c50f46c2a0f61d37cd23a1732f7b55" +source = "git+https://github.com/fufesou/mouce.git#26da8d4b0009b7f96996799c2a5c0990a8dbf08b" dependencies = [ "glob", + "libc", ] [[package]] From 634cb5ef1adeec2fc219b46a458a36eb00adb2d9 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 20 Jul 2022 09:50:08 -0700 Subject: [PATCH 8/9] linux_wayland_support: refactor Signed-off-by: fufesou --- src/platform/linux.rs | 2 +- src/server.rs | 8 +- src/server/input_service.rs | 661 +----------------------------------- src/server/uinput.rs | 651 +++++++++++++++++++++++++++++++++++ src/server/video_service.rs | 245 +++---------- src/server/wayland.rs | 179 ++++++++++ 6 files changed, 879 insertions(+), 867 deletions(-) create mode 100644 src/server/uinput.rs create mode 100644 src/server/wayland.rs diff --git a/src/platform/linux.rs b/src/platform/linux.rs index b08793d12..14d43e0e1 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -144,7 +144,7 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType { } fn start_uinput_service() { - use crate::server::input_service::uinput::service; + use crate::server::uinput::service; std::thread::spawn(|| { service::start_service_control(); }); diff --git a/src/server.rs b/src/server.rs index f1171e61d..a71efaee2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -7,7 +7,7 @@ use hbb_common::{ config::{Config, Config2, CONNECT_TIMEOUT, RELAY_PORT}, log, message_proto::*, - protobuf::{Message as _, Enum}, + protobuf::{Enum, Message as _}, rendezvous_proto::*, socket_client, sodiumoxide::crypto::{box_, secretbox, sign}, @@ -24,6 +24,10 @@ pub mod audio_service; cfg_if::cfg_if! { if #[cfg(not(any(target_os = "android", target_os = "ios")))] { mod clipboard_service; +#[cfg(target_os = "linux")] +mod wayland; +#[cfg(target_os = "linux")] +pub mod uinput; pub mod input_service; } else { mod clipboard_service { @@ -280,7 +284,7 @@ impl Drop for Server { s.join(); } #[cfg(target_os = "linux")] - video_service::wayland_support::clear(); + wayland::clear(); } } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 5d0ebe452..29f4207b3 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -191,9 +191,9 @@ lazy_static::lazy_static! { 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?; + let keyboard = super::uinput::client::UInputKeyboard::new().await?; log::info!("UInput keyboard created"); - let mouse = self::uinput::client::UInputMouse::new().await?; + let mouse = super::uinput::client::UInputMouse::new().await?; log::info!("UInput mouse created"); let mut en = ENIGO.lock().unwrap(); @@ -204,7 +204,7 @@ pub async fn set_uinput() -> ResultType<()> { #[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 + super::uinput::client::set_resolution(minx, maxx, miny, maxy).await } pub fn is_left_up(evt: &MouseEvent) -> bool { @@ -759,658 +759,3 @@ async fn send_sas() -> ResultType<()> { 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 { - 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 { - 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 { - 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 = 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 = 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 { - // TODO: ensure keys here - let mut keys = AttributeSet::::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::::new(); - leds.insert(evdev::LedType::LED_NUML); - leds.insert(evdev::LedType::LED_CAPSL); - leds.insert(evdev::LedType::LED_SCROLLL); - let mut miscs = AttributeSet::::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 { - 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(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"); - } - } -} diff --git a/src/server/uinput.rs b/src/server/uinput.rs new file mode 100644 index 000000000..7a6d47cff --- /dev/null +++ b/src/server/uinput.rs @@ -0,0 +1,651 @@ +use crate::ipc::{self, new_listener, Connection, Data, DataKeyboard, DataMouse}; +use enigo::{Key, KeyboardControllable, MouseButton, MouseControllable}; +use evdev::{ + uinput::{VirtualDevice, VirtualDeviceBuilder}, + AttributeSet, EventType, InputEvent, +}; +use hbb_common::{allow_err, bail, log, tokio, ResultType}; + +static IPC_CONN_TIMEOUT: u64 = 1000; +static IPC_REQUEST_TIMEOUT: u64 = 1000; +static IPC_POSTFIX_KEYBOARD: &str = "_uinput_keyboard"; +static IPC_POSTFIX_MOUSE: &str = "_uinput_mouse"; +static IPC_POSTFIX_CONTROL: &str = "_uinput_control"; + +pub mod client { + use super::*; + + pub struct UInputKeyboard { + conn: Connection, + } + + impl UInputKeyboard { + pub async fn new() -> ResultType { + 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 { + 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 { + 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 = 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 = 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 { + // TODO: ensure keys here + let mut keys = AttributeSet::::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::::new(); + leds.insert(evdev::LedType::LED_NUML); + leds.insert(evdev::LedType::LED_CAPSL); + leds.insert(evdev::LedType::LED_SCROLLL); + let mut miscs = AttributeSet::::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 { + 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(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"); + } +} diff --git a/src/server/video_service.rs b/src/server/video_service.rs index c8b628015..ede62efee 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -31,6 +31,7 @@ use scrap::{ use std::{ collections::HashSet, io::{ErrorKind::WouldBlock, Result}, + ops::{Deref, DerefMut}, time::{self, Duration, Instant}, }; #[cfg(windows)] @@ -127,7 +128,7 @@ impl VideoFrameController { } } -trait TraitCapturer { +pub(super) trait TraitCapturer { fn frame<'a>(&'a mut self, timeout: Duration) -> Result>; fn set_use_yuv(&mut self, use_yuv: bool); @@ -340,22 +341,36 @@ fn check_uac_switch(privacy_mode_id: i32, captuerer_privacy_mode_id: i32) -> Res Ok(()) } -struct CapturerInfo { - origin: (i32, i32), - width: usize, - height: usize, - ndisplay: usize, - current: usize, - privacy_mode_id: i32, - _captuerer_privacy_mode_id: i32, - capturer: Box, +pub(super) struct CapturerInfo { + pub origin: (i32, i32), + pub width: usize, + pub height: usize, + pub ndisplay: usize, + pub current: usize, + pub privacy_mode_id: i32, + pub _captuerer_privacy_mode_id: i32, + pub capturer: Box, +} + +impl Deref for CapturerInfo { + type Target = Box; + + fn deref(&self) -> &Self::Target { + &self.capturer + } +} + +impl DerefMut for CapturerInfo { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.capturer + } } fn get_capturer(use_yuv: bool) -> ResultType { #[cfg(target_os = "linux")] { if scrap::is_wayland() { - return wayland_support::get_capturer(); + return super::wayland::get_capturer(); } } @@ -441,7 +456,7 @@ fn run(sp: GenericService) -> ResultType<()> { Ok(x) => encoder = x, Err(err) => bail!("Failed to create encoder: {}", err), } - c.capturer.set_use_yuv(encoder.use_yuv()); + c.set_use_yuv(encoder.use_yuv()); if *SWITCH.lock().unwrap() { log::debug!("Broadcasting display switch"); @@ -467,7 +482,7 @@ fn run(sp: GenericService) -> ResultType<()> { #[cfg(windows)] let mut try_gdi = 1; #[cfg(windows)] - log::info!("gdi: {}", c.capturer.is_gdi()); + log::info!("gdi: {}", c.is_gdi()); while sp.ok() { #[cfg(windows)] @@ -515,7 +530,7 @@ fn run(sp: GenericService) -> ResultType<()> { frame_controller.reset(); #[cfg(any(target_os = "android", target_os = "ios"))] - let res = match (*c.capturer).frame(spf) { + let res = match c.frame(spf) { Ok(frame) => { let time = now - start; let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64; @@ -538,7 +553,7 @@ fn run(sp: GenericService) -> ResultType<()> { }; #[cfg(not(any(target_os = "android", target_os = "ios")))] - let res = match (*c.capturer).frame(spf) { + let res = match c.frame(spf) { Ok(frame) => { let time = now - start; let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64; @@ -557,9 +572,9 @@ fn run(sp: GenericService) -> ResultType<()> { Err(ref e) if e.kind() == WouldBlock => { #[cfg(windows)] - if try_gdi > 0 && !c.capturer.is_gdi() { + if try_gdi > 0 && !c.is_gdi() { if try_gdi > 3 { - c.capturer.set_gdi(); + c.set_gdi(); try_gdi = 0; log::info!("No image, fall back to gdi"); } @@ -574,8 +589,8 @@ fn run(sp: GenericService) -> ResultType<()> { } #[cfg(windows)] - if !c.capturer.is_gdi() { - c.capturer.set_gdi(); + if !c.is_gdi() { + c.set_gdi(); log::info!("dxgi error, fall back to gdi: {:?}", err); continue; } @@ -688,7 +703,7 @@ fn get_display_num() -> usize { #[cfg(target_os = "linux")] { if scrap::is_wayland() { - return if let Ok(n) = wayland_support::get_display_num() { + return if let Ok(n) = super::wayland::get_display_num() { n } else { 0 @@ -703,7 +718,7 @@ fn get_display_num() -> usize { } } -fn get_displays_2(all: &Vec) -> (usize, Vec) { +pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { let mut displays = Vec::new(); let mut primary = 0; for (i, d) in all.iter().enumerate() { @@ -731,7 +746,7 @@ pub async fn get_displays() -> ResultType<(usize, Vec)> { #[cfg(target_os = "linux")] { if scrap::is_wayland() { - return wayland_support::get_displays().await; + return super::wayland::get_displays().await; } } // switch to primary display if long time (30 seconds) no users @@ -760,7 +775,7 @@ fn get_primary() -> usize { #[cfg(target_os = "linux")] { if scrap::is_wayland() { - return match wayland_support::get_primary() { + return match super::wayland::get_primary() { Ok(n) => n, Err(_) => 0, }; @@ -816,7 +831,7 @@ fn try_get_displays() -> ResultType> { Ok(displays) } -fn get_current_display_2(mut all: Vec) -> ResultType<(usize, usize, Display)> { +pub(super) fn get_current_display_2(mut all: Vec) -> ResultType<(usize, usize, Display)> { let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize; if all.len() == 0 { bail!("No displays"); @@ -838,185 +853,3 @@ fn get_current_display_2(mut all: Vec) -> ResultType<(usize, usize, Dis 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 = RwLock::new(0); - } - struct CapDisplayInfo { - rects: Vec<((i32, i32), usize, usize)>, - displays: Vec, - num: usize, - primary: usize, - current: usize, - capturer: *mut Capturer, - } - - impl TraitCapturer for *mut Capturer { - fn frame<'a>(&'a mut self, timeout: Duration) -> Result> { - 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)> { - 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 { - 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 { - 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 { - 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"); - } - } -} diff --git a/src/server/wayland.rs b/src/server/wayland.rs new file mode 100644 index 000000000..b81d378a6 --- /dev/null +++ b/src/server/wayland.rs @@ -0,0 +1,179 @@ +use super::*; +use hbb_common::allow_err; +use scrap::{Capturer, Display, Frame}; +use std::{io::Result, time::Duration}; + +lazy_static::lazy_static! { + static ref CAP_DISPLAY_INFO: RwLock = RwLock::new(0); +} +struct CapDisplayInfo { + rects: Vec<((i32, i32), usize, usize)>, + displays: Vec, + num: usize, + primary: usize, + current: usize, + capturer: *mut Capturer, +} + +impl super::video_service::TraitCapturer for *mut Capturer { + fn frame<'a>(&'a mut self, timeout: Duration) -> Result> { + 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) = super::video_service::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) = + super::video_service::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)> { + 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 { + 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 { + 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 { + 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(super::video_service::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"); + } +} From 00dc473703b38b70d2ecc3dbc0a47fab9519a6d8 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 20 Jul 2022 10:44:27 -0700 Subject: [PATCH 9/9] linux_wayland_support: dup detecting function of x11 or wayland Signed-off-by: fufesou --- Cargo.lock | 1 + libs/enigo/Cargo.toml | 1 + libs/enigo/src/lib.rs | 2 +- libs/enigo/src/linux/mod.rs | 32 -------- libs/enigo/src/linux/nix_impl.rs | 2 +- libs/hbb_common/src/lib.rs | 1 + libs/hbb_common/src/platform/linux.rs | 102 +++++++++++++++++++++++++ libs/hbb_common/src/platform/mod.rs | 2 + libs/scrap/src/common/linux.rs | 23 +++--- src/platform/linux.rs | 103 +------------------------- src/server/video_service.rs | 10 +-- src/server/wayland.rs | 6 +- 12 files changed, 130 insertions(+), 155 deletions(-) create mode 100644 libs/hbb_common/src/platform/linux.rs create mode 100644 libs/hbb_common/src/platform/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 70c76a9cc..c91272112 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1266,6 +1266,7 @@ name = "enigo" version = "0.0.14" dependencies = [ "core-graphics 0.22.3", + "hbb_common", "libc", "log", "objc", diff --git a/libs/enigo/Cargo.toml b/libs/enigo/Cargo.toml index 6842dab56..b0028b564 100644 --- a/libs/enigo/Cargo.toml +++ b/libs/enigo/Cargo.toml @@ -22,6 +22,7 @@ appveyor = { repository = "pythoneer/enigo-85xiy" } serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } log = "0.4" +hbb_common = { path = "../hbb_common" } [features] with_serde = ["serde", "serde_derive"] diff --git a/libs/enigo/src/lib.rs b/libs/enigo/src/lib.rs index 40ba1c1db..164fb1c17 100644 --- a/libs/enigo/src/lib.rs +++ b/libs/enigo/src/lib.rs @@ -74,7 +74,7 @@ pub use macos::Enigo; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "linux")] -pub use crate::linux::{is_x11, Enigo}; +pub use crate::linux::Enigo; /// DSL parser module pub mod dsl; diff --git a/libs/enigo/src/linux/mod.rs b/libs/enigo/src/linux/mod.rs index 3c0784660..42e1dfebf 100644 --- a/libs/enigo/src/linux/mod.rs +++ b/libs/enigo/src/linux/mod.rs @@ -3,35 +3,3 @@ 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 - } -} diff --git a/libs/enigo/src/linux/nix_impl.rs b/libs/enigo/src/linux/nix_impl.rs index 332b6d16e..840290b2b 100644 --- a/libs/enigo/src/linux/nix_impl.rs +++ b/libs/enigo/src/linux/nix_impl.rs @@ -40,7 +40,7 @@ impl Enigo { impl Default for Enigo { fn default() -> Self { Self { - is_x11: crate::linux::is_x11(), + is_x11: "x11" == hbb_common::platform::linux::get_display_server(), uinput_keyboard: None, uinput_mouse: None, xdo: EnigoXdo::default(), diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index fdd32c4c7..a5443db0f 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -1,5 +1,6 @@ pub mod compress; pub mod protos; +pub mod platform; pub use protos::message as message_proto; pub use protos::rendezvous as rendezvous_proto; pub use bytes; diff --git a/libs/hbb_common/src/platform/linux.rs b/libs/hbb_common/src/platform/linux.rs new file mode 100644 index 000000000..da79e9e39 --- /dev/null +++ b/libs/hbb_common/src/platform/linux.rs @@ -0,0 +1,102 @@ +use crate::ResultType; + +pub fn get_display_server() -> String { + let session = get_value_of_seat0(0); + get_display_server_of_session(&session) +} + +fn get_display_server_of_session(session: &str) -> String { + if let Ok(output) = std::process::Command::new("loginctl") + .args(vec!["show-session", "-p", "Type", session]) + .output() + // Check session type of the session + { + let display_server = String::from_utf8_lossy(&output.stdout) + .replace("Type=", "") + .trim_end() + .into(); + if display_server == "tty" { + // If the type is tty... + if let Ok(output) = std::process::Command::new("loginctl") + .args(vec!["show-session", "-p", "TTY", session]) + .output() + // Get the tty number + { + let tty: String = String::from_utf8_lossy(&output.stdout) + .replace("TTY=", "") + .trim_end() + .into(); + if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty)) + // And check if Xorg is running on that tty + { + if xorg_results.trim_end().to_string() != "" { + // If it is, manually return "x11", otherwise return tty + "x11".to_owned() + } else { + display_server + } + } else { + // If any of these commands fail just fall back to the display server + display_server + } + } else { + display_server + } + } else { + // If the session is not a tty, then just return the type as usual + display_server + } + } else { + "".to_owned() + } +} + +pub fn get_value_of_seat0(i: usize) -> String { + if let Ok(output) = std::process::Command::new("loginctl").output() { + for line in String::from_utf8_lossy(&output.stdout).lines() { + if line.contains("seat0") { + if let Some(sid) = line.split_whitespace().nth(0) { + if is_active(sid) { + if let Some(uid) = line.split_whitespace().nth(i) { + return uid.to_owned(); + } + } + } + } + } + } + + // some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73 + if let Ok(output) = std::process::Command::new("loginctl").output() { + for line in String::from_utf8_lossy(&output.stdout).lines() { + if let Some(sid) = line.split_whitespace().nth(0) { + let d = get_display_server_of_session(sid); + if is_active(sid) && d != "tty" { + if let Some(uid) = line.split_whitespace().nth(i) { + return uid.to_owned(); + } + } + } + } + } + + return "".to_owned(); +} + +fn is_active(sid: &str) -> bool { + if let Ok(output) = std::process::Command::new("loginctl") + .args(vec!["show-session", "-p", "State", sid]) + .output() + { + String::from_utf8_lossy(&output.stdout).contains("active") + } else { + false + } +} + +pub fn run_cmds(cmds: String) -> ResultType { + let output = std::process::Command::new("sh") + .args(vec!["-c", &cmds]) + .output()?; + Ok(String::from_utf8_lossy(&output.stdout).to_string()) +} diff --git a/libs/hbb_common/src/platform/mod.rs b/libs/hbb_common/src/platform/mod.rs new file mode 100644 index 000000000..8daba257f --- /dev/null +++ b/libs/hbb_common/src/platform/mod.rs @@ -0,0 +1,2 @@ +#[cfg(target_os = "linux")] +pub mod linux; diff --git a/libs/scrap/src/common/linux.rs b/libs/scrap/src/common/linux.rs index 2a921477e..8498ab7ff 100644 --- a/libs/scrap/src/common/linux.rs +++ b/libs/scrap/src/common/linux.rs @@ -52,31 +52,30 @@ pub enum Display { } #[inline] -pub fn is_wayland() -> bool { - std::env::var("IS_WAYLAND").is_ok() - || std::env::var("XDG_SESSION_TYPE") == Ok("wayland".to_owned()) +pub fn is_x11() -> bool { + "x11" == hbb_common::platform::linux::get_display_server() } impl Display { pub fn primary() -> io::Result { - Ok(if is_wayland() { - Display::WAYLAND(wayland::Display::primary()?) - } else { + Ok(if is_x11() { Display::X11(x11::Display::primary()?) + } else { + Display::WAYLAND(wayland::Display::primary()?) }) } pub fn all() -> io::Result> { - Ok(if is_wayland() { - wayland::Display::all()? - .drain(..) - .map(|x| Display::WAYLAND(x)) - .collect() - } else { + Ok(if is_x11() { x11::Display::all()? .drain(..) .map(|x| Display::X11(x)) .collect() + } else { + wayland::Display::all()? + .drain(..) + .map(|x| Display::WAYLAND(x)) + .collect() }) } diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 14d43e0e1..5a12f5ac0 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,4 +1,5 @@ use super::{CursorData, ResultType}; +pub use hbb_common::platform::linux::*; use hbb_common::{allow_err, bail, log}; use libc::{c_char, c_int, c_void}; use std::{ @@ -8,6 +9,7 @@ use std::{ Arc, }, }; + type Xdo = *const c_void; pub const PA_SAMPLE_RATE: u32 = 48000; @@ -335,17 +337,6 @@ pub fn get_active_userid() -> String { get_value_of_seat0(1) } -fn is_active(sid: &str) -> bool { - if let Ok(output) = std::process::Command::new("loginctl") - .args(vec!["show-session", "-p", "State", sid]) - .output() - { - String::from_utf8_lossy(&output.stdout).contains("active") - } else { - false - } -} - fn get_cm() -> bool { if let Ok(output) = std::process::Command::new("ps").args(vec!["aux"]).output() { for line in String::from_utf8_lossy(&output.stdout).lines() { @@ -401,89 +392,6 @@ fn get_display() -> String { last } -fn get_value_of_seat0(i: usize) -> String { - if let Ok(output) = std::process::Command::new("loginctl").output() { - for line in String::from_utf8_lossy(&output.stdout).lines() { - if line.contains("seat0") { - if let Some(sid) = line.split_whitespace().nth(0) { - if is_active(sid) { - if let Some(uid) = line.split_whitespace().nth(i) { - return uid.to_owned(); - } - } - } - } - } - } - - // some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73 - if let Ok(output) = std::process::Command::new("loginctl").output() { - for line in String::from_utf8_lossy(&output.stdout).lines() { - if let Some(sid) = line.split_whitespace().nth(0) { - let d = get_display_server_of_session(sid); - if is_active(sid) && d != "tty" { - if let Some(uid) = line.split_whitespace().nth(i) { - return uid.to_owned(); - } - } - } - } - } - - return "".to_owned(); -} - -pub fn get_display_server() -> String { - let session = get_value_of_seat0(0); - get_display_server_of_session(&session) -} - -fn get_display_server_of_session(session: &str) -> String { - if let Ok(output) = std::process::Command::new("loginctl") - .args(vec!["show-session", "-p", "Type", session]) - .output() - // Check session type of the session - { - let display_server = String::from_utf8_lossy(&output.stdout) - .replace("Type=", "") - .trim_end() - .into(); - if display_server == "tty" { - // If the type is tty... - if let Ok(output) = std::process::Command::new("loginctl") - .args(vec!["show-session", "-p", "TTY", session]) - .output() - // Get the tty number - { - let tty: String = String::from_utf8_lossy(&output.stdout) - .replace("TTY=", "") - .trim_end() - .into(); - if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty)) - // And check if Xorg is running on that tty - { - if xorg_results.trim_end().to_string() != "" { - // If it is, manually return "x11", otherwise return tty - "x11".to_owned() - } else { - display_server - } - } else { - // If any of these commands fail just fall back to the display server - display_server - } - } else { - display_server - } - } else { - // If the session is not a tty, then just return the type as usual - display_server - } - } else { - "".to_owned() - } -} - pub fn is_login_wayland() -> bool { if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") { contents.contains("#WaylandEnable=false") @@ -690,13 +598,6 @@ pub fn is_installed() -> bool { true } -pub fn run_cmds(cmds: String) -> ResultType { - let output = std::process::Command::new("sh") - .args(vec!["-c", &cmds]) - .output()?; - Ok(String::from_utf8_lossy(&output.stdout).to_string()) -} - fn get_env_tries(name: &str, uid: &str, n: usize) -> String { for _ in 0..n { let x = get_env(name, uid); diff --git a/src/server/video_service.rs b/src/server/video_service.rs index ede62efee..e6ea713a1 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -193,7 +193,7 @@ fn check_display_changed( #[cfg(target_os = "linux")] { // wayland do not support changing display for now - if scrap::is_wayland() { + if !scrap::is_x11() { return false; } } @@ -369,7 +369,7 @@ impl DerefMut for CapturerInfo { fn get_capturer(use_yuv: bool) -> ResultType { #[cfg(target_os = "linux")] { - if scrap::is_wayland() { + if !scrap::is_x11() { return super::wayland::get_capturer(); } } @@ -702,7 +702,7 @@ pub fn handle_one_frame_encoded( fn get_display_num() -> usize { #[cfg(target_os = "linux")] { - if scrap::is_wayland() { + if !scrap::is_x11() { return if let Ok(n) = super::wayland::get_display_num() { n } else { @@ -745,7 +745,7 @@ pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { pub async fn get_displays() -> ResultType<(usize, Vec)> { #[cfg(target_os = "linux")] { - if scrap::is_wayland() { + if !scrap::is_x11() { return super::wayland::get_displays().await; } } @@ -774,7 +774,7 @@ pub fn refresh() { fn get_primary() -> usize { #[cfg(target_os = "linux")] { - if scrap::is_wayland() { + if !scrap::is_x11() { return match super::wayland::get_primary() { Ok(n) => n, Err(_) => 0, diff --git a/src/server/wayland.rs b/src/server/wayland.rs index b81d378a6..e84be3f7c 100644 --- a/src/server/wayland.rs +++ b/src/server/wayland.rs @@ -28,7 +28,7 @@ impl super::video_service::TraitCapturer for *mut Capturer { } async fn check_init() -> ResultType<()> { - if scrap::is_wayland() { + if !scrap::is_x11() { let mut minx = 0; let mut maxx = 0; let mut miny = 0; @@ -96,7 +96,7 @@ async fn check_init() -> ResultType<()> { } pub fn clear() { - if !scrap::is_wayland() { + if scrap::is_x11() { return; } @@ -153,7 +153,7 @@ pub(super) fn get_display_num() -> ResultType { } pub(super) fn get_capturer() -> ResultType { - if !scrap::is_wayland() { + if scrap::is_x11() { bail!("Do not call this function if not wayland"); } let addr = *CAP_DISPLAY_INFO.read().unwrap();