mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge branch 'master' of https://github.com/rustdesk/rustdesk
This commit is contained in:
@@ -23,6 +23,7 @@ serde = { version = "1.0", optional = true }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
log = "0.4"
|
||||
rdev = { git = "https://github.com/asur4s/rdev" }
|
||||
hbb_common = { path = "../hbb_common" }
|
||||
|
||||
[features]
|
||||
with_serde = ["serde", "serde_derive"]
|
||||
|
||||
@@ -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,
|
||||
|
||||
5
libs/enigo/src/linux/mod.rs
Normal file
5
libs/enigo/src/linux/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod nix_impl;
|
||||
mod pynput;
|
||||
mod xdo;
|
||||
|
||||
pub use self::nix_impl::Enigo;
|
||||
178
libs/enigo/src/linux/nix_impl.rs
Normal file
178
libs/enigo/src/linux/nix_impl.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
use super::{pynput::EnigoPynput, xdo::EnigoXdo};
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
// #[derive(Default)]
|
||||
pub struct Enigo {
|
||||
xdo: EnigoXdo,
|
||||
pynput: EnigoPynput,
|
||||
is_x11: bool,
|
||||
uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
|
||||
uinput_mouse: Option<Box<dyn MouseControllable + Send>>,
|
||||
}
|
||||
|
||||
impl Enigo {
|
||||
/// Get delay of xdo implementation.
|
||||
pub fn delay(&self) -> u64 {
|
||||
self.xdo.delay()
|
||||
}
|
||||
/// Set delay of xdo implemetation.
|
||||
pub fn set_delay(&mut self, delay: u64) {
|
||||
self.xdo.set_delay(delay)
|
||||
}
|
||||
/// Reset pynput.
|
||||
pub fn reset(&mut self) {
|
||||
self.pynput.reset();
|
||||
}
|
||||
/// Set uinput keyboard.
|
||||
pub fn set_uinput_keyboard(
|
||||
&mut self,
|
||||
uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
|
||||
) {
|
||||
self.uinput_keyboard = uinput_keyboard
|
||||
}
|
||||
/// Set uinput mouse.
|
||||
pub fn set_uinput_mouse(&mut self, uinput_mouse: Option<Box<dyn MouseControllable + Send>>) {
|
||||
self.uinput_mouse = uinput_mouse
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Enigo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_x11: "x11" == hbb_common::platform::linux::get_display_server(),
|
||||
uinput_keyboard: None,
|
||||
uinput_mouse: None,
|
||||
xdo: EnigoXdo::default(),
|
||||
pynput: EnigoPynput::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseControllable for Enigo {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_move_to(x, y);
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_move_to(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_move_relative(x, y);
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_move_relative(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_down(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_down(button)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_up(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_up(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_click(button)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_click(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_scroll_x(length)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_scroll_x(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
if self.is_x11 {
|
||||
self.xdo.mouse_scroll_y(length)
|
||||
} else {
|
||||
if let Some(mouse) = &mut self.uinput_mouse {
|
||||
mouse.mouse_scroll_y(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardControllable for Enigo {
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
if self.is_x11 {
|
||||
self.xdo.get_key_state(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.get_key_state(key)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
if self.is_x11 {
|
||||
self.xdo.key_sequence(sequence)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_sequence(sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
if self.is_x11 {
|
||||
if self.pynput.send_pynput(&key, true) {
|
||||
return Ok(());
|
||||
}
|
||||
self.xdo.key_down(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_down(key)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_up(&mut self, key: Key) {
|
||||
if self.is_x11 {
|
||||
if self.pynput.send_pynput(&key, false) {
|
||||
return;
|
||||
}
|
||||
self.xdo.key_up(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_up(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_click(&mut self, key: Key) {
|
||||
if self.is_x11 {
|
||||
self.xdo.key_click(key)
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.uinput_keyboard {
|
||||
keyboard.key_click(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
280
libs/enigo/src/linux/pynput.rs
Normal file
280
libs/enigo/src/linux/pynput.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
use crate::Key;
|
||||
use std::{io::prelude::*, sync::mpsc};
|
||||
|
||||
enum PyMsg {
|
||||
Char(char),
|
||||
Str(&'static str),
|
||||
}
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub(super) struct EnigoPynput {
|
||||
tx: mpsc::Sender<(PyMsg, bool)>,
|
||||
}
|
||||
|
||||
impl Default for EnigoPynput {
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
start_pynput_service(rx);
|
||||
Self { tx }
|
||||
}
|
||||
}
|
||||
impl EnigoPynput {
|
||||
pub(super) fn reset(&mut self) {
|
||||
self.tx.send((PyMsg::Char('\0'), true)).ok();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn send_pynput(&mut self, key: &Key, is_press: bool) -> bool {
|
||||
if unsafe { PYNPUT_EXIT || !PYNPUT_REDAY } {
|
||||
return false;
|
||||
}
|
||||
if let Key::Layout(c) = key {
|
||||
return self.tx.send((PyMsg::Char(*c), is_press)).is_ok();
|
||||
}
|
||||
if let Key::Raw(_) = key {
|
||||
return false;
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
let s = match key {
|
||||
Key::Alt => "Alt_L",
|
||||
Key::Backspace => "BackSpace",
|
||||
Key::CapsLock => "Caps_Lock",
|
||||
Key::Control => "Control_L",
|
||||
Key::Delete => "Delete",
|
||||
Key::DownArrow => "Down",
|
||||
Key::End => "End",
|
||||
Key::Escape => "Escape",
|
||||
Key::F1 => "F1",
|
||||
Key::F10 => "F10",
|
||||
Key::F11 => "F11",
|
||||
Key::F12 => "F12",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F6 => "F6",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
Key::F9 => "F9",
|
||||
Key::Home => "Home",
|
||||
Key::LeftArrow => "Left",
|
||||
Key::Option => "Option",
|
||||
Key::PageDown => "Page_Down",
|
||||
Key::PageUp => "Page_Up",
|
||||
Key::Return => "Return",
|
||||
Key::RightArrow => "Right",
|
||||
Key::Shift => "Shift_L",
|
||||
Key::Space => "space",
|
||||
Key::Tab => "Tab",
|
||||
Key::UpArrow => "Up",
|
||||
Key::Numpad0 => "0",
|
||||
Key::Numpad1 => "1",
|
||||
Key::Numpad2 => "2",
|
||||
Key::Numpad3 => "3",
|
||||
Key::Numpad4 => "4",
|
||||
Key::Numpad5 => "5",
|
||||
Key::Numpad6 => "6",
|
||||
Key::Numpad7 => "7",
|
||||
Key::Numpad8 => "8",
|
||||
Key::Numpad9 => "9",
|
||||
Key::Decimal => "KP_Decimal",
|
||||
Key::Cancel => "Cancel",
|
||||
Key::Clear => "Clear",
|
||||
Key::Pause => "Pause",
|
||||
Key::Kana => "Kana",
|
||||
Key::Hangul => "Hangul",
|
||||
Key::Hanja => "Hanja",
|
||||
Key::Kanji => "Kanji",
|
||||
Key::Select => "Select",
|
||||
Key::Print => "Print",
|
||||
Key::Execute => "Execute",
|
||||
Key::Snapshot => "3270_PrintScreen",
|
||||
Key::Insert => "Insert",
|
||||
Key::Help => "Help",
|
||||
Key::Separator => "KP_Separator",
|
||||
Key::Scroll => "Scroll_Lock",
|
||||
Key::NumLock => "Num_Lock",
|
||||
Key::RWin => "Super_R",
|
||||
Key::Apps => "Menu",
|
||||
Key::Multiply => "KP_Multiply",
|
||||
Key::Add => "KP_Add",
|
||||
Key::Subtract => "KP_Subtract",
|
||||
Key::Divide => "KP_Divide",
|
||||
Key::Equals => "KP_Equal",
|
||||
Key::NumpadEnter => "KP_Enter",
|
||||
Key::RightShift => "Shift_R",
|
||||
Key::RightControl => "Control_R",
|
||||
Key::RightAlt => "Mode_switch",
|
||||
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super_L",
|
||||
_ => {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
log::info!("send pynput: {:?}", &s);
|
||||
return self.tx.send((PyMsg::Str(s), is_press)).is_ok();
|
||||
}
|
||||
}
|
||||
|
||||
// impl MouseControllable for EnigoPynput {
|
||||
// fn mouse_move_to(&mut self, _x: i32, _y: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_move_relative(&mut self, _x: i32, _y: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_down(&mut self, _button: MouseButton) -> crate::ResultType {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_up(&mut self, _button: MouseButton) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_click(&mut self, _button: MouseButton) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_scroll_x(&mut self, _length: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn mouse_scroll_y(&mut self, _length: i32) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl KeyboardControllable for EnigoPynput {
|
||||
// fn get_key_state(&mut self, _key: Key) -> bool {
|
||||
// unimplemented!()
|
||||
// }
|
||||
|
||||
// fn key_sequence(&mut self, _sequence: &str) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
// let _ = self.send_pynput(&key, true);
|
||||
// Ok(())
|
||||
// }
|
||||
// fn key_up(&mut self, key: Key) {
|
||||
// let _ = self.send_pynput(&key, false);
|
||||
// }
|
||||
// fn key_click(&mut self, _key: Key) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// }
|
||||
|
||||
static mut PYNPUT_EXIT: bool = false;
|
||||
static mut PYNPUT_REDAY: bool = false;
|
||||
static IPC_FILE: &'static str = "/tmp/RustDesk/pynput_service";
|
||||
|
||||
fn start_pynput_service(rx: mpsc::Receiver<(PyMsg, bool)>) {
|
||||
let mut py = "./pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/share/rustdesk/files/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
py = "/usr/lib/rustdesk/pynput_service.py".to_owned();
|
||||
if !std::path::Path::new(&py).exists() {
|
||||
log::error!("{} not exits", py);
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("pynput service: {}", py);
|
||||
std::thread::spawn(move || {
|
||||
let username = std::env::var("PYNPUT_USERNAME").unwrap_or("".to_owned());
|
||||
let userid = std::env::var("PYNPUT_USERID").unwrap_or("".to_owned());
|
||||
let status = if username.is_empty() {
|
||||
std::process::Command::new("python3")
|
||||
.arg(&py)
|
||||
.arg(IPC_FILE)
|
||||
.status()
|
||||
.map(|x| x.success())
|
||||
} else {
|
||||
let mut status = Ok(true);
|
||||
for i in 0..100 {
|
||||
if i % 10 == 0 {
|
||||
log::info!("#{} try to start pynput server", i);
|
||||
}
|
||||
status = std::process::Command::new("sudo")
|
||||
.args(vec![
|
||||
"-E",
|
||||
&format!("XDG_RUNTIME_DIR=/run/user/{}", userid) as &str,
|
||||
"-u",
|
||||
&username,
|
||||
"python3",
|
||||
&py,
|
||||
IPC_FILE,
|
||||
])
|
||||
.status()
|
||||
.map(|x| x.success());
|
||||
match status {
|
||||
Ok(true) => break,
|
||||
_ => {}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
status
|
||||
};
|
||||
log::info!(
|
||||
"pynput server exit with username/id {}/{}: {:?}",
|
||||
username,
|
||||
userid,
|
||||
status
|
||||
);
|
||||
unsafe {
|
||||
PYNPUT_EXIT = true;
|
||||
}
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
for i in 0..300 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
let mut conn = match std::os::unix::net::UnixStream::connect(IPC_FILE) {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
if i % 15 == 0 {
|
||||
log::warn!("Failed to connect to {}: {}", IPC_FILE, err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) = conn.set_nonblocking(true) {
|
||||
log::error!("Failed to set ipc nonblocking: {}", err);
|
||||
return;
|
||||
}
|
||||
log::info!("Conntected to pynput server");
|
||||
let d = std::time::Duration::from_millis(30);
|
||||
unsafe {
|
||||
PYNPUT_REDAY = true;
|
||||
}
|
||||
let mut buf = [0u8; 1024];
|
||||
loop {
|
||||
if unsafe { PYNPUT_EXIT } {
|
||||
break;
|
||||
}
|
||||
match rx.recv_timeout(d) {
|
||||
Ok((msg, is_press)) => {
|
||||
let msg = match msg {
|
||||
PyMsg::Char(chr) => {
|
||||
format!("{}{}", if is_press { 'p' } else { 'r' }, chr)
|
||||
}
|
||||
PyMsg::Str(s) => format!("{}{}", if is_press { 'p' } else { 'r' }, s),
|
||||
};
|
||||
let n = msg.len();
|
||||
buf[0] = n as _;
|
||||
buf[1..(n + 1)].copy_from_slice(msg.as_bytes());
|
||||
if let Err(err) = conn.write_all(&buf[..n + 1]) {
|
||||
log::error!("Failed to write to ipc: {}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
mpsc::RecvTimeoutError::Disconnected => {
|
||||
log::error!("pynput sender disconnecte");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
PYNPUT_REDAY = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,7 @@ 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};
|
||||
|
||||
const CURRENT_WINDOW: c_int = 0;
|
||||
const DEFAULT_DELAY: u64 = 12000;
|
||||
type Window = c_int;
|
||||
@@ -59,34 +60,25 @@ fn mousebutton(button: MouseButton) -> c_int {
|
||||
}
|
||||
}
|
||||
|
||||
enum PyMsg {
|
||||
Char(char),
|
||||
Str(&'static str),
|
||||
}
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub struct Enigo {
|
||||
pub(super) struct EnigoXdo {
|
||||
xdo: Xdo,
|
||||
delay: u64,
|
||||
tx: mpsc::Sender<(PyMsg, bool)>,
|
||||
}
|
||||
// This is safe, we have a unique pointer.
|
||||
// TODO: use Unique<c_char> once stable.
|
||||
unsafe impl Send for Enigo {}
|
||||
unsafe impl Send for EnigoXdo {}
|
||||
|
||||
impl Default for Enigo {
|
||||
/// Create a new Enigo instance
|
||||
impl Default for EnigoXdo {
|
||||
/// Create a new EnigoXdo instance
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
start_pynput_service(rx);
|
||||
Self {
|
||||
xdo: unsafe { xdo_new(ptr::null()) },
|
||||
delay: DEFAULT_DELAY,
|
||||
tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Enigo {
|
||||
impl EnigoXdo {
|
||||
/// Get the delay per keypress.
|
||||
/// Default value is 12000.
|
||||
/// This is Linux-specific.
|
||||
@@ -98,102 +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(
|
||||
@@ -494,128 +386,3 @@ impl KeyboardControllable for Enigo {
|
||||
crate::dsl::eval(self, sequence)
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -7,9 +7,9 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
protobuf = "3.0.0-alpha.2"
|
||||
tokio = { version = "1.15", features = ["full"] }
|
||||
tokio-util = { version = "0.6", features = ["full"] }
|
||||
protobuf = { version = "3.1", features = ["with-bytes"] }
|
||||
tokio = { version = "1.20", features = ["full"] }
|
||||
tokio-util = { version = "0.7", features = ["full"] }
|
||||
futures = "0.3"
|
||||
bytes = "1.1"
|
||||
log = "0.4"
|
||||
@@ -23,6 +23,7 @@ directories-next = "2.0"
|
||||
rand = "0.8"
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
serde_with = "1.14.0"
|
||||
lazy_static = "1.4"
|
||||
confy = { git = "https://github.com/open-trade/confy" }
|
||||
dirs-next = "2.0"
|
||||
@@ -33,12 +34,13 @@ tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
mac_address = "1.1"
|
||||
machine-uid = "0.2"
|
||||
|
||||
[features]
|
||||
quic = []
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen-pure = "3.0.0-alpha.2"
|
||||
protobuf-codegen = { version = "3.1" }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
fn main() {
|
||||
std::fs::create_dir_all("src/protos").unwrap();
|
||||
protobuf_codegen_pure::Codegen::new()
|
||||
protobuf_codegen::Codegen::new()
|
||||
.pure()
|
||||
.out_dir("src/protos")
|
||||
.inputs(&["protos/rendezvous.proto", "protos/message.proto"])
|
||||
.include("protos")
|
||||
|
||||
@@ -63,6 +63,7 @@ message LoginRequest {
|
||||
PortForward port_forward = 8;
|
||||
}
|
||||
bool video_ack_required = 9;
|
||||
uint64 session_id = 10;
|
||||
}
|
||||
|
||||
message ChatMessage { string text = 1; }
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
use crate::log;
|
||||
use crate::{
|
||||
log,
|
||||
password_security::config::{
|
||||
decrypt_str_or_original, decrypt_vec_or_original, encrypt_str_or_original,
|
||||
encrypt_vec_or_original,
|
||||
},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use directories_next::ProjectDirs;
|
||||
use rand::Rng;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
@@ -17,6 +24,7 @@ pub const CONNECT_TIMEOUT: u64 = 18_000;
|
||||
pub const REG_INTERVAL: i64 = 12_000;
|
||||
pub const COMPRESS_LEVEL: i32 = 3;
|
||||
const SERIAL: i32 = 3;
|
||||
const PASSWORD_ENC_VERSION: &'static str = "00";
|
||||
// 128x128
|
||||
#[cfg(target_os = "macos")] // 128x128 on 160x160 canvas, then shrink to 128, mac looks better with padding
|
||||
pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAyVBMVEUAAAAAcf8Acf8Acf8Acv8Acf8Acf8Acf8Acf8AcP8Acf8Ab/8AcP8Acf////8AaP/z+f/o8v/k7v/5/v/T5f8AYP/u9v/X6f+hx/+Kuv95pP8Aef/B1/+TwP9xoP8BdP/g6P+Irv9ZmP8Bgf/E3f98q/9sn/+01f+Es/9nm/9Jif8hhv8off/M4P+syP+avP86iP/c7f+xy/9yqf9Om/9hk/9Rjv+60P99tv9fpf88lv8yjf8Tgf8deP+kvP8BiP8NeP8hkP80gP8oj2VLAAAADXRSTlMA7o7qLvnaxZ1FOxYPjH9HWgAABHJJREFUeNrtm+tW4jAQgBfwuu7MtIUWsOUiCCioIIgLiqvr+z/UHq/LJKVkmwTcc/r9E2nzlU4mSTP9lpGRkZGR8VX5cZjfL+yCEXYL+/nDH//U/Pd8DgyTy39Xbv7oIAcWyB0cqbW/sweW2NtRaj8H1sgpGOwUIAH7Bkd7YJW9dXFwAJY5WNP/cmCZQnJvzIN18on5LwfWySXlxEPYAIcad8D6PdiHDbCfIFCADVBIENiFDbCbIACKPPXrZ+cP8E6/0znvP4EymgIEravIRcTxu8HxNSJ60a8W0AYECKrlAN+YwAthCd9wm1Ug6wKzIn5SgRduXfwkqDasCjx0XFzi9PV6zwNcIuhcWBOg+ikySq8C9UD4dEKWBCoOcspvAuLHTo9sCDQiFPHotRM48j8G5gVur1FdAN2uaYEuiz7xFsgEJ2RUoMUakXuBTHHoGxQYOBhHjeUBAefEnMAowFhaLBOKuOemBBbxLRQrH2PBCgMvNCPQGMeevTb9zLrPxz2Mo+QbEaijzPUcOOHMQZkKGRAIPem39+bypREMPTkQW/oCfk866zAkiIFG4yIKRE/aAnfiSd0WrORY6pFdXQEqi9mvAQm0RIOSnoCcZ8vJoz3diCnjRk+g8VP4/fuQDJ2Lxr6WwG0gXs9aTpDzW0vgDBlVUpixR8gYk44AD8FrUKHr8JQJGgIDnoDqoALxmWPQSi9AVVzm8gKUuEPGr/QCvptwJkbSYT/TC4S8C96DGjTj86aHtAI0x2WaBIq0eSYYpRa4EsdWVVwWu9O0Aj6f6dyBMnwEraeOgSYu0wZlauzA47QCbT7DgAQSE+hZWoEBF/BBmWOewNMK3BsSqKUW4MGcWqCSVmDkbvkXGKQOwg6PAUO9oL3xXhA20yaiCjuwYygRVQlUOTWTCf2SuNJTxeFjgaHByGuAIvd8ItdPLTDhS7IuqEE1YSKVOgbayLhSFQhMzYh8hwfBs1r7c505YVIQYEdNoKwxK06MJiyrpUFHiF0NAfCQUVHoiRclIXJIR6C2fqG37pBHvcWpgwzvAtYwkR5UGV2e42UISdBJETl3mg8ouo54Rcnti1/vaT+iuUQBt500Cgo4U10BeHSkk57FB0JjWkKRMWgLUA0lLodtImAQdaMiiri3+gIAPZQoutHNsgKF1aaDMhMyIdBf8Th+Bh8MTjGWCpl5Wv43tDmnF+IUVMrcZgRoiAxhtrloYizNkZaAnF5leglbNhj0wYCAbCDvGb0mP4nib7O7ZlcYQ2m1gPtIZgVgGNNMeaVAaWR+57TrqgtUnm3sHQ+kYeE6fufUubG1ez50FXbPnWgBlgSABmN3TTcsRl2yWkHRrwbiunvk/W2+Mg1hPZplPDeXRbZzStFH15s1QIVd3UImP5z/bHpeeQLvRJ7XLFUffQIlCvqlXETQbgN9/rlYABGosv+Vi9m2Xs639YLGrZd0br+odetlvdsvbN56abfd4vbCzv9Q3v/ygoOV21A4OPpfXvH4Ai+5ZGRkZGRkbJA/t/I0QMzoMiEAAAAASUVORK5CYII=
|
||||
@@ -114,7 +122,7 @@ pub struct Config2 {
|
||||
pub options: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct PeerConfig {
|
||||
#[serde(default)]
|
||||
pub password: Vec<u8>,
|
||||
@@ -168,7 +176,7 @@ pub struct PeerInfoSerde {
|
||||
pub platform: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct TransferSerde {
|
||||
#[serde(default)]
|
||||
pub write_jobs: Vec<String>,
|
||||
@@ -207,7 +215,16 @@ fn patch(path: PathBuf) -> PathBuf {
|
||||
|
||||
impl Config2 {
|
||||
fn load() -> Config2 {
|
||||
Config::load_::<Config2>("2")
|
||||
let mut config = Config::load_::<Config2>("2");
|
||||
if let Some(mut socks) = config.socks {
|
||||
let (password, store) = decrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION);
|
||||
socks.password = password;
|
||||
config.socks = Some(socks);
|
||||
if store {
|
||||
config.store();
|
||||
}
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
pub fn file() -> PathBuf {
|
||||
@@ -215,7 +232,12 @@ impl Config2 {
|
||||
}
|
||||
|
||||
fn store(&self) {
|
||||
Config::store_(self, "2");
|
||||
let mut config = self.clone();
|
||||
if let Some(mut socks) = config.socks {
|
||||
socks.password = encrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION);
|
||||
config.socks = Some(socks);
|
||||
}
|
||||
Config::store_(&config, "2");
|
||||
}
|
||||
|
||||
pub fn get() -> Config2 {
|
||||
@@ -267,11 +289,19 @@ impl Config {
|
||||
}
|
||||
|
||||
fn load() -> Config {
|
||||
Config::load_::<Config>("")
|
||||
let mut config = Config::load_::<Config>("");
|
||||
let (password, store) = decrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
config.password = password;
|
||||
if store {
|
||||
config.store();
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
fn store(&self) {
|
||||
Config::store_(self, "");
|
||||
let mut config = self.clone();
|
||||
config.password = encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
Config::store_(&config, "");
|
||||
}
|
||||
|
||||
pub fn file() -> PathBuf {
|
||||
@@ -627,7 +657,7 @@ impl Config {
|
||||
log::info!("id updated from {} to {}", id, new_id);
|
||||
}
|
||||
|
||||
pub fn set_password(password: &str) {
|
||||
pub fn set_security_password(password: &str) {
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if password == config.password {
|
||||
return;
|
||||
@@ -636,13 +666,8 @@ impl Config {
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_password() -> String {
|
||||
let mut password = CONFIG.read().unwrap().password.clone();
|
||||
if password.is_empty() {
|
||||
password = Config::get_auto_password();
|
||||
Config::set_password(&password);
|
||||
}
|
||||
password
|
||||
pub fn get_security_password() -> String {
|
||||
CONFIG.read().unwrap().password.clone()
|
||||
}
|
||||
|
||||
pub fn set_salt(salt: &str) {
|
||||
@@ -714,7 +739,28 @@ impl PeerConfig {
|
||||
pub fn load(id: &str) -> PeerConfig {
|
||||
let _ = CONFIG.read().unwrap(); // for lock
|
||||
match confy::load_path(&Self::path(id)) {
|
||||
Ok(config) => config,
|
||||
Ok(config) => {
|
||||
let mut config: PeerConfig = config;
|
||||
let mut store = false;
|
||||
let (password, store2) =
|
||||
decrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
config.password = password;
|
||||
store = store || store2;
|
||||
config.options.get_mut("rdp_password").map(|v| {
|
||||
let (password, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
||||
*v = password;
|
||||
store = store || store2;
|
||||
});
|
||||
config.options.get_mut("os-password").map(|v| {
|
||||
let (password, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
||||
*v = password;
|
||||
store = store || store2;
|
||||
});
|
||||
if store {
|
||||
config.store(id);
|
||||
}
|
||||
config
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to load config: {}", err);
|
||||
Default::default()
|
||||
@@ -724,7 +770,17 @@ impl PeerConfig {
|
||||
|
||||
pub fn store(&self, id: &str) {
|
||||
let _ = CONFIG.read().unwrap(); // for lock
|
||||
if let Err(err) = confy::store_path(Self::path(id), self) {
|
||||
let mut config = self.clone();
|
||||
config.password = encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||
config
|
||||
.options
|
||||
.get_mut("rdp_password")
|
||||
.map(|v| *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION));
|
||||
config
|
||||
.options
|
||||
.get_mut("os-password")
|
||||
.map(|v| *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION));
|
||||
if let Err(err) = confy::store_path(Self::path(id), config) {
|
||||
log::error!("Failed to store config: {}", err);
|
||||
}
|
||||
}
|
||||
@@ -856,10 +912,26 @@ impl LocalConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct DiscoveryPeer {
|
||||
pub id: String,
|
||||
#[serde(with = "serde_with::rust::map_as_tuple_list")]
|
||||
pub ip_mac: HashMap<String, String>,
|
||||
pub username: String,
|
||||
pub hostname: String,
|
||||
pub platform: String,
|
||||
pub online: bool,
|
||||
}
|
||||
|
||||
impl DiscoveryPeer {
|
||||
pub fn is_same_peer(&self, other: &DiscoveryPeer) -> bool {
|
||||
self.id == other.id && self.username == other.username
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct LanPeers {
|
||||
#[serde(default)]
|
||||
pub peers: String,
|
||||
pub peers: Vec<DiscoveryPeer>,
|
||||
}
|
||||
|
||||
impl LanPeers {
|
||||
@@ -874,8 +946,10 @@ impl LanPeers {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store(peers: String) {
|
||||
let f = LanPeers { peers };
|
||||
pub fn store(peers: &Vec<DiscoveryPeer>) {
|
||||
let f = LanPeers {
|
||||
peers: peers.clone(),
|
||||
};
|
||||
if let Err(err) = confy::store_path(Config::file_("_lan_peers"), f) {
|
||||
log::error!("Failed to store lan peers: {}", err);
|
||||
}
|
||||
|
||||
@@ -573,7 +573,7 @@ impl TransferJob {
|
||||
log::info!("file num truncated, ignoring");
|
||||
} else {
|
||||
match r.union {
|
||||
Some(file_transfer_send_confirm_request::Union::skip(s)) => {
|
||||
Some(file_transfer_send_confirm_request::Union::Skip(s)) => {
|
||||
if s {
|
||||
log::debug!("skip file id:{}, file_num:{}", r.id, r.file_num);
|
||||
self.skip_current_file();
|
||||
@@ -581,7 +581,7 @@ impl TransferJob {
|
||||
self.set_file_confirmed(true);
|
||||
}
|
||||
}
|
||||
Some(file_transfer_send_confirm_request::Union::offset_blk(_offset)) => {
|
||||
Some(file_transfer_send_confirm_request::Union::OffsetBlk(_offset)) => {
|
||||
self.set_file_confirmed(true);
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
pub mod compress;
|
||||
#[path = "./protos/message.rs"]
|
||||
pub mod message_proto;
|
||||
#[path = "./protos/rendezvous.rs"]
|
||||
pub mod rendezvous_proto;
|
||||
pub mod protos;
|
||||
pub mod platform;
|
||||
pub use protos::message as message_proto;
|
||||
pub use protos::rendezvous as rendezvous_proto;
|
||||
pub use bytes;
|
||||
use config::Config;
|
||||
pub use futures;
|
||||
pub use protobuf;
|
||||
use std::{
|
||||
@@ -27,6 +28,7 @@ pub use anyhow::{self, bail};
|
||||
pub use futures_util;
|
||||
pub mod config;
|
||||
pub mod fs;
|
||||
pub use lazy_static;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use mac_address;
|
||||
pub use rand;
|
||||
@@ -35,7 +37,7 @@ pub use sodiumoxide;
|
||||
pub use tokio_socks;
|
||||
pub use tokio_socks::IntoTargetAddr;
|
||||
pub use tokio_socks::TargetAddr;
|
||||
pub use lazy_static;
|
||||
pub mod password_security;
|
||||
|
||||
#[cfg(feature = "quic")]
|
||||
pub type Stream = quic::Connection;
|
||||
@@ -200,6 +202,14 @@ pub fn get_modified_time(path: &std::path::Path) -> SystemTime {
|
||||
.unwrap_or(UNIX_EPOCH)
|
||||
}
|
||||
|
||||
pub fn get_uuid() -> Vec<u8> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(id) = machine_uid::get() {
|
||||
return id.into();
|
||||
}
|
||||
Config::get_key_pair().1
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
330
libs/hbb_common/src/password_security.rs
Normal file
330
libs/hbb_common/src/password_security.rs
Normal file
@@ -0,0 +1,330 @@
|
||||
pub mod password {
|
||||
use crate::config::Config;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
str::FromStr,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref RANDOM_PASSWORD:Arc<RwLock<String>> = Arc::new(RwLock::new(Config::get_auto_password()));
|
||||
}
|
||||
|
||||
const SECURITY_ENABLED: &'static str = "security-password-enabled";
|
||||
const RANDOM_ENABLED: &'static str = "random-password-enabled";
|
||||
const ONETIME_ENABLED: &'static str = "onetime-password-enabled";
|
||||
const ONETIME_ACTIVATED: &'static str = "onetime-password-activated";
|
||||
const UPDATE_METHOD: &'static str = "random-password-update-method";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum UpdateMethod {
|
||||
KEEP,
|
||||
UPDATE,
|
||||
DISABLE,
|
||||
}
|
||||
|
||||
impl FromStr for UpdateMethod {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == "KEEP" {
|
||||
Ok(Self::KEEP)
|
||||
} else if s == "UPDATE" {
|
||||
Ok(Self::UPDATE)
|
||||
} else if s == "DISABLE" {
|
||||
Ok(Self::DISABLE)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UpdateMethod {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UpdateMethod::KEEP => write!(f, "KEEP"),
|
||||
UpdateMethod::UPDATE => write!(f, "UPDATE"),
|
||||
UpdateMethod::DISABLE => write!(f, "DISABLE"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_random_password(password: &str) {
|
||||
*RANDOM_PASSWORD.write().unwrap() = password.to_owned();
|
||||
}
|
||||
|
||||
pub fn random_password() -> String {
|
||||
let mut password = RANDOM_PASSWORD.read().unwrap().clone();
|
||||
if password.is_empty() {
|
||||
password = Config::get_auto_password();
|
||||
set_random_password(&password);
|
||||
}
|
||||
password
|
||||
}
|
||||
|
||||
pub fn random_password_valid() -> bool {
|
||||
if random_enabled() {
|
||||
onetime_password_activated() || !onetime_password_enabled()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn passwords() -> Vec<String> {
|
||||
let mut v = vec![];
|
||||
if random_password_valid() {
|
||||
v.push(random_password());
|
||||
}
|
||||
if security_enabled() {
|
||||
v.push(Config::get_security_password());
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn after_session(authorized: bool) {
|
||||
if authorized && random_enabled() {
|
||||
UpdateMethod::from_str(&update_method())
|
||||
.map(|method| match method {
|
||||
UpdateMethod::KEEP => {}
|
||||
UpdateMethod::UPDATE => set_random_password(&Config::get_auto_password()),
|
||||
UpdateMethod::DISABLE => set_random_enabled(false),
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_method() -> String {
|
||||
let mut method = Config::get_option(UPDATE_METHOD);
|
||||
if UpdateMethod::from_str(&method).is_err() {
|
||||
method = UpdateMethod::KEEP.to_string(); // default is keep
|
||||
set_update_method(&method);
|
||||
}
|
||||
method
|
||||
}
|
||||
|
||||
pub fn set_update_method(method: &str) {
|
||||
Config::set_option(UPDATE_METHOD.to_owned(), method.to_owned());
|
||||
}
|
||||
|
||||
pub fn random_enabled() -> bool {
|
||||
str2bool(RANDOM_ENABLED, true, || {
|
||||
set_onetime_password_activated(false);
|
||||
set_random_password(&Config::get_auto_password());
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_random_enabled(enabled: bool) {
|
||||
if enabled != random_enabled() {
|
||||
Config::set_option(RANDOM_ENABLED.to_owned(), bool2str(enabled));
|
||||
set_onetime_password_activated(false);
|
||||
if enabled {
|
||||
set_random_password(&Config::get_auto_password());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn security_enabled() -> bool {
|
||||
str2bool(SECURITY_ENABLED, true, || {})
|
||||
}
|
||||
|
||||
pub fn set_security_enabled(enabled: bool) {
|
||||
if enabled != security_enabled() {
|
||||
Config::set_option(SECURITY_ENABLED.to_owned(), bool2str(enabled));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onetime_password_enabled() -> bool {
|
||||
str2bool(ONETIME_ENABLED, false, || {
|
||||
set_onetime_password_activated(false);
|
||||
set_random_password(&Config::get_auto_password());
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_onetime_password_enabled(enabled: bool) {
|
||||
if enabled != onetime_password_enabled() {
|
||||
Config::set_option(ONETIME_ENABLED.to_owned(), bool2str(enabled));
|
||||
set_onetime_password_activated(false);
|
||||
set_random_password(&Config::get_auto_password());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onetime_password_activated() -> bool {
|
||||
str2bool(ONETIME_ACTIVATED, false, || {})
|
||||
}
|
||||
|
||||
pub fn set_onetime_password_activated(activated: bool) {
|
||||
if activated != onetime_password_activated() {
|
||||
Config::set_option(ONETIME_ACTIVATED.to_owned(), bool2str(activated));
|
||||
if activated {
|
||||
set_random_password(&Config::get_auto_password());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notice: Function nesting
|
||||
fn str2bool(key: &str, default: bool, default_set: impl Fn()) -> bool {
|
||||
let option = Config::get_option(key);
|
||||
if option == "Y" {
|
||||
true
|
||||
} else if option == "N" {
|
||||
false
|
||||
} else {
|
||||
Config::set_option(key.to_owned(), bool2str(default));
|
||||
default_set();
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
fn bool2str(option: bool) -> String {
|
||||
if option { "Y" } else { "N" }.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod config {
|
||||
use super::base64::decrypt as decrypt00;
|
||||
use super::base64::encrypt as encrypt00;
|
||||
|
||||
const VERSION_LEN: usize = 2;
|
||||
|
||||
pub fn encrypt_str_or_original(s: &str, version: &str) -> String {
|
||||
if version.len() == VERSION_LEN {
|
||||
if version == "00" {
|
||||
if let Ok(s) = encrypt00(s.as_bytes()) {
|
||||
return version.to_owned() + &s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.to_owned()
|
||||
}
|
||||
|
||||
// bool: whether should store to re-encrypt when load
|
||||
pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool) {
|
||||
if s.len() > VERSION_LEN {
|
||||
let version = &s[..VERSION_LEN];
|
||||
if version == "00" {
|
||||
if let Ok(v) = decrypt00(&s[VERSION_LEN..].as_bytes()) {
|
||||
return (
|
||||
String::from_utf8_lossy(&v).to_string(),
|
||||
version != current_version,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(s.to_owned(), !s.is_empty())
|
||||
}
|
||||
|
||||
pub fn encrypt_vec_or_original(v: &[u8], version: &str) -> Vec<u8> {
|
||||
if version.len() == VERSION_LEN {
|
||||
if version == "00" {
|
||||
if let Ok(s) = encrypt00(v) {
|
||||
let mut version = version.to_owned().into_bytes();
|
||||
version.append(&mut s.into_bytes());
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.to_owned()
|
||||
}
|
||||
|
||||
// bool: whether should store to re-encrypt when load
|
||||
pub fn decrypt_vec_or_original(v: &[u8], current_version: &str) -> (Vec<u8>, bool) {
|
||||
if v.len() > VERSION_LEN {
|
||||
let version = String::from_utf8_lossy(&v[..VERSION_LEN]);
|
||||
if version == "00" {
|
||||
if let Ok(v) = decrypt00(&v[VERSION_LEN..]) {
|
||||
return (v, version != current_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(v.to_owned(), !v.is_empty())
|
||||
}
|
||||
|
||||
mod test {
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::password_security::config::*;
|
||||
|
||||
println!("test str");
|
||||
let data = "Hello World";
|
||||
let encrypted = encrypt_str_or_original(data, "00");
|
||||
let (decrypted, store) = decrypt_str_or_original(&encrypted, "00");
|
||||
println!("data: {}", data);
|
||||
println!("encrypted: {}", encrypted);
|
||||
println!("decrypted: {}", decrypted);
|
||||
assert_eq!(data, decrypted);
|
||||
assert_eq!("00", &encrypted[..2]);
|
||||
assert_eq!(store, false);
|
||||
let (_, store2) = decrypt_str_or_original(&encrypted, "01");
|
||||
assert_eq!(store2, true);
|
||||
|
||||
println!("test vec");
|
||||
let data: Vec<u8> = vec![1, 2, 3, 4];
|
||||
let encrypted = encrypt_vec_or_original(&data, "00");
|
||||
let (decrypted, store) = decrypt_vec_or_original(&encrypted, "00");
|
||||
println!("data: {:?}", data);
|
||||
println!("encrypted: {:?}", encrypted);
|
||||
println!("decrypted: {:?}", decrypted);
|
||||
assert_eq!(data, decrypted);
|
||||
assert_eq!("00".as_bytes(), &encrypted[..2]);
|
||||
assert_eq!(store, false);
|
||||
let (_, store2) = decrypt_vec_or_original(&encrypted, "01");
|
||||
assert_eq!(store2, true);
|
||||
|
||||
println!("test old");
|
||||
let data = "00Hello World";
|
||||
let (decrypted, store) = decrypt_str_or_original(&data, "00");
|
||||
assert_eq!(data, decrypted);
|
||||
assert_eq!(store, true);
|
||||
let data: Vec<u8> = vec!['0' as u8, '0' as u8, 1, 2, 3, 4];
|
||||
let (decrypted, store) = decrypt_vec_or_original(&data, "00");
|
||||
assert_eq!(data, decrypted);
|
||||
assert_eq!(store, true);
|
||||
let (_, store) = decrypt_str_or_original("", "00");
|
||||
assert_eq!(store, false);
|
||||
let (_, store) = decrypt_vec_or_original(&vec![], "00");
|
||||
assert_eq!(store, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod base64 {
|
||||
use super::symmetric_crypt;
|
||||
use sodiumoxide::base64;
|
||||
|
||||
pub fn encrypt(v: &[u8]) -> Result<String, ()> {
|
||||
if v.len() > 0 {
|
||||
symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt(v: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
if v.len() > 0 {
|
||||
base64::decode(v, base64::Variant::Original).and_then(|v| symmetric_crypt(&v, false))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result<Vec<u8>, ()> {
|
||||
use sodiumoxide::crypto::secretbox;
|
||||
use std::convert::TryInto;
|
||||
|
||||
let mut keybuf = crate::get_uuid();
|
||||
keybuf.resize(secretbox::KEYBYTES, 0);
|
||||
let key = secretbox::Key(keybuf.try_into().map_err(|_| ())?);
|
||||
let nonce = secretbox::Nonce([0; secretbox::NONCEBYTES]);
|
||||
|
||||
if encrypt {
|
||||
Ok(secretbox::seal(data, &nonce, &key))
|
||||
} else {
|
||||
secretbox::open(data, &nonce, &key)
|
||||
}
|
||||
}
|
||||
102
libs/hbb_common/src/platform/linux.rs
Normal file
102
libs/hbb_common/src/platform/linux.rs
Normal file
@@ -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<String> {
|
||||
let output = std::process::Command::new("sh")
|
||||
.args(vec!["-c", &cmds])
|
||||
.output()?;
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
2
libs/hbb_common/src/platform/mod.rs
Normal file
2
libs/hbb_common/src/platform/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
@@ -27,6 +27,8 @@ fn new_socket(addr: SocketAddr, reuse: bool, buf_size: usize) -> Result<Socket,
|
||||
socket.set_reuse_port(true)?;
|
||||
socket.set_reuse_address(true)?;
|
||||
}
|
||||
// only nonblocking work with tokio, https://stackoverflow.com/questions/64649405/receiver-on-tokiompscchannel-only-receives-messages-when-buffer-is-full
|
||||
socket.set_nonblocking(true)?;
|
||||
if buf_size > 0 {
|
||||
socket.set_recv_buffer_size(buf_size).ok();
|
||||
}
|
||||
|
||||
@@ -251,11 +251,11 @@ impl Decoder {
|
||||
rgb: &mut Vec<u8>,
|
||||
) -> ResultType<bool> {
|
||||
match frame {
|
||||
video_frame::Union::vp9s(vp9s) => {
|
||||
video_frame::Union::Vp9s(vp9s) => {
|
||||
Decoder::handle_vp9s_video_frame(&mut self.vpx, vp9s, rgb)
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
video_frame::Union::h264s(h264s) => {
|
||||
video_frame::Union::H264s(h264s) => {
|
||||
if let Some(decoder) = &mut self.hw.h264 {
|
||||
Decoder::handle_hw_video_frame(decoder, h264s, rgb, &mut self.i420)
|
||||
} else {
|
||||
@@ -263,7 +263,7 @@ impl Decoder {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
video_frame::Union::h265s(h265s) => {
|
||||
video_frame::Union::H265s(h265s) => {
|
||||
if let Some(decoder) = &mut self.hw.h265 {
|
||||
Decoder::handle_hw_video_frame(decoder, h265s, rgb, &mut self.i420)
|
||||
} else {
|
||||
|
||||
@@ -21,6 +21,10 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.inner.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
pub fn is_gdi(&self) -> bool {
|
||||
self.inner.is_gdi()
|
||||
}
|
||||
@@ -41,8 +45,8 @@ impl Capturer {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.inner.frame(timeout_ms.as_millis() as _) {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.inner.frame(timeout.as_millis() as _) {
|
||||
Ok(frame) => Ok(Frame(frame)),
|
||||
Err(ref error) if error.kind() == TimedOut => Err(WouldBlock.into()),
|
||||
Err(error) => Err(error),
|
||||
@@ -129,6 +133,11 @@ impl CapturerMag {
|
||||
data: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.inner.set_use_yuv(use_yuv)
|
||||
}
|
||||
|
||||
pub fn exclude(&mut self, cls: &str, name: &str) -> io::Result<bool> {
|
||||
self.inner.exclude(cls, name)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
@@ -17,6 +17,13 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
match self {
|
||||
Capturer::X11(d) => d.set_use_yuv(use_yuv),
|
||||
Capturer::WAYLAND(d) => d.set_use_yuv(use_yuv),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
match self {
|
||||
Capturer::X11(d) => d.width(),
|
||||
@@ -31,10 +38,10 @@ impl Capturer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self {
|
||||
Capturer::X11(d) => d.frame(timeout_ms),
|
||||
Capturer::WAYLAND(d) => d.frame(timeout_ms),
|
||||
Capturer::X11(d) => d.frame(timeout),
|
||||
Capturer::WAYLAND(d) => d.frame(timeout),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,31 +52,30 @@ pub enum Display {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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<Display> {
|
||||
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<Vec<Display>> {
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ cfg_if! {
|
||||
mod wayland;
|
||||
mod x11;
|
||||
pub use self::linux::*;
|
||||
pub use self::x11::Frame;
|
||||
} else {
|
||||
mod x11;
|
||||
pub use self::x11::*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::common::x11::Frame;
|
||||
use crate::wayland::{capturable::*, *};
|
||||
use std::io;
|
||||
use std::{io, time::Duration};
|
||||
|
||||
pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>);
|
||||
|
||||
@@ -14,6 +14,10 @@ impl Capturer {
|
||||
Ok(Capturer(display, r, yuv, Default::default()))
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.2 = use_yuv;
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.width()
|
||||
}
|
||||
@@ -22,8 +26,8 @@ impl Capturer {
|
||||
self.0.height()
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
match self.1.capture(timeout_ms as _).map_err(map_err)? {
|
||||
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
||||
match self.1.capture(timeout.as_millis() as _).map_err(map_err)? {
|
||||
PixelProvider::BGR0(w, h, x) => Ok(Frame(if self.2 {
|
||||
crate::common::bgra_to_i420(w as _, h as _, &x, &mut self.3);
|
||||
&self.3[..]
|
||||
|
||||
@@ -8,6 +8,10 @@ impl Capturer {
|
||||
x11::Capturer::new(display.0, yuv).map(Capturer)
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.0.set_use_yuv(use_yuv);
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.display().rect().w as usize
|
||||
}
|
||||
|
||||
@@ -446,6 +446,10 @@ impl CapturerMag {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub(crate) fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
|
||||
let name_c = CString::new(name).unwrap();
|
||||
unsafe {
|
||||
|
||||
@@ -156,6 +156,10 @@ impl Capturer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub fn is_gdi(&self) -> bool {
|
||||
self.gdi_capturer.is_some()
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ impl Capturer {
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
pub fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
self.use_yuv = use_yuv;
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &Display {
|
||||
&self.display
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user