From 904f75bbba13315babbc389518c22c8c5f6914ac Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 21 Mar 2023 21:30:10 +0800 Subject: [PATCH] prototype --- vdi/host/.cargo/config.toml | 2 + vdi/host/.devcontainer/Dockerfile | 16 -- vdi/host/.devcontainer/devcontainer.json | 28 -- vdi/host/Cargo.lock | 155 +++++++++-- vdi/host/Cargo.toml | 7 +- vdi/host/README.md | 4 + vdi/host/src/lib.rs | 1 + vdi/host/src/main.rs | 4 + vdi/host/src/server.rs | 335 +++++++++++++++++++++++ 9 files changed, 480 insertions(+), 72 deletions(-) create mode 100644 vdi/host/.cargo/config.toml delete mode 100644 vdi/host/.devcontainer/Dockerfile delete mode 100644 vdi/host/.devcontainer/devcontainer.json create mode 100644 vdi/host/src/lib.rs create mode 100644 vdi/host/src/server.rs diff --git a/vdi/host/.cargo/config.toml b/vdi/host/.cargo/config.toml new file mode 100644 index 000000000..70f9eaeb2 --- /dev/null +++ b/vdi/host/.cargo/config.toml @@ -0,0 +1,2 @@ +[registries.crates-io] +protocol = "sparse" diff --git a/vdi/host/.devcontainer/Dockerfile b/vdi/host/.devcontainer/Dockerfile deleted file mode 100644 index f02042771..000000000 --- a/vdi/host/.devcontainer/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM rockylinux:9.1 -ENV HOME=/home/vscode -ENV WORKDIR=$HOME/rustdesk/vdi/host - -# https://ciq.co/blog/top-10-things-to-do-after-rocky-linux-9-install/ also gpu driver install -WORKDIR $HOME -RUN dnf -y install epel-release -RUN dnf config-manager --set-enabled crb -RUN dnf -y install cargo libvpx-devel opus-devel usbredir-devel git cmake gcc-c++ pkg-config nasm yasm ninja-build automake libtool libva-devel libvdpau-devel llvm-devel -WORKDIR / - -RUN git clone https://chromium.googlesource.com/libyuv/libyuv -WORKDIR /libyuv -RUN cmake . -DCMAKE_INSTALL_PREFIX=/user -RUN make -j4 && make install -WORKDIR / \ No newline at end of file diff --git a/vdi/host/.devcontainer/devcontainer.json b/vdi/host/.devcontainer/devcontainer.json deleted file mode 100644 index f0016b5b1..000000000 --- a/vdi/host/.devcontainer/devcontainer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "rustdesk", - "build": { - "dockerfile": "./Dockerfile", - "context": "." - }, - "workspaceMount": "source=${localWorkspaceFolder}/../..,target=/home/vscode/rustdesk,type=bind,consistency=cache", - "workspaceFolder": "/home/vscode/rustdesk/vdi/host", - "customizations": { - "vscode": { - "extensions": [ - "vadimcn.vscode-lldb", - "mutantdino.resourcemonitor", - "rust-lang.rust-analyzer", - "tamasfe.even-better-toml", - "serayuzgur.crates", - "mhutchie.git-graph", - "formulahendry.terminal", - "eamodio.gitlens" - ], - "settings": { - "files.watcherExclude": { - "**/target/**": true - } - } - } - } -} \ No newline at end of file diff --git a/vdi/host/Cargo.lock b/vdi/host/Cargo.lock index 7b7cf26bd..52783ceef 100644 --- a/vdi/host/Cargo.lock +++ b/vdi/host/Cargo.lock @@ -157,17 +157,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -316,6 +305,16 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.14" @@ -475,17 +474,38 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -513,6 +533,24 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "flexi_logger" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eae57842a8221ef13f1f207632d786a175dd13bd8fbdc8be9d852f7c9cf1046" +dependencies = [ + "chrono", + "crossbeam-channel", + "crossbeam-queue", + "glob", + "is-terminal", + "lazy_static", + "log", + "nu-ansi-term", + "regex", + "thiserror", +] + [[package]] name = "futures" version = "0.3.26" @@ -634,6 +672,12 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hashbrown" version = "0.12.3" @@ -656,6 +700,7 @@ dependencies = [ "dirs-next", "env_logger", "filetime", + "flexi_logger", "futures", "futures-util", "lazy_static", @@ -680,15 +725,6 @@ dependencies = [ "zstd", ] -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.2.6" @@ -698,6 +734,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -753,6 +795,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + [[package]] name = "itoa" version = "1.0.5" @@ -822,6 +887,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -941,13 +1012,23 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1013,6 +1094,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking" version = "2.0.0" @@ -1328,6 +1415,20 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustix" +version = "0.36.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + [[package]] name = "ryu" version = "1.0.12" @@ -1515,9 +1616,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.24.7" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb4ebf3d49308b99e6e9dc95e989e2fdbdc210e4f67c39db0bb89ba927001c" +checksum = "f69e0d827cce279e61c2f3399eb789271a8f136d8245edef70f06e3c9601a670" dependencies = [ "cfg-if", "core-foundation-sys", diff --git a/vdi/host/Cargo.toml b/vdi/host/Cargo.toml index 6a67813a2..a1fd1e68e 100644 --- a/vdi/host/Cargo.toml +++ b/vdi/host/Cargo.toml @@ -5,5 +5,10 @@ authors = ["rustdesk "] edition = "2021" [dependencies] -qemu-display = { git = "https://gitlab.com/marcandre.lureau/qemu-display" } +qemu-display = { git = "https://github.com/rustdesk/qemu-display" } hbb_common = { path = "../../libs/hbb_common" } +clap = { version = "4.1", features = ["derive"] } +zbus = { version = "3.0" } +derivative = "2.2" +image = "0.24" +async-trait = "0.1" diff --git a/vdi/host/README.md b/vdi/host/README.md index 3b29a10e3..0283266bf 100644 --- a/vdi/host/README.md +++ b/vdi/host/README.md @@ -1 +1,5 @@ # RustDesk protocol on QEMU D-Bus display + +``` +sudo apt install libusbredirparser-dev libusbredirhost-dev libusb-1.0-0-dev +``` diff --git a/vdi/host/src/lib.rs b/vdi/host/src/lib.rs new file mode 100644 index 000000000..74f47ad34 --- /dev/null +++ b/vdi/host/src/lib.rs @@ -0,0 +1 @@ +pub mod server; diff --git a/vdi/host/src/main.rs b/vdi/host/src/main.rs index f79c691f0..ea32a028a 100644 --- a/vdi/host/src/main.rs +++ b/vdi/host/src/main.rs @@ -1,2 +1,6 @@ fn main() { + hbb_common::init_log(false, ""); + if let Err(err) = qemu_rustdesk::server::run() { + hbb_common::log::error!("{err}"); + } } diff --git a/vdi/host/src/server.rs b/vdi/host/src/server.rs new file mode 100644 index 000000000..0171741df --- /dev/null +++ b/vdi/host/src/server.rs @@ -0,0 +1,335 @@ +use clap::Parser; +use hbb_common::{ + anyhow::{anyhow, Context}, + log, + message_proto::*, + tokio, ResultType, +}; +use image::GenericImage; +use qemu_display::{Console, ConsoleListenerHandler, MouseButton, VMProxy}; +use std::{ + borrow::Borrow, + collections::HashSet, + error::Error, + io, + iter::FromIterator, + net::{TcpListener, TcpStream}, + sync::{mpsc, Arc, Mutex}, + thread, time, +}; + +#[derive(Parser, Debug)] +pub struct SocketAddrArgs { + /// IP address + #[clap(short, long, default_value = "0.0.0.0")] + address: std::net::IpAddr, + /// IP port number + #[clap(short, long, default_value = "21116")] + port: u16, +} + +impl From for std::net::SocketAddr { + fn from(args: SocketAddrArgs) -> Self { + (args.address, args.port).into() + } +} + +#[derive(Parser, Debug)] +struct Cli { + #[clap(flatten)] + address: SocketAddrArgs, + #[clap(short, long)] + dbus_address: Option, +} + +#[derive(Debug)] +enum Event { + ConsoleUpdate((i32, i32, i32, i32)), + Disconnected, +} + +const PIXMAN_X8R8G8B8: u32 = 0x20020888; +type BgraImage = image::ImageBuffer, Vec>; + +#[derive(derivative::Derivative)] +#[derivative(Debug)] +struct Client { + #[derivative(Debug = "ignore")] + server: Server, + share: bool, + last_update: Option, + has_update: bool, + req_update: bool, + last_buttons: HashSet, + dimensions: (u16, u16), +} + +impl Client { + fn new(server: Server, share: bool) -> Self { + Self { + server, + share, + last_update: None, + has_update: false, + req_update: false, + last_buttons: HashSet::new(), + dimensions: (0, 0), + } + } + + fn update_pending(&self) -> bool { + self.has_update && self.req_update + } + + async fn key_event(&self, qnum: u32, down: bool) -> ResultType<()> { + let inner = self.server.inner.lock().unwrap(); + if down { + inner.console.keyboard.press(qnum).await?; + } else { + inner.console.keyboard.release(qnum).await?; + } + Ok(()) + } + + fn desktop_resize(&mut self) -> ResultType<()> { + let (width, height) = self.server.dimensions(); + if (width, height) == self.dimensions { + return Ok(()); + } + self.dimensions = (width, height); + Ok(()) + } + + fn send_framebuffer_update(&mut self) -> ResultType<()> { + self.desktop_resize()?; + if self.has_update && self.req_update { + if let Some(last_update) = self.last_update { + if last_update.elapsed().as_millis() < 10 { + println!("TODO: <10ms, could delay update..") + } + } + // self.server.send_framebuffer_update(&self.vnc_server)?; + self.last_update = Some(time::Instant::now()); + self.has_update = false; + self.req_update = false; + } + Ok(()) + } + + async fn handle_event(&mut self, event: Option) -> ResultType { + match event { + Some(Event::ConsoleUpdate(_)) => { + self.has_update = true; + } + Some(Event::Disconnected) => { + return Ok(false); + } + None => { + self.send_framebuffer_update()?; + } + } + + Ok(true) + } +} + +#[derive(Debug)] +struct ConsoleListener { + server: Server, +} + +#[async_trait::async_trait] +impl ConsoleListenerHandler for ConsoleListener { + async fn scanout(&mut self, s: qemu_display::Scanout) { + let mut inner = self.server.inner.lock().unwrap(); + inner.image = image_from_vec(s.format, s.width, s.height, s.stride, s.data); + } + + async fn update(&mut self, u: qemu_display::Update) { + let mut inner = self.server.inner.lock().unwrap(); + let update = image_from_vec(u.format, u.w as _, u.h as _, u.stride, u.data); + if (u.x, u.y) == (0, 0) && update.dimensions() == inner.image.dimensions() { + inner.image = update; + } else { + inner.image.copy_from(&update, u.x as _, u.y as _).unwrap(); + } + inner + .tx + .send(Event::ConsoleUpdate((u.x, u.y, u.w, u.h))) + .unwrap(); + } + + async fn scanout_dmabuf(&mut self, _scanout: qemu_display::ScanoutDMABUF) { + unimplemented!() + } + + async fn update_dmabuf(&mut self, _update: qemu_display::UpdateDMABUF) { + unimplemented!() + } + + async fn mouse_set(&mut self, set: qemu_display::MouseSet) { + dbg!(set); + } + + async fn cursor_define(&mut self, cursor: qemu_display::Cursor) { + dbg!(cursor); + } + + fn disconnected(&mut self) { + dbg!(); + } +} + +#[derive(Debug)] +struct ServerInner { + console: Console, + image: BgraImage, + tx: mpsc::Sender, +} + +#[derive(Clone, Debug)] +struct Server { + vm_name: String, + rx: Arc>>, + inner: Arc>, +} + +impl Server { + async fn new(vm_name: String, console: Console) -> ResultType { + let width = console.width().await?; + let height = console.height().await?; + let image = BgraImage::new(width as _, height as _); + let (tx, rx) = mpsc::channel(); + Ok(Self { + vm_name, + rx: Arc::new(Mutex::new(rx)), + inner: Arc::new(Mutex::new(ServerInner { console, image, tx })), + }) + } + + fn stop_console(&self) -> ResultType<()> { + let mut inner = self.inner.lock().unwrap(); + inner.console.unregister_listener(); + Ok(()) + } + + async fn run_console(&self) -> ResultType<()> { + let inner = self.inner.lock().unwrap(); + inner + .console + .register_listener(ConsoleListener { + server: self.clone(), + }) + .await?; + Ok(()) + } + + fn dimensions(&self) -> (u16, u16) { + let inner = self.inner.lock().unwrap(); + (inner.image.width() as u16, inner.image.height() as u16) + } + + async fn handle_connection(&self, stream: TcpStream) -> ResultType<()> { + let (width, height) = self.dimensions(); + + let tx = self.inner.lock().unwrap().tx.clone(); + let _client_thread = thread::spawn(move || loop {}); + + self.run_console().await?; + let rx = self.rx.lock().unwrap(); + self.stop_console()?; + Ok(()) + } +} + +fn button_mask_to_set(mask: u8) -> HashSet { + let mut set = HashSet::new(); + if mask & 0b0000_0001 != 0 { + set.insert(MouseButton::Left); + } + if mask & 0b0000_0010 != 0 { + set.insert(MouseButton::Middle); + } + if mask & 0b0000_0100 != 0 { + set.insert(MouseButton::Right); + } + if mask & 0b0000_1000 != 0 { + set.insert(MouseButton::WheelUp); + } + if mask & 0b0001_0000 != 0 { + set.insert(MouseButton::WheelDown); + } + set +} + +fn image_from_vec(format: u32, width: u32, height: u32, stride: u32, data: Vec) -> BgraImage { + if format != PIXMAN_X8R8G8B8 { + todo!("unhandled pixman format: {}", format) + } + if cfg!(target_endian = "big") { + todo!("pixman/image in big endian") + } + let layout = image::flat::SampleLayout { + channels: 4, + channel_stride: 1, + width, + width_stride: 4, + height, + height_stride: stride as _, + }; + let samples = image::flat::FlatSamples { + samples: data, + layout, + color_hint: None, + }; + samples + .try_into_buffer::>() + .or_else::<&str, _>(|(_err, samples)| { + let view = samples.as_view::>().unwrap(); + let mut img = BgraImage::new(width, height); + img.copy_from(&view, 0, 0).unwrap(); + Ok(img) + }) + .unwrap() +} + +#[tokio::main] +pub async fn run() -> ResultType<()> { + let args = Cli::parse(); + + let listener = TcpListener::bind::(args.address.into()).unwrap(); + let dbus = if let Some(addr) = args.dbus_address { + zbus::ConnectionBuilder::address(addr.borrow())? + .build() + .await + } else { + zbus::Connection::session().await + } + .context("Failed to connect to DBus")?; + + let vm_name = VMProxy::new(&dbus).await?.name().await?; + + let console = Console::new(&dbus.into(), 0) + .await + .context("Failed to get the console")?; + let server = Server::new(format!("qemu-rustdesk ({})", vm_name), console).await?; + for stream in listener.incoming() { + match stream { + Ok(stream) => { + tokio::spawn(async { + /* + if let Err(err) = server.handle_connection(stream).await { + log::error!("Connection closed: {err}"); + } + */ + }); + } + Err(err) => { + log::error!("Failed to accept connection: {}", err); + continue; + } + } + } + + Ok(()) +}