From 883d1627dd91da6910408b0ad1806755301a68c4 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Thu, 29 May 2025 16:07:16 +0200 Subject: [PATCH 1/4] First rough implementation of evdev emulation --- Cargo.lock | 54 +++ Cargo.toml | 2 + input-emulation/Cargo.toml | 7 +- input-emulation/src/error.rs | 10 + input-emulation/src/evdev.rs | 731 +++++++++++++++++++++++++++++++++++ input-emulation/src/lib.rs | 11 + 6 files changed, 814 insertions(+), 1 deletion(-) create mode 100644 input-emulation/src/evdev.rs diff --git a/Cargo.lock b/Cargo.lock index 9d297abe9..143f510fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -419,6 +419,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -991,6 +1003,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "evdev" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c10865aeab1a7399b3c2d6046e8dcc7f5227b656f235ed63ef5ee45a47b8f8" +dependencies = [ + "bitvec", + "cfg-if", + "futures-core", + "libc", + "nix 0.29.0", + "tokio", +] + [[package]] name = "event-listener" version = "5.3.1" @@ -1080,6 +1106,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -1861,6 +1893,7 @@ dependencies = [ "async-trait", "bitflags 2.6.0", "core-graphics", + "evdev", "futures", "input-event", "keycode", @@ -2645,6 +2678,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -3124,6 +3163,12 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4002,6 +4047,15 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11" version = "2.21.0" diff --git a/Cargo.toml b/Cargo.toml index 9763eb868..a2930959b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ default = [ "libei_emulation", "rdp_emulation", "x11_emulation", + "evdev_emulation", ] gtk = ["dep:lan-mouse-gtk"] layer_shell_capture = ["input-capture/layer_shell"] @@ -89,6 +90,7 @@ libei_emulation = ["input-event/libei", "input-emulation/libei"] wlroots_emulation = ["input-emulation/wlroots"] x11_emulation = ["input-emulation/x11"] rdp_emulation = ["input-emulation/remote_desktop_portal"] +evdev_emulation = ["input-emulation/evdev"] [package.metadata.bundle] name = "Lan Mouse" diff --git a/input-emulation/Cargo.toml b/input-emulation/Cargo.toml index 009348985..fe641e5e9 100644 --- a/input-emulation/Cargo.toml +++ b/input-emulation/Cargo.toml @@ -43,6 +43,10 @@ ashpd = { version = "0.10", default-features = false, features = [ "tokio", ], optional = true } reis = { version = "0.4", features = ["tokio"], optional = true } +evdev = { version = "0.13.1", features = [ + "stream-trait", + "tokio", +], optional = true } [target.'cfg(target_os="macos")'.dependencies] bitflags = "2.6.0" @@ -61,13 +65,14 @@ windows = { version = "0.58.0", features = [ ] } [features] -default = ["wlroots", "x11", "remote_desktop_portal", "libei"] +default = ["wlroots", "x11", "remote_desktop_portal", "libei", "evdev"] wlroots = [ "dep:wayland-client", "dep:wayland-protocols", "dep:wayland-protocols-wlr", "dep:wayland-protocols-misc", ] +evdev = ["dep:evdev"] x11 = ["dep:x11"] remote_desktop_portal = ["dep:ashpd"] libei = ["dep:reis", "dep:ashpd"] diff --git a/input-emulation/src/error.rs b/input-emulation/src/error.rs index ae7742652..120673708 100644 --- a/input-emulation/src/error.rs +++ b/input-emulation/src/error.rs @@ -51,6 +51,9 @@ pub enum EmulationCreationError { #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[error("libei backend: `{0}`")] Libei(#[from] LibeiEmulationCreationError), + #[cfg(all(target_os = "linux", feature = "evdev"))] + #[error("evdev backend: `{0}`")] + Evdev(#[from] EvdevEmulationCreationError), #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))] #[error("xdg-desktop-portal: `{0}`")] Xdp(#[from] XdpEmulationCreationError), @@ -135,6 +138,13 @@ pub enum LibeiEmulationCreationError { Reis(#[from] reis::Error), } +#[cfg(all(target_os = "linux", feature = "evdev"))] +#[derive(Debug, Error)] +pub enum EvdevEmulationCreationError { + #[error(transparent)] + Io(#[from] std::io::Error), +} + #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))] #[derive(Debug, Error)] pub enum XdpEmulationCreationError { diff --git a/input-emulation/src/evdev.rs b/input-emulation/src/evdev.rs new file mode 100644 index 000000000..3e0a252a9 --- /dev/null +++ b/input-emulation/src/evdev.rs @@ -0,0 +1,731 @@ +use async_trait::async_trait; +use evdev::{uinput::VirtualDevice, AttributeSet, KeyCode, RelativeAxisCode}; +use input_event::{KeyboardEvent, PointerEvent}; + +use crate::{error::EvdevEmulationCreationError, Emulation, EmulationError, EmulationHandle}; + +pub(crate) struct EvdevEmulation { + dev: VirtualDevice, +} + +impl EvdevEmulation { + pub fn new() -> Result { + let dev = VirtualDevice::builder()? + .name("lan-mouse") + // BTN_LEFT, BTN_RIGHT or BTN_WHEEL must be enabled in order to emit mouse movement events + .with_keys(&AttributeSet::from_iter(ALL_KEYS))? + .with_relative_axes(&AttributeSet::from_iter([ + RelativeAxisCode::REL_X, + RelativeAxisCode::REL_Y, + RelativeAxisCode::REL_WHEEL, + RelativeAxisCode::REL_HWHEEL, + ]))? + .build()?; + Ok(EvdevEmulation { dev }) + } +} + +#[async_trait] +impl Emulation for EvdevEmulation { + async fn consume( + &mut self, + event: input_event::Event, + _: EmulationHandle, + ) -> Result<(), EmulationError> { + match event { + input_event::Event::Pointer(p) => match p { + PointerEvent::Motion { time: _, dx, dy } => { + self.dev.emit(&[ + *evdev::RelativeAxisEvent::new(RelativeAxisCode::REL_X, dx.round() as i32), + *evdev::RelativeAxisEvent::new(RelativeAxisCode::REL_Y, dy.round() as i32), + ])?; + } + PointerEvent::Button { + time: _, + button, + state, + } => { + self.dev + .emit(&[*evdev::KeyEvent::new(KeyCode(button as u16), state as i32)])?; + } + PointerEvent::Axis { + time: _, + axis, + value, + } => { + let axis = match axis { + 0 => RelativeAxisCode::REL_WHEEL, + _ => RelativeAxisCode::REL_HWHEEL, + }; + self.dev + .emit(&[*evdev::RelativeAxisEvent::new(axis, value as i32)])?; + } + PointerEvent::AxisDiscrete120 { axis, value } => { + let axis = match axis { + 0 => RelativeAxisCode::REL_WHEEL, + _ => RelativeAxisCode::REL_HWHEEL, + }; + // TODO check that the conversion for `value` is right + self.dev + .emit(&[*evdev::RelativeAxisEvent::new(axis, value as i32 * 120)])?; + } + }, + input_event::Event::Keyboard(k) => match k { + KeyboardEvent::Key { + time: _, + key, + state, + } => { + self.dev + .emit(&[*evdev::KeyEvent::new(KeyCode(key as u16), state as i32)])?; + } + KeyboardEvent::Modifiers { .. } => {} + }, + } + Ok(()) + } + + async fn create(&mut self, _: EmulationHandle) {} + async fn destroy(&mut self, _: EmulationHandle) {} + async fn terminate(&mut self) {} +} + +const ALL_KEYS: [KeyCode; 549] = [ + // KeyCode::KEY_RESERVED, + KeyCode::KEY_ESC, + KeyCode::KEY_1, + KeyCode::KEY_2, + KeyCode::KEY_3, + KeyCode::KEY_4, + KeyCode::KEY_5, + KeyCode::KEY_6, + KeyCode::KEY_7, + KeyCode::KEY_8, + KeyCode::KEY_9, + KeyCode::KEY_0, + KeyCode::KEY_MINUS, + KeyCode::KEY_EQUAL, + KeyCode::KEY_BACKSPACE, + KeyCode::KEY_TAB, + KeyCode::KEY_Q, + KeyCode::KEY_W, + KeyCode::KEY_E, + KeyCode::KEY_R, + KeyCode::KEY_T, + KeyCode::KEY_Y, + KeyCode::KEY_U, + KeyCode::KEY_I, + KeyCode::KEY_O, + KeyCode::KEY_P, + KeyCode::KEY_LEFTBRACE, + KeyCode::KEY_RIGHTBRACE, + KeyCode::KEY_ENTER, + KeyCode::KEY_LEFTCTRL, + KeyCode::KEY_A, + KeyCode::KEY_S, + KeyCode::KEY_D, + KeyCode::KEY_F, + KeyCode::KEY_G, + KeyCode::KEY_H, + KeyCode::KEY_J, + KeyCode::KEY_K, + KeyCode::KEY_L, + KeyCode::KEY_SEMICOLON, + KeyCode::KEY_APOSTROPHE, + KeyCode::KEY_GRAVE, + KeyCode::KEY_LEFTSHIFT, + KeyCode::KEY_BACKSLASH, + KeyCode::KEY_Z, + KeyCode::KEY_X, + KeyCode::KEY_C, + KeyCode::KEY_V, + KeyCode::KEY_B, + KeyCode::KEY_N, + KeyCode::KEY_M, + KeyCode::KEY_COMMA, + KeyCode::KEY_DOT, + KeyCode::KEY_SLASH, + KeyCode::KEY_RIGHTSHIFT, + KeyCode::KEY_KPASTERISK, + KeyCode::KEY_LEFTALT, + KeyCode::KEY_SPACE, + KeyCode::KEY_CAPSLOCK, + KeyCode::KEY_F1, + KeyCode::KEY_F2, + KeyCode::KEY_F3, + KeyCode::KEY_F4, + KeyCode::KEY_F5, + KeyCode::KEY_F6, + KeyCode::KEY_F7, + KeyCode::KEY_F8, + KeyCode::KEY_F9, + KeyCode::KEY_F10, + KeyCode::KEY_NUMLOCK, + KeyCode::KEY_SCROLLLOCK, + KeyCode::KEY_KP7, + KeyCode::KEY_KP8, + KeyCode::KEY_KP9, + KeyCode::KEY_KPMINUS, + KeyCode::KEY_KP4, + KeyCode::KEY_KP5, + KeyCode::KEY_KP6, + KeyCode::KEY_KPPLUS, + KeyCode::KEY_KP1, + KeyCode::KEY_KP2, + KeyCode::KEY_KP3, + KeyCode::KEY_KP0, + KeyCode::KEY_KPDOT, + KeyCode::KEY_ZENKAKUHANKAKU, + KeyCode::KEY_102ND, + KeyCode::KEY_F11, + KeyCode::KEY_F12, + KeyCode::KEY_RO, + KeyCode::KEY_KATAKANA, + KeyCode::KEY_HIRAGANA, + KeyCode::KEY_HENKAN, + KeyCode::KEY_KATAKANAHIRAGANA, + KeyCode::KEY_MUHENKAN, + KeyCode::KEY_KPJPCOMMA, + KeyCode::KEY_KPENTER, + KeyCode::KEY_RIGHTCTRL, + KeyCode::KEY_KPSLASH, + KeyCode::KEY_SYSRQ, + KeyCode::KEY_RIGHTALT, + KeyCode::KEY_LINEFEED, + KeyCode::KEY_HOME, + KeyCode::KEY_UP, + KeyCode::KEY_PAGEUP, + KeyCode::KEY_LEFT, + KeyCode::KEY_RIGHT, + KeyCode::KEY_END, + KeyCode::KEY_DOWN, + KeyCode::KEY_PAGEDOWN, + KeyCode::KEY_INSERT, + KeyCode::KEY_DELETE, + KeyCode::KEY_MACRO, + KeyCode::KEY_MUTE, + KeyCode::KEY_VOLUMEDOWN, + KeyCode::KEY_VOLUMEUP, + KeyCode::KEY_POWER, + KeyCode::KEY_KPEQUAL, + KeyCode::KEY_KPPLUSMINUS, + KeyCode::KEY_PAUSE, + KeyCode::KEY_SCALE, + KeyCode::KEY_KPCOMMA, + KeyCode::KEY_HANGEUL, + // KeyCode::KEY_HANGUEL, + KeyCode::KEY_HANJA, + KeyCode::KEY_YEN, + KeyCode::KEY_LEFTMETA, + KeyCode::KEY_RIGHTMETA, + KeyCode::KEY_COMPOSE, + KeyCode::KEY_STOP, + KeyCode::KEY_AGAIN, + KeyCode::KEY_PROPS, + KeyCode::KEY_UNDO, + KeyCode::KEY_FRONT, + KeyCode::KEY_COPY, + KeyCode::KEY_OPEN, + KeyCode::KEY_PASTE, + KeyCode::KEY_FIND, + KeyCode::KEY_CUT, + KeyCode::KEY_HELP, + KeyCode::KEY_MENU, + KeyCode::KEY_CALC, + KeyCode::KEY_SETUP, + KeyCode::KEY_SLEEP, + KeyCode::KEY_WAKEUP, + KeyCode::KEY_FILE, + KeyCode::KEY_SENDFILE, + KeyCode::KEY_DELETEFILE, + KeyCode::KEY_XFER, + KeyCode::KEY_PROG1, + KeyCode::KEY_PROG2, + KeyCode::KEY_WWW, + KeyCode::KEY_MSDOS, + KeyCode::KEY_COFFEE, + // KeyCode::KEY_SCREENLOCK, + KeyCode::KEY_ROTATE_DISPLAY, + KeyCode::KEY_DIRECTION, + KeyCode::KEY_CYCLEWINDOWS, + KeyCode::KEY_MAIL, + KeyCode::KEY_BOOKMARKS, + KeyCode::KEY_COMPUTER, + KeyCode::KEY_BACK, + KeyCode::KEY_FORWARD, + KeyCode::KEY_CLOSECD, + KeyCode::KEY_EJECTCD, + KeyCode::KEY_EJECTCLOSECD, + KeyCode::KEY_NEXTSONG, + KeyCode::KEY_PLAYPAUSE, + KeyCode::KEY_PREVIOUSSONG, + KeyCode::KEY_STOPCD, + KeyCode::KEY_RECORD, + KeyCode::KEY_REWIND, + KeyCode::KEY_PHONE, + KeyCode::KEY_ISO, + KeyCode::KEY_CONFIG, + KeyCode::KEY_HOMEPAGE, + KeyCode::KEY_REFRESH, + KeyCode::KEY_EXIT, + KeyCode::KEY_MOVE, + KeyCode::KEY_EDIT, + KeyCode::KEY_SCROLLUP, + KeyCode::KEY_SCROLLDOWN, + KeyCode::KEY_KPLEFTPAREN, + KeyCode::KEY_KPRIGHTPAREN, + KeyCode::KEY_NEW, + KeyCode::KEY_REDO, + KeyCode::KEY_F13, + KeyCode::KEY_F14, + KeyCode::KEY_F15, + KeyCode::KEY_F16, + KeyCode::KEY_F17, + KeyCode::KEY_F18, + KeyCode::KEY_F19, + KeyCode::KEY_F20, + KeyCode::KEY_F21, + KeyCode::KEY_F22, + KeyCode::KEY_F23, + KeyCode::KEY_F24, + KeyCode::KEY_PLAYCD, + KeyCode::KEY_PAUSECD, + KeyCode::KEY_PROG3, + KeyCode::KEY_PROG4, + // KeyCode::KEY_ALL_APPLICATIONS, + KeyCode::KEY_DASHBOARD, + KeyCode::KEY_SUSPEND, + KeyCode::KEY_CLOSE, + KeyCode::KEY_PLAY, + KeyCode::KEY_FASTFORWARD, + KeyCode::KEY_BASSBOOST, + KeyCode::KEY_PRINT, + KeyCode::KEY_HP, + KeyCode::KEY_CAMERA, + KeyCode::KEY_SOUND, + KeyCode::KEY_QUESTION, + KeyCode::KEY_EMAIL, + KeyCode::KEY_CHAT, + KeyCode::KEY_SEARCH, + KeyCode::KEY_CONNECT, + KeyCode::KEY_FINANCE, + KeyCode::KEY_SPORT, + KeyCode::KEY_SHOP, + KeyCode::KEY_ALTERASE, + KeyCode::KEY_CANCEL, + KeyCode::KEY_BRIGHTNESSDOWN, + KeyCode::KEY_BRIGHTNESSUP, + KeyCode::KEY_MEDIA, + KeyCode::KEY_SWITCHVIDEOMODE, + KeyCode::KEY_KBDILLUMTOGGLE, + KeyCode::KEY_KBDILLUMDOWN, + KeyCode::KEY_KBDILLUMUP, + KeyCode::KEY_SEND, + KeyCode::KEY_REPLY, + KeyCode::KEY_FORWARDMAIL, + KeyCode::KEY_SAVE, + KeyCode::KEY_DOCUMENTS, + KeyCode::KEY_BATTERY, + KeyCode::KEY_BLUETOOTH, + KeyCode::KEY_WLAN, + KeyCode::KEY_UWB, + KeyCode::KEY_UNKNOWN, + KeyCode::KEY_VIDEO_NEXT, + KeyCode::KEY_VIDEO_PREV, + KeyCode::KEY_BRIGHTNESS_CYCLE, + KeyCode::KEY_BRIGHTNESS_AUTO, + // KeyCode::KEY_BRIGHTNESS_ZERO, + KeyCode::KEY_DISPLAY_OFF, + KeyCode::KEY_WWAN, + // KeyCode::KEY_WIMAX, + KeyCode::KEY_RFKILL, + KeyCode::KEY_MICMUTE, + // KeyCode::BTN_MISC, + KeyCode::BTN_0, + KeyCode::BTN_1, + KeyCode::BTN_2, + KeyCode::BTN_3, + KeyCode::BTN_4, + KeyCode::BTN_5, + KeyCode::BTN_6, + KeyCode::BTN_7, + KeyCode::BTN_8, + KeyCode::BTN_9, + // KeyCode::BTN_MOUSE, + KeyCode::BTN_LEFT, + KeyCode::BTN_RIGHT, + KeyCode::BTN_MIDDLE, + KeyCode::BTN_SIDE, + KeyCode::BTN_EXTRA, + KeyCode::BTN_FORWARD, + KeyCode::BTN_BACK, + KeyCode::BTN_TASK, + // KeyCode::BTN_JOYSTICK, + KeyCode::BTN_TRIGGER, + KeyCode::BTN_THUMB, + KeyCode::BTN_THUMB2, + KeyCode::BTN_TOP, + KeyCode::BTN_TOP2, + KeyCode::BTN_PINKIE, + KeyCode::BTN_BASE, + KeyCode::BTN_BASE2, + KeyCode::BTN_BASE3, + KeyCode::BTN_BASE4, + KeyCode::BTN_BASE5, + KeyCode::BTN_BASE6, + KeyCode::BTN_DEAD, + // KeyCode::BTN_GAMEPAD, + KeyCode::BTN_SOUTH, + // KeyCode::BTN_A, + KeyCode::BTN_EAST, + // KeyCode::BTN_B, + KeyCode::BTN_C, + KeyCode::BTN_NORTH, + // KeyCode::BTN_X, + KeyCode::BTN_WEST, + // KeyCode::BTN_Y, + KeyCode::BTN_Z, + KeyCode::BTN_TL, + KeyCode::BTN_TR, + KeyCode::BTN_TL2, + KeyCode::BTN_TR2, + KeyCode::BTN_SELECT, + KeyCode::BTN_START, + KeyCode::BTN_MODE, + KeyCode::BTN_THUMBL, + KeyCode::BTN_THUMBR, + // KeyCode::BTN_DIGI, + KeyCode::BTN_TOOL_PEN, + KeyCode::BTN_TOOL_RUBBER, + KeyCode::BTN_TOOL_BRUSH, + KeyCode::BTN_TOOL_PENCIL, + KeyCode::BTN_TOOL_AIRBRUSH, + KeyCode::BTN_TOOL_FINGER, + KeyCode::BTN_TOOL_MOUSE, + KeyCode::BTN_TOOL_LENS, + KeyCode::BTN_TOOL_QUINTTAP, + // KeyCode::BTN_STYLUS3, + KeyCode::BTN_TOUCH, + KeyCode::BTN_STYLUS, + KeyCode::BTN_STYLUS2, + KeyCode::BTN_TOOL_DOUBLETAP, + KeyCode::BTN_TOOL_TRIPLETAP, + KeyCode::BTN_TOOL_QUADTAP, + // KeyCode::BTN_WHEEL, + KeyCode::BTN_GEAR_DOWN, + KeyCode::BTN_GEAR_UP, + KeyCode::KEY_OK, + KeyCode::KEY_SELECT, + KeyCode::KEY_GOTO, + KeyCode::KEY_CLEAR, + KeyCode::KEY_POWER2, + KeyCode::KEY_OPTION, + KeyCode::KEY_INFO, + KeyCode::KEY_TIME, + KeyCode::KEY_VENDOR, + KeyCode::KEY_ARCHIVE, + KeyCode::KEY_PROGRAM, + KeyCode::KEY_CHANNEL, + KeyCode::KEY_FAVORITES, + KeyCode::KEY_EPG, + KeyCode::KEY_PVR, + KeyCode::KEY_MHP, + KeyCode::KEY_LANGUAGE, + KeyCode::KEY_TITLE, + KeyCode::KEY_SUBTITLE, + KeyCode::KEY_ANGLE, + KeyCode::KEY_FULL_SCREEN, + KeyCode::KEY_ZOOM, + KeyCode::KEY_MODE, + KeyCode::KEY_KEYBOARD, + // KeyCode::KEY_ASPECT_RATIO, + KeyCode::KEY_SCREEN, + KeyCode::KEY_PC, + KeyCode::KEY_TV, + KeyCode::KEY_TV2, + KeyCode::KEY_VCR, + KeyCode::KEY_VCR2, + KeyCode::KEY_SAT, + KeyCode::KEY_SAT2, + KeyCode::KEY_CD, + KeyCode::KEY_TAPE, + KeyCode::KEY_RADIO, + KeyCode::KEY_TUNER, + KeyCode::KEY_PLAYER, + KeyCode::KEY_TEXT, + KeyCode::KEY_DVD, + KeyCode::KEY_AUX, + KeyCode::KEY_MP3, + KeyCode::KEY_AUDIO, + KeyCode::KEY_VIDEO, + KeyCode::KEY_DIRECTORY, + KeyCode::KEY_LIST, + KeyCode::KEY_MEMO, + KeyCode::KEY_CALENDAR, + KeyCode::KEY_RED, + KeyCode::KEY_GREEN, + KeyCode::KEY_YELLOW, + KeyCode::KEY_BLUE, + KeyCode::KEY_CHANNELUP, + KeyCode::KEY_CHANNELDOWN, + KeyCode::KEY_FIRST, + KeyCode::KEY_LAST, + KeyCode::KEY_AB, + KeyCode::KEY_NEXT, + KeyCode::KEY_RESTART, + KeyCode::KEY_SLOW, + KeyCode::KEY_SHUFFLE, + KeyCode::KEY_BREAK, + KeyCode::KEY_PREVIOUS, + KeyCode::KEY_DIGITS, + KeyCode::KEY_TEEN, + KeyCode::KEY_TWEN, + KeyCode::KEY_VIDEOPHONE, + KeyCode::KEY_GAMES, + KeyCode::KEY_ZOOMIN, + KeyCode::KEY_ZOOMOUT, + KeyCode::KEY_ZOOMRESET, + KeyCode::KEY_WORDPROCESSOR, + KeyCode::KEY_EDITOR, + KeyCode::KEY_SPREADSHEET, + KeyCode::KEY_GRAPHICSEDITOR, + KeyCode::KEY_PRESENTATION, + KeyCode::KEY_DATABASE, + KeyCode::KEY_NEWS, + KeyCode::KEY_VOICEMAIL, + KeyCode::KEY_ADDRESSBOOK, + KeyCode::KEY_MESSENGER, + KeyCode::KEY_DISPLAYTOGGLE, + // KeyCode::KEY_BRIGHTNESS_TOGGLE, + KeyCode::KEY_SPELLCHECK, + KeyCode::KEY_LOGOFF, + KeyCode::KEY_DOLLAR, + KeyCode::KEY_EURO, + KeyCode::KEY_FRAMEBACK, + KeyCode::KEY_FRAMEFORWARD, + KeyCode::KEY_CONTEXT_MENU, + KeyCode::KEY_MEDIA_REPEAT, + KeyCode::KEY_10CHANNELSUP, + KeyCode::KEY_10CHANNELSDOWN, + KeyCode::KEY_IMAGES, + // KeyCode::KEY_NOTIFICATION_CENTER, + KeyCode::KEY_PICKUP_PHONE, + KeyCode::KEY_HANGUP_PHONE, + // KeyCode::KEY_LINK_PHONE, + KeyCode::KEY_DEL_EOL, + KeyCode::KEY_DEL_EOS, + KeyCode::KEY_INS_LINE, + KeyCode::KEY_DEL_LINE, + KeyCode::KEY_FN, + KeyCode::KEY_FN_ESC, + KeyCode::KEY_FN_F1, + KeyCode::KEY_FN_F2, + KeyCode::KEY_FN_F3, + KeyCode::KEY_FN_F4, + KeyCode::KEY_FN_F5, + KeyCode::KEY_FN_F6, + KeyCode::KEY_FN_F7, + KeyCode::KEY_FN_F8, + KeyCode::KEY_FN_F9, + KeyCode::KEY_FN_F10, + KeyCode::KEY_FN_F11, + KeyCode::KEY_FN_F12, + KeyCode::KEY_FN_1, + KeyCode::KEY_FN_2, + KeyCode::KEY_FN_D, + KeyCode::KEY_FN_E, + KeyCode::KEY_FN_F, + KeyCode::KEY_FN_S, + KeyCode::KEY_FN_B, + // KeyCode::KEY_FN_RIGHT_SHIFT, + KeyCode::KEY_BRL_DOT1, + KeyCode::KEY_BRL_DOT2, + KeyCode::KEY_BRL_DOT3, + KeyCode::KEY_BRL_DOT4, + KeyCode::KEY_BRL_DOT5, + KeyCode::KEY_BRL_DOT6, + KeyCode::KEY_BRL_DOT7, + KeyCode::KEY_BRL_DOT8, + KeyCode::KEY_BRL_DOT9, + KeyCode::KEY_BRL_DOT10, + KeyCode::KEY_NUMERIC_0, + KeyCode::KEY_NUMERIC_1, + KeyCode::KEY_NUMERIC_2, + KeyCode::KEY_NUMERIC_3, + KeyCode::KEY_NUMERIC_4, + KeyCode::KEY_NUMERIC_5, + KeyCode::KEY_NUMERIC_6, + KeyCode::KEY_NUMERIC_7, + KeyCode::KEY_NUMERIC_8, + KeyCode::KEY_NUMERIC_9, + KeyCode::KEY_NUMERIC_STAR, + KeyCode::KEY_NUMERIC_POUND, + KeyCode::KEY_NUMERIC_A, + KeyCode::KEY_NUMERIC_B, + KeyCode::KEY_NUMERIC_C, + KeyCode::KEY_NUMERIC_D, + KeyCode::KEY_CAMERA_FOCUS, + KeyCode::KEY_WPS_BUTTON, + KeyCode::KEY_TOUCHPAD_TOGGLE, + KeyCode::KEY_TOUCHPAD_ON, + KeyCode::KEY_TOUCHPAD_OFF, + KeyCode::KEY_CAMERA_ZOOMIN, + KeyCode::KEY_CAMERA_ZOOMOUT, + KeyCode::KEY_CAMERA_UP, + KeyCode::KEY_CAMERA_DOWN, + KeyCode::KEY_CAMERA_LEFT, + KeyCode::KEY_CAMERA_RIGHT, + KeyCode::KEY_ATTENDANT_ON, + KeyCode::KEY_ATTENDANT_OFF, + KeyCode::KEY_ATTENDANT_TOGGLE, + KeyCode::KEY_LIGHTS_TOGGLE, + KeyCode::BTN_DPAD_UP, + KeyCode::BTN_DPAD_DOWN, + KeyCode::BTN_DPAD_LEFT, + KeyCode::BTN_DPAD_RIGHT, + KeyCode::KEY_ALS_TOGGLE, + // KeyCode::KEY_ROTATE_LOCK_TOGGLE, + // KeyCode::KEY_REFRESH_RATE_TOGGLE, + KeyCode::KEY_BUTTONCONFIG, + KeyCode::KEY_TASKMANAGER, + KeyCode::KEY_JOURNAL, + KeyCode::KEY_CONTROLPANEL, + KeyCode::KEY_APPSELECT, + KeyCode::KEY_SCREENSAVER, + KeyCode::KEY_VOICECOMMAND, + KeyCode::KEY_ASSISTANT, + KeyCode::KEY_KBD_LAYOUT_NEXT, + // KeyCode::KEY_EMOJI_PICKER, + // KeyCode::KEY_DICTATE, + // KeyCode::KEY_CAMERA_ACCESS_ENABLE, + // KeyCode::KEY_CAMERA_ACCESS_DISABLE, + // KeyCode::KEY_CAMERA_ACCESS_TOGGLE, + // KeyCode::KEY_ACCESSIBILITY, + // KeyCode::KEY_DO_NOT_DISTURB, + KeyCode::KEY_BRIGHTNESS_MIN, + KeyCode::KEY_BRIGHTNESS_MAX, + KeyCode::KEY_KBDINPUTASSIST_PREV, + KeyCode::KEY_KBDINPUTASSIST_NEXT, + KeyCode::KEY_KBDINPUTASSIST_PREVGROUP, + KeyCode::KEY_KBDINPUTASSIST_NEXTGROUP, + KeyCode::KEY_KBDINPUTASSIST_ACCEPT, + KeyCode::KEY_KBDINPUTASSIST_CANCEL, + KeyCode::KEY_RIGHT_UP, + KeyCode::KEY_RIGHT_DOWN, + KeyCode::KEY_LEFT_UP, + KeyCode::KEY_LEFT_DOWN, + KeyCode::KEY_ROOT_MENU, + KeyCode::KEY_MEDIA_TOP_MENU, + KeyCode::KEY_NUMERIC_11, + KeyCode::KEY_NUMERIC_12, + KeyCode::KEY_AUDIO_DESC, + KeyCode::KEY_3D_MODE, + KeyCode::KEY_NEXT_FAVORITE, + KeyCode::KEY_STOP_RECORD, + KeyCode::KEY_PAUSE_RECORD, + KeyCode::KEY_VOD, + KeyCode::KEY_UNMUTE, + KeyCode::KEY_FASTREVERSE, + KeyCode::KEY_SLOWREVERSE, + KeyCode::KEY_DATA, + KeyCode::KEY_ONSCREEN_KEYBOARD, + KeyCode::KEY_PRIVACY_SCREEN_TOGGLE, + KeyCode::KEY_SELECTIVE_SCREENSHOT, + // KeyCode::KEY_NEXT_ELEMENT, + // KeyCode::KEY_PREVIOUS_ELEMENT, + // KeyCode::KEY_AUTOPILOT_ENGAGE_TOGGLE, + // KeyCode::KEY_MARK_WAYPOINT, + // KeyCode::KEY_SOS, + // KeyCode::KEY_NAV_CHART, + // KeyCode::KEY_FISHING_CHART, + // KeyCode::KEY_SINGLE_RANGE_RADAR, + // KeyCode::KEY_DUAL_RANGE_RADAR, + // KeyCode::KEY_RADAR_OVERLAY, + // KeyCode::KEY_TRADITIONAL_SONAR, + // KeyCode::KEY_CLEARVU_SONAR, + // KeyCode::KEY_SIDEVU_SONAR, + // KeyCode::KEY_NAV_INFO, + // KeyCode::KEY_BRIGHTNESS_MENU, + // KeyCode::KEY_MACRO1, + // KeyCode::KEY_MACRO2, + // KeyCode::KEY_MACRO3, + // KeyCode::KEY_MACRO4, + // KeyCode::KEY_MACRO5, + // KeyCode::KEY_MACRO6, + // KeyCode::KEY_MACRO7, + // KeyCode::KEY_MACRO8, + // KeyCode::KEY_MACRO9, + // KeyCode::KEY_MACRO10, + // KeyCode::KEY_MACRO11, + // KeyCode::KEY_MACRO12, + // KeyCode::KEY_MACRO13, + // KeyCode::KEY_MACRO14, + // KeyCode::KEY_MACRO15, + // KeyCode::KEY_MACRO16, + // KeyCode::KEY_MACRO17, + // KeyCode::KEY_MACRO18, + // KeyCode::KEY_MACRO19, + // KeyCode::KEY_MACRO20, + // KeyCode::KEY_MACRO21, + // KeyCode::KEY_MACRO22, + // KeyCode::KEY_MACRO23, + // KeyCode::KEY_MACRO24, + // KeyCode::KEY_MACRO25, + // KeyCode::KEY_MACRO26, + // KeyCode::KEY_MACRO27, + // KeyCode::KEY_MACRO28, + // KeyCode::KEY_MACRO29, + // KeyCode::KEY_MACRO30, + // KeyCode::KEY_MACRO_RECORD_START, + // KeyCode::KEY_MACRO_RECORD_STOP, + // KeyCode::KEY_MACRO_PRESET_CYCLE, + // KeyCode::KEY_MACRO_PRESET1, + // KeyCode::KEY_MACRO_PRESET2, + // KeyCode::KEY_MACRO_PRESET3, + // KeyCode::KEY_KBD_LCD_MENU1, + // KeyCode::KEY_KBD_LCD_MENU2, + // KeyCode::KEY_KBD_LCD_MENU3, + // KeyCode::KEY_KBD_LCD_MENU4, + // KeyCode::KEY_KBD_LCD_MENU5, + // KeyCode::BTN_TRIGGER_HAPPY, + KeyCode::BTN_TRIGGER_HAPPY1, + KeyCode::BTN_TRIGGER_HAPPY2, + KeyCode::BTN_TRIGGER_HAPPY3, + KeyCode::BTN_TRIGGER_HAPPY4, + KeyCode::BTN_TRIGGER_HAPPY5, + KeyCode::BTN_TRIGGER_HAPPY6, + KeyCode::BTN_TRIGGER_HAPPY7, + KeyCode::BTN_TRIGGER_HAPPY8, + KeyCode::BTN_TRIGGER_HAPPY9, + KeyCode::BTN_TRIGGER_HAPPY10, + KeyCode::BTN_TRIGGER_HAPPY11, + KeyCode::BTN_TRIGGER_HAPPY12, + KeyCode::BTN_TRIGGER_HAPPY13, + KeyCode::BTN_TRIGGER_HAPPY14, + KeyCode::BTN_TRIGGER_HAPPY15, + KeyCode::BTN_TRIGGER_HAPPY16, + KeyCode::BTN_TRIGGER_HAPPY17, + KeyCode::BTN_TRIGGER_HAPPY18, + KeyCode::BTN_TRIGGER_HAPPY19, + KeyCode::BTN_TRIGGER_HAPPY20, + KeyCode::BTN_TRIGGER_HAPPY21, + KeyCode::BTN_TRIGGER_HAPPY22, + KeyCode::BTN_TRIGGER_HAPPY23, + KeyCode::BTN_TRIGGER_HAPPY24, + KeyCode::BTN_TRIGGER_HAPPY25, + KeyCode::BTN_TRIGGER_HAPPY26, + KeyCode::BTN_TRIGGER_HAPPY27, + KeyCode::BTN_TRIGGER_HAPPY28, + KeyCode::BTN_TRIGGER_HAPPY29, + KeyCode::BTN_TRIGGER_HAPPY30, + KeyCode::BTN_TRIGGER_HAPPY31, + KeyCode::BTN_TRIGGER_HAPPY32, + KeyCode::BTN_TRIGGER_HAPPY33, + KeyCode::BTN_TRIGGER_HAPPY34, + KeyCode::BTN_TRIGGER_HAPPY35, + KeyCode::BTN_TRIGGER_HAPPY36, + KeyCode::BTN_TRIGGER_HAPPY37, + KeyCode::BTN_TRIGGER_HAPPY38, + KeyCode::BTN_TRIGGER_HAPPY39, + KeyCode::BTN_TRIGGER_HAPPY40, +]; diff --git a/input-emulation/src/lib.rs b/input-emulation/src/lib.rs index 930695f9a..33d88dceb 100644 --- a/input-emulation/src/lib.rs +++ b/input-emulation/src/lib.rs @@ -20,6 +20,9 @@ mod wlroots; #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))] mod xdg_desktop_portal; +#[cfg(all(target_os = "linux", feature = "evdev"))] +mod evdev; + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] mod libei; @@ -38,6 +41,8 @@ pub enum Backend { Wlroots, #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] Libei, + #[cfg(all(target_os = "linux", feature = "evdev"))] + Evdev, #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))] Xdp, #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] @@ -56,6 +61,8 @@ impl Display for Backend { Backend::Wlroots => write!(f, "wlroots"), #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] Backend::Libei => write!(f, "libei"), + #[cfg(all(target_os = "linux", feature = "evdev"))] + Backend::Evdev => write!(f, "evdev"), #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))] Backend::Xdp => write!(f, "xdg-desktop-portal"), #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] @@ -82,6 +89,8 @@ impl InputEmulation { Backend::Wlroots => Box::new(wlroots::WlrootsEmulation::new()?), #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] Backend::Libei => Box::new(libei::LibeiEmulation::new().await?), + #[cfg(all(target_os = "linux", feature = "evdev"))] + Backend::Evdev => Box::new(evdev::EvdevEmulation::new()?), #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] Backend::X11 => Box::new(x11::X11Emulation::new()?), #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))] @@ -115,6 +124,8 @@ impl InputEmulation { Backend::Libei, #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))] Backend::Xdp, + #[cfg(all(target_os = "linux", feature = "evdev"))] + Backend::Evdev, #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] Backend::X11, #[cfg(windows)] From e9856e2aa59cf8ecc3903b4e85c36306f8476843 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Sun, 1 Jun 2025 13:59:34 +0200 Subject: [PATCH 2/4] Fix CLI backend overrides Co-authored-by: Ferdinand Schober --- src/config.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config.rs b/src/config.rs index 41f442ddc..361bb5e50 100644 --- a/src/config.rs +++ b/src/config.rs @@ -172,6 +172,9 @@ impl From for input_capture::Backend { #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)] pub enum EmulationBackend { + #[cfg(all(target_os = "linux", feature = "evdev_emulation"))] + #[serde(rename = "evdev")] + Evdev, #[cfg(all(unix, feature = "wlroots_emulation", not(target_os = "macos")))] #[serde(rename = "wlroots")] Wlroots, @@ -197,6 +200,8 @@ pub enum EmulationBackend { impl From for input_emulation::Backend { fn from(backend: EmulationBackend) -> Self { match backend { + #[cfg(all(target_os = "linux", feature = "evdev_emulation"))] + EmulationBackend::Evdev => Self::Evdev, #[cfg(all(unix, feature = "wlroots_emulation", not(target_os = "macos")))] EmulationBackend::Wlroots => Self::Wlroots, #[cfg(all(unix, feature = "libei_emulation", not(target_os = "macos")))] @@ -217,6 +222,8 @@ impl From for input_emulation::Backend { impl Display for EmulationBackend { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + #[cfg(all(target_os = "linux", feature = "evdev_emulation"))] + EmulationBackend::Evdev => write!(f, "evdev"), #[cfg(all(unix, feature = "wlroots_emulation", not(target_os = "macos")))] EmulationBackend::Wlroots => write!(f, "wlroots"), #[cfg(all(unix, feature = "libei_emulation", not(target_os = "macos")))] From ab238ba11e0e8e0c12fd4067fedbb0cc2ce84be4 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Sun, 1 Jun 2025 14:11:03 +0200 Subject: [PATCH 3/4] Fix clippy warnings --- input-capture/src/layer_shell.rs | 2 +- input-emulation/src/evdev.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/input-capture/src/layer_shell.rs b/input-capture/src/layer_shell.rs index 17cea5fd4..ab6342a77 100644 --- a/input-capture/src/layer_shell.rs +++ b/input-capture/src/layer_shell.rs @@ -813,7 +813,7 @@ impl Dispatch for State { })), )); } - wl_pointer::Event::Frame {} => { + wl_pointer::Event::Frame => { // TODO properly handle frame events // we simply insert a frame event on the client side // after each event for now diff --git a/input-emulation/src/evdev.rs b/input-emulation/src/evdev.rs index 3e0a252a9..7311cb687 100644 --- a/input-emulation/src/evdev.rs +++ b/input-emulation/src/evdev.rs @@ -67,7 +67,7 @@ impl Emulation for EvdevEmulation { }; // TODO check that the conversion for `value` is right self.dev - .emit(&[*evdev::RelativeAxisEvent::new(axis, value as i32 * 120)])?; + .emit(&[*evdev::RelativeAxisEvent::new(axis, value * 120)])?; } }, input_event::Event::Keyboard(k) => match k { From 1f222fa4b01184098f3cc8e85c404c5081ffd9cb Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Wed, 4 Jun 2025 13:43:43 +0200 Subject: [PATCH 4/4] Fix mouse wheel and use HI_RES The Linux kernel on REL_WHEEL and REL_HWEEL: > These event codes are legacy codes and REL_WHEEL_HI_RES and REL_HWHEEL_HI_RES should be preferred where available. This commit fixes the mouse wheel on my setup. The new constant `WHEEL_SENSITIVITY` might need to become a configuration variable. --- input-emulation/src/evdev.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/input-emulation/src/evdev.rs b/input-emulation/src/evdev.rs index 7311cb687..bbb656214 100644 --- a/input-emulation/src/evdev.rs +++ b/input-emulation/src/evdev.rs @@ -4,6 +4,8 @@ use input_event::{KeyboardEvent, PointerEvent}; use crate::{error::EvdevEmulationCreationError, Emulation, EmulationError, EmulationHandle}; +const WHEEL_SENSITIVITY: f64 = 3.0; + pub(crate) struct EvdevEmulation { dev: VirtualDevice, } @@ -19,6 +21,8 @@ impl EvdevEmulation { RelativeAxisCode::REL_Y, RelativeAxisCode::REL_WHEEL, RelativeAxisCode::REL_HWHEEL, + RelativeAxisCode::REL_WHEEL_HI_RES, + RelativeAxisCode::REL_HWHEEL_HI_RES, ]))? .build()?; Ok(EvdevEmulation { dev }) @@ -54,20 +58,19 @@ impl Emulation for EvdevEmulation { value, } => { let axis = match axis { - 0 => RelativeAxisCode::REL_WHEEL, - _ => RelativeAxisCode::REL_HWHEEL, + 0 => RelativeAxisCode::REL_WHEEL_HI_RES, + _ => RelativeAxisCode::REL_HWHEEL_HI_RES, }; self.dev - .emit(&[*evdev::RelativeAxisEvent::new(axis, value as i32)])?; + .emit(&[*evdev::RelativeAxisEvent::new(axis, (value * WHEEL_SENSITIVITY).round() as i32)])?; } PointerEvent::AxisDiscrete120 { axis, value } => { let axis = match axis { - 0 => RelativeAxisCode::REL_WHEEL, - _ => RelativeAxisCode::REL_HWHEEL, + 0 => RelativeAxisCode::REL_WHEEL_HI_RES, + _ => RelativeAxisCode::REL_HWHEEL_HI_RES, }; - // TODO check that the conversion for `value` is right self.dev - .emit(&[*evdev::RelativeAxisEvent::new(axis, value * 120)])?; + .emit(&[*evdev::RelativeAxisEvent::new(axis, value)])?; } }, input_event::Event::Keyboard(k) => match k {