Initial draft of wayland event source

This commit is contained in:
Federico Terzi 2021-02-04 22:12:30 +01:00
parent 1a21a81ace
commit 1d6b152c15
13 changed files with 975 additions and 4 deletions

14
Cargo.lock generated
View File

@ -142,10 +142,14 @@ dependencies = [
name = "espanso-detect" name = "espanso-detect"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"cc", "cc",
"enum-as-inner", "enum-as-inner",
"lazycell", "lazycell",
"libc",
"log", "log",
"scopeguard",
"thiserror",
"widestring", "widestring",
] ]
@ -204,9 +208,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.84" version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
[[package]] [[package]]
name = "libdbus-sys" name = "libdbus-sys"
@ -377,6 +381,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.123" version = "1.0.123"

View File

@ -12,6 +12,12 @@ lazycell = "1.3.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
widestring = "0.4.3" widestring = "0.4.3"
[target.'cfg(target_os="linux")'.dependencies]
libc = "0.2.85"
anyhow = "1.0.38"
thiserror = "1.0.23"
scopeguard = "1.1.0"
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.66"

View File

@ -37,15 +37,24 @@ fn cc_config() {
fn cc_config() { fn cc_config() {
println!("cargo:rerun-if-changed=src/x11/native.cpp"); println!("cargo:rerun-if-changed=src/x11/native.cpp");
println!("cargo:rerun-if-changed=src/x11/native.h"); println!("cargo:rerun-if-changed=src/x11/native.h");
println!("cargo:rerun-if-changed=src/evdev/native.cpp");
println!("cargo:rerun-if-changed=src/evdev/native.h");
cc::Build::new() cc::Build::new()
.cpp(true) .cpp(true)
.include("src/x11/native.h") .include("src/x11/native.h")
.file("src/x11/native.cpp") .file("src/x11/native.cpp")
.compile("espansodetect"); .compile("espansodetect");
cc::Build::new()
.cpp(true)
.include("src/evdev/native.h")
.file("src/evdev/native.cpp")
.compile("espansodetectevdev");
println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/"); println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/");
println!("cargo:rustc-link-lib=static=espansodetect"); println!("cargo:rustc-link-lib=static=espansodetect");
println!("cargo:rustc-link-lib=static=espansodetectevdev");
println!("cargo:rustc-link-lib=dylib=X11"); println!("cargo:rustc-link-lib=dylib=X11");
println!("cargo:rustc-link-lib=dylib=Xtst"); println!("cargo:rustc-link-lib=dylib=Xtst");
println!("cargo:rustc-link-lib=dylib=xkbcommon");
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View File

@ -0,0 +1,49 @@
// This code is a port of the libxkbcommon "interactive-evdev.c" example
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
use scopeguard::ScopeGuard;
use super::ffi::{XKB_CONTEXT_NO_FLAGS, xkb_context, xkb_context_new, xkb_context_unref};
use thiserror::Error;
use anyhow::Result;
pub struct Context {
context: *mut xkb_context,
}
impl Context {
pub fn new() -> Result<Context> {
let raw_context = unsafe { xkb_context_new(XKB_CONTEXT_NO_FLAGS) };
let context = scopeguard::guard(raw_context, |raw_context| {
unsafe {
xkb_context_unref(raw_context);
}
});
if raw_context.is_null() {
return Err(ContextError::FailedCreation().into());
}
Ok(Self {
context: ScopeGuard::into_inner(context),
})
}
pub fn get_handle(&self) -> *mut xkb_context {
self.context
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
xkb_context_unref(self.context);
}
}
}
#[derive(Error, Debug)]
pub enum ContextError {
#[error("could not create xkb context")]
FailedCreation(),
}

View File

@ -0,0 +1,248 @@
// This code is a port of the libxkbcommon "interactive-evdev.c" example
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
use anyhow::Result;
use libc::{input_event, size_t, ssize_t, EWOULDBLOCK, O_CLOEXEC, O_NONBLOCK, O_RDONLY};
use log::{trace};
use scopeguard::ScopeGuard;
use std::os::unix::io::AsRawFd;
use std::{
ffi::{c_void, CStr},
fs::OpenOptions,
mem::zeroed,
};
use std::{fs::File, os::unix::fs::OpenOptionsExt};
use thiserror::Error;
use super::{
ffi::{
is_keyboard, xkb_key_direction, xkb_keycode_t, xkb_keymap_key_repeats,
xkb_state, xkb_state_get_keymap, xkb_state_key_get_one_sym,
xkb_state_key_get_utf8, xkb_state_new, xkb_state_unref,
xkb_state_update_key, EV_KEY,
},
keymap::Keymap,
};
const EVDEV_OFFSET: i32 = 8;
const KEY_STATE_RELEASE: i32 = 0;
const KEY_STATE_PRESS: i32 = 1;
const KEY_STATE_REPEAT: i32 = 2;
#[derive(Debug)]
pub enum RawInputEvent {
Keyboard(KeyboardEvent),
Mouse(MouseEvent),
}
#[derive(Debug)]
pub struct KeyboardEvent {
pub sym: u32,
pub value: String,
pub is_down: bool,
}
#[derive(Debug)]
pub struct MouseEvent {
pub code: u16,
pub is_down: bool,
}
pub struct Device {
path: String,
file: File,
state: *mut xkb_state,
}
impl Device {
pub fn from(path: &str, keymap: &Keymap) -> Result<Device> {
let file = OpenOptions::new()
.read(true)
.custom_flags(O_NONBLOCK | O_CLOEXEC | O_RDONLY)
.open(&path)?;
if unsafe { is_keyboard(file.as_raw_fd()) == 0 } {
return Err(DeviceError::InvalidDevice(path.to_string()).into());
}
let raw_state = unsafe { xkb_state_new(keymap.get_handle()) };
// Automatically close the state if the function does not return correctly
let state = scopeguard::guard(raw_state, |raw_state| {
unsafe {
xkb_state_unref(raw_state);
}
});
if raw_state.is_null() {
return Err(DeviceError::InvalidState(path.to_string()).into());
}
Ok(Self {
path: path.to_string(),
file,
// Release the state without freeing it
state: ScopeGuard::into_inner(state),
})
}
pub fn get_state(&self) -> *mut xkb_state {
self.state
}
pub fn get_raw_fd(&self) -> i32 {
self.file.as_raw_fd()
}
pub fn get_path(&self) -> String {
self.path.to_string()
}
pub fn read(&self) -> Result<Vec<RawInputEvent>> {
let errno_ptr = unsafe { libc::__errno_location() };
let mut len: ssize_t;
let mut evs: [input_event; 16] = unsafe { std::mem::zeroed() };
let mut events = Vec::new();
loop {
len = unsafe {
libc::read(
self.file.as_raw_fd(),
evs.as_mut_ptr() as *mut c_void,
std::mem::size_of_val(&evs),
)
};
if len <= 0 {
break;
}
let nevs: size_t = len as usize / std::mem::size_of::<input_event>();
#[allow(clippy::needless_range_loop)]
for i in 0..nevs {
let event = self.process_event(evs[i].type_, evs[i].code, evs[i].value);
if let Some(event) = event {
events.push(event);
}
}
}
if len < 0 && unsafe { *errno_ptr } != EWOULDBLOCK {
return Err(DeviceError::BlockingReadOperation().into());
}
Ok(events)
}
fn process_event(&self, _type: u16, code: u16, value: i32) -> Option<RawInputEvent> {
if _type != EV_KEY {
return None;
}
let is_down = value == KEY_STATE_PRESS;
// Check if the current event originated from a mouse
if code >= 0x110 && code <= 0x117 {
// Mouse event
return Some(RawInputEvent::Mouse(MouseEvent { code, is_down }));
}
// Keyboard event
let keycode: xkb_keycode_t = EVDEV_OFFSET as u32 + code as u32;
let keymap = unsafe { xkb_state_get_keymap(self.get_state()) };
if value == KEY_STATE_REPEAT && unsafe { xkb_keymap_key_repeats(keymap, keycode) } != 0 {
return None;
}
let sym = unsafe { xkb_state_key_get_one_sym(self.get_state(), keycode) };
if sym == 0 {
return None;
}
// Extract the utf8 char
let mut buffer: [u8; 16] = [0; 16];
unsafe {
xkb_state_key_get_utf8(
self.get_state(),
keycode,
buffer.as_mut_ptr() as *mut i8,
std::mem::size_of_val(&buffer),
)
};
let content_raw = unsafe { CStr::from_ptr(buffer.as_ptr() as *mut i8) };
let content = content_raw.to_string_lossy().to_string();
let event = KeyboardEvent {
is_down,
sym,
value: content,
};
if value == KEY_STATE_RELEASE {
unsafe { xkb_state_update_key(self.get_state(), keycode, xkb_key_direction::UP) };
} else {
unsafe { xkb_state_update_key(self.get_state(), keycode, xkb_key_direction::DOWN) };
}
Some(RawInputEvent::Keyboard(event))
}
}
impl Drop for Device {
fn drop(&mut self) {
unsafe {
xkb_state_unref(self.state);
}
}
}
pub fn get_devices(keymap: &Keymap) -> Result<Vec<Device>> {
let mut keyboards = Vec::new();
let dirs = std::fs::read_dir("/dev/input/")?;
for entry in dirs {
match entry {
Ok(device) => {
// Skip non-eventX devices
if !device.file_name().to_string_lossy().starts_with("event") {
continue;
}
let path = device.path().to_string_lossy().to_string();
let keyboard = Device::from(&path, keymap);
match keyboard {
Ok(keyboard) => {
keyboards.push(keyboard);
}
Err(error) => {
trace!("error opening keyboard: {}", error);
}
}
}
Err(error) => {
trace!("could not read keyboard device: {}", error);
}
}
}
if keyboards.is_empty() {
return Err(DeviceError::NoDevicesFound().into());
}
Ok(keyboards)
}
#[derive(Error, Debug)]
pub enum DeviceError {
#[error("could not create xkb state for `{0}`")]
InvalidState(String),
#[error("`{0}` is not a valid device")]
InvalidDevice(String),
#[error("no devices found")]
NoDevicesFound(),
#[error("read operation can't block device")]
BlockingReadOperation(),
}

View File

@ -0,0 +1,77 @@
// Bindings taken from: https://github.com/rtbo/xkbcommon-rs/blob/master/src/xkb/ffi.rs
use std::os::raw::c_int;
use libc::c_char;
#[allow(non_camel_case_types)]
pub enum xkb_context {}
#[allow(non_camel_case_types)]
pub enum xkb_state {}
#[allow(non_camel_case_types)]
pub enum xkb_keymap {}
#[allow(non_camel_case_types)]
pub type xkb_keycode_t = u32;
#[allow(non_camel_case_types)]
pub type xkb_keysym_t = u32;
#[repr(C)]
pub struct xkb_rule_names {
pub rules: *const c_char,
pub model: *const c_char,
pub layout: *const c_char,
pub variant: *const c_char,
pub options: *const c_char,
}
#[repr(C)]
pub enum xkb_key_direction {
UP,
DOWN,
}
#[allow(non_camel_case_types)]
pub type xkb_keymap_compile_flags = u32;
pub const XKB_KEYMAP_COMPILE_NO_FLAGS: u32 = 0;
#[allow(non_camel_case_types)]
pub type xkb_context_flags = u32;
pub const XKB_CONTEXT_NO_FLAGS: u32 = 0;
#[allow(non_camel_case_types)]
pub type xkb_state_component = u32;
pub const EV_KEY: u16 = 0x01;
#[link(name = "xkbcommon")]
extern "C" {
pub fn xkb_state_unref(state: *mut xkb_state);
pub fn xkb_state_new(keymap: *mut xkb_keymap) -> *mut xkb_state;
pub fn xkb_keymap_new_from_names(
context: *mut xkb_context,
names: *const xkb_rule_names,
flags: xkb_keymap_compile_flags,
) -> *mut xkb_keymap;
pub fn xkb_keymap_unref(keymap: *mut xkb_keymap);
pub fn xkb_context_new(flags: xkb_context_flags) -> *mut xkb_context;
pub fn xkb_context_unref(context: *mut xkb_context);
pub fn xkb_state_get_keymap(state: *mut xkb_state) -> *mut xkb_keymap;
pub fn xkb_keymap_key_repeats(keymap: *mut xkb_keymap, key: xkb_keycode_t) -> c_int;
pub fn xkb_state_update_key(
state: *mut xkb_state,
key: xkb_keycode_t,
direction: xkb_key_direction,
) -> xkb_state_component;
pub fn xkb_state_key_get_utf8(
state: *mut xkb_state,
key: xkb_keycode_t,
buffer: *mut c_char,
size: usize,
) -> c_int;
pub fn xkb_state_key_get_one_sym(state: *mut xkb_state, key: xkb_keycode_t) -> xkb_keysym_t;
}
#[link(name = "espansodetectevdev", kind = "static")]
extern "C" {
pub fn is_keyboard(fd: i32) -> i32;
}

View File

@ -0,0 +1,50 @@
// This code is a port of the libxkbcommon "interactive-evdev.c" example
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
use scopeguard::ScopeGuard;
use thiserror::Error;
use anyhow::Result;
use super::{context::Context, ffi::{XKB_KEYMAP_COMPILE_NO_FLAGS, xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref}};
pub struct Keymap {
keymap: *mut xkb_keymap,
}
impl Keymap {
pub fn new(context: &Context) -> Result<Keymap> {
let raw_keymap = unsafe { xkb_keymap_new_from_names(context.get_handle(), std::ptr::null(), XKB_KEYMAP_COMPILE_NO_FLAGS) };
let keymap = scopeguard::guard(raw_keymap, |raw_keymap| {
unsafe {
xkb_keymap_unref(raw_keymap);
}
});
if raw_keymap.is_null() {
return Err(KeymapError::FailedCreation().into());
}
Ok(Self {
keymap: ScopeGuard::into_inner(keymap),
})
}
pub fn get_handle(&self) -> *mut xkb_keymap {
self.keymap
}
}
impl Drop for Keymap {
fn drop(&mut self) {
unsafe {
xkb_keymap_unref(self.keymap);
}
}
}
#[derive(Error, Debug)]
pub enum KeymapError {
#[error("could not create xkb keymap")]
FailedCreation(),
}

View File

@ -0,0 +1,333 @@
// This code is a port of the libxkbcommon "interactive-evdev.c" example
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
/*
* 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/>.
*/
mod context;
mod device;
mod ffi;
mod keymap;
use context::Context;
use device::{get_devices, Device};
use keymap::Keymap;
use libc::{
__errno_location, close, epoll_ctl, epoll_event, epoll_wait, EINTR, EPOLLIN, EPOLL_CTL_ADD,
};
use log::{error, trace};
use crate::event::Status::*;
use crate::event::Variant::*;
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
use crate::event::{Key::*, MouseButton, MouseEvent};
use self::device::{DeviceError, RawInputEvent};
const BTN_LEFT: u16 = 0x110;
const BTN_RIGHT: u16 = 0x111;
const BTN_MIDDLE: u16 = 0x112;
const BTN_SIDE: u16 = 0x113;
const BTN_EXTRA: u16 = 0x114;
pub type EVDEVSourceCallback = Box<dyn Fn(InputEvent)>;
pub struct EVDEVSource {
devices: Vec<Device>,
}
#[allow(clippy::new_without_default)]
impl EVDEVSource {
pub fn new() -> EVDEVSource {
Self {
devices: Vec::new(),
}
}
pub fn initialize(&mut self) {
let context = Context::new().expect("unable to obtain xkb context");
let keymap = Keymap::new(&context).expect("unable to create xkb keymap");
match get_devices(&keymap) {
Ok(devices) => self.devices = devices,
Err(error) => {
if let Some(device_error) = error.downcast_ref::<DeviceError>() {
if matches!(device_error, DeviceError::NoDevicesFound()) {
error!("Unable to open EVDEV devices, this usually has to do with permissions.");
error!(
"You can either add the current user to the 'input' group or run espanso as root"
);
}
}
panic!("error when initilizing EVDEV source {}", error);
}
}
}
pub fn eventloop(&self, event_callback: EVDEVSourceCallback) {
if self.devices.is_empty() {
panic!("can't start eventloop without evdev devices");
}
let raw_epfd = unsafe { libc::epoll_create1(0) };
let epfd = scopeguard::guard(raw_epfd, |raw_epfd| unsafe {
close(raw_epfd);
});
if *epfd < 0 {
panic!("could not create epoll instance");
}
// Setup epoll for all input devices
let errno_ptr = unsafe { __errno_location() };
for (i, device) in self.devices.iter().enumerate() {
let mut ev: epoll_event = unsafe { std::mem::zeroed() };
ev.events = EPOLLIN as u32;
ev.u64 = i as u64;
if unsafe { epoll_ctl(*epfd, EPOLL_CTL_ADD, device.get_raw_fd(), &mut ev) } != 0 {
panic!(format!(
"Could not add {} to epoll, errno {}",
device.get_path(),
unsafe { *errno_ptr }
));
}
}
// Read events indefinitely
let mut evs: [epoll_event; 16] = unsafe { std::mem::zeroed() };
loop {
let ret = unsafe { epoll_wait(*epfd, evs.as_mut_ptr(), 16, -1) };
if ret < 0 {
if unsafe { *errno_ptr } == EINTR {
continue;
} else {
panic!(format!("Could not poll for events, {}", unsafe {
*errno_ptr
}))
}
}
for ev in evs.iter() {
let device = &self.devices[ev.u64 as usize];
match device.read() {
Ok(events) if !events.is_empty() => {
// Convert raw events to the common format and invoke the callback
events.into_iter().for_each(|raw_event| {
let event: Option<InputEvent> = raw_event.into();
if let Some(event) = event {
event_callback(event);
} else {
trace!("unable to convert raw event to input event");
}
});
}
Ok(_) => { /* SKIP EMPTY */ }
Err(err) => error!("Can't read from device {}: {}", device.get_path(), err),
}
}
}
}
}
impl From<RawInputEvent> for Option<InputEvent> {
fn from(raw: RawInputEvent) -> Option<InputEvent> {
match raw {
RawInputEvent::Keyboard(keyboard_event) => {
let (key, variant) = key_sym_to_key(keyboard_event.sym as i32);
let value = if keyboard_event.value.is_empty() {
None
} else {
Some(keyboard_event.value)
};
let status = if keyboard_event.is_down { Pressed } else { Released };
return Some(InputEvent::Keyboard(KeyboardEvent {
key,
value,
status,
variant,
}));
}
RawInputEvent::Mouse(mouse_event) => {
let button = raw_to_mouse_button(mouse_event.code);
let status = if mouse_event.is_down { Pressed } else { Released };
if let Some(button) = button {
return Some(InputEvent::Mouse(MouseEvent { button, status }));
}
}
}
None
}
}
// Mappings from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
fn key_sym_to_key(key_sym: i32) -> (Key, Option<Variant>) {
match key_sym {
// Modifiers
0xFFE9 => (Alt, Some(Left)),
0xFFEA => (Alt, Some(Right)),
0xFFE5 => (CapsLock, None),
0xFFE3 => (Control, Some(Left)),
0xFFE4 => (Control, Some(Right)),
0xFFE7 | 0xFFEB => (Meta, Some(Left)),
0xFFE8 | 0xFFEC => (Meta, Some(Right)),
0xFF7F => (NumLock, None),
0xFFE1 => (Shift, Some(Left)),
0xFFE2 => (Shift, Some(Right)),
// Whitespace
0xFF0D => (Enter, None),
0xFF09 => (Tab, None),
0x20 => (Space, None),
// Navigation
0xFF54 => (ArrowDown, None),
0xFF51 => (ArrowLeft, None),
0xFF53 => (ArrowRight, None),
0xFF52 => (ArrowUp, None),
0xFF57 => (End, None),
0xFF50 => (Home, None),
0xFF56 => (PageDown, None),
0xFF55 => (PageUp, None),
// Editing keys
0xFF08 => (Backspace, None),
// Function keys
0xFFBE => (F1, None),
0xFFBF => (F2, None),
0xFFC0 => (F3, None),
0xFFC1 => (F4, None),
0xFFC2 => (F5, None),
0xFFC3 => (F6, None),
0xFFC4 => (F7, None),
0xFFC5 => (F8, None),
0xFFC6 => (F9, None),
0xFFC7 => (F10, None),
0xFFC8 => (F11, None),
0xFFC9 => (F12, None),
0xFFCA => (F13, None),
0xFFCB => (F14, None),
0xFFCC => (F15, None),
0xFFCD => (F16, None),
0xFFCE => (F17, None),
0xFFCF => (F18, None),
0xFFD0 => (F19, None),
0xFFD1 => (F20, None),
// Other keys, includes the raw code provided by the operating system
_ => (Other(key_sym), None),
}
}
// These codes can be found in the "input-event-codes.h" header file
fn raw_to_mouse_button(raw: u16) -> Option<MouseButton> {
match raw {
BTN_LEFT => Some(MouseButton::Left),
BTN_RIGHT=> Some(MouseButton::Right),
BTN_MIDDLE=> Some(MouseButton::Middle),
BTN_SIDE=> Some(MouseButton::Button1),
BTN_EXTRA=> Some(MouseButton::Button2),
_ => None,
}
}
/* TODO convert tests
#[cfg(test)]
mod tests {
use std::ffi::CString;
use super::*;
fn default_raw_input_event() -> RawInputEvent {
RawInputEvent {
event_type: INPUT_EVENT_TYPE_KEYBOARD,
buffer: [0; 24],
buffer_len: 0,
key_code: 0,
key_sym: 0,
status: INPUT_STATUS_PRESSED,
}
}
#[test]
fn raw_to_input_event_keyboard_works_correctly() {
let c_string = CString::new("k".to_string()).unwrap();
let mut buffer: [u8; 24] = [0; 24];
buffer[..1].copy_from_slice(c_string.as_bytes());
let mut raw = default_raw_input_event();
raw.buffer = buffer;
raw.buffer_len = 1;
raw.status = INPUT_STATUS_RELEASED;
raw.key_sym = 0x4B;
let result: Option<InputEvent> = raw.into();
assert_eq!(
result.unwrap(),
InputEvent::Keyboard(KeyboardEvent {
key: Other(0x4B),
status: Released,
value: Some("k".to_string()),
variant: None,
})
);
}
#[test]
fn raw_to_input_event_mouse_works_correctly() {
let mut raw = default_raw_input_event();
raw.event_type = INPUT_EVENT_TYPE_MOUSE;
raw.status = INPUT_STATUS_RELEASED;
raw.key_code = INPUT_MOUSE_RIGHT_BUTTON;
let result: Option<InputEvent> = raw.into();
assert_eq!(
result.unwrap(),
InputEvent::Mouse(MouseEvent {
status: Released,
button: MouseButton::Right,
})
);
}
#[test]
fn raw_to_input_invalid_buffer() {
let buffer: [u8; 24] = [123; 24];
let mut raw = default_raw_input_event();
raw.buffer = buffer;
raw.buffer_len = 5;
let result: Option<InputEvent> = raw.into();
assert!(result.unwrap().into_keyboard().unwrap().value.is_none());
}
#[test]
fn raw_to_input_event_returns_none_when_missing_type() {
let mut raw = default_raw_input_event();
raw.event_type = 0;
let result: Option<InputEvent> = raw.into();
assert!(result.is_none());
}
}
*/

View File

@ -0,0 +1,84 @@
// A good portion of the following code has been taken by the "interactive-evdev.c"
// example of "libxkbcommon" by Ran Benita. The original license is included as follows:
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
/*
* Copyright © 2012 Ran Benita <ran234@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "native.h"
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <limits.h>
#include <locale.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <string>
#include <sys/epoll.h>
#include <linux/input.h>
#include "xkbcommon/xkbcommon.h"
#define NLONGS(n) (((n) + LONG_BIT - 1) / LONG_BIT)
static bool
evdev_bit_is_set(const unsigned long *array, int bit)
{
return array[bit / LONG_BIT] & (1LL << (bit % LONG_BIT));
}
/* Some heuristics to see if the device is a keyboard. */
int32_t is_keyboard(int fd)
{
int i;
unsigned long evbits[NLONGS(EV_CNT)] = {0};
unsigned long keybits[NLONGS(KEY_CNT)] = {0};
errno = 0;
ioctl(fd, EVIOCGBIT(0, sizeof(evbits)), evbits);
if (errno)
return false;
if (!evdev_bit_is_set(evbits, EV_KEY))
return false;
errno = 0;
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits);
if (errno)
return false;
for (i = KEY_RESERVED; i <= KEY_MIN_INTERESTING; i++)
if (evdev_bit_is_set(keybits, i))
return true;
return false;
}

