Implement hotkeys handling on X11
This commit is contained in:
parent
474eae69d5
commit
fbeca8b6e9
|
@ -502,9 +502,97 @@ impl ShortcutKey {
|
||||||
Some(vkey)
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn to_code(&self) -> Option<u32> {
|
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>,
|
pub evdev_keyboard_rmlvo: Option<KeyboardConfig>,
|
||||||
|
|
||||||
// List of global hotkeys the detection module has to register
|
// 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>,
|
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)))
|
Ok(Box::new(evdev::EVDEVSource::new(options)))
|
||||||
} else {
|
} else {
|
||||||
info!("using X11Source");
|
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
|
#define INPUT_MOUSE_MIDDLE_BUTTON 3
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Keyboard or Mouse event
|
// Keyboard, Mouse or Hotkey event
|
||||||
int32_t event_type;
|
int32_t event_type;
|
||||||
|
|
||||||
// Contains the string corresponding to the key, if any
|
// Contains the string corresponding to the key, if any
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
#define INPUT_MOUSE_BUTTON_5 8
|
#define INPUT_MOUSE_BUTTON_5 8
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Keyboard or Mouse event
|
// Keyboard, Mouse or Hotkey event
|
||||||
int32_t event_type;
|
int32_t event_type;
|
||||||
|
|
||||||
// Contains the string corresponding to the key, if any
|
// Contains the string corresponding to the key, if any
|
||||||
|
|
|
@ -17,21 +17,25 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* 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 lazycell::LazyCell;
|
||||||
use log::{error, trace, warn};
|
use log::{debug, error, trace, warn};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::event::Variant::*;
|
use crate::event::{HotKeyEvent, Key::*, MouseButton, MouseEvent};
|
||||||
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
|
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
|
||||||
use crate::event::{Key::*, MouseButton, MouseEvent};
|
|
||||||
use crate::{event::Status::*, Source, SourceCallback};
|
use crate::{event::Status::*, Source, SourceCallback};
|
||||||
|
use crate::{event::Variant::*, hotkey::HotKey};
|
||||||
|
|
||||||
const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1;
|
const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1;
|
||||||
const INPUT_EVENT_TYPE_MOUSE: i32 = 2;
|
const INPUT_EVENT_TYPE_MOUSE: i32 = 2;
|
||||||
|
const INPUT_EVENT_TYPE_HOTKEY: i32 = 3;
|
||||||
|
|
||||||
const INPUT_STATUS_PRESSED: i32 = 1;
|
const INPUT_STATUS_PRESSED: i32 = 1;
|
||||||
const INPUT_STATUS_RELEASED: i32 = 2;
|
const INPUT_STATUS_RELEASED: i32 = 2;
|
||||||
|
@ -53,6 +57,33 @@ pub struct RawInputEvent {
|
||||||
pub key_sym: i32,
|
pub key_sym: i32,
|
||||||
pub key_code: i32,
|
pub key_code: i32,
|
||||||
pub status: 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)]
|
#[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_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(
|
pub fn detect_eventloop(
|
||||||
window: *const c_void,
|
context: *const c_void,
|
||||||
event_callback: extern "C" fn(_self: *mut X11Source, event: RawInputEvent),
|
event_callback: extern "C" fn(_self: *mut X11Source, event: RawInputEvent),
|
||||||
) -> i32;
|
) -> i32;
|
||||||
|
|
||||||
pub fn detect_destroy(window: *const c_void) -> i32;
|
pub fn detect_destroy(context: *const c_void) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct X11Source {
|
pub struct X11Source {
|
||||||
handle: *mut c_void,
|
handle: *mut c_void,
|
||||||
callback: LazyCell<SourceCallback>,
|
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)]
|
#[allow(clippy::new_without_default)]
|
||||||
impl X11Source {
|
impl X11Source {
|
||||||
pub fn new() -> X11Source {
|
pub fn new(hotkeys: &[HotKey]) -> X11Source {
|
||||||
Self {
|
Self {
|
||||||
handle: std::ptr::null_mut(),
|
handle: std::ptr::null_mut(),
|
||||||
callback: LazyCell::new(),
|
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());
|
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;
|
self.handle = handle;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -124,8 +193,13 @@ impl Source for X11Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn callback(_self: *mut X11Source, event: RawInputEvent) {
|
extern "C" fn callback(_self: *mut X11Source, event: RawInputEvent) {
|
||||||
let event: Option<InputEvent> = event.into();
|
let source_self = unsafe { &*_self };
|
||||||
if let Some(callback) = unsafe { (*_self).callback.borrow() } {
|
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 {
|
if let Some(event) = event {
|
||||||
callback(event)
|
callback(event)
|
||||||
} else {
|
} 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)]
|
#[derive(Error, Debug)]
|
||||||
pub enum X11SourceError {
|
pub enum X11SourceError {
|
||||||
#[error("cannot open displays")]
|
#[error("cannot open displays")]
|
||||||
|
@ -181,61 +266,70 @@ pub enum X11SourceError {
|
||||||
Internal(),
|
Internal(),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RawInputEvent> for Option<InputEvent> {
|
fn convert_raw_input_event_to_input_event(
|
||||||
fn from(raw: RawInputEvent) -> Option<InputEvent> {
|
raw: RawInputEvent,
|
||||||
let status = match raw.status {
|
raw_hotkey_mapping: &HashMap<(i32, u32), i32>,
|
||||||
INPUT_STATUS_RELEASED => Released,
|
valid_modifiers_mask: u32,
|
||||||
INPUT_STATUS_PRESSED => Pressed,
|
) -> Option<InputEvent> {
|
||||||
_ => Pressed,
|
let status = match raw.status {
|
||||||
};
|
INPUT_STATUS_RELEASED => Released,
|
||||||
|
INPUT_STATUS_PRESSED => Pressed,
|
||||||
|
_ => Pressed,
|
||||||
|
};
|
||||||
|
|
||||||
match raw.event_type {
|
match raw.event_type {
|
||||||
// Keyboard events
|
// Keyboard events
|
||||||
INPUT_EVENT_TYPE_KEYBOARD => {
|
INPUT_EVENT_TYPE_KEYBOARD => {
|
||||||
let (key, variant) = key_sym_to_key(raw.key_sym);
|
let (key, variant) = key_sym_to_key(raw.key_sym);
|
||||||
let value = if raw.buffer_len > 0 {
|
let value = if raw.buffer_len > 0 {
|
||||||
let raw_string_result =
|
let raw_string_result =
|
||||||
CStr::from_bytes_with_nul(&raw.buffer[..((raw.buffer_len + 1) as usize)]);
|
CStr::from_bytes_with_nul(&raw.buffer[..((raw.buffer_len + 1) as usize)]);
|
||||||
match raw_string_result {
|
match raw_string_result {
|
||||||
Ok(c_string) => {
|
Ok(c_string) => {
|
||||||
let string_result = c_string.to_str();
|
let string_result = c_string.to_str();
|
||||||
match string_result {
|
match string_result {
|
||||||
Ok(value) => Some(value.to_string()),
|
Ok(value) => Some(value.to_string()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("char conversion error: {}", err);
|
warn!("char conversion error: {}", err);
|
||||||
None
|
None
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
|
||||||
warn!("Received malformed char: {}", err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
Err(err) => {
|
||||||
None
|
warn!("Received malformed char: {}", err);
|
||||||
};
|
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 }));
|
|
||||||
}
|
}
|
||||||
}
|
} 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
|
// 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_code: 0,
|
||||||
key_sym: 0,
|
key_sym: 0,
|
||||||
status: INPUT_STATUS_PRESSED,
|
status: INPUT_STATUS_PRESSED,
|
||||||
|
state: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +437,7 @@ mod tests {
|
||||||
raw.status = INPUT_STATUS_RELEASED;
|
raw.status = INPUT_STATUS_RELEASED;
|
||||||
raw.key_sym = 0x4B;
|
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!(
|
assert_eq!(
|
||||||
result.unwrap(),
|
result.unwrap(),
|
||||||
InputEvent::Keyboard(KeyboardEvent {
|
InputEvent::Keyboard(KeyboardEvent {
|
||||||
|
@ -361,7 +456,7 @@ mod tests {
|
||||||
raw.status = INPUT_STATUS_RELEASED;
|
raw.status = INPUT_STATUS_RELEASED;
|
||||||
raw.key_code = INPUT_MOUSE_RIGHT_BUTTON;
|
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!(
|
assert_eq!(
|
||||||
result.unwrap(),
|
result.unwrap(),
|
||||||
InputEvent::Mouse(MouseEvent {
|
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]
|
#[test]
|
||||||
fn raw_to_input_invalid_buffer() {
|
fn raw_to_input_invalid_buffer() {
|
||||||
let buffer: [u8; 24] = [123; 24];
|
let buffer: [u8; 24] = [123; 24];
|
||||||
|
@ -379,7 +493,7 @@ mod tests {
|
||||||
raw.buffer = buffer;
|
raw.buffer = buffer;
|
||||||
raw.buffer_len = 5;
|
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());
|
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() {
|
fn raw_to_input_event_returns_none_when_missing_type() {
|
||||||
let mut raw = default_raw_input_event();
|
let mut raw = default_raw_input_event();
|
||||||
raw.event_type = 0;
|
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());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ We will refer to this extension as RE from now on.
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include <X11/Xlibint.h>
|
#include <X11/Xlibint.h>
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
|
@ -170,6 +171,85 @@ void *detect_initialize(void *_rust_instance, int32_t *error_code)
|
||||||
return context.release();
|
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)
|
int32_t detect_eventloop(void *_context, EventCallback _callback)
|
||||||
{
|
{
|
||||||
DetectContext *context = (DetectContext *)_context;
|
DetectContext *context = (DetectContext *)_context;
|
||||||
|
@ -211,6 +291,15 @@ int32_t detect_eventloop(void *_context, EventCallback _callback)
|
||||||
{
|
{
|
||||||
XRefreshKeyboardMapping(e);
|
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_KEYBOARD 1
|
||||||
#define INPUT_EVENT_TYPE_MOUSE 2
|
#define INPUT_EVENT_TYPE_MOUSE 2
|
||||||
|
#define INPUT_EVENT_TYPE_HOTKEY 3
|
||||||
|
|
||||||
#define INPUT_STATUS_PRESSED 1
|
#define INPUT_STATUS_PRESSED 1
|
||||||
#define INPUT_STATUS_RELEASED 2
|
#define INPUT_STATUS_RELEASED 2
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
// Keyboard or Mouse event
|
// Keyboard, Mouse or Hotkey event
|
||||||
int32_t event_type;
|
int32_t event_type;
|
||||||
|
|
||||||
// Contains the string corresponding to the key, if any
|
// Contains the string corresponding to the key, if any
|
||||||
|
@ -42,13 +43,37 @@ typedef struct
|
||||||
int32_t key_sym;
|
int32_t key_sym;
|
||||||
|
|
||||||
// Virtual key code of the pressed key in case of keyboard events
|
// 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;
|
int32_t key_code;
|
||||||
|
|
||||||
// Pressed or Released status
|
// Pressed or Released status
|
||||||
int32_t status;
|
int32_t status;
|
||||||
|
|
||||||
|
// Keycode state (modifiers) in a Hotkey event
|
||||||
|
uint32_t state;
|
||||||
} InputEvent;
|
} 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);
|
typedef void (*EventCallback)(void *rust_istance, InputEvent data);
|
||||||
|
|
||||||
// Check if a X11 context is available, returning a non-zero code if true.
|
// 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
|
// Initialize the XRecord API and return the context pointer
|
||||||
extern "C" void *detect_initialize(void *rust_istance, int32_t *error_code);
|
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.
|
// Run the event loop. Blocking call.
|
||||||
extern "C" int32_t detect_eventloop(void *context, EventCallback callback);
|
extern "C" int32_t detect_eventloop(void *context, EventCallback callback);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user