feat: add alternative X11 injection backend based on libxdo (#1068)
* feat(inject): first steps towards xdotool inject fallback * feat(inject): progress in the xdotool fallback implementation * feat(config): add options for alternative xdotool backend * feat(core): wire up alternative x11 backend
This commit is contained in:
		
							parent
							
								
									088080dd63
								
							
						
					
					
						commit
						f30395b8a6
					
				| 
						 | 
				
			
			@ -166,6 +166,10 @@ pub trait Config: Send + Sync {
 | 
			
		|||
  // the built-in native module on X11.
 | 
			
		||||
  fn x11_use_xclip_backend(&self) -> bool;
 | 
			
		||||
 | 
			
		||||
  // If true, use an alternative injection backend based on the `xdotool` library.
 | 
			
		||||
  // This might improve the situation for certain locales/layouts on X11.
 | 
			
		||||
  fn x11_use_xdotool_backend(&self) -> bool;
 | 
			
		||||
 | 
			
		||||
  // If true, filter out keyboard events without an explicit HID device source on Windows.
 | 
			
		||||
  // This is needed to filter out the software-generated events, including
 | 
			
		||||
  // those from espanso, but might need to be disabled when using some software-level keyboards.
 | 
			
		||||
| 
						 | 
				
			
			@ -212,6 +216,7 @@ pub trait Config: Send + Sync {
 | 
			
		|||
        secure_input_notification: {:?}
 | 
			
		||||
 | 
			
		||||
        x11_use_xclip_backend: {:?}
 | 
			
		||||
        x11_use_xdotool_backend: {:?}
 | 
			
		||||
        win32_exclude_orphan_events: {:?}
 | 
			
		||||
        win32_keyboard_layout_cache_interval: {:?}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -246,6 +251,7 @@ pub trait Config: Send + Sync {
 | 
			
		|||
      self.secure_input_notification(),
 | 
			
		||||
 | 
			
		||||
      self.x11_use_xclip_backend(),
 | 
			
		||||
      self.x11_use_xdotool_backend(),
 | 
			
		||||
      self.win32_exclude_orphan_events(),
 | 
			
		||||
      self.win32_keyboard_layout_cache_interval(),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,6 +49,7 @@ pub(crate) struct ParsedConfig {
 | 
			
		|||
  pub win32_exclude_orphan_events: Option<bool>,
 | 
			
		||||
  pub win32_keyboard_layout_cache_interval: Option<i64>,
 | 
			
		||||
  pub x11_use_xclip_backend: Option<bool>,
 | 
			
		||||
  pub x11_use_xdotool_backend: Option<bool>,
 | 
			
		||||
 | 
			
		||||
  pub pre_paste_delay: Option<usize>,
 | 
			
		||||
  pub restore_clipboard_delay: Option<usize>,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,6 +121,9 @@ pub(crate) struct YAMLConfig {
 | 
			
		|||
  #[serde(default)]
 | 
			
		||||
  pub x11_use_xclip_backend: Option<bool>,
 | 
			
		||||
 | 
			
		||||
  #[serde(default)]
 | 
			
		||||
  pub x11_use_xdotool_backend: Option<bool>,
 | 
			
		||||
 | 
			
		||||
  // Include/Exclude
 | 
			
		||||
  #[serde(default)]
 | 
			
		||||
  pub includes: Option<Vec<String>>,
 | 
			
		||||
| 
						 | 
				
			
			@ -213,6 +216,7 @@ impl TryFrom<YAMLConfig> for ParsedConfig {
 | 
			
		|||
      win32_exclude_orphan_events: yaml_config.win32_exclude_orphan_events,
 | 
			
		||||
      win32_keyboard_layout_cache_interval: yaml_config.win32_keyboard_layout_cache_interval,
 | 
			
		||||
      x11_use_xclip_backend: yaml_config.x11_use_xclip_backend,
 | 
			
		||||
      x11_use_xdotool_backend: yaml_config.x11_use_xdotool_backend,
 | 
			
		||||
 | 
			
		||||
      use_standard_includes: yaml_config.use_standard_includes,
 | 
			
		||||
      includes: yaml_config.includes,
 | 
			
		||||
| 
						 | 
				
			
			@ -273,6 +277,7 @@ mod tests {
 | 
			
		|||
    win32_exclude_orphan_events: false
 | 
			
		||||
    win32_keyboard_layout_cache_interval: 300
 | 
			
		||||
    x11_use_xclip_backend: true
 | 
			
		||||
    x11_use_xdotool_backend: true
 | 
			
		||||
      
 | 
			
		||||
    use_standard_includes: true
 | 
			
		||||
    includes: ["test1"]
 | 
			
		||||
| 
						 | 
				
			
			@ -329,6 +334,7 @@ mod tests {
 | 
			
		|||
        win32_exclude_orphan_events: Some(false),
 | 
			
		||||
        win32_keyboard_layout_cache_interval: Some(300),
 | 
			
		||||
        x11_use_xclip_backend: Some(true),
 | 
			
		||||
        x11_use_xdotool_backend: Some(true),
 | 
			
		||||
 | 
			
		||||
        pre_paste_delay: Some(300),
 | 
			
		||||
        evdev_modifier_delay: Some(40),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -331,6 +331,10 @@ impl Config for ResolvedConfig {
 | 
			
		|||
  fn x11_use_xclip_backend(&self) -> bool {
 | 
			
		||||
    self.parsed.x11_use_xclip_backend.unwrap_or(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn x11_use_xdotool_backend(&self) -> bool {
 | 
			
		||||
    self.parsed.x11_use_xdotool_backend.unwrap_or(false)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ResolvedConfig {
 | 
			
		||||
| 
						 | 
				
			
			@ -417,6 +421,7 @@ impl ResolvedConfig {
 | 
			
		|||
      win32_exclude_orphan_events,
 | 
			
		||||
      win32_keyboard_layout_cache_interval,
 | 
			
		||||
      x11_use_xclip_backend,
 | 
			
		||||
      x11_use_xdotool_backend,
 | 
			
		||||
      includes,
 | 
			
		||||
      excludes,
 | 
			
		||||
      extra_includes,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -410,6 +410,10 @@ impl Config for LegacyInteropConfig {
 | 
			
		|||
  fn x11_use_xclip_backend(&self) -> bool {
 | 
			
		||||
    false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn x11_use_xdotool_backend(&self) -> bool {
 | 
			
		||||
    false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct LegacyMatchGroup {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,9 @@ fn cc_config() {
 | 
			
		|||
fn cc_config() {
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/evdev/native.h");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/evdev/native.c");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/x11/xdotool/vendor/xdo.c");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/x11/xdotool/vendor/xdo.h");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/x11/xdotool/vendor/xdo_util.h");
 | 
			
		||||
  cc::Build::new()
 | 
			
		||||
    .include("src/evdev")
 | 
			
		||||
    .file("src/evdev/native.c")
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +50,14 @@ fn cc_config() {
 | 
			
		|||
  println!("cargo:rustc-link-lib=dylib=xkbcommon");
 | 
			
		||||
 | 
			
		||||
  if cfg!(not(feature = "wayland")) {
 | 
			
		||||
    cc::Build::new()
 | 
			
		||||
      .cpp(false)
 | 
			
		||||
      .include("src/x11/xdotool/vendor/xdo.h")
 | 
			
		||||
      .include("src/x11/xdotool/vendor/xdo_util.h")
 | 
			
		||||
      .file("src/x11/xdotool/vendor/xdo.c")
 | 
			
		||||
      .compile("xdotoolvendor");
 | 
			
		||||
 | 
			
		||||
    println!("cargo:rustc-link-lib=static=xdotoolvendor");
 | 
			
		||||
    println!("cargo:rustc-link-lib=dylib=X11");
 | 
			
		||||
    println!("cargo:rustc-link-lib=dylib=Xtst");
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,6 +61,10 @@ pub struct InjectionOptions {
 | 
			
		|||
  // Used to set a modifier-specific delay.
 | 
			
		||||
  // NOTE: Only relevant on Wayland systems.
 | 
			
		||||
  pub evdev_modifier_delay: u32,
 | 
			
		||||
 | 
			
		||||
  // If true, use the xdotool fallback to perform the expansions.
 | 
			
		||||
  // NOTE: Only relevant on Linux-X11 systems.
 | 
			
		||||
  pub x11_use_xdotool_fallback: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for InjectionOptions {
 | 
			
		||||
| 
						 | 
				
			
			@ -84,6 +88,7 @@ impl Default for InjectionOptions {
 | 
			
		|||
      delay: default_delay,
 | 
			
		||||
      disable_fast_inject: false,
 | 
			
		||||
      evdev_modifier_delay: 10,
 | 
			
		||||
      x11_use_xdotool_fallback: false,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -148,8 +153,8 @@ pub fn get_injector(options: InjectorCreationOptions) -> Result<Box<dyn Injector
 | 
			
		|||
    info!("using EVDEVInjector");
 | 
			
		||||
    Ok(Box::new(evdev::EVDEVInjector::new(options)?))
 | 
			
		||||
  } else {
 | 
			
		||||
    info!("using X11Injector");
 | 
			
		||||
    Ok(Box::new(x11::X11Injector::new()?))
 | 
			
		||||
    info!("using X11ProxyInjector");
 | 
			
		||||
    Ok(Box::new(x11::X11ProxyInjector::new()?))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,53 +0,0 @@
 | 
			
		|||
Same approach as evdev, but the lookup logic is:
 | 
			
		||||
 | 
			
		||||
#include <locale.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <X11/Xlibint.h>
 | 
			
		||||
#include <X11/Xlib.h>
 | 
			
		||||
#include <X11/Xutil.h>
 | 
			
		||||
#include <X11/cursorfont.h>
 | 
			
		||||
#include <X11/keysymdef.h>
 | 
			
		||||
#include <X11/keysym.h>
 | 
			
		||||
#include <X11/extensions/record.h>
 | 
			
		||||
#include <X11/extensions/XTest.h>
 | 
			
		||||
#include <X11/XKBlib.h>
 | 
			
		||||
#include <X11/Xatom.h>
 | 
			
		||||
 | 
			
		||||
Display *data_disp = NULL;
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
    data_disp = XOpenDisplay(NULL);
 | 
			
		||||
 | 
			
		||||
    for (int code = 0; code<256; code++) {
 | 
			
		||||
        for (int state = 0; state < 256; state++) {
 | 
			
		||||
        XKeyEvent event;
 | 
			
		||||
    event.display     = data_disp;
 | 
			
		||||
    event.window      = XDefaultRootWindow(data_disp);
 | 
			
		||||
    event.root        = XDefaultRootWindow(data_disp);
 | 
			
		||||
    event.subwindow   = None;
 | 
			
		||||
    event.time        = 0;
 | 
			
		||||
    event.x           = 1;
 | 
			
		||||
    event.y           = 1;
 | 
			
		||||
    event.x_root      = 1;
 | 
			
		||||
    event.y_root      = 1;
 | 
			
		||||
    event.same_screen = True;
 | 
			
		||||
    event.keycode     = code + 8;
 | 
			
		||||
    event.state       = state;
 | 
			
		||||
    event.type = KeyPress;
 | 
			
		||||
 | 
			
		||||
    char buffer[10];
 | 
			
		||||
    int res = XLookupString(&event, buffer, 9, NULL, NULL);
 | 
			
		||||
 | 
			
		||||
    printf("hey %d %d %s\n", code, state, buffer);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
This way, we get the state mask associated with a character, and we can pass it directly when injecting a character:
 | 
			
		||||
https://github.com/federico-terzi/espanso/blob/master/native/liblinuxbridge/fast_xdo.cpp#L37
 | 
			
		||||
							
								
								
									
										598
									
								
								espanso-inject/src/x11/default/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										598
									
								
								espanso-inject/src/x11/default/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,598 @@
 | 
			
		|||
/*
 | 
			
		||||
 * This file is part of espanso.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2019-2021 Federico Terzi
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
  collections::{HashMap, HashSet},
 | 
			
		||||
  ffi::{CStr, CString},
 | 
			
		||||
  os::raw::c_char,
 | 
			
		||||
  slice,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::x11::ffi::{
 | 
			
		||||
  Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow,
 | 
			
		||||
  XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XQueryKeymap,
 | 
			
		||||
  XSendEvent, XSync, XTestFakeKeyEvent,
 | 
			
		||||
};
 | 
			
		||||
use libc::c_void;
 | 
			
		||||
use log::{debug, error};
 | 
			
		||||
 | 
			
		||||
use crate::{linux::raw_keys::convert_to_sym_array, x11::ffi::Xutf8LookupString};
 | 
			
		||||
use anyhow::{bail, Result};
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
use crate::{keys, InjectionOptions, Injector};
 | 
			
		||||
 | 
			
		||||
use crate::x11::ffi::{
 | 
			
		||||
  XCloseIM, XCreateIC, XDestroyIC, XFilterEvent, XFree, XIMPreeditNothing, XIMStatusNothing,
 | 
			
		||||
  XNClientWindow_0, XNInputStyle_0, XOpenIM, XmbResetIC, XIC,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
 | 
			
		||||
// keycode set (where ESC is 9).
 | 
			
		||||
const EVDEV_OFFSET: u32 = 8;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug)]
 | 
			
		||||
struct KeyPair {
 | 
			
		||||
  // Keycode
 | 
			
		||||
  code: u32,
 | 
			
		||||
  // Modifier state which combined with the code produces the char
 | 
			
		||||
  // This is a bit mask:
 | 
			
		||||
  state: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug)]
 | 
			
		||||
struct KeyRecord {
 | 
			
		||||
  main: KeyPair,
 | 
			
		||||
 | 
			
		||||
  // Under some keyboard layouts (de, es), a deadkey
 | 
			
		||||
  // press might be needed to generate the right char
 | 
			
		||||
  preceding_dead_key: Option<KeyPair>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CharMap = HashMap<String, KeyRecord>;
 | 
			
		||||
type SymMap = HashMap<KeySym, KeyRecord>;
 | 
			
		||||
 | 
			
		||||
pub struct X11DefaultInjector {
 | 
			
		||||
  display: *mut Display,
 | 
			
		||||
 | 
			
		||||
  char_map: CharMap,
 | 
			
		||||
  sym_map: SymMap,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::new_without_default)]
 | 
			
		||||
impl X11DefaultInjector {
 | 
			
		||||
  pub fn new() -> Result<Self> {
 | 
			
		||||
    // Necessary to properly handle non-ascii chars
 | 
			
		||||
    let empty_string = CString::new("")?;
 | 
			
		||||
    unsafe {
 | 
			
		||||
      libc::setlocale(libc::LC_ALL, empty_string.as_ptr());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let display = unsafe { crate::x11::ffi::XOpenDisplay(std::ptr::null()) };
 | 
			
		||||
    if display.is_null() {
 | 
			
		||||
      return Err(X11InjectorError::Init().into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let (char_map, sym_map) = Self::generate_maps(display)?;
 | 
			
		||||
 | 
			
		||||
    Ok(Self {
 | 
			
		||||
      display,
 | 
			
		||||
      char_map,
 | 
			
		||||
      sym_map,
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn generate_maps(display: *mut Display) -> Result<(CharMap, SymMap)> {
 | 
			
		||||
    debug!("generating key maps");
 | 
			
		||||
 | 
			
		||||
    let mut char_map = HashMap::new();
 | 
			
		||||
    let mut sym_map = HashMap::new();
 | 
			
		||||
 | 
			
		||||
    let input_method = unsafe {
 | 
			
		||||
      XOpenIM(
 | 
			
		||||
        display,
 | 
			
		||||
        std::ptr::null_mut(),
 | 
			
		||||
        std::ptr::null_mut(),
 | 
			
		||||
        std::ptr::null_mut(),
 | 
			
		||||
      )
 | 
			
		||||
    };
 | 
			
		||||
    if input_method.is_null() {
 | 
			
		||||
      bail!("could not open input method");
 | 
			
		||||
    }
 | 
			
		||||
    let _im_guard = scopeguard::guard((), |_| {
 | 
			
		||||
      unsafe { XCloseIM(input_method) };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let input_context = unsafe {
 | 
			
		||||
      XCreateIC(
 | 
			
		||||
        input_method,
 | 
			
		||||
        XNInputStyle_0.as_ptr(),
 | 
			
		||||
        XIMPreeditNothing | XIMStatusNothing,
 | 
			
		||||
        XNClientWindow_0.as_ptr(),
 | 
			
		||||
        0,
 | 
			
		||||
        std::ptr::null_mut(),
 | 
			
		||||
      )
 | 
			
		||||
    };
 | 
			
		||||
    if input_context.is_null() {
 | 
			
		||||
      bail!("could not open input context");
 | 
			
		||||
    }
 | 
			
		||||
    let _ic_guard = scopeguard::guard((), |_| {
 | 
			
		||||
      unsafe { XDestroyIC(input_context) };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let deadkeys = Self::find_deadkeys(display, &input_context)?;
 | 
			
		||||
 | 
			
		||||
    // Cycle through all state/code combinations to populate the reverse lookup tables
 | 
			
		||||
    for key_code in 0..256u32 {
 | 
			
		||||
      for modifier_state in 0..256u32 {
 | 
			
		||||
        for dead_key in deadkeys.iter() {
 | 
			
		||||
          let code_with_offset = key_code + EVDEV_OFFSET;
 | 
			
		||||
 | 
			
		||||
          let preceding_dead_key = if let Some(dead_key) = dead_key {
 | 
			
		||||
            let mut dead_key_event = XKeyEvent {
 | 
			
		||||
              display,
 | 
			
		||||
              keycode: dead_key.code,
 | 
			
		||||
              state: dead_key.state,
 | 
			
		||||
 | 
			
		||||
              // These might not even need to be filled
 | 
			
		||||
              window: 0,
 | 
			
		||||
              root: 0,
 | 
			
		||||
              same_screen: 1,
 | 
			
		||||
              time: 0,
 | 
			
		||||
              type_: KeyPress,
 | 
			
		||||
              x_root: 1,
 | 
			
		||||
              y_root: 1,
 | 
			
		||||
              x: 1,
 | 
			
		||||
              y: 1,
 | 
			
		||||
              subwindow: 0,
 | 
			
		||||
              serial: 0,
 | 
			
		||||
              send_event: 0,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            unsafe { XFilterEvent(&mut dead_key_event, 0) };
 | 
			
		||||
 | 
			
		||||
            Some(*dead_key)
 | 
			
		||||
          } else {
 | 
			
		||||
            None
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          let mut key_event = XKeyEvent {
 | 
			
		||||
            display,
 | 
			
		||||
            keycode: code_with_offset,
 | 
			
		||||
            state: modifier_state,
 | 
			
		||||
 | 
			
		||||
            // These might not even need to be filled
 | 
			
		||||
            window: 0,
 | 
			
		||||
            root: 0,
 | 
			
		||||
            same_screen: 1,
 | 
			
		||||
            time: 0,
 | 
			
		||||
            type_: KeyPress,
 | 
			
		||||
            x_root: 1,
 | 
			
		||||
            y_root: 1,
 | 
			
		||||
            x: 1,
 | 
			
		||||
            y: 1,
 | 
			
		||||
            subwindow: 0,
 | 
			
		||||
            serial: 0,
 | 
			
		||||
            send_event: 0,
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          unsafe { XFilterEvent(&mut key_event, 0) };
 | 
			
		||||
          let mut sym: KeySym = 0;
 | 
			
		||||
          let mut buffer: [c_char; 10] = [0; 10];
 | 
			
		||||
 | 
			
		||||
          let result = unsafe {
 | 
			
		||||
            Xutf8LookupString(
 | 
			
		||||
              input_context,
 | 
			
		||||
              &mut key_event,
 | 
			
		||||
              buffer.as_mut_ptr(),
 | 
			
		||||
              (buffer.len() - 1) as i32,
 | 
			
		||||
              &mut sym,
 | 
			
		||||
              std::ptr::null_mut(),
 | 
			
		||||
            )
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          let key_record = KeyRecord {
 | 
			
		||||
            main: KeyPair {
 | 
			
		||||
              code: code_with_offset,
 | 
			
		||||
              state: modifier_state,
 | 
			
		||||
            },
 | 
			
		||||
            preceding_dead_key,
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // Keysym was found
 | 
			
		||||
          if sym != 0 {
 | 
			
		||||
            sym_map.entry(sym).or_insert(key_record);
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // Char was found
 | 
			
		||||
          if result > 0 {
 | 
			
		||||
            let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) };
 | 
			
		||||
            let string = raw_string.to_string_lossy().to_string();
 | 
			
		||||
            char_map.entry(string).or_insert(key_record);
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // We need to reset the context state to prevent
 | 
			
		||||
          // deadkeys effect to propagate to the next combination
 | 
			
		||||
          let _reset = unsafe { XmbResetIC(input_context) };
 | 
			
		||||
          unsafe { XFree(_reset as *mut c_void) };
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    debug!("Populated char_map with {} symbols", char_map.len());
 | 
			
		||||
    debug!("Populated sym_map with {} symbols", sym_map.len());
 | 
			
		||||
    debug!("Detected {} dead key combinations", deadkeys.len());
 | 
			
		||||
 | 
			
		||||
    Ok((char_map, sym_map))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn find_deadkeys(display: *mut Display, input_context: &XIC) -> Result<Vec<Option<KeyPair>>> {
 | 
			
		||||
    let mut deadkeys = vec![None];
 | 
			
		||||
    let mut seen_keysyms: HashSet<KeySym> = HashSet::new();
 | 
			
		||||
 | 
			
		||||
    // Cycle through all state/code combinations to populate the reverse lookup tables
 | 
			
		||||
    for key_code in 0..256u32 {
 | 
			
		||||
      for modifier_state in 0..256u32 {
 | 
			
		||||
        let code_with_offset = key_code + EVDEV_OFFSET;
 | 
			
		||||
        let mut event = XKeyEvent {
 | 
			
		||||
          display,
 | 
			
		||||
          keycode: code_with_offset,
 | 
			
		||||
          state: modifier_state,
 | 
			
		||||
 | 
			
		||||
          // These might not even need to be filled
 | 
			
		||||
          window: 0,
 | 
			
		||||
          root: 0,
 | 
			
		||||
          same_screen: 1,
 | 
			
		||||
          time: 0,
 | 
			
		||||
          type_: KeyPress,
 | 
			
		||||
          x_root: 1,
 | 
			
		||||
          y_root: 1,
 | 
			
		||||
          x: 1,
 | 
			
		||||
          y: 1,
 | 
			
		||||
          subwindow: 0,
 | 
			
		||||
          serial: 0,
 | 
			
		||||
          send_event: 0,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let filter = unsafe { XFilterEvent(&mut event, 0) };
 | 
			
		||||
        if filter == 1 {
 | 
			
		||||
          let mut sym: KeySym = 0;
 | 
			
		||||
          let mut buffer: [c_char; 10] = [0; 10];
 | 
			
		||||
 | 
			
		||||
          unsafe {
 | 
			
		||||
            Xutf8LookupString(
 | 
			
		||||
              *input_context,
 | 
			
		||||
              &mut event,
 | 
			
		||||
              buffer.as_mut_ptr(),
 | 
			
		||||
              (buffer.len() - 1) as i32,
 | 
			
		||||
              &mut sym,
 | 
			
		||||
              std::ptr::null_mut(),
 | 
			
		||||
            )
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          if sym != 0 && !seen_keysyms.contains(&sym) {
 | 
			
		||||
            let key_record = KeyPair {
 | 
			
		||||
              code: code_with_offset,
 | 
			
		||||
              state: modifier_state,
 | 
			
		||||
            };
 | 
			
		||||
            deadkeys.push(Some(key_record));
 | 
			
		||||
            seen_keysyms.insert(sym);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let _reset = unsafe { XmbResetIC(*input_context) };
 | 
			
		||||
        unsafe { XFree(_reset as *mut c_void) };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(deadkeys)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn convert_to_record_array(&self, syms: &[KeySym]) -> Result<Vec<KeyRecord>> {
 | 
			
		||||
    syms
 | 
			
		||||
      .iter()
 | 
			
		||||
      .map(|sym| {
 | 
			
		||||
        self
 | 
			
		||||
          .sym_map
 | 
			
		||||
          .get(sym)
 | 
			
		||||
          .cloned()
 | 
			
		||||
          .ok_or_else(|| X11InjectorError::SymMapping(*sym).into())
 | 
			
		||||
      })
 | 
			
		||||
      .collect()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // This method was inspired by the wonderful xdotool by Jordan Sissel
 | 
			
		||||
  // https://github.com/jordansissel/xdotool
 | 
			
		||||
  fn get_modifier_codes(&self) -> Vec<Vec<KeyCode>> {
 | 
			
		||||
    let modifiers_ptr = unsafe { XGetModifierMapping(self.display) };
 | 
			
		||||
    let modifiers = unsafe { *modifiers_ptr };
 | 
			
		||||
 | 
			
		||||
    let mut modifiers_codes = Vec::new();
 | 
			
		||||
 | 
			
		||||
    for mod_index in 0..=7 {
 | 
			
		||||
      let mut modifier_codes = Vec::new();
 | 
			
		||||
      for mod_key in 0..modifiers.max_keypermod {
 | 
			
		||||
        let modifier_map = unsafe {
 | 
			
		||||
          slice::from_raw_parts(
 | 
			
		||||
            modifiers.modifiermap,
 | 
			
		||||
            (8 * modifiers.max_keypermod) as usize,
 | 
			
		||||
          )
 | 
			
		||||
        };
 | 
			
		||||
        let keycode = modifier_map[(mod_index * modifiers.max_keypermod + mod_key) as usize];
 | 
			
		||||
        if keycode != 0 {
 | 
			
		||||
          modifier_codes.push(keycode);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      modifiers_codes.push(modifier_codes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    unsafe { XFreeModifiermap(modifiers_ptr) };
 | 
			
		||||
 | 
			
		||||
    modifiers_codes
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn render_key_combination(&self, original_records: &[KeyRecord]) -> Vec<KeyRecord> {
 | 
			
		||||
    let modifiers_codes = self.get_modifier_codes();
 | 
			
		||||
    let mut records = Vec::new();
 | 
			
		||||
 | 
			
		||||
    let mut current_state = 0u32;
 | 
			
		||||
    for record in original_records {
 | 
			
		||||
      let mut current_record = *record;
 | 
			
		||||
 | 
			
		||||
      // Render the state by applying the modifiers
 | 
			
		||||
      for (mod_index, modifier) in modifiers_codes.iter().enumerate() {
 | 
			
		||||
        if modifier.contains(&(record.main.code as u8)) {
 | 
			
		||||
          current_state |= 1 << mod_index;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      current_record.main.state = current_state;
 | 
			
		||||
      records.push(current_record);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    records
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn get_focused_window(&self) -> Window {
 | 
			
		||||
    let mut focused_window: Window = 0;
 | 
			
		||||
    let mut revert_to = 0;
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XGetInputFocus(self.display, &mut focused_window, &mut revert_to);
 | 
			
		||||
    }
 | 
			
		||||
    focused_window
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_key(&self, window: Window, record: &KeyPair, pressed: bool, delay_us: u32) {
 | 
			
		||||
    let root_window = unsafe { XDefaultRootWindow(self.display) };
 | 
			
		||||
    let mut event = XKeyEvent {
 | 
			
		||||
      display: self.display,
 | 
			
		||||
      keycode: record.code,
 | 
			
		||||
      state: record.state,
 | 
			
		||||
      window,
 | 
			
		||||
      root: root_window,
 | 
			
		||||
      same_screen: 1,
 | 
			
		||||
      time: 0,
 | 
			
		||||
      type_: if pressed { KeyPress } else { KeyRelease },
 | 
			
		||||
      x_root: 1,
 | 
			
		||||
      y_root: 1,
 | 
			
		||||
      x: 1,
 | 
			
		||||
      y: 1,
 | 
			
		||||
      subwindow: 0,
 | 
			
		||||
      serial: 0,
 | 
			
		||||
      send_event: 0,
 | 
			
		||||
    };
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XSendEvent(self.display, window, 1, 0, &mut event);
 | 
			
		||||
      XFlush(self.display);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if delay_us != 0 {
 | 
			
		||||
      unsafe {
 | 
			
		||||
        libc::usleep(delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn xtest_send_modifiers(&self, modmask: u32, pressed: bool) {
 | 
			
		||||
    let modifiers_codes = self.get_modifier_codes();
 | 
			
		||||
    for (mod_index, modifier_codes) in modifiers_codes.into_iter().enumerate() {
 | 
			
		||||
      if (modmask & (1 << mod_index)) != 0 {
 | 
			
		||||
        for keycode in modifier_codes {
 | 
			
		||||
          let is_press = if pressed { 1 } else { 0 };
 | 
			
		||||
          unsafe {
 | 
			
		||||
            XTestFakeKeyEvent(self.display, keycode as u32, is_press, 0);
 | 
			
		||||
            XSync(self.display, 0);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn xtest_send_key(&self, record: &KeyPair, pressed: bool, delay_us: u32) {
 | 
			
		||||
    // If the key requires any modifier, we need to send those events
 | 
			
		||||
    if record.state != 0 {
 | 
			
		||||
      self.xtest_send_modifiers(record.state, pressed);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let is_press = if pressed { 1 } else { 0 };
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XTestFakeKeyEvent(self.display, record.code, is_press, 0);
 | 
			
		||||
      XSync(self.display, 0);
 | 
			
		||||
      XFlush(self.display);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if delay_us != 0 {
 | 
			
		||||
      unsafe {
 | 
			
		||||
        libc::usleep(delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn xtest_release_all_keys(&self) {
 | 
			
		||||
    let mut keys: [u8; 32] = [0; 32];
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XQueryKeymap(self.display, keys.as_mut_ptr());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(clippy::needless_range_loop)]
 | 
			
		||||
    for i in 0..32 {
 | 
			
		||||
      // Only those that are pressed should be changed
 | 
			
		||||
      if keys[i] != 0 {
 | 
			
		||||
        for k in 0..8 {
 | 
			
		||||
          if (keys[i] & (1 << k)) != 0 {
 | 
			
		||||
            let key_code = i * 8 + k;
 | 
			
		||||
            unsafe {
 | 
			
		||||
              XTestFakeKeyEvent(self.display, key_code as u32, 0, 0);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Drop for X11DefaultInjector {
 | 
			
		||||
  fn drop(&mut self) {
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XCloseDisplay(self.display);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Injector for X11DefaultInjector {
 | 
			
		||||
  fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> {
 | 
			
		||||
    let focused_window = self.get_focused_window();
 | 
			
		||||
 | 
			
		||||
    if options.disable_fast_inject {
 | 
			
		||||
      self.xtest_release_all_keys();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Compute all the key record sequence first to make sure a mapping is available
 | 
			
		||||
    let records: Result<Vec<KeyRecord>> = string
 | 
			
		||||
      .chars()
 | 
			
		||||
      .map(|c| c.to_string())
 | 
			
		||||
      .map(|char| {
 | 
			
		||||
        self
 | 
			
		||||
          .char_map
 | 
			
		||||
          .get(&char)
 | 
			
		||||
          .cloned()
 | 
			
		||||
          .ok_or_else(|| X11InjectorError::CharMapping(char).into())
 | 
			
		||||
      })
 | 
			
		||||
      .collect();
 | 
			
		||||
 | 
			
		||||
    let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
 | 
			
		||||
 | 
			
		||||
    for record in records? {
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        if let Some(deadkey) = &record.preceding_dead_key {
 | 
			
		||||
          self.xtest_send_key(deadkey, true, delay_us);
 | 
			
		||||
          self.xtest_send_key(deadkey, false, delay_us);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.xtest_send_key(&record.main, true, delay_us);
 | 
			
		||||
        self.xtest_send_key(&record.main, false, delay_us);
 | 
			
		||||
      } else {
 | 
			
		||||
        if let Some(deadkey) = &record.preceding_dead_key {
 | 
			
		||||
          self.send_key(focused_window, deadkey, true, delay_us);
 | 
			
		||||
          self.send_key(focused_window, deadkey, false, delay_us);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.send_key(focused_window, &record.main, true, delay_us);
 | 
			
		||||
        self.send_key(focused_window, &record.main, false, delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
 | 
			
		||||
    let focused_window = self.get_focused_window();
 | 
			
		||||
 | 
			
		||||
    // Compute all the key record sequence first to make sure a mapping is available
 | 
			
		||||
    let syms = convert_to_sym_array(keys)?;
 | 
			
		||||
    let records = self.convert_to_record_array(&syms)?;
 | 
			
		||||
 | 
			
		||||
    if options.disable_fast_inject {
 | 
			
		||||
      self.xtest_release_all_keys();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
 | 
			
		||||
 | 
			
		||||
    for record in records {
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        self.xtest_send_key(&record.main, true, delay_us);
 | 
			
		||||
        self.xtest_send_key(&record.main, false, delay_us);
 | 
			
		||||
      } else {
 | 
			
		||||
        self.send_key(focused_window, &record.main, true, delay_us);
 | 
			
		||||
        self.send_key(focused_window, &record.main, false, delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
 | 
			
		||||
    let focused_window = self.get_focused_window();
 | 
			
		||||
 | 
			
		||||
    // Compute all the key record sequence first to make sure a mapping is available
 | 
			
		||||
    let syms = convert_to_sym_array(keys)?;
 | 
			
		||||
    let records = self.convert_to_record_array(&syms)?;
 | 
			
		||||
 | 
			
		||||
    // Render the correct modifier mask for the given sequence
 | 
			
		||||
    let records = self.render_key_combination(&records);
 | 
			
		||||
 | 
			
		||||
    if options.disable_fast_inject {
 | 
			
		||||
      self.xtest_release_all_keys();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
 | 
			
		||||
 | 
			
		||||
    // First press the keys
 | 
			
		||||
    for record in records.iter() {
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        self.xtest_send_key(&record.main, true, delay_us);
 | 
			
		||||
      } else {
 | 
			
		||||
        self.send_key(focused_window, &record.main, true, delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Then release them
 | 
			
		||||
    for record in records.iter().rev() {
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        self.xtest_send_key(&record.main, false, delay_us);
 | 
			
		||||
      } else {
 | 
			
		||||
        self.send_key(focused_window, &record.main, false, delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum X11InjectorError {
 | 
			
		||||
  #[error("failed to initialize x11 display")]
 | 
			
		||||
  Init(),
 | 
			
		||||
 | 
			
		||||
  #[error("missing vkey mapping for char `{0}`")]
 | 
			
		||||
  CharMapping(String),
 | 
			
		||||
 | 
			
		||||
  #[error("missing record mapping for sym `{0}`")]
 | 
			
		||||
  SymMapping(u64),
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ use std::{
 | 
			
		|||
  os::raw::{c_char, c_long, c_uint, c_ulong},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use libc::c_int;
 | 
			
		||||
use libc::{c_int, c_uchar};
 | 
			
		||||
 | 
			
		||||
pub enum Display {}
 | 
			
		||||
pub type Window = u64;
 | 
			
		||||
| 
						 | 
				
			
			@ -123,4 +123,6 @@ extern "C" {
 | 
			
		|||
  pub fn XFilterEvent(event: *mut XKeyEvent, window: c_ulong) -> c_int;
 | 
			
		||||
  pub fn XCloseIM(input_method: XIM) -> c_int;
 | 
			
		||||
  pub fn XFree(data: *mut c_void) -> c_int;
 | 
			
		||||
  pub fn XKeycodeToKeysym(display: *mut Display, keycode: c_uchar, index: c_int) -> c_ulong;
 | 
			
		||||
  pub fn XKeysymToString(keysym: c_ulong) -> *mut c_char;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,584 +17,89 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use crate::Injector;
 | 
			
		||||
 | 
			
		||||
use anyhow::{bail, ensure, Result};
 | 
			
		||||
use log::{error, warn};
 | 
			
		||||
 | 
			
		||||
mod default;
 | 
			
		||||
mod ffi;
 | 
			
		||||
mod xdotool;
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
  collections::{HashMap, HashSet},
 | 
			
		||||
  ffi::{CStr, CString},
 | 
			
		||||
  os::raw::c_char,
 | 
			
		||||
  slice,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use ffi::{
 | 
			
		||||
  Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow,
 | 
			
		||||
  XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XQueryKeymap,
 | 
			
		||||
  XSendEvent, XSync, XTestFakeKeyEvent,
 | 
			
		||||
};
 | 
			
		||||
use libc::c_void;
 | 
			
		||||
use log::{debug, error};
 | 
			
		||||
 | 
			
		||||
use crate::{linux::raw_keys::convert_to_sym_array, x11::ffi::Xutf8LookupString};
 | 
			
		||||
use anyhow::{bail, Result};
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
use crate::{keys, InjectionOptions, Injector};
 | 
			
		||||
 | 
			
		||||
use self::ffi::{
 | 
			
		||||
  XCloseIM, XCreateIC, XDestroyIC, XFilterEvent, XFree, XIMPreeditNothing, XIMStatusNothing,
 | 
			
		||||
  XNClientWindow_0, XNInputStyle_0, XOpenIM, XmbResetIC, XIC,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
 | 
			
		||||
// keycode set (where ESC is 9).
 | 
			
		||||
const EVDEV_OFFSET: u32 = 8;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug)]
 | 
			
		||||
struct KeyPair {
 | 
			
		||||
  // Keycode
 | 
			
		||||
  code: u32,
 | 
			
		||||
  // Modifier state which combined with the code produces the char
 | 
			
		||||
  // This is a bit mask:
 | 
			
		||||
  state: u32,
 | 
			
		||||
pub struct X11ProxyInjector {
 | 
			
		||||
  default_injector: Option<default::X11DefaultInjector>,
 | 
			
		||||
  xdotool_injector: Option<xdotool::X11XDOToolInjector>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug)]
 | 
			
		||||
struct KeyRecord {
 | 
			
		||||
  main: KeyPair,
 | 
			
		||||
 | 
			
		||||
  // Under some keyboard layouts (de, es), a deadkey
 | 
			
		||||
  // press might be needed to generate the right char
 | 
			
		||||
  preceding_dead_key: Option<KeyPair>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CharMap = HashMap<String, KeyRecord>;
 | 
			
		||||
type SymMap = HashMap<KeySym, KeyRecord>;
 | 
			
		||||
 | 
			
		||||
pub struct X11Injector {
 | 
			
		||||
  display: *mut Display,
 | 
			
		||||
 | 
			
		||||
  char_map: CharMap,
 | 
			
		||||
  sym_map: SymMap,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::new_without_default)]
 | 
			
		||||
impl X11Injector {
 | 
			
		||||
impl X11ProxyInjector {
 | 
			
		||||
  pub fn new() -> Result<Self> {
 | 
			
		||||
    // Necessary to properly handle non-ascii chars
 | 
			
		||||
    let empty_string = CString::new("")?;
 | 
			
		||||
    unsafe {
 | 
			
		||||
      libc::setlocale(libc::LC_ALL, empty_string.as_ptr());
 | 
			
		||||
    let default_injector = match default::X11DefaultInjector::new() {
 | 
			
		||||
      Ok(injector) => Some(injector),
 | 
			
		||||
      Err(err) => {
 | 
			
		||||
        error!("X11DefaultInjector could not be initialized: {:?}", err);
 | 
			
		||||
        warn!("falling back to xdotool injector");
 | 
			
		||||
        None
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let xdotool_injector = match xdotool::X11XDOToolInjector::new() {
 | 
			
		||||
      Ok(injector) => Some(injector),
 | 
			
		||||
      Err(err) => {
 | 
			
		||||
        error!("X11XDOToolInjector could not be initialized: {:?}", err);
 | 
			
		||||
        None
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if default_injector.is_none() && xdotool_injector.is_none() {
 | 
			
		||||
      bail!("unable to initialize injectors, neither the default or xdotool fallback could be initialized");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let display = unsafe { ffi::XOpenDisplay(std::ptr::null()) };
 | 
			
		||||
    if display.is_null() {
 | 
			
		||||
      return Err(X11InjectorError::Init().into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let (char_map, sym_map) = Self::generate_maps(display)?;
 | 
			
		||||
 | 
			
		||||
    Ok(Self {
 | 
			
		||||
      display,
 | 
			
		||||
      char_map,
 | 
			
		||||
      sym_map,
 | 
			
		||||
    Ok(X11ProxyInjector {
 | 
			
		||||
      default_injector,
 | 
			
		||||
      xdotool_injector,
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn generate_maps(display: *mut Display) -> Result<(CharMap, SymMap)> {
 | 
			
		||||
    debug!("generating key maps");
 | 
			
		||||
  fn get_active_injector(&self, options: &crate::InjectionOptions) -> Result<&dyn Injector> {
 | 
			
		||||
    ensure!(
 | 
			
		||||
      self.default_injector.is_some() || self.xdotool_injector.is_some(),
 | 
			
		||||
      "unable to get active injector, neither default or xdotool fallback are available."
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let mut char_map = HashMap::new();
 | 
			
		||||
    let mut sym_map = HashMap::new();
 | 
			
		||||
 | 
			
		||||
    let input_method = unsafe {
 | 
			
		||||
      XOpenIM(
 | 
			
		||||
        display,
 | 
			
		||||
        std::ptr::null_mut(),
 | 
			
		||||
        std::ptr::null_mut(),
 | 
			
		||||
        std::ptr::null_mut(),
 | 
			
		||||
      )
 | 
			
		||||
    };
 | 
			
		||||
    if input_method.is_null() {
 | 
			
		||||
      bail!("could not open input method");
 | 
			
		||||
    }
 | 
			
		||||
    let _im_guard = scopeguard::guard((), |_| {
 | 
			
		||||
      unsafe { XCloseIM(input_method) };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let input_context = unsafe {
 | 
			
		||||
      XCreateIC(
 | 
			
		||||
        input_method,
 | 
			
		||||
        XNInputStyle_0.as_ptr(),
 | 
			
		||||
        XIMPreeditNothing | XIMStatusNothing,
 | 
			
		||||
        XNClientWindow_0.as_ptr(),
 | 
			
		||||
        0,
 | 
			
		||||
        std::ptr::null_mut(),
 | 
			
		||||
      )
 | 
			
		||||
    };
 | 
			
		||||
    if input_context.is_null() {
 | 
			
		||||
      bail!("could not open input context");
 | 
			
		||||
    }
 | 
			
		||||
    let _ic_guard = scopeguard::guard((), |_| {
 | 
			
		||||
      unsafe { XDestroyIC(input_context) };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let deadkeys = Self::find_deadkeys(display, &input_context)?;
 | 
			
		||||
 | 
			
		||||
    // Cycle through all state/code combinations to populate the reverse lookup tables
 | 
			
		||||
    for key_code in 0..256u32 {
 | 
			
		||||
      for modifier_state in 0..256u32 {
 | 
			
		||||
        for dead_key in deadkeys.iter() {
 | 
			
		||||
          let code_with_offset = key_code + EVDEV_OFFSET;
 | 
			
		||||
 | 
			
		||||
          let preceding_dead_key = if let Some(dead_key) = dead_key {
 | 
			
		||||
            let mut dead_key_event = XKeyEvent {
 | 
			
		||||
              display,
 | 
			
		||||
              keycode: dead_key.code,
 | 
			
		||||
              state: dead_key.state,
 | 
			
		||||
 | 
			
		||||
              // These might not even need to be filled
 | 
			
		||||
              window: 0,
 | 
			
		||||
              root: 0,
 | 
			
		||||
              same_screen: 1,
 | 
			
		||||
              time: 0,
 | 
			
		||||
              type_: KeyPress,
 | 
			
		||||
              x_root: 1,
 | 
			
		||||
              y_root: 1,
 | 
			
		||||
              x: 1,
 | 
			
		||||
              y: 1,
 | 
			
		||||
              subwindow: 0,
 | 
			
		||||
              serial: 0,
 | 
			
		||||
              send_event: 0,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            unsafe { XFilterEvent(&mut dead_key_event, 0) };
 | 
			
		||||
 | 
			
		||||
            Some(*dead_key)
 | 
			
		||||
          } else {
 | 
			
		||||
            None
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          let mut key_event = XKeyEvent {
 | 
			
		||||
            display,
 | 
			
		||||
            keycode: code_with_offset,
 | 
			
		||||
            state: modifier_state,
 | 
			
		||||
 | 
			
		||||
            // These might not even need to be filled
 | 
			
		||||
            window: 0,
 | 
			
		||||
            root: 0,
 | 
			
		||||
            same_screen: 1,
 | 
			
		||||
            time: 0,
 | 
			
		||||
            type_: KeyPress,
 | 
			
		||||
            x_root: 1,
 | 
			
		||||
            y_root: 1,
 | 
			
		||||
            x: 1,
 | 
			
		||||
            y: 1,
 | 
			
		||||
            subwindow: 0,
 | 
			
		||||
            serial: 0,
 | 
			
		||||
            send_event: 0,
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          unsafe { XFilterEvent(&mut key_event, 0) };
 | 
			
		||||
          let mut sym: KeySym = 0;
 | 
			
		||||
          let mut buffer: [c_char; 10] = [0; 10];
 | 
			
		||||
 | 
			
		||||
          let result = unsafe {
 | 
			
		||||
            Xutf8LookupString(
 | 
			
		||||
              input_context,
 | 
			
		||||
              &mut key_event,
 | 
			
		||||
              buffer.as_mut_ptr(),
 | 
			
		||||
              (buffer.len() - 1) as i32,
 | 
			
		||||
              &mut sym,
 | 
			
		||||
              std::ptr::null_mut(),
 | 
			
		||||
            )
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          let key_record = KeyRecord {
 | 
			
		||||
            main: KeyPair {
 | 
			
		||||
              code: code_with_offset,
 | 
			
		||||
              state: modifier_state,
 | 
			
		||||
            },
 | 
			
		||||
            preceding_dead_key,
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // Keysym was found
 | 
			
		||||
          if sym != 0 {
 | 
			
		||||
            sym_map.entry(sym).or_insert(key_record);
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // Char was found
 | 
			
		||||
          if result > 0 {
 | 
			
		||||
            let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) };
 | 
			
		||||
            let string = raw_string.to_string_lossy().to_string();
 | 
			
		||||
            char_map.entry(string).or_insert(key_record);
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // We need to reset the context state to prevent
 | 
			
		||||
          // deadkeys effect to propagate to the next combination
 | 
			
		||||
          let _reset = unsafe { XmbResetIC(input_context) };
 | 
			
		||||
          unsafe { XFree(_reset as *mut c_void) };
 | 
			
		||||
        }
 | 
			
		||||
    if options.x11_use_xdotool_fallback {
 | 
			
		||||
      if let Some(xdotool_injector) = self.xdotool_injector.as_ref() {
 | 
			
		||||
        return Ok(xdotool_injector);
 | 
			
		||||
      } else if let Some(default_injector) = self.default_injector.as_ref() {
 | 
			
		||||
        return Ok(default_injector);
 | 
			
		||||
      }
 | 
			
		||||
    } else if let Some(default_injector) = self.default_injector.as_ref() {
 | 
			
		||||
      return Ok(default_injector);
 | 
			
		||||
    } else if let Some(xdotool_injector) = self.xdotool_injector.as_ref() {
 | 
			
		||||
      return Ok(xdotool_injector);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    debug!("Populated char_map with {} symbols", char_map.len());
 | 
			
		||||
    debug!("Populated sym_map with {} symbols", sym_map.len());
 | 
			
		||||
    debug!("Detected {} dead key combinations", deadkeys.len());
 | 
			
		||||
 | 
			
		||||
    Ok((char_map, sym_map))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn find_deadkeys(display: *mut Display, input_context: &XIC) -> Result<Vec<Option<KeyPair>>> {
 | 
			
		||||
    let mut deadkeys = vec![None];
 | 
			
		||||
    let mut seen_keysyms: HashSet<KeySym> = HashSet::new();
 | 
			
		||||
 | 
			
		||||
    // Cycle through all state/code combinations to populate the reverse lookup tables
 | 
			
		||||
    for key_code in 0..256u32 {
 | 
			
		||||
      for modifier_state in 0..256u32 {
 | 
			
		||||
        let code_with_offset = key_code + EVDEV_OFFSET;
 | 
			
		||||
        let mut event = XKeyEvent {
 | 
			
		||||
          display,
 | 
			
		||||
          keycode: code_with_offset,
 | 
			
		||||
          state: modifier_state,
 | 
			
		||||
 | 
			
		||||
          // These might not even need to be filled
 | 
			
		||||
          window: 0,
 | 
			
		||||
          root: 0,
 | 
			
		||||
          same_screen: 1,
 | 
			
		||||
          time: 0,
 | 
			
		||||
          type_: KeyPress,
 | 
			
		||||
          x_root: 1,
 | 
			
		||||
          y_root: 1,
 | 
			
		||||
          x: 1,
 | 
			
		||||
          y: 1,
 | 
			
		||||
          subwindow: 0,
 | 
			
		||||
          serial: 0,
 | 
			
		||||
          send_event: 0,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let filter = unsafe { XFilterEvent(&mut event, 0) };
 | 
			
		||||
        if filter == 1 {
 | 
			
		||||
          let mut sym: KeySym = 0;
 | 
			
		||||
          let mut buffer: [c_char; 10] = [0; 10];
 | 
			
		||||
 | 
			
		||||
          unsafe {
 | 
			
		||||
            Xutf8LookupString(
 | 
			
		||||
              *input_context,
 | 
			
		||||
              &mut event,
 | 
			
		||||
              buffer.as_mut_ptr(),
 | 
			
		||||
              (buffer.len() - 1) as i32,
 | 
			
		||||
              &mut sym,
 | 
			
		||||
              std::ptr::null_mut(),
 | 
			
		||||
            )
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          if sym != 0 && !seen_keysyms.contains(&sym) {
 | 
			
		||||
            let key_record = KeyPair {
 | 
			
		||||
              code: code_with_offset,
 | 
			
		||||
              state: modifier_state,
 | 
			
		||||
            };
 | 
			
		||||
            deadkeys.push(Some(key_record));
 | 
			
		||||
            seen_keysyms.insert(sym);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let _reset = unsafe { XmbResetIC(*input_context) };
 | 
			
		||||
        unsafe { XFree(_reset as *mut c_void) };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(deadkeys)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn convert_to_record_array(&self, syms: &[KeySym]) -> Result<Vec<KeyRecord>> {
 | 
			
		||||
    syms
 | 
			
		||||
      .iter()
 | 
			
		||||
      .map(|sym| {
 | 
			
		||||
        self
 | 
			
		||||
          .sym_map
 | 
			
		||||
          .get(sym)
 | 
			
		||||
          .cloned()
 | 
			
		||||
          .ok_or_else(|| X11InjectorError::SymMapping(*sym).into())
 | 
			
		||||
      })
 | 
			
		||||
      .collect()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // This method was inspired by the wonderful xdotool by Jordan Sissel
 | 
			
		||||
  // https://github.com/jordansissel/xdotool
 | 
			
		||||
  fn get_modifier_codes(&self) -> Vec<Vec<KeyCode>> {
 | 
			
		||||
    let modifiers_ptr = unsafe { XGetModifierMapping(self.display) };
 | 
			
		||||
    let modifiers = unsafe { *modifiers_ptr };
 | 
			
		||||
 | 
			
		||||
    let mut modifiers_codes = Vec::new();
 | 
			
		||||
 | 
			
		||||
    for mod_index in 0..=7 {
 | 
			
		||||
      let mut modifier_codes = Vec::new();
 | 
			
		||||
      for mod_key in 0..modifiers.max_keypermod {
 | 
			
		||||
        let modifier_map = unsafe {
 | 
			
		||||
          slice::from_raw_parts(
 | 
			
		||||
            modifiers.modifiermap,
 | 
			
		||||
            (8 * modifiers.max_keypermod) as usize,
 | 
			
		||||
          )
 | 
			
		||||
        };
 | 
			
		||||
        let keycode = modifier_map[(mod_index * modifiers.max_keypermod + mod_key) as usize];
 | 
			
		||||
        if keycode != 0 {
 | 
			
		||||
          modifier_codes.push(keycode);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      modifiers_codes.push(modifier_codes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    unsafe { XFreeModifiermap(modifiers_ptr) };
 | 
			
		||||
 | 
			
		||||
    modifiers_codes
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn render_key_combination(&self, original_records: &[KeyRecord]) -> Vec<KeyRecord> {
 | 
			
		||||
    let modifiers_codes = self.get_modifier_codes();
 | 
			
		||||
    let mut records = Vec::new();
 | 
			
		||||
 | 
			
		||||
    let mut current_state = 0u32;
 | 
			
		||||
    for record in original_records {
 | 
			
		||||
      let mut current_record = *record;
 | 
			
		||||
 | 
			
		||||
      // Render the state by applying the modifiers
 | 
			
		||||
      for (mod_index, modifier) in modifiers_codes.iter().enumerate() {
 | 
			
		||||
        if modifier.contains(&(record.main.code as u8)) {
 | 
			
		||||
          current_state |= 1 << mod_index;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      current_record.main.state = current_state;
 | 
			
		||||
      records.push(current_record);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    records
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn get_focused_window(&self) -> Window {
 | 
			
		||||
    let mut focused_window: Window = 0;
 | 
			
		||||
    let mut revert_to = 0;
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XGetInputFocus(self.display, &mut focused_window, &mut revert_to);
 | 
			
		||||
    }
 | 
			
		||||
    focused_window
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_key(&self, window: Window, record: &KeyPair, pressed: bool, delay_us: u32) {
 | 
			
		||||
    let root_window = unsafe { XDefaultRootWindow(self.display) };
 | 
			
		||||
    let mut event = XKeyEvent {
 | 
			
		||||
      display: self.display,
 | 
			
		||||
      keycode: record.code,
 | 
			
		||||
      state: record.state,
 | 
			
		||||
      window,
 | 
			
		||||
      root: root_window,
 | 
			
		||||
      same_screen: 1,
 | 
			
		||||
      time: 0,
 | 
			
		||||
      type_: if pressed { KeyPress } else { KeyRelease },
 | 
			
		||||
      x_root: 1,
 | 
			
		||||
      y_root: 1,
 | 
			
		||||
      x: 1,
 | 
			
		||||
      y: 1,
 | 
			
		||||
      subwindow: 0,
 | 
			
		||||
      serial: 0,
 | 
			
		||||
      send_event: 0,
 | 
			
		||||
    };
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XSendEvent(self.display, window, 1, 0, &mut event);
 | 
			
		||||
      XFlush(self.display);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if delay_us != 0 {
 | 
			
		||||
      unsafe {
 | 
			
		||||
        libc::usleep(delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn xtest_send_modifiers(&self, modmask: u32, pressed: bool) {
 | 
			
		||||
    let modifiers_codes = self.get_modifier_codes();
 | 
			
		||||
    for (mod_index, modifier_codes) in modifiers_codes.into_iter().enumerate() {
 | 
			
		||||
      if (modmask & (1 << mod_index)) != 0 {
 | 
			
		||||
        for keycode in modifier_codes {
 | 
			
		||||
          let is_press = if pressed { 1 } else { 0 };
 | 
			
		||||
          unsafe {
 | 
			
		||||
            XTestFakeKeyEvent(self.display, keycode as u32, is_press, 0);
 | 
			
		||||
            XSync(self.display, 0);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn xtest_send_key(&self, record: &KeyPair, pressed: bool, delay_us: u32) {
 | 
			
		||||
    // If the key requires any modifier, we need to send those events
 | 
			
		||||
    if record.state != 0 {
 | 
			
		||||
      self.xtest_send_modifiers(record.state, pressed);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let is_press = if pressed { 1 } else { 0 };
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XTestFakeKeyEvent(self.display, record.code, is_press, 0);
 | 
			
		||||
      XSync(self.display, 0);
 | 
			
		||||
      XFlush(self.display);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if delay_us != 0 {
 | 
			
		||||
      unsafe {
 | 
			
		||||
        libc::usleep(delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn xtest_release_all_keys(&self) {
 | 
			
		||||
    let mut keys: [u8; 32] = [0; 32];
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XQueryKeymap(self.display, keys.as_mut_ptr());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(clippy::needless_range_loop)]
 | 
			
		||||
    for i in 0..32 {
 | 
			
		||||
      // Only those that are pressed should be changed
 | 
			
		||||
      if keys[i] != 0 {
 | 
			
		||||
        for k in 0..8 {
 | 
			
		||||
          if (keys[i] & (1 << k)) != 0 {
 | 
			
		||||
            let key_code = i * 8 + k;
 | 
			
		||||
            unsafe {
 | 
			
		||||
              XTestFakeKeyEvent(self.display, key_code as u32, 0, 0);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    unreachable!()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Drop for X11Injector {
 | 
			
		||||
  fn drop(&mut self) {
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XCloseDisplay(self.display);
 | 
			
		||||
    }
 | 
			
		||||
impl Injector for X11ProxyInjector {
 | 
			
		||||
  fn send_string(&self, string: &str, options: crate::InjectionOptions) -> Result<()> {
 | 
			
		||||
    self
 | 
			
		||||
      .get_active_injector(&options)?
 | 
			
		||||
      .send_string(string, options)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_keys(&self, keys: &[crate::keys::Key], options: crate::InjectionOptions) -> Result<()> {
 | 
			
		||||
    self.get_active_injector(&options)?.send_keys(keys, options)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_key_combination(
 | 
			
		||||
    &self,
 | 
			
		||||
    keys: &[crate::keys::Key],
 | 
			
		||||
    options: crate::InjectionOptions,
 | 
			
		||||
  ) -> Result<()> {
 | 
			
		||||
    self
 | 
			
		||||
      .get_active_injector(&options)?
 | 
			
		||||
      .send_key_combination(keys, options)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Injector for X11Injector {
 | 
			
		||||
  fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> {
 | 
			
		||||
    let focused_window = self.get_focused_window();
 | 
			
		||||
 | 
			
		||||
    if options.disable_fast_inject {
 | 
			
		||||
      self.xtest_release_all_keys();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Compute all the key record sequence first to make sure a mapping is available
 | 
			
		||||
    let records: Result<Vec<KeyRecord>> = string
 | 
			
		||||
      .chars()
 | 
			
		||||
      .map(|c| c.to_string())
 | 
			
		||||
      .map(|char| {
 | 
			
		||||
        self
 | 
			
		||||
          .char_map
 | 
			
		||||
          .get(&char)
 | 
			
		||||
          .cloned()
 | 
			
		||||
          .ok_or_else(|| X11InjectorError::CharMapping(char).into())
 | 
			
		||||
      })
 | 
			
		||||
      .collect();
 | 
			
		||||
 | 
			
		||||
    let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
 | 
			
		||||
 | 
			
		||||
    for record in records? {
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        if let Some(deadkey) = &record.preceding_dead_key {
 | 
			
		||||
          self.xtest_send_key(deadkey, true, delay_us);
 | 
			
		||||
          self.xtest_send_key(deadkey, false, delay_us);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.xtest_send_key(&record.main, true, delay_us);
 | 
			
		||||
        self.xtest_send_key(&record.main, false, delay_us);
 | 
			
		||||
      } else {
 | 
			
		||||
        if let Some(deadkey) = &record.preceding_dead_key {
 | 
			
		||||
          self.send_key(focused_window, deadkey, true, delay_us);
 | 
			
		||||
          self.send_key(focused_window, deadkey, false, delay_us);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.send_key(focused_window, &record.main, true, delay_us);
 | 
			
		||||
        self.send_key(focused_window, &record.main, false, delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
 | 
			
		||||
    let focused_window = self.get_focused_window();
 | 
			
		||||
 | 
			
		||||
    // Compute all the key record sequence first to make sure a mapping is available
 | 
			
		||||
    let syms = convert_to_sym_array(keys)?;
 | 
			
		||||
    let records = self.convert_to_record_array(&syms)?;
 | 
			
		||||
 | 
			
		||||
    if options.disable_fast_inject {
 | 
			
		||||
      self.xtest_release_all_keys();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
 | 
			
		||||
 | 
			
		||||
    for record in records {
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        self.xtest_send_key(&record.main, true, delay_us);
 | 
			
		||||
        self.xtest_send_key(&record.main, false, delay_us);
 | 
			
		||||
      } else {
 | 
			
		||||
        self.send_key(focused_window, &record.main, true, delay_us);
 | 
			
		||||
        self.send_key(focused_window, &record.main, false, delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
 | 
			
		||||
    let focused_window = self.get_focused_window();
 | 
			
		||||
 | 
			
		||||
    // Compute all the key record sequence first to make sure a mapping is available
 | 
			
		||||
    let syms = convert_to_sym_array(keys)?;
 | 
			
		||||
    let records = self.convert_to_record_array(&syms)?;
 | 
			
		||||
 | 
			
		||||
    // Render the correct modifier mask for the given sequence
 | 
			
		||||
    let records = self.render_key_combination(&records);
 | 
			
		||||
 | 
			
		||||
    if options.disable_fast_inject {
 | 
			
		||||
      self.xtest_release_all_keys();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
 | 
			
		||||
 | 
			
		||||
    // First press the keys
 | 
			
		||||
    for record in records.iter() {
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        self.xtest_send_key(&record.main, true, delay_us);
 | 
			
		||||
      } else {
 | 
			
		||||
        self.send_key(focused_window, &record.main, true, delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Then release them
 | 
			
		||||
    for record in records.iter().rev() {
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        self.xtest_send_key(&record.main, false, delay_us);
 | 
			
		||||
      } else {
 | 
			
		||||
        self.send_key(focused_window, &record.main, false, delay_us);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum X11InjectorError {
 | 
			
		||||
  #[error("failed to initialize x11 display")]
 | 
			
		||||
  Init(),
 | 
			
		||||
 | 
			
		||||
  #[error("missing vkey mapping for char `{0}`")]
 | 
			
		||||
  CharMapping(String),
 | 
			
		||||
 | 
			
		||||
  #[error("missing record mapping for sym `{0}`")]
 | 
			
		||||
  SymMapping(u64),
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								espanso-inject/src/x11/xdotool/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								espanso-inject/src/x11/xdotool/README.md
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
This is a fallback injection module that relies on the awesome xdotool project: 
 | 
			
		||||
https://github.com/jordansissel/xdotool
 | 
			
		||||
							
								
								
									
										61
									
								
								espanso-inject/src/x11/xdotool/ffi.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								espanso-inject/src/x11/xdotool/ffi.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
use libc::{c_char, c_int, c_long, useconds_t, wchar_t};
 | 
			
		||||
 | 
			
		||||
use crate::x11::ffi::{Display, Window};
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
pub struct charcodemap_t {
 | 
			
		||||
  pub key: wchar_t,
 | 
			
		||||
  pub code: c_char,
 | 
			
		||||
  pub symbol: c_long,
 | 
			
		||||
  pub group: c_int,
 | 
			
		||||
  pub modmask: c_int,
 | 
			
		||||
  pub needs_binding: c_int,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
pub struct xdo_t {
 | 
			
		||||
  pub xdpy: *mut Display,
 | 
			
		||||
  pub display_name: *const c_char,
 | 
			
		||||
  pub charcodes: *const charcodemap_t,
 | 
			
		||||
  pub charcodes_len: c_int,
 | 
			
		||||
  pub keycode_high: c_int,
 | 
			
		||||
  pub keycode_low: c_int,
 | 
			
		||||
  pub keysyms_per_keycode: c_int,
 | 
			
		||||
  pub close_display_when_freed: c_int,
 | 
			
		||||
  pub quiet: c_int,
 | 
			
		||||
  pub debug: c_int,
 | 
			
		||||
  pub features_mask: c_int,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const CURRENTWINDOW: u64 = 0;
 | 
			
		||||
 | 
			
		||||
#[link(name = "xdotoolvendor", kind = "static")]
 | 
			
		||||
extern "C" {
 | 
			
		||||
  pub fn xdo_new(display: *const c_char) -> *mut xdo_t;
 | 
			
		||||
  pub fn xdo_free(xdo: *const xdo_t);
 | 
			
		||||
  pub fn xdo_enter_text_window(
 | 
			
		||||
    xdo: *const xdo_t,
 | 
			
		||||
    window: Window,
 | 
			
		||||
    string: *const c_char,
 | 
			
		||||
    delay: useconds_t,
 | 
			
		||||
  );
 | 
			
		||||
  pub fn xdo_send_keysequence_window(
 | 
			
		||||
    xdo: *const xdo_t,
 | 
			
		||||
    window: Window,
 | 
			
		||||
    keysequence: *const c_char,
 | 
			
		||||
    delay: useconds_t,
 | 
			
		||||
  );
 | 
			
		||||
  pub fn fast_send_event(xdo: *const xdo_t, window: Window, keycode: c_int, pressed: c_int);
 | 
			
		||||
  pub fn fast_enter_text_window(
 | 
			
		||||
    xdo: *const xdo_t,
 | 
			
		||||
    window: Window,
 | 
			
		||||
    string: *const c_char,
 | 
			
		||||
    delay: useconds_t,
 | 
			
		||||
  );
 | 
			
		||||
  pub fn fast_send_keysequence_window(
 | 
			
		||||
    xdo: *const xdo_t,
 | 
			
		||||
    window: Window,
 | 
			
		||||
    keysequence: *const c_char,
 | 
			
		||||
    delay: useconds_t,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										348
									
								
								espanso-inject/src/x11/xdotool/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								espanso-inject/src/x11/xdotool/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,348 @@
 | 
			
		|||
/*
 | 
			
		||||
 * This file is part of espanso.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2019-2022 Federico Terzi
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
  convert::TryInto,
 | 
			
		||||
  ffi::{CStr, CString},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::Injector;
 | 
			
		||||
use anyhow::{bail, Context, Result};
 | 
			
		||||
use log::debug;
 | 
			
		||||
 | 
			
		||||
mod ffi;
 | 
			
		||||
use self::ffi::{fast_send_keysequence_window, xdo_send_keysequence_window, xdo_t, CURRENTWINDOW};
 | 
			
		||||
 | 
			
		||||
use super::ffi::{
 | 
			
		||||
  Display, Window, XGetInputFocus, XKeycodeToKeysym, XKeysymToString, XQueryKeymap,
 | 
			
		||||
  XTestFakeKeyEvent,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub struct X11XDOToolInjector {
 | 
			
		||||
  xdo: *const xdo_t,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl X11XDOToolInjector {
 | 
			
		||||
  pub fn new() -> Result<Self> {
 | 
			
		||||
    let xdo = unsafe { ffi::xdo_new(std::ptr::null()) };
 | 
			
		||||
    if xdo.is_null() {
 | 
			
		||||
      bail!("unable to initialize xdo_t instance");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    debug!("initialized xdo_t object");
 | 
			
		||||
 | 
			
		||||
    Ok(Self { xdo })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn xfake_release_all_keys(&self) {
 | 
			
		||||
    let mut keys: [u8; 32] = [0; 32];
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XQueryKeymap((*self.xdo).xdpy, keys.as_mut_ptr());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(clippy::needless_range_loop)]
 | 
			
		||||
    for i in 0..32 {
 | 
			
		||||
      // Only those that are pressed should be changed
 | 
			
		||||
      if keys[i] != 0 {
 | 
			
		||||
        for k in 0..8 {
 | 
			
		||||
          if (keys[i] & (1 << k)) != 0 {
 | 
			
		||||
            let key_code = i * 8 + k;
 | 
			
		||||
            unsafe {
 | 
			
		||||
              XTestFakeKeyEvent((*self.xdo).xdpy, key_code as u32, 0, 0);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn get_focused_window(&self) -> Window {
 | 
			
		||||
    let mut focused_window: Window = 0;
 | 
			
		||||
    let mut revert_to = 0;
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XGetInputFocus((*self.xdo).xdpy, &mut focused_window, &mut revert_to);
 | 
			
		||||
    }
 | 
			
		||||
    focused_window
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn xfake_send_string(
 | 
			
		||||
    &self,
 | 
			
		||||
    string: &str,
 | 
			
		||||
    options: crate::InjectionOptions,
 | 
			
		||||
  ) -> anyhow::Result<()> {
 | 
			
		||||
    // It may happen that when an expansion is triggered, some keys are still pressed.
 | 
			
		||||
    // This causes a problem if the expanded match contains that character, as the injection
 | 
			
		||||
    // will not be able to register that keypress (as it is already pressed).
 | 
			
		||||
    // To solve the problem, before an expansion we get which keys are currently pressed
 | 
			
		||||
    // and inject a key_release event so that they can be further registered.
 | 
			
		||||
    self.xfake_release_all_keys();
 | 
			
		||||
 | 
			
		||||
    let c_string = CString::new(string).context("unable to create CString")?;
 | 
			
		||||
    let delay = options.delay * 1000;
 | 
			
		||||
 | 
			
		||||
    unsafe {
 | 
			
		||||
      ffi::xdo_enter_text_window(
 | 
			
		||||
        self.xdo,
 | 
			
		||||
        CURRENTWINDOW,
 | 
			
		||||
        c_string.as_ptr(),
 | 
			
		||||
        delay.try_into().unwrap(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn fast_release_all_keys(&self) {
 | 
			
		||||
    let mut keys: [u8; 32] = [0; 32];
 | 
			
		||||
    unsafe {
 | 
			
		||||
      XQueryKeymap((*self.xdo).xdpy, keys.as_mut_ptr());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let focused_window = self.get_focused_window();
 | 
			
		||||
 | 
			
		||||
    #[allow(clippy::needless_range_loop)]
 | 
			
		||||
    for i in 0..32 {
 | 
			
		||||
      // Only those that are pressed should be changed
 | 
			
		||||
      if keys[i] != 0 {
 | 
			
		||||
        for k in 0..8 {
 | 
			
		||||
          if (keys[i] & (1 << k)) != 0 {
 | 
			
		||||
            let key_code = i * 8 + k;
 | 
			
		||||
            unsafe {
 | 
			
		||||
              ffi::fast_send_event(self.xdo, focused_window, key_code.try_into().unwrap(), 0);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn fast_send_string(&self, string: &str, options: crate::InjectionOptions) -> anyhow::Result<()> {
 | 
			
		||||
    // It may happen that when an expansion is triggered, some keys are still pressed.
 | 
			
		||||
    // This causes a problem if the expanded match contains that character, as the injection
 | 
			
		||||
    // will not be able to register that keypress (as it is already pressed).
 | 
			
		||||
    // To solve the problem, before an expansion we get which keys are currently pressed
 | 
			
		||||
    // and inject a key_release event so that they can be further registered.
 | 
			
		||||
    self.fast_release_all_keys();
 | 
			
		||||
 | 
			
		||||
    let c_string = CString::new(string).context("unable to create CString")?;
 | 
			
		||||
    let delay = options.delay * 1000;
 | 
			
		||||
 | 
			
		||||
    unsafe {
 | 
			
		||||
      ffi::fast_enter_text_window(
 | 
			
		||||
        self.xdo,
 | 
			
		||||
        self.get_focused_window(),
 | 
			
		||||
        c_string.as_ptr(),
 | 
			
		||||
        delay.try_into().unwrap(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Injector for X11XDOToolInjector {
 | 
			
		||||
  fn send_string(&self, string: &str, options: crate::InjectionOptions) -> anyhow::Result<()> {
 | 
			
		||||
    if options.disable_fast_inject {
 | 
			
		||||
      self.xfake_send_string(string, options)
 | 
			
		||||
    } else {
 | 
			
		||||
      self.fast_send_string(string, options)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_keys(
 | 
			
		||||
    &self,
 | 
			
		||||
    keys: &[crate::keys::Key],
 | 
			
		||||
    options: crate::InjectionOptions,
 | 
			
		||||
  ) -> anyhow::Result<()> {
 | 
			
		||||
    let key_syms: Vec<String> = keys
 | 
			
		||||
      .iter()
 | 
			
		||||
      .filter_map(|key| unsafe { convert_key_to_keysym((*self.xdo).xdpy, key) })
 | 
			
		||||
      .collect();
 | 
			
		||||
 | 
			
		||||
    let delay = options.delay * 1000;
 | 
			
		||||
 | 
			
		||||
    for key in key_syms {
 | 
			
		||||
      let c_str = CString::new(key).context("unable to generate CString")?;
 | 
			
		||||
 | 
			
		||||
      if options.disable_fast_inject {
 | 
			
		||||
        unsafe {
 | 
			
		||||
          xdo_send_keysequence_window(
 | 
			
		||||
            self.xdo,
 | 
			
		||||
            CURRENTWINDOW,
 | 
			
		||||
            c_str.as_ptr(),
 | 
			
		||||
            delay.try_into().unwrap(),
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        unsafe {
 | 
			
		||||
          fast_send_keysequence_window(
 | 
			
		||||
            self.xdo,
 | 
			
		||||
            self.get_focused_window(),
 | 
			
		||||
            c_str.as_ptr(),
 | 
			
		||||
            delay.try_into().unwrap(),
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn send_key_combination(
 | 
			
		||||
    &self,
 | 
			
		||||
    keys: &[crate::keys::Key],
 | 
			
		||||
    options: crate::InjectionOptions,
 | 
			
		||||
  ) -> anyhow::Result<()> {
 | 
			
		||||
    let key_syms: Vec<String> = keys
 | 
			
		||||
      .iter()
 | 
			
		||||
      .filter_map(|key| unsafe { convert_key_to_keysym((*self.xdo).xdpy, key) })
 | 
			
		||||
      .collect();
 | 
			
		||||
    let key_combination = key_syms.join("+");
 | 
			
		||||
 | 
			
		||||
    let delay = options.delay * 1000;
 | 
			
		||||
 | 
			
		||||
    let c_key_combination = CString::new(key_combination).context("unable to generate CString")?;
 | 
			
		||||
 | 
			
		||||
    if options.disable_fast_inject {
 | 
			
		||||
      unsafe {
 | 
			
		||||
        xdo_send_keysequence_window(
 | 
			
		||||
          self.xdo,
 | 
			
		||||
          CURRENTWINDOW,
 | 
			
		||||
          c_key_combination.as_ptr(),
 | 
			
		||||
          delay.try_into().unwrap(),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      unsafe {
 | 
			
		||||
        fast_send_keysequence_window(
 | 
			
		||||
          self.xdo,
 | 
			
		||||
          self.get_focused_window(),
 | 
			
		||||
          c_key_combination.as_ptr(),
 | 
			
		||||
          delay.try_into().unwrap(),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Drop for X11XDOToolInjector {
 | 
			
		||||
  fn drop(&mut self) {
 | 
			
		||||
    unsafe { ffi::xdo_free(self.xdo) }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn convert_key_to_keysym(display: *mut Display, key: &crate::keys::Key) -> Option<String> {
 | 
			
		||||
  match key {
 | 
			
		||||
    crate::keys::Key::Alt => Some("Alt_L".to_string()),
 | 
			
		||||
    crate::keys::Key::CapsLock => Some("Caps_Lock".to_string()),
 | 
			
		||||
    crate::keys::Key::Control => Some("Control_L".to_string()),
 | 
			
		||||
    crate::keys::Key::Meta => Some("Meta_L".to_string()),
 | 
			
		||||
    crate::keys::Key::NumLock => Some("Num_Lock".to_string()),
 | 
			
		||||
    crate::keys::Key::Shift => Some("Shift_L".to_string()),
 | 
			
		||||
    crate::keys::Key::Enter => Some("Return".to_string()),
 | 
			
		||||
    crate::keys::Key::Tab => Some("Tab".to_string()),
 | 
			
		||||
    crate::keys::Key::Space => Some("space".to_string()),
 | 
			
		||||
    crate::keys::Key::ArrowDown => Some("downarrow".to_string()),
 | 
			
		||||
    crate::keys::Key::ArrowLeft => Some("leftarrow".to_string()),
 | 
			
		||||
    crate::keys::Key::ArrowRight => Some("rightarrow".to_string()),
 | 
			
		||||
    crate::keys::Key::ArrowUp => Some("uparrow".to_string()),
 | 
			
		||||
    crate::keys::Key::End => Some("End".to_string()),
 | 
			
		||||
    crate::keys::Key::Home => Some("Home".to_string()),
 | 
			
		||||
    crate::keys::Key::PageDown => Some("Page_Down".to_string()),
 | 
			
		||||
    crate::keys::Key::PageUp => Some("Page_Up".to_string()),
 | 
			
		||||
    crate::keys::Key::Escape => Some("Escape".to_string()),
 | 
			
		||||
    crate::keys::Key::Backspace => Some("BackSpace".to_string()),
 | 
			
		||||
    crate::keys::Key::Insert => Some("Insert".to_string()),
 | 
			
		||||
    crate::keys::Key::Delete => Some("Delete".to_string()),
 | 
			
		||||
    crate::keys::Key::F1 => Some("F1".to_string()),
 | 
			
		||||
    crate::keys::Key::F2 => Some("F2".to_string()),
 | 
			
		||||
    crate::keys::Key::F3 => Some("F3".to_string()),
 | 
			
		||||
    crate::keys::Key::F4 => Some("F4".to_string()),
 | 
			
		||||
    crate::keys::Key::F5 => Some("F5".to_string()),
 | 
			
		||||
    crate::keys::Key::F6 => Some("F6".to_string()),
 | 
			
		||||
    crate::keys::Key::F7 => Some("F7".to_string()),
 | 
			
		||||
    crate::keys::Key::F8 => Some("F8".to_string()),
 | 
			
		||||
    crate::keys::Key::F9 => Some("F9".to_string()),
 | 
			
		||||
    crate::keys::Key::F10 => Some("F10".to_string()),
 | 
			
		||||
    crate::keys::Key::F11 => Some("F11".to_string()),
 | 
			
		||||
    crate::keys::Key::F12 => Some("F12".to_string()),
 | 
			
		||||
    crate::keys::Key::F13 => Some("F13".to_string()),
 | 
			
		||||
    crate::keys::Key::F14 => Some("F14".to_string()),
 | 
			
		||||
    crate::keys::Key::F15 => Some("F15".to_string()),
 | 
			
		||||
    crate::keys::Key::F16 => Some("F16".to_string()),
 | 
			
		||||
    crate::keys::Key::F17 => Some("F17".to_string()),
 | 
			
		||||
    crate::keys::Key::F18 => Some("F18".to_string()),
 | 
			
		||||
    crate::keys::Key::F19 => Some("F19".to_string()),
 | 
			
		||||
    crate::keys::Key::F20 => Some("F20".to_string()),
 | 
			
		||||
    crate::keys::Key::A => Some("a".to_string()),
 | 
			
		||||
    crate::keys::Key::B => Some("b".to_string()),
 | 
			
		||||
    crate::keys::Key::C => Some("c".to_string()),
 | 
			
		||||
    crate::keys::Key::D => Some("d".to_string()),
 | 
			
		||||
    crate::keys::Key::E => Some("e".to_string()),
 | 
			
		||||
    crate::keys::Key::F => Some("f".to_string()),
 | 
			
		||||
    crate::keys::Key::G => Some("g".to_string()),
 | 
			
		||||
    crate::keys::Key::H => Some("h".to_string()),
 | 
			
		||||
    crate::keys::Key::I => Some("i".to_string()),
 | 
			
		||||
    crate::keys::Key::J => Some("j".to_string()),
 | 
			
		||||
    crate::keys::Key::K => Some("k".to_string()),
 | 
			
		||||
    crate::keys::Key::L => Some("l".to_string()),
 | 
			
		||||
    crate::keys::Key::M => Some("m".to_string()),
 | 
			
		||||
    crate::keys::Key::N => Some("n".to_string()),
 | 
			
		||||
    crate::keys::Key::O => Some("o".to_string()),
 | 
			
		||||
    crate::keys::Key::P => Some("p".to_string()),
 | 
			
		||||
    crate::keys::Key::Q => Some("q".to_string()),
 | 
			
		||||
    crate::keys::Key::R => Some("r".to_string()),
 | 
			
		||||
    crate::keys::Key::S => Some("s".to_string()),
 | 
			
		||||
    crate::keys::Key::T => Some("t".to_string()),
 | 
			
		||||
    crate::keys::Key::U => Some("u".to_string()),
 | 
			
		||||
    crate::keys::Key::V => Some("v".to_string()),
 | 
			
		||||
    crate::keys::Key::W => Some("w".to_string()),
 | 
			
		||||
    crate::keys::Key::X => Some("x".to_string()),
 | 
			
		||||
    crate::keys::Key::Y => Some("y".to_string()),
 | 
			
		||||
    crate::keys::Key::Z => Some("z".to_string()),
 | 
			
		||||
    crate::keys::Key::N0 => Some("0".to_string()),
 | 
			
		||||
    crate::keys::Key::N1 => Some("1".to_string()),
 | 
			
		||||
    crate::keys::Key::N2 => Some("2".to_string()),
 | 
			
		||||
    crate::keys::Key::N3 => Some("3".to_string()),
 | 
			
		||||
    crate::keys::Key::N4 => Some("4".to_string()),
 | 
			
		||||
    crate::keys::Key::N5 => Some("5".to_string()),
 | 
			
		||||
    crate::keys::Key::N6 => Some("6".to_string()),
 | 
			
		||||
    crate::keys::Key::N7 => Some("7".to_string()),
 | 
			
		||||
    crate::keys::Key::N8 => Some("8".to_string()),
 | 
			
		||||
    crate::keys::Key::N9 => Some("9".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad0 => Some("KP_0".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad1 => Some("KP_1".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad2 => Some("KP_2".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad3 => Some("KP_3".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad4 => Some("KP_4".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad5 => Some("KP_5".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad6 => Some("KP_6".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad7 => Some("KP_7".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad8 => Some("KP_8".to_string()),
 | 
			
		||||
    crate::keys::Key::Numpad9 => Some("KP_9".to_string()),
 | 
			
		||||
    crate::keys::Key::Raw(key_code) => unsafe {
 | 
			
		||||
      let key_sym = XKeycodeToKeysym(display, (*key_code).try_into().unwrap(), 0);
 | 
			
		||||
      let string = XKeysymToString(key_sym);
 | 
			
		||||
      let c_str = CStr::from_ptr(string);
 | 
			
		||||
      Some(c_str.to_string_lossy().to_string())
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								espanso-inject/src/x11/xdotool/vendor/COPYRIGHT
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								espanso-inject/src/x11/xdotool/vendor/COPYRIGHT
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
Copyright (c) 2007, 2008, 2009: Jordan Sissel.
 | 
			
		||||
All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are met:
 | 
			
		||||
    * Redistributions of source code must retain the above copyright
 | 
			
		||||
      notice, this list of conditions and the following disclaimer.
 | 
			
		||||
    * Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
      notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
      documentation and/or other materials provided with the distribution.
 | 
			
		||||
    * Neither the name of the Jordan Sissel nor the names of its contributors
 | 
			
		||||
      may be used to endorse or promote products derived from this software
 | 
			
		||||
      without specific prior written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY JORDAN SISSEL ``AS IS'' AND ANY
 | 
			
		||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 | 
			
		||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | 
			
		||||
DISCLAIMED. IN NO EVENT SHALL JORDAN SISSEL BE LIABLE FOR ANY
 | 
			
		||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 | 
			
		||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 | 
			
		||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 | 
			
		||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
			
		||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 | 
			
		||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
							
								
								
									
										2266
									
								
								espanso-inject/src/x11/xdotool/vendor/xdo.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2266
									
								
								espanso-inject/src/x11/xdotool/vendor/xdo.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										938
									
								
								espanso-inject/src/x11/xdotool/vendor/xdo.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										938
									
								
								espanso-inject/src/x11/xdotool/vendor/xdo.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,938 @@
 | 
			
		|||
/**
 | 
			
		||||
 * @file xdo.h
 | 
			
		||||
 */
 | 
			
		||||
#ifndef _XDO_H_
 | 
			
		||||
#define _XDO_H_
 | 
			
		||||
 | 
			
		||||
#ifndef __USE_XOPEN
 | 
			
		||||
#define __USE_XOPEN
 | 
			
		||||
#endif /* __USE_XOPEN */
 | 
			
		||||
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <X11/Xlib.h>
 | 
			
		||||
#include <X11/X.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <wchar.h>
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @mainpage
 | 
			
		||||
 *
 | 
			
		||||
 * libxdo helps you send fake mouse and keyboard input, search for windows,
 | 
			
		||||
 * perform various window management tasks such as desktop changes, window
 | 
			
		||||
 * movement, etc.
 | 
			
		||||
 *
 | 
			
		||||
 * For examples on libxdo usage, the xdotool source code is a good reference.
 | 
			
		||||
 *
 | 
			
		||||
 * @see xdo.h
 | 
			
		||||
 * @see xdo_new
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * When issuing a window size change, giving this flag will make the size
 | 
			
		||||
 * change be relative to the size hints of the window.  For terminals, this
 | 
			
		||||
 * generally means that the window size will be relative to the font size,
 | 
			
		||||
 * allowing you to change window sizes based on character rows and columns
 | 
			
		||||
 * instead of pixels.
 | 
			
		||||
 */
 | 
			
		||||
#define SIZE_USEHINTS (1L << 0)
 | 
			
		||||
#define SIZE_USEHINTS_X (1L << 1)
 | 
			
		||||
#define SIZE_USEHINTS_Y (1L << 2)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * CURRENTWINDOW is a special identify for xdo input faking (mouse and
 | 
			
		||||
 * keyboard) functions like xdo_send_keysequence_window that indicate we should target the
 | 
			
		||||
 * current window, not a specific window.
 | 
			
		||||
 *
 | 
			
		||||
 * Generally, this means we will use XTEST instead of XSendEvent when sending
 | 
			
		||||
 * events.
 | 
			
		||||
 */
 | 
			
		||||
#define CURRENTWINDOW (0)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @internal
 | 
			
		||||
 * Map character to whatever information we need to be able to send
 | 
			
		||||
 * this key (keycode, modifiers, group, etc)
 | 
			
		||||
 */
 | 
			
		||||
typedef struct charcodemap {
 | 
			
		||||
  wchar_t key; /** the letter for this key, like 'a' */
 | 
			
		||||
  KeyCode code; /** the keycode that this key is on */
 | 
			
		||||
  KeySym symbol; /** the symbol representing this key */
 | 
			
		||||
  int group; /** the keyboard group that has this key in it */
 | 
			
		||||
  int modmask; /** the modifiers to apply when sending this key */
 | 
			
		||||
   /** if this key need to be bound at runtime because it does not
 | 
			
		||||
    * exist in the current keymap, this will be set to 1. */
 | 
			
		||||
  int needs_binding;
 | 
			
		||||
} charcodemap_t;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
  XDO_FEATURE_XTEST, /** Is XTest available? */
 | 
			
		||||
} XDO_FEATURES;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The main context.
 | 
			
		||||
 */
 | 
			
		||||
typedef struct xdo {
 | 
			
		||||
 | 
			
		||||
  /** The Display for Xlib */
 | 
			
		||||
  Display *xdpy;
 | 
			
		||||
 | 
			
		||||
  /** The display name, if any. NULL if not specified. */
 | 
			
		||||
  char *display_name;
 | 
			
		||||
 | 
			
		||||
  /** @internal Array of known keys/characters */
 | 
			
		||||
  charcodemap_t *charcodes;
 | 
			
		||||
 | 
			
		||||
  /** @internal Length of charcodes array */
 | 
			
		||||
  int charcodes_len;
 | 
			
		||||
 | 
			
		||||
  /** @internal highest keycode value */
 | 
			
		||||
  int keycode_high; /* highest and lowest keycodes */
 | 
			
		||||
 | 
			
		||||
  /** @internal lowest keycode value */
 | 
			
		||||
  int keycode_low;  /* used by this X server */
 | 
			
		||||
  
 | 
			
		||||
  /** @internal number of keysyms per keycode */
 | 
			
		||||
  int keysyms_per_keycode;
 | 
			
		||||
 | 
			
		||||
  /** Should we close the display when calling xdo_free? */
 | 
			
		||||
  int close_display_when_freed;
 | 
			
		||||
 | 
			
		||||
  /** Be extra quiet? (omits some error/message output) */
 | 
			
		||||
  int quiet;
 | 
			
		||||
 | 
			
		||||
  /** Enable debug output? */
 | 
			
		||||
  int debug;
 | 
			
		||||
 | 
			
		||||
  /** Feature flags, such as XDO_FEATURE_XTEST, etc... */
 | 
			
		||||
  int features_mask;
 | 
			
		||||
 | 
			
		||||
} xdo_t;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search only window title. DEPRECATED - Use SEARCH_NAME
 | 
			
		||||
 * @see xdo_search_windows
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_TITLE (1UL << 0)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search only window class.
 | 
			
		||||
 * @see xdo_search_windows
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_CLASS (1UL << 1)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search only window name.
 | 
			
		||||
 * @see xdo_search_windows
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_NAME (1UL << 2)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search only window pid.
 | 
			
		||||
 * @see xdo_search_windows
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_PID  (1UL << 3)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search only visible windows.
 | 
			
		||||
 * @see xdo_search_windows
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_ONLYVISIBLE  (1UL << 4)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search only a specific screen. 
 | 
			
		||||
 * @see xdo_search.screen
 | 
			
		||||
 * @see xdo_search_windows
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_SCREEN  (1UL << 5)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search only window class name.
 | 
			
		||||
 * @see xdo_search
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_CLASSNAME (1UL << 6)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search a specific desktop
 | 
			
		||||
 * @see xdo_search.screen
 | 
			
		||||
 * @see xdo_search_windows
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_DESKTOP (1UL << 7)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search only window role.
 | 
			
		||||
 * @see xdo_search
 | 
			
		||||
 */
 | 
			
		||||
#define SEARCH_ROLE (1UL << 8)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The window search query structure.
 | 
			
		||||
 *
 | 
			
		||||
 * @see xdo_search_windows
 | 
			
		||||
 */
 | 
			
		||||
typedef struct xdo_search {
 | 
			
		||||
  const char *title;        /** pattern to test against a window title */
 | 
			
		||||
  const char *winclass;     /** pattern to test against a window class */
 | 
			
		||||
  const char *winclassname; /** pattern to test against a window class */
 | 
			
		||||
  const char *winname;      /** pattern to test against a window name */
 | 
			
		||||
  const char *winrole;      /** pattern to test against a window role */
 | 
			
		||||
  int pid;            /** window pid (From window atom _NET_WM_PID) */
 | 
			
		||||
  long max_depth;     /** depth of search. 1 means only toplevel windows */
 | 
			
		||||
  int only_visible;   /** boolean; set true to search only visible windows */
 | 
			
		||||
  int screen;         /** what screen to search, if any. If none given, search 
 | 
			
		||||
                         all screens */
 | 
			
		||||
 | 
			
		||||
  /** Should the tests be 'and' or 'or' ? If 'and', any failure will skip the
 | 
			
		||||
   * window. If 'or', any success will keep the window in search results. */
 | 
			
		||||
  enum { SEARCH_ANY, SEARCH_ALL } require;
 | 
			
		||||
  
 | 
			
		||||
  /** bitmask of things you are searching for, such as SEARCH_NAME, etc.
 | 
			
		||||
   * @see SEARCH_NAME, SEARCH_CLASS, SEARCH_PID, SEARCH_CLASSNAME, etc
 | 
			
		||||
   */
 | 
			
		||||
  unsigned int searchmask; 
 | 
			
		||||
 | 
			
		||||
  /** What desktop to search, if any. If none given, search all screens. */
 | 
			
		||||
  long desktop;
 | 
			
		||||
 | 
			
		||||
  /** How many results to return? If 0, return all. */
 | 
			
		||||
  unsigned int limit;
 | 
			
		||||
} xdo_search_t;
 | 
			
		||||
 | 
			
		||||
#define XDO_ERROR 1
 | 
			
		||||
#define XDO_SUCCESS 0
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create a new xdo_t instance.
 | 
			
		||||
 *
 | 
			
		||||
 * @param display the string display name, such as ":0". If null, uses the
 | 
			
		||||
 * environment variable DISPLAY just like XOpenDisplay(NULL).
 | 
			
		||||
 *
 | 
			
		||||
 * @return Pointer to a new xdo_t or NULL on failure
 | 
			
		||||
 */
 | 
			
		||||
xdo_t* xdo_new(const char *display);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create a new xdo_t instance with an existing X11 Display instance.
 | 
			
		||||
 *
 | 
			
		||||
 * @param xdpy the Display pointer given by a previous XOpenDisplay()
 | 
			
		||||
 * @param display the string display name
 | 
			
		||||
 * @param close_display_when_freed If true, we will close the display when
 | 
			
		||||
 * xdo_free is called. Otherwise, we leave it open.
 | 
			
		||||
 */
 | 
			
		||||
xdo_t* xdo_new_with_opened_display(Display *xdpy, const char *display,
 | 
			
		||||
                                   int close_display_when_freed);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Return a string representing the version of this library
 | 
			
		||||
 */
 | 
			
		||||
const char *xdo_version(void);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Free and destroy an xdo_t instance.
 | 
			
		||||
 *
 | 
			
		||||
 * If close_display_when_freed is set, then we will also close the Display.
 | 
			
		||||
 */
 | 
			
		||||
void xdo_free(xdo_t *xdo);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Move the mouse to a specific location.
 | 
			
		||||
 *
 | 
			
		||||
 * @param x the target X coordinate on the screen in pixels.
 | 
			
		||||
 * @param y the target Y coordinate on the screen in pixels.
 | 
			
		||||
 * @param screen the screen (number) you want to move on.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_move_mouse(const xdo_t *xdo, int x, int y, int screen);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Move the mouse to a specific location relative to the top-left corner
 | 
			
		||||
 * of a window.
 | 
			
		||||
 *
 | 
			
		||||
 * @param x the target X coordinate on the screen in pixels.
 | 
			
		||||
 * @param y the target Y coordinate on the screen in pixels.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_move_mouse_relative_to_window(const xdo_t *xdo, Window window, int x, int y);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Move the mouse relative to it's current position.
 | 
			
		||||
 *
 | 
			
		||||
 * @param x the distance in pixels to move on the X axis.
 | 
			
		||||
 * @param y the distance in pixels to move on the Y axis.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_move_mouse_relative(const xdo_t *xdo, int x, int y);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send a mouse press (aka mouse down) for a given button at the current mouse
 | 
			
		||||
 * location.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window The window you want to send the event to or CURRENTWINDOW
 | 
			
		||||
 * @param button The mouse button. Generally, 1 is left, 2 is middle, 3 is
 | 
			
		||||
 *    right, 4 is wheel up, 5 is wheel down.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_mouse_down(const xdo_t *xdo, Window window, int button);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send a mouse release (aka mouse up) for a given button at the current mouse
 | 
			
		||||
 * location.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window The window you want to send the event to or CURRENTWINDOW
 | 
			
		||||
 * @param button The mouse button. Generally, 1 is left, 2 is middle, 3 is
 | 
			
		||||
 *    right, 4 is wheel up, 5 is wheel down.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_mouse_up(const xdo_t *xdo, Window window, int button);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the current mouse location (coordinates and screen number).
 | 
			
		||||
 *
 | 
			
		||||
 * @param x integer pointer where the X coordinate will be stored
 | 
			
		||||
 * @param y integer pointer where the Y coordinate will be stored
 | 
			
		||||
 * @param screen_num integer pointer where the screen number will be stored
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_mouse_location(const xdo_t *xdo, int *x, int *y, int *screen_num);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the window the mouse is currently over
 | 
			
		||||
 *
 | 
			
		||||
 * @param window_ret Window pointer where the window will be stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_window_at_mouse(const xdo_t *xdo, Window *window_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get all mouse location-related data.
 | 
			
		||||
 *
 | 
			
		||||
 * If null is passed for any parameter, we simply do not store it.
 | 
			
		||||
 * Useful if you only want the 'y' coordinate, for example.
 | 
			
		||||
 *
 | 
			
		||||
 * @param x integer pointer where the X coordinate will be stored
 | 
			
		||||
 * @param y integer pointer where the Y coordinate will be stored
 | 
			
		||||
 * @param screen_num integer pointer where the screen number will be stored
 | 
			
		||||
 * @param window Window pointer where the window/client the mouse is over
 | 
			
		||||
 *   will be stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_mouse_location2(const xdo_t *xdo, int *x_ret, int *y_ret,
 | 
			
		||||
                            int *screen_num_ret, Window *window_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wait for the mouse to move from a location. This function will block
 | 
			
		||||
 * until the condition has been satisfied.
 | 
			
		||||
 *
 | 
			
		||||
 * @param origin_x the X position you expect the mouse to move from
 | 
			
		||||
 * @param origin_y the Y position you expect the mouse to move from
 | 
			
		||||
 */
 | 
			
		||||
int xdo_wait_for_mouse_move_from(const xdo_t *xdo, int origin_x, int origin_y);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wait for the mouse to move to a location. This function will block
 | 
			
		||||
 * until the condition has been satisfied.
 | 
			
		||||
 *
 | 
			
		||||
 * @param dest_x the X position you expect the mouse to move to
 | 
			
		||||
 * @param dest_y the Y position you expect the mouse to move to
 | 
			
		||||
 */
 | 
			
		||||
int xdo_wait_for_mouse_move_to(const xdo_t *xdo, int dest_x, int dest_y);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send a click for a specific mouse button at the current mouse location.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window The window you want to send the event to or CURRENTWINDOW
 | 
			
		||||
 * @param button The mouse button. Generally, 1 is left, 2 is middle, 3 is
 | 
			
		||||
 *    right, 4 is wheel up, 5 is wheel down.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_click_window(const xdo_t *xdo, Window window, int button);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send a one or more clicks for a specific mouse button at the current mouse
 | 
			
		||||
 * location.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window The window you want to send the event to or CURRENTWINDOW
 | 
			
		||||
 * @param button The mouse button. Generally, 1 is left, 2 is middle, 3 is
 | 
			
		||||
 *    right, 4 is wheel up, 5 is wheel down.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_click_window_multiple(const xdo_t *xdo, Window window, int button,
 | 
			
		||||
                       int repeat, useconds_t delay);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type a string to the specified window.
 | 
			
		||||
 *
 | 
			
		||||
 * If you want to send a specific key or key sequence, such as "alt+l", you
 | 
			
		||||
 * want instead xdo_send_keysequence_window(...).
 | 
			
		||||
 *
 | 
			
		||||
 * @param window The window you want to send keystrokes to or CURRENTWINDOW
 | 
			
		||||
 * @param string The string to type, like "Hello world!"
 | 
			
		||||
 * @param delay The delay between keystrokes in microseconds. 12000 is a decent
 | 
			
		||||
 *    choice if you don't have other plans.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_enter_text_window(const xdo_t *xdo, Window window, const char *string, useconds_t delay);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send a keysequence to the specified window.
 | 
			
		||||
 *
 | 
			
		||||
 * This allows you to send keysequences by symbol name. Any combination
 | 
			
		||||
 * of X11 KeySym names separated by '+' are valid. Single KeySym names
 | 
			
		||||
 * are valid, too.
 | 
			
		||||
 *
 | 
			
		||||
 * Examples:
 | 
			
		||||
 *   "l"
 | 
			
		||||
 *   "semicolon"
 | 
			
		||||
 *   "alt+Return"
 | 
			
		||||
 *   "Alt_L+Tab"
 | 
			
		||||
 *
 | 
			
		||||
 * If you want to type a string, such as "Hello world." you want to instead
 | 
			
		||||
 * use xdo_enter_text_window.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window The window you want to send the keysequence to or
 | 
			
		||||
 *   CURRENTWINDOW
 | 
			
		||||
 * @param keysequence The string keysequence to send.
 | 
			
		||||
 * @param delay The delay between keystrokes in microseconds.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_send_keysequence_window(const xdo_t *xdo, Window window,
 | 
			
		||||
                    const char *keysequence, useconds_t delay);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send key release (up) events for the given key sequence.
 | 
			
		||||
 *
 | 
			
		||||
 * @see xdo_send_keysequence_window
 | 
			
		||||
 */
 | 
			
		||||
int xdo_send_keysequence_window_up(const xdo_t *xdo, Window window,
 | 
			
		||||
                       const char *keysequence, useconds_t delay);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send key press (down) events for the given key sequence.
 | 
			
		||||
 *
 | 
			
		||||
 * @see xdo_send_keysequence_window
 | 
			
		||||
 */
 | 
			
		||||
int xdo_send_keysequence_window_down(const xdo_t *xdo, Window window,
 | 
			
		||||
                         const char *keysequence, useconds_t delay);
 | 
			
		||||
                         
 | 
			
		||||
/**
 | 
			
		||||
 * Send a series of keystrokes.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window The window to send events to or CURRENTWINDOW
 | 
			
		||||
 * @param keys The array of charcodemap_t entities to send.
 | 
			
		||||
 * @param nkeys The length of the keys parameter
 | 
			
		||||
 * @param pressed 1 for key press, 0 for key release.
 | 
			
		||||
 * @param modifier Pointer to integer to record the modifiers activated by
 | 
			
		||||
 *   the keys being pressed. If NULL, we don't save the modifiers.
 | 
			
		||||
 * @param delay The delay between keystrokes in microseconds.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_send_keysequence_window_list_do(const xdo_t *xdo, Window window,
 | 
			
		||||
                            charcodemap_t *keys, int nkeys,
 | 
			
		||||
                            int pressed, int *modifier, useconds_t delay);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wait for a window to have a specific map state.
 | 
			
		||||
 *
 | 
			
		||||
 * State possibilities:
 | 
			
		||||
 *   IsUnmapped - window is not displayed.
 | 
			
		||||
 *   IsViewable - window is mapped and shown (though may be clipped by windows
 | 
			
		||||
 *     on top of it)
 | 
			
		||||
 *   IsUnviewable - window is mapped but a parent window is unmapped.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window you want to wait for.
 | 
			
		||||
 * @param map_state the state to wait for.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_wait_for_window_map_state(const xdo_t *xdo, Window wid, int map_state);
 | 
			
		||||
 | 
			
		||||
#define SIZE_TO 0
 | 
			
		||||
#define SIZE_FROM 1
 | 
			
		||||
int xdo_wait_for_window_size(const xdo_t *xdo, Window window, unsigned int width,
 | 
			
		||||
                             unsigned int height, int flags, int to_or_from);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Move a window to a specific location.
 | 
			
		||||
 *
 | 
			
		||||
 * The top left corner of the window will be moved to the x,y coordinate.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to move
 | 
			
		||||
 * @param x the X coordinate to move to.
 | 
			
		||||
 * @param y the Y coordinate to move to.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_move_window(const xdo_t *xdo, Window wid, int x, int y);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Apply a window's sizing hints (if any) to a given width and height.
 | 
			
		||||
 *
 | 
			
		||||
 * This function wraps XGetWMNormalHints() and applies any 
 | 
			
		||||
 * resize increment and base size to your given width and height values.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window the window to use
 | 
			
		||||
 * @param width the unit width you want to translate
 | 
			
		||||
 * @param height the unit height you want to translate
 | 
			
		||||
 * @param width_ret the return location of the translated width
 | 
			
		||||
 * @param height_ret the return location of the translated height
 | 
			
		||||
 */
 | 
			
		||||
int xdo_translate_window_with_sizehint(const xdo_t *xdo, Window window,
 | 
			
		||||
                                       unsigned int width, unsigned int height,
 | 
			
		||||
                                       unsigned int *width_ret, unsigned int *height_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Change the window size.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to resize
 | 
			
		||||
 * @param w the new desired width
 | 
			
		||||
 * @param h the new desired height
 | 
			
		||||
 * @param flags if 0, use pixels for units. If SIZE_USEHINTS, then
 | 
			
		||||
 *   the units will be relative to the window size hints.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_window_size(const xdo_t *xdo, Window wid, int w, int h, int flags);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Change a window property.
 | 
			
		||||
 *
 | 
			
		||||
 * Example properties you can change are WM_NAME, WM_ICON_NAME, etc.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid The window to change a property of.
 | 
			
		||||
 * @param property the string name of the property.
 | 
			
		||||
 * @param value the string value of the property.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_window_property(const xdo_t *xdo, Window wid, const char *property,
 | 
			
		||||
                        const char *value);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Change the window's classname and or class.
 | 
			
		||||
 *
 | 
			
		||||
 * @param name The new class name. If NULL, no change.
 | 
			
		||||
 * @param _class The new class. If NULL, no change.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_window_class(const xdo_t *xdo, Window wid, const char *name,
 | 
			
		||||
                        const char *_class);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sets the urgency hint for a window.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_window_urgency (const xdo_t *xdo, Window wid, int urgency);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the override_redirect value for a window. This generally means
 | 
			
		||||
 * whether or not a window manager will manage this window.
 | 
			
		||||
 *
 | 
			
		||||
 * If you set it to 1, the window manager will usually not draw borders on the
 | 
			
		||||
 * window, etc. If you set it to 0, the window manager will see it like a
 | 
			
		||||
 * normal application window.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_window_override_redirect(const xdo_t *xdo, Window wid,
 | 
			
		||||
                                     int override_redirect);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Focus a window.
 | 
			
		||||
 *
 | 
			
		||||
 * @see xdo_activate_window
 | 
			
		||||
 * @param wid the window to focus.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_focus_window(const xdo_t *xdo, Window wid);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Raise a window to the top of the window stack. This is also sometimes
 | 
			
		||||
 * termed as bringing the window forward.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid The window to raise.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_raise_window(const xdo_t *xdo, Window wid);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the window currently having focus.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window_ret Pointer to a window where the currently-focused window
 | 
			
		||||
 *   will be stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_focused_window(const xdo_t *xdo, Window *window_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wait for a window to have or lose focus.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window The window to wait on
 | 
			
		||||
 * @param want_focus If 1, wait for focus. If 0, wait for loss of focus.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_wait_for_window_focus(const xdo_t *xdo, Window window, int want_focus);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the PID owning a window. Not all applications support this.
 | 
			
		||||
 * It looks at the _NET_WM_PID property of the window.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window the window to query.
 | 
			
		||||
 * @return the process id or 0 if no pid found.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_pid_window(const xdo_t *xdo, Window window);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Like xdo_get_focused_window, but return the first ancestor-or-self window *
 | 
			
		||||
 * having a property of WM_CLASS. This allows you to get the "real" or
 | 
			
		||||
 * top-level-ish window having focus rather than something you may not expect
 | 
			
		||||
 * to be the window having focused.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window_ret Pointer to a window where the currently-focused window
 | 
			
		||||
 *   will be stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_focused_window_sane(const xdo_t *xdo, Window *window_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Activate a window. This is generally a better choice than xdo_focus_window
 | 
			
		||||
 * for a variety of reasons, but it requires window manager support:
 | 
			
		||||
 *   - If the window is on another desktop, that desktop is switched to.
 | 
			
		||||
 *   - It moves the window forward rather than simply focusing it
 | 
			
		||||
 *
 | 
			
		||||
 * Requires your window manager to support this.
 | 
			
		||||
 * Uses _NET_ACTIVE_WINDOW from the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to activate
 | 
			
		||||
 */
 | 
			
		||||
int xdo_activate_window(const xdo_t *xdo, Window wid);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wait for a window to be active or not active.
 | 
			
		||||
 *
 | 
			
		||||
 * Requires your window manager to support this.
 | 
			
		||||
 * Uses _NET_ACTIVE_WINDOW from the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window the window to wait on
 | 
			
		||||
 * @param active If 1, wait for active. If 0, wait for inactive.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_wait_for_window_active(const xdo_t *xdo, Window window, int active);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Map a window. This mostly means to make the window visible if it is
 | 
			
		||||
 * not currently mapped.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to map.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_map_window(const xdo_t *xdo, Window wid);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unmap a window
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to unmap
 | 
			
		||||
 */
 | 
			
		||||
int xdo_unmap_window(const xdo_t *xdo, Window wid);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Minimize a window.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_minimize_window(const xdo_t *xdo, Window wid);
 | 
			
		||||
 | 
			
		||||
#define _NET_WM_STATE_REMOVE        0    /* remove/unset property */
 | 
			
		||||
#define _NET_WM_STATE_ADD           1    /* add/set property */
 | 
			
		||||
#define _NET_WM_STATE_TOGGLE        2    /* toggle property  */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get window classname
 | 
			
		||||
 * @param window the window
 | 
			
		||||
 * @param class_ret Pointer to the window classname WM_CLASS
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_window_classname(const xdo_t *xdo, Window window, unsigned char **class_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Change window state
 | 
			
		||||
 * @param action the _NET_WM_STATE action
 | 
			
		||||
 */
 | 
			
		||||
int xdo_window_state(xdo_t *xdo, Window window, unsigned long action, const char *property);
 | 
			
		||||
 | 
			
		||||
/** 
 | 
			
		||||
 * Reparents a window
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid_source the window to reparent
 | 
			
		||||
 * @param wid_target the new parent window
 | 
			
		||||
 */
 | 
			
		||||
int xdo_reparent_window(const xdo_t *xdo, Window wid_source, Window wid_target);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get a window's location.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to query
 | 
			
		||||
 * @param x_ret pointer to int where the X location is stored. If NULL, X is
 | 
			
		||||
 *   ignored.
 | 
			
		||||
 * @param y_ret pointer to int where the Y location is stored. If NULL, X is
 | 
			
		||||
 *   ignored.
 | 
			
		||||
 * @param screen_ret Pointer to Screen* where the Screen* the window on is
 | 
			
		||||
 *   stored. If NULL, this parameter is ignored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_window_location(const xdo_t *xdo, Window wid,
 | 
			
		||||
                            int *x_ret, int *y_ret, Screen **screen_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get a window's size.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to query
 | 
			
		||||
 * @param width_ret pointer to unsigned int where the width is stored.
 | 
			
		||||
 * @param height_ret pointer to unsigned int where the height is stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_window_size(const xdo_t *xdo, Window wid, unsigned int *width_ret,
 | 
			
		||||
                        unsigned int *height_ret);
 | 
			
		||||
 | 
			
		||||
/* pager-like behaviors */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the currently-active window.
 | 
			
		||||
 * Requires your window manager to support this.
 | 
			
		||||
 * Uses _NET_ACTIVE_WINDOW from the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window_ret Pointer to Window where the active window is stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_active_window(const xdo_t *xdo, Window *window_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get a window ID by clicking on it. This function blocks until a selection
 | 
			
		||||
 * is made.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window_ret Pointer to Window where the selected window is stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_select_window_with_click(const xdo_t *xdo, Window *window_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the number of desktops.
 | 
			
		||||
 * Uses _NET_NUMBER_OF_DESKTOPS of the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * @param ndesktops the new number of desktops to set.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_number_of_desktops(const xdo_t *xdo, long ndesktops);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the current number of desktops.
 | 
			
		||||
 * Uses _NET_NUMBER_OF_DESKTOPS of the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * @param ndesktops pointer to long where the current number of desktops is
 | 
			
		||||
 *   stored
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_number_of_desktops(const xdo_t *xdo, long *ndesktops);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Switch to another desktop.
 | 
			
		||||
 * Uses _NET_CURRENT_DESKTOP of the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * @param desktop The desktop number to switch to.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_current_desktop(const xdo_t *xdo, long desktop);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the current desktop.
 | 
			
		||||
 * Uses _NET_CURRENT_DESKTOP of the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * @param desktop pointer to long where the current desktop number is stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_current_desktop(const xdo_t *xdo, long *desktop);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Move a window to another desktop
 | 
			
		||||
 * Uses _NET_WM_DESKTOP of the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to move
 | 
			
		||||
 * @param desktop the desktop destination for the window
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_desktop_for_window(const xdo_t *xdo, Window wid, long desktop);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the desktop a window is on.
 | 
			
		||||
 * Uses _NET_WM_DESKTOP of the EWMH spec.
 | 
			
		||||
 *
 | 
			
		||||
 * If your desktop does not support _NET_WM_DESKTOP, then '*desktop' remains
 | 
			
		||||
 * unmodified.
 | 
			
		||||
 *
 | 
			
		||||
 * @param wid the window to query
 | 
			
		||||
 * @param deskto pointer to long where the desktop of the window is stored
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_desktop_for_window(const xdo_t *xdo, Window wid, long *desktop);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search for windows.
 | 
			
		||||
 *
 | 
			
		||||
 * @param search the search query.
 | 
			
		||||
 * @param windowlist_ret the list of matching windows to return
 | 
			
		||||
 * @param nwindows_ret the number of windows (length of windowlist_ret)
 | 
			
		||||
 * @see xdo_search_t
 | 
			
		||||
 */
 | 
			
		||||
int xdo_search_windows(const xdo_t *xdo, const xdo_search_t *search,
 | 
			
		||||
                      Window **windowlist_ret, unsigned int *nwindows_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generic property fetch.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window the window to query
 | 
			
		||||
 * @param atom the Atom to request
 | 
			
		||||
 * @param nitems the number of items 
 | 
			
		||||
 * @param type the type of the return
 | 
			
		||||
 * @param size the size of the type
 | 
			
		||||
 * @return data consisting of 'nitems' items of size 'size' and type 'type'
 | 
			
		||||
 *   will need to be cast to the type before using.
 | 
			
		||||
 */
 | 
			
		||||
unsigned char *xdo_get_window_property_by_atom(const xdo_t *xdo, Window window, Atom atom,
 | 
			
		||||
                              long *nitems, Atom *type, int *size);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get property of window by name of atom.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window the window to query
 | 
			
		||||
 * @param property the name of the atom
 | 
			
		||||
 * @param nitems the number of items 
 | 
			
		||||
 * @param type the type of the return
 | 
			
		||||
 * @param size the size of the type
 | 
			
		||||
 * @return data consisting of 'nitems' items of size 'size' and type 'type'
 | 
			
		||||
 *   will need to be cast to the type before using.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_window_property(const xdo_t *xdo, Window window, const char *property,
 | 
			
		||||
                            unsigned char **value, long *nitems, Atom *type, int *size);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the current input state. This is a mask value containing any of the
 | 
			
		||||
 * following: ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask,
 | 
			
		||||
 * Mod4Mask, or Mod5Mask.
 | 
			
		||||
 *
 | 
			
		||||
 * @return the input mask
 | 
			
		||||
 */
 | 
			
		||||
unsigned int xdo_get_input_state(const xdo_t *xdo);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * If you need the symbol map, use this method.
 | 
			
		||||
 *
 | 
			
		||||
 * The symbol map is an array of string pairs mapping common tokens to X Keysym
 | 
			
		||||
 * strings, such as "alt" to "Alt_L"
 | 
			
		||||
 *
 | 
			
		||||
 * @returns array of strings.
 | 
			
		||||
 */
 | 
			
		||||
const char **xdo_get_symbol_map(void);
 | 
			
		||||
 | 
			
		||||
/* active modifiers stuff */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get a list of active keys. Uses XQueryKeymap.
 | 
			
		||||
 *
 | 
			
		||||
 * @param keys Pointer to the array of charcodemap_t that will be allocated
 | 
			
		||||
 *    by this function.
 | 
			
		||||
 * @param nkeys Pointer to integer where the number of keys will be stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_active_modifiers(const xdo_t *xdo, charcodemap_t **keys,
 | 
			
		||||
                             int *nkeys);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send any events necessary to clear the active modifiers.
 | 
			
		||||
 * For example, if you are holding 'alt' when xdo_get_active_modifiers is 
 | 
			
		||||
 * called, then this method will send a key-up for 'alt'
 | 
			
		||||
 */
 | 
			
		||||
int xdo_clear_active_modifiers(const xdo_t *xdo, Window window,
 | 
			
		||||
                               charcodemap_t *active_mods,
 | 
			
		||||
                               int active_mods_n);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Send any events necessary to make these modifiers active.
 | 
			
		||||
 * This is useful if you just cleared the active modifiers and then wish
 | 
			
		||||
 * to restore them after.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_active_modifiers(const xdo_t *xdo, Window window,
 | 
			
		||||
                             charcodemap_t *active_mods,
 | 
			
		||||
                             int active_mods_n);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the position of the current viewport.
 | 
			
		||||
 *
 | 
			
		||||
 * This is only relevant if your window manager supports
 | 
			
		||||
 * _NET_DESKTOP_VIEWPORT 
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_desktop_viewport(const xdo_t *xdo, int *x_ret, int *y_ret);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the position of the current viewport.
 | 
			
		||||
 *
 | 
			
		||||
 * This is only relevant if your window manager supports
 | 
			
		||||
 * _NET_DESKTOP_VIEWPORT
 | 
			
		||||
 */
 | 
			
		||||
int xdo_set_desktop_viewport(const xdo_t *xdo, int x, int y);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Kill a window and the client owning it.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
int xdo_kill_window(const xdo_t *xdo, Window window);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Close a window without trying to kill the client.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
int xdo_close_window(const xdo_t *xdo, Window window);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Request that a window close, gracefully.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
int xdo_quit_window(const xdo_t *xdo, Window window);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Find a client window that is a parent of the window given
 | 
			
		||||
 */
 | 
			
		||||
#define XDO_FIND_PARENTS (0)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Find a client window that is a child of the window given
 | 
			
		||||
 */
 | 
			
		||||
#define XDO_FIND_CHILDREN (1)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Find a client window (child) in a given window. Useful if you get the
 | 
			
		||||
 * window manager's decorator window rather than the client window.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_find_window_client(const xdo_t *xdo, Window window, Window *window_ret,
 | 
			
		||||
                           int direction);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get a window's name, if any.
 | 
			
		||||
 *
 | 
			
		||||
 * @param window window to get the name of.
 | 
			
		||||
 * @param name_ret character pointer pointer where the address of the window name will be stored.
 | 
			
		||||
 * @param name_len_ret integer pointer where the length of the window name will be stored.
 | 
			
		||||
 * @param name_type integer pointer where the type (atom) of the window name will be stored.
 | 
			
		||||
 */
 | 
			
		||||
int xdo_get_window_name(const xdo_t *xdo, Window window, 
 | 
			
		||||
                        unsigned char **name_ret, int *name_len_ret,
 | 
			
		||||
                        int *name_type);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Disable an xdo feature.
 | 
			
		||||
 *
 | 
			
		||||
 * This function is mainly used by libxdo itself, however, you may find it useful
 | 
			
		||||
 * in your own applications.
 | 
			
		||||
 * 
 | 
			
		||||
 * @see XDO_FEATURES
 | 
			
		||||
 */
 | 
			
		||||
void xdo_disable_feature(xdo_t *xdo, int feature);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Enable an xdo feature.
 | 
			
		||||
 *
 | 
			
		||||
 * This function is mainly used by libxdo itself, however, you may find it useful
 | 
			
		||||
 * in your own applications.
 | 
			
		||||
 * 
 | 
			
		||||
 * @see XDO_FEATURES
 | 
			
		||||
 */
 | 
			
		||||
void xdo_enable_feature(xdo_t *xdo, int feature);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if a feature is enabled.
 | 
			
		||||
 *
 | 
			
		||||
 * This function is mainly used by libxdo itself, however, you may find it useful
 | 
			
		||||
 * in your own applications.
 | 
			
		||||
 * 
 | 
			
		||||
 * @see XDO_FEATURES
 | 
			
		||||
 */
 | 
			
		||||
int xdo_has_feature(xdo_t *xdo, int feature);
 | 
			
		||||
 | 
			
		||||
// Espanso-specific variants
 | 
			
		||||
 | 
			
		||||
KeySym fast_keysym_from_char(const xdo_t *xdo, wchar_t key);
 | 
			
		||||
void fast_charcodemap_from_char(const xdo_t *xdo, charcodemap_t *key);
 | 
			
		||||
void fast_charcodemap_from_keysym(const xdo_t *xdo, charcodemap_t *key, KeySym keysym);
 | 
			
		||||
void fast_init_xkeyevent(const xdo_t *xdo, XKeyEvent *xk);
 | 
			
		||||
void fast_send_key(const xdo_t *xdo, Window window, charcodemap_t *key,
 | 
			
		||||
                          int modstate, int is_press, useconds_t delay);
 | 
			
		||||
int fast_enter_text_window(const xdo_t *xdo, Window window, const char *string, useconds_t delay);
 | 
			
		||||
void fast_send_event(const xdo_t *xdo, Window window, int keycode, int pressed);
 | 
			
		||||
int fast_send_keysequence_window(const xdo_t *xdo, Window window,
 | 
			
		||||
                    const char *keysequence, useconds_t delay);
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
} /* extern "C" */
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif /* ifndef _XDO_H_ */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								espanso-inject/src/x11/xdotool/vendor/xdo_util.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								espanso-inject/src/x11/xdotool/vendor/xdo_util.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
/* xdo utility pieces 
 | 
			
		||||
 *
 | 
			
		||||
 * $Id$
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef _XDO_UTIL_H_
 | 
			
		||||
#define _XDO_UTIL_H_
 | 
			
		||||
 | 
			
		||||
#include "xdo.h"
 | 
			
		||||
 | 
			
		||||
/* human to Keysym string mapping */
 | 
			
		||||
static const char *symbol_map[] = {
 | 
			
		||||
  "alt", "Alt_L",
 | 
			
		||||
  "ctrl", "Control_L",
 | 
			
		||||
  "control", "Control_L",
 | 
			
		||||
  "meta", "Meta_L",
 | 
			
		||||
  "super", "Super_L",
 | 
			
		||||
  "shift", "Shift_L",
 | 
			
		||||
  "enter", "Return",
 | 
			
		||||
  "return", "Return",
 | 
			
		||||
  NULL, NULL,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif /* ifndef _XDO_UTIL_H_ */
 | 
			
		||||
| 
						 | 
				
			
			@ -143,6 +143,7 @@ impl<'a> super::engine::dispatch::executor::clipboard_injector::ClipboardParamsP
 | 
			
		|||
      restore_clipboard: active.preserve_clipboard(),
 | 
			
		||||
      restore_clipboard_delay: active.restore_clipboard_delay(),
 | 
			
		||||
      x11_use_xclip_backend: active.x11_use_xclip_backend(),
 | 
			
		||||
      x11_use_xdotool_backend: active.x11_use_xdotool_backend(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -164,6 +165,7 @@ impl<'a> super::engine::dispatch::executor::InjectParamsProvider for ConfigManag
 | 
			
		|||
      inject_delay: active.inject_delay(),
 | 
			
		||||
      key_delay: active.key_delay(),
 | 
			
		||||
      evdev_modifier_delay: active.evdev_modifier_delay(),
 | 
			
		||||
      x11_use_xdotool_backend: active.x11_use_xdotool_backend(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,7 @@ pub struct ClipboardParams {
 | 
			
		|||
  pub restore_clipboard: bool,
 | 
			
		||||
  pub restore_clipboard_delay: usize,
 | 
			
		||||
  pub x11_use_xclip_backend: bool,
 | 
			
		||||
  pub x11_use_xdotool_backend: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct ClipboardInjectorAdapter<'a> {
 | 
			
		||||
| 
						 | 
				
			
			@ -95,6 +96,7 @@ impl<'a> ClipboardInjectorAdapter<'a> {
 | 
			
		|||
      InjectionOptions {
 | 
			
		||||
        delay: params.paste_shortcut_event_delay as i32,
 | 
			
		||||
        disable_fast_inject: params.disable_x11_fast_inject,
 | 
			
		||||
        x11_use_xdotool_fallback: params.x11_use_xdotool_backend,
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
      },
 | 
			
		||||
    )?;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,6 +67,7 @@ impl<'a> TextInjector for EventInjectorAdapter<'a> {
 | 
			
		|||
        })
 | 
			
		||||
        .try_into()
 | 
			
		||||
        .unwrap(),
 | 
			
		||||
      x11_use_xdotool_fallback: params.x11_use_xdotool_backend,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // We don't use the lines() method because it skips emtpy lines, which is not what we want.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,6 +59,7 @@ impl<'a> KeyInjector for KeyInjectorAdapter<'a> {
 | 
			
		|||
        })
 | 
			
		||||
        .try_into()
 | 
			
		||||
        .unwrap(),
 | 
			
		||||
      x11_use_xdotool_fallback: params.x11_use_xdotool_backend,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let converted_keys: Vec<_> = keys.iter().map(convert_to_inject_key).collect();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,4 +34,5 @@ pub struct InjectParams {
 | 
			
		|||
  pub key_delay: Option<usize>,
 | 
			
		||||
  pub disable_x11_fast_inject: bool,
 | 
			
		||||
  pub evdev_modifier_delay: Option<usize>,
 | 
			
		||||
  pub x11_use_xdotool_backend: bool,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,5 +53,6 @@ generate_patchable_config!(
 | 
			
		|||
  win32_exclude_orphan_events -> bool,
 | 
			
		||||
  win32_keyboard_layout_cache_interval -> i64,
 | 
			
		||||
  x11_use_xclip_backend -> bool,
 | 
			
		||||
  x11_use_xdotool_backend -> bool,
 | 
			
		||||
  keyboard_layout -> Option<RMLVOConfig>
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user