View File

@ -0,0 +1,27 @@
/*
* 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/>.
*/
#ifndef ESPANSO_DETECT_EVDEV_H
#define ESPANSO_DETECT_EVDEV_H
#include <stdint.h>
extern "C" int32_t is_keyboard(int fd);
#endif //ESPANSO_DETECT_EVDEV_H

View File

@ -24,3 +24,6 @@ pub mod win32;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub mod x11; pub mod x11;
#[cfg(target_os = "linux")]
pub mod evdev;

View File

@ -38,7 +38,7 @@ fn main() {
let handle = std::thread::spawn(move || { let handle = std::thread::spawn(move || {
//let mut source = espanso_detect::win32::Win32Source::new(); //let mut source = espanso_detect::win32::Win32Source::new();
let mut source = espanso_detect::x11::X11Source::new(); let mut source = espanso_detect::evdev::EVDEVSource::new();
source.initialize(); source.initialize();
source.eventloop(Box::new(move |event: InputEvent| { source.eventloop(Box::new(move |event: InputEvent| {
println!("ev {:?}", event); println!("ev {:?}", event);

75
espanso/src/main_old.rs Normal file
View File

@ -0,0 +1,75 @@
use espanso_detect::event::{InputEvent, Status};
use espanso_ui::{linux::LinuxUIOptions, menu::*};
use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode};
fn main() {
println!("Hello, world!z");
CombinedLogger::init(vec![
TermLogger::new(LevelFilter::Debug, Config::default(), TerminalMode::Mixed),
// WriteLogger::new(
// LevelFilter::Info,
// Config::default(),
// File::create("my_rust_binary.log").unwrap(),
// ),
])
.unwrap();
let icon_paths = vec![
(
espanso_ui::icons::TrayIcon::Normal,
r"C:\Users\Freddy\AppData\Local\espanso\espanso.ico".to_string(),
),
(
espanso_ui::icons::TrayIcon::Disabled,
r"C:\Users\Freddy\AppData\Local\espanso\espansored.ico".to_string(),
),
];
// let (remote, mut eventloop) = espanso_ui::win32::create(espanso_ui::win32::Win32UIOptions {
// show_icon: true,
// icon_paths: &icon_paths,
// notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png"
// .to_string(),
// });
let (remote, mut eventloop) = espanso_ui::linux::create(LinuxUIOptions {
notification_icon_path: r"/home/freddy/insync/Development/Espanso/Images/icongreensmall.png".to_owned(),
});
let handle = std::thread::spawn(move || {
//let mut source = espanso_detect::win32::Win32Source::new();
let mut source = espanso_detect::x11::X11Source::new();
source.initialize();
source.eventloop(Box::new(move |event: InputEvent| {
println!("ev {:?}", event);
match event {
InputEvent::Mouse(_) => {}
InputEvent::Keyboard(evt) => {
if evt.key == espanso_detect::event::Key::Shift && evt.status == Status::Pressed {
//remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled);
remote.show_notification("Espanso is running!");
}
}
}
}));
});
// eventloop.initialize();
// eventloop.run(Box::new(move |event| {
// println!("ui {:?}", event);
// let menu = Menu::from(vec![
// MenuItem::Simple(SimpleMenuItem::new("open", "Open")),
// MenuItem::Separator,
// MenuItem::Sub(SubMenuItem::new(
// "Sub",
// vec![
// MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")),
// MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")),
// ],
// )),
// ])
// .unwrap();
// remote.show_context_menu(&menu);
// }))
eventloop.run();
}