Implement hotkeys handling on X11
This commit is contained in:
		
							parent
							
								
									474eae69d5
								
							
						
					
					
						commit
						fbeca8b6e9
					
				| 
						 | 
				
			
			@ -502,9 +502,97 @@ impl ShortcutKey {
 | 
			
		|||
    Some(vkey)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Linux mappings
 | 
			
		||||
  // NOTE: on linux, this method returns the KeySym and not the KeyCode
 | 
			
		||||
  //       which should be obtained in other ways depending on the backend.
 | 
			
		||||
  //       (X11 or Wayland)
 | 
			
		||||
  #[cfg(target_os = "linux")]
 | 
			
		||||
  pub fn to_code(&self) -> Option<u32> {
 | 
			
		||||
    None // Not supported on Linux
 | 
			
		||||
    match self {
 | 
			
		||||
      ShortcutKey::Alt => Some(0xFFE9),
 | 
			
		||||
      ShortcutKey::Control => Some(0xFFE3),
 | 
			
		||||
      ShortcutKey::Meta => Some(0xFFEB),
 | 
			
		||||
      ShortcutKey::Shift => Some(0xFFE1),
 | 
			
		||||
      ShortcutKey::Enter => Some(0xFF0D),
 | 
			
		||||
      ShortcutKey::Tab => Some(0xFF09),
 | 
			
		||||
      ShortcutKey::Space => Some(0x20),
 | 
			
		||||
      ShortcutKey::ArrowDown => Some(0xFF54),
 | 
			
		||||
      ShortcutKey::ArrowLeft => Some(0xFF51),
 | 
			
		||||
      ShortcutKey::ArrowRight => Some(0xFF53),
 | 
			
		||||
      ShortcutKey::ArrowUp => Some(0xFF52),
 | 
			
		||||
      ShortcutKey::End => Some(0xFF57),
 | 
			
		||||
      ShortcutKey::Home => Some(0xFF50),
 | 
			
		||||
      ShortcutKey::PageDown => Some(0xFF56),
 | 
			
		||||
      ShortcutKey::PageUp => Some(0xFF55),
 | 
			
		||||
      ShortcutKey::Insert => Some(0xff63),
 | 
			
		||||
      ShortcutKey::F1 => Some(0xFFBE),
 | 
			
		||||
      ShortcutKey::F2 => Some(0xFFBF),
 | 
			
		||||
      ShortcutKey::F3 => Some(0xFFC0),
 | 
			
		||||
      ShortcutKey::F4 => Some(0xFFC1),
 | 
			
		||||
      ShortcutKey::F5 => Some(0xFFC2),
 | 
			
		||||
      ShortcutKey::F6 => Some(0xFFC3),
 | 
			
		||||
      ShortcutKey::F7 => Some(0xFFC4),
 | 
			
		||||
      ShortcutKey::F8 => Some(0xFFC5),
 | 
			
		||||
      ShortcutKey::F9 => Some(0xFFC6),
 | 
			
		||||
      ShortcutKey::F10 => Some(0xFFC7),
 | 
			
		||||
      ShortcutKey::F11 => Some(0xFFC8),
 | 
			
		||||
      ShortcutKey::F12 => Some(0xFFC9),
 | 
			
		||||
      ShortcutKey::F13 => Some(0xFFCA),
 | 
			
		||||
      ShortcutKey::F14 => Some(0xFFCB),
 | 
			
		||||
      ShortcutKey::F15 => Some(0xFFCC),
 | 
			
		||||
      ShortcutKey::F16 => Some(0xFFCD),
 | 
			
		||||
      ShortcutKey::F17 => Some(0xFFCE),
 | 
			
		||||
      ShortcutKey::F18 => Some(0xFFCF),
 | 
			
		||||
      ShortcutKey::F19 => Some(0xFFD0),
 | 
			
		||||
      ShortcutKey::F20 => Some(0xFFD1),
 | 
			
		||||
      ShortcutKey::A => Some(0x0061),
 | 
			
		||||
      ShortcutKey::B => Some(0x0062),
 | 
			
		||||
      ShortcutKey::C => Some(0x0063),
 | 
			
		||||
      ShortcutKey::D => Some(0x0064),
 | 
			
		||||
      ShortcutKey::E => Some(0x0065),
 | 
			
		||||
      ShortcutKey::F => Some(0x0066),
 | 
			
		||||
      ShortcutKey::G => Some(0x0067),
 | 
			
		||||
      ShortcutKey::H => Some(0x0068),
 | 
			
		||||
      ShortcutKey::I => Some(0x0069),
 | 
			
		||||
      ShortcutKey::J => Some(0x006a),
 | 
			
		||||
      ShortcutKey::K => Some(0x006b),
 | 
			
		||||
      ShortcutKey::L => Some(0x006c),
 | 
			
		||||
      ShortcutKey::M => Some(0x006d),
 | 
			
		||||
      ShortcutKey::N => Some(0x006e),
 | 
			
		||||
      ShortcutKey::O => Some(0x006f),
 | 
			
		||||
      ShortcutKey::P => Some(0x0070),
 | 
			
		||||
      ShortcutKey::Q => Some(0x0071),
 | 
			
		||||
      ShortcutKey::R => Some(0x0072),
 | 
			
		||||
      ShortcutKey::S => Some(0x0073),
 | 
			
		||||
      ShortcutKey::T => Some(0x0074),
 | 
			
		||||
      ShortcutKey::U => Some(0x0075),
 | 
			
		||||
      ShortcutKey::V => Some(0x0076),
 | 
			
		||||
      ShortcutKey::W => Some(0x0077),
 | 
			
		||||
      ShortcutKey::X => Some(0x0078),
 | 
			
		||||
      ShortcutKey::Y => Some(0x0079),
 | 
			
		||||
      ShortcutKey::Z => Some(0x007a),
 | 
			
		||||
      ShortcutKey::N0 => Some(0x0030),
 | 
			
		||||
      ShortcutKey::N1 => Some(0x0031),
 | 
			
		||||
      ShortcutKey::N2 => Some(0x0032),
 | 
			
		||||
      ShortcutKey::N3 => Some(0x0033),
 | 
			
		||||
      ShortcutKey::N4 => Some(0x0034),
 | 
			
		||||
      ShortcutKey::N5 => Some(0x0035),
 | 
			
		||||
      ShortcutKey::N6 => Some(0x0036),
 | 
			
		||||
      ShortcutKey::N7 => Some(0x0037),
 | 
			
		||||
      ShortcutKey::N8 => Some(0x0038),
 | 
			
		||||
      ShortcutKey::N9 => Some(0x0039),
 | 
			
		||||
      ShortcutKey::Numpad0 => Some(0xffb0),
 | 
			
		||||
      ShortcutKey::Numpad1 => Some(0xffb1),
 | 
			
		||||
      ShortcutKey::Numpad2 => Some(0xffb2),
 | 
			
		||||
      ShortcutKey::Numpad3 => Some(0xffb3),
 | 
			
		||||
      ShortcutKey::Numpad4 => Some(0xffb4),
 | 
			
		||||
      ShortcutKey::Numpad5 => Some(0xffb5),
 | 
			
		||||
      ShortcutKey::Numpad6 => Some(0xffb6),
 | 
			
		||||
      ShortcutKey::Numpad7 => Some(0xffb7),
 | 
			
		||||
      ShortcutKey::Numpad8 => Some(0xffb8),
 | 
			
		||||
      ShortcutKey::Numpad9 => Some(0xffb9),
 | 
			
		||||
      ShortcutKey::Raw(code) => Some(*code as u32),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,7 @@ pub struct SourceCreationOptions {
 | 
			
		|||
  pub evdev_keyboard_rmlvo: Option<KeyboardConfig>,
 | 
			
		||||
 | 
			
		||||
  // List of global hotkeys the detection module has to register
 | 
			
		||||
  // NOTE: Hotkeys are ignored on Linux
 | 
			
		||||
  // NOTE: Hotkeys don't work under the EVDEV backend yet (Wayland)
 | 
			
		||||
  pub hotkeys: Vec<HotKey>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +103,7 @@ pub fn get_source(options: SourceCreationOptions) -> Result<Box<dyn Source>> {
 | 
			
		|||
    Ok(Box::new(evdev::EVDEVSource::new(options)))
 | 
			
		||||
  } else {
 | 
			
		||||
    info!("using X11Source");
 | 
			
		||||
    Ok(Box::new(x11::X11Source::new()))
 | 
			
		||||
    Ok(Box::new(x11::X11Source::new(&options.hotkeys)))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,7 +37,7 @@
 | 
			
		|||
#define INPUT_MOUSE_MIDDLE_BUTTON 3
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  // Keyboard or Mouse event
 | 
			
		||||
  // Keyboard, Mouse or Hotkey event
 | 
			
		||||
  int32_t event_type;
 | 
			
		||||
 | 
			
		||||
  // Contains the string corresponding to the key, if any
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,7 @@
 | 
			
		|||
#define INPUT_MOUSE_BUTTON_5      8
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  // Keyboard or Mouse event
 | 
			
		||||
  // Keyboard, Mouse or Hotkey event
 | 
			
		||||
  int32_t event_type;
 | 
			
		||||
 | 
			
		||||
  // Contains the string corresponding to the key, if any
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,21 +17,25 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::ffi::{c_void, CStr};
 | 
			
		||||
use std::{
 | 
			
		||||
  collections::HashMap,
 | 
			
		||||
  ffi::{c_void, CStr},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use lazycell::LazyCell;
 | 
			
		||||
use log::{error, trace, warn};
 | 
			
		||||
use log::{debug, error, trace, warn};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
use crate::event::Variant::*;
 | 
			
		||||
use crate::event::{HotKeyEvent, Key::*, MouseButton, MouseEvent};
 | 
			
		||||
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
 | 
			
		||||
use crate::event::{Key::*, MouseButton, MouseEvent};
 | 
			
		||||
use crate::{event::Status::*, Source, SourceCallback};
 | 
			
		||||
use crate::{event::Variant::*, hotkey::HotKey};
 | 
			
		||||
 | 
			
		||||
const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1;
 | 
			
		||||
const INPUT_EVENT_TYPE_MOUSE: i32 = 2;
 | 
			
		||||
const INPUT_EVENT_TYPE_HOTKEY: i32 = 3;
 | 
			
		||||
 | 
			
		||||
const INPUT_STATUS_PRESSED: i32 = 1;
 | 
			
		||||
const INPUT_STATUS_RELEASED: i32 = 2;
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +57,33 @@ pub struct RawInputEvent {
 | 
			
		|||
  pub key_sym: i32,
 | 
			
		||||
  pub key_code: i32,
 | 
			
		||||
  pub status: i32,
 | 
			
		||||
  pub state: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
pub struct RawModifierIndexes {
 | 
			
		||||
  pub ctrl: i32,
 | 
			
		||||
  pub alt: i32,
 | 
			
		||||
  pub shift: i32,
 | 
			
		||||
  pub meta: i32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
pub struct RawHotKeyRequest {
 | 
			
		||||
  pub key_sym: u32,
 | 
			
		||||
  pub ctrl: i32,
 | 
			
		||||
  pub alt: i32,
 | 
			
		||||
  pub shift: i32,
 | 
			
		||||
  pub meta: i32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct RawHotKeyResult {
 | 
			
		||||
  pub success: i32,
 | 
			
		||||
  pub key_code: i32,
 | 
			
		||||
  pub state: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(improper_ctypes)]
 | 
			
		||||
| 
						 | 
				
			
			@ -62,25 +93,40 @@ extern "C" {
 | 
			
		|||
 | 
			
		||||
  pub fn detect_initialize(_self: *const X11Source, error_code: *mut i32) -> *mut c_void;
 | 
			
		||||
 | 
			
		||||
  pub fn detect_get_modifier_indexes(context: *const c_void) -> RawModifierIndexes;
 | 
			
		||||
 | 
			
		||||
  pub fn detect_register_hotkey(
 | 
			
		||||
    context: *const c_void,
 | 
			
		||||
    request: RawHotKeyRequest,
 | 
			
		||||
    mod_indexes: RawModifierIndexes,
 | 
			
		||||
  ) -> RawHotKeyResult;
 | 
			
		||||
 | 
			
		||||
  pub fn detect_eventloop(
 | 
			
		||||
    window: *const c_void,
 | 
			
		||||
    context: *const c_void,
 | 
			
		||||
    event_callback: extern "C" fn(_self: *mut X11Source, event: RawInputEvent),
 | 
			
		||||
  ) -> i32;
 | 
			
		||||
 | 
			
		||||
  pub fn detect_destroy(window: *const c_void) -> i32;
 | 
			
		||||
  pub fn detect_destroy(context: *const c_void) -> i32;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct X11Source {
 | 
			
		||||
  handle: *mut c_void,
 | 
			
		||||
  callback: LazyCell<SourceCallback>,
 | 
			
		||||
  hotkeys: Vec<HotKey>,
 | 
			
		||||
 | 
			
		||||
  raw_hotkey_mapping: HashMap<(i32, u32), i32>, // (key_code, state) -> hotkey ID
 | 
			
		||||
  valid_modifiers_mask: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::new_without_default)]
 | 
			
		||||
impl X11Source {
 | 
			
		||||
  pub fn new() -> X11Source {
 | 
			
		||||
  pub fn new(hotkeys: &[HotKey]) -> X11Source {
 | 
			
		||||
    Self {
 | 
			
		||||
      handle: std::ptr::null_mut(),
 | 
			
		||||
      callback: LazyCell::new(),
 | 
			
		||||
      hotkeys: hotkeys.to_vec(),
 | 
			
		||||
      raw_hotkey_mapping: HashMap::new(),
 | 
			
		||||
      valid_modifiers_mask: 0,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +153,29 @@ impl Source for X11Source {
 | 
			
		|||
      return Err(error.into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mod_indexes = unsafe { detect_get_modifier_indexes(handle) };
 | 
			
		||||
    self.valid_modifiers_mask |= 1 << mod_indexes.ctrl;
 | 
			
		||||
    self.valid_modifiers_mask |= 1 << mod_indexes.alt;
 | 
			
		||||
    self.valid_modifiers_mask |= 1 << mod_indexes.meta;
 | 
			
		||||
    self.valid_modifiers_mask |= 1 << mod_indexes.shift;
 | 
			
		||||
 | 
			
		||||
    // Register the hotkeys
 | 
			
		||||
    let raw_hotkey_mapping = &mut self.raw_hotkey_mapping;
 | 
			
		||||
    self.hotkeys.iter().for_each(|hk| {
 | 
			
		||||
      let raw = convert_hotkey_to_raw(&hk);
 | 
			
		||||
      if let Some(raw_hk) = raw {
 | 
			
		||||
        let result = unsafe { detect_register_hotkey(handle, raw_hk, mod_indexes) };
 | 
			
		||||
        if result.success == 0 {
 | 
			
		||||
          error!("unable to register hotkey: {}", hk);
 | 
			
		||||
        } else {
 | 
			
		||||
          raw_hotkey_mapping.insert((result.key_code, result.state), hk.id);
 | 
			
		||||
          debug!("registered hotkey: {}", hk);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        error!("unable to generate raw hotkey mapping: {}", hk);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    self.handle = handle;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
| 
						 | 
				
			
			@ -124,8 +193,13 @@ impl Source for X11Source {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    extern "C" fn callback(_self: *mut X11Source, event: RawInputEvent) {
 | 
			
		||||
      let event: Option<InputEvent> = event.into();
 | 
			
		||||
      if let Some(callback) = unsafe { (*_self).callback.borrow() } {
 | 
			
		||||
      let source_self = unsafe { &*_self };
 | 
			
		||||
      let event: Option<InputEvent> = convert_raw_input_event_to_input_event(
 | 
			
		||||
        event,
 | 
			
		||||
        &source_self.raw_hotkey_mapping,
 | 
			
		||||
        source_self.valid_modifiers_mask,
 | 
			
		||||
      );
 | 
			
		||||
      if let Some(callback) = source_self.callback.borrow() {
 | 
			
		||||
        if let Some(event) = event {
 | 
			
		||||
          callback(event)
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -160,6 +234,17 @@ impl Drop for X11Source {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn convert_hotkey_to_raw(hk: &HotKey) -> Option<RawHotKeyRequest> {
 | 
			
		||||
  let key_sym = hk.key.to_code()?;
 | 
			
		||||
  Some(RawHotKeyRequest {
 | 
			
		||||
    key_sym,
 | 
			
		||||
    ctrl: if hk.has_ctrl() { 1 } else { 0 },
 | 
			
		||||
    alt: if hk.has_alt() { 1 } else { 0 },
 | 
			
		||||
    shift: if hk.has_shift() { 1 } else { 0 },
 | 
			
		||||
    meta: if hk.has_meta() { 1 } else { 0 },
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum X11SourceError {
 | 
			
		||||
  #[error("cannot open displays")]
 | 
			
		||||
| 
						 | 
				
			
			@ -181,61 +266,70 @@ pub enum X11SourceError {
 | 
			
		|||
  Internal(),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<RawInputEvent> for Option<InputEvent> {
 | 
			
		||||
  fn from(raw: RawInputEvent) -> Option<InputEvent> {
 | 
			
		||||
    let status = match raw.status {
 | 
			
		||||
      INPUT_STATUS_RELEASED => Released,
 | 
			
		||||
      INPUT_STATUS_PRESSED => Pressed,
 | 
			
		||||
      _ => Pressed,
 | 
			
		||||
    };
 | 
			
		||||
fn convert_raw_input_event_to_input_event(
 | 
			
		||||
  raw: RawInputEvent,
 | 
			
		||||
  raw_hotkey_mapping: &HashMap<(i32, u32), i32>,
 | 
			
		||||
  valid_modifiers_mask: u32,
 | 
			
		||||
) -> Option<InputEvent> {
 | 
			
		||||
  let status = match raw.status {
 | 
			
		||||
    INPUT_STATUS_RELEASED => Released,
 | 
			
		||||
    INPUT_STATUS_PRESSED => Pressed,
 | 
			
		||||
    _ => Pressed,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    match raw.event_type {
 | 
			
		||||
      // Keyboard events
 | 
			
		||||
      INPUT_EVENT_TYPE_KEYBOARD => {
 | 
			
		||||
        let (key, variant) = key_sym_to_key(raw.key_sym);
 | 
			
		||||
        let value = if raw.buffer_len > 0 {
 | 
			
		||||
          let raw_string_result =
 | 
			
		||||
            CStr::from_bytes_with_nul(&raw.buffer[..((raw.buffer_len + 1) as usize)]);
 | 
			
		||||
          match raw_string_result {
 | 
			
		||||
            Ok(c_string) => {
 | 
			
		||||
              let string_result = c_string.to_str();
 | 
			
		||||
              match string_result {
 | 
			
		||||
                Ok(value) => Some(value.to_string()),
 | 
			
		||||
                Err(err) => {
 | 
			
		||||
                  warn!("char conversion error: {}", err);
 | 
			
		||||
                  None
 | 
			
		||||
                }
 | 
			
		||||
  match raw.event_type {
 | 
			
		||||
    // Keyboard events
 | 
			
		||||
    INPUT_EVENT_TYPE_KEYBOARD => {
 | 
			
		||||
      let (key, variant) = key_sym_to_key(raw.key_sym);
 | 
			
		||||
      let value = if raw.buffer_len > 0 {
 | 
			
		||||
        let raw_string_result =
 | 
			
		||||
          CStr::from_bytes_with_nul(&raw.buffer[..((raw.buffer_len + 1) as usize)]);
 | 
			
		||||
        match raw_string_result {
 | 
			
		||||
          Ok(c_string) => {
 | 
			
		||||
            let string_result = c_string.to_str();
 | 
			
		||||
            match string_result {
 | 
			
		||||
              Ok(value) => Some(value.to_string()),
 | 
			
		||||
              Err(err) => {
 | 
			
		||||
                warn!("char conversion error: {}", err);
 | 
			
		||||
                None
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
              warn!("Received malformed char: {}", err);
 | 
			
		||||
              None
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          None
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return Some(InputEvent::Keyboard(KeyboardEvent {
 | 
			
		||||
          key,
 | 
			
		||||
          value,
 | 
			
		||||
          status,
 | 
			
		||||
          variant,
 | 
			
		||||
        }));
 | 
			
		||||
      }
 | 
			
		||||
      // Mouse events
 | 
			
		||||
      INPUT_EVENT_TYPE_MOUSE => {
 | 
			
		||||
        let button = raw_to_mouse_button(raw.key_code);
 | 
			
		||||
 | 
			
		||||
        if let Some(button) = button {
 | 
			
		||||
          return Some(InputEvent::Mouse(MouseEvent { button, status }));
 | 
			
		||||
          Err(err) => {
 | 
			
		||||
            warn!("Received malformed char: {}", err);
 | 
			
		||||
            None
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      _ => {}
 | 
			
		||||
    }
 | 
			
		||||
      } else {
 | 
			
		||||
        None
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
    None
 | 
			
		||||
      return Some(InputEvent::Keyboard(KeyboardEvent {
 | 
			
		||||
        key,
 | 
			
		||||
        value,
 | 
			
		||||
        status,
 | 
			
		||||
        variant,
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
    // Mouse events
 | 
			
		||||
    INPUT_EVENT_TYPE_MOUSE => {
 | 
			
		||||
      let button = raw_to_mouse_button(raw.key_code);
 | 
			
		||||
 | 
			
		||||
      if let Some(button) = button {
 | 
			
		||||
        return Some(InputEvent::Mouse(MouseEvent { button, status }));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Hotkey events
 | 
			
		||||
    INPUT_EVENT_TYPE_HOTKEY => {
 | 
			
		||||
      let state = raw.state & valid_modifiers_mask;
 | 
			
		||||
      if let Some(id) = raw_hotkey_mapping.get(&(raw.key_code, state)) {
 | 
			
		||||
        return Some(InputEvent::HotKey(HotKeyEvent { hotkey_id: *id }));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    _ => {}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  None
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Mappings from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
 | 
			
		||||
| 
						 | 
				
			
			@ -327,6 +421,7 @@ mod tests {
 | 
			
		|||
      key_code: 0,
 | 
			
		||||
      key_sym: 0,
 | 
			
		||||
      status: INPUT_STATUS_PRESSED,
 | 
			
		||||
      state: 0,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -342,7 +437,7 @@ mod tests {
 | 
			
		|||
    raw.status = INPUT_STATUS_RELEASED;
 | 
			
		||||
    raw.key_sym = 0x4B;
 | 
			
		||||
 | 
			
		||||
    let result: Option<InputEvent> = raw.into();
 | 
			
		||||
    let result: Option<InputEvent> = convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      result.unwrap(),
 | 
			
		||||
      InputEvent::Keyboard(KeyboardEvent {
 | 
			
		||||
| 
						 | 
				
			
			@ -361,7 +456,7 @@ mod tests {
 | 
			
		|||
    raw.status = INPUT_STATUS_RELEASED;
 | 
			
		||||
    raw.key_code = INPUT_MOUSE_RIGHT_BUTTON;
 | 
			
		||||
 | 
			
		||||
    let result: Option<InputEvent> = raw.into();
 | 
			
		||||
    let result: Option<InputEvent> = convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      result.unwrap(),
 | 
			
		||||
      InputEvent::Mouse(MouseEvent {
 | 
			
		||||
| 
						 | 
				
			
			@ -371,6 +466,25 @@ mod tests {
 | 
			
		|||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn raw_to_input_event_hotkey_works_correctly() {
 | 
			
		||||
    let mut raw = default_raw_input_event();
 | 
			
		||||
    raw.event_type = INPUT_EVENT_TYPE_HOTKEY;
 | 
			
		||||
    raw.state = 0b00000011;
 | 
			
		||||
    raw.key_code = 10;
 | 
			
		||||
 | 
			
		||||
    let mut raw_hotkey_mapping = HashMap::new();
 | 
			
		||||
    raw_hotkey_mapping.insert((10, 1), 20);
 | 
			
		||||
 | 
			
		||||
    let result: Option<InputEvent> = convert_raw_input_event_to_input_event(raw, &raw_hotkey_mapping, 1);
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      result.unwrap(),
 | 
			
		||||
      InputEvent::HotKey(HotKeyEvent {
 | 
			
		||||
        hotkey_id: 20,
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn raw_to_input_invalid_buffer() {
 | 
			
		||||
    let buffer: [u8; 24] = [123; 24];
 | 
			
		||||
| 
						 | 
				
			
			@ -379,7 +493,7 @@ mod tests {
 | 
			
		|||
    raw.buffer = buffer;
 | 
			
		||||
    raw.buffer_len = 5;
 | 
			
		||||
 | 
			
		||||
    let result: Option<InputEvent> = raw.into();
 | 
			
		||||
    let result: Option<InputEvent> = convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
 | 
			
		||||
    assert!(result.unwrap().into_keyboard().unwrap().value.is_none());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -387,7 +501,7 @@ mod tests {
 | 
			
		|||
  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();
 | 
			
		||||
    let result: Option<InputEvent> = convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
 | 
			
		||||
    assert!(result.is_none());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,7 @@ We will refer to this extension as RE from now on.
 | 
			
		|||
#include <array>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
#include <X11/Xlibint.h>
 | 
			
		||||
#include <X11/Xlib.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -170,6 +171,85 @@ void *detect_initialize(void *_rust_instance, int32_t *error_code)
 | 
			
		|||
  return context.release();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ModifierIndexes detect_get_modifier_indexes(void *_context) {
 | 
			
		||||
  DetectContext *context = (DetectContext *)_context;
 | 
			
		||||
  XModifierKeymap *map = XGetModifierMapping(context->ctrl_disp);
 | 
			
		||||
 | 
			
		||||
  ModifierIndexes indexes = {};
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i<8; i++) {
 | 
			
		||||
    if (map->max_keypermod > 0) {
 | 
			
		||||
      int code = map->modifiermap[i * map->max_keypermod];
 | 
			
		||||
      KeySym sym = XkbKeycodeToKeysym(context->ctrl_disp, code, 0, 0);
 | 
			
		||||
      if (sym == XK_Control_L || sym == XK_Control_R) {
 | 
			
		||||
        indexes.ctrl = i;
 | 
			
		||||
      } else if (sym == XK_Super_L || sym == XK_Super_R) {
 | 
			
		||||
        indexes.meta = i;
 | 
			
		||||
      } else if (sym == XK_Shift_L || sym == XK_Shift_R) {
 | 
			
		||||
        indexes.shift = i;
 | 
			
		||||
      } else if (sym == XK_Alt_L || sym == XK_Alt_R) {
 | 
			
		||||
        indexes.alt = i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  XFreeModifiermap(map);
 | 
			
		||||
 | 
			
		||||
  return indexes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
HotKeyResult detect_register_hotkey(void *_context, HotKeyRequest request, ModifierIndexes mod_indexes) {
 | 
			
		||||
  DetectContext *context = (DetectContext *)_context;
 | 
			
		||||
  KeyCode key_code = XKeysymToKeycode(context->ctrl_disp, request.key_sym);
 | 
			
		||||
 | 
			
		||||
  HotKeyResult result = {};
 | 
			
		||||
  if (key_code == 0) {
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t valid_modifiers = 0;
 | 
			
		||||
  valid_modifiers |= 1 << mod_indexes.alt;
 | 
			
		||||
  valid_modifiers |= 1 << mod_indexes.ctrl;
 | 
			
		||||
  valid_modifiers |= 1 << mod_indexes.shift;
 | 
			
		||||
  valid_modifiers |= 1 << mod_indexes.meta;
 | 
			
		||||
 | 
			
		||||
  uint32_t target_modifiers = 0;
 | 
			
		||||
  if (request.ctrl) {
 | 
			
		||||
    target_modifiers |= 1 << mod_indexes.ctrl;
 | 
			
		||||
  }
 | 
			
		||||
  if (request.alt) {
 | 
			
		||||
    target_modifiers |= 1 << mod_indexes.alt;
 | 
			
		||||
  }
 | 
			
		||||
  if (request.shift) {
 | 
			
		||||
    target_modifiers |= 1 << mod_indexes.shift;
 | 
			
		||||
  }
 | 
			
		||||
  if (request.meta) {
 | 
			
		||||
    target_modifiers |= 1 << mod_indexes.meta;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  result.state = target_modifiers;
 | 
			
		||||
  result.key_code = key_code;
 | 
			
		||||
  result.success = 1;
 | 
			
		||||
 | 
			
		||||
  Window root = DefaultRootWindow(context->ctrl_disp);
 | 
			
		||||
 | 
			
		||||
  // We need to register an hotkey for all combinations of "useless" modifiers,
 | 
			
		||||
  // such as the NumLock, as the XGrabKey method wants an exact match.
 | 
			
		||||
  for (uint state = 0; state<256; state++) {
 | 
			
		||||
    // Check if the current state includes a "useless modifier" but none of the valid ones
 | 
			
		||||
    if ((state == 0 || (state & ~valid_modifiers) != 0) && (state & valid_modifiers) == 0) {
 | 
			
		||||
      uint final_modifiers = state | target_modifiers;
 | 
			
		||||
      
 | 
			
		||||
      int res = XGrabKey(context->ctrl_disp, key_code, final_modifiers, root, False, GrabModeAsync, GrabModeAsync);
 | 
			
		||||
      if (res == BadAccess || res == BadValue) {
 | 
			
		||||
        result.success = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t detect_eventloop(void *_context, EventCallback _callback)
 | 
			
		||||
{
 | 
			
		||||
  DetectContext *context = (DetectContext *)_context;
 | 
			
		||||
| 
						 | 
				
			
			@ -211,6 +291,15 @@ int32_t detect_eventloop(void *_context, EventCallback _callback)
 | 
			
		|||
        {
 | 
			
		||||
          XRefreshKeyboardMapping(e);
 | 
			
		||||
        }
 | 
			
		||||
      } else if (event.type == KeyPress) {
 | 
			
		||||
        InputEvent inputEvent = {};
 | 
			
		||||
        inputEvent.event_type = INPUT_EVENT_TYPE_HOTKEY;
 | 
			
		||||
        inputEvent.key_code = event.xkey.keycode;
 | 
			
		||||
        inputEvent.state = event.xkey.state;
 | 
			
		||||
        if (context->event_callback)
 | 
			
		||||
        {
 | 
			
		||||
          context->event_callback(context->rust_instance, inputEvent);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,13 +24,14 @@
 | 
			
		|||
 | 
			
		||||
#define INPUT_EVENT_TYPE_KEYBOARD 1
 | 
			
		||||
#define INPUT_EVENT_TYPE_MOUSE 2
 | 
			
		||||
#define INPUT_EVENT_TYPE_HOTKEY 3
 | 
			
		||||
 | 
			
		||||
#define INPUT_STATUS_PRESSED 1
 | 
			
		||||
#define INPUT_STATUS_RELEASED 2
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
  // Keyboard or Mouse event
 | 
			
		||||
  // Keyboard, Mouse or Hotkey event
 | 
			
		||||
  int32_t event_type;
 | 
			
		||||
 | 
			
		||||
  // Contains the string corresponding to the key, if any
 | 
			
		||||
| 
						 | 
				
			
			@ -42,13 +43,37 @@ typedef struct
 | 
			
		|||
  int32_t key_sym;
 | 
			
		||||
 | 
			
		||||
  // Virtual key code of the pressed key in case of keyboard events
 | 
			
		||||
  // Mouse button code otherwise.
 | 
			
		||||
  // Mouse button code for mouse events.
 | 
			
		||||
  int32_t key_code;
 | 
			
		||||
 | 
			
		||||
  // Pressed or Released status
 | 
			
		||||
  int32_t status;
 | 
			
		||||
 | 
			
		||||
  // Keycode state (modifiers) in a Hotkey event
 | 
			
		||||
  uint32_t state;
 | 
			
		||||
} InputEvent;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  int32_t key_sym;
 | 
			
		||||
  int32_t ctrl;
 | 
			
		||||
  int32_t alt;
 | 
			
		||||
  int32_t shift;
 | 
			
		||||
  int32_t meta;
 | 
			
		||||
} HotKeyRequest;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  int32_t success;
 | 
			
		||||
  int32_t key_code;
 | 
			
		||||
  uint32_t state;
 | 
			
		||||
} HotKeyResult;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  int32_t ctrl;
 | 
			
		||||
  int32_t alt;
 | 
			
		||||
  int32_t shift;
 | 
			
		||||
  int32_t meta;
 | 
			
		||||
} ModifierIndexes;
 | 
			
		||||
 | 
			
		||||
typedef void (*EventCallback)(void *rust_istance, InputEvent data);
 | 
			
		||||
 | 
			
		||||
// Check if a X11 context is available, returning a non-zero code if true.
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +82,12 @@ extern "C" int32_t detect_check_x11();
 | 
			
		|||
// Initialize the XRecord API and return the context pointer
 | 
			
		||||
extern "C" void *detect_initialize(void *rust_istance, int32_t *error_code);
 | 
			
		||||
 | 
			
		||||
// Get the modifiers indexes in the field mask
 | 
			
		||||
extern "C" ModifierIndexes detect_get_modifier_indexes(void *context);
 | 
			
		||||
 | 
			
		||||
// Register the given hotkey
 | 
			
		||||
extern "C" HotKeyResult detect_register_hotkey(void *context, HotKeyRequest request, ModifierIndexes mod_indexes);
 | 
			
		||||
 | 
			
		||||
// Run the event loop. Blocking call.
 | 
			
		||||
extern "C" int32_t detect_eventloop(void *context, EventCallback callback);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user