diff --git a/examples/blink_virtual_keyboard_leds.rs b/examples/blink_virtual_keyboard_leds.rs new file mode 100644 index 0000000..e1d1748 --- /dev/null +++ b/examples/blink_virtual_keyboard_leds.rs @@ -0,0 +1,44 @@ +// Create a virtual keyboard, just while this is running. +// Generally this requires root. + +use evdev::{ + uinput::VirtualDeviceBuilder, AttributeSet, EventType, InputEvent, KeyCode, LedCode, +}; +use std::thread::sleep; +use std::time::Duration; + +fn main() -> std::io::Result<()> { + let mut keys = AttributeSet::::new(); + keys.insert(KeyCode::KEY_CAPSLOCK); + keys.insert(KeyCode::KEY_SCROLLLOCK); + + let mut leds = AttributeSet::::new(); + leds.insert(LedCode::LED_CAPSL); + leds.insert(LedCode::LED_SCROLLL); + + let mut device = VirtualDeviceBuilder::new()? + .name("Fake Keyboard") + .with_keys(&keys)? + .with_leds(&leds)? + .build() + .unwrap(); + + for path in device.enumerate_dev_nodes_blocking()? { + let path = path?; + println!("Available as {}", path.display()); + } + + println!("Blinking the Virtual Keyboard LEDS..."); + for _ in 0..4 { + let capslock_down = InputEvent::new(EventType::KEY.0, KeyCode::KEY_CAPSLOCK.code(), 1); + let capslock_up = InputEvent::new(EventType::KEY.0, KeyCode::KEY_CAPSLOCK.code(), 0); + device.emit(&[capslock_down, capslock_up])?; + sleep(Duration::from_millis(300)); + println!( + "Capslock clicked, get_key_state: {:?}", + device.get_led_state() + ); + sleep(Duration::from_secs(2)); + } + Ok(()) +} diff --git a/examples/virtual_keyboard.rs b/examples/virtual_keyboard.rs index 4750eca..6183d96 100644 --- a/examples/virtual_keyboard.rs +++ b/examples/virtual_keyboard.rs @@ -30,14 +30,20 @@ fn main() -> std::io::Result<()> { // this guarantees a key event let down_event = *KeyEvent::new(KeyCode(code), 1); device.emit(&[down_event]).unwrap(); - println!("Pressed."); + println!( + "BTN_DPAD_UP pressed, get_key_state: {:?}", + device.get_key_state() + ); sleep(Duration::from_secs(2)); // alternativeley we can create a InputEvent, which will be any variant of InputEvent // depending on the type_ value let up_event = InputEvent::new(EventType::KEY.0, code, 0); device.emit(&[up_event]).unwrap(); - println!("Released."); + println!( + "BTN_DPAD_UP peleased, get_key_state: {:?}", + device.get_key_state() + ); sleep(Duration::from_secs(2)); } } diff --git a/src/uinput.rs b/src/uinput.rs index cc8c9dd..0802eb9 100644 --- a/src/uinput.rs +++ b/src/uinput.rs @@ -6,14 +6,18 @@ use crate::compat::{input_event, input_id, uinput_abs_setup, uinput_setup, UINPU use crate::ff::FFEffectData; use crate::inputid::{BusType, InputId}; use crate::{ - sys, AttributeSetRef, FFEffectCode, InputEvent, KeyCode, MiscCode, PropType, RelativeAxisCode, - SwitchCode, SynchronizationEvent, UInputCode, UInputEvent, UinputAbsSetup, + sys, AttributeSet, AttributeSetRef, FFEffectCode, InputEvent, KeyCode, LedCode, MiscCode, + PropType, RelativeAxisCode, SwitchCode, SynchronizationEvent, UInputCode, UInputEvent, + UinputAbsSetup, }; +use libc::O_NONBLOCK; use std::ffi::{CString, OsStr}; +use std::fs::{self, File, OpenOptions}; +use std::io; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; use std::os::unix::ffi::OsStrExt; +use std::os::unix::fs::OpenOptionsExt; use std::path::{Path, PathBuf}; -use std::{fs, io}; const UINPUT_PATH: &str = "/dev/uinput"; const SYSFS_PATH: &str = "/sys/devices/virtual/input"; @@ -198,6 +202,26 @@ impl<'a> VirtualDeviceBuilder<'a> { Ok(self) } + pub fn with_leds(self, keys: &AttributeSetRef) -> io::Result { + unsafe { + sys::ui_set_evbit( + self.fd.as_raw_fd(), + crate::EventType::LED.0 as nix::sys::ioctl::ioctl_param_type, + )?; + } + + for bit in keys.iter() { + unsafe { + sys::ui_set_ledbit( + self.fd.as_raw_fd(), + bit.0 as nix::sys::ioctl::ioctl_param_type, + )?; + } + } + + Ok(self) + } + pub fn build(self) -> io::Result { // Populate the uinput_setup struct @@ -230,6 +254,7 @@ const DEFAULT_ID: input_id = input_id { pub struct VirtualDevice { fd: OwnedFd, pub(crate) event_buf: Vec, + fd_event: OwnedFd, } impl VirtualDevice { @@ -238,25 +263,41 @@ impl VirtualDevice { unsafe { sys::ui_dev_setup(fd.as_raw_fd(), usetup)? }; unsafe { sys::ui_dev_create(fd.as_raw_fd())? }; + let file_event = Self::open_event_file(fd.as_raw_fd())?; + Ok(VirtualDevice { fd, event_buf: vec![], + fd_event: file_event.into(), }) } + fn open_event_file(fd: RawFd) -> io::Result { + let nodes_blocking = Self::enumerate_dev_nodes_blocking_(fd)?; + for entry in nodes_blocking { + if let Ok(entry) = entry { + return OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK) + .open(entry); + } + } + + Err(io::Error::new( + io::ErrorKind::NotFound, + format!("Failed to find event file for uinput virtual device."), + )) + } + #[inline] fn write_raw(&mut self, events: &[InputEvent]) -> io::Result<()> { crate::write_events(self.fd.as_fd(), events)?; Ok(()) } - /// Get the syspath representing this uinput device. - /// - /// The syspath returned is the one of the input node itself (e.g. - /// `/sys/devices/virtual/input/input123`), not the syspath of the device node. - pub fn get_syspath(&mut self) -> io::Result { + fn get_syspath_(fd: RawFd) -> io::Result { let mut syspath = vec![0u8; 256]; - let len = unsafe { sys::ui_get_sysname(self.fd.as_raw_fd(), &mut syspath)? }; + let len = unsafe { sys::ui_get_sysname(fd, &mut syspath)? }; syspath.truncate(len as usize - 1); let syspath = OsStr::from_bytes(&syspath); @@ -264,14 +305,26 @@ impl VirtualDevice { Ok(Path::new(SYSFS_PATH).join(syspath)) } - /// Get the syspaths of the corresponding device nodes in /dev/input. - pub fn enumerate_dev_nodes_blocking(&mut self) -> io::Result { - let path = self.get_syspath()?; + /// Get the syspath representing this uinput device. + /// + /// The syspath returned is the one of the input node itself (e.g. + /// `/sys/devices/virtual/input/input123`), not the syspath of the device node. + pub fn get_syspath(&mut self) -> io::Result { + Self::get_syspath_(self.fd.as_raw_fd()) + } + + fn enumerate_dev_nodes_blocking_(fd: RawFd) -> io::Result { + let path = Self::get_syspath_(fd)?; let dir = std::fs::read_dir(path)?; Ok(DevNodesBlocking { dir }) } + /// Get the syspaths of the corresponding device nodes in /dev/input. + pub fn enumerate_dev_nodes_blocking(&mut self) -> io::Result { + Self::enumerate_dev_nodes_blocking_(self.fd.as_raw_fd()) + } + /// Get the syspaths of the corresponding device nodes in /dev/input. #[cfg(feature = "tokio")] pub async fn enumerate_dev_nodes(&mut self) -> io::Result { @@ -379,6 +432,60 @@ impl VirtualDevice { pub fn into_event_stream(self) -> io::Result { VirtualEventStream::new(self) } + + /// Retrieve the current keypress state directly via kernel syscall. + #[inline] + pub fn get_key_state(&self) -> io::Result> { + let mut key_vals = AttributeSet::new(); + self.update_key_state(&mut key_vals)?; + Ok(key_vals) + } + + /// Fetch the current kernel key state directly into the provided buffer. + /// If you don't already have a buffer, you probably want + /// [`get_key_state`](Self::get_key_state) instead. + #[inline] + pub fn update_key_state(&self, key_vals: &mut AttributeSet) -> io::Result<()> { + unsafe { sys::eviocgkey(self.fd_event.as_raw_fd(), key_vals.as_mut_raw_slice())? }; + Ok(()) + } + + /// Retrieve the current switch state directly via kernel syscall. + #[inline] + pub fn get_switch_state(&self) -> io::Result> { + let mut switch_vals = AttributeSet::new(); + self.update_switch_state(&mut switch_vals)?; + Ok(switch_vals) + } + + /// Retrieve the current LED state directly via kernel syscall. + #[inline] + pub fn get_led_state(&self) -> io::Result> { + let mut led_vals = AttributeSet::new(); + self.update_led_state(&mut led_vals)?; + Ok(led_vals) + } + + /// Fetch the current kernel switch state directly into the provided buffer. + /// If you don't already have a buffer, you probably want + /// [`get_switch_state`](Self::get_switch_state) instead. + #[inline] + pub fn update_switch_state( + &self, + switch_vals: &mut AttributeSet, + ) -> io::Result<()> { + unsafe { sys::eviocgsw(self.fd_event.as_raw_fd(), switch_vals.as_mut_raw_slice())? }; + Ok(()) + } + + /// Fetch the current kernel LED state directly into the provided buffer. + /// If you don't already have a buffer, you probably want + /// [`get_led_state`](Self::get_led_state) instead. + #[inline] + pub fn update_led_state(&self, led_vals: &mut AttributeSet) -> io::Result<()> { + unsafe { sys::eviocgled(self.fd_event.as_raw_fd(), led_vals.as_mut_raw_slice())? }; + Ok(()) + } } /// This struct is returned from the [VirtualDevice::enumerate_dev_nodes_blocking] function and will yield @@ -617,5 +724,6 @@ mod tokio_stream { } } } + #[cfg(feature = "tokio")] pub use tokio_stream::VirtualEventStream;