113 lines
3.0 KiB
Rust
113 lines
3.0 KiB
Rust
/*
|
|
* This file is part of espanso.
|
|
*
|
|
* Copyright (C) 2019-2021 Federico Terzi
|
|
*
|
|
* espanso is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* espanso is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use std::ffi::CString;
|
|
|
|
use libc::{c_uint, close, ioctl, open, O_NONBLOCK, O_WRONLY};
|
|
use scopeguard::ScopeGuard;
|
|
|
|
use anyhow::Result;
|
|
use log::error;
|
|
use thiserror::Error;
|
|
|
|
use super::ffi::{
|
|
setup_uinput_device, ui_dev_create, ui_dev_destroy, ui_set_evbit, ui_set_keybit, uinput_emit,
|
|
EV_KEY,
|
|
};
|
|
|
|
pub struct UInputDevice {
|
|
fd: i32,
|
|
}
|
|
|
|
impl UInputDevice {
|
|
pub fn new() -> Result<UInputDevice> {
|
|
let uinput_path = CString::new("/dev/uinput").expect("unable to generate /dev/uinput path");
|
|
let raw_fd = unsafe { open(uinput_path.as_ptr(), O_WRONLY | O_NONBLOCK) };
|
|
if raw_fd < 0 {
|
|
error!("Error: could not open uinput device");
|
|
error!("This might be due to a recent kernel update, please restart your PC so that the uinput module can be loaded correctly.");
|
|
|
|
return Err(UInputDeviceError::Open().into());
|
|
}
|
|
let fd = scopeguard::guard(raw_fd, |raw_fd| unsafe {
|
|
close(raw_fd);
|
|
});
|
|
|
|
// Enable keyboard events
|
|
if unsafe { ioctl(*fd, ui_set_evbit(), EV_KEY as c_uint) } != 0 {
|
|
return Err(UInputDeviceError::KeyEVBit().into());
|
|
}
|
|
|
|
// Register all keycodes
|
|
for key_code in 0..256 {
|
|
if unsafe { ioctl(*fd, ui_set_keybit(), key_code) } != 0 {
|
|
return Err(UInputDeviceError::KeyBit().into());
|
|
}
|
|
}
|
|
|
|
// Register the virtual device
|
|
if unsafe { setup_uinput_device(*fd) } != 0 {
|
|
return Err(UInputDeviceError::DeviceSetup().into());
|
|
}
|
|
|
|
// Create the device
|
|
if unsafe { ioctl(*fd, ui_dev_create()) } != 0 {
|
|
return Err(UInputDeviceError::DeviceCreate().into());
|
|
}
|
|
|
|
Ok(Self {
|
|
fd: ScopeGuard::into_inner(fd),
|
|
})
|
|
}
|
|
|
|
pub fn emit(&self, key_code: u32, pressed: bool) {
|
|
let pressed = if pressed { 1 } else { 0 };
|
|
unsafe {
|
|
uinput_emit(self.fd, key_code, pressed);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for UInputDevice {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
ioctl(self.fd, ui_dev_destroy());
|
|
close(self.fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum UInputDeviceError {
|
|
#[error("could not open uinput device")]
|
|
Open(),
|
|
|
|
#[error("could not set keyboard evbit")]
|
|
KeyEVBit(),
|
|
|
|
#[error("could not set keyboard keybit")]
|
|
KeyBit(),
|
|
|
|
#[error("could not register virtual device")]
|
|
DeviceSetup(),
|
|
|
|
#[error("could not create uinput device")]
|
|
DeviceCreate(),
|
|
}
|