From e4cbd98859e38402c6827b1545b2d50b35f4a17a Mon Sep 17 00:00:00 2001 From: Unknow Date: Mon, 12 Jan 2026 00:13:11 +0100 Subject: [PATCH 1/8] Add support for windows as a client --- Cargo.lock | 22 +- rkvm-client/src/client.rs | 2 +- rkvm-input/Cargo.toml | 11 +- rkvm-input/src/abs.rs | 135 --- rkvm-input/src/interceptor.rs | 291 +----- rkvm-input/src/key.rs | 24 - rkvm-input/src/key/button.rs | 236 ----- rkvm-input/src/key/keyboard.rs | 984 ------------------ rkvm-input/src/lib.rs | 10 +- rkvm-input/src/linux/abs_convert.rs | 136 +++ rkvm-input/src/linux/button_convert.rs | 237 +++++ rkvm-input/src/{ => linux}/evdev.rs | 0 rkvm-input/src/{ => linux}/glue.rs | 1 - rkvm-input/src/linux/interceptor.rs | 324 ++++++ .../src/{ => linux}/interceptor/caps.rs | 65 +- rkvm-input/src/linux/key_convert.rs | 24 + rkvm-input/src/linux/keyboard_convert.rs | 984 ++++++++++++++++++ rkvm-input/src/linux/mod.rs | 14 + rkvm-input/src/linux/monitor.rs | 99 ++ rkvm-input/src/{ => linux}/registry.rs | 0 rkvm-input/src/linux/rel_convert.rs | 45 + rkvm-input/src/linux/sync_convert.rs | 25 + rkvm-input/src/{ => linux}/uinput.rs | 0 rkvm-input/src/linux/writer.rs | 296 ++++++ rkvm-input/src/monitor.rs | 103 +- rkvm-input/src/rel.rs | 47 +- rkvm-input/src/sync.rs | 26 - rkvm-input/src/windows/interceptor.rs | 45 + rkvm-input/src/windows/mod.rs | 4 + rkvm-input/src/windows/monitor.rs | 91 ++ rkvm-input/src/windows/writer.rs | 261 +++++ rkvm-input/src/writer.rs | 292 +----- rkvm-server/src/server.rs | 9 +- 33 files changed, 2693 insertions(+), 2150 deletions(-) create mode 100644 rkvm-input/src/linux/abs_convert.rs create mode 100644 rkvm-input/src/linux/button_convert.rs rename rkvm-input/src/{ => linux}/evdev.rs (100%) rename rkvm-input/src/{ => linux}/glue.rs (98%) create mode 100644 rkvm-input/src/linux/interceptor.rs rename rkvm-input/src/{ => linux}/interceptor/caps.rs (69%) create mode 100644 rkvm-input/src/linux/key_convert.rs create mode 100644 rkvm-input/src/linux/keyboard_convert.rs create mode 100644 rkvm-input/src/linux/mod.rs create mode 100644 rkvm-input/src/linux/monitor.rs rename rkvm-input/src/{ => linux}/registry.rs (100%) create mode 100644 rkvm-input/src/linux/rel_convert.rs create mode 100644 rkvm-input/src/linux/sync_convert.rs rename rkvm-input/src/{ => linux}/uinput.rs (100%) create mode 100644 rkvm-input/src/linux/writer.rs create mode 100644 rkvm-input/src/windows/interceptor.rs create mode 100644 rkvm-input/src/windows/mod.rs create mode 100644 rkvm-input/src/windows/monitor.rs create mode 100644 rkvm-input/src/windows/writer.rs diff --git a/Cargo.lock b/Cargo.lock index 12f950d..a8b88e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -831,6 +831,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "windows", ] [[package]] @@ -1291,6 +1292,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/rkvm-client/src/client.rs b/rkvm-client/src/client.rs index 9cefbf1..ec9dea4 100644 --- a/rkvm-client/src/client.rs +++ b/rkvm-client/src/client.rs @@ -1,4 +1,4 @@ -use rkvm_input::writer::Writer; +use rkvm_input::writer::{Writer,WriterPlatform,WriterBuilderPlatform}; use rkvm_net::auth::{AuthChallenge, AuthStatus}; use rkvm_net::message::Message; use rkvm_net::version::Version; diff --git a/rkvm-input/Cargo.toml b/rkvm-input/Cargo.toml index 1e2deed..85fa200 100644 --- a/rkvm-input/Cargo.toml +++ b/rkvm-input/Cargo.toml @@ -10,12 +10,19 @@ edition = "2021" serde = { version = "1.0.117", features = ["derive"] } futures = "0.3.8" smallvec = { version = "1.10.0", features = ["serde"] } -inotify = "0.10.0" tokio = { version = "1.0.1", features = ["fs", "io-util", "net", "sync", "rt", "time", "macros"] } -libc = "0.2.77" thiserror = "1.0.40" tracing = "0.1.37" +[target.'cfg(windows)'.dependencies] +windows = { version = "0.52", features = [ + "Win32_UI_Input_KeyboardAndMouse", + "Win32_Foundation" +] } +[target.'cfg(unix)'.dependencies] +inotify = "0.10.0" +libc = "0.2.77" + [build-dependencies] bindgen = "0.65.1" cc = "1.0.83" diff --git a/rkvm-input/src/abs.rs b/rkvm-input/src/abs.rs index 7b38473..aa8464e 100644 --- a/rkvm-input/src/abs.rs +++ b/rkvm-input/src/abs.rs @@ -1,6 +1,3 @@ -use crate::convert::Convert; -use crate::glue; - use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] @@ -54,111 +51,6 @@ pub enum AbsAxis { MtToolY, } -impl Convert for AbsAxis { - type Raw = u16; - - fn from_raw(raw: Self::Raw) -> Option { - let axis = match raw as _ { - glue::ABS_X => Self::X, - glue::ABS_Y => Self::Y, - glue::ABS_Z => Self::Z, - glue::ABS_RX => Self::Rx, - glue::ABS_RY => Self::Ry, - glue::ABS_RZ => Self::Rz, - glue::ABS_THROTTLE => Self::Throttle, - glue::ABS_RUDDER => Self::Rudder, - glue::ABS_WHEEL => Self::Wheel, - glue::ABS_GAS => Self::Gas, - glue::ABS_BRAKE => Self::Brake, - glue::ABS_HAT0X => Self::Hat0X, - glue::ABS_HAT0Y => Self::Hat0Y, - glue::ABS_HAT1X => Self::Hat1X, - glue::ABS_HAT1Y => Self::Hat1Y, - glue::ABS_HAT2X => Self::Hat2X, - glue::ABS_HAT2Y => Self::Hat2Y, - glue::ABS_HAT3X => Self::Hat3X, - glue::ABS_HAT3Y => Self::Hat3Y, - glue::ABS_PRESSURE => Self::Pressure, - glue::ABS_DISTANCE => Self::Distance, - glue::ABS_TILT_X => Self::TiltX, - glue::ABS_TILT_Y => Self::TiltY, - glue::ABS_TOOL_WIDTH => Self::ToolWidth, - glue::ABS_VOLUME => Self::Volume, - #[cfg(have_abs_profile)] - glue::ABS_PROFILE => Self::Profile, - glue::ABS_MISC => Self::Misc, - glue::ABS_MT_SLOT => Self::MtSlot, - glue::ABS_MT_TOUCH_MAJOR => Self::MtTouchMajor, - glue::ABS_MT_TOUCH_MINOR => Self::MtTouchMinor, - glue::ABS_MT_WIDTH_MAJOR => Self::MtWidthMajor, - glue::ABS_MT_WIDTH_MINOR => Self::MtWidthMinor, - glue::ABS_MT_ORIENTATION => Self::MtOrientation, - glue::ABS_MT_POSITION_X => Self::MtPositionX, - glue::ABS_MT_POSITION_Y => Self::MtPositionY, - glue::ABS_MT_BLOB_ID => Self::MtBlobId, - glue::ABS_MT_TRACKING_ID => Self::MtTrackingId, - glue::ABS_MT_PRESSURE => Self::MtPressure, - glue::ABS_MT_DISTANCE => Self::MtDistance, - glue::ABS_MT_TOOL_X => Self::MtToolX, - glue::ABS_MT_TOOL_Y => Self::MtToolY, - _ => return None, - }; - - Some(axis) - } - - fn to_raw(&self) -> Option { - let code = match self { - Self::X => glue::ABS_X, - Self::Y => glue::ABS_Y, - Self::Z => glue::ABS_Z, - Self::Rx => glue::ABS_RX, - Self::Ry => glue::ABS_RY, - Self::Rz => glue::ABS_RZ, - Self::Throttle => glue::ABS_THROTTLE, - Self::Rudder => glue::ABS_RUDDER, - Self::Wheel => glue::ABS_WHEEL, - Self::Gas => glue::ABS_GAS, - Self::Brake => glue::ABS_BRAKE, - Self::Hat0X => glue::ABS_HAT0X, - Self::Hat0Y => glue::ABS_HAT0Y, - Self::Hat1X => glue::ABS_HAT1X, - Self::Hat1Y => glue::ABS_HAT1Y, - Self::Hat2X => glue::ABS_HAT2X, - Self::Hat2Y => glue::ABS_HAT2Y, - Self::Hat3X => glue::ABS_HAT3X, - Self::Hat3Y => glue::ABS_HAT3Y, - Self::Pressure => glue::ABS_PRESSURE, - Self::Distance => glue::ABS_DISTANCE, - Self::TiltX => glue::ABS_TILT_X, - Self::TiltY => glue::ABS_TILT_Y, - Self::ToolWidth => glue::ABS_TOOL_WIDTH, - Self::Volume => glue::ABS_VOLUME, - #[cfg(have_abs_profile)] - Self::Profile => glue::ABS_PROFILE, - #[cfg(not(have_abs_profile))] - Self::Profile => return None, - Self::Misc => glue::ABS_MISC, - Self::MtSlot => glue::ABS_MT_SLOT, - Self::MtTouchMajor => glue::ABS_MT_TOUCH_MAJOR, - Self::MtTouchMinor => glue::ABS_MT_TOUCH_MINOR, - Self::MtWidthMajor => glue::ABS_MT_WIDTH_MAJOR, - Self::MtWidthMinor => glue::ABS_MT_WIDTH_MINOR, - Self::MtOrientation => glue::ABS_MT_ORIENTATION, - Self::MtPositionX => glue::ABS_MT_POSITION_X, - Self::MtPositionY => glue::ABS_MT_POSITION_Y, - Self::MtBlobId => glue::ABS_MT_BLOB_ID, - Self::MtTrackingId => glue::ABS_MT_TRACKING_ID, - Self::MtPressure => glue::ABS_MT_PRESSURE, - Self::MtDistance => glue::ABS_MT_DISTANCE, - Self::MtToolX => glue::ABS_MT_TOOL_X, - Self::MtToolY => glue::ABS_MT_TOOL_Y, - }; - - Some(code as _) - } -} - // See struct input_absinfo. #[derive(Clone, Copy, Deserialize, Serialize, Debug)] pub struct AbsInfo { @@ -176,30 +68,3 @@ pub enum ToolType { Palm, Dial, } - -impl Convert for ToolType { - type Raw = i32; - - fn from_raw(raw: Self::Raw) -> Option { - let r#type = match raw as _ { - glue::MT_TOOL_FINGER => Self::Finger, - glue::MT_TOOL_PEN => Self::Pen, - glue::MT_TOOL_PALM => Self::Palm, - glue::MT_TOOL_DIAL => Self::Dial, - _ => return None, - }; - - Some(r#type) - } - - fn to_raw(&self) -> Option { - let value = match self { - Self::Finger => glue::MT_TOOL_FINGER, - Self::Pen => glue::MT_TOOL_PEN, - Self::Palm => glue::MT_TOOL_PALM, - Self::Dial => glue::MT_TOOL_DIAL, - }; - - Some(value as _) - } -} diff --git a/rkvm-input/src/interceptor.rs b/rkvm-input/src/interceptor.rs index 9194bb1..c119491 100644 --- a/rkvm-input/src/interceptor.rs +++ b/rkvm-input/src/interceptor.rs @@ -1,284 +1,35 @@ -mod caps; - -pub use caps::{AbsCaps, KeyCaps, RelCaps, Repeat}; - -use crate::abs::{AbsAxis, AbsEvent, ToolType}; -use crate::convert::Convert; -use crate::evdev::Evdev; +use crate::abs::{AbsAxis, AbsInfo}; use crate::event::Event; -use crate::glue; -use crate::key::{Key, KeyEvent}; -use crate::registry::{Entry, Handle, Registry}; -use crate::rel::{RelAxis, RelEvent}; -use crate::sync::SyncEvent; -use crate::writer::Writer; +use crate::key::Key; +use crate::rel::RelAxis; -use std::collections::VecDeque; -use std::ffi::CStr; -use std::fs; -use std::io::{Error, ErrorKind}; -use std::mem::MaybeUninit; -use std::path::Path; -use thiserror::Error; -pub struct Interceptor { - evdev: Evdev, - writer: Writer, - // The state of `read` is stored here to make it cancel safe. - events: VecDeque, - writing: Option<(u16, u16, i32)>, - dropped: bool, +use std::collections::{HashMap, HashSet}; +use std::ffi::CStr; +use std::io::Error; - _reader_handle: Handle, - _writer_handle: Handle, +pub struct Repeat { + pub delay: Option, + pub period: Option, } -impl Interceptor { - #[tracing::instrument(fields(path = ?self.writer.path()), skip(self))] - pub async fn read(&mut self) -> Result { - if let Some((r#type, code, value)) = self.writing { - tracing::trace!("Resuming interrupted write"); - - self.writer.write_raw(r#type, code, value).await?; - self.writing = None; - } - - while !matches!(self.events.back(), Some(Event::Sync(SyncEvent::All))) { - let (r#type, code, value) = self.read_raw().await?; - let event = match r#type as _ { - glue::EV_REL if !self.dropped => { - RelAxis::from_raw(code).map(|axis| Event::Rel(RelEvent { axis, value })) - } - glue::EV_ABS if !self.dropped => match code as _ { - glue::ABS_MT_TOOL_TYPE => { - ToolType::from_raw(value).map(|value| AbsEvent::MtToolType { value }) - } - _ => AbsAxis::from_raw(code).map(|axis| AbsEvent::Axis { axis, value }), - } - .map(Event::Abs), - glue::EV_KEY if !self.dropped && (value == 0 || value == 1) => Key::from_raw(code) - .map(|key| { - Event::Key(KeyEvent { - key, - down: value == 1, - }) - }), - glue::EV_SYN => match code as _ { - glue::SYN_REPORT => { - if self.dropped { - self.dropped = false; - continue; - } - - Some(Event::Sync(SyncEvent::All)) - } - glue::SYN_DROPPED => { - tracing::warn!( - "Dropped {} event{}", - self.events.len(), - if self.events.len() == 1 { "" } else { "s" } - ); - - self.events.clear(); - self.dropped = true; - continue; - } - glue::SYN_MT_REPORT if !self.dropped => Some(Event::Sync(SyncEvent::Mt)), - _ => continue, - }, - _ => None, - }; - - if let Some(event) = event { - self.events.push_back(event); - continue; - } - - self.writing = Some((r#type, code, value)); - self.writer.write_raw(r#type, code, value).await?; - self.writing = None; - } - - Ok(self.events.pop_front().unwrap()) - } - - pub async fn write(&mut self, event: &Event) -> Result<(), Error> { - self.writer.write(event).await - } - - pub fn name(&self) -> &CStr { - let name = unsafe { glue::libevdev_get_name(self.evdev.as_ptr()) }; - let name = unsafe { CStr::from_ptr(name) }; - - name - } - - pub fn vendor(&self) -> u16 { - unsafe { glue::libevdev_get_id_vendor(self.evdev.as_ptr()) as _ } - } - - pub fn product(&self) -> u16 { - unsafe { glue::libevdev_get_id_product(self.evdev.as_ptr()) as _ } - } - - pub fn version(&self) -> u16 { - unsafe { glue::libevdev_get_id_version(self.evdev.as_ptr()) as _ } - } - - pub fn rel(&self) -> RelCaps { - RelCaps::new(self) - } - - pub fn abs(&self) -> AbsCaps { - AbsCaps::new(self) - } - - pub fn key(&self) -> KeyCaps { - KeyCaps::new(self) - } +pub trait InterceptorPlatform: Sized { + fn read<'a>(&'a mut self) -> impl std::future::Future> + Send + 'a; + fn write<'a>(&'a mut self, event: &'a Event) -> impl std::future::Future> + Send + 'a; + + fn name(&self) -> &CStr; - pub fn repeat(&self) -> Repeat { - Repeat::new(self) - } + fn vendor(&self) -> u16; - async fn read_raw(&mut self) -> Result<(u16, u16, i32), Error> { - let file = self.evdev.file().unwrap(); + fn product(&self) -> u16; - loop { - let result = file.readable().await?.try_io(|_| { - let mut event = MaybeUninit::uninit(); - let ret = unsafe { - glue::libevdev_next_event( - self.evdev.as_ptr(), - glue::libevdev_read_flag_LIBEVDEV_READ_FLAG_NORMAL, - event.as_mut_ptr(), - ) - }; + fn version(&self) -> u16; - if ret < 0 { - // ENODEV means that the device got disconnected. - // However, ErrorKind doesn't have support for it yet, - // so translate to BrokenPipe here to not introduce - // platform specific code to rkvm-server. - let err = if ret == -libc::ENODEV { - Error::new(ErrorKind::BrokenPipe, "Device disconnected") - } else { - Error::from_raw_os_error(-ret) - }; + fn rel(&self) -> HashSet; - return Err(err); - } - - let event = unsafe { event.assume_init() }; - Ok((event.type_, event.code, event.value)) - }); - - match result { - Ok(result) => return result, - Err(_) => continue, // This means it would block. - } - } - } - - #[tracing::instrument(skip(registry))] - pub(crate) async fn open(path: &Path, registry: &Registry) -> Result { - let evdev = Evdev::open(path).await?; - let metadata = evdev.file().unwrap().get_ref().metadata()?; - - let reader_handle = registry - .register(Entry::from_metadata(&metadata)) - .ok_or(OpenError::NotAppliable)?; - - // "Upon binding to a device or resuming from suspend, a driver must report - // the current switch state. This ensures that the device, kernel, and userspace - // state is in sync." - // We have no way of knowing that. - let sw = unsafe { glue::libevdev_has_event_type(evdev.as_ptr(), glue::EV_SW) }; - if sw == 1 { - return Err(OpenError::NotAppliable); - } - - // Some buggy kernels can report nonsense abs info, so check for it and disable the axes. - for i in 0..glue::ABS_CNT { - let abs_info = unsafe { glue::libevdev_get_abs_info(evdev.as_ptr(), i).as_ref() }; - let abs_info = match abs_info { - Some(abs_info) => abs_info, - None => continue, - }; - - // See Linux source at drivers/input/misc/uinput.c#L408 commit 93f5de5f648d2b1ce3540a4ac71756d4a852dc23. - - let min = abs_info.minimum; - let max = abs_info.maximum; - - if (min != 0 || max != 0) && max < min { - tracing::warn!( - min = %min, - max = max, - axis = i, - "Detected nonsense min and max values for absolute axis, disabling it", - ); - - let ret = - unsafe { glue::libevdev_disable_event_code(evdev.as_ptr(), glue::EV_ABS, i) }; - - if ret < 0 { - return Err(Error::from_raw_os_error(-ret).into()); - } - } - } - - unsafe { - glue::libevdev_set_id_bustype(evdev.as_ptr(), glue::BUS_VIRTUAL as _); - } - - let ret = - unsafe { glue::libevdev_grab(evdev.as_ptr(), glue::libevdev_grab_mode_LIBEVDEV_GRAB) }; - - if ret < 0 { - // We do not use ErrorKind::ResourceBusy because it is a nightly-only API. - let err = if ret == -libc::EBUSY { - tracing::info!( - "Ignored {:?} because it is busy and can not be grabbed", - path - ); - OpenError::NotAppliable - } else { - Error::from_raw_os_error(-ret).into() - }; - - return Err(err); - } - - let writer = Writer::from_evdev(&evdev).await?; - let path = writer - .path() - .ok_or_else(|| Error::new(ErrorKind::Other, "No syspath for writer"))?; - - let metadata = fs::metadata(path)?; - let writer_handle = registry - .register(Entry::from_metadata(&metadata)) - .ok_or_else(|| Error::new(ErrorKind::Other, "Writer already registered"))?; - - Ok(Self { - evdev, - writer, - events: VecDeque::new(), - dropped: false, - writing: None, - - _reader_handle: reader_handle, - _writer_handle: writer_handle, - }) - } -} + fn abs(&self) -> HashMap; -unsafe impl Send for Interceptor {} + fn key(&self) -> HashSet; -#[derive(Error, Debug)] -pub(crate) enum OpenError { - #[error("Not appliable")] - NotAppliable, - #[error(transparent)] - Io(#[from] Error), + fn repeat(&self) -> Repeat; } diff --git a/rkvm-input/src/key.rs b/rkvm-input/src/key.rs index f911e6c..791487d 100644 --- a/rkvm-input/src/key.rs +++ b/rkvm-input/src/key.rs @@ -4,8 +4,6 @@ mod keyboard; pub use button::Button; pub use keyboard::Keyboard; -use crate::convert::Convert; - use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] @@ -20,25 +18,3 @@ pub enum Key { Button(Button), } -impl Convert for Key { - type Raw = u16; - - fn from_raw(code: Self::Raw) -> Option { - if let Some(key) = Keyboard::from_raw(code) { - return Some(Self::Key(key)); - } - - if let Some(button) = Button::from_raw(code) { - return Some(Self::Button(button)); - } - - None - } - - fn to_raw(&self) -> Option { - match self { - Self::Key(key) => key.to_raw(), - Self::Button(button) => button.to_raw(), - } - } -} diff --git a/rkvm-input/src/key/button.rs b/rkvm-input/src/key/button.rs index c3cb0a1..8a6d7a6 100644 --- a/rkvm-input/src/key/button.rs +++ b/rkvm-input/src/key/button.rs @@ -1,5 +1,3 @@ -use crate::convert::Convert; -use crate::glue; use serde::{Deserialize, Serialize}; @@ -115,237 +113,3 @@ pub enum Button { TriggerHappy40, } -impl Convert for Button { - type Raw = u16; - - fn to_raw(&self) -> Option { - let raw = match self { - Self::B0 => glue::BTN_0, - Self::B1 => glue::BTN_1, - Self::B2 => glue::BTN_2, - Self::B3 => glue::BTN_3, - Self::B4 => glue::BTN_4, - Self::B5 => glue::BTN_5, - Self::B6 => glue::BTN_6, - Self::B7 => glue::BTN_7, - Self::B8 => glue::BTN_8, - Self::B9 => glue::BTN_9, - Self::Left => glue::BTN_LEFT, - Self::Right => glue::BTN_RIGHT, - Self::Middle => glue::BTN_MIDDLE, - Self::Side => glue::BTN_SIDE, - Self::Extra => glue::BTN_EXTRA, - Self::Forward => glue::BTN_FORWARD, - Self::Back => glue::BTN_BACK, - Self::Task => glue::BTN_TASK, - Self::Trigger => glue::BTN_TRIGGER, - Self::Thumb => glue::BTN_THUMB, - Self::Thumb2 => glue::BTN_THUMB2, - Self::Top => glue::BTN_TOP, - Self::Top2 => glue::BTN_TOP2, - Self::Pinkie => glue::BTN_PINKIE, - Self::Base => glue::BTN_BASE, - Self::Base2 => glue::BTN_BASE2, - Self::Base3 => glue::BTN_BASE3, - Self::Base4 => glue::BTN_BASE4, - Self::Base5 => glue::BTN_BASE5, - Self::Base6 => glue::BTN_BASE6, - Self::Dead => glue::BTN_DEAD, - Self::South => glue::BTN_SOUTH, - Self::East => glue::BTN_EAST, - Self::C => glue::BTN_C, - Self::North => glue::BTN_NORTH, - Self::West => glue::BTN_WEST, - Self::Z => glue::BTN_Z, - Self::TL => glue::BTN_TL, - Self::Tr => glue::BTN_TR, - Self::Tl2 => glue::BTN_TL2, - Self::Tr2 => glue::BTN_TR2, - Self::Select => glue::BTN_SELECT, - Self::Start => glue::BTN_START, - Self::Mode => glue::BTN_MODE, - Self::ThumbL => glue::BTN_THUMBL, - Self::ThumbR => glue::BTN_THUMBR, - Self::ToolPen => glue::BTN_TOOL_PEN, - Self::ToolRubber => glue::BTN_TOOL_RUBBER, - Self::ToolBrush => glue::BTN_TOOL_BRUSH, - Self::ToolPencil => glue::BTN_TOOL_PENCIL, - Self::ToolAirbrush => glue::BTN_TOOL_AIRBRUSH, - Self::ToolFinger => glue::BTN_TOOL_FINGER, - Self::ToolMouse => glue::BTN_TOOL_MOUSE, - Self::ToolLens => glue::BTN_TOOL_LENS, - Self::QuintTap => glue::BTN_TOOL_QUINTTAP, - Self::Stylus3 => glue::BTN_STYLUS3, - Self::Touch => glue::BTN_TOUCH, - Self::Stylus => glue::BTN_STYLUS, - Self::Stylus2 => glue::BTN_STYLUS2, - Self::DoubleTap => glue::BTN_TOOL_DOUBLETAP, - Self::TripleTap => glue::BTN_TOOL_TRIPLETAP, - Self::QuadTap => glue::BTN_TOOL_QUADTAP, - Self::GearDown => glue::BTN_GEAR_DOWN, - Self::GearUp => glue::BTN_GEAR_UP, - Self::DPadUp => glue::BTN_DPAD_UP, - Self::DPadDown => glue::BTN_DPAD_DOWN, - Self::DPadLeft => glue::BTN_DPAD_LEFT, - Self::DPadRight => glue::BTN_DPAD_RIGHT, - Self::TriggerHappy1 => glue::BTN_TRIGGER_HAPPY1, - Self::TrigerHappy2 => glue::BTN_TRIGGER_HAPPY2, - Self::TriggerHappy3 => glue::BTN_TRIGGER_HAPPY3, - Self::TriggerHappy4 => glue::BTN_TRIGGER_HAPPY4, - Self::TriggerHappy5 => glue::BTN_TRIGGER_HAPPY5, - Self::TriggerHappy6 => glue::BTN_TRIGGER_HAPPY6, - Self::TriggerHappy7 => glue::BTN_TRIGGER_HAPPY7, - Self::TriggerHappy8 => glue::BTN_TRIGGER_HAPPY8, - Self::TriggerHappy9 => glue::BTN_TRIGGER_HAPPY9, - Self::TriggerHappy10 => glue::BTN_TRIGGER_HAPPY10, - Self::TriggerHappy11 => glue::BTN_TRIGGER_HAPPY11, - Self::TriggerHappy12 => glue::BTN_TRIGGER_HAPPY12, - Self::TriggerHappy13 => glue::BTN_TRIGGER_HAPPY13, - Self::TriggerHappy14 => glue::BTN_TRIGGER_HAPPY14, - Self::TriggerHappy15 => glue::BTN_TRIGGER_HAPPY15, - Self::TriggerHappy16 => glue::BTN_TRIGGER_HAPPY16, - Self::TriggerHappy17 => glue::BTN_TRIGGER_HAPPY17, - Self::TriggerHappy18 => glue::BTN_TRIGGER_HAPPY18, - Self::TriggerHappy19 => glue::BTN_TRIGGER_HAPPY19, - Self::TriggerHappy20 => glue::BTN_TRIGGER_HAPPY20, - Self::TriggerHappy21 => glue::BTN_TRIGGER_HAPPY21, - Self::TriggerHappy22 => glue::BTN_TRIGGER_HAPPY22, - Self::TriggerHappy23 => glue::BTN_TRIGGER_HAPPY23, - Self::TriggerHappy24 => glue::BTN_TRIGGER_HAPPY24, - Self::TriggerHappy25 => glue::BTN_TRIGGER_HAPPY25, - Self::TriggerHappy26 => glue::BTN_TRIGGER_HAPPY26, - Self::TriggerHappy27 => glue::BTN_TRIGGER_HAPPY27, - Self::TriggerHappy28 => glue::BTN_TRIGGER_HAPPY28, - Self::TriggerHappy29 => glue::BTN_TRIGGER_HAPPY29, - Self::TriggerHappy30 => glue::BTN_TRIGGER_HAPPY30, - Self::TriggerHappy31 => glue::BTN_TRIGGER_HAPPY31, - Self::TriggerHappy32 => glue::BTN_TRIGGER_HAPPY32, - Self::TriggerHappy33 => glue::BTN_TRIGGER_HAPPY33, - Self::TriggerHappy34 => glue::BTN_TRIGGER_HAPPY34, - Self::TriggerHappy35 => glue::BTN_TRIGGER_HAPPY35, - Self::TriggerHappy36 => glue::BTN_TRIGGER_HAPPY36, - Self::TriggerHappy37 => glue::BTN_TRIGGER_HAPPY37, - Self::TriggerHappy38 => glue::BTN_TRIGGER_HAPPY38, - Self::TriggerHappy39 => glue::BTN_TRIGGER_HAPPY39, - Self::TriggerHappy40 => glue::BTN_TRIGGER_HAPPY40, - }; - - Some(raw as _) - } - - fn from_raw(raw: Self::Raw) -> Option { - let button = match raw as _ { - glue::BTN_0 => Self::B0, - glue::BTN_1 => Self::B1, - glue::BTN_2 => Self::B2, - glue::BTN_3 => Self::B3, - glue::BTN_4 => Self::B4, - glue::BTN_5 => Self::B5, - glue::BTN_6 => Self::B6, - glue::BTN_7 => Self::B7, - glue::BTN_8 => Self::B8, - glue::BTN_9 => Self::B9, - glue::BTN_LEFT => Self::Left, - glue::BTN_RIGHT => Self::Right, - glue::BTN_MIDDLE => Self::Middle, - glue::BTN_SIDE => Self::Side, - glue::BTN_EXTRA => Self::Extra, - glue::BTN_FORWARD => Self::Forward, - glue::BTN_BACK => Self::Back, - glue::BTN_TASK => Self::Task, - glue::BTN_TRIGGER => Self::Trigger, - glue::BTN_THUMB => Self::Thumb, - glue::BTN_THUMB2 => Self::Thumb2, - glue::BTN_TOP => Self::Top, - glue::BTN_TOP2 => Self::Top2, - glue::BTN_PINKIE => Self::Pinkie, - glue::BTN_BASE => Self::Base, - glue::BTN_BASE2 => Self::Base2, - glue::BTN_BASE3 => Self::Base3, - glue::BTN_BASE4 => Self::Base4, - glue::BTN_BASE5 => Self::Base5, - glue::BTN_BASE6 => Self::Base6, - glue::BTN_DEAD => Self::Dead, - glue::BTN_SOUTH => Self::South, - glue::BTN_EAST => Self::East, - glue::BTN_C => Self::C, - glue::BTN_NORTH => Self::North, - glue::BTN_WEST => Self::West, - glue::BTN_Z => Self::Z, - glue::BTN_TL => Self::TL, - glue::BTN_TR => Self::Tr, - glue::BTN_TL2 => Self::Tl2, - glue::BTN_TR2 => Self::Tr2, - glue::BTN_SELECT => Self::Select, - glue::BTN_START => Self::Start, - glue::BTN_MODE => Self::Mode, - glue::BTN_THUMBL => Self::ThumbL, - glue::BTN_THUMBR => Self::ThumbR, - glue::BTN_TOOL_PEN => Self::ToolPen, - glue::BTN_TOOL_RUBBER => Self::ToolRubber, - glue::BTN_TOOL_BRUSH => Self::ToolBrush, - glue::BTN_TOOL_PENCIL => Self::ToolPencil, - glue::BTN_TOOL_AIRBRUSH => Self::ToolAirbrush, - glue::BTN_TOOL_FINGER => Self::ToolFinger, - glue::BTN_TOOL_MOUSE => Self::ToolMouse, - glue::BTN_TOOL_LENS => Self::ToolLens, - glue::BTN_TOOL_QUINTTAP => Self::QuintTap, - glue::BTN_STYLUS3 => Self::Stylus3, - glue::BTN_TOUCH => Self::Touch, - glue::BTN_STYLUS => Self::Stylus, - glue::BTN_STYLUS2 => Self::Stylus2, - glue::BTN_TOOL_DOUBLETAP => Self::DoubleTap, - glue::BTN_TOOL_TRIPLETAP => Self::TripleTap, - glue::BTN_TOOL_QUADTAP => Self::QuadTap, - glue::BTN_GEAR_DOWN => Self::GearDown, - glue::BTN_GEAR_UP => Self::GearUp, - glue::BTN_DPAD_UP => Self::DPadUp, - glue::BTN_DPAD_DOWN => Self::DPadDown, - glue::BTN_DPAD_LEFT => Self::DPadLeft, - glue::BTN_DPAD_RIGHT => Self::DPadRight, - glue::BTN_TRIGGER_HAPPY1 => Self::TriggerHappy1, - glue::BTN_TRIGGER_HAPPY2 => Self::TrigerHappy2, - glue::BTN_TRIGGER_HAPPY3 => Self::TriggerHappy3, - glue::BTN_TRIGGER_HAPPY4 => Self::TriggerHappy4, - glue::BTN_TRIGGER_HAPPY5 => Self::TriggerHappy5, - glue::BTN_TRIGGER_HAPPY6 => Self::TriggerHappy6, - glue::BTN_TRIGGER_HAPPY7 => Self::TriggerHappy7, - glue::BTN_TRIGGER_HAPPY8 => Self::TriggerHappy8, - glue::BTN_TRIGGER_HAPPY9 => Self::TriggerHappy9, - glue::BTN_TRIGGER_HAPPY10 => Self::TriggerHappy10, - glue::BTN_TRIGGER_HAPPY11 => Self::TriggerHappy11, - glue::BTN_TRIGGER_HAPPY12 => Self::TriggerHappy12, - glue::BTN_TRIGGER_HAPPY13 => Self::TriggerHappy13, - glue::BTN_TRIGGER_HAPPY14 => Self::TriggerHappy14, - glue::BTN_TRIGGER_HAPPY15 => Self::TriggerHappy15, - glue::BTN_TRIGGER_HAPPY16 => Self::TriggerHappy16, - glue::BTN_TRIGGER_HAPPY17 => Self::TriggerHappy17, - glue::BTN_TRIGGER_HAPPY18 => Self::TriggerHappy18, - glue::BTN_TRIGGER_HAPPY19 => Self::TriggerHappy19, - glue::BTN_TRIGGER_HAPPY20 => Self::TriggerHappy20, - glue::BTN_TRIGGER_HAPPY21 => Self::TriggerHappy21, - glue::BTN_TRIGGER_HAPPY22 => Self::TriggerHappy22, - glue::BTN_TRIGGER_HAPPY23 => Self::TriggerHappy23, - glue::BTN_TRIGGER_HAPPY24 => Self::TriggerHappy24, - glue::BTN_TRIGGER_HAPPY25 => Self::TriggerHappy25, - glue::BTN_TRIGGER_HAPPY26 => Self::TriggerHappy26, - glue::BTN_TRIGGER_HAPPY27 => Self::TriggerHappy27, - glue::BTN_TRIGGER_HAPPY28 => Self::TriggerHappy28, - glue::BTN_TRIGGER_HAPPY29 => Self::TriggerHappy29, - glue::BTN_TRIGGER_HAPPY30 => Self::TriggerHappy30, - glue::BTN_TRIGGER_HAPPY31 => Self::TriggerHappy31, - glue::BTN_TRIGGER_HAPPY32 => Self::TriggerHappy32, - glue::BTN_TRIGGER_HAPPY33 => Self::TriggerHappy33, - glue::BTN_TRIGGER_HAPPY34 => Self::TriggerHappy34, - glue::BTN_TRIGGER_HAPPY35 => Self::TriggerHappy35, - glue::BTN_TRIGGER_HAPPY36 => Self::TriggerHappy36, - glue::BTN_TRIGGER_HAPPY37 => Self::TriggerHappy37, - glue::BTN_TRIGGER_HAPPY38 => Self::TriggerHappy38, - glue::BTN_TRIGGER_HAPPY39 => Self::TriggerHappy39, - glue::BTN_TRIGGER_HAPPY40 => Self::TriggerHappy40, - _ => return None, - }; - - Some(button) - } -} diff --git a/rkvm-input/src/key/keyboard.rs b/rkvm-input/src/key/keyboard.rs index b391941..73625f9 100644 --- a/rkvm-input/src/key/keyboard.rs +++ b/rkvm-input/src/key/keyboard.rs @@ -1,5 +1,3 @@ -use crate::convert::Convert; -use crate::glue; use serde::{Deserialize, Serialize}; @@ -488,985 +486,3 @@ pub enum Keyboard { ZoomOut, ZoomReset, } - -impl Convert for Keyboard { - type Raw = u16; - - fn to_raw(&self) -> Option { - let raw = match *self { - Keyboard::A => glue::KEY_A, - Keyboard::Ab => glue::KEY_AB, - Keyboard::AddressBook => glue::KEY_ADDRESSBOOK, - Keyboard::Again => glue::KEY_AGAIN, - Keyboard::AlsToggle => glue::KEY_ALS_TOGGLE, - Keyboard::AltErase => glue::KEY_ALTERASE, - Keyboard::Angle => glue::KEY_ANGLE, - Keyboard::Apostrophe => glue::KEY_APOSTROPHE, - Keyboard::Appselect => glue::KEY_APPSELECT, - Keyboard::Archive => glue::KEY_ARCHIVE, - Keyboard::AspectRatio => glue::KEY_ASPECT_RATIO, - Keyboard::Assistant => glue::KEY_ASSISTANT, - Keyboard::AttendantOff => glue::KEY_ATTENDANT_OFF, - Keyboard::AttendantOn => glue::KEY_ATTENDANT_ON, - Keyboard::AttendantToggle => glue::KEY_ATTENDANT_TOGGLE, - Keyboard::Audio => glue::KEY_AUDIO, - Keyboard::AudioDesc => glue::KEY_AUDIO_DESC, - Keyboard::Aux => glue::KEY_AUX, - Keyboard::B => glue::KEY_B, - Keyboard::Back => glue::KEY_BACK, - Keyboard::Backslash => glue::KEY_BACKSLASH, - Keyboard::Backspace => glue::KEY_BACKSPACE, - Keyboard::BassBoost => glue::KEY_BASSBOOST, - Keyboard::Battery => glue::KEY_BATTERY, - Keyboard::Blue => glue::KEY_BLUE, - Keyboard::Bluetooth => glue::KEY_BLUETOOTH, - Keyboard::Bookmarks => glue::KEY_BOOKMARKS, - Keyboard::Break => glue::KEY_BREAK, - Keyboard::BrightnessAuto => glue::KEY_BRIGHTNESS_AUTO, - Keyboard::BrightnessCycle => glue::KEY_BRIGHTNESS_CYCLE, - Keyboard::BrightnessMax => glue::KEY_BRIGHTNESS_MAX, - Keyboard::BrightnessMin => glue::KEY_BRIGHTNESS_MIN, - Keyboard::BrightnessToggle => glue::KEY_BRIGHTNESS_TOGGLE, - Keyboard::BrightnessDown => glue::KEY_BRIGHTNESSDOWN, - Keyboard::BrightnessUp => glue::KEY_BRIGHTNESSUP, - Keyboard::BrlDot1 => glue::KEY_BRL_DOT1, - Keyboard::BrlDot10 => glue::KEY_BRL_DOT10, - Keyboard::BrlDot2 => glue::KEY_BRL_DOT2, - Keyboard::BrlDot3 => glue::KEY_BRL_DOT3, - Keyboard::BrlDot4 => glue::KEY_BRL_DOT4, - Keyboard::BrlDot5 => glue::KEY_BRL_DOT5, - Keyboard::BrlDot6 => glue::KEY_BRL_DOT6, - Keyboard::BrlDot7 => glue::KEY_BRL_DOT7, - Keyboard::BrlDot8 => glue::KEY_BRL_DOT8, - Keyboard::BrlDot9 => glue::KEY_BRL_DOT9, - Keyboard::ButtonConfig => glue::KEY_BUTTONCONFIG, - Keyboard::C => glue::KEY_C, - Keyboard::Calc => glue::KEY_CALC, - Keyboard::Calendar => glue::KEY_CALENDAR, - Keyboard::Camera => glue::KEY_CAMERA, - Keyboard::CameraDown => glue::KEY_CAMERA_DOWN, - Keyboard::CameraFocus => glue::KEY_CAMERA_FOCUS, - Keyboard::CameraLeft => glue::KEY_CAMERA_LEFT, - Keyboard::CameraRight => glue::KEY_CAMERA_RIGHT, - Keyboard::CameraUp => glue::KEY_CAMERA_UP, - Keyboard::CameraZoomIn => glue::KEY_CAMERA_ZOOMIN, - Keyboard::CameraZoomOut => glue::KEY_CAMERA_ZOOMOUT, - Keyboard::Cancel => glue::KEY_CANCEL, - Keyboard::CapsLock => glue::KEY_CAPSLOCK, - Keyboard::Cd => glue::KEY_CD, - Keyboard::Channel => glue::KEY_CHANNEL, - Keyboard::ChannelDown => glue::KEY_CHANNELDOWN, - Keyboard::ChannelUp => glue::KEY_CHANNELUP, - Keyboard::Chat => glue::KEY_CHAT, - Keyboard::Clear => glue::KEY_CLEAR, - Keyboard::Close => glue::KEY_CLOSE, - Keyboard::CloseCd => glue::KEY_CLOSECD, - Keyboard::Coffee => glue::KEY_COFFEE, - Keyboard::Comma => glue::KEY_COMMA, - Keyboard::Compose => glue::KEY_COMPOSE, - Keyboard::Computer => glue::KEY_COMPUTER, - Keyboard::Config => glue::KEY_CONFIG, - Keyboard::Connect => glue::KEY_CONNECT, - Keyboard::ContextMenu => glue::KEY_CONTEXT_MENU, - Keyboard::Controlpanel => glue::KEY_CONTROLPANEL, - Keyboard::Copy => glue::KEY_COPY, - Keyboard::Cut => glue::KEY_CUT, - Keyboard::CycleWindows => glue::KEY_CYCLEWINDOWS, - Keyboard::D => glue::KEY_D, - Keyboard::Dashboard => glue::KEY_DASHBOARD, - Keyboard::Data => glue::KEY_DATA, - Keyboard::Database => glue::KEY_DATABASE, - Keyboard::DelEol => glue::KEY_DEL_EOL, - Keyboard::DelEos => glue::KEY_DEL_EOS, - Keyboard::DelLine => glue::KEY_DEL_LINE, - Keyboard::Delete => glue::KEY_DELETE, - Keyboard::DeleteFile => glue::KEY_DELETEFILE, - Keyboard::Digits => glue::KEY_DIGITS, - Keyboard::Directory => glue::KEY_DIRECTORY, - Keyboard::DisplayOff => glue::KEY_DISPLAY_OFF, - Keyboard::DisplayToggle => glue::KEY_DISPLAYTOGGLE, - Keyboard::Documents => glue::KEY_DOCUMENTS, - Keyboard::Dollar => glue::KEY_DOLLAR, - Keyboard::Dot => glue::KEY_DOT, - Keyboard::Down => glue::KEY_DOWN, - Keyboard::Dvd => glue::KEY_DVD, - Keyboard::E => glue::KEY_E, - Keyboard::Edit => glue::KEY_EDIT, - Keyboard::Editor => glue::KEY_EDITOR, - Keyboard::EjectCd => glue::KEY_EJECTCD, - Keyboard::EjectCloseCd => glue::KEY_EJECTCLOSECD, - Keyboard::Email => glue::KEY_EMAIL, - Keyboard::End => glue::KEY_END, - Keyboard::Enter => glue::KEY_ENTER, - Keyboard::Epg => glue::KEY_EPG, - Keyboard::Equal => glue::KEY_EQUAL, - Keyboard::Esc => glue::KEY_ESC, - Keyboard::Euro => glue::KEY_EURO, - Keyboard::Exit => glue::KEY_EXIT, - Keyboard::F => glue::KEY_F, - Keyboard::F1 => glue::KEY_F1, - Keyboard::F10 => glue::KEY_F10, - Keyboard::F11 => glue::KEY_F11, - Keyboard::F12 => glue::KEY_F12, - Keyboard::F13 => glue::KEY_F13, - Keyboard::F14 => glue::KEY_F14, - Keyboard::F15 => glue::KEY_F15, - Keyboard::F16 => glue::KEY_F16, - Keyboard::F17 => glue::KEY_F17, - Keyboard::F18 => glue::KEY_F18, - Keyboard::F19 => glue::KEY_F19, - Keyboard::F2 => glue::KEY_F2, - Keyboard::F20 => glue::KEY_F20, - Keyboard::F21 => glue::KEY_F21, - Keyboard::F22 => glue::KEY_F22, - Keyboard::F23 => glue::KEY_F23, - Keyboard::F24 => glue::KEY_F24, - Keyboard::F3 => glue::KEY_F3, - Keyboard::F4 => glue::KEY_F4, - Keyboard::F5 => glue::KEY_F5, - Keyboard::F6 => glue::KEY_F6, - Keyboard::F7 => glue::KEY_F7, - Keyboard::F8 => glue::KEY_F8, - Keyboard::F9 => glue::KEY_F9, - Keyboard::FastForward => glue::KEY_FASTFORWARD, - Keyboard::FastReverse => glue::KEY_FASTREVERSE, - Keyboard::Favorites => glue::KEY_FAVORITES, - Keyboard::File => glue::KEY_FILE, - Keyboard::Finance => glue::KEY_FINANCE, - Keyboard::Find => glue::KEY_FIND, - Keyboard::First => glue::KEY_FIRST, - Keyboard::Fn => glue::KEY_FN, - Keyboard::Fn1 => glue::KEY_FN_1, - Keyboard::Fn2 => glue::KEY_FN_2, - Keyboard::FnB => glue::KEY_FN_B, - Keyboard::FnD => glue::KEY_FN_D, - Keyboard::FnE => glue::KEY_FN_E, - Keyboard::FnEsc => glue::KEY_FN_ESC, - Keyboard::FnF => glue::KEY_FN_F, - Keyboard::FnF1 => glue::KEY_FN_F1, - Keyboard::FnF10 => glue::KEY_FN_F10, - Keyboard::FnF11 => glue::KEY_FN_F11, - Keyboard::FnF12 => glue::KEY_FN_F12, - Keyboard::FnF2 => glue::KEY_FN_F2, - Keyboard::FnF3 => glue::KEY_FN_F3, - Keyboard::FnF4 => glue::KEY_FN_F4, - Keyboard::FnF5 => glue::KEY_FN_F5, - Keyboard::FnF6 => glue::KEY_FN_F6, - Keyboard::FnF7 => glue::KEY_FN_F7, - Keyboard::FnF8 => glue::KEY_FN_F8, - Keyboard::FnF9 => glue::KEY_FN_F9, - Keyboard::FnS => glue::KEY_FN_S, - Keyboard::Forward => glue::KEY_FORWARD, - Keyboard::ForwardMail => glue::KEY_FORWARDMAIL, - Keyboard::Frameback => glue::KEY_FRAMEBACK, - Keyboard::FrameForward => glue::KEY_FRAMEFORWARD, - Keyboard::Front => glue::KEY_FRONT, - Keyboard::FullScreen => glue::KEY_FULL_SCREEN, - Keyboard::G => glue::KEY_G, - Keyboard::Games => glue::KEY_GAMES, - Keyboard::Goto => glue::KEY_GOTO, - Keyboard::GraphicsEditor => glue::KEY_GRAPHICSEDITOR, - Keyboard::Grave => glue::KEY_GRAVE, - Keyboard::Green => glue::KEY_GREEN, - Keyboard::H => glue::KEY_H, - Keyboard::Hangeul => glue::KEY_HANGEUL, - Keyboard::Hanja => glue::KEY_HANJA, - Keyboard::Help => glue::KEY_HELP, - Keyboard::Henkan => glue::KEY_HENKAN, - Keyboard::Hiragana => glue::KEY_HIRAGANA, - Keyboard::Home => glue::KEY_HOME, - Keyboard::Homepage => glue::KEY_HOMEPAGE, - Keyboard::Hp => glue::KEY_HP, - Keyboard::I => glue::KEY_I, - Keyboard::Images => glue::KEY_IMAGES, - Keyboard::Info => glue::KEY_INFO, - Keyboard::InsLine => glue::KEY_INS_LINE, - Keyboard::Insert => glue::KEY_INSERT, - Keyboard::Iso => glue::KEY_ISO, - Keyboard::J => glue::KEY_J, - Keyboard::Journal => glue::KEY_JOURNAL, - Keyboard::K => glue::KEY_K, - Keyboard::Katakana => glue::KEY_KATAKANA, - Keyboard::KatakanaHiragana => glue::KEY_KATAKANAHIRAGANA, - Keyboard::KbdLayoutNext => glue::KEY_KBD_LAYOUT_NEXT, - Keyboard::KbdLcdMenu1 => glue::KEY_KBD_LCD_MENU1, - Keyboard::KbdLcdMenu2 => glue::KEY_KBD_LCD_MENU2, - Keyboard::KbdLcdMenu3 => glue::KEY_KBD_LCD_MENU3, - Keyboard::KbdLcdMenu4 => glue::KEY_KBD_LCD_MENU4, - Keyboard::KbdLcdMenu5 => glue::KEY_KBD_LCD_MENU5, - Keyboard::KbdIllumDown => glue::KEY_KBDILLUMDOWN, - Keyboard::KbdIllumToggle => glue::KEY_KBDILLUMTOGGLE, - Keyboard::KbdIllumUp => glue::KEY_KBDILLUMUP, - Keyboard::KbdInputAssistAccept => glue::KEY_KBDINPUTASSIST_ACCEPT, - Keyboard::KbdInputAssistCancel => glue::KEY_KBDINPUTASSIST_CANCEL, - Keyboard::KbdInputAssistNext => glue::KEY_KBDINPUTASSIST_NEXT, - Keyboard::KbdInputAssistNextgroup => glue::KEY_KBDINPUTASSIST_NEXTGROUP, - Keyboard::KbdInputAssistPrev => glue::KEY_KBDINPUTASSIST_PREV, - Keyboard::KbdInputAssistPrevgroup => glue::KEY_KBDINPUTASSIST_PREVGROUP, - Keyboard::Keyboard => glue::KEY_KEYBOARD, - Keyboard::Kp0 => glue::KEY_KP0, - Keyboard::Kp1 => glue::KEY_KP1, - Keyboard::Kp2 => glue::KEY_KP2, - Keyboard::Kp3 => glue::KEY_KP3, - Keyboard::Kp4 => glue::KEY_KP4, - Keyboard::Kp5 => glue::KEY_KP5, - Keyboard::Kp6 => glue::KEY_KP6, - Keyboard::Kp7 => glue::KEY_KP7, - Keyboard::Kp8 => glue::KEY_KP8, - Keyboard::Kp9 => glue::KEY_KP9, - Keyboard::KpAsterisk => glue::KEY_KPASTERISK, - Keyboard::KpComma => glue::KEY_KPCOMMA, - Keyboard::KpDot => glue::KEY_KPDOT, - Keyboard::KpEnter => glue::KEY_KPENTER, - Keyboard::KpEqual => glue::KEY_KPEQUAL, - Keyboard::KpJpComma => glue::KEY_KPJPCOMMA, - Keyboard::KpLeftParen => glue::KEY_KPLEFTPAREN, - Keyboard::KpMinus => glue::KEY_KPMINUS, - Keyboard::KpPlus => glue::KEY_KPPLUS, - Keyboard::KpPlusMinus => glue::KEY_KPPLUSMINUS, - Keyboard::KpRightParen => glue::KEY_KPRIGHTPAREN, - Keyboard::KpSlash => glue::KEY_KPSLASH, - Keyboard::L => glue::KEY_L, - Keyboard::Language => glue::KEY_LANGUAGE, - Keyboard::Last => glue::KEY_LAST, - Keyboard::Left => glue::KEY_LEFT, - Keyboard::LeftDown => glue::KEY_LEFT_DOWN, - Keyboard::LeftUp => glue::KEY_LEFT_UP, - Keyboard::LeftAlt => glue::KEY_LEFTALT, - Keyboard::LeftBrace => glue::KEY_LEFTBRACE, - Keyboard::LeftCtrl => glue::KEY_LEFTCTRL, - Keyboard::LeftMeta => glue::KEY_LEFTMETA, - Keyboard::LeftShift => glue::KEY_LEFTSHIFT, - Keyboard::LightsToggle => glue::KEY_LIGHTS_TOGGLE, - Keyboard::LineFeed => glue::KEY_LINEFEED, - Keyboard::List => glue::KEY_LIST, - Keyboard::LogOff => glue::KEY_LOGOFF, - Keyboard::M => glue::KEY_M, - Keyboard::Macro => glue::KEY_MACRO, - Keyboard::Macro1 => glue::KEY_MACRO1, - Keyboard::Macro10 => glue::KEY_MACRO10, - Keyboard::Macro11 => glue::KEY_MACRO11, - Keyboard::Macro12 => glue::KEY_MACRO12, - Keyboard::Macro13 => glue::KEY_MACRO13, - Keyboard::Macro14 => glue::KEY_MACRO14, - Keyboard::Macro15 => glue::KEY_MACRO15, - Keyboard::Macro16 => glue::KEY_MACRO16, - Keyboard::Macro17 => glue::KEY_MACRO17, - Keyboard::Macro18 => glue::KEY_MACRO18, - Keyboard::Macro19 => glue::KEY_MACRO19, - Keyboard::Macro2 => glue::KEY_MACRO2, - Keyboard::Macro20 => glue::KEY_MACRO20, - Keyboard::Macro21 => glue::KEY_MACRO21, - Keyboard::Macro22 => glue::KEY_MACRO22, - Keyboard::Macro23 => glue::KEY_MACRO23, - Keyboard::Macro24 => glue::KEY_MACRO24, - Keyboard::Macro25 => glue::KEY_MACRO25, - Keyboard::Macro26 => glue::KEY_MACRO26, - Keyboard::Macro27 => glue::KEY_MACRO27, - Keyboard::Macro28 => glue::KEY_MACRO28, - Keyboard::Macro29 => glue::KEY_MACRO29, - Keyboard::Macro3 => glue::KEY_MACRO3, - Keyboard::Macro30 => glue::KEY_MACRO30, - Keyboard::Macro4 => glue::KEY_MACRO4, - Keyboard::Macro5 => glue::KEY_MACRO5, - Keyboard::Macro6 => glue::KEY_MACRO6, - Keyboard::Macro7 => glue::KEY_MACRO7, - Keyboard::Macro8 => glue::KEY_MACRO8, - Keyboard::Macro9 => glue::KEY_MACRO9, - Keyboard::MacroPreset1 => glue::KEY_MACRO_PRESET1, - Keyboard::MacroPreset2 => glue::KEY_MACRO_PRESET2, - Keyboard::MacroPreset3 => glue::KEY_MACRO_PRESET3, - Keyboard::MacroPresetCycle => glue::KEY_MACRO_PRESET_CYCLE, - Keyboard::MacroRecordStart => glue::KEY_MACRO_RECORD_START, - Keyboard::MacroRecordStop => glue::KEY_MACRO_RECORD_STOP, - Keyboard::Mail => glue::KEY_MAIL, - Keyboard::Media => glue::KEY_MEDIA, - Keyboard::MediaRepeat => glue::KEY_MEDIA_REPEAT, - Keyboard::MediaTopMenu => glue::KEY_MEDIA_TOP_MENU, - Keyboard::Memo => glue::KEY_MEMO, - Keyboard::Menu => glue::KEY_MENU, - Keyboard::Messenger => glue::KEY_MESSENGER, - Keyboard::Mhp => glue::KEY_MHP, - Keyboard::MicMute => glue::KEY_MICMUTE, - Keyboard::Minus => glue::KEY_MINUS, - Keyboard::Mode => glue::KEY_MODE, - Keyboard::Move => glue::KEY_MOVE, - Keyboard::Mp3 => glue::KEY_MP3, - Keyboard::MsDos => glue::KEY_MSDOS, - Keyboard::Muhenkan => glue::KEY_MUHENKAN, - Keyboard::Mute => glue::KEY_MUTE, - Keyboard::N => glue::KEY_N, - Keyboard::N0 => glue::KEY_0, - Keyboard::N1 => glue::KEY_1, - Keyboard::N102nd => glue::KEY_102ND, - Keyboard::N10ChannelsDown => glue::KEY_10CHANNELSDOWN, - Keyboard::N10ChannelsUp => glue::KEY_10CHANNELSUP, - Keyboard::N2 => glue::KEY_2, - Keyboard::N3 => glue::KEY_3, - Keyboard::N3dMode => glue::KEY_3D_MODE, - Keyboard::N4 => glue::KEY_4, - Keyboard::N5 => glue::KEY_5, - Keyboard::N6 => glue::KEY_6, - Keyboard::N7 => glue::KEY_7, - Keyboard::N8 => glue::KEY_8, - Keyboard::N9 => glue::KEY_9, - Keyboard::New => glue::KEY_NEW, - Keyboard::News => glue::KEY_NEWS, - Keyboard::Next => glue::KEY_NEXT, - Keyboard::NextFavorite => glue::KEY_NEXT_FAVORITE, - Keyboard::NextSong => glue::KEY_NEXTSONG, - Keyboard::Numeric0 => glue::KEY_NUMERIC_0, - Keyboard::Numeric1 => glue::KEY_NUMERIC_1, - Keyboard::Numeric11 => glue::KEY_NUMERIC_11, - Keyboard::Numeric12 => glue::KEY_NUMERIC_12, - Keyboard::Numeric2 => glue::KEY_NUMERIC_2, - Keyboard::Numeric3 => glue::KEY_NUMERIC_3, - Keyboard::Numeric4 => glue::KEY_NUMERIC_4, - Keyboard::Numeric5 => glue::KEY_NUMERIC_5, - Keyboard::Numeric6 => glue::KEY_NUMERIC_6, - Keyboard::Numeric7 => glue::KEY_NUMERIC_7, - Keyboard::Numeric8 => glue::KEY_NUMERIC_8, - Keyboard::Numeric9 => glue::KEY_NUMERIC_9, - Keyboard::NumericA => glue::KEY_NUMERIC_A, - Keyboard::NumericB => glue::KEY_NUMERIC_B, - Keyboard::NumericC => glue::KEY_NUMERIC_C, - Keyboard::NumericD => glue::KEY_NUMERIC_D, - Keyboard::NumericPound => glue::KEY_NUMERIC_POUND, - Keyboard::NumericStar => glue::KEY_NUMERIC_STAR, - Keyboard::NumLock => glue::KEY_NUMLOCK, - Keyboard::O => glue::KEY_O, - Keyboard::Ok => glue::KEY_OK, - Keyboard::OnscreenKeyboard => glue::KEY_ONSCREEN_KEYBOARD, - Keyboard::Open => glue::KEY_OPEN, - Keyboard::Option => glue::KEY_OPTION, - Keyboard::P => glue::KEY_P, - Keyboard::PageDown => glue::KEY_PAGEDOWN, - Keyboard::PageUp => glue::KEY_PAGEUP, - Keyboard::Paste => glue::KEY_PASTE, - Keyboard::Pause => glue::KEY_PAUSE, - Keyboard::PauseRecord => glue::KEY_PAUSE_RECORD, - Keyboard::PauseCd => glue::KEY_PAUSECD, - Keyboard::Pc => glue::KEY_PC, - Keyboard::Phone => glue::KEY_PHONE, - Keyboard::Play => glue::KEY_PLAY, - Keyboard::PlayCd => glue::KEY_PLAYCD, - Keyboard::Player => glue::KEY_PLAYER, - Keyboard::PlayPause => glue::KEY_PLAYPAUSE, - Keyboard::Power => glue::KEY_POWER, - Keyboard::Power2 => glue::KEY_POWER2, - Keyboard::Presentation => glue::KEY_PRESENTATION, - Keyboard::Previous => glue::KEY_PREVIOUS, - Keyboard::PreviousSong => glue::KEY_PREVIOUSSONG, - Keyboard::Print => glue::KEY_PRINT, - Keyboard::PrivacyScreenToggle => glue::KEY_PRIVACY_SCREEN_TOGGLE, - Keyboard::Prog1 => glue::KEY_PROG1, - Keyboard::Prog2 => glue::KEY_PROG2, - Keyboard::Prog3 => glue::KEY_PROG3, - Keyboard::Prog4 => glue::KEY_PROG4, - Keyboard::Program => glue::KEY_PROGRAM, - Keyboard::Props => glue::KEY_PROPS, - Keyboard::Pvr => glue::KEY_PVR, - Keyboard::Q => glue::KEY_Q, - Keyboard::Question => glue::KEY_QUESTION, - Keyboard::R => glue::KEY_R, - Keyboard::Radio => glue::KEY_RADIO, - Keyboard::Record => glue::KEY_RECORD, - Keyboard::Red => glue::KEY_RED, - Keyboard::Redo => glue::KEY_REDO, - Keyboard::Refresh => glue::KEY_REFRESH, - Keyboard::Reply => glue::KEY_REPLY, - Keyboard::Reserved => glue::KEY_RESERVED, - Keyboard::Restart => glue::KEY_RESTART, - Keyboard::Rewind => glue::KEY_REWIND, - Keyboard::RfKill => glue::KEY_RFKILL, - Keyboard::Right => glue::KEY_RIGHT, - Keyboard::RightDown => glue::KEY_RIGHT_DOWN, - Keyboard::RightUp => glue::KEY_RIGHT_UP, - Keyboard::RightAlt => glue::KEY_RIGHTALT, - Keyboard::RightBrace => glue::KEY_RIGHTBRACE, - Keyboard::RightCtrl => glue::KEY_RIGHTCTRL, - Keyboard::RightMeta => glue::KEY_RIGHTMETA, - Keyboard::RightShift => glue::KEY_RIGHTSHIFT, - Keyboard::Ro => glue::KEY_RO, - Keyboard::RootMenu => glue::KEY_ROOT_MENU, - Keyboard::RotateDisplay => glue::KEY_ROTATE_DISPLAY, - Keyboard::RotateLockToggle => glue::KEY_ROTATE_LOCK_TOGGLE, - Keyboard::S => glue::KEY_S, - Keyboard::Sat => glue::KEY_SAT, - Keyboard::Sat2 => glue::KEY_SAT2, - Keyboard::Save => glue::KEY_SAVE, - Keyboard::Scale => glue::KEY_SCALE, - Keyboard::Screensaver => glue::KEY_SCREENSAVER, - Keyboard::ScrollDown => glue::KEY_SCROLLDOWN, - Keyboard::ScrollLock => glue::KEY_SCROLLLOCK, - Keyboard::ScrollUp => glue::KEY_SCROLLUP, - Keyboard::Search => glue::KEY_SEARCH, - Keyboard::Select => glue::KEY_SELECT, - Keyboard::SelectiveScreenshot => glue::KEY_SELECTIVE_SCREENSHOT, - Keyboard::Semicolon => glue::KEY_SEMICOLON, - Keyboard::Send => glue::KEY_SEND, - Keyboard::SendFile => glue::KEY_SENDFILE, - Keyboard::Setup => glue::KEY_SETUP, - Keyboard::Shop => glue::KEY_SHOP, - Keyboard::Shuffle => glue::KEY_SHUFFLE, - Keyboard::Slash => glue::KEY_SLASH, - Keyboard::Sleep => glue::KEY_SLEEP, - Keyboard::Slow => glue::KEY_SLOW, - Keyboard::SlowReverse => glue::KEY_SLOWREVERSE, - Keyboard::Sound => glue::KEY_SOUND, - Keyboard::Space => glue::KEY_SPACE, - Keyboard::Spellcheck => glue::KEY_SPELLCHECK, - Keyboard::Sport => glue::KEY_SPORT, - Keyboard::Spreadsheet => glue::KEY_SPREADSHEET, - Keyboard::Stop => glue::KEY_STOP, - Keyboard::StopRecord => glue::KEY_STOP_RECORD, - Keyboard::StopCd => glue::KEY_STOPCD, - Keyboard::Subtitle => glue::KEY_SUBTITLE, - Keyboard::Suspend => glue::KEY_SUSPEND, - Keyboard::SwitchVideoMode => glue::KEY_SWITCHVIDEOMODE, - Keyboard::SysRq => glue::KEY_SYSRQ, - Keyboard::T => glue::KEY_T, - Keyboard::Tab => glue::KEY_TAB, - Keyboard::Tape => glue::KEY_TAPE, - Keyboard::TaskManager => glue::KEY_TASKMANAGER, - Keyboard::Teen => glue::KEY_TEEN, - Keyboard::Text => glue::KEY_TEXT, - Keyboard::Time => glue::KEY_TIME, - Keyboard::Title => glue::KEY_TITLE, - Keyboard::TouchpadOff => glue::KEY_TOUCHPAD_OFF, - Keyboard::TouchpadOn => glue::KEY_TOUCHPAD_ON, - Keyboard::TouchpadToggle => glue::KEY_TOUCHPAD_TOGGLE, - Keyboard::Tuner => glue::KEY_TUNER, - Keyboard::Tv => glue::KEY_TV, - Keyboard::Tv2 => glue::KEY_TV2, - Keyboard::Twen => glue::KEY_TWEN, - Keyboard::U => glue::KEY_U, - Keyboard::Undo => glue::KEY_UNDO, - Keyboard::Unknown => glue::KEY_UNKNOWN, - Keyboard::Unmute => glue::KEY_UNMUTE, - Keyboard::Up => glue::KEY_UP, - Keyboard::Uwb => glue::KEY_UWB, - Keyboard::V => glue::KEY_V, - Keyboard::Vcr => glue::KEY_VCR, - Keyboard::Vcr2 => glue::KEY_VCR2, - Keyboard::Vendor => glue::KEY_VENDOR, - Keyboard::Video => glue::KEY_VIDEO, - Keyboard::VideoNext => glue::KEY_VIDEO_NEXT, - Keyboard::VideoPrev => glue::KEY_VIDEO_PREV, - Keyboard::VideoPhone => glue::KEY_VIDEOPHONE, - Keyboard::Vod => glue::KEY_VOD, - Keyboard::VoiceCommand => glue::KEY_VOICECOMMAND, - Keyboard::VoiceMail => glue::KEY_VOICEMAIL, - Keyboard::VolumeDown => glue::KEY_VOLUMEDOWN, - Keyboard::VolumeUp => glue::KEY_VOLUMEUP, - Keyboard::W => glue::KEY_W, - Keyboard::WakeUp => glue::KEY_WAKEUP, - Keyboard::Wlan => glue::KEY_WLAN, - Keyboard::WordProcessor => glue::KEY_WORDPROCESSOR, - Keyboard::WpsButton => glue::KEY_WPS_BUTTON, - Keyboard::Wwan => glue::KEY_WWAN, - Keyboard::Www => glue::KEY_WWW, - Keyboard::X => glue::KEY_X, - Keyboard::Xfer => glue::KEY_XFER, - Keyboard::Y => glue::KEY_Y, - Keyboard::Yellow => glue::KEY_YELLOW, - Keyboard::Yen => glue::KEY_YEN, - Keyboard::Z => glue::KEY_Z, - Keyboard::ZenkakuHankaku => glue::KEY_ZENKAKUHANKAKU, - Keyboard::ZoomIn => glue::KEY_ZOOMIN, - Keyboard::ZoomOut => glue::KEY_ZOOMOUT, - Keyboard::ZoomReset => glue::KEY_ZOOMRESET, - }; - - Some(raw as _) - } - - fn from_raw(raw: Self::Raw) -> Option { - let keyboard = match raw as _ { - glue::KEY_A => Keyboard::A, - glue::KEY_AB => Keyboard::Ab, - glue::KEY_ADDRESSBOOK => Keyboard::AddressBook, - glue::KEY_AGAIN => Keyboard::Again, - glue::KEY_ALS_TOGGLE => Keyboard::AlsToggle, - glue::KEY_ALTERASE => Keyboard::AltErase, - glue::KEY_ANGLE => Keyboard::Angle, - glue::KEY_APOSTROPHE => Keyboard::Apostrophe, - glue::KEY_APPSELECT => Keyboard::Appselect, - glue::KEY_ARCHIVE => Keyboard::Archive, - glue::KEY_ASPECT_RATIO => Keyboard::AspectRatio, - glue::KEY_ASSISTANT => Keyboard::Assistant, - glue::KEY_ATTENDANT_OFF => Keyboard::AttendantOff, - glue::KEY_ATTENDANT_ON => Keyboard::AttendantOn, - glue::KEY_ATTENDANT_TOGGLE => Keyboard::AttendantToggle, - glue::KEY_AUDIO => Keyboard::Audio, - glue::KEY_AUDIO_DESC => Keyboard::AudioDesc, - glue::KEY_AUX => Keyboard::Aux, - glue::KEY_B => Keyboard::B, - glue::KEY_BACK => Keyboard::Back, - glue::KEY_BACKSLASH => Keyboard::Backslash, - glue::KEY_BACKSPACE => Keyboard::Backspace, - glue::KEY_BASSBOOST => Keyboard::BassBoost, - glue::KEY_BATTERY => Keyboard::Battery, - glue::KEY_BLUE => Keyboard::Blue, - glue::KEY_BLUETOOTH => Keyboard::Bluetooth, - glue::KEY_BOOKMARKS => Keyboard::Bookmarks, - glue::KEY_BREAK => Keyboard::Break, - glue::KEY_BRIGHTNESS_AUTO => Keyboard::BrightnessAuto, - glue::KEY_BRIGHTNESS_CYCLE => Keyboard::BrightnessCycle, - glue::KEY_BRIGHTNESS_MAX => Keyboard::BrightnessMax, - glue::KEY_BRIGHTNESS_MIN => Keyboard::BrightnessMin, - glue::KEY_BRIGHTNESS_TOGGLE => Keyboard::BrightnessToggle, - glue::KEY_BRIGHTNESSDOWN => Keyboard::BrightnessDown, - glue::KEY_BRIGHTNESSUP => Keyboard::BrightnessUp, - glue::KEY_BRL_DOT1 => Keyboard::BrlDot1, - glue::KEY_BRL_DOT10 => Keyboard::BrlDot10, - glue::KEY_BRL_DOT2 => Keyboard::BrlDot2, - glue::KEY_BRL_DOT3 => Keyboard::BrlDot3, - glue::KEY_BRL_DOT4 => Keyboard::BrlDot4, - glue::KEY_BRL_DOT5 => Keyboard::BrlDot5, - glue::KEY_BRL_DOT6 => Keyboard::BrlDot6, - glue::KEY_BRL_DOT7 => Keyboard::BrlDot7, - glue::KEY_BRL_DOT8 => Keyboard::BrlDot8, - glue::KEY_BRL_DOT9 => Keyboard::BrlDot9, - glue::KEY_BUTTONCONFIG => Keyboard::ButtonConfig, - glue::KEY_C => Keyboard::C, - glue::KEY_CALC => Keyboard::Calc, - glue::KEY_CALENDAR => Keyboard::Calendar, - glue::KEY_CAMERA => Keyboard::Camera, - glue::KEY_CAMERA_DOWN => Keyboard::CameraDown, - glue::KEY_CAMERA_FOCUS => Keyboard::CameraFocus, - glue::KEY_CAMERA_LEFT => Keyboard::CameraLeft, - glue::KEY_CAMERA_RIGHT => Keyboard::CameraRight, - glue::KEY_CAMERA_UP => Keyboard::CameraUp, - glue::KEY_CAMERA_ZOOMIN => Keyboard::CameraZoomIn, - glue::KEY_CAMERA_ZOOMOUT => Keyboard::CameraZoomOut, - glue::KEY_CANCEL => Keyboard::Cancel, - glue::KEY_CAPSLOCK => Keyboard::CapsLock, - glue::KEY_CD => Keyboard::Cd, - glue::KEY_CHANNEL => Keyboard::Channel, - glue::KEY_CHANNELDOWN => Keyboard::ChannelDown, - glue::KEY_CHANNELUP => Keyboard::ChannelUp, - glue::KEY_CHAT => Keyboard::Chat, - glue::KEY_CLEAR => Keyboard::Clear, - glue::KEY_CLOSE => Keyboard::Close, - glue::KEY_CLOSECD => Keyboard::CloseCd, - glue::KEY_COFFEE => Keyboard::Coffee, - glue::KEY_COMMA => Keyboard::Comma, - glue::KEY_COMPOSE => Keyboard::Compose, - glue::KEY_COMPUTER => Keyboard::Computer, - glue::KEY_CONFIG => Keyboard::Config, - glue::KEY_CONNECT => Keyboard::Connect, - glue::KEY_CONTEXT_MENU => Keyboard::ContextMenu, - glue::KEY_CONTROLPANEL => Keyboard::Controlpanel, - glue::KEY_COPY => Keyboard::Copy, - glue::KEY_CUT => Keyboard::Cut, - glue::KEY_CYCLEWINDOWS => Keyboard::CycleWindows, - glue::KEY_D => Keyboard::D, - glue::KEY_DASHBOARD => Keyboard::Dashboard, - glue::KEY_DATA => Keyboard::Data, - glue::KEY_DATABASE => Keyboard::Database, - glue::KEY_DEL_EOL => Keyboard::DelEol, - glue::KEY_DEL_EOS => Keyboard::DelEos, - glue::KEY_DEL_LINE => Keyboard::DelLine, - glue::KEY_DELETE => Keyboard::Delete, - glue::KEY_DELETEFILE => Keyboard::DeleteFile, - glue::KEY_DIGITS => Keyboard::Digits, - glue::KEY_DIRECTORY => Keyboard::Directory, - glue::KEY_DISPLAY_OFF => Keyboard::DisplayOff, - glue::KEY_DOCUMENTS => Keyboard::Documents, - glue::KEY_DOLLAR => Keyboard::Dollar, - glue::KEY_DOT => Keyboard::Dot, - glue::KEY_DOWN => Keyboard::Down, - glue::KEY_DVD => Keyboard::Dvd, - glue::KEY_E => Keyboard::E, - glue::KEY_EDIT => Keyboard::Edit, - glue::KEY_EDITOR => Keyboard::Editor, - glue::KEY_EJECTCD => Keyboard::EjectCd, - glue::KEY_EJECTCLOSECD => Keyboard::EjectCloseCd, - glue::KEY_EMAIL => Keyboard::Email, - glue::KEY_END => Keyboard::End, - glue::KEY_ENTER => Keyboard::Enter, - glue::KEY_EPG => Keyboard::Epg, - glue::KEY_EQUAL => Keyboard::Equal, - glue::KEY_ESC => Keyboard::Esc, - glue::KEY_EURO => Keyboard::Euro, - glue::KEY_EXIT => Keyboard::Exit, - glue::KEY_F => Keyboard::F, - glue::KEY_F1 => Keyboard::F1, - glue::KEY_F10 => Keyboard::F10, - glue::KEY_F11 => Keyboard::F11, - glue::KEY_F12 => Keyboard::F12, - glue::KEY_F13 => Keyboard::F13, - glue::KEY_F14 => Keyboard::F14, - glue::KEY_F15 => Keyboard::F15, - glue::KEY_F16 => Keyboard::F16, - glue::KEY_F17 => Keyboard::F17, - glue::KEY_F18 => Keyboard::F18, - glue::KEY_F19 => Keyboard::F19, - glue::KEY_F2 => Keyboard::F2, - glue::KEY_F20 => Keyboard::F20, - glue::KEY_F21 => Keyboard::F21, - glue::KEY_F22 => Keyboard::F22, - glue::KEY_F23 => Keyboard::F23, - glue::KEY_F24 => Keyboard::F24, - glue::KEY_F3 => Keyboard::F3, - glue::KEY_F4 => Keyboard::F4, - glue::KEY_F5 => Keyboard::F5, - glue::KEY_F6 => Keyboard::F6, - glue::KEY_F7 => Keyboard::F7, - glue::KEY_F8 => Keyboard::F8, - glue::KEY_F9 => Keyboard::F9, - glue::KEY_FASTFORWARD => Keyboard::FastForward, - glue::KEY_FASTREVERSE => Keyboard::FastReverse, - glue::KEY_FAVORITES => Keyboard::Favorites, - glue::KEY_FILE => Keyboard::File, - glue::KEY_FINANCE => Keyboard::Finance, - glue::KEY_FIND => Keyboard::Find, - glue::KEY_FIRST => Keyboard::First, - glue::KEY_FN => Keyboard::Fn, - glue::KEY_FN_1 => Keyboard::Fn1, - glue::KEY_FN_2 => Keyboard::Fn2, - glue::KEY_FN_B => Keyboard::FnB, - glue::KEY_FN_D => Keyboard::FnD, - glue::KEY_FN_E => Keyboard::FnE, - glue::KEY_FN_ESC => Keyboard::FnEsc, - glue::KEY_FN_F => Keyboard::FnF, - glue::KEY_FN_F1 => Keyboard::FnF1, - glue::KEY_FN_F10 => Keyboard::FnF10, - glue::KEY_FN_F11 => Keyboard::FnF11, - glue::KEY_FN_F12 => Keyboard::FnF12, - glue::KEY_FN_F2 => Keyboard::FnF2, - glue::KEY_FN_F3 => Keyboard::FnF3, - glue::KEY_FN_F4 => Keyboard::FnF4, - glue::KEY_FN_F5 => Keyboard::FnF5, - glue::KEY_FN_F6 => Keyboard::FnF6, - glue::KEY_FN_F7 => Keyboard::FnF7, - glue::KEY_FN_F8 => Keyboard::FnF8, - glue::KEY_FN_F9 => Keyboard::FnF9, - glue::KEY_FN_S => Keyboard::FnS, - glue::KEY_FORWARD => Keyboard::Forward, - glue::KEY_FORWARDMAIL => Keyboard::ForwardMail, - glue::KEY_FRAMEBACK => Keyboard::Frameback, - glue::KEY_FRAMEFORWARD => Keyboard::FrameForward, - glue::KEY_FRONT => Keyboard::Front, - glue::KEY_FULL_SCREEN => Keyboard::FullScreen, - glue::KEY_G => Keyboard::G, - glue::KEY_GAMES => Keyboard::Games, - glue::KEY_GOTO => Keyboard::Goto, - glue::KEY_GRAPHICSEDITOR => Keyboard::GraphicsEditor, - glue::KEY_GRAVE => Keyboard::Grave, - glue::KEY_GREEN => Keyboard::Green, - glue::KEY_H => Keyboard::H, - glue::KEY_HANGEUL => Keyboard::Hangeul, - glue::KEY_HANJA => Keyboard::Hanja, - glue::KEY_HELP => Keyboard::Help, - glue::KEY_HENKAN => Keyboard::Henkan, - glue::KEY_HIRAGANA => Keyboard::Hiragana, - glue::KEY_HOME => Keyboard::Home, - glue::KEY_HOMEPAGE => Keyboard::Homepage, - glue::KEY_HP => Keyboard::Hp, - glue::KEY_I => Keyboard::I, - glue::KEY_IMAGES => Keyboard::Images, - glue::KEY_INFO => Keyboard::Info, - glue::KEY_INS_LINE => Keyboard::InsLine, - glue::KEY_INSERT => Keyboard::Insert, - glue::KEY_ISO => Keyboard::Iso, - glue::KEY_J => Keyboard::J, - glue::KEY_JOURNAL => Keyboard::Journal, - glue::KEY_K => Keyboard::K, - glue::KEY_KATAKANA => Keyboard::Katakana, - glue::KEY_KATAKANAHIRAGANA => Keyboard::KatakanaHiragana, - glue::KEY_KBD_LAYOUT_NEXT => Keyboard::KbdLayoutNext, - glue::KEY_KBD_LCD_MENU1 => Keyboard::KbdLcdMenu1, - glue::KEY_KBD_LCD_MENU2 => Keyboard::KbdLcdMenu2, - glue::KEY_KBD_LCD_MENU3 => Keyboard::KbdLcdMenu3, - glue::KEY_KBD_LCD_MENU4 => Keyboard::KbdLcdMenu4, - glue::KEY_KBD_LCD_MENU5 => Keyboard::KbdLcdMenu5, - glue::KEY_KBDILLUMDOWN => Keyboard::KbdIllumDown, - glue::KEY_KBDILLUMTOGGLE => Keyboard::KbdIllumToggle, - glue::KEY_KBDILLUMUP => Keyboard::KbdIllumUp, - glue::KEY_KBDINPUTASSIST_ACCEPT => Keyboard::KbdInputAssistAccept, - glue::KEY_KBDINPUTASSIST_CANCEL => Keyboard::KbdInputAssistCancel, - glue::KEY_KBDINPUTASSIST_NEXT => Keyboard::KbdInputAssistNext, - glue::KEY_KBDINPUTASSIST_NEXTGROUP => Keyboard::KbdInputAssistNextgroup, - glue::KEY_KBDINPUTASSIST_PREV => Keyboard::KbdInputAssistPrev, - glue::KEY_KBDINPUTASSIST_PREVGROUP => Keyboard::KbdInputAssistPrevgroup, - glue::KEY_KEYBOARD => Keyboard::Keyboard, - glue::KEY_KP0 => Keyboard::Kp0, - glue::KEY_KP1 => Keyboard::Kp1, - glue::KEY_KP2 => Keyboard::Kp2, - glue::KEY_KP3 => Keyboard::Kp3, - glue::KEY_KP4 => Keyboard::Kp4, - glue::KEY_KP5 => Keyboard::Kp5, - glue::KEY_KP6 => Keyboard::Kp6, - glue::KEY_KP7 => Keyboard::Kp7, - glue::KEY_KP8 => Keyboard::Kp8, - glue::KEY_KP9 => Keyboard::Kp9, - glue::KEY_KPASTERISK => Keyboard::KpAsterisk, - glue::KEY_KPCOMMA => Keyboard::KpComma, - glue::KEY_KPDOT => Keyboard::KpDot, - glue::KEY_KPENTER => Keyboard::KpEnter, - glue::KEY_KPEQUAL => Keyboard::KpEqual, - glue::KEY_KPJPCOMMA => Keyboard::KpJpComma, - glue::KEY_KPLEFTPAREN => Keyboard::KpLeftParen, - glue::KEY_KPMINUS => Keyboard::KpMinus, - glue::KEY_KPPLUS => Keyboard::KpPlus, - glue::KEY_KPPLUSMINUS => Keyboard::KpPlusMinus, - glue::KEY_KPRIGHTPAREN => Keyboard::KpRightParen, - glue::KEY_KPSLASH => Keyboard::KpSlash, - glue::KEY_L => Keyboard::L, - glue::KEY_LANGUAGE => Keyboard::Language, - glue::KEY_LAST => Keyboard::Last, - glue::KEY_LEFT => Keyboard::Left, - glue::KEY_LEFT_DOWN => Keyboard::LeftDown, - glue::KEY_LEFT_UP => Keyboard::LeftUp, - glue::KEY_LEFTALT => Keyboard::LeftAlt, - glue::KEY_LEFTBRACE => Keyboard::LeftBrace, - glue::KEY_LEFTCTRL => Keyboard::LeftCtrl, - glue::KEY_LEFTMETA => Keyboard::LeftMeta, - glue::KEY_LEFTSHIFT => Keyboard::LeftShift, - glue::KEY_LIGHTS_TOGGLE => Keyboard::LightsToggle, - glue::KEY_LINEFEED => Keyboard::LineFeed, - glue::KEY_LIST => Keyboard::List, - glue::KEY_LOGOFF => Keyboard::LogOff, - glue::KEY_M => Keyboard::M, - glue::KEY_MACRO => Keyboard::Macro, - glue::KEY_MACRO1 => Keyboard::Macro1, - glue::KEY_MACRO10 => Keyboard::Macro10, - glue::KEY_MACRO11 => Keyboard::Macro11, - glue::KEY_MACRO12 => Keyboard::Macro12, - glue::KEY_MACRO13 => Keyboard::Macro13, - glue::KEY_MACRO14 => Keyboard::Macro14, - glue::KEY_MACRO15 => Keyboard::Macro15, - glue::KEY_MACRO16 => Keyboard::Macro16, - glue::KEY_MACRO17 => Keyboard::Macro17, - glue::KEY_MACRO18 => Keyboard::Macro18, - glue::KEY_MACRO19 => Keyboard::Macro19, - glue::KEY_MACRO2 => Keyboard::Macro2, - glue::KEY_MACRO20 => Keyboard::Macro20, - glue::KEY_MACRO21 => Keyboard::Macro21, - glue::KEY_MACRO22 => Keyboard::Macro22, - glue::KEY_MACRO23 => Keyboard::Macro23, - glue::KEY_MACRO24 => Keyboard::Macro24, - glue::KEY_MACRO25 => Keyboard::Macro25, - glue::KEY_MACRO26 => Keyboard::Macro26, - glue::KEY_MACRO27 => Keyboard::Macro27, - glue::KEY_MACRO28 => Keyboard::Macro28, - glue::KEY_MACRO29 => Keyboard::Macro29, - glue::KEY_MACRO3 => Keyboard::Macro3, - glue::KEY_MACRO30 => Keyboard::Macro30, - glue::KEY_MACRO4 => Keyboard::Macro4, - glue::KEY_MACRO5 => Keyboard::Macro5, - glue::KEY_MACRO6 => Keyboard::Macro6, - glue::KEY_MACRO7 => Keyboard::Macro7, - glue::KEY_MACRO8 => Keyboard::Macro8, - glue::KEY_MACRO9 => Keyboard::Macro9, - glue::KEY_MACRO_PRESET1 => Keyboard::MacroPreset1, - glue::KEY_MACRO_PRESET2 => Keyboard::MacroPreset2, - glue::KEY_MACRO_PRESET3 => Keyboard::MacroPreset3, - glue::KEY_MACRO_PRESET_CYCLE => Keyboard::MacroPresetCycle, - glue::KEY_MACRO_RECORD_START => Keyboard::MacroRecordStart, - glue::KEY_MACRO_RECORD_STOP => Keyboard::MacroRecordStop, - glue::KEY_MAIL => Keyboard::Mail, - glue::KEY_MEDIA => Keyboard::Media, - glue::KEY_MEDIA_REPEAT => Keyboard::MediaRepeat, - glue::KEY_MEDIA_TOP_MENU => Keyboard::MediaTopMenu, - glue::KEY_MEMO => Keyboard::Memo, - glue::KEY_MENU => Keyboard::Menu, - glue::KEY_MESSENGER => Keyboard::Messenger, - glue::KEY_MHP => Keyboard::Mhp, - glue::KEY_MICMUTE => Keyboard::MicMute, - glue::KEY_MINUS => Keyboard::Minus, - glue::KEY_MODE => Keyboard::Mode, - glue::KEY_MOVE => Keyboard::Move, - glue::KEY_MP3 => Keyboard::Mp3, - glue::KEY_MSDOS => Keyboard::MsDos, - glue::KEY_MUHENKAN => Keyboard::Muhenkan, - glue::KEY_MUTE => Keyboard::Mute, - glue::KEY_N => Keyboard::N, - glue::KEY_0 => Keyboard::N0, - glue::KEY_1 => Keyboard::N1, - glue::KEY_102ND => Keyboard::N102nd, - glue::KEY_10CHANNELSDOWN => Keyboard::N10ChannelsDown, - glue::KEY_10CHANNELSUP => Keyboard::N10ChannelsUp, - glue::KEY_2 => Keyboard::N2, - glue::KEY_3 => Keyboard::N3, - glue::KEY_3D_MODE => Keyboard::N3dMode, - glue::KEY_4 => Keyboard::N4, - glue::KEY_5 => Keyboard::N5, - glue::KEY_6 => Keyboard::N6, - glue::KEY_7 => Keyboard::N7, - glue::KEY_8 => Keyboard::N8, - glue::KEY_9 => Keyboard::N9, - glue::KEY_NEW => Keyboard::New, - glue::KEY_NEWS => Keyboard::News, - glue::KEY_NEXT => Keyboard::Next, - glue::KEY_NEXT_FAVORITE => Keyboard::NextFavorite, - glue::KEY_NEXTSONG => Keyboard::NextSong, - glue::KEY_NUMERIC_0 => Keyboard::Numeric0, - glue::KEY_NUMERIC_1 => Keyboard::Numeric1, - glue::KEY_NUMERIC_11 => Keyboard::Numeric11, - glue::KEY_NUMERIC_12 => Keyboard::Numeric12, - glue::KEY_NUMERIC_2 => Keyboard::Numeric2, - glue::KEY_NUMERIC_3 => Keyboard::Numeric3, - glue::KEY_NUMERIC_4 => Keyboard::Numeric4, - glue::KEY_NUMERIC_5 => Keyboard::Numeric5, - glue::KEY_NUMERIC_6 => Keyboard::Numeric6, - glue::KEY_NUMERIC_7 => Keyboard::Numeric7, - glue::KEY_NUMERIC_8 => Keyboard::Numeric8, - glue::KEY_NUMERIC_9 => Keyboard::Numeric9, - glue::KEY_NUMERIC_A => Keyboard::NumericA, - glue::KEY_NUMERIC_B => Keyboard::NumericB, - glue::KEY_NUMERIC_C => Keyboard::NumericC, - glue::KEY_NUMERIC_D => Keyboard::NumericD, - glue::KEY_NUMERIC_POUND => Keyboard::NumericPound, - glue::KEY_NUMERIC_STAR => Keyboard::NumericStar, - glue::KEY_NUMLOCK => Keyboard::NumLock, - glue::KEY_O => Keyboard::O, - glue::KEY_OK => Keyboard::Ok, - glue::KEY_ONSCREEN_KEYBOARD => Keyboard::OnscreenKeyboard, - glue::KEY_OPEN => Keyboard::Open, - glue::KEY_OPTION => Keyboard::Option, - glue::KEY_P => Keyboard::P, - glue::KEY_PAGEDOWN => Keyboard::PageDown, - glue::KEY_PAGEUP => Keyboard::PageUp, - glue::KEY_PASTE => Keyboard::Paste, - glue::KEY_PAUSE => Keyboard::Pause, - glue::KEY_PAUSE_RECORD => Keyboard::PauseRecord, - glue::KEY_PAUSECD => Keyboard::PauseCd, - glue::KEY_PC => Keyboard::Pc, - glue::KEY_PHONE => Keyboard::Phone, - glue::KEY_PLAY => Keyboard::Play, - glue::KEY_PLAYCD => Keyboard::PlayCd, - glue::KEY_PLAYER => Keyboard::Player, - glue::KEY_PLAYPAUSE => Keyboard::PlayPause, - glue::KEY_POWER => Keyboard::Power, - glue::KEY_POWER2 => Keyboard::Power2, - glue::KEY_PRESENTATION => Keyboard::Presentation, - glue::KEY_PREVIOUS => Keyboard::Previous, - glue::KEY_PREVIOUSSONG => Keyboard::PreviousSong, - glue::KEY_PRINT => Keyboard::Print, - glue::KEY_PRIVACY_SCREEN_TOGGLE => Keyboard::PrivacyScreenToggle, - glue::KEY_PROG1 => Keyboard::Prog1, - glue::KEY_PROG2 => Keyboard::Prog2, - glue::KEY_PROG3 => Keyboard::Prog3, - glue::KEY_PROG4 => Keyboard::Prog4, - glue::KEY_PROGRAM => Keyboard::Program, - glue::KEY_PROPS => Keyboard::Props, - glue::KEY_PVR => Keyboard::Pvr, - glue::KEY_Q => Keyboard::Q, - glue::KEY_QUESTION => Keyboard::Question, - glue::KEY_R => Keyboard::R, - glue::KEY_RADIO => Keyboard::Radio, - glue::KEY_RECORD => Keyboard::Record, - glue::KEY_RED => Keyboard::Red, - glue::KEY_REDO => Keyboard::Redo, - glue::KEY_REFRESH => Keyboard::Refresh, - glue::KEY_REPLY => Keyboard::Reply, - glue::KEY_RESERVED => Keyboard::Reserved, - glue::KEY_RESTART => Keyboard::Restart, - glue::KEY_REWIND => Keyboard::Rewind, - glue::KEY_RFKILL => Keyboard::RfKill, - glue::KEY_RIGHT => Keyboard::Right, - glue::KEY_RIGHT_DOWN => Keyboard::RightDown, - glue::KEY_RIGHT_UP => Keyboard::RightUp, - glue::KEY_RIGHTALT => Keyboard::RightAlt, - glue::KEY_RIGHTBRACE => Keyboard::RightBrace, - glue::KEY_RIGHTCTRL => Keyboard::RightCtrl, - glue::KEY_RIGHTMETA => Keyboard::RightMeta, - glue::KEY_RIGHTSHIFT => Keyboard::RightShift, - glue::KEY_RO => Keyboard::Ro, - glue::KEY_ROOT_MENU => Keyboard::RootMenu, - glue::KEY_ROTATE_DISPLAY => Keyboard::RotateDisplay, - glue::KEY_ROTATE_LOCK_TOGGLE => Keyboard::RotateLockToggle, - glue::KEY_S => Keyboard::S, - glue::KEY_SAT => Keyboard::Sat, - glue::KEY_SAT2 => Keyboard::Sat2, - glue::KEY_SAVE => Keyboard::Save, - glue::KEY_SCALE => Keyboard::Scale, - glue::KEY_SCREENSAVER => Keyboard::Screensaver, - glue::KEY_SCROLLDOWN => Keyboard::ScrollDown, - glue::KEY_SCROLLLOCK => Keyboard::ScrollLock, - glue::KEY_SCROLLUP => Keyboard::ScrollUp, - glue::KEY_SEARCH => Keyboard::Search, - glue::KEY_SELECT => Keyboard::Select, - glue::KEY_SELECTIVE_SCREENSHOT => Keyboard::SelectiveScreenshot, - glue::KEY_SEMICOLON => Keyboard::Semicolon, - glue::KEY_SEND => Keyboard::Send, - glue::KEY_SENDFILE => Keyboard::SendFile, - glue::KEY_SETUP => Keyboard::Setup, - glue::KEY_SHOP => Keyboard::Shop, - glue::KEY_SHUFFLE => Keyboard::Shuffle, - glue::KEY_SLASH => Keyboard::Slash, - glue::KEY_SLEEP => Keyboard::Sleep, - glue::KEY_SLOW => Keyboard::Slow, - glue::KEY_SLOWREVERSE => Keyboard::SlowReverse, - glue::KEY_SOUND => Keyboard::Sound, - glue::KEY_SPACE => Keyboard::Space, - glue::KEY_SPELLCHECK => Keyboard::Spellcheck, - glue::KEY_SPORT => Keyboard::Sport, - glue::KEY_SPREADSHEET => Keyboard::Spreadsheet, - glue::KEY_STOP => Keyboard::Stop, - glue::KEY_STOP_RECORD => Keyboard::StopRecord, - glue::KEY_STOPCD => Keyboard::StopCd, - glue::KEY_SUBTITLE => Keyboard::Subtitle, - glue::KEY_SUSPEND => Keyboard::Suspend, - glue::KEY_SWITCHVIDEOMODE => Keyboard::SwitchVideoMode, - glue::KEY_SYSRQ => Keyboard::SysRq, - glue::KEY_T => Keyboard::T, - glue::KEY_TAB => Keyboard::Tab, - glue::KEY_TAPE => Keyboard::Tape, - glue::KEY_TASKMANAGER => Keyboard::TaskManager, - glue::KEY_TEEN => Keyboard::Teen, - glue::KEY_TEXT => Keyboard::Text, - glue::KEY_TIME => Keyboard::Time, - glue::KEY_TITLE => Keyboard::Title, - glue::KEY_TOUCHPAD_OFF => Keyboard::TouchpadOff, - glue::KEY_TOUCHPAD_ON => Keyboard::TouchpadOn, - glue::KEY_TOUCHPAD_TOGGLE => Keyboard::TouchpadToggle, - glue::KEY_TUNER => Keyboard::Tuner, - glue::KEY_TV => Keyboard::Tv, - glue::KEY_TV2 => Keyboard::Tv2, - glue::KEY_TWEN => Keyboard::Twen, - glue::KEY_U => Keyboard::U, - glue::KEY_UNDO => Keyboard::Undo, - glue::KEY_UNKNOWN => Keyboard::Unknown, - glue::KEY_UNMUTE => Keyboard::Unmute, - glue::KEY_UP => Keyboard::Up, - glue::KEY_UWB => Keyboard::Uwb, - glue::KEY_V => Keyboard::V, - glue::KEY_VCR => Keyboard::Vcr, - glue::KEY_VCR2 => Keyboard::Vcr2, - glue::KEY_VENDOR => Keyboard::Vendor, - glue::KEY_VIDEO => Keyboard::Video, - glue::KEY_VIDEO_NEXT => Keyboard::VideoNext, - glue::KEY_VIDEO_PREV => Keyboard::VideoPrev, - glue::KEY_VIDEOPHONE => Keyboard::VideoPhone, - glue::KEY_VOD => Keyboard::Vod, - glue::KEY_VOICECOMMAND => Keyboard::VoiceCommand, - glue::KEY_VOICEMAIL => Keyboard::VoiceMail, - glue::KEY_VOLUMEDOWN => Keyboard::VolumeDown, - glue::KEY_VOLUMEUP => Keyboard::VolumeUp, - glue::KEY_W => Keyboard::W, - glue::KEY_WAKEUP => Keyboard::WakeUp, - glue::KEY_WLAN => Keyboard::Wlan, - glue::KEY_WORDPROCESSOR => Keyboard::WordProcessor, - glue::KEY_WPS_BUTTON => Keyboard::WpsButton, - glue::KEY_WWAN => Keyboard::Wwan, - glue::KEY_WWW => Keyboard::Www, - glue::KEY_X => Keyboard::X, - glue::KEY_XFER => Keyboard::Xfer, - glue::KEY_Y => Keyboard::Y, - glue::KEY_YELLOW => Keyboard::Yellow, - glue::KEY_YEN => Keyboard::Yen, - glue::KEY_Z => Keyboard::Z, - glue::KEY_ZENKAKUHANKAKU => Keyboard::ZenkakuHankaku, - glue::KEY_ZOOMIN => Keyboard::ZoomIn, - glue::KEY_ZOOMOUT => Keyboard::ZoomOut, - glue::KEY_ZOOMRESET => Keyboard::ZoomReset, - _ => return None, - }; - - Some(keyboard) - } -} diff --git a/rkvm-input/src/lib.rs b/rkvm-input/src/lib.rs index bed81ab..80026b8 100644 --- a/rkvm-input/src/lib.rs +++ b/rkvm-input/src/lib.rs @@ -8,7 +8,9 @@ pub mod sync; pub mod writer; mod convert; -mod evdev; -mod glue; -mod registry; -mod uinput; + +#[cfg(target_os = "windows")] +pub mod windows; +#[cfg(target_os = "linux")] +pub mod linux; + diff --git a/rkvm-input/src/linux/abs_convert.rs b/rkvm-input/src/linux/abs_convert.rs new file mode 100644 index 0000000..52fc39d --- /dev/null +++ b/rkvm-input/src/linux/abs_convert.rs @@ -0,0 +1,136 @@ +use crate::convert::Convert; +use crate::glue; + +use crate::AbsAxis; + +impl Convert for AbsAxis { + type Raw = u16; + + fn from_raw(raw: Self::Raw) -> Option { + let axis = match raw as _ { + glue::ABS_X => Self::X, + glue::ABS_Y => Self::Y, + glue::ABS_Z => Self::Z, + glue::ABS_RX => Self::Rx, + glue::ABS_RY => Self::Ry, + glue::ABS_RZ => Self::Rz, + glue::ABS_THROTTLE => Self::Throttle, + glue::ABS_RUDDER => Self::Rudder, + glue::ABS_WHEEL => Self::Wheel, + glue::ABS_GAS => Self::Gas, + glue::ABS_BRAKE => Self::Brake, + glue::ABS_HAT0X => Self::Hat0X, + glue::ABS_HAT0Y => Self::Hat0Y, + glue::ABS_HAT1X => Self::Hat1X, + glue::ABS_HAT1Y => Self::Hat1Y, + glue::ABS_HAT2X => Self::Hat2X, + glue::ABS_HAT2Y => Self::Hat2Y, + glue::ABS_HAT3X => Self::Hat3X, + glue::ABS_HAT3Y => Self::Hat3Y, + glue::ABS_PRESSURE => Self::Pressure, + glue::ABS_DISTANCE => Self::Distance, + glue::ABS_TILT_X => Self::TiltX, + glue::ABS_TILT_Y => Self::TiltY, + glue::ABS_TOOL_WIDTH => Self::ToolWidth, + glue::ABS_VOLUME => Self::Volume, + #[cfg(have_abs_profile)] + glue::ABS_PROFILE => Self::Profile, + glue::ABS_MISC => Self::Misc, + glue::ABS_MT_SLOT => Self::MtSlot, + glue::ABS_MT_TOUCH_MAJOR => Self::MtTouchMajor, + glue::ABS_MT_TOUCH_MINOR => Self::MtTouchMinor, + glue::ABS_MT_WIDTH_MAJOR => Self::MtWidthMajor, + glue::ABS_MT_WIDTH_MINOR => Self::MtWidthMinor, + glue::ABS_MT_ORIENTATION => Self::MtOrientation, + glue::ABS_MT_POSITION_X => Self::MtPositionX, + glue::ABS_MT_POSITION_Y => Self::MtPositionY, + glue::ABS_MT_BLOB_ID => Self::MtBlobId, + glue::ABS_MT_TRACKING_ID => Self::MtTrackingId, + glue::ABS_MT_PRESSURE => Self::MtPressure, + glue::ABS_MT_DISTANCE => Self::MtDistance, + glue::ABS_MT_TOOL_X => Self::MtToolX, + glue::ABS_MT_TOOL_Y => Self::MtToolY, + _ => return None, + }; + + Some(axis) + } + + fn to_raw(&self) -> Option { + let code = match self { + Self::X => glue::ABS_X, + Self::Y => glue::ABS_Y, + Self::Z => glue::ABS_Z, + Self::Rx => glue::ABS_RX, + Self::Ry => glue::ABS_RY, + Self::Rz => glue::ABS_RZ, + Self::Throttle => glue::ABS_THROTTLE, + Self::Rudder => glue::ABS_RUDDER, + Self::Wheel => glue::ABS_WHEEL, + Self::Gas => glue::ABS_GAS, + Self::Brake => glue::ABS_BRAKE, + Self::Hat0X => glue::ABS_HAT0X, + Self::Hat0Y => glue::ABS_HAT0Y, + Self::Hat1X => glue::ABS_HAT1X, + Self::Hat1Y => glue::ABS_HAT1Y, + Self::Hat2X => glue::ABS_HAT2X, + Self::Hat2Y => glue::ABS_HAT2Y, + Self::Hat3X => glue::ABS_HAT3X, + Self::Hat3Y => glue::ABS_HAT3Y, + Self::Pressure => glue::ABS_PRESSURE, + Self::Distance => glue::ABS_DISTANCE, + Self::TiltX => glue::ABS_TILT_X, + Self::TiltY => glue::ABS_TILT_Y, + Self::ToolWidth => glue::ABS_TOOL_WIDTH, + Self::Volume => glue::ABS_VOLUME, + #[cfg(have_abs_profile)] + Self::Profile => glue::ABS_PROFILE, + #[cfg(not(have_abs_profile))] + Self::Profile => return None, + Self::Misc => glue::ABS_MISC, + Self::MtSlot => glue::ABS_MT_SLOT, + Self::MtTouchMajor => glue::ABS_MT_TOUCH_MAJOR, + Self::MtTouchMinor => glue::ABS_MT_TOUCH_MINOR, + Self::MtWidthMajor => glue::ABS_MT_WIDTH_MAJOR, + Self::MtWidthMinor => glue::ABS_MT_WIDTH_MINOR, + Self::MtOrientation => glue::ABS_MT_ORIENTATION, + Self::MtPositionX => glue::ABS_MT_POSITION_X, + Self::MtPositionY => glue::ABS_MT_POSITION_Y, + Self::MtBlobId => glue::ABS_MT_BLOB_ID, + Self::MtTrackingId => glue::ABS_MT_TRACKING_ID, + Self::MtPressure => glue::ABS_MT_PRESSURE, + Self::MtDistance => glue::ABS_MT_DISTANCE, + Self::MtToolX => glue::ABS_MT_TOOL_X, + Self::MtToolY => glue::ABS_MT_TOOL_Y, + }; + + Some(code as _) + } +} + +impl Convert for ToolType { + type Raw = i32; + + fn from_raw(raw: Self::Raw) -> Option { + let r#type = match raw as _ { + glue::MT_TOOL_FINGER => Self::Finger, + glue::MT_TOOL_PEN => Self::Pen, + glue::MT_TOOL_PALM => Self::Palm, + glue::MT_TOOL_DIAL => Self::Dial, + _ => return None, + }; + + Some(r#type) + } + + fn to_raw(&self) -> Option { + let value = match self { + Self::Finger => glue::MT_TOOL_FINGER, + Self::Pen => glue::MT_TOOL_PEN, + Self::Palm => glue::MT_TOOL_PALM, + Self::Dial => glue::MT_TOOL_DIAL, + }; + + Some(value as _) + } +} \ No newline at end of file diff --git a/rkvm-input/src/linux/button_convert.rs b/rkvm-input/src/linux/button_convert.rs new file mode 100644 index 0000000..cb98bfb --- /dev/null +++ b/rkvm-input/src/linux/button_convert.rs @@ -0,0 +1,237 @@ +use crate::convert::Convert; +use crate::glue; + +impl Convert for Button { + type Raw = u16; + + fn to_raw(&self) -> Option { + let raw = match self { + Self::B0 => glue::BTN_0, + Self::B1 => glue::BTN_1, + Self::B2 => glue::BTN_2, + Self::B3 => glue::BTN_3, + Self::B4 => glue::BTN_4, + Self::B5 => glue::BTN_5, + Self::B6 => glue::BTN_6, + Self::B7 => glue::BTN_7, + Self::B8 => glue::BTN_8, + Self::B9 => glue::BTN_9, + Self::Left => glue::BTN_LEFT, + Self::Right => glue::BTN_RIGHT, + Self::Middle => glue::BTN_MIDDLE, + Self::Side => glue::BTN_SIDE, + Self::Extra => glue::BTN_EXTRA, + Self::Forward => glue::BTN_FORWARD, + Self::Back => glue::BTN_BACK, + Self::Task => glue::BTN_TASK, + Self::Trigger => glue::BTN_TRIGGER, + Self::Thumb => glue::BTN_THUMB, + Self::Thumb2 => glue::BTN_THUMB2, + Self::Top => glue::BTN_TOP, + Self::Top2 => glue::BTN_TOP2, + Self::Pinkie => glue::BTN_PINKIE, + Self::Base => glue::BTN_BASE, + Self::Base2 => glue::BTN_BASE2, + Self::Base3 => glue::BTN_BASE3, + Self::Base4 => glue::BTN_BASE4, + Self::Base5 => glue::BTN_BASE5, + Self::Base6 => glue::BTN_BASE6, + Self::Dead => glue::BTN_DEAD, + Self::South => glue::BTN_SOUTH, + Self::East => glue::BTN_EAST, + Self::C => glue::BTN_C, + Self::North => glue::BTN_NORTH, + Self::West => glue::BTN_WEST, + Self::Z => glue::BTN_Z, + Self::TL => glue::BTN_TL, + Self::Tr => glue::BTN_TR, + Self::Tl2 => glue::BTN_TL2, + Self::Tr2 => glue::BTN_TR2, + Self::Select => glue::BTN_SELECT, + Self::Start => glue::BTN_START, + Self::Mode => glue::BTN_MODE, + Self::ThumbL => glue::BTN_THUMBL, + Self::ThumbR => glue::BTN_THUMBR, + Self::ToolPen => glue::BTN_TOOL_PEN, + Self::ToolRubber => glue::BTN_TOOL_RUBBER, + Self::ToolBrush => glue::BTN_TOOL_BRUSH, + Self::ToolPencil => glue::BTN_TOOL_PENCIL, + Self::ToolAirbrush => glue::BTN_TOOL_AIRBRUSH, + Self::ToolFinger => glue::BTN_TOOL_FINGER, + Self::ToolMouse => glue::BTN_TOOL_MOUSE, + Self::ToolLens => glue::BTN_TOOL_LENS, + Self::QuintTap => glue::BTN_TOOL_QUINTTAP, + Self::Stylus3 => glue::BTN_STYLUS3, + Self::Touch => glue::BTN_TOUCH, + Self::Stylus => glue::BTN_STYLUS, + Self::Stylus2 => glue::BTN_STYLUS2, + Self::DoubleTap => glue::BTN_TOOL_DOUBLETAP, + Self::TripleTap => glue::BTN_TOOL_TRIPLETAP, + Self::QuadTap => glue::BTN_TOOL_QUADTAP, + Self::GearDown => glue::BTN_GEAR_DOWN, + Self::GearUp => glue::BTN_GEAR_UP, + Self::DPadUp => glue::BTN_DPAD_UP, + Self::DPadDown => glue::BTN_DPAD_DOWN, + Self::DPadLeft => glue::BTN_DPAD_LEFT, + Self::DPadRight => glue::BTN_DPAD_RIGHT, + Self::TriggerHappy1 => glue::BTN_TRIGGER_HAPPY1, + Self::TrigerHappy2 => glue::BTN_TRIGGER_HAPPY2, + Self::TriggerHappy3 => glue::BTN_TRIGGER_HAPPY3, + Self::TriggerHappy4 => glue::BTN_TRIGGER_HAPPY4, + Self::TriggerHappy5 => glue::BTN_TRIGGER_HAPPY5, + Self::TriggerHappy6 => glue::BTN_TRIGGER_HAPPY6, + Self::TriggerHappy7 => glue::BTN_TRIGGER_HAPPY7, + Self::TriggerHappy8 => glue::BTN_TRIGGER_HAPPY8, + Self::TriggerHappy9 => glue::BTN_TRIGGER_HAPPY9, + Self::TriggerHappy10 => glue::BTN_TRIGGER_HAPPY10, + Self::TriggerHappy11 => glue::BTN_TRIGGER_HAPPY11, + Self::TriggerHappy12 => glue::BTN_TRIGGER_HAPPY12, + Self::TriggerHappy13 => glue::BTN_TRIGGER_HAPPY13, + Self::TriggerHappy14 => glue::BTN_TRIGGER_HAPPY14, + Self::TriggerHappy15 => glue::BTN_TRIGGER_HAPPY15, + Self::TriggerHappy16 => glue::BTN_TRIGGER_HAPPY16, + Self::TriggerHappy17 => glue::BTN_TRIGGER_HAPPY17, + Self::TriggerHappy18 => glue::BTN_TRIGGER_HAPPY18, + Self::TriggerHappy19 => glue::BTN_TRIGGER_HAPPY19, + Self::TriggerHappy20 => glue::BTN_TRIGGER_HAPPY20, + Self::TriggerHappy21 => glue::BTN_TRIGGER_HAPPY21, + Self::TriggerHappy22 => glue::BTN_TRIGGER_HAPPY22, + Self::TriggerHappy23 => glue::BTN_TRIGGER_HAPPY23, + Self::TriggerHappy24 => glue::BTN_TRIGGER_HAPPY24, + Self::TriggerHappy25 => glue::BTN_TRIGGER_HAPPY25, + Self::TriggerHappy26 => glue::BTN_TRIGGER_HAPPY26, + Self::TriggerHappy27 => glue::BTN_TRIGGER_HAPPY27, + Self::TriggerHappy28 => glue::BTN_TRIGGER_HAPPY28, + Self::TriggerHappy29 => glue::BTN_TRIGGER_HAPPY29, + Self::TriggerHappy30 => glue::BTN_TRIGGER_HAPPY30, + Self::TriggerHappy31 => glue::BTN_TRIGGER_HAPPY31, + Self::TriggerHappy32 => glue::BTN_TRIGGER_HAPPY32, + Self::TriggerHappy33 => glue::BTN_TRIGGER_HAPPY33, + Self::TriggerHappy34 => glue::BTN_TRIGGER_HAPPY34, + Self::TriggerHappy35 => glue::BTN_TRIGGER_HAPPY35, + Self::TriggerHappy36 => glue::BTN_TRIGGER_HAPPY36, + Self::TriggerHappy37 => glue::BTN_TRIGGER_HAPPY37, + Self::TriggerHappy38 => glue::BTN_TRIGGER_HAPPY38, + Self::TriggerHappy39 => glue::BTN_TRIGGER_HAPPY39, + Self::TriggerHappy40 => glue::BTN_TRIGGER_HAPPY40, + }; + + Some(raw as _) + } + + fn from_raw(raw: Self::Raw) -> Option { + let button = match raw as _ { + glue::BTN_0 => Self::B0, + glue::BTN_1 => Self::B1, + glue::BTN_2 => Self::B2, + glue::BTN_3 => Self::B3, + glue::BTN_4 => Self::B4, + glue::BTN_5 => Self::B5, + glue::BTN_6 => Self::B6, + glue::BTN_7 => Self::B7, + glue::BTN_8 => Self::B8, + glue::BTN_9 => Self::B9, + glue::BTN_LEFT => Self::Left, + glue::BTN_RIGHT => Self::Right, + glue::BTN_MIDDLE => Self::Middle, + glue::BTN_SIDE => Self::Side, + glue::BTN_EXTRA => Self::Extra, + glue::BTN_FORWARD => Self::Forward, + glue::BTN_BACK => Self::Back, + glue::BTN_TASK => Self::Task, + glue::BTN_TRIGGER => Self::Trigger, + glue::BTN_THUMB => Self::Thumb, + glue::BTN_THUMB2 => Self::Thumb2, + glue::BTN_TOP => Self::Top, + glue::BTN_TOP2 => Self::Top2, + glue::BTN_PINKIE => Self::Pinkie, + glue::BTN_BASE => Self::Base, + glue::BTN_BASE2 => Self::Base2, + glue::BTN_BASE3 => Self::Base3, + glue::BTN_BASE4 => Self::Base4, + glue::BTN_BASE5 => Self::Base5, + glue::BTN_BASE6 => Self::Base6, + glue::BTN_DEAD => Self::Dead, + glue::BTN_SOUTH => Self::South, + glue::BTN_EAST => Self::East, + glue::BTN_C => Self::C, + glue::BTN_NORTH => Self::North, + glue::BTN_WEST => Self::West, + glue::BTN_Z => Self::Z, + glue::BTN_TL => Self::TL, + glue::BTN_TR => Self::Tr, + glue::BTN_TL2 => Self::Tl2, + glue::BTN_TR2 => Self::Tr2, + glue::BTN_SELECT => Self::Select, + glue::BTN_START => Self::Start, + glue::BTN_MODE => Self::Mode, + glue::BTN_THUMBL => Self::ThumbL, + glue::BTN_THUMBR => Self::ThumbR, + glue::BTN_TOOL_PEN => Self::ToolPen, + glue::BTN_TOOL_RUBBER => Self::ToolRubber, + glue::BTN_TOOL_BRUSH => Self::ToolBrush, + glue::BTN_TOOL_PENCIL => Self::ToolPencil, + glue::BTN_TOOL_AIRBRUSH => Self::ToolAirbrush, + glue::BTN_TOOL_FINGER => Self::ToolFinger, + glue::BTN_TOOL_MOUSE => Self::ToolMouse, + glue::BTN_TOOL_LENS => Self::ToolLens, + glue::BTN_TOOL_QUINTTAP => Self::QuintTap, + glue::BTN_STYLUS3 => Self::Stylus3, + glue::BTN_TOUCH => Self::Touch, + glue::BTN_STYLUS => Self::Stylus, + glue::BTN_STYLUS2 => Self::Stylus2, + glue::BTN_TOOL_DOUBLETAP => Self::DoubleTap, + glue::BTN_TOOL_TRIPLETAP => Self::TripleTap, + glue::BTN_TOOL_QUADTAP => Self::QuadTap, + glue::BTN_GEAR_DOWN => Self::GearDown, + glue::BTN_GEAR_UP => Self::GearUp, + glue::BTN_DPAD_UP => Self::DPadUp, + glue::BTN_DPAD_DOWN => Self::DPadDown, + glue::BTN_DPAD_LEFT => Self::DPadLeft, + glue::BTN_DPAD_RIGHT => Self::DPadRight, + glue::BTN_TRIGGER_HAPPY1 => Self::TriggerHappy1, + glue::BTN_TRIGGER_HAPPY2 => Self::TrigerHappy2, + glue::BTN_TRIGGER_HAPPY3 => Self::TriggerHappy3, + glue::BTN_TRIGGER_HAPPY4 => Self::TriggerHappy4, + glue::BTN_TRIGGER_HAPPY5 => Self::TriggerHappy5, + glue::BTN_TRIGGER_HAPPY6 => Self::TriggerHappy6, + glue::BTN_TRIGGER_HAPPY7 => Self::TriggerHappy7, + glue::BTN_TRIGGER_HAPPY8 => Self::TriggerHappy8, + glue::BTN_TRIGGER_HAPPY9 => Self::TriggerHappy9, + glue::BTN_TRIGGER_HAPPY10 => Self::TriggerHappy10, + glue::BTN_TRIGGER_HAPPY11 => Self::TriggerHappy11, + glue::BTN_TRIGGER_HAPPY12 => Self::TriggerHappy12, + glue::BTN_TRIGGER_HAPPY13 => Self::TriggerHappy13, + glue::BTN_TRIGGER_HAPPY14 => Self::TriggerHappy14, + glue::BTN_TRIGGER_HAPPY15 => Self::TriggerHappy15, + glue::BTN_TRIGGER_HAPPY16 => Self::TriggerHappy16, + glue::BTN_TRIGGER_HAPPY17 => Self::TriggerHappy17, + glue::BTN_TRIGGER_HAPPY18 => Self::TriggerHappy18, + glue::BTN_TRIGGER_HAPPY19 => Self::TriggerHappy19, + glue::BTN_TRIGGER_HAPPY20 => Self::TriggerHappy20, + glue::BTN_TRIGGER_HAPPY21 => Self::TriggerHappy21, + glue::BTN_TRIGGER_HAPPY22 => Self::TriggerHappy22, + glue::BTN_TRIGGER_HAPPY23 => Self::TriggerHappy23, + glue::BTN_TRIGGER_HAPPY24 => Self::TriggerHappy24, + glue::BTN_TRIGGER_HAPPY25 => Self::TriggerHappy25, + glue::BTN_TRIGGER_HAPPY26 => Self::TriggerHappy26, + glue::BTN_TRIGGER_HAPPY27 => Self::TriggerHappy27, + glue::BTN_TRIGGER_HAPPY28 => Self::TriggerHappy28, + glue::BTN_TRIGGER_HAPPY29 => Self::TriggerHappy29, + glue::BTN_TRIGGER_HAPPY30 => Self::TriggerHappy30, + glue::BTN_TRIGGER_HAPPY31 => Self::TriggerHappy31, + glue::BTN_TRIGGER_HAPPY32 => Self::TriggerHappy32, + glue::BTN_TRIGGER_HAPPY33 => Self::TriggerHappy33, + glue::BTN_TRIGGER_HAPPY34 => Self::TriggerHappy34, + glue::BTN_TRIGGER_HAPPY35 => Self::TriggerHappy35, + glue::BTN_TRIGGER_HAPPY36 => Self::TriggerHappy36, + glue::BTN_TRIGGER_HAPPY37 => Self::TriggerHappy37, + glue::BTN_TRIGGER_HAPPY38 => Self::TriggerHappy38, + glue::BTN_TRIGGER_HAPPY39 => Self::TriggerHappy39, + glue::BTN_TRIGGER_HAPPY40 => Self::TriggerHappy40, + _ => return None, + }; + + Some(button) + } +} diff --git a/rkvm-input/src/evdev.rs b/rkvm-input/src/linux/evdev.rs similarity index 100% rename from rkvm-input/src/evdev.rs rename to rkvm-input/src/linux/evdev.rs diff --git a/rkvm-input/src/glue.rs b/rkvm-input/src/linux/glue.rs similarity index 98% rename from rkvm-input/src/glue.rs rename to rkvm-input/src/linux/glue.rs index 28ef030..b420d74 100644 --- a/rkvm-input/src/glue.rs +++ b/rkvm-input/src/linux/glue.rs @@ -1,3 +1,2 @@ #![allow(warnings)] - include!(concat!(env!("OUT_DIR"), "/glue.rs")); diff --git a/rkvm-input/src/linux/interceptor.rs b/rkvm-input/src/linux/interceptor.rs new file mode 100644 index 0000000..a1c634d --- /dev/null +++ b/rkvm-input/src/linux/interceptor.rs @@ -0,0 +1,324 @@ +mod caps; + +pub use caps::{AbsCaps, KeyCaps, RelCaps}; + +use crate::abs::{AbsAxis, AbsInfo, AbsEvent, ToolType}; +use crate::interceptor::{InterceptorPlatform,Repeat}; +use crate::convert::Convert; +use crate::evdev::Evdev; +use crate::event::Event; +use crate::glue; +use crate::key::{Key, KeyEvent}; +use crate::registry::{Entry, Handle, Registry}; +use crate::rel::{RelAxis, RelEvent}; +use crate::sync::SyncEvent; +use crate::writer::Writer; + +use std::collections::{HashMap, HashSet, VecDeque}; +use std::ffi::CStr; +use std::fs; +use std::io::{Error, ErrorKind}; +use std::mem::MaybeUninit; +use std::path::Path; +use thiserror::Error; + +pub struct InterceptorLinux { + evdev: Evdev, + writer: Writer, + // The state of `read` is stored here to make it cancel safe. + events: VecDeque, + writing: Option<(u16, u16, i32)>, + dropped: bool, + + _reader_handle: Handle, + _writer_handle: Handle, +} + +impl InterceptorPlatform for InterceptorLinux { + #[tracing::instrument(fields(path = ?self.writer.path()), skip(self))] + async fn read(&mut self) -> Result { + if let Some((r#type, code, value)) = self.writing { + tracing::trace!("Resuming interrupted write"); + + self.writer.write_raw(r#type, code, value).await?; + self.writing = None; + } + + while !matches!(self.events.back(), Some(Event::Sync(SyncEvent::All))) { + let (r#type, code, value) = self.read_raw().await?; + let event = match r#type as _ { + glue::EV_REL if !self.dropped => { + RelAxis::from_raw(code).map(|axis| Event::Rel(RelEvent { axis, value })) + } + glue::EV_ABS if !self.dropped => match code as _ { + glue::ABS_MT_TOOL_TYPE => { + ToolType::from_raw(value).map(|value| AbsEvent::MtToolType { value }) + } + _ => AbsAxis::from_raw(code).map(|axis| AbsEvent::Axis { axis, value }), + } + .map(Event::Abs), + glue::EV_KEY if !self.dropped && (value == 0 || value == 1) => Key::from_raw(code) + .map(|key| { + Event::Key(KeyEvent { + key, + down: value == 1, + }) + }), + glue::EV_SYN => match code as _ { + glue::SYN_REPORT => { + if self.dropped { + self.dropped = false; + continue; + } + + Some(Event::Sync(SyncEvent::All)) + } + glue::SYN_DROPPED => { + tracing::warn!( + "Dropped {} event{}", + self.events.len(), + if self.events.len() == 1 { "" } else { "s" } + ); + + self.events.clear(); + self.dropped = true; + continue; + } + glue::SYN_MT_REPORT if !self.dropped => Some(Event::Sync(SyncEvent::Mt)), + _ => continue, + }, + _ => None, + }; + + if let Some(event) = event { + self.events.push_back(event); + continue; + } + + self.writing = Some((r#type, code, value)); + self.writer.write_raw(r#type, code, value).await?; + self.writing = None; + } + + Ok(self.events.pop_front().unwrap()) + } + + async fn write(&mut self, event: &Event) -> Result<(), Error> { + self.writer.write(event).await + } + + fn name(&self) -> &CStr { + let name = unsafe { glue::libevdev_get_name(self.evdev.as_ptr()) }; + let name = unsafe { CStr::from_ptr(name) }; + + name + } + + fn vendor(&self) -> u16 { + unsafe { glue::libevdev_get_id_vendor(self.evdev.as_ptr()) as _ } + } + + fn product(&self) -> u16 { + unsafe { glue::libevdev_get_id_product(self.evdev.as_ptr()) as _ } + } + + fn version(&self) -> u16 { + unsafe { glue::libevdev_get_id_version(self.evdev.as_ptr()) as _ } + } + + fn rel(&self) -> HashSet { + RelCaps::new(self).collect::>() + } + + fn abs(&self) -> HashMap { + AbsCaps::new(self).collect::>() + } + + fn key(&self) -> HashSet { + KeyCaps::new(self).collect::>() + } + + fn repeat(&self) -> Repeat { + let has = unsafe { + glue::libevdev_has_event_code(self.evdev.as_ptr(), glue::EV_REP, glue::REP_DELAY) + == 1 + }; + + let delay = if has { + Some(unsafe { + glue::libevdev_get_event_value( + self.evdev.as_ptr(), + glue::EV_REP, + glue::REP_DELAY, + ) + }) + } else { + None + }; + + let has = unsafe { + glue::libevdev_has_event_code( + self.evdev.as_ptr(), + glue::EV_REP, + glue::REP_PERIOD, + ) == 1 + }; + + let period = if has { + Some(unsafe { + glue::libevdev_get_event_value( + self.evdev.as_ptr(), + glue::EV_REP, + glue::REP_PERIOD, + ) + }) + } else { + None + }; + + Reapeat { delay, period } + } + + async fn read_raw(&mut self) -> Result<(u16, u16, i32), Error> { + let file = self.evdev.file().unwrap(); + + loop { + let result = file.readable().await?.try_io(|_| { + let mut event = MaybeUninit::uninit(); + let ret = unsafe { + glue::libevdev_next_event( + self.evdev.as_ptr(), + glue::libevdev_read_flag_LIBEVDEV_READ_FLAG_NORMAL, + event.as_mut_ptr(), + ) + }; + + if ret < 0 { + // ENODEV means that the device got disconnected. + // However, ErrorKind doesn't have support for it yet, + // so translate to BrokenPipe here to not introduce + // platform specific code to rkvm-server. + let err = if ret == -libc::ENODEV { + Error::new(ErrorKind::BrokenPipe, "Device disconnected") + } else { + Error::from_raw_os_error(-ret) + }; + + return Err(err); + } + + let event = unsafe { event.assume_init() }; + Ok((event.type_, event.code, event.value)) + }); + + match result { + Ok(result) => return result, + Err(_) => continue, // This means it would block. + } + } + } +} + +impl InterceptorLinux { + #[tracing::instrument(skip(registry))] + pub(crate) async fn open(path: &Path, registry: &Registry) -> Result { + let evdev = Evdev::open(path).await?; + let metadata = evdev.file().unwrap().get_ref().metadata()?; + + let reader_handle = registry + .register(Entry::from_metadata(&metadata)) + .ok_or(OpenError::NotAppliable)?; + + // "Upon binding to a device or resuming from suspend, a driver must report + // the current switch state. This ensures that the device, kernel, and userspace + // state is in sync." + // We have no way of knowing that. + let sw = unsafe { glue::libevdev_has_event_type(evdev.as_ptr(), glue::EV_SW) }; + if sw == 1 { + return Err(OpenError::NotAppliable); + } + + // Some buggy kernels can report nonsense abs info, so check for it and disable the axes. + for i in 0..glue::ABS_CNT { + let abs_info = unsafe { glue::libevdev_get_abs_info(evdev.as_ptr(), i).as_ref() }; + let abs_info = match abs_info { + Some(abs_info) => abs_info, + None => continue, + }; + + // See Linux source at drivers/input/misc/uinput.c#L408 commit 93f5de5f648d2b1ce3540a4ac71756d4a852dc23. + + let min = abs_info.minimum; + let max = abs_info.maximum; + + if (min != 0 || max != 0) && max < min { + tracing::warn!( + min = %min, + max = max, + axis = i, + "Detected nonsense min and max values for absolute axis, disabling it", + ); + + let ret = + unsafe { glue::libevdev_disable_event_code(evdev.as_ptr(), glue::EV_ABS, i) }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret).into()); + } + } + } + + unsafe { + glue::libevdev_set_id_bustype(evdev.as_ptr(), glue::BUS_VIRTUAL as _); + } + + let ret = + unsafe { glue::libevdev_grab(evdev.as_ptr(), glue::libevdev_grab_mode_LIBEVDEV_GRAB) }; + + if ret < 0 { + // We do not use ErrorKind::ResourceBusy because it is a nightly-only API. + let err = if ret == -libc::EBUSY { + tracing::info!( + "Ignored {:?} because it is busy and can not be grabbed", + path + ); + OpenError::NotAppliable + } else { + Error::from_raw_os_error(-ret).into() + }; + + return Err(err); + } + + let writer = Writer::from_evdev(&evdev).await?; + let path = writer + .path() + .ok_or_else(|| Error::new(ErrorKind::Other, "No syspath for writer"))?; + + let metadata = fs::metadata(path)?; + let writer_handle = registry + .register(Entry::from_metadata(&metadata)) + .ok_or_else(|| Error::new(ErrorKind::Other, "Writer already registered"))?; + + Ok(Self { + evdev, + writer, + events: VecDeque::new(), + dropped: false, + writing: None, + + _reader_handle: reader_handle, + _writer_handle: writer_handle, + }) + } +} + +unsafe impl Send for Interceptor {} + +#[derive(Error, Debug)] +pub(crate) enum OpenError { + #[error("Not appliable")] + NotAppliable, + #[error(transparent)] + Io(#[from] Error), +} diff --git a/rkvm-input/src/interceptor/caps.rs b/rkvm-input/src/linux/interceptor/caps.rs similarity index 69% rename from rkvm-input/src/interceptor/caps.rs rename to rkvm-input/src/linux/interceptor/caps.rs index 3b365ea..46e10dd 100644 --- a/rkvm-input/src/interceptor/caps.rs +++ b/rkvm-input/src/linux/interceptor/caps.rs @@ -1,17 +1,18 @@ use crate::abs::{AbsAxis, AbsInfo}; +use crate::interceptor::RelCaps; use crate::convert::Convert; use crate::glue; -use crate::interceptor::Interceptor; +use crate::linux::interceptor::InterceptorLinux; use crate::key::Key; use crate::rel::RelAxis; pub struct RelCaps<'a> { current: u16, - interceptor: &'a Interceptor, + interceptor: &'a InterceptorLinux, } impl<'a> RelCaps<'a> { - pub(super) fn new(interceptor: &'a Interceptor) -> Self { + fn new(interceptor: &'a InterceptorLinux) -> Self { let has = unsafe { glue::libevdev_has_event_type(interceptor.evdev.as_ptr(), glue::EV_REL) == 1 }; @@ -22,7 +23,7 @@ impl<'a> RelCaps<'a> { } } -impl Iterator for RelCaps<'_> { +impl Iterator for RelCapsLinux<'_> { type Item = RelAxis; fn next(&mut self) -> Option { @@ -52,11 +53,11 @@ impl Iterator for RelCaps<'_> { pub struct AbsCaps<'a> { current: u16, - interceptor: &'a Interceptor, + interceptor: &'a InterceptorLinux, } impl<'a> AbsCaps<'a> { - pub(super) fn new(interceptor: &'a Interceptor) -> Self { + pub(super) fn new(interceptor: &'a InterceptorLinux) -> Self { let has = unsafe { glue::libevdev_has_event_type(interceptor.evdev.as_ptr(), glue::EV_ABS) == 1 }; @@ -113,11 +114,11 @@ impl Iterator for AbsCaps<'_> { pub struct KeyCaps<'a> { current: u16, - interceptor: &'a Interceptor, + interceptor: &'a InterceptorLinux, } impl<'a> KeyCaps<'a> { - pub(super) fn new(interceptor: &'a Interceptor) -> Self { + pub(super) fn new(interceptor: &'a InterceptorLinux) -> Self { let has = unsafe { glue::libevdev_has_event_type(interceptor.evdev.as_ptr(), glue::EV_KEY) == 1 }; @@ -155,51 +156,3 @@ impl Iterator for KeyCaps<'_> { None } } - -pub struct Repeat { - pub delay: Option, - pub period: Option, -} - -impl Repeat { - pub(super) fn new(interceptor: &Interceptor) -> Self { - let has = unsafe { - glue::libevdev_has_event_code(interceptor.evdev.as_ptr(), glue::EV_REP, glue::REP_DELAY) - == 1 - }; - - let delay = if has { - Some(unsafe { - glue::libevdev_get_event_value( - interceptor.evdev.as_ptr(), - glue::EV_REP, - glue::REP_DELAY, - ) - }) - } else { - None - }; - - let has = unsafe { - glue::libevdev_has_event_code( - interceptor.evdev.as_ptr(), - glue::EV_REP, - glue::REP_PERIOD, - ) == 1 - }; - - let period = if has { - Some(unsafe { - glue::libevdev_get_event_value( - interceptor.evdev.as_ptr(), - glue::EV_REP, - glue::REP_PERIOD, - ) - }) - } else { - None - }; - - Self { delay, period } - } -} diff --git a/rkvm-input/src/linux/key_convert.rs b/rkvm-input/src/linux/key_convert.rs new file mode 100644 index 0000000..c2178d6 --- /dev/null +++ b/rkvm-input/src/linux/key_convert.rs @@ -0,0 +1,24 @@ +use crate::convert::Convert; + +impl Convert for Key { + type Raw = u16; + + fn from_raw(code: Self::Raw) -> Option { + if let Some(key) = Keyboard::from_raw(code) { + return Some(Self::Key(key)); + } + + if let Some(button) = Button::from_raw(code) { + return Some(Self::Button(button)); + } + + None + } + + fn to_raw(&self) -> Option { + match self { + Self::Key(key) => key.to_raw(), + Self::Button(button) => button.to_raw(), + } + } +} \ No newline at end of file diff --git a/rkvm-input/src/linux/keyboard_convert.rs b/rkvm-input/src/linux/keyboard_convert.rs new file mode 100644 index 0000000..aa1fd3d --- /dev/null +++ b/rkvm-input/src/linux/keyboard_convert.rs @@ -0,0 +1,984 @@ +use crate::convert::Convert; +use crate::glue; + +impl Convert for Keyboard { + type Raw = u16; + + fn to_raw(&self) -> Option { + let raw = match *self { + Keyboard::A => glue::KEY_A, + Keyboard::Ab => glue::KEY_AB, + Keyboard::AddressBook => glue::KEY_ADDRESSBOOK, + Keyboard::Again => glue::KEY_AGAIN, + Keyboard::AlsToggle => glue::KEY_ALS_TOGGLE, + Keyboard::AltErase => glue::KEY_ALTERASE, + Keyboard::Angle => glue::KEY_ANGLE, + Keyboard::Apostrophe => glue::KEY_APOSTROPHE, + Keyboard::Appselect => glue::KEY_APPSELECT, + Keyboard::Archive => glue::KEY_ARCHIVE, + Keyboard::AspectRatio => glue::KEY_ASPECT_RATIO, + Keyboard::Assistant => glue::KEY_ASSISTANT, + Keyboard::AttendantOff => glue::KEY_ATTENDANT_OFF, + Keyboard::AttendantOn => glue::KEY_ATTENDANT_ON, + Keyboard::AttendantToggle => glue::KEY_ATTENDANT_TOGGLE, + Keyboard::Audio => glue::KEY_AUDIO, + Keyboard::AudioDesc => glue::KEY_AUDIO_DESC, + Keyboard::Aux => glue::KEY_AUX, + Keyboard::B => glue::KEY_B, + Keyboard::Back => glue::KEY_BACK, + Keyboard::Backslash => glue::KEY_BACKSLASH, + Keyboard::Backspace => glue::KEY_BACKSPACE, + Keyboard::BassBoost => glue::KEY_BASSBOOST, + Keyboard::Battery => glue::KEY_BATTERY, + Keyboard::Blue => glue::KEY_BLUE, + Keyboard::Bluetooth => glue::KEY_BLUETOOTH, + Keyboard::Bookmarks => glue::KEY_BOOKMARKS, + Keyboard::Break => glue::KEY_BREAK, + Keyboard::BrightnessAuto => glue::KEY_BRIGHTNESS_AUTO, + Keyboard::BrightnessCycle => glue::KEY_BRIGHTNESS_CYCLE, + Keyboard::BrightnessMax => glue::KEY_BRIGHTNESS_MAX, + Keyboard::BrightnessMin => glue::KEY_BRIGHTNESS_MIN, + Keyboard::BrightnessToggle => glue::KEY_BRIGHTNESS_TOGGLE, + Keyboard::BrightnessDown => glue::KEY_BRIGHTNESSDOWN, + Keyboard::BrightnessUp => glue::KEY_BRIGHTNESSUP, + Keyboard::BrlDot1 => glue::KEY_BRL_DOT1, + Keyboard::BrlDot10 => glue::KEY_BRL_DOT10, + Keyboard::BrlDot2 => glue::KEY_BRL_DOT2, + Keyboard::BrlDot3 => glue::KEY_BRL_DOT3, + Keyboard::BrlDot4 => glue::KEY_BRL_DOT4, + Keyboard::BrlDot5 => glue::KEY_BRL_DOT5, + Keyboard::BrlDot6 => glue::KEY_BRL_DOT6, + Keyboard::BrlDot7 => glue::KEY_BRL_DOT7, + Keyboard::BrlDot8 => glue::KEY_BRL_DOT8, + Keyboard::BrlDot9 => glue::KEY_BRL_DOT9, + Keyboard::ButtonConfig => glue::KEY_BUTTONCONFIG, + Keyboard::C => glue::KEY_C, + Keyboard::Calc => glue::KEY_CALC, + Keyboard::Calendar => glue::KEY_CALENDAR, + Keyboard::Camera => glue::KEY_CAMERA, + Keyboard::CameraDown => glue::KEY_CAMERA_DOWN, + Keyboard::CameraFocus => glue::KEY_CAMERA_FOCUS, + Keyboard::CameraLeft => glue::KEY_CAMERA_LEFT, + Keyboard::CameraRight => glue::KEY_CAMERA_RIGHT, + Keyboard::CameraUp => glue::KEY_CAMERA_UP, + Keyboard::CameraZoomIn => glue::KEY_CAMERA_ZOOMIN, + Keyboard::CameraZoomOut => glue::KEY_CAMERA_ZOOMOUT, + Keyboard::Cancel => glue::KEY_CANCEL, + Keyboard::CapsLock => glue::KEY_CAPSLOCK, + Keyboard::Cd => glue::KEY_CD, + Keyboard::Channel => glue::KEY_CHANNEL, + Keyboard::ChannelDown => glue::KEY_CHANNELDOWN, + Keyboard::ChannelUp => glue::KEY_CHANNELUP, + Keyboard::Chat => glue::KEY_CHAT, + Keyboard::Clear => glue::KEY_CLEAR, + Keyboard::Close => glue::KEY_CLOSE, + Keyboard::CloseCd => glue::KEY_CLOSECD, + Keyboard::Coffee => glue::KEY_COFFEE, + Keyboard::Comma => glue::KEY_COMMA, + Keyboard::Compose => glue::KEY_COMPOSE, + Keyboard::Computer => glue::KEY_COMPUTER, + Keyboard::Config => glue::KEY_CONFIG, + Keyboard::Connect => glue::KEY_CONNECT, + Keyboard::ContextMenu => glue::KEY_CONTEXT_MENU, + Keyboard::Controlpanel => glue::KEY_CONTROLPANEL, + Keyboard::Copy => glue::KEY_COPY, + Keyboard::Cut => glue::KEY_CUT, + Keyboard::CycleWindows => glue::KEY_CYCLEWINDOWS, + Keyboard::D => glue::KEY_D, + Keyboard::Dashboard => glue::KEY_DASHBOARD, + Keyboard::Data => glue::KEY_DATA, + Keyboard::Database => glue::KEY_DATABASE, + Keyboard::DelEol => glue::KEY_DEL_EOL, + Keyboard::DelEos => glue::KEY_DEL_EOS, + Keyboard::DelLine => glue::KEY_DEL_LINE, + Keyboard::Delete => glue::KEY_DELETE, + Keyboard::DeleteFile => glue::KEY_DELETEFILE, + Keyboard::Digits => glue::KEY_DIGITS, + Keyboard::Directory => glue::KEY_DIRECTORY, + Keyboard::DisplayOff => glue::KEY_DISPLAY_OFF, + Keyboard::DisplayToggle => glue::KEY_DISPLAYTOGGLE, + Keyboard::Documents => glue::KEY_DOCUMENTS, + Keyboard::Dollar => glue::KEY_DOLLAR, + Keyboard::Dot => glue::KEY_DOT, + Keyboard::Down => glue::KEY_DOWN, + Keyboard::Dvd => glue::KEY_DVD, + Keyboard::E => glue::KEY_E, + Keyboard::Edit => glue::KEY_EDIT, + Keyboard::Editor => glue::KEY_EDITOR, + Keyboard::EjectCd => glue::KEY_EJECTCD, + Keyboard::EjectCloseCd => glue::KEY_EJECTCLOSECD, + Keyboard::Email => glue::KEY_EMAIL, + Keyboard::End => glue::KEY_END, + Keyboard::Enter => glue::KEY_ENTER, + Keyboard::Epg => glue::KEY_EPG, + Keyboard::Equal => glue::KEY_EQUAL, + Keyboard::Esc => glue::KEY_ESC, + Keyboard::Euro => glue::KEY_EURO, + Keyboard::Exit => glue::KEY_EXIT, + Keyboard::F => glue::KEY_F, + Keyboard::F1 => glue::KEY_F1, + Keyboard::F10 => glue::KEY_F10, + Keyboard::F11 => glue::KEY_F11, + Keyboard::F12 => glue::KEY_F12, + Keyboard::F13 => glue::KEY_F13, + Keyboard::F14 => glue::KEY_F14, + Keyboard::F15 => glue::KEY_F15, + Keyboard::F16 => glue::KEY_F16, + Keyboard::F17 => glue::KEY_F17, + Keyboard::F18 => glue::KEY_F18, + Keyboard::F19 => glue::KEY_F19, + Keyboard::F2 => glue::KEY_F2, + Keyboard::F20 => glue::KEY_F20, + Keyboard::F21 => glue::KEY_F21, + Keyboard::F22 => glue::KEY_F22, + Keyboard::F23 => glue::KEY_F23, + Keyboard::F24 => glue::KEY_F24, + Keyboard::F3 => glue::KEY_F3, + Keyboard::F4 => glue::KEY_F4, + Keyboard::F5 => glue::KEY_F5, + Keyboard::F6 => glue::KEY_F6, + Keyboard::F7 => glue::KEY_F7, + Keyboard::F8 => glue::KEY_F8, + Keyboard::F9 => glue::KEY_F9, + Keyboard::FastForward => glue::KEY_FASTFORWARD, + Keyboard::FastReverse => glue::KEY_FASTREVERSE, + Keyboard::Favorites => glue::KEY_FAVORITES, + Keyboard::File => glue::KEY_FILE, + Keyboard::Finance => glue::KEY_FINANCE, + Keyboard::Find => glue::KEY_FIND, + Keyboard::First => glue::KEY_FIRST, + Keyboard::Fn => glue::KEY_FN, + Keyboard::Fn1 => glue::KEY_FN_1, + Keyboard::Fn2 => glue::KEY_FN_2, + Keyboard::FnB => glue::KEY_FN_B, + Keyboard::FnD => glue::KEY_FN_D, + Keyboard::FnE => glue::KEY_FN_E, + Keyboard::FnEsc => glue::KEY_FN_ESC, + Keyboard::FnF => glue::KEY_FN_F, + Keyboard::FnF1 => glue::KEY_FN_F1, + Keyboard::FnF10 => glue::KEY_FN_F10, + Keyboard::FnF11 => glue::KEY_FN_F11, + Keyboard::FnF12 => glue::KEY_FN_F12, + Keyboard::FnF2 => glue::KEY_FN_F2, + Keyboard::FnF3 => glue::KEY_FN_F3, + Keyboard::FnF4 => glue::KEY_FN_F4, + Keyboard::FnF5 => glue::KEY_FN_F5, + Keyboard::FnF6 => glue::KEY_FN_F6, + Keyboard::FnF7 => glue::KEY_FN_F7, + Keyboard::FnF8 => glue::KEY_FN_F8, + Keyboard::FnF9 => glue::KEY_FN_F9, + Keyboard::FnS => glue::KEY_FN_S, + Keyboard::Forward => glue::KEY_FORWARD, + Keyboard::ForwardMail => glue::KEY_FORWARDMAIL, + Keyboard::Frameback => glue::KEY_FRAMEBACK, + Keyboard::FrameForward => glue::KEY_FRAMEFORWARD, + Keyboard::Front => glue::KEY_FRONT, + Keyboard::FullScreen => glue::KEY_FULL_SCREEN, + Keyboard::G => glue::KEY_G, + Keyboard::Games => glue::KEY_GAMES, + Keyboard::Goto => glue::KEY_GOTO, + Keyboard::GraphicsEditor => glue::KEY_GRAPHICSEDITOR, + Keyboard::Grave => glue::KEY_GRAVE, + Keyboard::Green => glue::KEY_GREEN, + Keyboard::H => glue::KEY_H, + Keyboard::Hangeul => glue::KEY_HANGEUL, + Keyboard::Hanja => glue::KEY_HANJA, + Keyboard::Help => glue::KEY_HELP, + Keyboard::Henkan => glue::KEY_HENKAN, + Keyboard::Hiragana => glue::KEY_HIRAGANA, + Keyboard::Home => glue::KEY_HOME, + Keyboard::Homepage => glue::KEY_HOMEPAGE, + Keyboard::Hp => glue::KEY_HP, + Keyboard::I => glue::KEY_I, + Keyboard::Images => glue::KEY_IMAGES, + Keyboard::Info => glue::KEY_INFO, + Keyboard::InsLine => glue::KEY_INS_LINE, + Keyboard::Insert => glue::KEY_INSERT, + Keyboard::Iso => glue::KEY_ISO, + Keyboard::J => glue::KEY_J, + Keyboard::Journal => glue::KEY_JOURNAL, + Keyboard::K => glue::KEY_K, + Keyboard::Katakana => glue::KEY_KATAKANA, + Keyboard::KatakanaHiragana => glue::KEY_KATAKANAHIRAGANA, + Keyboard::KbdLayoutNext => glue::KEY_KBD_LAYOUT_NEXT, + Keyboard::KbdLcdMenu1 => glue::KEY_KBD_LCD_MENU1, + Keyboard::KbdLcdMenu2 => glue::KEY_KBD_LCD_MENU2, + Keyboard::KbdLcdMenu3 => glue::KEY_KBD_LCD_MENU3, + Keyboard::KbdLcdMenu4 => glue::KEY_KBD_LCD_MENU4, + Keyboard::KbdLcdMenu5 => glue::KEY_KBD_LCD_MENU5, + Keyboard::KbdIllumDown => glue::KEY_KBDILLUMDOWN, + Keyboard::KbdIllumToggle => glue::KEY_KBDILLUMTOGGLE, + Keyboard::KbdIllumUp => glue::KEY_KBDILLUMUP, + Keyboard::KbdInputAssistAccept => glue::KEY_KBDINPUTASSIST_ACCEPT, + Keyboard::KbdInputAssistCancel => glue::KEY_KBDINPUTASSIST_CANCEL, + Keyboard::KbdInputAssistNext => glue::KEY_KBDINPUTASSIST_NEXT, + Keyboard::KbdInputAssistNextgroup => glue::KEY_KBDINPUTASSIST_NEXTGROUP, + Keyboard::KbdInputAssistPrev => glue::KEY_KBDINPUTASSIST_PREV, + Keyboard::KbdInputAssistPrevgroup => glue::KEY_KBDINPUTASSIST_PREVGROUP, + Keyboard::Keyboard => glue::KEY_KEYBOARD, + Keyboard::Kp0 => glue::KEY_KP0, + Keyboard::Kp1 => glue::KEY_KP1, + Keyboard::Kp2 => glue::KEY_KP2, + Keyboard::Kp3 => glue::KEY_KP3, + Keyboard::Kp4 => glue::KEY_KP4, + Keyboard::Kp5 => glue::KEY_KP5, + Keyboard::Kp6 => glue::KEY_KP6, + Keyboard::Kp7 => glue::KEY_KP7, + Keyboard::Kp8 => glue::KEY_KP8, + Keyboard::Kp9 => glue::KEY_KP9, + Keyboard::KpAsterisk => glue::KEY_KPASTERISK, + Keyboard::KpComma => glue::KEY_KPCOMMA, + Keyboard::KpDot => glue::KEY_KPDOT, + Keyboard::KpEnter => glue::KEY_KPENTER, + Keyboard::KpEqual => glue::KEY_KPEQUAL, + Keyboard::KpJpComma => glue::KEY_KPJPCOMMA, + Keyboard::KpLeftParen => glue::KEY_KPLEFTPAREN, + Keyboard::KpMinus => glue::KEY_KPMINUS, + Keyboard::KpPlus => glue::KEY_KPPLUS, + Keyboard::KpPlusMinus => glue::KEY_KPPLUSMINUS, + Keyboard::KpRightParen => glue::KEY_KPRIGHTPAREN, + Keyboard::KpSlash => glue::KEY_KPSLASH, + Keyboard::L => glue::KEY_L, + Keyboard::Language => glue::KEY_LANGUAGE, + Keyboard::Last => glue::KEY_LAST, + Keyboard::Left => glue::KEY_LEFT, + Keyboard::LeftDown => glue::KEY_LEFT_DOWN, + Keyboard::LeftUp => glue::KEY_LEFT_UP, + Keyboard::LeftAlt => glue::KEY_LEFTALT, + Keyboard::LeftBrace => glue::KEY_LEFTBRACE, + Keyboard::LeftCtrl => glue::KEY_LEFTCTRL, + Keyboard::LeftMeta => glue::KEY_LEFTMETA, + Keyboard::LeftShift => glue::KEY_LEFTSHIFT, + Keyboard::LightsToggle => glue::KEY_LIGHTS_TOGGLE, + Keyboard::LineFeed => glue::KEY_LINEFEED, + Keyboard::List => glue::KEY_LIST, + Keyboard::LogOff => glue::KEY_LOGOFF, + Keyboard::M => glue::KEY_M, + Keyboard::Macro => glue::KEY_MACRO, + Keyboard::Macro1 => glue::KEY_MACRO1, + Keyboard::Macro10 => glue::KEY_MACRO10, + Keyboard::Macro11 => glue::KEY_MACRO11, + Keyboard::Macro12 => glue::KEY_MACRO12, + Keyboard::Macro13 => glue::KEY_MACRO13, + Keyboard::Macro14 => glue::KEY_MACRO14, + Keyboard::Macro15 => glue::KEY_MACRO15, + Keyboard::Macro16 => glue::KEY_MACRO16, + Keyboard::Macro17 => glue::KEY_MACRO17, + Keyboard::Macro18 => glue::KEY_MACRO18, + Keyboard::Macro19 => glue::KEY_MACRO19, + Keyboard::Macro2 => glue::KEY_MACRO2, + Keyboard::Macro20 => glue::KEY_MACRO20, + Keyboard::Macro21 => glue::KEY_MACRO21, + Keyboard::Macro22 => glue::KEY_MACRO22, + Keyboard::Macro23 => glue::KEY_MACRO23, + Keyboard::Macro24 => glue::KEY_MACRO24, + Keyboard::Macro25 => glue::KEY_MACRO25, + Keyboard::Macro26 => glue::KEY_MACRO26, + Keyboard::Macro27 => glue::KEY_MACRO27, + Keyboard::Macro28 => glue::KEY_MACRO28, + Keyboard::Macro29 => glue::KEY_MACRO29, + Keyboard::Macro3 => glue::KEY_MACRO3, + Keyboard::Macro30 => glue::KEY_MACRO30, + Keyboard::Macro4 => glue::KEY_MACRO4, + Keyboard::Macro5 => glue::KEY_MACRO5, + Keyboard::Macro6 => glue::KEY_MACRO6, + Keyboard::Macro7 => glue::KEY_MACRO7, + Keyboard::Macro8 => glue::KEY_MACRO8, + Keyboard::Macro9 => glue::KEY_MACRO9, + Keyboard::MacroPreset1 => glue::KEY_MACRO_PRESET1, + Keyboard::MacroPreset2 => glue::KEY_MACRO_PRESET2, + Keyboard::MacroPreset3 => glue::KEY_MACRO_PRESET3, + Keyboard::MacroPresetCycle => glue::KEY_MACRO_PRESET_CYCLE, + Keyboard::MacroRecordStart => glue::KEY_MACRO_RECORD_START, + Keyboard::MacroRecordStop => glue::KEY_MACRO_RECORD_STOP, + Keyboard::Mail => glue::KEY_MAIL, + Keyboard::Media => glue::KEY_MEDIA, + Keyboard::MediaRepeat => glue::KEY_MEDIA_REPEAT, + Keyboard::MediaTopMenu => glue::KEY_MEDIA_TOP_MENU, + Keyboard::Memo => glue::KEY_MEMO, + Keyboard::Menu => glue::KEY_MENU, + Keyboard::Messenger => glue::KEY_MESSENGER, + Keyboard::Mhp => glue::KEY_MHP, + Keyboard::MicMute => glue::KEY_MICMUTE, + Keyboard::Minus => glue::KEY_MINUS, + Keyboard::Mode => glue::KEY_MODE, + Keyboard::Move => glue::KEY_MOVE, + Keyboard::Mp3 => glue::KEY_MP3, + Keyboard::MsDos => glue::KEY_MSDOS, + Keyboard::Muhenkan => glue::KEY_MUHENKAN, + Keyboard::Mute => glue::KEY_MUTE, + Keyboard::N => glue::KEY_N, + Keyboard::N0 => glue::KEY_0, + Keyboard::N1 => glue::KEY_1, + Keyboard::N102nd => glue::KEY_102ND, + Keyboard::N10ChannelsDown => glue::KEY_10CHANNELSDOWN, + Keyboard::N10ChannelsUp => glue::KEY_10CHANNELSUP, + Keyboard::N2 => glue::KEY_2, + Keyboard::N3 => glue::KEY_3, + Keyboard::N3dMode => glue::KEY_3D_MODE, + Keyboard::N4 => glue::KEY_4, + Keyboard::N5 => glue::KEY_5, + Keyboard::N6 => glue::KEY_6, + Keyboard::N7 => glue::KEY_7, + Keyboard::N8 => glue::KEY_8, + Keyboard::N9 => glue::KEY_9, + Keyboard::New => glue::KEY_NEW, + Keyboard::News => glue::KEY_NEWS, + Keyboard::Next => glue::KEY_NEXT, + Keyboard::NextFavorite => glue::KEY_NEXT_FAVORITE, + Keyboard::NextSong => glue::KEY_NEXTSONG, + Keyboard::Numeric0 => glue::KEY_NUMERIC_0, + Keyboard::Numeric1 => glue::KEY_NUMERIC_1, + Keyboard::Numeric11 => glue::KEY_NUMERIC_11, + Keyboard::Numeric12 => glue::KEY_NUMERIC_12, + Keyboard::Numeric2 => glue::KEY_NUMERIC_2, + Keyboard::Numeric3 => glue::KEY_NUMERIC_3, + Keyboard::Numeric4 => glue::KEY_NUMERIC_4, + Keyboard::Numeric5 => glue::KEY_NUMERIC_5, + Keyboard::Numeric6 => glue::KEY_NUMERIC_6, + Keyboard::Numeric7 => glue::KEY_NUMERIC_7, + Keyboard::Numeric8 => glue::KEY_NUMERIC_8, + Keyboard::Numeric9 => glue::KEY_NUMERIC_9, + Keyboard::NumericA => glue::KEY_NUMERIC_A, + Keyboard::NumericB => glue::KEY_NUMERIC_B, + Keyboard::NumericC => glue::KEY_NUMERIC_C, + Keyboard::NumericD => glue::KEY_NUMERIC_D, + Keyboard::NumericPound => glue::KEY_NUMERIC_POUND, + Keyboard::NumericStar => glue::KEY_NUMERIC_STAR, + Keyboard::NumLock => glue::KEY_NUMLOCK, + Keyboard::O => glue::KEY_O, + Keyboard::Ok => glue::KEY_OK, + Keyboard::OnscreenKeyboard => glue::KEY_ONSCREEN_KEYBOARD, + Keyboard::Open => glue::KEY_OPEN, + Keyboard::Option => glue::KEY_OPTION, + Keyboard::P => glue::KEY_P, + Keyboard::PageDown => glue::KEY_PAGEDOWN, + Keyboard::PageUp => glue::KEY_PAGEUP, + Keyboard::Paste => glue::KEY_PASTE, + Keyboard::Pause => glue::KEY_PAUSE, + Keyboard::PauseRecord => glue::KEY_PAUSE_RECORD, + Keyboard::PauseCd => glue::KEY_PAUSECD, + Keyboard::Pc => glue::KEY_PC, + Keyboard::Phone => glue::KEY_PHONE, + Keyboard::Play => glue::KEY_PLAY, + Keyboard::PlayCd => glue::KEY_PLAYCD, + Keyboard::Player => glue::KEY_PLAYER, + Keyboard::PlayPause => glue::KEY_PLAYPAUSE, + Keyboard::Power => glue::KEY_POWER, + Keyboard::Power2 => glue::KEY_POWER2, + Keyboard::Presentation => glue::KEY_PRESENTATION, + Keyboard::Previous => glue::KEY_PREVIOUS, + Keyboard::PreviousSong => glue::KEY_PREVIOUSSONG, + Keyboard::Print => glue::KEY_PRINT, + Keyboard::PrivacyScreenToggle => glue::KEY_PRIVACY_SCREEN_TOGGLE, + Keyboard::Prog1 => glue::KEY_PROG1, + Keyboard::Prog2 => glue::KEY_PROG2, + Keyboard::Prog3 => glue::KEY_PROG3, + Keyboard::Prog4 => glue::KEY_PROG4, + Keyboard::Program => glue::KEY_PROGRAM, + Keyboard::Props => glue::KEY_PROPS, + Keyboard::Pvr => glue::KEY_PVR, + Keyboard::Q => glue::KEY_Q, + Keyboard::Question => glue::KEY_QUESTION, + Keyboard::R => glue::KEY_R, + Keyboard::Radio => glue::KEY_RADIO, + Keyboard::Record => glue::KEY_RECORD, + Keyboard::Red => glue::KEY_RED, + Keyboard::Redo => glue::KEY_REDO, + Keyboard::Refresh => glue::KEY_REFRESH, + Keyboard::Reply => glue::KEY_REPLY, + Keyboard::Reserved => glue::KEY_RESERVED, + Keyboard::Restart => glue::KEY_RESTART, + Keyboard::Rewind => glue::KEY_REWIND, + Keyboard::RfKill => glue::KEY_RFKILL, + Keyboard::Right => glue::KEY_RIGHT, + Keyboard::RightDown => glue::KEY_RIGHT_DOWN, + Keyboard::RightUp => glue::KEY_RIGHT_UP, + Keyboard::RightAlt => glue::KEY_RIGHTALT, + Keyboard::RightBrace => glue::KEY_RIGHTBRACE, + Keyboard::RightCtrl => glue::KEY_RIGHTCTRL, + Keyboard::RightMeta => glue::KEY_RIGHTMETA, + Keyboard::RightShift => glue::KEY_RIGHTSHIFT, + Keyboard::Ro => glue::KEY_RO, + Keyboard::RootMenu => glue::KEY_ROOT_MENU, + Keyboard::RotateDisplay => glue::KEY_ROTATE_DISPLAY, + Keyboard::RotateLockToggle => glue::KEY_ROTATE_LOCK_TOGGLE, + Keyboard::S => glue::KEY_S, + Keyboard::Sat => glue::KEY_SAT, + Keyboard::Sat2 => glue::KEY_SAT2, + Keyboard::Save => glue::KEY_SAVE, + Keyboard::Scale => glue::KEY_SCALE, + Keyboard::Screensaver => glue::KEY_SCREENSAVER, + Keyboard::ScrollDown => glue::KEY_SCROLLDOWN, + Keyboard::ScrollLock => glue::KEY_SCROLLLOCK, + Keyboard::ScrollUp => glue::KEY_SCROLLUP, + Keyboard::Search => glue::KEY_SEARCH, + Keyboard::Select => glue::KEY_SELECT, + Keyboard::SelectiveScreenshot => glue::KEY_SELECTIVE_SCREENSHOT, + Keyboard::Semicolon => glue::KEY_SEMICOLON, + Keyboard::Send => glue::KEY_SEND, + Keyboard::SendFile => glue::KEY_SENDFILE, + Keyboard::Setup => glue::KEY_SETUP, + Keyboard::Shop => glue::KEY_SHOP, + Keyboard::Shuffle => glue::KEY_SHUFFLE, + Keyboard::Slash => glue::KEY_SLASH, + Keyboard::Sleep => glue::KEY_SLEEP, + Keyboard::Slow => glue::KEY_SLOW, + Keyboard::SlowReverse => glue::KEY_SLOWREVERSE, + Keyboard::Sound => glue::KEY_SOUND, + Keyboard::Space => glue::KEY_SPACE, + Keyboard::Spellcheck => glue::KEY_SPELLCHECK, + Keyboard::Sport => glue::KEY_SPORT, + Keyboard::Spreadsheet => glue::KEY_SPREADSHEET, + Keyboard::Stop => glue::KEY_STOP, + Keyboard::StopRecord => glue::KEY_STOP_RECORD, + Keyboard::StopCd => glue::KEY_STOPCD, + Keyboard::Subtitle => glue::KEY_SUBTITLE, + Keyboard::Suspend => glue::KEY_SUSPEND, + Keyboard::SwitchVideoMode => glue::KEY_SWITCHVIDEOMODE, + Keyboard::SysRq => glue::KEY_SYSRQ, + Keyboard::T => glue::KEY_T, + Keyboard::Tab => glue::KEY_TAB, + Keyboard::Tape => glue::KEY_TAPE, + Keyboard::TaskManager => glue::KEY_TASKMANAGER, + Keyboard::Teen => glue::KEY_TEEN, + Keyboard::Text => glue::KEY_TEXT, + Keyboard::Time => glue::KEY_TIME, + Keyboard::Title => glue::KEY_TITLE, + Keyboard::TouchpadOff => glue::KEY_TOUCHPAD_OFF, + Keyboard::TouchpadOn => glue::KEY_TOUCHPAD_ON, + Keyboard::TouchpadToggle => glue::KEY_TOUCHPAD_TOGGLE, + Keyboard::Tuner => glue::KEY_TUNER, + Keyboard::Tv => glue::KEY_TV, + Keyboard::Tv2 => glue::KEY_TV2, + Keyboard::Twen => glue::KEY_TWEN, + Keyboard::U => glue::KEY_U, + Keyboard::Undo => glue::KEY_UNDO, + Keyboard::Unknown => glue::KEY_UNKNOWN, + Keyboard::Unmute => glue::KEY_UNMUTE, + Keyboard::Up => glue::KEY_UP, + Keyboard::Uwb => glue::KEY_UWB, + Keyboard::V => glue::KEY_V, + Keyboard::Vcr => glue::KEY_VCR, + Keyboard::Vcr2 => glue::KEY_VCR2, + Keyboard::Vendor => glue::KEY_VENDOR, + Keyboard::Video => glue::KEY_VIDEO, + Keyboard::VideoNext => glue::KEY_VIDEO_NEXT, + Keyboard::VideoPrev => glue::KEY_VIDEO_PREV, + Keyboard::VideoPhone => glue::KEY_VIDEOPHONE, + Keyboard::Vod => glue::KEY_VOD, + Keyboard::VoiceCommand => glue::KEY_VOICECOMMAND, + Keyboard::VoiceMail => glue::KEY_VOICEMAIL, + Keyboard::VolumeDown => glue::KEY_VOLUMEDOWN, + Keyboard::VolumeUp => glue::KEY_VOLUMEUP, + Keyboard::W => glue::KEY_W, + Keyboard::WakeUp => glue::KEY_WAKEUP, + Keyboard::Wlan => glue::KEY_WLAN, + Keyboard::WordProcessor => glue::KEY_WORDPROCESSOR, + Keyboard::WpsButton => glue::KEY_WPS_BUTTON, + Keyboard::Wwan => glue::KEY_WWAN, + Keyboard::Www => glue::KEY_WWW, + Keyboard::X => glue::KEY_X, + Keyboard::Xfer => glue::KEY_XFER, + Keyboard::Y => glue::KEY_Y, + Keyboard::Yellow => glue::KEY_YELLOW, + Keyboard::Yen => glue::KEY_YEN, + Keyboard::Z => glue::KEY_Z, + Keyboard::ZenkakuHankaku => glue::KEY_ZENKAKUHANKAKU, + Keyboard::ZoomIn => glue::KEY_ZOOMIN, + Keyboard::ZoomOut => glue::KEY_ZOOMOUT, + Keyboard::ZoomReset => glue::KEY_ZOOMRESET, + }; + + Some(raw as _) + } + + fn from_raw(raw: Self::Raw) -> Option { + let keyboard = match raw as _ { + glue::KEY_A => Keyboard::A, + glue::KEY_AB => Keyboard::Ab, + glue::KEY_ADDRESSBOOK => Keyboard::AddressBook, + glue::KEY_AGAIN => Keyboard::Again, + glue::KEY_ALS_TOGGLE => Keyboard::AlsToggle, + glue::KEY_ALTERASE => Keyboard::AltErase, + glue::KEY_ANGLE => Keyboard::Angle, + glue::KEY_APOSTROPHE => Keyboard::Apostrophe, + glue::KEY_APPSELECT => Keyboard::Appselect, + glue::KEY_ARCHIVE => Keyboard::Archive, + glue::KEY_ASPECT_RATIO => Keyboard::AspectRatio, + glue::KEY_ASSISTANT => Keyboard::Assistant, + glue::KEY_ATTENDANT_OFF => Keyboard::AttendantOff, + glue::KEY_ATTENDANT_ON => Keyboard::AttendantOn, + glue::KEY_ATTENDANT_TOGGLE => Keyboard::AttendantToggle, + glue::KEY_AUDIO => Keyboard::Audio, + glue::KEY_AUDIO_DESC => Keyboard::AudioDesc, + glue::KEY_AUX => Keyboard::Aux, + glue::KEY_B => Keyboard::B, + glue::KEY_BACK => Keyboard::Back, + glue::KEY_BACKSLASH => Keyboard::Backslash, + glue::KEY_BACKSPACE => Keyboard::Backspace, + glue::KEY_BASSBOOST => Keyboard::BassBoost, + glue::KEY_BATTERY => Keyboard::Battery, + glue::KEY_BLUE => Keyboard::Blue, + glue::KEY_BLUETOOTH => Keyboard::Bluetooth, + glue::KEY_BOOKMARKS => Keyboard::Bookmarks, + glue::KEY_BREAK => Keyboard::Break, + glue::KEY_BRIGHTNESS_AUTO => Keyboard::BrightnessAuto, + glue::KEY_BRIGHTNESS_CYCLE => Keyboard::BrightnessCycle, + glue::KEY_BRIGHTNESS_MAX => Keyboard::BrightnessMax, + glue::KEY_BRIGHTNESS_MIN => Keyboard::BrightnessMin, + glue::KEY_BRIGHTNESS_TOGGLE => Keyboard::BrightnessToggle, + glue::KEY_BRIGHTNESSDOWN => Keyboard::BrightnessDown, + glue::KEY_BRIGHTNESSUP => Keyboard::BrightnessUp, + glue::KEY_BRL_DOT1 => Keyboard::BrlDot1, + glue::KEY_BRL_DOT10 => Keyboard::BrlDot10, + glue::KEY_BRL_DOT2 => Keyboard::BrlDot2, + glue::KEY_BRL_DOT3 => Keyboard::BrlDot3, + glue::KEY_BRL_DOT4 => Keyboard::BrlDot4, + glue::KEY_BRL_DOT5 => Keyboard::BrlDot5, + glue::KEY_BRL_DOT6 => Keyboard::BrlDot6, + glue::KEY_BRL_DOT7 => Keyboard::BrlDot7, + glue::KEY_BRL_DOT8 => Keyboard::BrlDot8, + glue::KEY_BRL_DOT9 => Keyboard::BrlDot9, + glue::KEY_BUTTONCONFIG => Keyboard::ButtonConfig, + glue::KEY_C => Keyboard::C, + glue::KEY_CALC => Keyboard::Calc, + glue::KEY_CALENDAR => Keyboard::Calendar, + glue::KEY_CAMERA => Keyboard::Camera, + glue::KEY_CAMERA_DOWN => Keyboard::CameraDown, + glue::KEY_CAMERA_FOCUS => Keyboard::CameraFocus, + glue::KEY_CAMERA_LEFT => Keyboard::CameraLeft, + glue::KEY_CAMERA_RIGHT => Keyboard::CameraRight, + glue::KEY_CAMERA_UP => Keyboard::CameraUp, + glue::KEY_CAMERA_ZOOMIN => Keyboard::CameraZoomIn, + glue::KEY_CAMERA_ZOOMOUT => Keyboard::CameraZoomOut, + glue::KEY_CANCEL => Keyboard::Cancel, + glue::KEY_CAPSLOCK => Keyboard::CapsLock, + glue::KEY_CD => Keyboard::Cd, + glue::KEY_CHANNEL => Keyboard::Channel, + glue::KEY_CHANNELDOWN => Keyboard::ChannelDown, + glue::KEY_CHANNELUP => Keyboard::ChannelUp, + glue::KEY_CHAT => Keyboard::Chat, + glue::KEY_CLEAR => Keyboard::Clear, + glue::KEY_CLOSE => Keyboard::Close, + glue::KEY_CLOSECD => Keyboard::CloseCd, + glue::KEY_COFFEE => Keyboard::Coffee, + glue::KEY_COMMA => Keyboard::Comma, + glue::KEY_COMPOSE => Keyboard::Compose, + glue::KEY_COMPUTER => Keyboard::Computer, + glue::KEY_CONFIG => Keyboard::Config, + glue::KEY_CONNECT => Keyboard::Connect, + glue::KEY_CONTEXT_MENU => Keyboard::ContextMenu, + glue::KEY_CONTROLPANEL => Keyboard::Controlpanel, + glue::KEY_COPY => Keyboard::Copy, + glue::KEY_CUT => Keyboard::Cut, + glue::KEY_CYCLEWINDOWS => Keyboard::CycleWindows, + glue::KEY_D => Keyboard::D, + glue::KEY_DASHBOARD => Keyboard::Dashboard, + glue::KEY_DATA => Keyboard::Data, + glue::KEY_DATABASE => Keyboard::Database, + glue::KEY_DEL_EOL => Keyboard::DelEol, + glue::KEY_DEL_EOS => Keyboard::DelEos, + glue::KEY_DEL_LINE => Keyboard::DelLine, + glue::KEY_DELETE => Keyboard::Delete, + glue::KEY_DELETEFILE => Keyboard::DeleteFile, + glue::KEY_DIGITS => Keyboard::Digits, + glue::KEY_DIRECTORY => Keyboard::Directory, + glue::KEY_DISPLAY_OFF => Keyboard::DisplayOff, + glue::KEY_DOCUMENTS => Keyboard::Documents, + glue::KEY_DOLLAR => Keyboard::Dollar, + glue::KEY_DOT => Keyboard::Dot, + glue::KEY_DOWN => Keyboard::Down, + glue::KEY_DVD => Keyboard::Dvd, + glue::KEY_E => Keyboard::E, + glue::KEY_EDIT => Keyboard::Edit, + glue::KEY_EDITOR => Keyboard::Editor, + glue::KEY_EJECTCD => Keyboard::EjectCd, + glue::KEY_EJECTCLOSECD => Keyboard::EjectCloseCd, + glue::KEY_EMAIL => Keyboard::Email, + glue::KEY_END => Keyboard::End, + glue::KEY_ENTER => Keyboard::Enter, + glue::KEY_EPG => Keyboard::Epg, + glue::KEY_EQUAL => Keyboard::Equal, + glue::KEY_ESC => Keyboard::Esc, + glue::KEY_EURO => Keyboard::Euro, + glue::KEY_EXIT => Keyboard::Exit, + glue::KEY_F => Keyboard::F, + glue::KEY_F1 => Keyboard::F1, + glue::KEY_F10 => Keyboard::F10, + glue::KEY_F11 => Keyboard::F11, + glue::KEY_F12 => Keyboard::F12, + glue::KEY_F13 => Keyboard::F13, + glue::KEY_F14 => Keyboard::F14, + glue::KEY_F15 => Keyboard::F15, + glue::KEY_F16 => Keyboard::F16, + glue::KEY_F17 => Keyboard::F17, + glue::KEY_F18 => Keyboard::F18, + glue::KEY_F19 => Keyboard::F19, + glue::KEY_F2 => Keyboard::F2, + glue::KEY_F20 => Keyboard::F20, + glue::KEY_F21 => Keyboard::F21, + glue::KEY_F22 => Keyboard::F22, + glue::KEY_F23 => Keyboard::F23, + glue::KEY_F24 => Keyboard::F24, + glue::KEY_F3 => Keyboard::F3, + glue::KEY_F4 => Keyboard::F4, + glue::KEY_F5 => Keyboard::F5, + glue::KEY_F6 => Keyboard::F6, + glue::KEY_F7 => Keyboard::F7, + glue::KEY_F8 => Keyboard::F8, + glue::KEY_F9 => Keyboard::F9, + glue::KEY_FASTFORWARD => Keyboard::FastForward, + glue::KEY_FASTREVERSE => Keyboard::FastReverse, + glue::KEY_FAVORITES => Keyboard::Favorites, + glue::KEY_FILE => Keyboard::File, + glue::KEY_FINANCE => Keyboard::Finance, + glue::KEY_FIND => Keyboard::Find, + glue::KEY_FIRST => Keyboard::First, + glue::KEY_FN => Keyboard::Fn, + glue::KEY_FN_1 => Keyboard::Fn1, + glue::KEY_FN_2 => Keyboard::Fn2, + glue::KEY_FN_B => Keyboard::FnB, + glue::KEY_FN_D => Keyboard::FnD, + glue::KEY_FN_E => Keyboard::FnE, + glue::KEY_FN_ESC => Keyboard::FnEsc, + glue::KEY_FN_F => Keyboard::FnF, + glue::KEY_FN_F1 => Keyboard::FnF1, + glue::KEY_FN_F10 => Keyboard::FnF10, + glue::KEY_FN_F11 => Keyboard::FnF11, + glue::KEY_FN_F12 => Keyboard::FnF12, + glue::KEY_FN_F2 => Keyboard::FnF2, + glue::KEY_FN_F3 => Keyboard::FnF3, + glue::KEY_FN_F4 => Keyboard::FnF4, + glue::KEY_FN_F5 => Keyboard::FnF5, + glue::KEY_FN_F6 => Keyboard::FnF6, + glue::KEY_FN_F7 => Keyboard::FnF7, + glue::KEY_FN_F8 => Keyboard::FnF8, + glue::KEY_FN_F9 => Keyboard::FnF9, + glue::KEY_FN_S => Keyboard::FnS, + glue::KEY_FORWARD => Keyboard::Forward, + glue::KEY_FORWARDMAIL => Keyboard::ForwardMail, + glue::KEY_FRAMEBACK => Keyboard::Frameback, + glue::KEY_FRAMEFORWARD => Keyboard::FrameForward, + glue::KEY_FRONT => Keyboard::Front, + glue::KEY_FULL_SCREEN => Keyboard::FullScreen, + glue::KEY_G => Keyboard::G, + glue::KEY_GAMES => Keyboard::Games, + glue::KEY_GOTO => Keyboard::Goto, + glue::KEY_GRAPHICSEDITOR => Keyboard::GraphicsEditor, + glue::KEY_GRAVE => Keyboard::Grave, + glue::KEY_GREEN => Keyboard::Green, + glue::KEY_H => Keyboard::H, + glue::KEY_HANGEUL => Keyboard::Hangeul, + glue::KEY_HANJA => Keyboard::Hanja, + glue::KEY_HELP => Keyboard::Help, + glue::KEY_HENKAN => Keyboard::Henkan, + glue::KEY_HIRAGANA => Keyboard::Hiragana, + glue::KEY_HOME => Keyboard::Home, + glue::KEY_HOMEPAGE => Keyboard::Homepage, + glue::KEY_HP => Keyboard::Hp, + glue::KEY_I => Keyboard::I, + glue::KEY_IMAGES => Keyboard::Images, + glue::KEY_INFO => Keyboard::Info, + glue::KEY_INS_LINE => Keyboard::InsLine, + glue::KEY_INSERT => Keyboard::Insert, + glue::KEY_ISO => Keyboard::Iso, + glue::KEY_J => Keyboard::J, + glue::KEY_JOURNAL => Keyboard::Journal, + glue::KEY_K => Keyboard::K, + glue::KEY_KATAKANA => Keyboard::Katakana, + glue::KEY_KATAKANAHIRAGANA => Keyboard::KatakanaHiragana, + glue::KEY_KBD_LAYOUT_NEXT => Keyboard::KbdLayoutNext, + glue::KEY_KBD_LCD_MENU1 => Keyboard::KbdLcdMenu1, + glue::KEY_KBD_LCD_MENU2 => Keyboard::KbdLcdMenu2, + glue::KEY_KBD_LCD_MENU3 => Keyboard::KbdLcdMenu3, + glue::KEY_KBD_LCD_MENU4 => Keyboard::KbdLcdMenu4, + glue::KEY_KBD_LCD_MENU5 => Keyboard::KbdLcdMenu5, + glue::KEY_KBDILLUMDOWN => Keyboard::KbdIllumDown, + glue::KEY_KBDILLUMTOGGLE => Keyboard::KbdIllumToggle, + glue::KEY_KBDILLUMUP => Keyboard::KbdIllumUp, + glue::KEY_KBDINPUTASSIST_ACCEPT => Keyboard::KbdInputAssistAccept, + glue::KEY_KBDINPUTASSIST_CANCEL => Keyboard::KbdInputAssistCancel, + glue::KEY_KBDINPUTASSIST_NEXT => Keyboard::KbdInputAssistNext, + glue::KEY_KBDINPUTASSIST_NEXTGROUP => Keyboard::KbdInputAssistNextgroup, + glue::KEY_KBDINPUTASSIST_PREV => Keyboard::KbdInputAssistPrev, + glue::KEY_KBDINPUTASSIST_PREVGROUP => Keyboard::KbdInputAssistPrevgroup, + glue::KEY_KEYBOARD => Keyboard::Keyboard, + glue::KEY_KP0 => Keyboard::Kp0, + glue::KEY_KP1 => Keyboard::Kp1, + glue::KEY_KP2 => Keyboard::Kp2, + glue::KEY_KP3 => Keyboard::Kp3, + glue::KEY_KP4 => Keyboard::Kp4, + glue::KEY_KP5 => Keyboard::Kp5, + glue::KEY_KP6 => Keyboard::Kp6, + glue::KEY_KP7 => Keyboard::Kp7, + glue::KEY_KP8 => Keyboard::Kp8, + glue::KEY_KP9 => Keyboard::Kp9, + glue::KEY_KPASTERISK => Keyboard::KpAsterisk, + glue::KEY_KPCOMMA => Keyboard::KpComma, + glue::KEY_KPDOT => Keyboard::KpDot, + glue::KEY_KPENTER => Keyboard::KpEnter, + glue::KEY_KPEQUAL => Keyboard::KpEqual, + glue::KEY_KPJPCOMMA => Keyboard::KpJpComma, + glue::KEY_KPLEFTPAREN => Keyboard::KpLeftParen, + glue::KEY_KPMINUS => Keyboard::KpMinus, + glue::KEY_KPPLUS => Keyboard::KpPlus, + glue::KEY_KPPLUSMINUS => Keyboard::KpPlusMinus, + glue::KEY_KPRIGHTPAREN => Keyboard::KpRightParen, + glue::KEY_KPSLASH => Keyboard::KpSlash, + glue::KEY_L => Keyboard::L, + glue::KEY_LANGUAGE => Keyboard::Language, + glue::KEY_LAST => Keyboard::Last, + glue::KEY_LEFT => Keyboard::Left, + glue::KEY_LEFT_DOWN => Keyboard::LeftDown, + glue::KEY_LEFT_UP => Keyboard::LeftUp, + glue::KEY_LEFTALT => Keyboard::LeftAlt, + glue::KEY_LEFTBRACE => Keyboard::LeftBrace, + glue::KEY_LEFTCTRL => Keyboard::LeftCtrl, + glue::KEY_LEFTMETA => Keyboard::LeftMeta, + glue::KEY_LEFTSHIFT => Keyboard::LeftShift, + glue::KEY_LIGHTS_TOGGLE => Keyboard::LightsToggle, + glue::KEY_LINEFEED => Keyboard::LineFeed, + glue::KEY_LIST => Keyboard::List, + glue::KEY_LOGOFF => Keyboard::LogOff, + glue::KEY_M => Keyboard::M, + glue::KEY_MACRO => Keyboard::Macro, + glue::KEY_MACRO1 => Keyboard::Macro1, + glue::KEY_MACRO10 => Keyboard::Macro10, + glue::KEY_MACRO11 => Keyboard::Macro11, + glue::KEY_MACRO12 => Keyboard::Macro12, + glue::KEY_MACRO13 => Keyboard::Macro13, + glue::KEY_MACRO14 => Keyboard::Macro14, + glue::KEY_MACRO15 => Keyboard::Macro15, + glue::KEY_MACRO16 => Keyboard::Macro16, + glue::KEY_MACRO17 => Keyboard::Macro17, + glue::KEY_MACRO18 => Keyboard::Macro18, + glue::KEY_MACRO19 => Keyboard::Macro19, + glue::KEY_MACRO2 => Keyboard::Macro2, + glue::KEY_MACRO20 => Keyboard::Macro20, + glue::KEY_MACRO21 => Keyboard::Macro21, + glue::KEY_MACRO22 => Keyboard::Macro22, + glue::KEY_MACRO23 => Keyboard::Macro23, + glue::KEY_MACRO24 => Keyboard::Macro24, + glue::KEY_MACRO25 => Keyboard::Macro25, + glue::KEY_MACRO26 => Keyboard::Macro26, + glue::KEY_MACRO27 => Keyboard::Macro27, + glue::KEY_MACRO28 => Keyboard::Macro28, + glue::KEY_MACRO29 => Keyboard::Macro29, + glue::KEY_MACRO3 => Keyboard::Macro3, + glue::KEY_MACRO30 => Keyboard::Macro30, + glue::KEY_MACRO4 => Keyboard::Macro4, + glue::KEY_MACRO5 => Keyboard::Macro5, + glue::KEY_MACRO6 => Keyboard::Macro6, + glue::KEY_MACRO7 => Keyboard::Macro7, + glue::KEY_MACRO8 => Keyboard::Macro8, + glue::KEY_MACRO9 => Keyboard::Macro9, + glue::KEY_MACRO_PRESET1 => Keyboard::MacroPreset1, + glue::KEY_MACRO_PRESET2 => Keyboard::MacroPreset2, + glue::KEY_MACRO_PRESET3 => Keyboard::MacroPreset3, + glue::KEY_MACRO_PRESET_CYCLE => Keyboard::MacroPresetCycle, + glue::KEY_MACRO_RECORD_START => Keyboard::MacroRecordStart, + glue::KEY_MACRO_RECORD_STOP => Keyboard::MacroRecordStop, + glue::KEY_MAIL => Keyboard::Mail, + glue::KEY_MEDIA => Keyboard::Media, + glue::KEY_MEDIA_REPEAT => Keyboard::MediaRepeat, + glue::KEY_MEDIA_TOP_MENU => Keyboard::MediaTopMenu, + glue::KEY_MEMO => Keyboard::Memo, + glue::KEY_MENU => Keyboard::Menu, + glue::KEY_MESSENGER => Keyboard::Messenger, + glue::KEY_MHP => Keyboard::Mhp, + glue::KEY_MICMUTE => Keyboard::MicMute, + glue::KEY_MINUS => Keyboard::Minus, + glue::KEY_MODE => Keyboard::Mode, + glue::KEY_MOVE => Keyboard::Move, + glue::KEY_MP3 => Keyboard::Mp3, + glue::KEY_MSDOS => Keyboard::MsDos, + glue::KEY_MUHENKAN => Keyboard::Muhenkan, + glue::KEY_MUTE => Keyboard::Mute, + glue::KEY_N => Keyboard::N, + glue::KEY_0 => Keyboard::N0, + glue::KEY_1 => Keyboard::N1, + glue::KEY_102ND => Keyboard::N102nd, + glue::KEY_10CHANNELSDOWN => Keyboard::N10ChannelsDown, + glue::KEY_10CHANNELSUP => Keyboard::N10ChannelsUp, + glue::KEY_2 => Keyboard::N2, + glue::KEY_3 => Keyboard::N3, + glue::KEY_3D_MODE => Keyboard::N3dMode, + glue::KEY_4 => Keyboard::N4, + glue::KEY_5 => Keyboard::N5, + glue::KEY_6 => Keyboard::N6, + glue::KEY_7 => Keyboard::N7, + glue::KEY_8 => Keyboard::N8, + glue::KEY_9 => Keyboard::N9, + glue::KEY_NEW => Keyboard::New, + glue::KEY_NEWS => Keyboard::News, + glue::KEY_NEXT => Keyboard::Next, + glue::KEY_NEXT_FAVORITE => Keyboard::NextFavorite, + glue::KEY_NEXTSONG => Keyboard::NextSong, + glue::KEY_NUMERIC_0 => Keyboard::Numeric0, + glue::KEY_NUMERIC_1 => Keyboard::Numeric1, + glue::KEY_NUMERIC_11 => Keyboard::Numeric11, + glue::KEY_NUMERIC_12 => Keyboard::Numeric12, + glue::KEY_NUMERIC_2 => Keyboard::Numeric2, + glue::KEY_NUMERIC_3 => Keyboard::Numeric3, + glue::KEY_NUMERIC_4 => Keyboard::Numeric4, + glue::KEY_NUMERIC_5 => Keyboard::Numeric5, + glue::KEY_NUMERIC_6 => Keyboard::Numeric6, + glue::KEY_NUMERIC_7 => Keyboard::Numeric7, + glue::KEY_NUMERIC_8 => Keyboard::Numeric8, + glue::KEY_NUMERIC_9 => Keyboard::Numeric9, + glue::KEY_NUMERIC_A => Keyboard::NumericA, + glue::KEY_NUMERIC_B => Keyboard::NumericB, + glue::KEY_NUMERIC_C => Keyboard::NumericC, + glue::KEY_NUMERIC_D => Keyboard::NumericD, + glue::KEY_NUMERIC_POUND => Keyboard::NumericPound, + glue::KEY_NUMERIC_STAR => Keyboard::NumericStar, + glue::KEY_NUMLOCK => Keyboard::NumLock, + glue::KEY_O => Keyboard::O, + glue::KEY_OK => Keyboard::Ok, + glue::KEY_ONSCREEN_KEYBOARD => Keyboard::OnscreenKeyboard, + glue::KEY_OPEN => Keyboard::Open, + glue::KEY_OPTION => Keyboard::Option, + glue::KEY_P => Keyboard::P, + glue::KEY_PAGEDOWN => Keyboard::PageDown, + glue::KEY_PAGEUP => Keyboard::PageUp, + glue::KEY_PASTE => Keyboard::Paste, + glue::KEY_PAUSE => Keyboard::Pause, + glue::KEY_PAUSE_RECORD => Keyboard::PauseRecord, + glue::KEY_PAUSECD => Keyboard::PauseCd, + glue::KEY_PC => Keyboard::Pc, + glue::KEY_PHONE => Keyboard::Phone, + glue::KEY_PLAY => Keyboard::Play, + glue::KEY_PLAYCD => Keyboard::PlayCd, + glue::KEY_PLAYER => Keyboard::Player, + glue::KEY_PLAYPAUSE => Keyboard::PlayPause, + glue::KEY_POWER => Keyboard::Power, + glue::KEY_POWER2 => Keyboard::Power2, + glue::KEY_PRESENTATION => Keyboard::Presentation, + glue::KEY_PREVIOUS => Keyboard::Previous, + glue::KEY_PREVIOUSSONG => Keyboard::PreviousSong, + glue::KEY_PRINT => Keyboard::Print, + glue::KEY_PRIVACY_SCREEN_TOGGLE => Keyboard::PrivacyScreenToggle, + glue::KEY_PROG1 => Keyboard::Prog1, + glue::KEY_PROG2 => Keyboard::Prog2, + glue::KEY_PROG3 => Keyboard::Prog3, + glue::KEY_PROG4 => Keyboard::Prog4, + glue::KEY_PROGRAM => Keyboard::Program, + glue::KEY_PROPS => Keyboard::Props, + glue::KEY_PVR => Keyboard::Pvr, + glue::KEY_Q => Keyboard::Q, + glue::KEY_QUESTION => Keyboard::Question, + glue::KEY_R => Keyboard::R, + glue::KEY_RADIO => Keyboard::Radio, + glue::KEY_RECORD => Keyboard::Record, + glue::KEY_RED => Keyboard::Red, + glue::KEY_REDO => Keyboard::Redo, + glue::KEY_REFRESH => Keyboard::Refresh, + glue::KEY_REPLY => Keyboard::Reply, + glue::KEY_RESERVED => Keyboard::Reserved, + glue::KEY_RESTART => Keyboard::Restart, + glue::KEY_REWIND => Keyboard::Rewind, + glue::KEY_RFKILL => Keyboard::RfKill, + glue::KEY_RIGHT => Keyboard::Right, + glue::KEY_RIGHT_DOWN => Keyboard::RightDown, + glue::KEY_RIGHT_UP => Keyboard::RightUp, + glue::KEY_RIGHTALT => Keyboard::RightAlt, + glue::KEY_RIGHTBRACE => Keyboard::RightBrace, + glue::KEY_RIGHTCTRL => Keyboard::RightCtrl, + glue::KEY_RIGHTMETA => Keyboard::RightMeta, + glue::KEY_RIGHTSHIFT => Keyboard::RightShift, + glue::KEY_RO => Keyboard::Ro, + glue::KEY_ROOT_MENU => Keyboard::RootMenu, + glue::KEY_ROTATE_DISPLAY => Keyboard::RotateDisplay, + glue::KEY_ROTATE_LOCK_TOGGLE => Keyboard::RotateLockToggle, + glue::KEY_S => Keyboard::S, + glue::KEY_SAT => Keyboard::Sat, + glue::KEY_SAT2 => Keyboard::Sat2, + glue::KEY_SAVE => Keyboard::Save, + glue::KEY_SCALE => Keyboard::Scale, + glue::KEY_SCREENSAVER => Keyboard::Screensaver, + glue::KEY_SCROLLDOWN => Keyboard::ScrollDown, + glue::KEY_SCROLLLOCK => Keyboard::ScrollLock, + glue::KEY_SCROLLUP => Keyboard::ScrollUp, + glue::KEY_SEARCH => Keyboard::Search, + glue::KEY_SELECT => Keyboard::Select, + glue::KEY_SELECTIVE_SCREENSHOT => Keyboard::SelectiveScreenshot, + glue::KEY_SEMICOLON => Keyboard::Semicolon, + glue::KEY_SEND => Keyboard::Send, + glue::KEY_SENDFILE => Keyboard::SendFile, + glue::KEY_SETUP => Keyboard::Setup, + glue::KEY_SHOP => Keyboard::Shop, + glue::KEY_SHUFFLE => Keyboard::Shuffle, + glue::KEY_SLASH => Keyboard::Slash, + glue::KEY_SLEEP => Keyboard::Sleep, + glue::KEY_SLOW => Keyboard::Slow, + glue::KEY_SLOWREVERSE => Keyboard::SlowReverse, + glue::KEY_SOUND => Keyboard::Sound, + glue::KEY_SPACE => Keyboard::Space, + glue::KEY_SPELLCHECK => Keyboard::Spellcheck, + glue::KEY_SPORT => Keyboard::Sport, + glue::KEY_SPREADSHEET => Keyboard::Spreadsheet, + glue::KEY_STOP => Keyboard::Stop, + glue::KEY_STOP_RECORD => Keyboard::StopRecord, + glue::KEY_STOPCD => Keyboard::StopCd, + glue::KEY_SUBTITLE => Keyboard::Subtitle, + glue::KEY_SUSPEND => Keyboard::Suspend, + glue::KEY_SWITCHVIDEOMODE => Keyboard::SwitchVideoMode, + glue::KEY_SYSRQ => Keyboard::SysRq, + glue::KEY_T => Keyboard::T, + glue::KEY_TAB => Keyboard::Tab, + glue::KEY_TAPE => Keyboard::Tape, + glue::KEY_TASKMANAGER => Keyboard::TaskManager, + glue::KEY_TEEN => Keyboard::Teen, + glue::KEY_TEXT => Keyboard::Text, + glue::KEY_TIME => Keyboard::Time, + glue::KEY_TITLE => Keyboard::Title, + glue::KEY_TOUCHPAD_OFF => Keyboard::TouchpadOff, + glue::KEY_TOUCHPAD_ON => Keyboard::TouchpadOn, + glue::KEY_TOUCHPAD_TOGGLE => Keyboard::TouchpadToggle, + glue::KEY_TUNER => Keyboard::Tuner, + glue::KEY_TV => Keyboard::Tv, + glue::KEY_TV2 => Keyboard::Tv2, + glue::KEY_TWEN => Keyboard::Twen, + glue::KEY_U => Keyboard::U, + glue::KEY_UNDO => Keyboard::Undo, + glue::KEY_UNKNOWN => Keyboard::Unknown, + glue::KEY_UNMUTE => Keyboard::Unmute, + glue::KEY_UP => Keyboard::Up, + glue::KEY_UWB => Keyboard::Uwb, + glue::KEY_V => Keyboard::V, + glue::KEY_VCR => Keyboard::Vcr, + glue::KEY_VCR2 => Keyboard::Vcr2, + glue::KEY_VENDOR => Keyboard::Vendor, + glue::KEY_VIDEO => Keyboard::Video, + glue::KEY_VIDEO_NEXT => Keyboard::VideoNext, + glue::KEY_VIDEO_PREV => Keyboard::VideoPrev, + glue::KEY_VIDEOPHONE => Keyboard::VideoPhone, + glue::KEY_VOD => Keyboard::Vod, + glue::KEY_VOICECOMMAND => Keyboard::VoiceCommand, + glue::KEY_VOICEMAIL => Keyboard::VoiceMail, + glue::KEY_VOLUMEDOWN => Keyboard::VolumeDown, + glue::KEY_VOLUMEUP => Keyboard::VolumeUp, + glue::KEY_W => Keyboard::W, + glue::KEY_WAKEUP => Keyboard::WakeUp, + glue::KEY_WLAN => Keyboard::Wlan, + glue::KEY_WORDPROCESSOR => Keyboard::WordProcessor, + glue::KEY_WPS_BUTTON => Keyboard::WpsButton, + glue::KEY_WWAN => Keyboard::Wwan, + glue::KEY_WWW => Keyboard::Www, + glue::KEY_X => Keyboard::X, + glue::KEY_XFER => Keyboard::Xfer, + glue::KEY_Y => Keyboard::Y, + glue::KEY_YELLOW => Keyboard::Yellow, + glue::KEY_YEN => Keyboard::Yen, + glue::KEY_Z => Keyboard::Z, + glue::KEY_ZENKAKUHANKAKU => Keyboard::ZenkakuHankaku, + glue::KEY_ZOOMIN => Keyboard::ZoomIn, + glue::KEY_ZOOMOUT => Keyboard::ZoomOut, + glue::KEY_ZOOMRESET => Keyboard::ZoomReset, + _ => return None, + }; + + Some(keyboard) + } +} diff --git a/rkvm-input/src/linux/mod.rs b/rkvm-input/src/linux/mod.rs new file mode 100644 index 0000000..ceaccf4 --- /dev/null +++ b/rkvm-input/src/linux/mod.rs @@ -0,0 +1,14 @@ +pub mod writer; +pub mod monitor; + +mod evdev; +mod glue; +mod uinput; +mod registry.rs + +mod abs_convert; +mod button_convert; +mod keyboard_convert; +mod key_convert; +mod rel_convert; +mod sync_convert; \ No newline at end of file diff --git a/rkvm-input/src/linux/monitor.rs b/rkvm-input/src/linux/monitor.rs new file mode 100644 index 0000000..4c0cd00 --- /dev/null +++ b/rkvm-input/src/linux/monitor.rs @@ -0,0 +1,99 @@ +use crate::monitor::MonitorPlatform; +use crate::linux::interceptor::{InterceptorLinux, OpenError}; +use crate::registry::Registry; +use crate::interceptor::InterceptorPlatform; + +use futures::StreamExt; +use inotify::{Inotify, WatchMask}; +use std::ffi::OsStr; +use std::io::{Error, ErrorKind}; +use std::path::Path; +use tokio::fs; +use tokio::sync::mpsc::{self, Receiver, Sender}; + +const EVENT_PATH: &str = "/dev/input"; + +pub struct MonitorLinux { + receiver: Receiver>, +} + +impl MonitorPlatform for MonitorLinux { + type Interceptor = InterceptorLinux; + fn new() -> Self { + let (sender, receiver) = mpsc::channel(1); + tokio::spawn(monitor(sender)); + + Self { receiver } + } + + async fn read(&mut self) -> Result { + self.receiver + .recv() + .await + .ok_or_else(|| Error::new(ErrorKind::BrokenPipe, "Monitor task exited"))? + } +} + +async fn monitor(sender: Sender>) { + let run = async { + let registry = Registry::new(); + + let mut read_dir = fs::read_dir(EVENT_PATH).await?; + + let inotify = Inotify::init()?; + inotify.watches().add(EVENT_PATH, WatchMask::CREATE)?; + + // This buffer size should be OK, since we don't expect a lot of devices + // to be plugged in frequently. + let mut stream = inotify.into_event_stream([0; 512])?; + + loop { + let path = match read_dir.next_entry().await? { + Some(entry) => entry.path(), + None => match stream.next().await { + Some(event) => { + let event = event?; + let name = match event.name { + Some(name) => name, + None => continue, + }; + + Path::new(EVENT_PATH).join(&name) + } + None => break, + }, + }; + + if !path + .file_name() + .and_then(OsStr::to_str) + .map_or(false, |name| name.starts_with("event")) + { + tracing::debug!("Skipping non event file {:?}", path); + continue; + } + + let interceptor = match InterceptorLinux::open(&path, ®istry).await { + Ok(interceptor) => interceptor, + Err(OpenError::Io(err)) => return Err(err), + Err(OpenError::NotAppliable) => continue, + }; + + if sender.send(Ok(interceptor)).await.is_err() { + return Ok(()); + } + } + + Ok(()) + }; + + tokio::select! { + result = run => match result { + Ok(_) => {}, + Err(err) => { + let _ = sender.send(Err(err)).await; + } + }, + _ = sender.closed() => {} + } +} diff --git a/rkvm-input/src/registry.rs b/rkvm-input/src/linux/registry.rs similarity index 100% rename from rkvm-input/src/registry.rs rename to rkvm-input/src/linux/registry.rs diff --git a/rkvm-input/src/linux/rel_convert.rs b/rkvm-input/src/linux/rel_convert.rs new file mode 100644 index 0000000..a94c191 --- /dev/null +++ b/rkvm-input/src/linux/rel_convert.rs @@ -0,0 +1,45 @@ +use crate::convert::Convert; +use crate::glue; + +impl Convert for RelAxis { + type Raw = u16; + + fn from_raw(code: Self::Raw) -> Option { + let axis = match code as _ { + glue::REL_X => Self::X, + glue::REL_Y => Self::Y, + glue::REL_Z => Self::Z, + glue::REL_RX => Self::Rx, + glue::REL_RY => Self::Ry, + glue::REL_RZ => Self::Rz, + glue::REL_HWHEEL => Self::HWheel, + glue::REL_DIAL => Self::Dial, + glue::REL_WHEEL => Self::Wheel, + glue::REL_MISC => Self::Misc, + glue::REL_WHEEL_HI_RES => Self::WheelHiRes, + glue::REL_HWHEEL_HI_RES => Self::HWheelHiRes, + _ => return None, + }; + + Some(axis) + } + + fn to_raw(&self) -> Option { + let code = match self { + Self::X => glue::REL_X, + Self::Y => glue::REL_Y, + Self::Z => glue::REL_Z, + Self::Rx => glue::REL_RX, + Self::Ry => glue::REL_RY, + Self::Rz => glue::REL_RZ, + Self::HWheel => glue::REL_HWHEEL, + Self::Dial => glue::REL_DIAL, + Self::Wheel => glue::REL_WHEEL, + Self::Misc => glue::REL_MISC, + Self::WheelHiRes => glue::REL_WHEEL_HI_RES, + Self::HWheelHiRes => glue::REL_HWHEEL_HI_RES, + }; + + Some(code as _) + } +} diff --git a/rkvm-input/src/linux/sync_convert.rs b/rkvm-input/src/linux/sync_convert.rs new file mode 100644 index 0000000..686ed8a --- /dev/null +++ b/rkvm-input/src/linux/sync_convert.rs @@ -0,0 +1,25 @@ +use crate::convert::Convert; +use crate::glue; + +impl Convert for SyncEvent { + type Raw = u16; + + fn to_raw(&self) -> Option { + let raw = match self { + Self::All => glue::SYN_REPORT, + Self::Mt => glue::SYN_MT_REPORT, + }; + + Some(raw as _) + } + + fn from_raw(raw: Self::Raw) -> Option { + let event = match raw as _ { + glue::SYN_REPORT => SyncEvent::All, + glue::SYN_MT_REPORT => SyncEvent::Mt, + _ => return None, + }; + + Some(event) + } +} diff --git a/rkvm-input/src/uinput.rs b/rkvm-input/src/linux/uinput.rs similarity index 100% rename from rkvm-input/src/uinput.rs rename to rkvm-input/src/linux/uinput.rs diff --git a/rkvm-input/src/linux/writer.rs b/rkvm-input/src/linux/writer.rs new file mode 100644 index 0000000..5d01dde --- /dev/null +++ b/rkvm-input/src/linux/writer.rs @@ -0,0 +1,296 @@ +use libc::c_int; + +use crate::writer::{WriterPlatform,WriterBuilderPlatform}; +use crate::abs::{AbsAxis, AbsEvent, AbsInfo}; +use crate::convert::Convert; +use crate::evdev::Evdev; +use crate::event::Event; +use crate::glue::{self, input_absinfo}; +use crate::key::{Key, KeyEvent}; +use crate::rel::{RelAxis, RelEvent}; +use crate::uinput::Uinput; + +use std::ffi::{CString, OsStr}; +use std::io::Error; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; +use std::ptr; + +pub struct WriterLinux { + uinput: Uinput, +} + +impl WriterPlatform for WriterLinux { + type Builder = WriterLinuxBuilder; + + fn builder() -> Result { + WriterLinuxBuilder::new() + } + + async fn write(&mut self, event: &Event) -> Result<(), Error> { + let (r#type, code, value) = match event { + Event::Rel(RelEvent { axis, value }) => (glue::EV_REL, axis.to_raw(), Some(*value)), + Event::Abs(event) => match event { + AbsEvent::Axis { axis, value } => (glue::EV_ABS, axis.to_raw(), Some(*value)), + AbsEvent::MtToolType { value } => ( + glue::EV_ABS, + Some(glue::ABS_MT_TOOL_TYPE as _), + value.to_raw(), + ), + }, + Event::Key(KeyEvent { down, key }) => (glue::EV_KEY, key.to_raw(), Some(*down as _)), + Event::Sync(event) => (glue::EV_SYN, event.to_raw(), Some(0)), + }; + + if let (Some(code), Some(value)) = (code, value) { + self.write_raw(r#type as _, code, value).await?; + } + + Ok(()) + } + + fn path(&self) -> Option<&Path> { + let path = unsafe { glue::libevdev_uinput_get_devnode(self.uinput.as_ptr()) }; + if path.is_null() { + return None; + } + + let path = unsafe { CStr::from_ptr(path) }; + let path = OsStr::from_bytes(path.to_bytes()); + let path = Path::new(path); + + Some(path) + } + + async fn from_evdev(evdev: &Evdev) -> Result { + Ok(Self { + uinput: Uinput::from_evdev(evdev).await?, + }) + } + + async fn write_raw( + &mut self, + r#type: u16, + code: u16, + value: i32, + ) -> Result<(), Error> { + loop { + let result = self.uinput.file().writable().await?.try_io(|_| { + let ret = unsafe { + glue::libevdev_uinput_write_event( + self.uinput.as_ptr(), + r#type as _, + code as _, + value, + ) + }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret).into()); + } + + Ok(()) + }); + + match result { + Ok(result) => return result, + Err(_) => continue, // This means it would block. + } + } + } +} + +pub struct WriterLinuxBuilder { + evdev: Evdev, +} + +impl WriterLinuxBuilder { + fn new() -> Result { + let evdev = Evdev::new()?; + + unsafe { + glue::libevdev_set_id_bustype(evdev.as_ptr(), glue::BUS_VIRTUAL as _); + } + + Ok(Self { evdev }) + } +} + +impl WriterBuilderPlatform for WriterLinuxBuilder { + type Writer = WriterLinux; + + + fn name(self, name: &CString) -> Self { + unsafe { + glue::libevdev_set_name(self.evdev.as_ptr(), name.as_ptr()); + } + + self + } + + fn vendor(self, value: u16) -> Self { + unsafe { + glue::libevdev_set_id_vendor(self.evdev.as_ptr(), value as _); + } + + self + } + + fn product(self, value: u16) -> Self { + unsafe { + glue::libevdev_set_id_product(self.evdev.as_ptr(), value as _); + } + + self + } + + fn version(self, value: u16) -> Self { + unsafe { + glue::libevdev_set_id_version(self.evdev.as_ptr(), value as _); + } + + self + } + + fn rel>(self, items: T) -> Result { + for axis in items { + let axis = match axis.to_raw() { + Some(axis) => axis, + None => continue, + }; + + let ret = unsafe { + glue::libevdev_enable_event_code( + self.evdev.as_ptr(), + glue::EV_REL, + axis as _, + ptr::null(), + ) + }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret)); + } + } + + Ok(self) + } + + fn abs>(self, items: T) -> Result { + let ret = unsafe { + glue::libevdev_enable_event_code( + self.evdev.as_ptr(), + glue::EV_SYN, + glue::SYN_MT_REPORT, + ptr::null(), + ) + }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret)); + } + + for (axis, info) in items { + let code = match axis.to_raw() { + Some(code) => code, + None => continue, + }; + + let info = input_absinfo { + value: info.min, + minimum: info.min, + maximum: info.max, + fuzz: info.fuzz, + flat: info.flat, + resolution: info.resolution, + }; + + let ret = unsafe { + glue::libevdev_enable_event_code( + self.evdev.as_ptr(), + glue::EV_ABS, + code as _, + &info as *const _ as *const _, + ) + }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret)); + } + } + + Ok(self) + } + + fn key>(self, items: T) -> Result { + for key in items { + let key = match key.to_raw() { + Some(key) => key, + None => continue, + }; + + let ret = unsafe { + glue::libevdev_enable_event_code( + self.evdev.as_ptr(), + glue::EV_KEY, + key as _, + ptr::null(), + ) + }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret)); + } + } + + Ok(self) + } + + fn delay(self, value: Option) -> Result { + let value: c_int = match value { + Some(value) => value, + None => return Ok(self), + }; + + let ret = unsafe { + glue::libevdev_enable_event_code( + self.evdev.as_ptr(), + glue::EV_REP, + glue::REP_DELAY, + &value as *const _ as *const _, + ) + }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret)); + } + + Ok(self) + } + + fn period(self, value: Option) -> Result { + let value: c_int = match value { + Some(value) => value, + None => return Ok(self), + }; + + let ret = unsafe { + glue::libevdev_enable_event_code( + self.evdev.as_ptr(), + glue::EV_REP, + glue::REP_PERIOD, + &value as *const _ as *const _, + ) + }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret)); + } + + Ok(self) + } + + async fn build(self) -> Result { + WriterLinux::from_evdev(&self.evdev).await + } +} diff --git a/rkvm-input/src/monitor.rs b/rkvm-input/src/monitor.rs index 403a5d3..91b6b2d 100644 --- a/rkvm-input/src/monitor.rs +++ b/rkvm-input/src/monitor.rs @@ -1,96 +1,15 @@ -use crate::interceptor::{Interceptor, OpenError}; -use crate::registry::Registry; +use crate::interceptor::InterceptorPlatform; +use std::io::Error; -use futures::StreamExt; -use inotify::{Inotify, WatchMask}; -use std::ffi::OsStr; -use std::io::{Error, ErrorKind}; -use std::path::Path; -use tokio::fs; -use tokio::sync::mpsc::{self, Receiver, Sender}; +#[cfg(target_os = "windows")] +pub use {crate::windows::monitor::MonitorWindows as Monitor}; +#[cfg(target_os = "linux")] +pub use {crate::linux::monitor::MonitorLinux as Monitor}; -const EVENT_PATH: &str = "/dev/input"; +pub trait MonitorPlatform: Sized { + type Interceptor: InterceptorPlatform; -pub struct Monitor { - receiver: Receiver>, -} + fn new() -> Self; -impl Monitor { - pub fn new() -> Self { - let (sender, receiver) = mpsc::channel(1); - tokio::spawn(monitor(sender)); - - Self { receiver } - } - - pub async fn read(&mut self) -> Result { - self.receiver - .recv() - .await - .ok_or_else(|| Error::new(ErrorKind::BrokenPipe, "Monitor task exited"))? - } -} - -async fn monitor(sender: Sender>) { - let run = async { - let registry = Registry::new(); - - let mut read_dir = fs::read_dir(EVENT_PATH).await?; - - let inotify = Inotify::init()?; - inotify.watches().add(EVENT_PATH, WatchMask::CREATE)?; - - // This buffer size should be OK, since we don't expect a lot of devices - // to be plugged in frequently. - let mut stream = inotify.into_event_stream([0; 512])?; - - loop { - let path = match read_dir.next_entry().await? { - Some(entry) => entry.path(), - None => match stream.next().await { - Some(event) => { - let event = event?; - let name = match event.name { - Some(name) => name, - None => continue, - }; - - Path::new(EVENT_PATH).join(&name) - } - None => break, - }, - }; - - if !path - .file_name() - .and_then(OsStr::to_str) - .map_or(false, |name| name.starts_with("event")) - { - tracing::debug!("Skipping non event file {:?}", path); - continue; - } - - let interceptor = match Interceptor::open(&path, ®istry).await { - Ok(interceptor) => interceptor, - Err(OpenError::Io(err)) => return Err(err), - Err(OpenError::NotAppliable) => continue, - }; - - if sender.send(Ok(interceptor)).await.is_err() { - return Ok(()); - } - } - - Ok(()) - }; - - tokio::select! { - result = run => match result { - Ok(_) => {}, - Err(err) => { - let _ = sender.send(Err(err)).await; - } - }, - _ = sender.closed() => {} - } -} + fn read<'a>(&'a mut self) -> impl std::future::Future> +Send + 'a; +} \ No newline at end of file diff --git a/rkvm-input/src/rel.rs b/rkvm-input/src/rel.rs index a9bc32a..a70b16a 100644 --- a/rkvm-input/src/rel.rs +++ b/rkvm-input/src/rel.rs @@ -1,5 +1,3 @@ -use crate::convert::Convert; -use crate::glue; use serde::{Deserialize, Serialize}; @@ -23,47 +21,4 @@ pub enum RelAxis { Misc, WheelHiRes, HWheelHiRes, -} - -impl Convert for RelAxis { - type Raw = u16; - - fn from_raw(code: Self::Raw) -> Option { - let axis = match code as _ { - glue::REL_X => Self::X, - glue::REL_Y => Self::Y, - glue::REL_Z => Self::Z, - glue::REL_RX => Self::Rx, - glue::REL_RY => Self::Ry, - glue::REL_RZ => Self::Rz, - glue::REL_HWHEEL => Self::HWheel, - glue::REL_DIAL => Self::Dial, - glue::REL_WHEEL => Self::Wheel, - glue::REL_MISC => Self::Misc, - glue::REL_WHEEL_HI_RES => Self::WheelHiRes, - glue::REL_HWHEEL_HI_RES => Self::HWheelHiRes, - _ => return None, - }; - - Some(axis) - } - - fn to_raw(&self) -> Option { - let code = match self { - Self::X => glue::REL_X, - Self::Y => glue::REL_Y, - Self::Z => glue::REL_Z, - Self::Rx => glue::REL_RX, - Self::Ry => glue::REL_RY, - Self::Rz => glue::REL_RZ, - Self::HWheel => glue::REL_HWHEEL, - Self::Dial => glue::REL_DIAL, - Self::Wheel => glue::REL_WHEEL, - Self::Misc => glue::REL_MISC, - Self::WheelHiRes => glue::REL_WHEEL_HI_RES, - Self::HWheelHiRes => glue::REL_HWHEEL_HI_RES, - }; - - Some(code as _) - } -} +} \ No newline at end of file diff --git a/rkvm-input/src/sync.rs b/rkvm-input/src/sync.rs index 3e63ffe..819ea14 100644 --- a/rkvm-input/src/sync.rs +++ b/rkvm-input/src/sync.rs @@ -1,6 +1,3 @@ -use crate::convert::Convert; -use crate::glue; - use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] @@ -8,26 +5,3 @@ pub enum SyncEvent { All, Mt, } - -impl Convert for SyncEvent { - type Raw = u16; - - fn to_raw(&self) -> Option { - let raw = match self { - Self::All => glue::SYN_REPORT, - Self::Mt => glue::SYN_MT_REPORT, - }; - - Some(raw as _) - } - - fn from_raw(raw: Self::Raw) -> Option { - let event = match raw as _ { - glue::SYN_REPORT => SyncEvent::All, - glue::SYN_MT_REPORT => SyncEvent::Mt, - _ => return None, - }; - - Some(event) - } -} diff --git a/rkvm-input/src/windows/interceptor.rs b/rkvm-input/src/windows/interceptor.rs new file mode 100644 index 0000000..123f858 --- /dev/null +++ b/rkvm-input/src/windows/interceptor.rs @@ -0,0 +1,45 @@ +use crate::interceptor::{InterceptorPlatform,Repeat}; +use crate::abs::{AbsAxis, AbsInfo}; +use crate::event::Event; +use crate::key::Key; +use crate::rel::RelAxis; + + +use std::collections::{HashMap, HashSet}; +use std::ffi::CStr; +use std::io::Error; + +pub struct InterceptorWindows; + +impl InterceptorPlatform for InterceptorWindows { + async fn read(&mut self) -> Result { + unimplemented!() + } + async fn write(&mut self, _event: &Event) -> Result<(), Error> { + unimplemented!() + } + fn name(&self) -> &CStr { + unimplemented!() + } + fn vendor(&self) -> u16 { + unimplemented!() + } + fn product(&self) -> u16 { + unimplemented!() + } + fn version(&self) -> u16 { + unimplemented!() + } + fn rel(&self) -> HashSet { + unimplemented!() + } + fn abs(&self) -> HashMap { + unimplemented!() + } + fn key(&self) -> HashSet { + unimplemented!() + } + fn repeat(&self) -> Repeat { + unimplemented!() + } +} \ No newline at end of file diff --git a/rkvm-input/src/windows/mod.rs b/rkvm-input/src/windows/mod.rs new file mode 100644 index 0000000..4a579ee --- /dev/null +++ b/rkvm-input/src/windows/mod.rs @@ -0,0 +1,4 @@ +pub mod writer; +pub mod monitor; + +mod interceptor; \ No newline at end of file diff --git a/rkvm-input/src/windows/monitor.rs b/rkvm-input/src/windows/monitor.rs new file mode 100644 index 0000000..97a2ec3 --- /dev/null +++ b/rkvm-input/src/windows/monitor.rs @@ -0,0 +1,91 @@ +use crate::monitor::MonitorPlatform; +use crate::windows::interceptor::InterceptorWindows; + +use std::io::{Error, ErrorKind}; +use tokio::sync::mpsc::{self, Receiver}; + +pub struct MonitorWindows { + receiver: Receiver>, +} + +impl MonitorPlatform for MonitorWindows { + type Interceptor = InterceptorWindows; + fn new() -> Self { + let (_sender, receiver) = mpsc::channel(1); + // tokio::spawn(monitor(sender)); + + Self { receiver } + } + + async fn read(&mut self) -> Result { + self.receiver + .recv() + .await + .ok_or_else(|| Error::new(ErrorKind::BrokenPipe, "Monitor task exited"))? + } +} +/* +async fn monitor(sender: Sender>) { + let run = async { + let registry = Registry::new(); + + let mut read_dir = fs::read_dir(EVENT_PATH).await?; + + let inotify = Inotify::init()?; + inotify.watches().add(EVENT_PATH, WatchMask::CREATE)?; + + // This buffer size should be OK, since we don't expect a lot of devices + // to be plugged in frequently. + let mut stream = inotify.into_event_stream([0; 512])?; + + loop { + let path = match read_dir.next_entry().await? { + Some(entry) => entry.path(), + None => match stream.next().await { + Some(event) => { + let event = event?; + let name = match event.name { + Some(name) => name, + None => continue, + }; + + Path::new(EVENT_PATH).join(&name) + } + None => break, + }, + }; + + if !path + .file_name() + .and_then(OsStr::to_str) + .map_or(false, |name| name.starts_with("event")) + { + tracing::debug!("Skipping non event file {:?}", path); + continue; + } + + let interceptor = match Interceptor::open(&path, ®istry).await { + Ok(interceptor) => interceptor, + Err(OpenError::Io(err)) => return Err(err), + Err(OpenError::NotAppliable) => continue, + }; + + if sender.send(Ok(interceptor)).await.is_err() { + return Ok(()); + } + } + + Ok(()) + }; + + tokio::select! { + result = run => match result { + Ok(_) => {}, + Err(err) => { + let _ = sender.send(Err(err)).await; + } + }, + _ = sender.closed() => {} + } +} +*/ \ No newline at end of file diff --git a/rkvm-input/src/windows/writer.rs b/rkvm-input/src/windows/writer.rs new file mode 100644 index 0000000..91c6719 --- /dev/null +++ b/rkvm-input/src/windows/writer.rs @@ -0,0 +1,261 @@ +use crate::writer::{WriterPlatform,WriterBuilderPlatform}; +use crate::abs::{AbsAxis, AbsInfo}; +use crate::event::Event; +use crate::key::{Key, KeyEvent,Keyboard}; +use crate::rel::{RelAxis, RelEvent}; + +use std::ffi::CString; +use std::io::Error; + +use windows::Win32::UI::Input::KeyboardAndMouse::*; + +pub struct WriterWindows { + buffer: Vec, +} + +impl WriterWindows { + fn push(&mut self, input: INPUT) { + self.buffer.push(input); + } + + fn flush(&mut self) -> Result<(), Error> { + if self.buffer.is_empty() { + return Ok(()); + } + + unsafe { + let sent = SendInput( + self.buffer.as_slice(), + self.buffer.len() as i32, + ); + + if sent == 0 { + return Err(Error::last_os_error()); + } + } + + self.buffer.clear(); + Ok(()) + } + + fn key(&mut self, key: &Keyboard, down:&bool) { + if let Some((scan, extended)) = map_key_to_scancode(key) { + let mut flags = KEYEVENTF_SCANCODE; + if !down { flags |= KEYEVENTF_KEYUP; } + if extended { flags |= KEYEVENTF_EXTENDEDKEY; } + + self.push(INPUT { + r#type: INPUT_KEYBOARD, + Anonymous: INPUT_0 { + ki: KEYBDINPUT { + wVk: VIRTUAL_KEY::default(), + wScan: scan, + dwFlags: flags, + time: 0, + dwExtraInfo: 0, + }, + }, + }); + } + } + + fn mouse_move(&mut self, dx: i32, dy: i32) { + self.push(INPUT { + r#type: INPUT_MOUSE, + Anonymous: INPUT_0 { + mi: MOUSEINPUT { + dx, + dy, + mouseData: 0, + dwFlags: MOUSEEVENTF_MOVE, + time: 0, + dwExtraInfo: 0, + }, + }, + }) + } + + fn mouse_wheel(&mut self, delta: i32) { + self.push(INPUT { + r#type: INPUT_MOUSE, + Anonymous: INPUT_0 { + mi: MOUSEINPUT { + dx: 0, + dy: 0, + mouseData: (delta * 120) as u32, + dwFlags: MOUSEEVENTF_WHEEL, + time: 0, + dwExtraInfo: 0, + }, + }, + }) + } +} + +impl WriterPlatform for WriterWindows { + type Builder = WriterWindowsBuilder; + fn builder() -> Result { + Ok(WriterWindowsBuilder) + } + + async fn write(&mut self, event: &Event) -> Result<(), Error> { + match event { + Event::Key(KeyEvent { key, down }) => { + match key { + Key::Key(key) => self.key(key, down), + Key::Button(button) => { + } + } + + } + Event::Rel(RelEvent { axis, value }) => { + match axis { + RelAxis::X => self.mouse_move(*value, 0), + RelAxis::Y => self.mouse_move(0, *value), + RelAxis::Wheel => self.mouse_wheel(*value), + _ => tracing::error!("Axe not handled: {:?}", axis), + } + } + Event::Abs(_event) => {} + Event::Sync(_) => self.flush()? + } + + Ok(()) + } + + +} + +pub struct WriterWindowsBuilder; + +impl WriterBuilderPlatform for WriterWindowsBuilder { + type Writer = WriterWindows; + + fn name(self, _name: &CString) -> Self { + self + } + + fn vendor(self, _value: u16) -> Self { + self + } + + fn product(self, _value: u16) -> Self { + self + } + + fn version(self, _value: u16) -> Self { + self + } + fn rel>(self, _items: T) -> Result { + Ok(self) + } + fn abs>(self, _items: T) -> Result { + Ok(self) + } + fn key>(self, _items: T) -> Result { + Ok(self) + } + + fn delay(self, _value: Option) -> Result { + Ok(self) + } + + fn period(self, _value: Option) -> Result { + Ok(self) + } + + async fn build(self) -> Result { + Ok(WriterWindows{ + buffer: Vec::with_capacity(16), + }) + } +} + +const fn map_key_to_scancode(key: &Keyboard) -> Option<(u16, bool)> { + match key { + // Letters + Keyboard::A => Some((0x1E, false)), + Keyboard::B => Some((0x30, false)), + Keyboard::C => Some((0x2E, false)), + Keyboard::D => Some((0x20, false)), + Keyboard::E => Some((0x12, false)), + Keyboard::F => Some((0x21, false)), + Keyboard::G => Some((0x22, false)), + Keyboard::H => Some((0x23, false)), + Keyboard::I => Some((0x17, false)), + Keyboard::J => Some((0x24, false)), + Keyboard::K => Some((0x25, false)), + Keyboard::L => Some((0x26, false)), + Keyboard::M => Some((0x32, false)), + Keyboard::N => Some((0x31, false)), + Keyboard::O => Some((0x18, false)), + Keyboard::P => Some((0x19, false)), + Keyboard::Q => Some((0x10, false)), + Keyboard::R => Some((0x13, false)), + Keyboard::S => Some((0x1F, false)), + Keyboard::T => Some((0x14, false)), + Keyboard::U => Some((0x16, false)), + Keyboard::V => Some((0x2F, false)), + Keyboard::W => Some((0x11, false)), + Keyboard::X => Some((0x2D, false)), + Keyboard::Y => Some((0x15, false)), + Keyboard::Z => Some((0x2C, false)), + + // Numbers + Keyboard::N1 => Some((0x02, false)), + Keyboard::N2 => Some((0x03, false)), + Keyboard::N3 => Some((0x04, false)), + Keyboard::N4 => Some((0x05, false)), + Keyboard::N5 => Some((0x06, false)), + Keyboard::N6 => Some((0x07, false)), + Keyboard::N7 => Some((0x08, false)), + Keyboard::N8 => Some((0x09, false)), + Keyboard::N9 => Some((0x0A, false)), + Keyboard::N0 => Some((0x0B, false)), + + // Arrows + Keyboard::Up => Some((0x48, true)), + Keyboard::Down => Some((0x50, true)), + Keyboard::Left => Some((0x4B, true)), + Keyboard::Right => Some((0x4D, true)), + + // Functions + Keyboard::F1 => Some((0x3B, false)), + Keyboard::F2 => Some((0x3C, false)), + Keyboard::F3 => Some((0x3D, false)), + Keyboard::F4 => Some((0x3E, false)), + Keyboard::F5 => Some((0x3F, false)), + Keyboard::F6 => Some((0x40, false)), + Keyboard::F7 => Some((0x41, false)), + Keyboard::F8 => Some((0x42, false)), + Keyboard::F9 => Some((0x43, false)), + Keyboard::F10 => Some((0x44, false)), + Keyboard::F11 => Some((0x57, false)), + Keyboard::F12 => Some((0x58, false)), + + // Special Keyboards + Keyboard::Enter => Some((0x1C, false)), + Keyboard::Esc => Some((0x01, false)), + Keyboard::Backspace => Some((0x0E, false)), + Keyboard::Tab => Some((0x0F, false)), + Keyboard::Space => Some((0x39, false)), + Keyboard::CapsLock => Some((0x3A, false)), + Keyboard::LeftShift => Some((0x2A, false)), + Keyboard::RightShift => Some((0x36, false)), + Keyboard::LeftCtrl => Some((0x1D, false)), + Keyboard::RightCtrl => Some((0x1D, true)), + Keyboard::LeftAlt => Some((0x38, false)), + Keyboard::RightAlt => Some((0x38, true)), + Keyboard::LeftMeta => Some((0x5B, true)), // Windows Keyboard + Keyboard::RightMeta => Some((0x5C, true)), + + Keyboard::Insert => Some((0x52, true)), + Keyboard::Delete => Some((0x53, true)), + Keyboard::Home => Some((0x47, true)), + Keyboard::End => Some((0x4F, true)), + Keyboard::PageUp => Some((0x49, true)), + Keyboard::PageDown => Some((0x51, true)), + + _ => None, // ingore unsupported keys + } +} \ No newline at end of file diff --git a/rkvm-input/src/writer.rs b/rkvm-input/src/writer.rs index 7106b95..40859d4 100644 --- a/rkvm-input/src/writer.rs +++ b/rkvm-input/src/writer.rs @@ -1,288 +1,44 @@ -use libc::c_int; - -use crate::abs::{AbsAxis, AbsEvent, AbsInfo}; -use crate::convert::Convert; -use crate::evdev::Evdev; +use crate::abs::{AbsAxis, AbsInfo}; use crate::event::Event; -use crate::glue::{self, input_absinfo}; -use crate::key::{Key, KeyEvent}; -use crate::rel::{RelAxis, RelEvent}; -use crate::uinput::Uinput; +use crate::key::Key; +use crate::rel::RelAxis; -use std::ffi::{CStr, OsStr}; +use std::ffi::CString; use std::io::Error; -use std::os::unix::ffi::OsStrExt; -use std::path::Path; -use std::ptr; - -pub struct Writer { - uinput: Uinput, -} - -impl Writer { - pub fn builder() -> Result { - WriterBuilder::new() - } - - pub async fn write(&mut self, event: &Event) -> Result<(), Error> { - let (r#type, code, value) = match event { - Event::Rel(RelEvent { axis, value }) => (glue::EV_REL, axis.to_raw(), Some(*value)), - Event::Abs(event) => match event { - AbsEvent::Axis { axis, value } => (glue::EV_ABS, axis.to_raw(), Some(*value)), - AbsEvent::MtToolType { value } => ( - glue::EV_ABS, - Some(glue::ABS_MT_TOOL_TYPE as _), - value.to_raw(), - ), - }, - Event::Key(KeyEvent { down, key }) => (glue::EV_KEY, key.to_raw(), Some(*down as _)), - Event::Sync(event) => (glue::EV_SYN, event.to_raw(), Some(0)), - }; - - if let (Some(code), Some(value)) = (code, value) { - self.write_raw(r#type as _, code, value).await?; - } - - Ok(()) - } - - pub fn path(&self) -> Option<&Path> { - let path = unsafe { glue::libevdev_uinput_get_devnode(self.uinput.as_ptr()) }; - if path.is_null() { - return None; - } - - let path = unsafe { CStr::from_ptr(path) }; - let path = OsStr::from_bytes(path.to_bytes()); - let path = Path::new(path); - - Some(path) - } - pub(crate) async fn from_evdev(evdev: &Evdev) -> Result { - Ok(Self { - uinput: Uinput::from_evdev(evdev).await?, - }) - } +#[cfg(target_os = "windows")] +pub use {crate::windows::writer::WriterWindows as Writer, crate::windows::writer::WriterWindowsBuilder as WriterBuilder}; +#[cfg(target_os = "linux")] +pub use {crate::linux::writer::WriterLinux as Writer, crate::linux::writer::WriterLinuxBuilder as WriterBuilder}; - pub(crate) async fn write_raw( - &mut self, - r#type: u16, - code: u16, - value: i32, - ) -> Result<(), Error> { - loop { - let result = self.uinput.file().writable().await?.try_io(|_| { - let ret = unsafe { - glue::libevdev_uinput_write_event( - self.uinput.as_ptr(), - r#type as _, - code as _, - value, - ) - }; +pub trait WriterPlatform { + type Builder: WriterBuilderPlatform; - if ret < 0 { - return Err(Error::from_raw_os_error(-ret).into()); - } + fn builder() -> Result; - Ok(()) - }); - - match result { - Ok(result) => return result, - Err(_) => continue, // This means it would block. - } - } - } -} - -pub struct WriterBuilder { - evdev: Evdev, + fn write<'a>(&'a mut self, event: &'a Event) -> impl std::future::Future> + Send + 'a; } -impl WriterBuilder { - pub fn new() -> Result { - let evdev = Evdev::new()?; - - unsafe { - glue::libevdev_set_id_bustype(evdev.as_ptr(), glue::BUS_VIRTUAL as _); - } - - Ok(Self { evdev }) - } - - pub fn name(self, name: &CStr) -> Self { - unsafe { - glue::libevdev_set_name(self.evdev.as_ptr(), name.as_ptr()); - } - - self - } - - pub fn vendor(self, value: u16) -> Self { - unsafe { - glue::libevdev_set_id_vendor(self.evdev.as_ptr(), value as _); - } - - self - } - - pub fn product(self, value: u16) -> Self { - unsafe { - glue::libevdev_set_id_product(self.evdev.as_ptr(), value as _); - } - - self - } - - pub fn version(self, value: u16) -> Self { - unsafe { - glue::libevdev_set_id_version(self.evdev.as_ptr(), value as _); - } - - self - } - - pub fn rel>(self, items: T) -> Result { - for axis in items { - let axis = match axis.to_raw() { - Some(axis) => axis, - None => continue, - }; - - let ret = unsafe { - glue::libevdev_enable_event_code( - self.evdev.as_ptr(), - glue::EV_REL, - axis as _, - ptr::null(), - ) - }; - - if ret < 0 { - return Err(Error::from_raw_os_error(-ret)); - } - } - - Ok(self) - } - - pub fn abs>(self, items: T) -> Result { - let ret = unsafe { - glue::libevdev_enable_event_code( - self.evdev.as_ptr(), - glue::EV_SYN, - glue::SYN_MT_REPORT, - ptr::null(), - ) - }; - - if ret < 0 { - return Err(Error::from_raw_os_error(-ret)); - } - - for (axis, info) in items { - let code = match axis.to_raw() { - Some(code) => code, - None => continue, - }; - - let info = input_absinfo { - value: info.min, - minimum: info.min, - maximum: info.max, - fuzz: info.fuzz, - flat: info.flat, - resolution: info.resolution, - }; - - let ret = unsafe { - glue::libevdev_enable_event_code( - self.evdev.as_ptr(), - glue::EV_ABS, - code as _, - &info as *const _ as *const _, - ) - }; - - if ret < 0 { - return Err(Error::from_raw_os_error(-ret)); - } - } - - Ok(self) - } - - pub fn key>(self, items: T) -> Result { - for key in items { - let key = match key.to_raw() { - Some(key) => key, - None => continue, - }; - - let ret = unsafe { - glue::libevdev_enable_event_code( - self.evdev.as_ptr(), - glue::EV_KEY, - key as _, - ptr::null(), - ) - }; - - if ret < 0 { - return Err(Error::from_raw_os_error(-ret)); - } - } +pub trait WriterBuilderPlatform: Sized { + type Writer: WriterPlatform; - Ok(self) - } + fn name(self, name: &CString) -> Self; - pub fn delay(self, value: Option) -> Result { - let value: c_int = match value { - Some(value) => value, - None => return Ok(self), - }; + fn vendor(self, value: u16) -> Self; - let ret = unsafe { - glue::libevdev_enable_event_code( - self.evdev.as_ptr(), - glue::EV_REP, - glue::REP_DELAY, - &value as *const _ as *const _, - ) - }; + fn product(self, value: u16) -> Self; - if ret < 0 { - return Err(Error::from_raw_os_error(-ret)); - } + fn version(self, value: u16) -> Self; - Ok(self) - } + fn rel>(self, items: T) -> Result; - pub fn period(self, value: Option) -> Result { - let value: c_int = match value { - Some(value) => value, - None => return Ok(self), - }; + fn abs>(self, items: T) -> Result; - let ret = unsafe { - glue::libevdev_enable_event_code( - self.evdev.as_ptr(), - glue::EV_REP, - glue::REP_PERIOD, - &value as *const _ as *const _, - ) - }; + fn key>(self, items: T) -> Result; - if ret < 0 { - return Err(Error::from_raw_os_error(-ret)); - } + fn delay(self, value: Option) -> Result; - Ok(self) - } + fn period(self, value: Option) -> Result; - pub async fn build(self) -> Result { - Writer::from_evdev(&self.evdev).await - } + fn build(self) -> impl std::future::Future> + Send; } diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index b783181..503369a 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -1,7 +1,8 @@ use rkvm_input::abs::{AbsAxis, AbsInfo}; use rkvm_input::event::Event; use rkvm_input::key::{Key, KeyEvent}; -use rkvm_input::monitor::Monitor; +use rkvm_input::monitor::{Monitor,MonitorPlatform}; +use rkvm_input::interceptor::InterceptorPlatform; use rkvm_input::rel::RelAxis; use rkvm_input::sync::SyncEvent; use rkvm_net::auth::{AuthChallenge, AuthResponse, AuthStatus}; @@ -108,9 +109,9 @@ pub async fn run( let version = interceptor.version(); let vendor = interceptor.vendor(); let product = interceptor.product(); - let rel = interceptor.rel().collect::>(); - let abs = interceptor.abs().collect::>(); - let keys = interceptor.key().collect::>(); + let rel = interceptor.rel(); + let abs = interceptor.abs(); + let keys = interceptor.key(); let repeat = interceptor.repeat(); for (_, (sender, _)) in &clients { From a52686799a88cf53eb38658be4bb294b767854a7 Mon Sep 17 00:00:00 2001 From: Unknow Date: Mon, 12 Jan 2026 00:44:56 +0100 Subject: [PATCH 2/8] update linux files --- rkvm-input/Cargo.toml | 3 + rkvm-input/build.rs | 2 +- rkvm-input/src/linux/abs_convert.rs | 12 +- rkvm-input/src/linux/button_convert.rs | 4 +- rkvm-input/src/linux/evdev.rs | 2 +- rkvm-input/src/linux/interceptor.rs | 278 ++++++++++++----------- rkvm-input/src/linux/interceptor/caps.rs | 7 +- rkvm-input/src/linux/key_convert.rs | 4 +- rkvm-input/src/linux/keyboard_convert.rs | 4 +- rkvm-input/src/linux/mod.rs | 5 +- rkvm-input/src/linux/monitor.rs | 5 +- rkvm-input/src/linux/rel_convert.rs | 4 +- rkvm-input/src/linux/sync_convert.rs | 4 +- rkvm-input/src/linux/uinput.rs | 4 +- rkvm-input/src/linux/writer.rs | 74 +++--- 15 files changed, 214 insertions(+), 198 deletions(-) diff --git a/rkvm-input/Cargo.toml b/rkvm-input/Cargo.toml index 85fa200..d93be81 100644 --- a/rkvm-input/Cargo.toml +++ b/rkvm-input/Cargo.toml @@ -30,3 +30,6 @@ pkg-config = "0.3.19" [lib] doctest = false + +[features] +abs_profile = [] diff --git a/rkvm-input/build.rs b/rkvm-input/build.rs index 972bd16..5471af8 100644 --- a/rkvm-input/build.rs +++ b/rkvm-input/build.rs @@ -46,6 +46,6 @@ fn main() { .windows(RKVM_HAVE_ABS_PROFILE.len()) .any(|window| window == RKVM_HAVE_ABS_PROFILE) { - println!("cargo:rustc-cfg=have_abs_profile"); + println!("cargo:rustc-cfg=feature=\"abs_profile\""); } } diff --git a/rkvm-input/src/linux/abs_convert.rs b/rkvm-input/src/linux/abs_convert.rs index 52fc39d..06d9a0b 100644 --- a/rkvm-input/src/linux/abs_convert.rs +++ b/rkvm-input/src/linux/abs_convert.rs @@ -1,7 +1,7 @@ use crate::convert::Convert; -use crate::glue; +use crate::linux::glue; -use crate::AbsAxis; +use crate::abs::{AbsAxis, ToolType}; impl Convert for AbsAxis { type Raw = u16; @@ -33,7 +33,7 @@ impl Convert for AbsAxis { glue::ABS_TILT_Y => Self::TiltY, glue::ABS_TOOL_WIDTH => Self::ToolWidth, glue::ABS_VOLUME => Self::Volume, - #[cfg(have_abs_profile)] + #[cfg(feature = "abs_profile")] glue::ABS_PROFILE => Self::Profile, glue::ABS_MISC => Self::Misc, glue::ABS_MT_SLOT => Self::MtSlot, @@ -83,9 +83,9 @@ impl Convert for AbsAxis { Self::TiltY => glue::ABS_TILT_Y, Self::ToolWidth => glue::ABS_TOOL_WIDTH, Self::Volume => glue::ABS_VOLUME, - #[cfg(have_abs_profile)] + #[cfg(feature = "abs_profile")] Self::Profile => glue::ABS_PROFILE, - #[cfg(not(have_abs_profile))] + #[cfg(not(feature = "abs_profile"))] Self::Profile => return None, Self::Misc => glue::ABS_MISC, Self::MtSlot => glue::ABS_MT_SLOT, @@ -133,4 +133,4 @@ impl Convert for ToolType { Some(value as _) } -} \ No newline at end of file +} diff --git a/rkvm-input/src/linux/button_convert.rs b/rkvm-input/src/linux/button_convert.rs index cb98bfb..e626fdc 100644 --- a/rkvm-input/src/linux/button_convert.rs +++ b/rkvm-input/src/linux/button_convert.rs @@ -1,5 +1,7 @@ use crate::convert::Convert; -use crate::glue; +use crate::linux::glue; + +use crate::key::Button; impl Convert for Button { type Raw = u16; diff --git a/rkvm-input/src/linux/evdev.rs b/rkvm-input/src/linux/evdev.rs index c09ad60..06199de 100644 --- a/rkvm-input/src/linux/evdev.rs +++ b/rkvm-input/src/linux/evdev.rs @@ -1,4 +1,4 @@ -use crate::glue::{self, libevdev}; +use crate::linux::glue::{self, libevdev}; use std::fs::File; use std::io::{Error, ErrorKind}; diff --git a/rkvm-input/src/linux/interceptor.rs b/rkvm-input/src/linux/interceptor.rs index a1c634d..b235a46 100644 --- a/rkvm-input/src/linux/interceptor.rs +++ b/rkvm-input/src/linux/interceptor.rs @@ -5,11 +5,13 @@ pub use caps::{AbsCaps, KeyCaps, RelCaps}; use crate::abs::{AbsAxis, AbsInfo, AbsEvent, ToolType}; use crate::interceptor::{InterceptorPlatform,Repeat}; use crate::convert::Convert; -use crate::evdev::Evdev; +use crate::linux::evdev::Evdev; use crate::event::Event; -use crate::glue; +use crate::linux::glue; use crate::key::{Key, KeyEvent}; -use crate::registry::{Entry, Handle, Registry}; +use crate::linux::registry::{Entry, Handle, Registry}; +use crate::linux::writer::WriterLinux; +use crate::writer::WriterPlatform; use crate::rel::{RelAxis, RelEvent}; use crate::sync::SyncEvent; use crate::writer::Writer; @@ -34,6 +36,139 @@ pub struct InterceptorLinux { _writer_handle: Handle, } +impl InterceptorLinux { + #[tracing::instrument(skip(registry))] + pub(crate) async fn open(path: &Path, registry: &Registry) -> Result { + let evdev = Evdev::open(path).await?; + let metadata = evdev.file().unwrap().get_ref().metadata()?; + + let reader_handle = registry + .register(Entry::from_metadata(&metadata)) + .ok_or(OpenError::NotAppliable)?; + + // "Upon binding to a device or resuming from suspend, a driver must report + // the current switch state. This ensures that the device, kernel, and userspace + // state is in sync." + // We have no way of knowing that. + let sw = unsafe { glue::libevdev_has_event_type(evdev.as_ptr(), glue::EV_SW) }; + if sw == 1 { + return Err(OpenError::NotAppliable); + } + + // Some buggy kernels can report nonsense abs info, so check for it and disable the axes. + for i in 0..glue::ABS_CNT { + let abs_info = unsafe { glue::libevdev_get_abs_info(evdev.as_ptr(), i).as_ref() }; + let abs_info = match abs_info { + Some(abs_info) => abs_info, + None => continue, + }; + + // See Linux source at drivers/input/misc/uinput.c#L408 commit 93f5de5f648d2b1ce3540a4ac71756d4a852dc23. + + let min = abs_info.minimum; + let max = abs_info.maximum; + + if (min != 0 || max != 0) && max < min { + tracing::warn!( + min = %min, + max = max, + axis = i, + "Detected nonsense min and max values for absolute axis, disabling it", + ); + + let ret = + unsafe { glue::libevdev_disable_event_code(evdev.as_ptr(), glue::EV_ABS, i) }; + + if ret < 0 { + return Err(Error::from_raw_os_error(-ret).into()); + } + } + } + + unsafe { + glue::libevdev_set_id_bustype(evdev.as_ptr(), glue::BUS_VIRTUAL as _); + } + + let ret = + unsafe { glue::libevdev_grab(evdev.as_ptr(), glue::libevdev_grab_mode_LIBEVDEV_GRAB) }; + + if ret < 0 { + // We do not use ErrorKind::ResourceBusy because it is a nightly-only API. + let err = if ret == -libc::EBUSY { + tracing::info!( + "Ignored {:?} because it is busy and can not be grabbed", + path + ); + OpenError::NotAppliable + } else { + Error::from_raw_os_error(-ret).into() + }; + + return Err(err); + } + + let writer = WriterLinux::from_evdev(&evdev).await?; + let path = writer + .path() + .ok_or_else(|| Error::new(ErrorKind::Other, "No syspath for writer"))?; + + let metadata = fs::metadata(path)?; + let writer_handle = registry + .register(Entry::from_metadata(&metadata)) + .ok_or_else(|| Error::new(ErrorKind::Other, "Writer already registered"))?; + + Ok(Self { + evdev, + writer, + events: VecDeque::new(), + dropped: false, + writing: None, + + _reader_handle: reader_handle, + _writer_handle: writer_handle, + }) + } + + async fn read_raw(&mut self) -> Result<(u16, u16, i32), Error> { + let file = self.evdev.file().unwrap(); + + loop { + let result = file.readable().await?.try_io(|_| { + let mut event = MaybeUninit::uninit(); + let ret = unsafe { + glue::libevdev_next_event( + self.evdev.as_ptr(), + glue::libevdev_read_flag_LIBEVDEV_READ_FLAG_NORMAL, + event.as_mut_ptr(), + ) + }; + + if ret < 0 { + // ENODEV means that the device got disconnected. + // However, ErrorKind doesn't have support for it yet, + // so translate to BrokenPipe here to not introduce + // platform specific code to rkvm-server. + let err = if ret == -libc::ENODEV { + Error::new(ErrorKind::BrokenPipe, "Device disconnected") + } else { + Error::from_raw_os_error(-ret) + }; + + return Err(err); + } + + let event = unsafe { event.assume_init() }; + Ok((event.type_, event.code, event.value)) + }); + + match result { + Ok(result) => return result, + Err(_) => continue, // This means it would block. + } + } + } +} + impl InterceptorPlatform for InterceptorLinux { #[tracing::instrument(fields(path = ?self.writer.path()), skip(self))] async fn read(&mut self) -> Result { @@ -176,144 +311,11 @@ impl InterceptorPlatform for InterceptorLinux { None }; - Reapeat { delay, period } - } - - async fn read_raw(&mut self) -> Result<(u16, u16, i32), Error> { - let file = self.evdev.file().unwrap(); - - loop { - let result = file.readable().await?.try_io(|_| { - let mut event = MaybeUninit::uninit(); - let ret = unsafe { - glue::libevdev_next_event( - self.evdev.as_ptr(), - glue::libevdev_read_flag_LIBEVDEV_READ_FLAG_NORMAL, - event.as_mut_ptr(), - ) - }; - - if ret < 0 { - // ENODEV means that the device got disconnected. - // However, ErrorKind doesn't have support for it yet, - // so translate to BrokenPipe here to not introduce - // platform specific code to rkvm-server. - let err = if ret == -libc::ENODEV { - Error::new(ErrorKind::BrokenPipe, "Device disconnected") - } else { - Error::from_raw_os_error(-ret) - }; - - return Err(err); - } - - let event = unsafe { event.assume_init() }; - Ok((event.type_, event.code, event.value)) - }); - - match result { - Ok(result) => return result, - Err(_) => continue, // This means it would block. - } - } - } -} - -impl InterceptorLinux { - #[tracing::instrument(skip(registry))] - pub(crate) async fn open(path: &Path, registry: &Registry) -> Result { - let evdev = Evdev::open(path).await?; - let metadata = evdev.file().unwrap().get_ref().metadata()?; - - let reader_handle = registry - .register(Entry::from_metadata(&metadata)) - .ok_or(OpenError::NotAppliable)?; - - // "Upon binding to a device or resuming from suspend, a driver must report - // the current switch state. This ensures that the device, kernel, and userspace - // state is in sync." - // We have no way of knowing that. - let sw = unsafe { glue::libevdev_has_event_type(evdev.as_ptr(), glue::EV_SW) }; - if sw == 1 { - return Err(OpenError::NotAppliable); - } - - // Some buggy kernels can report nonsense abs info, so check for it and disable the axes. - for i in 0..glue::ABS_CNT { - let abs_info = unsafe { glue::libevdev_get_abs_info(evdev.as_ptr(), i).as_ref() }; - let abs_info = match abs_info { - Some(abs_info) => abs_info, - None => continue, - }; - - // See Linux source at drivers/input/misc/uinput.c#L408 commit 93f5de5f648d2b1ce3540a4ac71756d4a852dc23. - - let min = abs_info.minimum; - let max = abs_info.maximum; - - if (min != 0 || max != 0) && max < min { - tracing::warn!( - min = %min, - max = max, - axis = i, - "Detected nonsense min and max values for absolute axis, disabling it", - ); - - let ret = - unsafe { glue::libevdev_disable_event_code(evdev.as_ptr(), glue::EV_ABS, i) }; - - if ret < 0 { - return Err(Error::from_raw_os_error(-ret).into()); - } - } - } - - unsafe { - glue::libevdev_set_id_bustype(evdev.as_ptr(), glue::BUS_VIRTUAL as _); - } - - let ret = - unsafe { glue::libevdev_grab(evdev.as_ptr(), glue::libevdev_grab_mode_LIBEVDEV_GRAB) }; - - if ret < 0 { - // We do not use ErrorKind::ResourceBusy because it is a nightly-only API. - let err = if ret == -libc::EBUSY { - tracing::info!( - "Ignored {:?} because it is busy and can not be grabbed", - path - ); - OpenError::NotAppliable - } else { - Error::from_raw_os_error(-ret).into() - }; - - return Err(err); - } - - let writer = Writer::from_evdev(&evdev).await?; - let path = writer - .path() - .ok_or_else(|| Error::new(ErrorKind::Other, "No syspath for writer"))?; - - let metadata = fs::metadata(path)?; - let writer_handle = registry - .register(Entry::from_metadata(&metadata)) - .ok_or_else(|| Error::new(ErrorKind::Other, "Writer already registered"))?; - - Ok(Self { - evdev, - writer, - events: VecDeque::new(), - dropped: false, - writing: None, - - _reader_handle: reader_handle, - _writer_handle: writer_handle, - }) + Repeat { delay, period } } } -unsafe impl Send for Interceptor {} +unsafe impl Send for InterceptorLinux {} #[derive(Error, Debug)] pub(crate) enum OpenError { diff --git a/rkvm-input/src/linux/interceptor/caps.rs b/rkvm-input/src/linux/interceptor/caps.rs index 46e10dd..28ec6cf 100644 --- a/rkvm-input/src/linux/interceptor/caps.rs +++ b/rkvm-input/src/linux/interceptor/caps.rs @@ -1,7 +1,6 @@ use crate::abs::{AbsAxis, AbsInfo}; -use crate::interceptor::RelCaps; use crate::convert::Convert; -use crate::glue; +use crate::linux::glue; use crate::linux::interceptor::InterceptorLinux; use crate::key::Key; use crate::rel::RelAxis; @@ -12,7 +11,7 @@ pub struct RelCaps<'a> { } impl<'a> RelCaps<'a> { - fn new(interceptor: &'a InterceptorLinux) -> Self { + pub(super) fn new(interceptor: &'a InterceptorLinux) -> Self { let has = unsafe { glue::libevdev_has_event_type(interceptor.evdev.as_ptr(), glue::EV_REL) == 1 }; @@ -23,7 +22,7 @@ impl<'a> RelCaps<'a> { } } -impl Iterator for RelCapsLinux<'_> { +impl Iterator for RelCaps<'_> { type Item = RelAxis; fn next(&mut self) -> Option { diff --git a/rkvm-input/src/linux/key_convert.rs b/rkvm-input/src/linux/key_convert.rs index c2178d6..c2e33be 100644 --- a/rkvm-input/src/linux/key_convert.rs +++ b/rkvm-input/src/linux/key_convert.rs @@ -1,5 +1,7 @@ use crate::convert::Convert; +use crate::key::{Key,Keyboard,Button}; + impl Convert for Key { type Raw = u16; @@ -21,4 +23,4 @@ impl Convert for Key { Self::Button(button) => button.to_raw(), } } -} \ No newline at end of file +} diff --git a/rkvm-input/src/linux/keyboard_convert.rs b/rkvm-input/src/linux/keyboard_convert.rs index aa1fd3d..d4af3b7 100644 --- a/rkvm-input/src/linux/keyboard_convert.rs +++ b/rkvm-input/src/linux/keyboard_convert.rs @@ -1,5 +1,7 @@ +use crate::key::Keyboard; + use crate::convert::Convert; -use crate::glue; +use crate::linux::glue; impl Convert for Keyboard { type Raw = u16; diff --git a/rkvm-input/src/linux/mod.rs b/rkvm-input/src/linux/mod.rs index ceaccf4..044b455 100644 --- a/rkvm-input/src/linux/mod.rs +++ b/rkvm-input/src/linux/mod.rs @@ -4,11 +4,12 @@ pub mod monitor; mod evdev; mod glue; mod uinput; -mod registry.rs +mod registry; +mod interceptor; mod abs_convert; mod button_convert; mod keyboard_convert; mod key_convert; mod rel_convert; -mod sync_convert; \ No newline at end of file +mod sync_convert; diff --git a/rkvm-input/src/linux/monitor.rs b/rkvm-input/src/linux/monitor.rs index 4c0cd00..acbb0fd 100644 --- a/rkvm-input/src/linux/monitor.rs +++ b/rkvm-input/src/linux/monitor.rs @@ -1,7 +1,6 @@ use crate::monitor::MonitorPlatform; use crate::linux::interceptor::{InterceptorLinux, OpenError}; -use crate::registry::Registry; -use crate::interceptor::InterceptorPlatform; +use crate::linux::registry::Registry; use futures::StreamExt; use inotify::{Inotify, WatchMask}; @@ -34,7 +33,7 @@ impl MonitorPlatform for MonitorLinux { } } -async fn monitor(sender: Sender>) { +async fn monitor(sender: Sender>) { let run = async { let registry = Registry::new(); diff --git a/rkvm-input/src/linux/rel_convert.rs b/rkvm-input/src/linux/rel_convert.rs index a94c191..efbd486 100644 --- a/rkvm-input/src/linux/rel_convert.rs +++ b/rkvm-input/src/linux/rel_convert.rs @@ -1,5 +1,7 @@ use crate::convert::Convert; -use crate::glue; +use crate::linux::glue; + +use crate::rel::RelAxis; impl Convert for RelAxis { type Raw = u16; diff --git a/rkvm-input/src/linux/sync_convert.rs b/rkvm-input/src/linux/sync_convert.rs index 686ed8a..c1c1bcc 100644 --- a/rkvm-input/src/linux/sync_convert.rs +++ b/rkvm-input/src/linux/sync_convert.rs @@ -1,5 +1,7 @@ use crate::convert::Convert; -use crate::glue; +use crate::linux::glue; + +use crate::sync::SyncEvent; impl Convert for SyncEvent { type Raw = u16; diff --git a/rkvm-input/src/linux/uinput.rs b/rkvm-input/src/linux/uinput.rs index bdf5495..71642be 100644 --- a/rkvm-input/src/linux/uinput.rs +++ b/rkvm-input/src/linux/uinput.rs @@ -1,5 +1,5 @@ -use crate::evdev::Evdev; -use crate::glue::{self, libevdev_uinput}; +use crate::linux::evdev::Evdev; +use crate::linux::glue::{self, libevdev_uinput}; use std::fs::File; use std::io::Error; diff --git a/rkvm-input/src/linux/writer.rs b/rkvm-input/src/linux/writer.rs index 5d01dde..1c4ffaa 100644 --- a/rkvm-input/src/linux/writer.rs +++ b/rkvm-input/src/linux/writer.rs @@ -3,14 +3,14 @@ use libc::c_int; use crate::writer::{WriterPlatform,WriterBuilderPlatform}; use crate::abs::{AbsAxis, AbsEvent, AbsInfo}; use crate::convert::Convert; -use crate::evdev::Evdev; use crate::event::Event; -use crate::glue::{self, input_absinfo}; +use crate::linux::glue::{self, input_absinfo}; use crate::key::{Key, KeyEvent}; use crate::rel::{RelAxis, RelEvent}; -use crate::uinput::Uinput; +use crate::linux::uinput::Uinput; +use crate::linux::evdev::Evdev; -use std::ffi::{CString, OsStr}; +use std::ffi::{CString, CStr, OsStr}; use std::io::Error; use std::os::unix::ffi::OsStrExt; use std::path::Path; @@ -20,36 +20,8 @@ pub struct WriterLinux { uinput: Uinput, } -impl WriterPlatform for WriterLinux { - type Builder = WriterLinuxBuilder; - - fn builder() -> Result { - WriterLinuxBuilder::new() - } - - async fn write(&mut self, event: &Event) -> Result<(), Error> { - let (r#type, code, value) = match event { - Event::Rel(RelEvent { axis, value }) => (glue::EV_REL, axis.to_raw(), Some(*value)), - Event::Abs(event) => match event { - AbsEvent::Axis { axis, value } => (glue::EV_ABS, axis.to_raw(), Some(*value)), - AbsEvent::MtToolType { value } => ( - glue::EV_ABS, - Some(glue::ABS_MT_TOOL_TYPE as _), - value.to_raw(), - ), - }, - Event::Key(KeyEvent { down, key }) => (glue::EV_KEY, key.to_raw(), Some(*down as _)), - Event::Sync(event) => (glue::EV_SYN, event.to_raw(), Some(0)), - }; - - if let (Some(code), Some(value)) = (code, value) { - self.write_raw(r#type as _, code, value).await?; - } - - Ok(()) - } - - fn path(&self) -> Option<&Path> { +impl WriterLinux { + pub fn path(&self) -> Option<&Path> { let path = unsafe { glue::libevdev_uinput_get_devnode(self.uinput.as_ptr()) }; if path.is_null() { return None; @@ -62,13 +34,13 @@ impl WriterPlatform for WriterLinux { Some(path) } - async fn from_evdev(evdev: &Evdev) -> Result { + pub async fn from_evdev(evdev: &Evdev) -> Result { Ok(Self { uinput: Uinput::from_evdev(evdev).await?, }) } - async fn write_raw( + pub async fn write_raw( &mut self, r#type: u16, code: u16, @@ -100,6 +72,36 @@ impl WriterPlatform for WriterLinux { } } +impl WriterPlatform for WriterLinux { + type Builder = WriterLinuxBuilder; + + fn builder() -> Result { + WriterLinuxBuilder::new() + } + + async fn write(&mut self, event: &Event) -> Result<(), Error> { + let (r#type, code, value) = match event { + Event::Rel(RelEvent { axis, value }) => (glue::EV_REL, axis.to_raw(), Some(*value)), + Event::Abs(event) => match event { + AbsEvent::Axis { axis, value } => (glue::EV_ABS, axis.to_raw(), Some(*value)), + AbsEvent::MtToolType { value } => ( + glue::EV_ABS, + Some(glue::ABS_MT_TOOL_TYPE as _), + value.to_raw(), + ), + }, + Event::Key(KeyEvent { down, key }) => (glue::EV_KEY, key.to_raw(), Some(*down as _)), + Event::Sync(event) => (glue::EV_SYN, event.to_raw(), Some(0)), + }; + + if let (Some(code), Some(value)) = (code, value) { + self.write_raw(r#type as _, code, value).await?; + } + + Ok(()) + } +} + pub struct WriterLinuxBuilder { evdev: Evdev, } From 104caac5f10a1d0dc627939d1a96e5c4c9dcfa6f Mon Sep 17 00:00:00 2001 From: Unknow Date: Mon, 12 Jan 2026 21:45:35 +0100 Subject: [PATCH 3/8] improve event mapping --- rkvm-input/src/windows/mod.rs | 3 +- rkvm-input/src/windows/normalizer.rs | 17 +++ rkvm-input/src/windows/writer.rs | 214 +++++++++++++++++++++++---- 3 files changed, 208 insertions(+), 26 deletions(-) create mode 100644 rkvm-input/src/windows/normalizer.rs diff --git a/rkvm-input/src/windows/mod.rs b/rkvm-input/src/windows/mod.rs index 4a579ee..eff8a4f 100644 --- a/rkvm-input/src/windows/mod.rs +++ b/rkvm-input/src/windows/mod.rs @@ -1,4 +1,5 @@ pub mod writer; pub mod monitor; -mod interceptor; \ No newline at end of file +mod interceptor; +mod normalizer; \ No newline at end of file diff --git a/rkvm-input/src/windows/normalizer.rs b/rkvm-input/src/windows/normalizer.rs new file mode 100644 index 0000000..86f44f3 --- /dev/null +++ b/rkvm-input/src/windows/normalizer.rs @@ -0,0 +1,17 @@ + +pub struct AxisNormalizer { + min: i32, + max: i32, +} + +impl AxisNormalizer { + pub fn new(min: i32, max: i32) -> Self { + Self { min, max } + } + pub fn normalize(&self, value: i32) -> i32 { + if self.max == self.min { + return 0; + } + return (value * 65535 - self.min) / (self.max - self.min) + } +} \ No newline at end of file diff --git a/rkvm-input/src/windows/writer.rs b/rkvm-input/src/windows/writer.rs index 91c6719..61ba8f0 100644 --- a/rkvm-input/src/windows/writer.rs +++ b/rkvm-input/src/windows/writer.rs @@ -1,16 +1,23 @@ use crate::writer::{WriterPlatform,WriterBuilderPlatform}; -use crate::abs::{AbsAxis, AbsInfo}; +use crate::abs::{AbsAxis, AbsInfo, AbsEvent}; use crate::event::Event; -use crate::key::{Key, KeyEvent,Keyboard}; +use crate::key::{Key, KeyEvent,Keyboard, Button}; use crate::rel::{RelAxis, RelEvent}; +use crate::windows::normalizer::AxisNormalizer; + use std::ffi::CString; use std::io::Error; +use std::collections::HashMap; use windows::Win32::UI::Input::KeyboardAndMouse::*; pub struct WriterWindows { - buffer: Vec, + buffer: Vec, + + hi_wheel: bool, + hi_hwheel: bool, + abs_norm: HashMap, } impl WriterWindows { @@ -26,7 +33,7 @@ impl WriterWindows { unsafe { let sent = SendInput( self.buffer.as_slice(), - self.buffer.len() as i32, + size_of::() as i32, ); if sent == 0 { @@ -59,15 +66,38 @@ impl WriterWindows { } } - fn mouse_move(&mut self, dx: i32, dy: i32) { - self.push(INPUT { + fn button(&mut self, button: &Button, down:&bool) { + if let Some((flags, mousedata)) = map_button(button, down) { + self.push(INPUT { + r#type: INPUT_MOUSE, + Anonymous: INPUT_0 { + mi: MOUSEINPUT { + dx: 0, + dy: 0, + mouseData: mousedata, + dwFlags: flags, + time: 0, + dwExtraInfo: 0, + }, + }, + }) + } + } + + fn mouse_move(&mut self, abs: bool, dx: i32, dy: i32) { + tracing::info!("Mouse move: dx={}, dy={}", dx, dy); + let mut flags = MOUSEEVENTF_MOVE; + if abs { + flags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK; + } + self.push(INPUT { r#type: INPUT_MOUSE, Anonymous: INPUT_0 { mi: MOUSEINPUT { dx, dy, mouseData: 0, - dwFlags: MOUSEEVENTF_MOVE, + dwFlags: flags, time: 0, dwExtraInfo: 0, }, @@ -82,7 +112,7 @@ impl WriterWindows { mi: MOUSEINPUT { dx: 0, dy: 0, - mouseData: (delta * 120) as u32, + mouseData: delta as u32, dwFlags: MOUSEEVENTF_WHEEL, time: 0, dwExtraInfo: 0, @@ -90,43 +120,96 @@ impl WriterWindows { }, }) } + + fn mouse_hwheel(&mut self, delta: i32) { + self.push(INPUT { + r#type: INPUT_MOUSE, + Anonymous: INPUT_0 { + mi: MOUSEINPUT { + dx: 0, + dy: 0, + mouseData: delta as u32, + dwFlags: MOUSEEVENTF_HWHEEL, + time: 0, + dwExtraInfo: 0, + }, + }, + }) + } } impl WriterPlatform for WriterWindows { type Builder = WriterWindowsBuilder; - fn builder() -> Result { - Ok(WriterWindowsBuilder) + fn builder() -> Result { + Ok(WriterWindowsBuilder::new()) } async fn write(&mut self, event: &Event) -> Result<(), Error> { - match event { + match event { Event::Key(KeyEvent { key, down }) => { match key { Key::Key(key) => self.key(key, down), - Key::Button(button) => { - } + Key::Button(button) => self.button(button, down), } - } Event::Rel(RelEvent { axis, value }) => { match axis { - RelAxis::X => self.mouse_move(*value, 0), - RelAxis::Y => self.mouse_move(0, *value), - RelAxis::Wheel => self.mouse_wheel(*value), - _ => tracing::error!("Axe not handled: {:?}", axis), + RelAxis::X => self.mouse_move(false, *value, 0), + RelAxis::Y => self.mouse_move(false, 0, *value), + RelAxis::Wheel => if !self.hi_wheel { + self.mouse_wheel(*value*120) + } + RelAxis::HWheel => if !self.hi_hwheel { + self.mouse_hwheel(*value*120) + } + RelAxis::WheelHiRes => if self.hi_wheel { + self.mouse_wheel(*value) + } + RelAxis::HWheelHiRes => if self.hi_hwheel { + self.mouse_hwheel(*value) + } + _ => tracing::warn!("Axe not handled: {:?}", axis), + } + } + Event::Abs(event) => { + match event { + AbsEvent::Axis { axis, value } => { + if let Some(normalizer) = self.abs_norm.get(axis) { + let nv = normalizer.normalize(*value); + match axis { + AbsAxis::X => self.mouse_move(true, nv, 0), + AbsAxis::Y => self.mouse_move(true, 0, nv), + _ => tracing::warn!("Abs Axis not handled: {:?}", axis) + } + } else { + tracing::warn!("Abs Axis not handled: {:?}", axis); + } + } + _ => tracing::warn!("Abs event not handled: {:?}", event), } } - Event::Abs(_event) => {} Event::Sync(_) => self.flush()? } Ok(()) } +} - +pub struct WriterWindowsBuilder { + hi_wheel: bool, + hi_hwheel: bool, + abs_norm: HashMap, } -pub struct WriterWindowsBuilder; +impl WriterWindowsBuilder { + fn new() -> Self { + Self { + hi_wheel: false, + hi_hwheel: false, + abs_norm: HashMap::new(), + } + } +} impl WriterBuilderPlatform for WriterWindowsBuilder { type Writer = WriterWindows; @@ -146,10 +229,21 @@ impl WriterBuilderPlatform for WriterWindowsBuilder { fn version(self, _value: u16) -> Self { self } - fn rel>(self, _items: T) -> Result { + fn rel>(mut self, items: T) -> Result { + for axis in items { + match axis { + RelAxis::WheelHiRes => self.hi_wheel = true, + RelAxis::HWheelHiRes => self.hi_hwheel = true, + _ => {}, + } + } Ok(self) } - fn abs>(self, _items: T) -> Result { + fn abs>(mut self, items: T) -> Result { + items.into_iter().for_each(|(axis, info)| { + let normalizer = AxisNormalizer::new(info.min, info.max); + self.abs_norm.insert(axis, normalizer); + }); Ok(self) } fn key>(self, _items: T) -> Result { @@ -167,11 +261,43 @@ impl WriterBuilderPlatform for WriterWindowsBuilder { async fn build(self) -> Result { Ok(WriterWindows{ buffer: Vec::with_capacity(16), + hi_wheel: self.hi_wheel, + hi_hwheel: self.hi_hwheel, + abs_norm: self.abs_norm, }) } } -const fn map_key_to_scancode(key: &Keyboard) -> Option<(u16, bool)> { +fn map_button(button: &Button, down:&bool) -> Option<(MOUSE_EVENT_FLAGS,u32)> { + match button { + Button::Left => Some(( + if *down { MOUSEEVENTF_LEFTDOWN } else { MOUSEEVENTF_LEFTUP }, + 0 as u32, + )), + Button::Right => Some(( + if *down { MOUSEEVENTF_RIGHTDOWN } else { MOUSEEVENTF_RIGHTUP }, + 0 as u32, + )), + Button::Middle => Some(( + if *down { MOUSEEVENTF_MIDDLEDOWN } else { MOUSEEVENTF_MIDDLEUP }, + 0 as u32, + )), + Button::Side => Some(( + if *down { MOUSEEVENTF_XDOWN } else { MOUSEEVENTF_XUP }, + 1 as u32, + )), + Button::Extra => Some(( + if *down { MOUSEEVENTF_XDOWN } else { MOUSEEVENTF_XUP }, + 2 as u32, + )), + _ => { + tracing::warn!("Unsupported mouse button: {:?}", button); + None + } + } +} + +fn map_key_to_scancode(key: &Keyboard) -> Option<(u16, bool)> { match key { // Letters Keyboard::A => Some((0x1E, false)), @@ -233,8 +359,21 @@ const fn map_key_to_scancode(key: &Keyboard) -> Option<(u16, bool)> { Keyboard::F11 => Some((0x57, false)), Keyboard::F12 => Some((0x58, false)), + + // Special Keyboards Keyboard::Enter => Some((0x1C, false)), + Keyboard::Minus => Some((0x0C, false)), + Keyboard::Equal => Some((0x0D, false)), + Keyboard::LeftBrace => Some((0x1A, false)), + Keyboard::RightBrace => Some((0x1B, false)), + Keyboard::Apostrophe => Some((0x28, false)), + Keyboard::Slash => Some((0x35, false)), + Keyboard::Dot => Some((0x34, false)), + Keyboard::Semicolon => Some((0x27, false)), + Keyboard::Grave => Some((0x29, false)), + Keyboard::Comma => Some((0x33, false)), + Keyboard::Backslash => Some((0x2B, false)), Keyboard::Esc => Some((0x01, false)), Keyboard::Backspace => Some((0x0E, false)), Keyboard::Tab => Some((0x0F, false)), @@ -248,6 +387,9 @@ const fn map_key_to_scancode(key: &Keyboard) -> Option<(u16, bool)> { Keyboard::RightAlt => Some((0x38, true)), Keyboard::LeftMeta => Some((0x5B, true)), // Windows Keyboard Keyboard::RightMeta => Some((0x5C, true)), + Keyboard::SysRq => Some((0x54, false)), + Keyboard::ScrollLock => Some((0x46, false)), + Keyboard::Compose => Some((0x5D, true)), Keyboard::Insert => Some((0x52, true)), Keyboard::Delete => Some((0x53, true)), @@ -256,6 +398,28 @@ const fn map_key_to_scancode(key: &Keyboard) -> Option<(u16, bool)> { Keyboard::PageUp => Some((0x49, true)), Keyboard::PageDown => Some((0x51, true)), - _ => None, // ingore unsupported keys + // keypad + Keyboard::NumLock => Some((0x45, false)), + Keyboard::Kp0 => Some((0x52, false)), + Keyboard::Kp1 => Some((0x4F, false)), + Keyboard::Kp2 => Some((0x50, false)), + Keyboard::Kp3 => Some((0x51, false)), + Keyboard::Kp4 => Some((0x4B, false)), + Keyboard::Kp5 => Some((0x4C, false)), + Keyboard::Kp6 => Some((0x4D, false)), + Keyboard::Kp7 => Some((0x47, false)), + Keyboard::Kp8 => Some((0x48, false)), + Keyboard::Kp9 => Some((0x49, false)), + Keyboard::KpDot => Some((0x53, false)), + Keyboard::KpAsterisk => Some((0x37, false)), + Keyboard::KpEnter => Some((0x1C, true)), + Keyboard::KpMinus => Some((0x4A, false)), + Keyboard::KpPlus => Some((0x4E, false)), + Keyboard::KpSlash => Some((0x35, true)), + + _ => { + tracing::warn!("Unsupported keyboard key : {:?}", key); + None + } } } \ No newline at end of file From 5db59f374fdb6ec46562e21ed01df05ce1786d5d Mon Sep 17 00:00:00 2001 From: Unknow Date: Tue, 13 Jan 2026 15:59:39 +0100 Subject: [PATCH 4/8] add key repeat for windows --- rkvm-input/src/windows/key_repeater.rs | 91 ++++++++++++++++++++++++++ rkvm-input/src/windows/mod.rs | 3 +- rkvm-input/src/windows/writer.rs | 46 +++++++++++-- 3 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 rkvm-input/src/windows/key_repeater.rs diff --git a/rkvm-input/src/windows/key_repeater.rs b/rkvm-input/src/windows/key_repeater.rs new file mode 100644 index 0000000..9f40c2d --- /dev/null +++ b/rkvm-input/src/windows/key_repeater.rs @@ -0,0 +1,91 @@ +use tokio::task::JoinHandle; +use tokio::time::{Duration, Instant, interval_at,Interval}; +use windows::Win32::UI::Input::KeyboardAndMouse::*; + +use crate::key::Keyboard; + +pub struct KeyRepeater { + delay: Duration, + period: Duration, + + key: Keyboard, + task: Option>, + +} + +impl Drop for KeyRepeater { + fn drop(&mut self) { + self.stop(); + } +} + +impl KeyRepeater { + pub fn new(key: Keyboard, scan: u16, flags: KEYBD_EVENT_FLAGS, delay: Duration, period: Duration) -> Self { + let mut kr = Self { + delay: delay, + period: period, + key: key, + task: None, + }; + + kr.start(scan, flags); + + kr + } + + pub fn key(&mut self, key: Keyboard, flags: KEYBD_EVENT_FLAGS, scan: u16, down: &bool) -> bool { + if self.key == key { + if !down { + self.stop(); + return true; + } + return false; + } + + if *down { + self.key=key; + self.start(scan, flags); + } + return false; + } + + fn stop(&mut self) { + match &self.task { + None => return, + Some(t) => { + t.abort(); + self.task = None; + } + } + } + + fn start(&mut self, scan: u16, flags: KEYBD_EVENT_FLAGS) { + self.stop(); + + let start = Instant::now() + self.delay; + let interval = interval_at(start, self.period); + self.task = Some(tokio::spawn(run(interval, scan, flags))); + } +} + +async fn run(mut interval: Interval, scan: u16, flags: KEYBD_EVENT_FLAGS) { + loop { + interval.tick().await; + + let input = INPUT { + r#type: INPUT_KEYBOARD, + Anonymous: INPUT_0 { + ki: KEYBDINPUT { + wVk: VIRTUAL_KEY::default(), + wScan: scan, + dwFlags: flags, + time: 0, + dwExtraInfo: 0, + }, + }, + }; + unsafe { + SendInput(&[input], size_of::() as i32); + } + } +} \ No newline at end of file diff --git a/rkvm-input/src/windows/mod.rs b/rkvm-input/src/windows/mod.rs index eff8a4f..599d713 100644 --- a/rkvm-input/src/windows/mod.rs +++ b/rkvm-input/src/windows/mod.rs @@ -2,4 +2,5 @@ pub mod writer; pub mod monitor; mod interceptor; -mod normalizer; \ No newline at end of file +mod normalizer; +mod key_repeater; \ No newline at end of file diff --git a/rkvm-input/src/windows/writer.rs b/rkvm-input/src/windows/writer.rs index 61ba8f0..062be43 100644 --- a/rkvm-input/src/windows/writer.rs +++ b/rkvm-input/src/windows/writer.rs @@ -4,28 +4,34 @@ use crate::event::Event; use crate::key::{Key, KeyEvent,Keyboard, Button}; use crate::rel::{RelAxis, RelEvent}; +use crate::windows::key_repeater::KeyRepeater; use crate::windows::normalizer::AxisNormalizer; use std::ffi::CString; use std::io::Error; use std::collections::HashMap; +use std::time::Duration; use windows::Win32::UI::Input::KeyboardAndMouse::*; pub struct WriterWindows { buffer: Vec, + repeat_delay: Duration, + repeat_period: Duration, + key_reapeter: Option, + hi_wheel: bool, hi_hwheel: bool, abs_norm: HashMap, } impl WriterWindows { - fn push(&mut self, input: INPUT) { + pub fn push(&mut self, input: INPUT) { self.buffer.push(input); } - fn flush(&mut self) -> Result<(), Error> { + pub fn flush(&mut self) -> Result<(), Error> { if self.buffer.is_empty() { return Ok(()); } @@ -45,11 +51,21 @@ impl WriterWindows { Ok(()) } - fn key(&mut self, key: &Keyboard, down:&bool) { + pub fn key(&mut self, key: &Keyboard, down:&bool) { if let Some((scan, extended)) = map_key_to_scancode(key) { let mut flags = KEYEVENTF_SCANCODE; if !down { flags |= KEYEVENTF_KEYUP; } if extended { flags |= KEYEVENTF_EXTENDEDKEY; } + + match (&mut self.key_reapeter,down) { + (Some(kr), _) => { + if kr.key(*key, flags, scan, down) { + self.key_reapeter = None; + } + } + (None, true) => self.key_reapeter = Some(KeyRepeater::new(*key, scan, flags, self.repeat_delay, self.repeat_period)), + (_,_) => {} + } self.push(INPUT { r#type: INPUT_KEYBOARD, @@ -85,7 +101,6 @@ impl WriterWindows { } fn mouse_move(&mut self, abs: bool, dx: i32, dy: i32) { - tracing::info!("Mouse move: dx={}, dy={}", dx, dy); let mut flags = MOUSEEVENTF_MOVE; if abs { flags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK; @@ -199,6 +214,8 @@ pub struct WriterWindowsBuilder { hi_wheel: bool, hi_hwheel: bool, abs_norm: HashMap, + repeat_delay: Duration, + repeat_period: Duration, } impl WriterWindowsBuilder { @@ -207,6 +224,8 @@ impl WriterWindowsBuilder { hi_wheel: false, hi_hwheel: false, abs_norm: HashMap::new(), + repeat_delay: Duration::ZERO, + repeat_period: Duration::ZERO, } } } @@ -250,11 +269,21 @@ impl WriterBuilderPlatform for WriterWindowsBuilder { Ok(self) } - fn delay(self, _value: Option) -> Result { + fn delay(mut self, value: Option) -> Result { + if let Some(delay) = value { + if delay > 0 { + self.repeat_delay = Duration::from_millis(delay as u64); + } + } Ok(self) } - fn period(self, _value: Option) -> Result { + fn period(mut self, value: Option) -> Result { + if let Some(period) = value { + if period > 0 { + self.repeat_period = Duration::from_millis(period as u64); + } + } Ok(self) } @@ -264,6 +293,9 @@ impl WriterBuilderPlatform for WriterWindowsBuilder { hi_wheel: self.hi_wheel, hi_hwheel: self.hi_hwheel, abs_norm: self.abs_norm, + repeat_delay: self.repeat_delay, + repeat_period: self.repeat_period, + key_reapeter: None, }) } } @@ -390,6 +422,8 @@ fn map_key_to_scancode(key: &Keyboard) -> Option<(u16, bool)> { Keyboard::SysRq => Some((0x54, false)), Keyboard::ScrollLock => Some((0x46, false)), Keyboard::Compose => Some((0x5D, true)), + Keyboard::Pause => Some((0x45, true)), + Keyboard::N102nd => Some((0x56, false)), Keyboard::Insert => Some((0x52, true)), Keyboard::Delete => Some((0x53, true)), From e4db9c91f8e2a7718d9c96790bc8e09076643202 Mon Sep 17 00:00:00 2001 From: Unknow Date: Tue, 20 Jan 2026 22:45:00 +0100 Subject: [PATCH 5/8] add option to log to file and reconnect on error --- rkvm-client/src/client.rs | 2 +- rkvm-client/src/config.rs | 1 + rkvm-client/src/main.rs | 88 +++++++++++++++++++--------- rkvm-input/src/windows/normalizer.rs | 22 +++---- rkvm-input/src/windows/writer.rs | 2 +- 5 files changed, 73 insertions(+), 42 deletions(-) diff --git a/rkvm-client/src/client.rs b/rkvm-client/src/client.rs index ec9dea4..a990232 100644 --- a/rkvm-client/src/client.rs +++ b/rkvm-client/src/client.rs @@ -29,7 +29,7 @@ pub enum Error { pub async fn run( hostname: &ServerName, port: u16, - connector: TlsConnector, + connector: &TlsConnector, password: &str, ) -> Result<(), Error> { // Intentionally don't impose any timeout for TCP connect. diff --git a/rkvm-client/src/config.rs b/rkvm-client/src/config.rs index 8d8145e..e86e1e8 100644 --- a/rkvm-client/src/config.rs +++ b/rkvm-client/src/config.rs @@ -12,6 +12,7 @@ pub struct Config { pub server: Server, pub certificate: PathBuf, pub password: String, + pub reconnect_delay: Option, } pub struct Server { diff --git a/rkvm-client/src/main.rs b/rkvm-client/src/main.rs index 5e15b42..2185b78 100644 --- a/rkvm-client/src/main.rs +++ b/rkvm-client/src/main.rs @@ -1,15 +1,19 @@ +#![cfg_attr(target_os = "windows", windows_subsystem = "windows")] mod client; mod config; mod tls; use clap::Parser; use config::Config; +use std::time::Duration; use std::path::PathBuf; +use std::fs::OpenOptions; +use std::io::{stdout, BufWriter}; use std::process::ExitCode; use tokio::{fs, signal}; -use tracing::subscriber; -use tracing_subscriber::filter::{EnvFilter, LevelFilter}; -use tracing_subscriber::fmt; +use tokio::time::sleep; +use tokio_rustls::TlsConnector; +use tracing_subscriber::{fmt,Registry,EnvFilter}; use tracing_subscriber::prelude::*; #[derive(Parser)] @@ -17,21 +21,54 @@ use tracing_subscriber::prelude::*; struct Args { #[clap(help = "Path to configuration file")] config_path: PathBuf, + #[clap(long, default_value = "info", help = "log filter")] + log_level: String, + /// Optional log file + #[clap(long, help = "output file for the logs")] + log_file: Option, } -#[tokio::main] -async fn main() -> ExitCode { - let filter = EnvFilter::builder() - .with_default_directive(LevelFilter::INFO.into()) - .from_env_lossy(); +async fn main_loop(config: &Config, connector: &TlsConnector) -> ExitCode { + tokio::select! { + result = client::run(&config.server.hostname, config.server.port, connector, &config.password) => { + if let Err(err) = result { + tracing::error!("Error: {}", err); + return ExitCode::FAILURE; + } + } + // This is needed to properly clean libevdev stuff up. + result = signal::ctrl_c() => { + if let Err(err) = result { + tracing::error!("Error setting up signal handler: {}", err); + return ExitCode::FAILURE; + } - let registry = tracing_subscriber::registry() - .with(filter) - .with(fmt::layer().without_time()); + tracing::info!("Exiting on signal"); + } + } - subscriber::set_global_default(registry).unwrap(); + ExitCode::SUCCESS +} +fn init_tracing(log_level: &String, log_file: &Option) { + let filter = EnvFilter::new(log_level); + if let Some(path) = log_file { + let file = OpenOptions::new().create(true).append(true).open(path).unwrap(); + let fmt_layer = fmt::layer().with_writer(move || BufWriter::new(file.try_clone().unwrap())).without_time(); + let registry = Registry::default().with(filter).with(fmt_layer); + tracing::subscriber::set_global_default(registry).unwrap(); + } else { + let fmt_layer = fmt::layer().with_writer(stdout).without_time(); + let registry = Registry::default().with(filter).with(fmt_layer); + tracing::subscriber::set_global_default(registry).unwrap(); + } +} + +#[tokio::main] +async fn main() -> ExitCode { let args = Args::parse(); + init_tracing(&args.log_level, &args.log_file); + let config = match fs::read_to_string(&args.config_path).await { Ok(config) => config, Err(err) => { @@ -55,24 +92,17 @@ async fn main() -> ExitCode { return ExitCode::FAILURE; } }; - - tokio::select! { - result = client::run(&config.server.hostname, config.server.port, connector, &config.password) => { - if let Err(err) = result { - tracing::error!("Error: {}", err); - return ExitCode::FAILURE; - } - } - // This is needed to properly clean libevdev stuff up. - result = signal::ctrl_c() => { - if let Err(err) = result { - tracing::error!("Error setting up signal handler: {}", err); - return ExitCode::FAILURE; + + match config.reconnect_delay.map(Duration::from_secs) { + None => main_loop(&config, &connector).await, + Some(reconnect_delay) => { + loop { + let code = main_loop(&config, &connector).await; + if code == ExitCode::SUCCESS { + return code; + } + sleep(reconnect_delay).await; } - - tracing::info!("Exiting on signal"); } } - - ExitCode::SUCCESS } diff --git a/rkvm-input/src/windows/normalizer.rs b/rkvm-input/src/windows/normalizer.rs index 86f44f3..01afebb 100644 --- a/rkvm-input/src/windows/normalizer.rs +++ b/rkvm-input/src/windows/normalizer.rs @@ -1,17 +1,17 @@ pub struct AxisNormalizer { - min: i32, - max: i32, + min: i32, + max: i32, } impl AxisNormalizer { - pub fn new(min: i32, max: i32) -> Self { - Self { min, max } - } - pub fn normalize(&self, value: i32) -> i32 { - if self.max == self.min { - return 0; - } - return (value * 65535 - self.min) / (self.max - self.min) - } + pub fn new(min: i32, max: i32) -> Self { + Self { min, max } + } + pub fn normalize(&self, value: i32) -> i32 { + if self.max == self.min { + return 0; + } + return (value * 65535 - self.min) / (self.max - self.min) + } } \ No newline at end of file diff --git a/rkvm-input/src/windows/writer.rs b/rkvm-input/src/windows/writer.rs index 062be43..73c41f6 100644 --- a/rkvm-input/src/windows/writer.rs +++ b/rkvm-input/src/windows/writer.rs @@ -43,7 +43,7 @@ impl WriterWindows { ); if sent == 0 { - return Err(Error::last_os_error()); + tracing::error!("SendInput failed: {:?}", Error::last_os_error()); } } From a157bfa2f8fab43edc2f3fea8285bb67d7f37a01 Mon Sep 17 00:00:00 2001 From: Unknow Date: Sun, 22 Feb 2026 17:58:14 +0100 Subject: [PATCH 6/8] stub windows service --- rkvm-client/Cargo.toml | 19 +++ rkvm-client/src/client.rs | 234 ++++++++++++++++------------ rkvm-client/src/config.rs | 1 - rkvm-client/src/main.rs | 136 ++++++++-------- rkvm-client/src/stream.rs | 136 ++++++++++++++++ rkvm-client/src/tls.rs | 13 +- rkvm-client/src/windows-service.rs | 240 +++++++++++++++++++++++++++++ rkvm-net/src/lib.rs | 7 +- rkvm-net/src/version.rs | 2 +- rkvm-server/src/server.rs | 14 +- windows-service/all.bat | 19 +++ windows-service/install.bat | 24 +++ windows-service/uninstall.bat | 17 ++ 13 files changed, 669 insertions(+), 193 deletions(-) create mode 100644 rkvm-client/src/stream.rs create mode 100644 rkvm-client/src/windows-service.rs create mode 100644 windows-service/all.bat create mode 100644 windows-service/install.bat create mode 100644 windows-service/uninstall.bat diff --git a/rkvm-client/Cargo.toml b/rkvm-client/Cargo.toml index 272a814..09cb374 100644 --- a/rkvm-client/Cargo.toml +++ b/rkvm-client/Cargo.toml @@ -5,9 +5,14 @@ version = "0.6.1" authors = ["Jan Trefil <8711792+htrefil@users.noreply.github.com>"] edition = "2021" +[[bin]] +name = "rkvm-service" +path = "src/windows-service.rs" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = "0.1.89" tokio = { version = "1.0.1", features = ["macros", "time", "fs", "net", "signal", "rt-multi-thread", "sync"] } rkvm-input = { path = "../rkvm-input" } rkvm-net = { path = "../rkvm-net" } @@ -21,6 +26,20 @@ rustls-pemfile = "1.0.2" tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +[target.'cfg(windows)'.dependencies] +bincode = "1.3.3" +windows = { version = "0.62", features = [ + "Win32_System_RemoteDesktop", + "Win32_Foundation", + "Win32_Security_Authorization", + "Win32_System_Threading", + "Win32_Security", + "Win32_System_Environment", +] } +windows-core = "0.62" +windows-sys = { version = "0.61", features = [ "Win32" ]} +windows-service = "0.8" + [package.metadata.rpm] package = "rkvm-client" diff --git a/rkvm-client/src/client.rs b/rkvm-client/src/client.rs index a990232..dfb7612 100644 --- a/rkvm-client/src/client.rs +++ b/rkvm-client/src/client.rs @@ -1,37 +1,69 @@ +use crate::config::Config; +use crate::stream::{RkvmStream, RkvmWriter}; + use rkvm_input::writer::{Writer,WriterPlatform,WriterBuilderPlatform}; use rkvm_net::auth::{AuthChallenge, AuthStatus}; use rkvm_net::message::Message; use rkvm_net::version::Version; -use rkvm_net::{Pong, Update}; +use rkvm_net::Update; use std::collections::hash_map::Entry; use std::collections::HashMap; -use std::io; +use std::fs::OpenOptions; +use std::io::{self, stdout, BufWriter}; +use std::path::Path; use std::time::Instant; use thiserror::Error; -use tokio::io::{AsyncWriteExt, BufStream}; +use tokio::fs; +use tokio::io::{AsyncRead, AsyncWriteExt, BufStream}; use tokio::net::TcpStream; use tokio::time; -use tokio_rustls::rustls::ServerName; +use tokio_rustls::rustls::{self, ServerName}; use tokio_rustls::TlsConnector; +use tracing_subscriber::{fmt,Registry,EnvFilter}; +use tracing_subscriber::prelude::*; +#[cfg(target_os="windows")] +use windows::core; #[derive(Error, Debug)] pub enum Error { #[error("Network error: {0}")] Network(io::Error), - #[error("Input error: {0}")] - Input(io::Error), + #[error("Io error: {0}")] + Io(#[from] io::Error), + #[error(transparent)] + Rustls(#[from] rustls::Error), + #[error("Toml error: {0}")] + Toml(#[from] toml::de::Error), + #[cfg(target_os="windows")] + #[error("Windows API error: {0}")] + Windows(#[from] core::Error), #[error("Incompatible server version (got {server}, expected {client})")] Version { server: Version, client: Version }, #[error("Invalid password")] Auth, } -pub async fn run( - hostname: &ServerName, - port: u16, - connector: &TlsConnector, - password: &str, -) -> Result<(), Error> { +pub fn init_tracing>(log_level: &String, log_file: &Option

) { + let filter = EnvFilter::new(log_level); + if let Some(path) = log_file { + let file = OpenOptions::new().create(true).append(true).open(path).unwrap(); + let fmt_layer = fmt::layer().with_ansi(false).with_writer(move || BufWriter::new(file.try_clone().unwrap())).without_time(); + let registry = Registry::default().with(filter).with(fmt_layer); + tracing::subscriber::set_global_default(registry).unwrap(); + } else { + let fmt_layer = fmt::layer().with_writer(stdout).without_time(); + let registry = Registry::default().with(filter).with(fmt_layer); + tracing::subscriber::set_global_default(registry).unwrap(); + } +} + +pub async fn init_config + ?Sized> (path: &P) -> Result { + let config = fs::read_to_string(path).await?; + let config = toml::from_str::(&config)?; + return Ok(config); + } + +pub async fn init_stream(hostname: &ServerName, port: u16, connector: &TlsConnector, password: &str) -> Result { // Intentionally don't impose any timeout for TCP connect. let stream = match hostname { ServerName::DnsName(name) => TcpStream::connect(&(name.as_ref(), port)).await, @@ -98,111 +130,119 @@ pub async fn run( } tracing::info!("Authenticated successfully"); + Ok(RkvmStream::Tcp(stream)) +} + +pub async fn run(mut reader: R, mut writer: W, mut handler: H) -> Result<(), Error> + where + R: AsyncRead + Send + Unpin, + W: RkvmWriter + Send, + H: AsyncFnMut(Update) -> Result<(), Error>, { let mut start = Instant::now(); let mut interval = time::interval(rkvm_net::PING_INTERVAL + rkvm_net::READ_TIMEOUT); - let mut writers = HashMap::new(); // Interval ticks immediately after creation. interval.tick().await; loop { let update = tokio::select! { - update = Update::decode(&mut stream) => update.map_err(Error::Network)?, + update = Update::decode(&mut reader) => update.map_err(Error::Network)?, _ = interval.tick() => return Err(Error::Network(io::Error::new(io::ErrorKind::TimedOut, "Ping timed out"))), }; - match update { - Update::CreateDevice { - id, - name, - vendor, - product, - version, - rel, - abs, - keys, - delay, - period, - } => { - let entry = writers.entry(id); - if let Entry::Occupied(_) = entry { - return Err(Error::Network(io::Error::new( - io::ErrorKind::InvalidData, - "Server created the same device twice", - ))); - } - - let writer = async { - Writer::builder()? - .name(&name) - .vendor(vendor) - .product(product) - .version(version) - .rel(rel)? - .abs(abs)? - .key(keys)? - .delay(delay)? - .period(period)? - .build() - .await - } - .await - .map_err(Error::Input)?; - - entry.or_insert(writer); - - tracing::info!( - id = %id, - name = ?name, - vendor = %vendor, - product = %product, - version = %version, - "Created new device" - ); - } - Update::DestroyDevice { id } => { - if writers.remove(&id).is_none() { - return Err(Error::Network(io::Error::new( - io::ErrorKind::InvalidData, - "Server destroyed a nonexistent device", - ))); - } - - tracing::info!(id = %id, "Destroyed device"); - } - Update::Event { id, event } => { - let writer = writers.get_mut(&id).ok_or_else(|| { - Error::Network(io::Error::new( - io::ErrorKind::InvalidData, - "Server sent an event to a nonexistent device", - )) - })?; + tracing::debug!("received {:?}", update); + interval.reset(); - writer.write(&event).await.map_err(Error::Input)?; + if let Update::Ping = &update { + let duration = start.elapsed(); + tracing::debug!(duration = ?duration, "Received ping"); - tracing::trace!(id = %id, "Wrote an event to device"); - } - Update::Ping => { - let duration = start.elapsed(); - tracing::debug!(duration = ?duration, "Received ping"); + start = Instant::now(); - start = Instant::now(); - interval.reset(); + writer.send(Update::Pong).await?; - rkvm_net::timeout(rkvm_net::WRITE_TIMEOUT, async { - Pong.encode(&mut stream).await?; - stream.flush().await?; + let duration = start.elapsed(); + tracing::debug!(duration = ?duration, "Sent pong"); + } + handler(update).await? + } +} - Ok(()) - }) - .await - .map_err(Error::Network)?; +pub async fn handler(writers: &mut HashMap, update: Update) -> Result<(), Error> { + match update { + Update::CreateDevice { + id, + name, + vendor, + product, + version, + rel, + abs, + keys, + delay, + period, + } => { + let entry = writers.entry(id); + if let Entry::Occupied(_) = entry { + return Err(Error::Network(io::Error::new( + io::ErrorKind::InvalidData, + "Server created the same device twice", + ))); + } - let duration = start.elapsed(); - tracing::debug!(duration = ?duration, "Sent pong"); + let writer = async { + Writer::builder()? + .name(&name) + .vendor(vendor) + .product(product) + .version(version) + .rel(rel)? + .abs(abs)? + .key(keys)? + .delay(delay)? + .period(period)? + .build() + .await + } + .await?; + + entry.or_insert(writer); + + tracing::info!( + id = %id, + name = ?name, + vendor = %vendor, + product = %product, + version = %version, + "Created new device" + ); + } + Update::DestroyDevice { id } => { + if writers.remove(&id).is_none() { + return Err(Error::Network(io::Error::new( + io::ErrorKind::InvalidData, + "Server destroyed a nonexistent device", + ))); } + + tracing::info!(id = %id, "Destroyed device"); + } + Update::Event { id, event } => { + let writer = writers.get_mut(&id).ok_or_else(|| { + Error::Network(io::Error::new( + io::ErrorKind::InvalidData, + "Server sent an event to a nonexistent device", + )) + })?; + + writer.write(&event).await?; + + tracing::trace!(id = %id, "Wrote an event to device"); } + Update::Ping => {} + Update::Pong => {} } + Ok(()) } diff --git a/rkvm-client/src/config.rs b/rkvm-client/src/config.rs index e86e1e8..8d8145e 100644 --- a/rkvm-client/src/config.rs +++ b/rkvm-client/src/config.rs @@ -12,7 +12,6 @@ pub struct Config { pub server: Server, pub certificate: PathBuf, pub password: String, - pub reconnect_delay: Option, } pub struct Server { diff --git a/rkvm-client/src/main.rs b/rkvm-client/src/main.rs index 2185b78..65f10f0 100644 --- a/rkvm-client/src/main.rs +++ b/rkvm-client/src/main.rs @@ -1,20 +1,26 @@ -#![cfg_attr(target_os = "windows", windows_subsystem = "windows")] -mod client; +mod client; mod config; +mod stream; mod tls; use clap::Parser; -use config::Config; -use std::time::Duration; +use client::{Error, init_tracing, init_config}; +use rkvm_input::writer::Writer; +use std::cell::RefCell; +use std::collections::HashMap; use std::path::PathBuf; -use std::fs::OpenOptions; -use std::io::{stdout, BufWriter}; use std::process::ExitCode; -use tokio::{fs, signal}; -use tokio::time::sleep; -use tokio_rustls::TlsConnector; -use tracing_subscriber::{fmt,Registry,EnvFilter}; -use tracing_subscriber::prelude::*; +use std::rc::Rc; +use stream::RkvmStream; +use tokio::io::{split, BufStream}; +use tokio::signal; +use tokio::time::{Duration, sleep}; + +#[cfg(target_os="windows")] +use tokio::net::windows::named_pipe::ClientOptions; +#[cfg(target_os="windows")] +use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY; + #[derive(Parser)] #[structopt(name = "rkvm-client", about = "The rkvm client application")] @@ -23,44 +29,39 @@ struct Args { config_path: PathBuf, #[clap(long, default_value = "info", help = "log filter")] log_level: String, - /// Optional log file #[clap(long, help = "output file for the logs")] log_file: Option, + #[cfg(target_os="windows")] + #[clap(long, help = "internal use")] + pipe: bool, } -async fn main_loop(config: &Config, connector: &TlsConnector) -> ExitCode { - tokio::select! { - result = client::run(&config.server.hostname, config.server.port, connector, &config.password) => { - if let Err(err) = result { - tracing::error!("Error: {}", err); - return ExitCode::FAILURE; - } - } - // This is needed to properly clean libevdev stuff up. - result = signal::ctrl_c() => { - if let Err(err) = result { - tracing::error!("Error setting up signal handler: {}", err); - return ExitCode::FAILURE; - } - - tracing::info!("Exiting on signal"); - } - } - - ExitCode::SUCCESS +async fn process_args_default(args: Args) -> Result { + let config = init_config(&args.config_path).await?; + let connector = tls::configure(&config.certificate).await?; + client::init_stream(&config.server.hostname, config.server.port, &connector, &config.password).await +} +#[cfg(not(target_os="windows"))] +async fn process_args(args: Args) -> Result { + process_args_default(arg).await } -fn init_tracing(log_level: &String, log_file: &Option) { - let filter = EnvFilter::new(log_level); - if let Some(path) = log_file { - let file = OpenOptions::new().create(true).append(true).open(path).unwrap(); - let fmt_layer = fmt::layer().with_writer(move || BufWriter::new(file.try_clone().unwrap())).without_time(); - let registry = Registry::default().with(filter).with(fmt_layer); - tracing::subscriber::set_global_default(registry).unwrap(); - } else { - let fmt_layer = fmt::layer().with_writer(stdout).without_time(); - let registry = Registry::default().with(filter).with(fmt_layer); - tracing::subscriber::set_global_default(registry).unwrap(); +#[cfg(target_os="windows")] +async fn process_args(args: Args) -> Result { + if args.pipe { + tracing::info!("pipe mode {:?}", args.config_path); + let pipe = loop { + match ClientOptions::new().open(&args.config_path) { + Ok(client) => break client, + Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (), + Err(e) => return Err(Error::Io(e)), + } + + sleep(Duration::from_millis(50)).await; + }; + Ok(RkvmStream::Pipe(BufStream::with_capacity(1024, 1024, pipe))) + }else { + process_args_default(args).await } } @@ -69,40 +70,37 @@ async fn main() -> ExitCode { let args = Args::parse(); init_tracing(&args.log_level, &args.log_file); - let config = match fs::read_to_string(&args.config_path).await { - Ok(config) => config, - Err(err) => { - tracing::error!("Error reading config: {}", err); + let stream = match process_args(args).await { + Ok(stream) => stream, + Err(e) => { + tracing::error!("Failed to open stream {}", e); return ExitCode::FAILURE; } }; - let config = match toml::from_str::(&config) { - Ok(config) => config, - Err(err) => { - tracing::error!("Error parsing config: {}", err); - return ExitCode::FAILURE; - } + let writers = Rc::new(RefCell::new(HashMap::::new())); + let update = |update| async { + client::handler(&mut writers.borrow_mut(), update).await }; - let connector = match tls::configure(&config.certificate).await { - Ok(connector) => connector, - Err(err) => { - tracing::error!("Error configuring TLS: {}", err); - return ExitCode::FAILURE; + let (r,w) = split(stream); + tokio::select! { + result = client::run(r, w, update) => { + if let Err(err) = result { + tracing::error!("Error: {}", err); + return ExitCode::FAILURE; + } } - }; - - match config.reconnect_delay.map(Duration::from_secs) { - None => main_loop(&config, &connector).await, - Some(reconnect_delay) => { - loop { - let code = main_loop(&config, &connector).await; - if code == ExitCode::SUCCESS { - return code; - } - sleep(reconnect_delay).await; + // This is needed to properly clean libevdev stuff up. + result = signal::ctrl_c() => { + if let Err(err) = result { + tracing::error!("Error setting up signal handler: {}", err); + return ExitCode::FAILURE; } + + tracing::info!("Exiting on signal"); } } + + ExitCode::SUCCESS } diff --git a/rkvm-client/src/stream.rs b/rkvm-client/src/stream.rs new file mode 100644 index 0000000..b1a1478 --- /dev/null +++ b/rkvm-client/src/stream.rs @@ -0,0 +1,136 @@ +use crate::client::Error; + +use async_trait::async_trait; +use rkvm_net::{Update, message::Message}; +use std::pin::Pin; +use std::io; +use std::sync::Arc; +use std::task::{Context, Poll}; +use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufStream}; +use tokio::net::TcpStream; +use tokio::sync::{Mutex, mpsc::Sender}; +use tokio_rustls::client::TlsStream; + +#[cfg(target_os = "windows")] +use tokio::net::windows::named_pipe::NamedPipeClient; + +pub enum RkvmStream { + Tcp(BufStream>), + + #[cfg(target_os = "windows")] + Pipe(BufStream), +} + +impl AsyncRead for RkvmStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + + match self.get_mut() { + RkvmStream::Tcp(stream) => + Pin::new(stream).poll_read(cx, buf), + + #[cfg(target_os = "windows")] + RkvmStream::Pipe(stream) => + Pin::new(stream).poll_read(cx, buf), + } + } +} + +impl AsyncWrite for RkvmStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + + match self.get_mut() { + RkvmStream::Tcp(stream) => + Pin::new(stream).poll_write(cx, buf), + + #[cfg(target_os = "windows")] + RkvmStream::Pipe(stream) => + Pin::new(stream).poll_write(cx, buf), + } + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + + match self.get_mut() { + RkvmStream::Tcp(stream) => + Pin::new(stream).poll_flush(cx), + + #[cfg(target_os = "windows")] + RkvmStream::Pipe(stream) => + Pin::new(stream).poll_flush(cx), + } + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + + match self.get_mut() { + RkvmStream::Tcp(stream) => + Pin::new(stream).poll_shutdown(cx), + + #[cfg(target_os = "windows")] + RkvmStream::Pipe(stream) => + Pin::new(stream).poll_shutdown(cx), + } + } +} + +#[async_trait] +pub trait RkvmWriter { + async fn send(&mut self, update: Update) -> Result<(), Error>; +} + +#[async_trait] +impl RkvmWriter for W +where + W: AsyncWrite + Unpin + Send, +{ + async fn send(&mut self, update: Update) -> Result<(), Error> { + update.encode(self).await?; + self.flush().await?; + Ok(()) + } +} + +pub struct SenderWriter { + tx: Sender, +} + +#[async_trait] +impl RkvmWriter for SenderWriter { + async fn send(&mut self, update: Update) -> Result<(), Error> { + self.tx.send(update).await.map_err(|e| io::Error::other(e.to_string()))?; + Ok(()) + } +} + +pub struct LockWriter { + w: Arc> +} + +impl LockWriter { + pub fn new(w: Arc>) -> Self { + LockWriter { w: w } + } +} + +#[async_trait] +impl RkvmWriter for LockWriter +where + W: RkvmWriter + Send { + async fn send(&mut self, update: Update) -> Result<(), Error> { + self.w.lock().await.send(update).await + } +} diff --git a/rkvm-client/src/tls.rs b/rkvm-client/src/tls.rs index a32832a..c65eeb2 100644 --- a/rkvm-client/src/tls.rs +++ b/rkvm-client/src/tls.rs @@ -1,19 +1,10 @@ -use std::io; +use crate::client::Error; use std::path::Path; use std::sync::Arc; -use thiserror::Error; use tokio::fs; -use tokio_rustls::rustls::{self, Certificate, ClientConfig, RootCertStore}; +use tokio_rustls::rustls::{Certificate, ClientConfig, RootCertStore}; use tokio_rustls::TlsConnector; -#[derive(Error, Debug)] -pub enum Error { - #[error(transparent)] - Rustls(#[from] rustls::Error), - #[error(transparent)] - Io(#[from] io::Error), -} - pub async fn configure(certificate: &Path) -> Result { let certificate = fs::read(certificate).await?; let certificates = rustls_pemfile::certs(&mut certificate.as_slice())?; diff --git a/rkvm-client/src/windows-service.rs b/rkvm-client/src/windows-service.rs new file mode 100644 index 0000000..7c27157 --- /dev/null +++ b/rkvm-client/src/windows-service.rs @@ -0,0 +1,240 @@ +#![cfg(target_os="windows")] +mod client; +mod config; +mod stream; +mod tls; + +use crate::stream::{RkvmWriter, LockWriter}; + +use bincode; +use client::{init_tracing, init_config, Error}; +use rkvm_net::{Update, message::Message}; +use std::ffi::{OsString, c_void}; +use std::io; +use std::path::PathBuf; +use std::ptr::{addr_of_mut, null_mut}; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Duration; +use tokio::io::{AsyncWriteExt, BufStream, split}; +use tokio::sync::{Mutex, Notify}; +use tokio::sync::mpsc::{channel, Receiver}; +use tokio::net::windows::named_pipe::{ServerOptions, NamedPipeServer}; +use windows::Win32::Foundation::{CloseHandle, HANDLE}; +use windows::Win32::Security::{SECURITY_ATTRIBUTES, DuplicateTokenEx, SecurityImpersonation, TokenPrimary, TOKEN_ALL_ACCESS, PSECURITY_DESCRIPTOR}; +use windows::Win32::Security::Authorization::{ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1}; +use windows::Win32::System::Environment::CreateEnvironmentBlock; +use windows::Win32::System::RemoteDesktop::{WTSGetActiveConsoleSessionId, WTSQueryUserToken}; +use windows::Win32::System::Threading::{CreateProcessAsUserW, TerminateProcess, PROCESS_INFORMATION, STARTUPINFOW}; +use windows::core::{PWSTR,PCWSTR}; +use windows_service::define_windows_service; +use windows_service::service::{ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,ServiceType}; +use windows_service::service_control_handler::{self, ServiceControlHandlerResult}; +use windows_service::service_dispatcher; +use windows_sys::Win32::Foundation::LocalFree; + +const SERVICE_NAME: &str = "RkvmService"; +const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS; +const SERVICE_LOG: &str = r"C:\ProgramData\rkvm\service.log"; +const SERVICE_CFG: &str = r"C:\ProgramData\rkvm\client.toml"; +const SERVICE_PIPE: &str = r"\\.\pipe\rkvm"; +const CLIENT_PATH: &str = r"C:\ProgramData\rkvm\rkvm-client.exe"; +const CLIENT_LOG: &str = r"C:\ProgramData\rkvm\client.log"; + +define_windows_service!(ffi_service_main, service_main); + +fn main() -> windows_service::Result<()> { + service_dispatcher::start(SERVICE_NAME, ffi_service_main)?; + Ok(()) +} + +fn service_main(_arguments: Vec) { + init_tracing(&"info".to_string(), &Some(PathBuf::from(SERVICE_LOG))); + if let Err(e) = run_service() { + tracing::error!("Service error: {:?}", e); + } +} + +fn run_service() -> windows_service::Result<()> { + let stop_notify = Arc::new(Notify::new()); + let stop_clone = stop_notify.clone(); + let event_handler = move |control_event| -> ServiceControlHandlerResult { + match control_event { + ServiceControl::Stop => { + tracing::info!("Service stop requested"); + stop_clone.notify_waiters(); + ServiceControlHandlerResult::NoError + } + + ServiceControl::SessionChange(change) => { + tracing::info!("Session change: {:?}", change); + ServiceControlHandlerResult::NoError + } + + _ => ServiceControlHandlerResult::NotImplemented, + } + }; + + let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?; + + let status = ServiceStatus { + service_type: SERVICE_TYPE, + current_state: ServiceState::Running, + controls_accepted: ServiceControlAccept::STOP + | ServiceControlAccept::SESSION_CHANGE, + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + }; + status_handle.set_service_status(status)?; + + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap(); + match rt.block_on(process(stop_notify)) { + Ok(_) => { + tracing::info!("Service stopped normally"); + + status_handle.set_service_status(ServiceStatus { + service_type: SERVICE_TYPE, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::empty(), + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + })?; + } + Err(e) => { + tracing::error!(error = ?e, "Service crashed"); + + status_handle.set_service_status(ServiceStatus { + service_type: SERVICE_TYPE, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::empty(), + exit_code: ServiceExitCode::Win32(1), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + })?; + } + } + Ok(()) +} + +async fn channel_reader(mut rx: Receiver) { + while let Some(i) = rx.recv().await { + println!("got = {:?}", i); + } +} + +async fn process(stop_notify: Arc) -> Result<(), Error> { + tracing::info!("Service started"); + + let pipe = new_pipe()?; + let connect = pipe.connect(); + let rkvm_client = RkvmClient::new(); + connect.await?; + + let (pipe_r, pipe_w) = split(pipe); + + let config = init_config(SERVICE_CFG).await?; + let connector = tls::configure(&config.certificate).await?; + let stream = client::init_stream(&config.server.hostname, config.server.port, &connector, &config.password).await?; + let (stream_r, stream_w) = split(stream); + + let pw = Arc::new(Mutex::new(pipe_w)); + let mut pipe_w = LockWriter::new(pw.clone()); + let srv_update = |update| async { + tracing::debug!("Forward {:?}", update); + pw.lock().await.send(update).await + }; + + let client_update = |update| async move { + // handle missing communication + tracing::debug!("client respond {:?}", update); + Ok(()) + }; + + tokio::select! { + res = client::run(stream_r, stream_w, srv_update) => res, + res = client::run(pipe_r, pipe_w, client_update) => res, + _ = stop_notify.notified() => { + tracing::info!("Service requested stop"); + Ok(()) + } + }?; + Ok(()) +} + + +fn new_pipe() -> Result { + unsafe { + let sddl = to_wide("D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;AU)"); + + let mut security_descriptor: PSECURITY_DESCRIPTOR = PSECURITY_DESCRIPTOR(null_mut()); + + ConvertStringSecurityDescriptorToSecurityDescriptorW( + PCWSTR(sddl.as_ptr()), + SDDL_REVISION_1, + &mut security_descriptor, + None + )?; + tracing::info!("Created descriptor"); + + let mut sa = SECURITY_ATTRIBUTES { + nLength: std::mem::size_of::() as u32, + lpSecurityDescriptor: security_descriptor.0, + bInheritHandle: false.into(), + }; + tracing::info!("Created security attributes"); + + let pipe=ServerOptions::new().first_pipe_instance(true).write_dac(true).create_with_security_attributes_raw(SERVICE_PIPE, addr_of_mut!(sa) as *mut c_void)?; + tracing::info!("Created pipe"); + + Ok(pipe) + } +} + +fn to_wide(s: &str) -> Vec { + s.encode_utf16().chain(std::iter::once(0)).collect() +} + +struct RkvmClient { + handle: HANDLE, +} + +impl Drop for RkvmClient { + fn drop(&mut self) { + unsafe { + let _ = TerminateProcess(self.handle, 0); + let _ = CloseHandle(self.handle); + } + } +} + +impl RkvmClient { + pub fn new() -> Result { + unsafe { + let session_id = WTSGetActiveConsoleSessionId(); + + let mut user_token: HANDLE = HANDLE::default(); + WTSQueryUserToken(session_id, &mut user_token)?; + + let mut primary_token: HANDLE = HANDLE::default(); + DuplicateTokenEx(user_token, TOKEN_ALL_ACCESS, None, SecurityImpersonation, TokenPrimary, &mut primary_token)?; + + let mut si = STARTUPINFOW::default(); + si.cb = std::mem::size_of::() as u32; + + let mut pi = PROCESS_INFORMATION::default(); + + let mut cmd: Vec = to_wide(format!("\"{}\" --log-file {} --pipe {}", CLIENT_PATH, CLIENT_LOG, SERVICE_PIPE).as_str()); + CreateProcessAsUserW(Some(primary_token), PCWSTR::null(), Some(PWSTR(cmd.as_mut_ptr())), None, None, false, Default::default(), None, PCWSTR::null(), &si, &mut pi)?; + + CloseHandle(pi.hThread)?; + CloseHandle(primary_token)?; + CloseHandle(user_token)?; + Ok(RkvmClient{ handle: pi.hProcess }) + } + } +} diff --git a/rkvm-net/src/lib.rs b/rkvm-net/src/lib.rs index 9425e38..7aaea22 100644 --- a/rkvm-net/src/lib.rs +++ b/rkvm-net/src/lib.rs @@ -50,11 +50,9 @@ pub enum Update { event: Event, }, Ping, + Pong, } -#[derive(Deserialize, Serialize, Debug)] -pub struct Pong; - pub async fn timeout>, U>( duration: Duration, future: T, @@ -64,6 +62,7 @@ pub async fn timeout>, U>( .map_err(|_| Error::new(ErrorKind::TimedOut, "Message timeout"))? } + #[cfg(test)] mod test { use super::message::Message; @@ -72,7 +71,7 @@ mod test { #[tokio::test] async fn pong_is_not_empty() { let mut data = Vec::new(); - Pong.encode(&mut data).await.unwrap(); + Update::Pong.encode(&mut data).await.unwrap(); assert!(!data.is_empty()); } diff --git a/rkvm-net/src/version.rs b/rkvm-net/src/version.rs index 8cd0143..76e3898 100644 --- a/rkvm-net/src/version.rs +++ b/rkvm-net/src/version.rs @@ -8,7 +8,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; pub struct Version(u16); impl Version { - pub const CURRENT: Self = Self(5); + pub const CURRENT: Self = Self(6); } impl Display for Version { diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index 503369a..9e1fed4 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -8,7 +8,7 @@ use rkvm_input::sync::SyncEvent; use rkvm_net::auth::{AuthChallenge, AuthResponse, AuthStatus}; use rkvm_net::message::Message; use rkvm_net::version::Version; -use rkvm_net::{Pong, Update}; +use rkvm_net::Update; use slab::Slab; use std::collections::{HashMap, HashSet, VecDeque}; use std::ffi::CString; @@ -341,8 +341,7 @@ async fn client( }) .await?; - let response = - rkvm_net::timeout(rkvm_net::READ_TIMEOUT, AuthResponse::decode(&mut stream)).await?; + let response = rkvm_net::timeout(rkvm_net::READ_TIMEOUT, AuthResponse::decode(&mut stream)).await?; let status = match response.verify(&challenge, password) { true => AuthStatus::Passed, false => AuthStatus::Failed, @@ -387,6 +386,7 @@ async fn client( }; let start = Instant::now(); + interval.reset(); rkvm_net::timeout(rkvm_net::WRITE_TIMEOUT, async { update.encode(&mut stream).await?; stream.flush().await?; @@ -394,17 +394,11 @@ async fn client( Ok(()) }) .await?; - let duration = start.elapsed(); if let Update::Ping = update { + let duration = start.elapsed(); // Keeping these as debug because it's not as frequent as other updates. tracing::debug!(duration = ?duration, "Sent ping"); - - let start = Instant::now(); - rkvm_net::timeout(rkvm_net::READ_TIMEOUT, Pong::decode(&mut stream)).await?; - let duration = start.elapsed(); - - tracing::debug!(duration = ?duration, "Received pong"); } tracing::trace!("Wrote an update"); diff --git a/windows-service/all.bat b/windows-service/all.bat new file mode 100644 index 0000000..4ba0a7f --- /dev/null +++ b/windows-service/all.bat @@ -0,0 +1,19 @@ +@echo off + +set SERVICE_NAME=RkvmService +set BASE_PATH=C:\ProgramData\rkvm +set SERVICE_PATH=%BASE_PATH%\rkvm-service.exe + +cd /d "%~dp0\.." + +call windows-service\uninstall.bat + +echo Building.... +cargo build --release +if %errorlevel% neq 0 exit /b %errorlevel% + +call windows-service\install.bat + +timeout /t 5 + +call windows-service\uninstall.bat diff --git a/windows-service/install.bat b/windows-service/install.bat new file mode 100644 index 0000000..d6c93bd --- /dev/null +++ b/windows-service/install.bat @@ -0,0 +1,24 @@ +@echo off + +set SERVICE_NAME=RkvmService +set BASE_PATH=C:\ProgramData\rkvm +set SERVICE_PATH=%BASE_PATH%\rkvm-service.exe + +cd /d "%~dp0\.." + +echo Copying release +if not exist "%BASE_PATH%" mkdir "%BASE_PATH%" +del "%BASE_PATH%"\*.log "%BASE_PATH%"\*.exe +copy target\release\rkvm-service.exe "%BASE_PATH%" +copy target\release\rkvm-client.exe "%BASE_PATH%" + +echo Installing service... +sc create "%SERVICE_NAME%" binPath= "%SERVICE_PATH%" start= auto obj= LocalSystem +sc failure "%SERVICE_NAME%" reset= 86400 actions= restart/1000/restart/1500/restart/10000 +sc failureflag "%SERVICE_NAME%" 1 + +echo Starting service... +sc start "%SERVICE_NAME%" + +echo Done. + diff --git a/windows-service/uninstall.bat b/windows-service/uninstall.bat new file mode 100644 index 0000000..4de0818 --- /dev/null +++ b/windows-service/uninstall.bat @@ -0,0 +1,17 @@ +@echo off + +set SERVICE_NAME=RkvmService +set BASE_PATH=C:\ProgramData\rkvm +set SERVICE_PATH=%BASE_PATH%\rkvm-service.exe + +cd /d "%~dp0\.." + +echo Stopping service... +sc stop "%SERVICE_NAME%" +timeout /t 2 /nobreak > nul + +echo Deleting service... +sc delete "%SERVICE_NAME%" + +timeout /t 2 /nobreak > nul +taskkill /IM rkvm-service.exe /F /T \ No newline at end of file From b532c01c3d1a98168078ff96377b55fa0b42ab1f Mon Sep 17 00:00:00 2001 From: Unknow Date: Sun, 22 Feb 2026 21:37:27 +0100 Subject: [PATCH 7/8] update service --- Cargo.lock | 262 ++++++++++++++++++++++++- rkvm-client/Cargo.toml | 6 +- rkvm-client/src/client.rs | 29 ++- rkvm-client/src/main.rs | 2 +- rkvm-client/src/windows-service.rs | 41 ++-- rkvm-server/src/server.rs | 9 +- windows-service/all.bat | 19 -- windows-service/build-install-kill.bat | 7 + windows-service/build-install.bat | 13 ++ 9 files changed, 311 insertions(+), 77 deletions(-) delete mode 100644 windows-service/all.bat create mode 100644 windows-service/build-install-kill.bat create mode 100644 windows-service/build-install.bat diff --git a/Cargo.lock b/Cargo.lock index a8b88e2..860b3b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -269,6 +280,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -510,6 +530,12 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + [[package]] name = "lazy_static" version = "1.5.0" @@ -611,6 +637,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num_cpus" version = "1.16.0" @@ -621,6 +653,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.36.1" @@ -666,6 +707,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -684,9 +731,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -802,6 +849,8 @@ dependencies = [ name = "rkvm-client" version = "0.6.1" dependencies = [ + "async-trait", + "bincode", "clap", "env_logger", "rkvm-input", @@ -814,6 +863,10 @@ dependencies = [ "toml", "tracing", "tracing-subscriber", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-service", + "windows-sys 0.61.2", ] [[package]] @@ -831,7 +884,7 @@ dependencies = [ "thiserror", "tokio", "tracing", - "windows", + "windows 0.52.0", ] [[package]] @@ -937,18 +990,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.204" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1038,9 +1101,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.71" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1098,6 +1161,39 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "time-macros" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tokio" version = "1.38.0" @@ -1202,6 +1298,7 @@ dependencies = [ "sharded-slab", "smallvec", "thread_local", + "time", "tracing", "tracing-core", "tracing-log", @@ -1261,6 +1358,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -1298,10 +1401,31 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core", + "windows-core 0.52.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -1311,6 +1435,97 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-service" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac" +dependencies = [ + "bitflags 2.6.0", + "widestring", + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1329,6 +1544,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1360,6 +1593,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" diff --git a/rkvm-client/Cargo.toml b/rkvm-client/Cargo.toml index 09cb374..ab7a9be 100644 --- a/rkvm-client/Cargo.toml +++ b/rkvm-client/Cargo.toml @@ -5,9 +5,13 @@ version = "0.6.1" authors = ["Jan Trefil <8711792+htrefil@users.noreply.github.com>"] edition = "2021" +[features] +windows-service = [] + [[bin]] name = "rkvm-service" path = "src/windows-service.rs" +required-features = ["windows-service"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -24,7 +28,7 @@ thiserror = "1.0.40" tokio-rustls = "0.24.0" rustls-pemfile = "1.0.2" tracing = "0.1.37" -tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.17", features = ["env-filter", "local-time"] } [target.'cfg(windows)'.dependencies] bincode = "1.3.3" diff --git a/rkvm-client/src/client.rs b/rkvm-client/src/client.rs index dfb7612..aa44179 100644 --- a/rkvm-client/src/client.rs +++ b/rkvm-client/src/client.rs @@ -19,7 +19,8 @@ use tokio::net::TcpStream; use tokio::time; use tokio_rustls::rustls::{self, ServerName}; use tokio_rustls::TlsConnector; -use tracing_subscriber::{fmt,Registry,EnvFilter}; +use tracing_subscriber::{fmt, Registry,EnvFilter}; +use tracing_subscriber::fmt::time::LocalTime; use tracing_subscriber::prelude::*; #[cfg(target_os="windows")] use windows::core; @@ -47,7 +48,7 @@ pub fn init_tracing>(log_level: &String, log_file: &Option

) { let filter = EnvFilter::new(log_level); if let Some(path) = log_file { let file = OpenOptions::new().create(true).append(true).open(path).unwrap(); - let fmt_layer = fmt::layer().with_ansi(false).with_writer(move || BufWriter::new(file.try_clone().unwrap())).without_time(); + let fmt_layer = fmt::layer().with_ansi(false).with_timer(LocalTime::rfc_3339()).with_writer(move || BufWriter::new(file.try_clone().unwrap())); let registry = Registry::default().with(filter).with(fmt_layer); tracing::subscriber::set_global_default(registry).unwrap(); } else { @@ -141,28 +142,20 @@ pub async fn run(mut reader: R, mut writer: W, mut handler: H) -> Result< let mut start = Instant::now(); - let mut interval = time::interval(rkvm_net::PING_INTERVAL + rkvm_net::READ_TIMEOUT); - - // Interval ticks immediately after creation. - interval.tick().await; + let timeout_duration = rkvm_net::PING_INTERVAL + rkvm_net::READ_TIMEOUT; loop { - let update = tokio::select! { - update = Update::decode(&mut reader) => update.map_err(Error::Network)?, - _ = interval.tick() => return Err(Error::Network(io::Error::new(io::ErrorKind::TimedOut, "Ping timed out"))), - }; + let update = match time::timeout(timeout_duration, Update::decode(&mut reader)).await { + Err(_) => Err(Error::Network(io::Error::new(io::ErrorKind::TimedOut, "Ping timeout"))), + Ok(res) => res.map_err(Error::Network) + }?; - tracing::debug!("received {:?}", update); - interval.reset(); + let duration = start.elapsed(); + tracing::debug!(duration = ?duration, "received {:?}", update); + start = Instant::now(); if let Update::Ping = &update { - let duration = start.elapsed(); - tracing::debug!(duration = ?duration, "Received ping"); - - start = Instant::now(); - writer.send(Update::Pong).await?; - let duration = start.elapsed(); tracing::debug!(duration = ?duration, "Sent pong"); } diff --git a/rkvm-client/src/main.rs b/rkvm-client/src/main.rs index 65f10f0..88a6f73 100644 --- a/rkvm-client/src/main.rs +++ b/rkvm-client/src/main.rs @@ -43,7 +43,7 @@ async fn process_args_default(args: Args) -> Result { } #[cfg(not(target_os="windows"))] async fn process_args(args: Args) -> Result { - process_args_default(arg).await + process_args_default(args).await } #[cfg(target_os="windows")] diff --git a/rkvm-client/src/windows-service.rs b/rkvm-client/src/windows-service.rs index 7c27157..b2373c9 100644 --- a/rkvm-client/src/windows-service.rs +++ b/rkvm-client/src/windows-service.rs @@ -6,32 +6,28 @@ mod tls; use crate::stream::{RkvmWriter, LockWriter}; -use bincode; use client::{init_tracing, init_config, Error}; -use rkvm_net::{Update, message::Message}; +use rkvm_net::Update; use std::ffi::{OsString, c_void}; -use std::io; use std::path::PathBuf; use std::ptr::{addr_of_mut, null_mut}; -use std::rc::Rc; use std::sync::Arc; use std::time::Duration; -use tokio::io::{AsyncWriteExt, BufStream, split}; -use tokio::sync::{Mutex, Notify}; -use tokio::sync::mpsc::{channel, Receiver}; +use tokio::io::split; use tokio::net::windows::named_pipe::{ServerOptions, NamedPipeServer}; +use tokio::sync::{Mutex, Notify}; +use tokio::time; +use tracing::Instrument; use windows::Win32::Foundation::{CloseHandle, HANDLE}; use windows::Win32::Security::{SECURITY_ATTRIBUTES, DuplicateTokenEx, SecurityImpersonation, TokenPrimary, TOKEN_ALL_ACCESS, PSECURITY_DESCRIPTOR}; use windows::Win32::Security::Authorization::{ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1}; -use windows::Win32::System::Environment::CreateEnvironmentBlock; use windows::Win32::System::RemoteDesktop::{WTSGetActiveConsoleSessionId, WTSQueryUserToken}; -use windows::Win32::System::Threading::{CreateProcessAsUserW, TerminateProcess, PROCESS_INFORMATION, STARTUPINFOW}; +use windows::Win32::System::Threading::{CreateProcessAsUserW, TerminateProcess, PROCESS_INFORMATION, STARTUPINFOW, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT}; use windows::core::{PWSTR,PCWSTR}; use windows_service::define_windows_service; use windows_service::service::{ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,ServiceType}; use windows_service::service_control_handler::{self, ServiceControlHandlerResult}; use windows_service::service_dispatcher; -use windows_sys::Win32::Foundation::LocalFree; const SERVICE_NAME: &str = "RkvmService"; const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS; @@ -121,12 +117,6 @@ fn run_service() -> windows_service::Result<()> { Ok(()) } -async fn channel_reader(mut rx: Receiver) { - while let Some(i) = rx.recv().await { - println!("got = {:?}", i); - } -} - async fn process(stop_notify: Arc) -> Result<(), Error> { tracing::info!("Service started"); @@ -143,7 +133,7 @@ async fn process(stop_notify: Arc) -> Result<(), Error> { let (stream_r, stream_w) = split(stream); let pw = Arc::new(Mutex::new(pipe_w)); - let mut pipe_w = LockWriter::new(pw.clone()); + let pipe_w = LockWriter::new(pw.clone()); let srv_update = |update| async { tracing::debug!("Forward {:?}", update); pw.lock().await.send(update).await @@ -155,9 +145,20 @@ async fn process(stop_notify: Arc) -> Result<(), Error> { Ok(()) }; + let mut interval = time::interval(rkvm_net::PING_INTERVAL); + interval.tick().await; + + let span_server = tracing::info_span!("run", stream="server"); + let span_client = tracing::info_span!("run", stream="client"); tokio::select! { - res = client::run(stream_r, stream_w, srv_update) => res, - res = client::run(pipe_r, pipe_w, client_update) => res, + res = client::run(stream_r, stream_w, srv_update).instrument(span_server) => res, + res = client::run(pipe_r, pipe_w, client_update).instrument(span_client) => res, + res = async { + loop { + interval.tick().await; + pw.lock().await.send(Update::Ping).await? + } + } => res, _ = stop_notify.notified() => { tracing::info!("Service requested stop"); Ok(()) @@ -229,7 +230,7 @@ impl RkvmClient { let mut pi = PROCESS_INFORMATION::default(); let mut cmd: Vec = to_wide(format!("\"{}\" --log-file {} --pipe {}", CLIENT_PATH, CLIENT_LOG, SERVICE_PIPE).as_str()); - CreateProcessAsUserW(Some(primary_token), PCWSTR::null(), Some(PWSTR(cmd.as_mut_ptr())), None, None, false, Default::default(), None, PCWSTR::null(), &si, &mut pi)?; + CreateProcessAsUserW(Some(primary_token), PCWSTR::null(), Some(PWSTR(cmd.as_mut_ptr())), None, None, false, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, None, PCWSTR::null(), &si, &mut pi)?; CloseHandle(pi.hThread)?; CloseHandle(primary_token)?; diff --git a/rkvm-server/src/server.rs b/rkvm-server/src/server.rs index 9e1fed4..d7dcff6 100644 --- a/rkvm-server/src/server.rs +++ b/rkvm-server/src/server.rs @@ -386,7 +386,6 @@ async fn client( }; let start = Instant::now(); - interval.reset(); rkvm_net::timeout(rkvm_net::WRITE_TIMEOUT, async { update.encode(&mut stream).await?; stream.flush().await?; @@ -395,13 +394,7 @@ async fn client( }) .await?; - if let Update::Ping = update { - let duration = start.elapsed(); - // Keeping these as debug because it's not as frequent as other updates. - tracing::debug!(duration = ?duration, "Sent ping"); - } - - tracing::trace!("Wrote an update"); + tracing::trace!(duration = ?start.elapsed(), "Wrote an update"); } Ok(()) diff --git a/windows-service/all.bat b/windows-service/all.bat deleted file mode 100644 index 4ba0a7f..0000000 --- a/windows-service/all.bat +++ /dev/null @@ -1,19 +0,0 @@ -@echo off - -set SERVICE_NAME=RkvmService -set BASE_PATH=C:\ProgramData\rkvm -set SERVICE_PATH=%BASE_PATH%\rkvm-service.exe - -cd /d "%~dp0\.." - -call windows-service\uninstall.bat - -echo Building.... -cargo build --release -if %errorlevel% neq 0 exit /b %errorlevel% - -call windows-service\install.bat - -timeout /t 5 - -call windows-service\uninstall.bat diff --git a/windows-service/build-install-kill.bat b/windows-service/build-install-kill.bat new file mode 100644 index 0000000..a363152 --- /dev/null +++ b/windows-service/build-install-kill.bat @@ -0,0 +1,7 @@ +@echo off + +cd /d "%~dp0\.." + +call windows-service\build-install.bat + +call windows-service\uninstall.bat diff --git a/windows-service/build-install.bat b/windows-service/build-install.bat new file mode 100644 index 0000000..39601b1 --- /dev/null +++ b/windows-service/build-install.bat @@ -0,0 +1,13 @@ +@echo off + +cd /d "%~dp0\.." + +call windows-service\uninstall.bat + +echo Building.... +cargo build --release --features windows-service +if %errorlevel% neq 0 exit /b %errorlevel% + +call windows-service\install.bat + +pause From 6ff87be706195fd2fd4cef96849bf01a9a90a921 Mon Sep 17 00:00:00 2001 From: Unknow Date: Sun, 22 Feb 2026 22:58:36 +0100 Subject: [PATCH 8/8] run client with elevated token --- rkvm-client/src/windows-service.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rkvm-client/src/windows-service.rs b/rkvm-client/src/windows-service.rs index b2373c9..7d8673a 100644 --- a/rkvm-client/src/windows-service.rs +++ b/rkvm-client/src/windows-service.rs @@ -19,7 +19,7 @@ use tokio::sync::{Mutex, Notify}; use tokio::time; use tracing::Instrument; use windows::Win32::Foundation::{CloseHandle, HANDLE}; -use windows::Win32::Security::{SECURITY_ATTRIBUTES, DuplicateTokenEx, SecurityImpersonation, TokenPrimary, TOKEN_ALL_ACCESS, PSECURITY_DESCRIPTOR}; +use windows::Win32::Security::{SECURITY_ATTRIBUTES, DuplicateTokenEx, GetTokenInformation, SecurityImpersonation, TokenPrimary, TokenLinkedToken, TOKEN_ALL_ACCESS, TOKEN_LINKED_TOKEN, PSECURITY_DESCRIPTOR}; use windows::Win32::Security::Authorization::{ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1}; use windows::Win32::System::RemoteDesktop::{WTSGetActiveConsoleSessionId, WTSQueryUserToken}; use windows::Win32::System::Threading::{CreateProcessAsUserW, TerminateProcess, PROCESS_INFORMATION, STARTUPINFOW, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT}; @@ -221,6 +221,18 @@ impl RkvmClient { let mut user_token: HANDLE = HANDLE::default(); WTSQueryUserToken(session_id, &mut user_token)?; + + let mut needed = 0 as u32; + let _ = GetTokenInformation(user_token, TokenLinkedToken, None, 0, &mut needed); + let mut buffer = vec![0u8; needed as usize]; + match GetTokenInformation(user_token, TokenLinkedToken, Some(buffer.as_mut_ptr() as *mut _), needed, &mut needed) { + Ok(_) => { + let token_linked: &TOKEN_LINKED_TOKEN = &*(buffer.as_ptr() as *const TOKEN_LINKED_TOKEN); + user_token = token_linked.LinkedToken + }, + Err(e) => tracing::info!("Failed to get linked token {:?}", e) + }; + let mut primary_token: HANDLE = HANDLE::default(); DuplicateTokenEx(user_token, TOKEN_ALL_ACCESS, None, SecurityImpersonation, TokenPrimary, &mut primary_token)?;