Initial draft of wayland event source
This commit is contained in:
		
							parent
							
								
									1a21a81ace
								
							
						
					
					
						commit
						1d6b152c15
					
				
							
								
								
									
										14
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -142,10 +142,14 @@ dependencies = [
 | 
			
		|||
name = "espanso-detect"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "cc",
 | 
			
		||||
 "enum-as-inner",
 | 
			
		||||
 "lazycell",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "log",
 | 
			
		||||
 "scopeguard",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "widestring",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -204,9 +208,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "libc"
 | 
			
		||||
version = "0.2.84"
 | 
			
		||||
version = "0.2.85"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
 | 
			
		||||
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "libdbus-sys"
 | 
			
		||||
| 
						 | 
				
			
			@ -377,6 +381,12 @@ version = "1.0.5"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "scopeguard"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde"
 | 
			
		||||
version = "1.0.123"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,12 @@ lazycell = "1.3.0"
 | 
			
		|||
[target.'cfg(windows)'.dependencies]
 | 
			
		||||
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]
 | 
			
		||||
cc = "1.0.66"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,15 +37,24 @@ fn cc_config() {
 | 
			
		|||
fn cc_config() {
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/x11/native.cpp");
 | 
			
		||||
  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()
 | 
			
		||||
    .cpp(true)
 | 
			
		||||
    .include("src/x11/native.h")
 | 
			
		||||
    .file("src/x11/native.cpp")
 | 
			
		||||
    .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-lib=static=espansodetect");
 | 
			
		||||
  println!("cargo:rustc-link-lib=static=espansodetectevdev");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=X11");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=Xtst");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=xkbcommon");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										49
									
								
								espanso-detect/src/evdev/context.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								espanso-detect/src/evdev/context.rs
									
									
									
									
									
										Normal 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(),
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										248
									
								
								espanso-detect/src/evdev/device.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								espanso-detect/src/evdev/device.rs
									
									
									
									
									
										Normal 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(),
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								espanso-detect/src/evdev/ffi.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								espanso-detect/src/evdev/ffi.rs
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								espanso-detect/src/evdev/keymap.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								espanso-detect/src/evdev/keymap.rs
									
									
									
									
									
										Normal 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(),
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										333
									
								
								espanso-detect/src/evdev/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								espanso-detect/src/evdev/mod.rs
									
									
									
									
									
										Normal 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());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
							
								
								
									
										84
									
								
								espanso-detect/src/evdev/native.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								espanso-detect/src/evdev/native.cpp
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								espanso-detect/src/evdev/native.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								espanso-detect/src/evdev/native.h
									
									
									
									
									
										Normal 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
 | 
			
		||||
| 
						 | 
				
			
			@ -23,4 +23,7 @@ pub mod event;
 | 
			
		|||
pub mod win32;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
pub mod x11;
 | 
			
		||||
pub mod x11;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
pub mod evdev;
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +38,7 @@ fn main() {
 | 
			
		|||
 | 
			
		||||
  let handle = std::thread::spawn(move || {
 | 
			
		||||
    //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.eventloop(Box::new(move |event: InputEvent| {
 | 
			
		||||
      println!("ev {:?}", event);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										75
									
								
								espanso/src/main_old.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								espanso/src/main_old.rs
									
									
									
									
									
										Normal 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();
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user