From 6a4dd740c357bba05ff2090969ac8c8397f189fb Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Wed, 10 Jul 2024 00:33:49 +0200 Subject: [PATCH] code cleanup + purge anyhow in library code (#157) --- Cargo.lock | 143 ++++--- input-capture/src/error.rs | 146 +++---- input-capture/src/lib.rs | 2 +- input-capture/src/libei.rs | 290 ++++++------- input-capture/src/wayland.rs | 13 +- input-capture/src/windows.rs | 8 +- input-emulation/Cargo.toml | 1 - input-emulation/src/error.rs | 136 ++---- input-emulation/src/lib.rs | 2 - input-emulation/src/libei.rs | 25 +- input-emulation/src/macos.rs | 20 +- input-emulation/src/windows.rs | 8 +- input-emulation/src/wlroots.rs | 6 +- input-emulation/src/x11.rs | 8 +- input-emulation/src/xdg_desktop_portal.rs | 9 +- input-event/Cargo.toml | 2 +- input-event/src/error.rs | 17 + input-event/src/lib.rs | 478 +--------------------- input-event/src/proto.rs | 307 ++++++++++++++ src/emulation_test.rs | 8 +- src/server.rs | 2 +- src/server/emulation_task.rs | 238 +++++++---- src/server/network_task.rs | 121 ++++-- 23 files changed, 926 insertions(+), 1064 deletions(-) create mode 100644 input-event/src/error.rs create mode 100644 input-event/src/proto.rs diff --git a/Cargo.lock b/Cargo.lock index f3d3ddb..2254718 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,9 +224,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", @@ -326,9 +326,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.104" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" [[package]] name = "cfg-expr" @@ -354,9 +354,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -364,9 +364,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", @@ -1218,7 +1218,6 @@ dependencies = [ name = "input-emulation" version = "0.1.0" dependencies = [ - "anyhow", "ashpd", "async-trait", "core-graphics", @@ -1242,11 +1241,11 @@ dependencies = [ name = "input-event" version = "0.1.0" dependencies = [ - "anyhow", "futures-core", "log", "num_enum", "serde", + "thiserror", ] [[package]] @@ -1376,7 +1375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1592,7 +1591,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1677,9 +1676,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" dependencies = [ "memchr", ] @@ -1836,18 +1835,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -1944,9 +1943,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.68" +version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", @@ -1968,9 +1967,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" [[package]] name = "tempfile" @@ -2006,9 +2005,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2058,7 +2057,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.15", ] [[package]] @@ -2083,9 +2082,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" dependencies = [ "indexmap", "serde", @@ -2207,9 +2206,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wayland-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34e9e6b6d4a2bb4e7e69433e0b35c7923b95d4dc8503a84d25ec917a4bbfdf07" +checksum = "269c04f203640d0da2092d1b8d89a2d081714ae3ac2f1b53e99f205740517198" dependencies = [ "cc", "downcast-rs", @@ -2221,9 +2220,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.3" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e63801c85358a431f986cffa74ba9599ff571fc5774ac113ed3b490c19a1133" +checksum = "08bd0f46c069d3382a36c8666c1b9ccef32b8b04f41667ca1fef06a1adcc2982" dependencies = [ "bitflags 2.6.0", "rustix", @@ -2233,9 +2232,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d0f1056570486e26a3773ec633885124d79ae03827de05ba6c85f79904026c" +checksum = "1794d82d869f38439d15c24b26f06f6c8603d27d47b4f786d5197c99044de415" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -2245,9 +2244,9 @@ dependencies = [ [[package]] name = "wayland-protocols-misc" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e76311e1866c955afbbc46ae97e57542acda1dc9b0298358263a8550b5247331" +checksum = "28b497d7edd777ca365f2cce3837046b7832365b2a0b32f9c913b4e6f8d99839" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -2258,9 +2257,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dab47671043d9f5397035975fe1cac639e5bca5cc0b3c32d09f01612e34d24" +checksum = "fa43c961473aed713d44c1f616f775186249dfca657f256d8841ca0690366aba" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -2271,9 +2270,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67da50b9f80159dec0ea4c11c13e24ef9e7574bd6ce24b01860a175010cea565" +checksum = "edf466fc49a4feb65a511ca403fec3601494d0dee85dbf37fff6fa0dd4eec3b6" dependencies = [ "proc-macro2", "quick-xml", @@ -2282,9 +2281,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "105b1842da6554f91526c14a2a2172897b7f745a805d62af4ce698706be79c12" +checksum = "4a6754825230fa5b27bafaa28c30b3c9e72c55530581220cef401fa422c0fae7" dependencies = [ "dlib", "log", @@ -2326,7 +2325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core 0.52.0", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2336,7 +2335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ "windows-core 0.57.0", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2345,7 +2344,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2357,7 +2356,7 @@ dependencies = [ "windows-implement", "windows-interface", "windows-result", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2388,7 +2387,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2406,7 +2405,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2426,18 +2425,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2448,9 +2447,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2460,9 +2459,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2472,15 +2471,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2490,9 +2489,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2502,9 +2501,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2514,9 +2513,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2526,9 +2525,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" diff --git a/input-capture/src/error.rs b/input-capture/src/error.rs index 778aacb..35b7839 100644 --- a/input-capture/src/error.rs +++ b/input-capture/src/error.rs @@ -1,4 +1,3 @@ -use std::fmt::Display; use thiserror::Error; #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] @@ -10,65 +9,78 @@ use wayland_client::{ ConnectError, DispatchError, }; +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +use reis::tokio::{EiConvertEventStreamError, HandshakeError}; + +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[derive(Debug, Error)] -pub enum CaptureCreationError { - NoAvailableBackend, - #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] - Libei(#[from] LibeiCaptureCreationError), - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] - LayerShell(#[from] LayerShellCaptureCreationError), - #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] - X11(#[from] X11InputCaptureCreationError), - #[cfg(target_os = "macos")] - Macos(#[from] MacOSInputCaptureCreationError), - #[cfg(windows)] - Windows, +#[error("error in libei stream: {inner:?}")] +pub struct ReisConvertEventStreamError { + inner: EiConvertEventStreamError, } -impl Display for CaptureCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let reason = match self { - #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] - CaptureCreationError::Libei(reason) => { - format!("error creating portal backend: {reason}") - } - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] - CaptureCreationError::LayerShell(reason) => { - format!("error creating layer-shell backend: {reason}") - } - #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] - CaptureCreationError::X11(e) => format!("{e}"), - #[cfg(target_os = "macos")] - CaptureCreationError::Macos(e) => format!("{e}"), - #[cfg(windows)] - CaptureCreationError::Windows => String::new(), - CaptureCreationError::NoAvailableBackend => "no available backend".to_string(), - }; - write!(f, "could not create input capture: {reason}") +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +impl From for ReisConvertEventStreamError { + fn from(e: EiConvertEventStreamError) -> Self { + Self { inner: e } } } +#[derive(Debug, Error)] +pub enum CaptureError { + #[error("activation stream closed unexpectedly")] + ActivationClosed, + #[error("libei stream was closed")] + EndOfStream, + #[error("io error: `{0}`")] + Io(#[from] std::io::Error), + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + #[error("error in libei stream: `{0}`")] + Reis(#[from] ReisConvertEventStreamError), + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + #[error("libei handshake failed: `{0}`")] + Handshake(#[from] HandshakeError), + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + #[error(transparent)] + Portal(#[from] ashpd::Error), +} + +#[derive(Debug, Error)] +pub enum CaptureCreationError { + #[error("no backend available")] + NoAvailableBackend, + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + #[error("error creating input-capture-portal backend: `{0}`")] + Libei(#[from] LibeiCaptureCreationError), + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + #[error("error creating layer-shell capture backend: `{0}`")] + LayerShell(#[from] LayerShellCaptureCreationError), + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + #[error("error creating x11 capture backend: `{0}`")] + X11(#[from] X11InputCaptureCreationError), + #[cfg(target_os = "macos")] + #[error("error creating macos capture backend: `{0}`")] + Macos(#[from] MacOSInputCaptureCreationError), + #[cfg(windows)] + #[error("error creating windows capture backend")] + Windows, +} + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[derive(Debug, Error)] pub enum LibeiCaptureCreationError { + #[error("xdg-desktop-portal: `{0}`")] Ashpd(#[from] ashpd::Error), } -#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] -impl Display for LibeiCaptureCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LibeiCaptureCreationError::Ashpd(portal_error) => write!(f, "{portal_error}"), - } - } -} - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[derive(Debug, Error)] +#[error("{protocol} protocol not supported: {inner}")] pub struct WaylandBindError { inner: BindError, protocol: &'static str, } + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] impl WaylandBindError { pub(crate) fn new(inner: BindError, protocol: &'static str) -> Self { @@ -76,67 +88,33 @@ impl WaylandBindError { } } -#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] -impl Display for WaylandBindError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} protocol not supported: {}", - self.protocol, self.inner - ) - } -} - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[derive(Debug, Error)] pub enum LayerShellCaptureCreationError { + #[error(transparent)] Connect(#[from] ConnectError), + #[error(transparent)] Global(#[from] GlobalError), + #[error(transparent)] Wayland(#[from] WaylandError), + #[error(transparent)] Bind(#[from] WaylandBindError), + #[error(transparent)] Dispatch(#[from] DispatchError), + #[error(transparent)] Io(#[from] io::Error), } -#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] -impl Display for LayerShellCaptureCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LayerShellCaptureCreationError::Bind(e) => write!(f, "{e}"), - LayerShellCaptureCreationError::Connect(e) => { - write!(f, "could not connect to wayland compositor: {e}") - } - LayerShellCaptureCreationError::Global(e) => write!(f, "wayland error: {e}"), - LayerShellCaptureCreationError::Wayland(e) => write!(f, "wayland error: {e}"), - LayerShellCaptureCreationError::Dispatch(e) => { - write!(f, "error dispatching wayland events: {e}") - } - LayerShellCaptureCreationError::Io(e) => write!(f, "io error: {e}"), - } - } -} - #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] #[derive(Debug, Error)] pub enum X11InputCaptureCreationError { + #[error("X11 input capture is not yet implemented :(")] NotImplemented, } -#[cfg(all(unix, feature = "x11", not(target_os = "macos")))] -impl Display for X11InputCaptureCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "X11 input capture is not yet implemented :(") - } -} #[cfg(target_os = "macos")] #[derive(Debug, Error)] pub enum MacOSInputCaptureCreationError { + #[error("MacOS input capture is not yet implemented :(")] NotImplemented, } - -#[cfg(target_os = "macos")] -impl Display for MacOSInputCaptureCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "macos input capture is not yet implemented :(") - } -} diff --git a/input-capture/src/lib.rs b/input-capture/src/lib.rs index f24f166..90ed483 100644 --- a/input-capture/src/lib.rs +++ b/input-capture/src/lib.rs @@ -4,7 +4,7 @@ use futures_core::Stream; use input_event::Event; -use self::error::CaptureCreationError; +pub use error::{CaptureCreationError, CaptureError}; pub mod error; diff --git a/input-capture/src/libei.rs b/input-capture/src/libei.rs index 84b9099..0b19203 100644 --- a/input-capture/src/libei.rs +++ b/input-capture/src/libei.rs @@ -1,4 +1,3 @@ -use anyhow::{anyhow, Result}; use ashpd::{ desktop::{ input_capture::{Activated, Barrier, BarrierID, Capabilities, InputCapture, Region, Zones}, @@ -32,6 +31,8 @@ use once_cell::sync::Lazy; use input_event::{Event, KeyboardEvent, PointerEvent}; +use crate::error::{CaptureError, ReisConvertEventStreamError}; + use super::{ error::LibeiCaptureCreationError, CaptureHandle, InputCapture as LanMouseInputCapture, Position, }; @@ -46,7 +47,7 @@ enum ProducerEvent { #[allow(dead_code)] pub struct LibeiInputCapture<'a> { input_capture: Pin>>, - libei_task: JoinHandle>, + libei_task: JoinHandle>, event_rx: tokio::sync::mpsc::Receiver<(CaptureHandle, Event)>, notify_tx: tokio::sync::mpsc::Sender, } @@ -108,7 +109,7 @@ async fn update_barriers( session: &Session<'_>, active_clients: &Vec<(CaptureHandle, Position)>, next_barrier_id: &mut u32, -) -> Result> { +) -> Result, ashpd::Error> { let zones = input_capture.zones(session).await?.response()?; log::debug!("zones: {zones:?}"); @@ -154,7 +155,7 @@ async fn create_session<'a>( async fn connect_to_eis( input_capture: &InputCapture<'_>, session: &Session<'_>, -) -> Result<(ei::Context, EiConvertEventStream)> { +) -> Result<(ei::Context, EiConvertEventStream), CaptureError> { log::debug!("connect_to_eis"); let fd = input_capture.connect_to_eis(session).await?; @@ -165,17 +166,13 @@ async fn connect_to_eis( // create ei context let context = ei::Context::new(stream)?; let mut event_stream = EiEventStream::new(context.clone())?; - let response = match reis::tokio::ei_handshake( + let response = reis::tokio::ei_handshake( &mut event_stream, "de.feschber.LanMouse", ei::handshake::ContextType::Receiver, &INTERFACES, ) - .await - { - Ok(res) => res, - Err(e) => return Err(anyhow!("ei handshake failed: {e:?}")), - }; + .await?; let event_stream = EiConvertEventStream::new(event_stream, response.serial); Ok((context, event_stream)) @@ -186,13 +183,13 @@ async fn libei_event_handler( context: ei::Context, event_tx: Sender<(CaptureHandle, Event)>, current_client: Rc>>, -) -> Result<()> { +) -> Result<(), CaptureError> { loop { - let ei_event = match ei_event_stream.next().await { - Some(Ok(event)) => event, - Some(Err(e)) => return Err(anyhow!("libei connection closed: {e:?}")), - None => return Err(anyhow!("libei connection closed")), - }; + let ei_event = ei_event_stream + .next() + .await + .ok_or(CaptureError::EndOfStream)? + .map_err(ReisConvertEventStreamError::from)?; log::trace!("from ei: {ei_event:?}"); let client = current_client.get(); handle_ei_event(ei_event, client, &context, &event_tx).await; @@ -202,136 +199,26 @@ async fn libei_event_handler( async fn wait_for_active_client( notify_rx: &mut Receiver, active_clients: &mut Vec<(CaptureHandle, Position)>, -) -> Result<()> { +) { // wait for a client update while let Some(producer_event) = notify_rx.recv().await { if let ProducerEvent::Create(c, p) = producer_event { - handle_producer_event(ProducerEvent::Create(c, p), active_clients)?; + handle_producer_event(ProducerEvent::Create(c, p), active_clients); break; } } - Ok(()) } impl<'a> LibeiInputCapture<'a> { pub async fn new() -> std::result::Result { let input_capture = Box::pin(InputCapture::new().await?); let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture<'static>; - let mut first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?); + let first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?); let (event_tx, event_rx) = tokio::sync::mpsc::channel(32); - let (notify_tx, mut notify_rx) = tokio::sync::mpsc::channel(32); - let libei_task = tokio::task::spawn_local(async move { - /* safety: libei_task does not outlive Self */ - let input_capture = unsafe { &*input_capture_ptr }; - - let mut active_clients: Vec<(CaptureHandle, Position)> = vec![]; - let mut next_barrier_id = 1u32; - - /* there is a bug in xdg-remote-desktop-portal-gnome / mutter that - * prevents receiving further events after a session has been disabled once. - * Therefore the session needs to recreated when the barriers are updated */ - - loop { - // otherwise it asks to capture input even with no active clients - if active_clients.is_empty() { - wait_for_active_client(&mut notify_rx, &mut active_clients).await?; - continue; - } - - let current_client = Rc::new(Cell::new(None)); - - // create session - let (session, _) = match first_session.take() { - Some(s) => s, - _ => create_session(input_capture).await?, - }; - - // connect to eis server - let (context, ei_event_stream) = connect_to_eis(input_capture, &session).await?; - - // async event task - let mut ei_task: JoinHandle> = - tokio::task::spawn_local(libei_event_handler( - ei_event_stream, - context, - event_tx.clone(), - current_client.clone(), - )); - - let mut activated = input_capture.receive_activated().await?; - let mut zones_changed = input_capture.receive_zones_changed().await?; - - // set barriers - let client_for_barrier_id = update_barriers( - input_capture, - &session, - &active_clients, - &mut next_barrier_id, - ) - .await?; - - log::debug!("enabling session"); - input_capture.enable(&session).await?; - - loop { - tokio::select! { - activated = activated.next() => { - let activated = activated.ok_or(anyhow!("error receiving activation token"))?; - log::debug!("activated: {activated:?}"); - - let client = *client_for_barrier_id - .get(&activated.barrier_id()) - .expect("invalid barrier id"); - current_client.replace(Some(client)); - - event_tx.send((client, Event::Enter())).await?; - - tokio::select! { - producer_event = notify_rx.recv() => { - let producer_event = producer_event.expect("channel closed"); - if handle_producer_event(producer_event, &mut active_clients)? { - break; /* clients updated */ - } - } - zones_changed = zones_changed.next() => { - log::debug!("zones changed: {zones_changed:?}"); - break; - } - res = &mut ei_task => { - if let Err(e) = res.expect("ei task paniced") { - log::warn!("libei task exited: {e}"); - } - break; - } - } - release_capture( - input_capture, - &session, - activated, - client, - &active_clients, - ).await?; - } - producer_event = notify_rx.recv() => { - let producer_event = producer_event.expect("channel closed"); - if handle_producer_event(producer_event, &mut active_clients)? { - /* clients updated */ - break; - } - }, - res = &mut ei_task => { - if let Err(e) = res.expect("ei task paniced") { - log::warn!("libei task exited: {e}"); - } - break; - } - } - } - ei_task.abort(); - input_capture.disable(&session).await?; - } - }); + let (notify_tx, notify_rx) = tokio::sync::mpsc::channel(32); + let capture = do_capture(input_capture_ptr, notify_rx, first_session, event_tx); + let libei_task = tokio::task::spawn_local(capture); let producer = Self { input_capture, @@ -344,13 +231,139 @@ impl<'a> LibeiInputCapture<'a> { } } +async fn do_capture<'a>( + input_capture_ptr: *const InputCapture<'static>, + mut notify_rx: Receiver, + mut first_session: Option<(Session<'a>, BitFlags)>, + event_tx: Sender<(CaptureHandle, Event)>, +) -> Result<(), CaptureError> { + /* safety: libei_task does not outlive Self */ + let input_capture = unsafe { &*input_capture_ptr }; + + let mut active_clients: Vec<(CaptureHandle, Position)> = vec![]; + let mut next_barrier_id = 1u32; + + /* there is a bug in xdg-remote-desktop-portal-gnome / mutter that + * prevents receiving further events after a session has been disabled once. + * Therefore the session needs to recreated when the barriers are updated */ + + loop { + // otherwise it asks to capture input even with no active clients + if active_clients.is_empty() { + wait_for_active_client(&mut notify_rx, &mut active_clients).await; + if notify_rx.is_closed() { + break Ok(()); + } else { + continue; + } + } + + let current_client = Rc::new(Cell::new(None)); + + // create session + let (session, _) = match first_session.take() { + Some(s) => s, + _ => create_session(input_capture).await?, + }; + + // connect to eis server + let (context, ei_event_stream) = connect_to_eis(input_capture, &session).await?; + + // async event task + let mut ei_task: JoinHandle> = + tokio::task::spawn_local(libei_event_handler( + ei_event_stream, + context, + event_tx.clone(), + current_client.clone(), + )); + + let mut activated = input_capture.receive_activated().await?; + let mut zones_changed = input_capture.receive_zones_changed().await?; + + // set barriers + let client_for_barrier_id = update_barriers( + input_capture, + &session, + &active_clients, + &mut next_barrier_id, + ) + .await?; + + log::debug!("enabling session"); + input_capture.enable(&session).await?; + + loop { + tokio::select! { + activated = activated.next() => { + let activated = activated.ok_or(CaptureError::ActivationClosed)?; + log::debug!("activated: {activated:?}"); + + let client = *client_for_barrier_id + .get(&activated.barrier_id()) + .expect("invalid barrier id"); + current_client.replace(Some(client)); + + if event_tx.send((client, Event::Enter())).await.is_err() { + break; + }; + + tokio::select! { + producer_event = notify_rx.recv() => { + let producer_event = producer_event.expect("channel closed"); + if handle_producer_event(producer_event, &mut active_clients) { + break; /* clients updated */ + } + } + zones_changed = zones_changed.next() => { + log::debug!("zones changed: {zones_changed:?}"); + break; + } + res = &mut ei_task => { + if let Err(e) = res.expect("ei task paniced") { + log::warn!("libei task exited: {e}"); + } + break; + } + } + release_capture( + input_capture, + &session, + activated, + client, + &active_clients, + ).await?; + } + producer_event = notify_rx.recv() => { + let producer_event = producer_event.expect("channel closed"); + if handle_producer_event(producer_event, &mut active_clients) { + /* clients updated */ + break; + } + }, + res = &mut ei_task => { + if let Err(e) = res.expect("ei task paniced") { + log::warn!("libei task exited: {e}"); + } + break; + } + } + } + ei_task.abort(); + input_capture.disable(&session).await?; + if event_tx.is_closed() { + break Ok(()); + } + } +} + async fn release_capture( input_capture: &InputCapture<'_>, session: &Session<'_>, activated: Activated, current_client: CaptureHandle, active_clients: &[(CaptureHandle, Position)], -) -> Result<()> { +) -> Result<(), CaptureError> { log::debug!("releasing input capture {}", activated.activation_id()); let (x, y) = activated.cursor_position(); let pos = active_clients @@ -377,9 +390,9 @@ async fn release_capture( fn handle_producer_event( producer_event: ProducerEvent, active_clients: &mut Vec<(CaptureHandle, Position)>, -) -> Result { +) -> bool { log::debug!("handling event: {producer_event:?}"); - let updated = match producer_event { + match producer_event { ProducerEvent::Release => false, ProducerEvent::Create(c, p) => { active_clients.push((c, p)); @@ -389,8 +402,7 @@ fn handle_producer_event( active_clients.retain(|(h, _)| *h != c); true } - }; - Ok(updated) + } } async fn handle_ei_event( @@ -440,8 +452,8 @@ async fn handle_ei_event( EiEvent::PointerMotion(motion) => { let motion_event = PointerEvent::Motion { time: motion.time as u32, - relative_x: motion.dx as f64, - relative_y: motion.dy as f64, + dx: motion.dx as f64, + dy: motion.dy as f64, }; if let Some(current_client) = current_client { event_tx diff --git a/input-capture/src/wayland.rs b/input-capture/src/wayland.rs index d11030c..22368cf 100644 --- a/input-capture/src/wayland.rs +++ b/input-capture/src/wayland.rs @@ -837,21 +837,16 @@ impl Dispatch for State { if let zwp_relative_pointer_v1::Event::RelativeMotion { utime_hi, utime_lo, - dx: _, - dy: _, - dx_unaccel: surface_x, - dy_unaccel: surface_y, + dx_unaccel: dx, + dy_unaccel: dy, + .. } = event { if let Some((_window, client)) = &app.focused { let time = (((utime_hi as u64) << 32 | utime_lo as u64) / 1000) as u32; app.pending_events.push_back(( *client, - Event::Pointer(PointerEvent::Motion { - time, - relative_x: surface_x, - relative_y: surface_y, - }), + Event::Pointer(PointerEvent::Motion { time, dx, dy }), )); } } diff --git a/input-capture/src/windows.rs b/input-capture/src/windows.rs index 9c92501..bfbf6c3 100644 --- a/input-capture/src/windows.rs +++ b/input-capture/src/windows.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use core::task::{Context, Poll}; use futures::Stream; use once_cell::unsync::Lazy; @@ -145,11 +144,8 @@ fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option { let (x, y) = (mouse_low_level.pt.x, mouse_low_level.pt.y); let (ex, ey) = ENTRY_POINT; let (dx, dy) = (x - ex, y - ey); - Some(PointerEvent::Motion { - time: 0, - relative_x: dx as f64, - relative_y: dy as f64, - }) + let (dx, dy) = (dx as f64, dy as f64); + Some(PointerEvent::Motion { time: 0, dx, dy }) }, WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::AxisDiscrete120 { axis: 0, diff --git a/input-emulation/Cargo.toml b/input-emulation/Cargo.toml index bfd00ca..d7df386 100644 --- a/input-emulation/Cargo.toml +++ b/input-emulation/Cargo.toml @@ -7,7 +7,6 @@ license = "GPL-3.0-or-later" repository = "https://github.com/ferdinandschober/lan-mouse" [dependencies] -anyhow = "1.0.86" async-trait = "0.1.80" futures = "0.3.28" log = "0.4.22" diff --git a/input-emulation/src/error.rs b/input-emulation/src/error.rs index 0d5b025..8695777 100644 --- a/input-emulation/src/error.rs +++ b/input-emulation/src/error.rs @@ -1,6 +1,8 @@ -use std::{fmt::Display, io}; - +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +use reis::tokio::EiConvertEventStreamError; +use std::io; use thiserror::Error; + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] use wayland_client::{ backend::WaylandError, @@ -11,11 +13,30 @@ use wayland_client::{ #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] use reis::tokio::HandshakeError; +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +#[derive(Debug, Error)] +#[error("error in libei stream: {inner:?}")] +pub struct ReisConvertStreamError { + inner: EiConvertEventStreamError, +} + +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +impl From for ReisConvertStreamError { + fn from(e: EiConvertEventStreamError) -> Self { + Self { inner: e } + } +} + #[derive(Debug, Error)] pub enum EmulationError { + #[error("event stream closed")] + EndOfStream, #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[error("libei error flushing events: `{0}`")] Libei(#[from] reis::event::Error), + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + #[error("")] + LibeiConvertStream(#[from] ReisConvertStreamError), #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[error("wayland error: `{0}`")] Wayland(#[from] wayland_client::backend::WaylandError), @@ -33,58 +54,52 @@ pub enum EmulationError { #[derive(Debug, Error)] pub enum EmulationCreationError { #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + #[error("wlroots backend: `{0}`")] Wlroots(#[from] WlrootsEmulationCreationError), #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + #[error("libei backend: `{0}`")] Libei(#[from] LibeiEmulationCreationError), #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] + #[error("xdg-desktop-portal: `{0}`")] Xdp(#[from] XdpEmulationCreationError), #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + #[error("x11: `{0}`")] X11(#[from] X11EmulationCreationError), #[cfg(target_os = "macos")] + #[error("macos: `{0}`")] MacOs(#[from] MacOSEmulationCreationError), #[cfg(windows)] + #[error("windows: `{0}`")] Windows(#[from] WindowsEmulationCreationError), + #[error("capture error")] NoAvailableBackend, } -impl Display for EmulationCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let reason = match self { - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] - EmulationCreationError::Wlroots(e) => format!("wlroots backend: {e}"), - #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] - EmulationCreationError::Libei(e) => format!("libei backend: {e}"), - #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] - EmulationCreationError::Xdp(e) => format!("desktop portal backend: {e}"), - #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] - EmulationCreationError::X11(e) => format!("x11 backend: {e}"), - #[cfg(target_os = "macos")] - EmulationCreationError::MacOs(e) => format!("macos backend: {e}"), - #[cfg(windows)] - EmulationCreationError::Windows(e) => format!("windows backend: {e}"), - EmulationCreationError::NoAvailableBackend => "no backend available".to_string(), - }; - write!(f, "could not create input emulation backend: {reason}") - } -} - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[derive(Debug, Error)] pub enum WlrootsEmulationCreationError { + #[error(transparent)] Connect(#[from] ConnectError), + #[error(transparent)] Global(#[from] GlobalError), + #[error(transparent)] Wayland(#[from] WaylandError), + #[error(transparent)] Bind(#[from] WaylandBindError), + #[error(transparent)] Dispatch(#[from] DispatchError), + #[error(transparent)] Io(#[from] std::io::Error), } #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[derive(Debug, Error)] +#[error("wayland protocol \"{protocol}\" not supported: {inner}")] pub struct WaylandBindError { inner: BindError, protocol: &'static str, } + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] impl WaylandBindError { pub(crate) fn new(inner: BindError, protocol: &'static str) -> Self { @@ -92,101 +107,38 @@ impl WaylandBindError { } } -#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] -impl Display for WaylandBindError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} protocol not supported: {}", - self.protocol, self.inner - ) - } -} - -#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] -impl Display for WlrootsEmulationCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - WlrootsEmulationCreationError::Bind(e) => write!(f, "{e}"), - WlrootsEmulationCreationError::Connect(e) => { - write!(f, "could not connect to wayland compositor: {e}") - } - WlrootsEmulationCreationError::Global(e) => write!(f, "wayland error: {e}"), - WlrootsEmulationCreationError::Wayland(e) => write!(f, "wayland error: {e}"), - WlrootsEmulationCreationError::Dispatch(e) => { - write!(f, "error dispatching wayland events: {e}") - } - WlrootsEmulationCreationError::Io(e) => write!(f, "io error: {e}"), - } - } -} - #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[derive(Debug, Error)] pub enum LibeiEmulationCreationError { + #[error(transparent)] Ashpd(#[from] ashpd::Error), + #[error(transparent)] Io(#[from] std::io::Error), + #[error(transparent)] Handshake(#[from] HandshakeError), } -#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] -impl Display for LibeiEmulationCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LibeiEmulationCreationError::Ashpd(e) => write!(f, "xdg-desktop-portal: {e}"), - LibeiEmulationCreationError::Io(e) => write!(f, "io error: {e}"), - LibeiEmulationCreationError::Handshake(e) => write!(f, "error in libei handshake: {e}"), - } - } -} - #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] #[derive(Debug, Error)] pub enum XdpEmulationCreationError { + #[error(transparent)] Ashpd(#[from] ashpd::Error), } -#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] -impl Display for XdpEmulationCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - XdpEmulationCreationError::Ashpd(e) => write!(f, "portal error: {e}"), - } - } -} - #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] #[derive(Debug, Error)] pub enum X11EmulationCreationError { + #[error("could not open display")] OpenDisplay, } -#[cfg(all(unix, feature = "x11", not(target_os = "macos")))] -impl Display for X11EmulationCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - X11EmulationCreationError::OpenDisplay => write!(f, "could not open display!"), - } - } -} - #[cfg(target_os = "macos")] #[derive(Debug, Error)] pub enum MacOSEmulationCreationError { + #[error("could not create event source")] EventSourceCreation, } -#[cfg(target_os = "macos")] -impl Display for MacOSEmulationCreationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - MacOSEmulationCreationError::EventSourceCreation => { - write!(f, "could not create event source") - } - } - } -} - #[cfg(windows)] #[derive(Debug, Error)] pub enum WindowsEmulationCreationError {} diff --git a/input-emulation/src/lib.rs b/input-emulation/src/lib.rs index 17a8c7d..d05aff8 100644 --- a/input-emulation/src/lib.rs +++ b/input-emulation/src/lib.rs @@ -4,8 +4,6 @@ use std::fmt::Display; use input_event::Event; -use anyhow::Result; - use self::error::EmulationCreationError; #[cfg(windows)] diff --git a/input-emulation/src/libei.rs b/input-emulation/src/libei.rs index d417431..824fe4b 100644 --- a/input-emulation/src/libei.rs +++ b/input-emulation/src/libei.rs @@ -1,4 +1,3 @@ -use anyhow::{anyhow, Result}; use futures::StreamExt; use once_cell::sync::Lazy; use std::{ @@ -33,7 +32,7 @@ use reis::{ use input_event::{Event, KeyboardEvent, PointerEvent}; -use crate::error::EmulationError; +use crate::error::{EmulationError, ReisConvertStreamError}; use super::{error::LibeiEmulationCreationError, EmulationHandle, InputEmulation}; @@ -65,7 +64,7 @@ pub struct LibeiEmulation { context: ei::Context, devices: Devices, serial: AtomicU32, - ei_task: JoinHandle>, + ei_task: JoinHandle>, } async fn get_ei_fd() -> Result { @@ -116,8 +115,8 @@ impl LibeiEmulation { .await?; let events = EiConvertEventStream::new(events, handshake.serial); let devices = Devices::default(); - let ei_task = - tokio::task::spawn_local(ei_event_handler(events, context.clone(), devices.clone())); + let ei_handler = ei_event_handler(events, context.clone(), devices.clone()); + let ei_task = tokio::task::spawn_local(ei_handler); let serial = AtomicU32::new(handshake.serial); @@ -149,14 +148,10 @@ impl InputEmulation for LibeiEmulation { .as_micros() as u64; match event { Event::Pointer(p) => match p { - PointerEvent::Motion { - time: _, - relative_x, - relative_y, - } => { + PointerEvent::Motion { time: _, dx, dy } => { let pointer_device = self.devices.pointer.read().unwrap(); if let Some((d, p)) = pointer_device.as_ref() { - p.motion_relative(relative_x as f32, relative_y as f32); + p.motion_relative(dx as f32, dy as f32); d.frame(self.serial.load(Ordering::SeqCst), now); } } @@ -239,13 +234,13 @@ async fn ei_event_handler( mut events: EiConvertEventStream, context: ei::Context, devices: Devices, -) -> Result<()> { +) -> Result<(), EmulationError> { loop { let event = events .next() .await - .ok_or(anyhow!("ei stream closed"))? - .map_err(|e| anyhow!("libei error: {e:?}"))?; + .ok_or(EmulationError::EndOfStream)? + .map_err(ReisConvertStreamError::from)?; const CAPABILITIES: &[DeviceCapability] = &[ DeviceCapability::Pointer, DeviceCapability::PointerAbsolute, @@ -330,7 +325,7 @@ async fn ei_event_handler( // EiEvent::TouchMotion(_) => { }, _ => unreachable!("unexpected ei event"), } - context.flush()?; + context.flush().map_err(|e| io::Error::new(e.kind(), e))?; } Ok(()) } diff --git a/input-emulation/src/macos.rs b/input-emulation/src/macos.rs index ee4afe6..392aec1 100644 --- a/input-emulation/src/macos.rs +++ b/input-emulation/src/macos.rs @@ -114,11 +114,7 @@ impl InputEmulation for MacOSEmulation { ) -> Result<(), EmulationError> { match event { Event::Pointer(pointer_event) => match pointer_event { - PointerEvent::Motion { - time: _, - relative_x, - relative_y, - } => { + PointerEvent::Motion { time: _, dx, dy } => { // FIXME secondary displays? let (min_x, min_y, max_x, max_y) = unsafe { let display = CGMainDisplayID(); @@ -137,8 +133,8 @@ impl InputEmulation for MacOSEmulation { } }; - mouse_location.x = (mouse_location.x + relative_x).clamp(min_x, max_x - 1.); - mouse_location.y = (mouse_location.y + relative_y).clamp(min_y, max_y - 1.); + mouse_location.x = (mouse_location.x + dx).clamp(min_x, max_x - 1.); + mouse_location.y = (mouse_location.y + dy).clamp(min_y, max_y - 1.); let mut event_type = CGEventType::MouseMoved; if self.button_state.left { @@ -160,14 +156,8 @@ impl InputEmulation for MacOSEmulation { return Ok(()); } }; - event.set_integer_value_field( - EventField::MOUSE_EVENT_DELTA_X, - relative_x as i64, - ); - event.set_integer_value_field( - EventField::MOUSE_EVENT_DELTA_Y, - relative_y as i64, - ); + event.set_integer_value_field(EventField::MOUSE_EVENT_DELTA_X, dx as i64); + event.set_integer_value_field(EventField::MOUSE_EVENT_DELTA_Y, dy as i64); event.post(CGEventTapLocation::HID); } PointerEvent::Button { diff --git a/input-emulation/src/windows.rs b/input-emulation/src/windows.rs index 1ec0b74..3276769 100644 --- a/input-emulation/src/windows.rs +++ b/input-emulation/src/windows.rs @@ -39,12 +39,8 @@ impl InputEmulation for WindowsEmulation { async fn consume(&mut self, event: Event, _: EmulationHandle) -> Result<(), EmulationError> { match event { Event::Pointer(pointer_event) => match pointer_event { - PointerEvent::Motion { - time: _, - relative_x, - relative_y, - } => { - rel_mouse(relative_x as i32, relative_y as i32); + PointerEvent::Motion { time: _, dx, dy } => { + rel_mouse(dx as i32, dy as i32); } PointerEvent::Button { time: _, diff --git a/input-emulation/src/wlroots.rs b/input-emulation/src/wlroots.rs index 4423aba..7d545c8 100644 --- a/input-emulation/src/wlroots.rs +++ b/input-emulation/src/wlroots.rs @@ -177,11 +177,7 @@ impl VirtualInput { match event { Event::Pointer(e) => { match e { - PointerEvent::Motion { - time, - relative_x, - relative_y, - } => self.pointer.motion(time, relative_x, relative_y), + PointerEvent::Motion { time, dx, dy } => self.pointer.motion(time, dx, dy), PointerEvent::Button { time, button, diff --git a/input-emulation/src/x11.rs b/input-emulation/src/x11.rs index 5470cac..790466a 100644 --- a/input-emulation/src/x11.rs +++ b/input-emulation/src/x11.rs @@ -103,12 +103,8 @@ impl InputEmulation for X11Emulation { async fn consume(&mut self, event: Event, _: EmulationHandle) -> Result<(), EmulationError> { match event { Event::Pointer(pointer_event) => match pointer_event { - PointerEvent::Motion { - time: _, - relative_x, - relative_y, - } => { - self.relative_motion(relative_x as i32, relative_y as i32); + PointerEvent::Motion { time: _, dx, dy } => { + self.relative_motion(dx as i32, dy as i32); } PointerEvent::Button { time: _, diff --git a/input-emulation/src/xdg_desktop_portal.rs b/input-emulation/src/xdg_desktop_portal.rs index b0f7bf3..8c6ff0c 100644 --- a/input-emulation/src/xdg_desktop_portal.rs +++ b/input-emulation/src/xdg_desktop_portal.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use ashpd::{ desktop::{ remote_desktop::{Axis, DeviceType, KeyState, RemoteDesktop}, @@ -70,13 +69,9 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> { ) -> Result<(), EmulationError> { match event { Pointer(p) => match p { - PointerEvent::Motion { - time: _, - relative_x, - relative_y, - } => { + PointerEvent::Motion { time: _, dx, dy } => { self.proxy - .notify_pointer_motion(&self.session, relative_x, relative_y) + .notify_pointer_motion(&self.session, dx, dy) .await?; } PointerEvent::Button { diff --git a/input-event/Cargo.toml b/input-event/Cargo.toml index fb486e1..82d6f6b 100644 --- a/input-event/Cargo.toml +++ b/input-event/Cargo.toml @@ -7,8 +7,8 @@ license = "GPL-3.0-or-later" repository = "https://github.com/ferdinandschober/lan-mouse" [dependencies] -anyhow = "1.0.86" futures-core = "0.3.30" log = "0.4.22" num_enum = "0.7.2" serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0.61" diff --git a/input-event/src/error.rs b/input-event/src/error.rs new file mode 100644 index 0000000..8700adf --- /dev/null +++ b/input-event/src/error.rs @@ -0,0 +1,17 @@ +use std::array::TryFromSliceError; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ProtocolError { + #[error(transparent)] + MissingData(#[from] TryFromSliceError), + #[error("invalid event id: `{0}`")] + InvalidEventId(u8), + #[error("invalid pointer event type: `{0}`")] + InvalidPointerEventId(u8), + #[error("invalid keyboard event type: `{0}`")] + InvalidKeyboardEventId(u8), + #[error("expected data at idx `{0:?}`")] + Data(String), +} diff --git a/input-event/src/lib.rs b/input-event/src/lib.rs index db31a25..c66c214 100644 --- a/input-event/src/lib.rs +++ b/input-event/src/lib.rs @@ -1,9 +1,8 @@ -use anyhow::{anyhow, Result}; -use std::{ - error::Error, - fmt::{self, Display}, -}; +pub use error::ProtocolError; +use std::fmt::{self, Display}; +pub mod error; +pub mod proto; pub mod scancode; // FIXME @@ -15,35 +14,23 @@ pub const BTN_FORWARD: u32 = 0x114; #[derive(Debug, PartialEq, Clone, Copy)] pub enum PointerEvent { - Motion { - time: u32, - relative_x: f64, - relative_y: f64, - }, - Button { - time: u32, - button: u32, - state: u32, - }, - Axis { - time: u32, - axis: u8, - value: f64, - }, - AxisDiscrete120 { - axis: u8, - value: i32, - }, + /// relative motion event + Motion { time: u32, dx: f64, dy: f64 }, + /// mouse button event + Button { time: u32, button: u32, state: u32 }, + /// axis event, scroll event for touchpads + Axis { time: u32, axis: u8, value: f64 }, + /// discrete axis event, scroll event for mice - 120 = one scroll tick + AxisDiscrete120 { axis: u8, value: i32 }, + /// frame event Frame {}, } #[derive(Debug, PartialEq, Clone, Copy)] pub enum KeyboardEvent { - Key { - time: u32, - key: u32, - state: u8, - }, + /// a key press / release event + Key { time: u32, key: u32, state: u8 }, + /// modifiers changed state Modifiers { mods_depressed: u32, mods_latched: u32, @@ -80,11 +67,7 @@ pub enum Event { impl Display for PointerEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PointerEvent::Motion { - time: _, - relative_x, - relative_y, - } => write!(f, "motion({relative_x},{relative_y})"), + PointerEvent::Motion { time: _, dx, dy } => write!(f, "motion({dx},{dy})"), PointerEvent::Button { time: _, button, @@ -158,430 +141,3 @@ impl Display for Event { } } } - -impl Event { - fn event_type(&self) -> EventType { - match self { - Self::Pointer(_) => EventType::Pointer, - Self::Keyboard(_) => EventType::Keyboard, - Self::Enter() => EventType::Enter, - Self::Leave() => EventType::Leave, - Self::Ping() => EventType::Ping, - Self::Pong() => EventType::Pong, - Self::Disconnect() => EventType::Disconnect, - } - } -} - -impl PointerEvent { - fn event_type(&self) -> PointerEventType { - match self { - Self::Motion { .. } => PointerEventType::Motion, - Self::Button { .. } => PointerEventType::Button, - Self::Axis { .. } => PointerEventType::Axis, - Self::AxisDiscrete120 { .. } => PointerEventType::AxisDiscrete120, - Self::Frame { .. } => PointerEventType::Frame, - } - } -} - -impl KeyboardEvent { - fn event_type(&self) -> KeyboardEventType { - match self { - KeyboardEvent::Key { .. } => KeyboardEventType::Key, - KeyboardEvent::Modifiers { .. } => KeyboardEventType::Modifiers, - } - } -} - -enum PointerEventType { - Motion, - Button, - Axis, - AxisDiscrete120, - Frame, -} -enum KeyboardEventType { - Key, - Modifiers, -} -enum EventType { - Pointer, - Keyboard, - Enter, - Leave, - Ping, - Pong, - Disconnect, -} - -impl TryFrom for PointerEventType { - type Error = anyhow::Error; - - fn try_from(value: u8) -> Result { - match value { - x if x == Self::Motion as u8 => Ok(Self::Motion), - x if x == Self::Button as u8 => Ok(Self::Button), - x if x == Self::Axis as u8 => Ok(Self::Axis), - x if x == Self::AxisDiscrete120 as u8 => Ok(Self::AxisDiscrete120), - x if x == Self::Frame as u8 => Ok(Self::Frame), - _ => Err(anyhow!(ProtocolError { - msg: format!("invalid pointer event type {}", value), - })), - } - } -} - -impl TryFrom for KeyboardEventType { - type Error = anyhow::Error; - - fn try_from(value: u8) -> Result { - match value { - x if x == Self::Key as u8 => Ok(Self::Key), - x if x == Self::Modifiers as u8 => Ok(Self::Modifiers), - _ => Err(anyhow!(ProtocolError { - msg: format!("invalid keyboard event type {}", value), - })), - } - } -} - -impl From<&Event> for Vec { - fn from(event: &Event) -> Self { - let event_id = vec![event.event_type() as u8]; - let event_data = match event { - Event::Pointer(p) => p.into(), - Event::Keyboard(k) => k.into(), - Event::Enter() => vec![], - Event::Leave() => vec![], - Event::Ping() => vec![], - Event::Pong() => vec![], - Event::Disconnect() => vec![], - }; - [event_id, event_data].concat() - } -} - -#[derive(Debug)] -struct ProtocolError { - msg: String, -} - -impl fmt::Display for ProtocolError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Protocol violation: {}", self.msg) - } -} -impl Error for ProtocolError {} - -impl TryFrom> for Event { - type Error = anyhow::Error; - - fn try_from(value: Vec) -> Result { - let event_id = u8::from_be_bytes(value[..1].try_into()?); - match event_id { - i if i == (EventType::Pointer as u8) => Ok(Event::Pointer(value.try_into()?)), - i if i == (EventType::Keyboard as u8) => Ok(Event::Keyboard(value.try_into()?)), - i if i == (EventType::Enter as u8) => Ok(Event::Enter()), - i if i == (EventType::Leave as u8) => Ok(Event::Leave()), - i if i == (EventType::Ping as u8) => Ok(Event::Ping()), - i if i == (EventType::Pong as u8) => Ok(Event::Pong()), - i if i == (EventType::Disconnect as u8) => Ok(Event::Disconnect()), - _ => Err(anyhow!(ProtocolError { - msg: format!("invalid event_id {}", event_id), - })), - } - } -} - -impl From<&PointerEvent> for Vec { - fn from(event: &PointerEvent) -> Self { - let id = vec![event.event_type() as u8]; - let data = match event { - PointerEvent::Motion { - time, - relative_x, - relative_y, - } => { - let time = time.to_be_bytes(); - let relative_x = relative_x.to_be_bytes(); - let relative_y = relative_y.to_be_bytes(); - [&time[..], &relative_x[..], &relative_y[..]].concat() - } - PointerEvent::Button { - time, - button, - state, - } => { - let time = time.to_be_bytes(); - let button = button.to_be_bytes(); - let state = state.to_be_bytes(); - [&time[..], &button[..], &state[..]].concat() - } - PointerEvent::Axis { time, axis, value } => { - let time = time.to_be_bytes(); - let axis = axis.to_be_bytes(); - let value = value.to_be_bytes(); - [&time[..], &axis[..], &value[..]].concat() - } - PointerEvent::AxisDiscrete120 { axis, value } => { - let axis = axis.to_be_bytes(); - let value = value.to_be_bytes(); - [&axis[..], &value[..]].concat() - } - PointerEvent::Frame {} => { - vec![] - } - }; - [id, data].concat() - } -} - -impl TryFrom> for PointerEvent { - type Error = anyhow::Error; - - fn try_from(data: Vec) -> Result { - match data.get(1) { - Some(id) => { - let event_type = match id.to_owned().try_into() { - Ok(event_type) => event_type, - Err(e) => return Err(e), - }; - match event_type { - PointerEventType::Motion => { - let time = match data.get(2..6) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 2".into(), - })) - } - }; - let relative_x = match data.get(6..14) { - Some(d) => f64::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 8 Bytes at index 6".into(), - })) - } - }; - let relative_y = match data.get(14..22) { - Some(d) => f64::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 8 Bytes at index 14".into(), - })) - } - }; - Ok(Self::Motion { - time, - relative_x, - relative_y, - }) - } - PointerEventType::Button => { - let time = match data.get(2..6) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 2".into(), - })) - } - }; - let button = match data.get(6..10) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 10".into(), - })) - } - }; - let state = match data.get(10..14) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 14".into(), - })) - } - }; - Ok(Self::Button { - time, - button, - state, - }) - } - PointerEventType::Axis => { - let time = match data.get(2..6) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 2".into(), - })) - } - }; - let axis = match data.get(6) { - Some(d) => *d, - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 1 Byte at index 6".into(), - })); - } - }; - let value = match data.get(7..15) { - Some(d) => f64::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 8 Bytes at index 7".into(), - })); - } - }; - Ok(Self::Axis { time, axis, value }) - } - PointerEventType::AxisDiscrete120 => { - let axis = match data.get(2) { - Some(d) => *d, - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 1 Byte at index 2".into(), - })); - } - }; - let value = match data.get(3..7) { - Some(d) => i32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 3".into(), - })); - } - }; - Ok(Self::AxisDiscrete120 { axis, value }) - } - PointerEventType::Frame => Ok(Self::Frame {}), - } - } - None => Err(anyhow!(ProtocolError { - msg: "Expected an element at index 0".into(), - })), - } - } -} - -impl From<&KeyboardEvent> for Vec { - fn from(event: &KeyboardEvent) -> Self { - let id = vec![event.event_type() as u8]; - let data = match event { - KeyboardEvent::Key { time, key, state } => { - let time = time.to_be_bytes(); - let key = key.to_be_bytes(); - let state = state.to_be_bytes(); - [&time[..], &key[..], &state[..]].concat() - } - KeyboardEvent::Modifiers { - mods_depressed, - mods_latched, - mods_locked, - group, - } => { - let mods_depressed = mods_depressed.to_be_bytes(); - let mods_latched = mods_latched.to_be_bytes(); - let mods_locked = mods_locked.to_be_bytes(); - let group = group.to_be_bytes(); - [ - &mods_depressed[..], - &mods_latched[..], - &mods_locked[..], - &group[..], - ] - .concat() - } - }; - [id, data].concat() - } -} - -impl TryFrom> for KeyboardEvent { - type Error = anyhow::Error; - - fn try_from(data: Vec) -> Result { - match data.get(1) { - Some(id) => { - let event_type = match id.to_owned().try_into() { - Ok(event_type) => event_type, - Err(e) => return Err(e), - }; - match event_type { - KeyboardEventType::Key => { - let time = match data.get(2..6) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 6".into(), - })) - } - }; - let key = match data.get(6..10) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 10".into(), - })) - } - }; - let state = match data.get(10) { - Some(d) => *d, - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 1 Bytes at index 14".into(), - })) - } - }; - Ok(KeyboardEvent::Key { time, key, state }) - } - KeyboardEventType::Modifiers => { - let mods_depressed = match data.get(2..6) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 6".into(), - })) - } - }; - let mods_latched = match data.get(6..10) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 10".into(), - })) - } - }; - let mods_locked = match data.get(10..14) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 14".into(), - })) - } - }; - let group = match data.get(14..18) { - Some(d) => u32::from_be_bytes(d.try_into()?), - None => { - return Err(anyhow!(ProtocolError { - msg: "Expected 4 Bytes at index 18".into(), - })) - } - }; - Ok(KeyboardEvent::Modifiers { - mods_depressed, - mods_latched, - mods_locked, - group, - }) - } - } - } - None => Err(anyhow!(ProtocolError { - msg: "Expected an element at index 0".into(), - })), - } - } -} diff --git a/input-event/src/proto.rs b/input-event/src/proto.rs new file mode 100644 index 0000000..7397569 --- /dev/null +++ b/input-event/src/proto.rs @@ -0,0 +1,307 @@ +use std::{fmt::Debug, slice::SliceIndex}; + +use crate::ProtocolError; + +use super::{Event, KeyboardEvent, PointerEvent}; + +enum PointerEventType { + Motion, + Button, + Axis, + AxisDiscrete120, + Frame, +} + +enum KeyboardEventType { + Key, + Modifiers, +} + +enum EventType { + Pointer, + Keyboard, + Enter, + Leave, + Ping, + Pong, + Disconnect, +} + +impl Event { + fn event_type(&self) -> EventType { + match self { + Self::Pointer(_) => EventType::Pointer, + Self::Keyboard(_) => EventType::Keyboard, + Self::Enter() => EventType::Enter, + Self::Leave() => EventType::Leave, + Self::Ping() => EventType::Ping, + Self::Pong() => EventType::Pong, + Self::Disconnect() => EventType::Disconnect, + } + } +} + +impl PointerEvent { + fn event_type(&self) -> PointerEventType { + match self { + Self::Motion { .. } => PointerEventType::Motion, + Self::Button { .. } => PointerEventType::Button, + Self::Axis { .. } => PointerEventType::Axis, + Self::AxisDiscrete120 { .. } => PointerEventType::AxisDiscrete120, + Self::Frame { .. } => PointerEventType::Frame, + } + } +} + +impl KeyboardEvent { + fn event_type(&self) -> KeyboardEventType { + match self { + KeyboardEvent::Key { .. } => KeyboardEventType::Key, + KeyboardEvent::Modifiers { .. } => KeyboardEventType::Modifiers, + } + } +} + +impl TryFrom for PointerEventType { + type Error = ProtocolError; + + fn try_from(value: u8) -> Result { + match value { + x if x == Self::Motion as u8 => Ok(Self::Motion), + x if x == Self::Button as u8 => Ok(Self::Button), + x if x == Self::Axis as u8 => Ok(Self::Axis), + x if x == Self::AxisDiscrete120 as u8 => Ok(Self::AxisDiscrete120), + x if x == Self::Frame as u8 => Ok(Self::Frame), + _ => Err(ProtocolError::InvalidPointerEventId(value)), + } + } +} + +impl TryFrom for KeyboardEventType { + type Error = ProtocolError; + + fn try_from(value: u8) -> Result { + match value { + x if x == Self::Key as u8 => Ok(Self::Key), + x if x == Self::Modifiers as u8 => Ok(Self::Modifiers), + _ => Err(ProtocolError::InvalidKeyboardEventId(value)), + } + } +} + +impl From<&Event> for Vec { + fn from(event: &Event) -> Self { + let event_id = vec![event.event_type() as u8]; + let event_data = match event { + Event::Pointer(p) => p.into(), + Event::Keyboard(k) => k.into(), + Event::Enter() => vec![], + Event::Leave() => vec![], + Event::Ping() => vec![], + Event::Pong() => vec![], + Event::Disconnect() => vec![], + }; + [event_id, event_data].concat() + } +} + +impl TryFrom> for Event { + type Error = ProtocolError; + + fn try_from(value: Vec) -> Result { + let event_id = u8::from_be_bytes(value[..1].try_into()?); + match event_id { + i if i == (EventType::Pointer as u8) => Ok(Event::Pointer(value.try_into()?)), + i if i == (EventType::Keyboard as u8) => Ok(Event::Keyboard(value.try_into()?)), + i if i == (EventType::Enter as u8) => Ok(Event::Enter()), + i if i == (EventType::Leave as u8) => Ok(Event::Leave()), + i if i == (EventType::Ping as u8) => Ok(Event::Ping()), + i if i == (EventType::Pong as u8) => Ok(Event::Pong()), + i if i == (EventType::Disconnect as u8) => Ok(Event::Disconnect()), + _ => Err(ProtocolError::InvalidEventId(event_id)), + } + } +} + +impl From<&PointerEvent> for Vec { + fn from(event: &PointerEvent) -> Self { + let id = vec![event.event_type() as u8]; + let data = match event { + PointerEvent::Motion { time, dx, dy } => { + let time = time.to_be_bytes(); + let dx = dx.to_be_bytes(); + let dy = dy.to_be_bytes(); + [&time[..], &dx[..], &dy[..]].concat() + } + PointerEvent::Button { + time, + button, + state, + } => { + let time = time.to_be_bytes(); + let button = button.to_be_bytes(); + let state = state.to_be_bytes(); + [&time[..], &button[..], &state[..]].concat() + } + PointerEvent::Axis { time, axis, value } => { + let time = time.to_be_bytes(); + let axis = axis.to_be_bytes(); + let value = value.to_be_bytes(); + [&time[..], &axis[..], &value[..]].concat() + } + PointerEvent::AxisDiscrete120 { axis, value } => { + let axis = axis.to_be_bytes(); + let value = value.to_be_bytes(); + [&axis[..], &value[..]].concat() + } + PointerEvent::Frame {} => { + vec![] + } + }; + [id, data].concat() + } +} + +fn decode_u8(data: &[u8], idx: I) -> Result +where + I: SliceIndex<[u8], Output = [u8]> + Debug + Clone, +{ + let data = data + .get(idx.clone()) + .ok_or(ProtocolError::Data(format!("{:?}", idx)))?; + Ok(u8::from_be_bytes(data.try_into()?)) +} + +fn decode_u32(data: &[u8], idx: I) -> Result +where + I: SliceIndex<[u8], Output = [u8]> + Debug + Clone, +{ + let data = data + .get(idx.clone()) + .ok_or(ProtocolError::Data(format!("{:?}", idx)))?; + Ok(u32::from_be_bytes(data.try_into()?)) +} + +fn decode_i32(data: &[u8], idx: I) -> Result +where + I: SliceIndex<[u8], Output = [u8]> + Debug + Clone, +{ + let data = data + .get(idx.clone()) + .ok_or(ProtocolError::Data(format!("{:?}", idx)))?; + Ok(i32::from_be_bytes(data.try_into()?)) +} +fn decode_f64(data: &[u8], idx: I) -> Result +where + I: SliceIndex<[u8], Output = [u8]> + Debug + Clone, +{ + let data = data + .get(idx.clone()) + .ok_or(ProtocolError::Data(format!("{:?}", idx)))?; + Ok(f64::from_be_bytes(data.try_into()?)) +} + +impl TryFrom> for PointerEvent { + type Error = ProtocolError; + + fn try_from(data: Vec) -> Result { + match data.get(1) { + Some(id) => match id.to_owned().try_into()? { + PointerEventType::Motion => { + let time = decode_u32(&data, 2..6)?; + let dx = decode_f64(&data, 6..14)?; + let dy = decode_f64(&data, 14..22)?; + + Ok(Self::Motion { time, dx, dy }) + } + PointerEventType::Button => { + let time = decode_u32(&data, 2..6)?; + let button = decode_u32(&data, 6..10)?; + let state = decode_u32(&data, 10..14)?; + + Ok(Self::Button { + time, + button, + state, + }) + } + PointerEventType::Axis => { + let time = decode_u32(&data, 2..6)?; + let axis = decode_u8(&data, 6..7)?; + let value = decode_f64(&data, 7..15)?; + Ok(Self::Axis { time, axis, value }) + } + PointerEventType::AxisDiscrete120 => { + let axis = decode_u8(&data, 2..3)?; + let value = decode_i32(&data, 3..7)?; + Ok(Self::AxisDiscrete120 { axis, value }) + } + PointerEventType::Frame => Ok(Self::Frame {}), + }, + None => Err(ProtocolError::Data("0".to_string())), + } + } +} + +impl From<&KeyboardEvent> for Vec { + fn from(event: &KeyboardEvent) -> Self { + let id = vec![event.event_type() as u8]; + let data = match event { + KeyboardEvent::Key { time, key, state } => { + let time = time.to_be_bytes(); + let key = key.to_be_bytes(); + let state = state.to_be_bytes(); + [&time[..], &key[..], &state[..]].concat() + } + KeyboardEvent::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + } => { + let mods_depressed = mods_depressed.to_be_bytes(); + let mods_latched = mods_latched.to_be_bytes(); + let mods_locked = mods_locked.to_be_bytes(); + let group = group.to_be_bytes(); + [ + &mods_depressed[..], + &mods_latched[..], + &mods_locked[..], + &group[..], + ] + .concat() + } + }; + [id, data].concat() + } +} + +impl TryFrom> for KeyboardEvent { + type Error = ProtocolError; + + fn try_from(data: Vec) -> Result { + match data.get(1) { + Some(id) => match id.to_owned().try_into()? { + KeyboardEventType::Key => { + let time = decode_u32(&data, 2..6)?; + let key = decode_u32(&data, 6..10)?; + let state = decode_u8(&data, 10..11)?; + Ok(KeyboardEvent::Key { time, key, state }) + } + KeyboardEventType::Modifiers => { + let mods_depressed = decode_u32(&data, 2..6)?; + let mods_latched = decode_u32(&data, 6..10)?; + let mods_locked = decode_u32(&data, 10..14)?; + let group = decode_u32(&data, 14..18)?; + Ok(KeyboardEvent::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + }) + } + }, + None => Err(ProtocolError::Data("0".to_string())), + } + } +} diff --git a/src/emulation_test.rs b/src/emulation_test.rs index d132e38..610597a 100644 --- a/src/emulation_test.rs +++ b/src/emulation_test.rs @@ -37,12 +37,8 @@ async fn input_emulation_test(config: Config) -> Result<()> { if new_offset != offset { let relative_motion = (new_offset.0 - offset.0, new_offset.1 - offset.1); offset = new_offset; - let (relative_x, relative_y) = (relative_motion.0 as f64, relative_motion.1 as f64); - let event = Event::Pointer(PointerEvent::Motion { - time: 0, - relative_x, - relative_y, - }); + let (dx, dy) = (relative_motion.0 as f64, relative_motion.1 as f64); + let event = Event::Pointer(PointerEvent::Motion { time: 0, dx, dy }); emulation.consume(event, 0).await?; } } diff --git a/src/server.rs b/src/server.rs index ca61ca2..4f6b9c8 100644 --- a/src/server.rs +++ b/src/server.rs @@ -116,7 +116,7 @@ impl Server { sender_tx.clone(), capture_channel.clone(), timer_tx, - )?; + ); // create dns resolver let resolver = dns::DnsResolver::new().await?; diff --git a/src/server/emulation_task.rs b/src/server/emulation_task.rs index 5b0906b..619885c 100644 --- a/src/server/emulation_task.rs +++ b/src/server/emulation_task.rs @@ -1,12 +1,16 @@ -use anyhow::{anyhow, Result}; use std::net::SocketAddr; +use thiserror::Error; use tokio::{ sync::mpsc::{Receiver, Sender}, task::JoinHandle, }; -use crate::{client::ClientHandle, config::EmulationBackend, server::State}; +use crate::{ + client::{ClientHandle, ClientManager}, + config::EmulationBackend, + server::State, +}; use input_emulation::{ self, error::{EmulationCreationError, EmulationError}, @@ -14,7 +18,7 @@ use input_emulation::{ }; use input_event::{Event, KeyboardEvent}; -use super::{CaptureEvent, Server}; +use super::{network_task::NetworkError, CaptureEvent, Server}; #[derive(Clone, Debug)] pub enum EmulationEvent { @@ -31,51 +35,73 @@ pub enum EmulationEvent { pub fn new( backend: Option, server: Server, - mut udp_rx: Receiver>, + udp_rx: Receiver>, sender_tx: Sender<(Event, SocketAddr)>, capture_tx: Sender, timer_tx: Sender<()>, -) -> Result<(JoinHandle>, Sender), EmulationCreationError> { - let (tx, mut rx) = tokio::sync::mpsc::channel(32); - let emulate_task = tokio::task::spawn_local(async move { - let backend = backend.map(|b| b.into()); - let mut emulate = input_emulation::create(backend).await?; - let mut last_ignored = None; +) -> ( + JoinHandle>, + Sender, +) { + let (tx, rx) = tokio::sync::mpsc::channel(32); + let emulation_task = + emulation_task(backend, rx, server, udp_rx, sender_tx, capture_tx, timer_tx); + let emulate_task = tokio::task::spawn_local(emulation_task); + (emulate_task, tx) +} - loop { - tokio::select! { - udp_event = udp_rx.recv() => { - let udp_event = udp_event.ok_or(anyhow!("receiver closed"))??; - handle_udp_rx(&server, &capture_tx, &mut emulate, &sender_tx, &mut last_ignored, udp_event, &timer_tx).await?; - } - emulate_event = rx.recv() => { - match emulate_event { - Some(e) => match e { - EmulationEvent::Create(h) => emulate.create(h).await, - EmulationEvent::Destroy(h) => emulate.destroy(h).await, - EmulationEvent::ReleaseKeys(c) => release_keys(&server, &mut emulate, c).await?, - EmulationEvent::Terminate => break, - }, - None => break, +#[derive(Debug, Error)] +pub enum LanMouseEmulationError { + #[error("error creating input-emulation: `{0}`")] + Create(#[from] EmulationCreationError), + #[error("error emulating input: `{0}`")] + Emulate(#[from] EmulationError), +} + +async fn emulation_task( + backend: Option, + mut rx: Receiver, + server: Server, + mut udp_rx: Receiver>, + sender_tx: Sender<(Event, SocketAddr)>, + capture_tx: Sender, + timer_tx: Sender<()>, +) -> Result<(), LanMouseEmulationError> { + let backend = backend.map(|b| b.into()); + let mut emulation = input_emulation::create(backend).await?; + + let mut last_ignored = None; + loop { + tokio::select! { + udp_event = udp_rx.recv() => { + let udp_event = match udp_event { + Some(Ok(e)) => e, + Some(Err(e)) => { + log::warn!("network error: {e}"); + continue; } + None => break, + }; + handle_udp_rx(&server, &capture_tx, &mut emulation, &sender_tx, &mut last_ignored, udp_event, &timer_tx).await?; + } + emulate_event = rx.recv() => { + match emulate_event { + Some(e) => match e { + EmulationEvent::Create(h) => emulation.create(h).await, + EmulationEvent::Destroy(h) => emulation.destroy(h).await, + EmulationEvent::ReleaseKeys(c) => release_keys(&server, &mut emulation, c).await?, + EmulationEvent::Terminate => break, + }, + None => break, } } } + } - // release potentially still pressed keys - let clients = server - .client_manager - .borrow() - .get_client_states() - .map(|(h, _)| h) - .collect::>(); - for client in clients { - release_keys(&server, &mut emulate, client).await?; - } + // release potentially still pressed keys + release_all_keys(&server, &mut emulation).await?; - anyhow::Ok(()) - }); - Ok((emulate_task, tx)) + Ok(()) } async fn handle_udp_rx( @@ -89,38 +115,15 @@ async fn handle_udp_rx( ) -> Result<(), EmulationError> { let (event, addr) = event; - // get handle for addr - let handle = match server.client_manager.borrow().get_client(addr) { - Some(a) => a, - None => { - if last_ignored.is_none() || last_ignored.is_some() && last_ignored.unwrap() != addr { - log::warn!("ignoring events from client {addr}"); - last_ignored.replace(addr); - } - return Ok(()); - } + log::trace!("{:20} <-<-<-<------ {addr}", event.to_string()); + + // get client handle for addr + let Some(handle) = + activate_client_if_exists(&mut server.client_manager.borrow_mut(), addr, last_ignored) + else { + return Ok(()); }; - // next event can be logged as ignored again - last_ignored.take(); - - log::trace!("{:20} <-<-<-<------ {addr} ({handle})", event.to_string()); - { - let mut client_manager = server.client_manager.borrow_mut(); - let client_state = match client_manager.get_mut(handle) { - Some((_, s)) => s, - None => { - log::error!("unknown handle"); - return Ok(()); - } - }; - - // reset ttl for client and - client_state.alive = true; - // set addr as new default for this client - client_state.active_addr = Some(addr); - } - match (event, addr) { (Event::Pong(), _) => { /* ignore pong events */ } (Event::Ping(), addr) => { @@ -148,30 +151,22 @@ async fn handle_udp_rx( } } State::Receiving => { - let mut ignore_event = false; - if let Event::Keyboard(KeyboardEvent::Key { - time: _, - key, - state, - }) = event - { - let mut client_manager = server.client_manager.borrow_mut(); - let client_state = if let Some((_, s)) = client_manager.get_mut(handle) { - s + let ignore_event = + if let Event::Keyboard(KeyboardEvent::Key { key, state, .. }) = event { + let (ignore_event, restart_timer) = update_client_keys( + &mut server.client_manager.borrow_mut(), + handle, + key, + state, + ); + // restart timer if necessary + if restart_timer { + let _ = timer_tx.try_send(()); + } + ignore_event } else { - log::error!("unknown handle"); - return Ok(()); + false }; - if state == 0 { - // ignore release event if key not pressed - ignore_event = !client_state.pressed_keys.remove(&key); - } else { - // ignore press event if key not released - ignore_event = !client_state.pressed_keys.insert(key); - let _ = timer_tx.try_send(()); - } - } - // ignore double press / release events to // workaround buggy rdp backend. if !ignore_event { // consume event @@ -203,6 +198,22 @@ async fn handle_udp_rx( Ok(()) } +async fn release_all_keys( + server: &Server, + emulation: &mut Box, +) -> Result<(), EmulationError> { + let clients = server + .client_manager + .borrow() + .get_client_states() + .map(|(h, _)| h) + .collect::>(); + for client in clients { + release_keys(server, emulation, client).await?; + } + Ok(()) +} + async fn release_keys( server: &Server, emulate: &mut Box, @@ -237,3 +248,50 @@ async fn release_keys( emulate.consume(event, client).await?; Ok(()) } + +fn activate_client_if_exists( + client_manager: &mut ClientManager, + addr: SocketAddr, + last_ignored: &mut Option, +) -> Option { + let Some(handle) = client_manager.get_client(addr) else { + // log ignored if it is the first event from the client in a series + if last_ignored.is_none() || last_ignored.is_some() && last_ignored.unwrap() != addr { + log::warn!("ignoring events from client {addr}"); + last_ignored.replace(addr); + } + return None; + }; + // next event can be logged as ignored again + last_ignored.take(); + + let (_, client_state) = client_manager.get_mut(handle)?; + + // reset ttl for client + client_state.alive = true; + // set addr as new default for this client + client_state.active_addr = Some(addr); + Some(handle) +} + +fn update_client_keys( + client_manager: &mut ClientManager, + handle: ClientHandle, + key: u32, + state: u8, +) -> (bool, bool) { + let Some(client_state) = client_manager.get_mut(handle).map(|(_, s)| s) else { + return (true, false); + }; + + // ignore double press / release events + let ignore_event = if state == 0 { + // ignore release event if key not pressed + !client_state.pressed_keys.remove(&key) + } else { + // ignore press event if key not released + !client_state.pressed_keys.insert(key) + }; + let restart_timer = !client_state.pressed_keys.is_empty(); + (ignore_event, restart_timer) +} diff --git a/src/server/network_task.rs b/src/server/network_task.rs index cdc9c2a..1e8fb0f 100644 --- a/src/server/network_task.rs +++ b/src/server/network_task.rs @@ -1,6 +1,6 @@ -use std::net::SocketAddr; +use std::{io, net::SocketAddr}; -use anyhow::Result; +use thiserror::Error; use tokio::{ net::UdpSocket, sync::mpsc::{Receiver, Sender}, @@ -8,66 +8,37 @@ use tokio::{ }; use crate::frontend::FrontendEvent; -use input_event::Event; +use input_event::{Event, ProtocolError}; use super::Server; pub async fn new( server: Server, frontend_notify_tx: Sender, -) -> Result<( +) -> io::Result<( JoinHandle<()>, Sender<(Event, SocketAddr)>, - Receiver>, + Receiver>, Sender, )> { // bind the udp socket let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), server.port.get()); let mut socket = UdpSocket::bind(listen_addr).await?; let (receiver_tx, receiver_rx) = tokio::sync::mpsc::channel(32); - let (sender_tx, mut sender_rx) = tokio::sync::mpsc::channel(32); + let (sender_tx, sender_rx) = tokio::sync::mpsc::channel(32); let (port_tx, mut port_rx) = tokio::sync::mpsc::channel(32); let udp_task = tokio::task::spawn_local(async move { + let mut sender_rx = sender_rx; loop { + let udp_receiver = udp_receiver(&socket, &receiver_tx); + let udp_sender = udp_sender(&socket, &mut sender_rx); tokio::select! { - event = receive_event(&socket) => { - let _ = receiver_tx.send(event).await; - } - event = sender_rx.recv() => { - let Some((event, addr)) = event else { - break; - }; - if let Err(e) = send_event(&socket, event, addr) { - log::warn!("udp send failed: {e}"); - }; - } - port = port_rx.recv() => { - let Some(port) = port else { - break; - }; - - if socket.local_addr().unwrap().port() == port { - continue; - } - - let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port); - match UdpSocket::bind(listen_addr).await { - Ok(new_socket) => { - socket = new_socket; - server.port.replace(port); - let _ = frontend_notify_tx.send(FrontendEvent::PortChanged(port, None)).await; - } - Err(e) => { - log::warn!("could not change port: {e}"); - let port = socket.local_addr().unwrap().port(); - let _ = frontend_notify_tx.send(FrontendEvent::PortChanged( - port, - Some(format!("could not change port: {e}")), - )).await; - } - } - + _ = udp_receiver => break, /* channel closed */ + _ = udp_sender => break, /* channel closed */ + port = port_rx.recv() => match port { + Some(port) => update_port(&server, &frontend_notify_tx, &mut socket, port).await, + _ => break, } } } @@ -75,13 +46,73 @@ pub async fn new( Ok((udp_task, sender_tx, receiver_rx, port_tx)) } -async fn receive_event(socket: &UdpSocket) -> Result<(Event, SocketAddr)> { +async fn update_port( + server: &Server, + frontend_chan: &Sender, + socket: &mut UdpSocket, + port: u16, +) { + // if port is the same, we dont need to change it + if socket.local_addr().unwrap().port() == port { + return; + } + + // create new socket + let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port); + let frontend_event = match UdpSocket::bind(listen_addr).await { + Ok(new_socket) => { + *socket = new_socket; + server.port.replace(port); + FrontendEvent::PortChanged(port, None) + } + Err(e) => { + log::warn!("could not change port: {e}"); + let port = socket.local_addr().unwrap().port(); + FrontendEvent::PortChanged(port, Some(format!("could not change port: {e}"))) + } + }; + let _ = frontend_chan.send(frontend_event).await; +} + +async fn udp_receiver( + socket: &UdpSocket, + receiver_tx: &Sender>, +) { + loop { + let event = receive_event(socket).await; + if receiver_tx.send(event).await.is_err() { + break; + } + } +} + +async fn udp_sender(socket: &UdpSocket, rx: &mut Receiver<(Event, SocketAddr)>) { + loop { + let (event, addr) = match rx.recv().await { + Some(e) => e, + None => return, + }; + if let Err(e) = send_event(socket, event, addr) { + log::warn!("udp send failed: {e}"); + }; + } +} + +#[derive(Debug, Error)] +pub(crate) enum NetworkError { + #[error(transparent)] + Protocol(#[from] ProtocolError), + #[error("network error: `{0}`")] + Io(#[from] io::Error), +} + +async fn receive_event(socket: &UdpSocket) -> Result<(Event, SocketAddr), NetworkError> { let mut buf = vec![0u8; 22]; let (_amt, src) = socket.recv_from(&mut buf).await?; Ok((Event::try_from(buf)?, src)) } -fn send_event(sock: &UdpSocket, e: Event, addr: SocketAddr) -> Result { +fn send_event(sock: &UdpSocket, e: Event, addr: SocketAddr) -> Result { log::trace!("{:20} ------>->->-> {addr}", e.to_string()); let data: Vec = (&e).into(); // When udp blocks, we dont want to block the event loop.