Introduce wayland feature
This commit is contained in:
		
							parent
							
								
									a9d24d400d
								
							
						
					
					
						commit
						3737eed034
					
				| 
						 | 
				
			
			@ -5,6 +5,11 @@ authors = ["Federico Terzi <federico-terzi@users.noreply.github.com>"]
 | 
			
		|||
edition = "2018"
 | 
			
		||||
build="build.rs"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
# If the wayland feature is enabled, all X11 dependencies will be dropped
 | 
			
		||||
# and only EVDEV-based methods will be supported.
 | 
			
		||||
wayland = []
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
log = "0.4.14"
 | 
			
		||||
lazycell = "1.3.0"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,21 +39,27 @@ fn cc_config() {
 | 
			
		|||
  println!("cargo:rerun-if-changed=src/x11/native.h");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/evdev/native.cpp");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/evdev/native.h");
 | 
			
		||||
  cc::Build::new()
 | 
			
		||||
    .cpp(true)
 | 
			
		||||
    .include("src/x11/native.h")
 | 
			
		||||
    .file("src/x11/native.cpp")
 | 
			
		||||
    .compile("espansodetect");
 | 
			
		||||
 | 
			
		||||
  if cfg!(not(feature = "wayland")) {
 | 
			
		||||
    cc::Build::new()
 | 
			
		||||
      .cpp(true)
 | 
			
		||||
      .include("src/x11/native.h")
 | 
			
		||||
      .file("src/x11/native.cpp")
 | 
			
		||||
      .compile("espansodetect");
 | 
			
		||||
 | 
			
		||||
    println!("cargo:rustc-link-lib=static=espansodetect");
 | 
			
		||||
    println!("cargo:rustc-link-lib=dylib=X11");
 | 
			
		||||
    println!("cargo:rustc-link-lib=dylib=Xtst");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cc::Build::new()
 | 
			
		||||
    .cpp(true)
 | 
			
		||||
    .include("src/evdev/native.h")
 | 
			
		||||
    .file("src/evdev/native.cpp")
 | 
			
		||||
    .compile("espansodetectevdev");
 | 
			
		||||
 | 
			
		||||
  println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/");
 | 
			
		||||
  println!("cargo:rustc-link-lib=static=espansodetect");
 | 
			
		||||
  println!("cargo:rustc-link-lib=static=espansodetectevdev");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=X11");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=Xtst");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=xkbcommon");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,26 +1,32 @@
 | 
			
		|||
// This code is a port of the libxkbcommon "interactive-evdev.c" example
 | 
			
		||||
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
 | 
			
		||||
 | 
			
		||||
use std::ffi::CString;
 | 
			
		||||
 | 
			
		||||
use scopeguard::ScopeGuard;
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
  context::Context,
 | 
			
		||||
  ffi::{xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, XKB_KEYMAP_COMPILE_NO_FLAGS},
 | 
			
		||||
};
 | 
			
		||||
use crate::KeyboardConfig;
 | 
			
		||||
 | 
			
		||||
use super::{context::Context, ffi::{XKB_KEYMAP_COMPILE_NO_FLAGS, xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, xkb_rule_names}};
 | 
			
		||||
 | 
			
		||||
pub struct Keymap {
 | 
			
		||||
  keymap: *mut xkb_keymap,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Keymap {
 | 
			
		||||
  pub fn new(context: &Context) -> Result<Keymap> {
 | 
			
		||||
  pub fn new(context: &Context, rmlvo: Option<KeyboardConfig>) -> Result<Keymap> {
 | 
			
		||||
    let names = rmlvo.map(|rmlvo| {
 | 
			
		||||
      Self::generate_names(rmlvo)
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let names_ptr = names.map_or(std::ptr::null(), |names| &names);
 | 
			
		||||
    let raw_keymap = unsafe {
 | 
			
		||||
      xkb_keymap_new_from_names(
 | 
			
		||||
        context.get_handle(),
 | 
			
		||||
        std::ptr::null(),
 | 
			
		||||
        names_ptr,
 | 
			
		||||
        XKB_KEYMAP_COMPILE_NO_FLAGS,
 | 
			
		||||
      )
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +46,22 @@ impl Keymap {
 | 
			
		|||
  pub fn get_handle(&self) -> *mut xkb_keymap {
 | 
			
		||||
    self.keymap
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn generate_names(rmlvo: KeyboardConfig) -> xkb_rule_names {
 | 
			
		||||
    let rules = rmlvo.rules.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
 | 
			
		||||
    let model = rmlvo.model.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
 | 
			
		||||
    let layout = rmlvo.layout.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
 | 
			
		||||
    let variant = rmlvo.variant.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
 | 
			
		||||
    let options = rmlvo.options.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
 | 
			
		||||
 | 
			
		||||
    xkb_rule_names {
 | 
			
		||||
      rules: rules.map_or(std::ptr::null(), |s| s.as_ptr()),
 | 
			
		||||
      model: model.map_or(std::ptr::null(), |s| s.as_ptr()),
 | 
			
		||||
      layout: layout.map_or(std::ptr::null(), |s| s.as_ptr()),
 | 
			
		||||
      variant: variant.map_or(std::ptr::null(), |s| s.as_ptr()),
 | 
			
		||||
      options: options.map_or(std::ptr::null(), |s| s.as_ptr()),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Drop for Keymap {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ use libc::{
 | 
			
		|||
use log::{error, trace};
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
use crate::event::Status::*;
 | 
			
		||||
use crate::{KeyboardConfig, Source, SourceCallback, SourceCreationOptions, event::Status::*};
 | 
			
		||||
use crate::event::Variant::*;
 | 
			
		||||
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
 | 
			
		||||
use crate::event::{Key::*, MouseButton, MouseEvent};
 | 
			
		||||
| 
						 | 
				
			
			@ -49,27 +49,30 @@ const BTN_MIDDLE: u16 = 0x112;
 | 
			
		|||
const BTN_SIDE: u16 = 0x113;
 | 
			
		||||
const BTN_EXTRA: u16 = 0x114;
 | 
			
		||||
 | 
			
		||||
pub type EVDEVSourceCallback = Box<dyn Fn(InputEvent)>;
 | 
			
		||||
pub struct EVDEVSource {
 | 
			
		||||
  devices: Vec<Device>,
 | 
			
		||||
 | 
			
		||||
  _keyboard_rmlvo: Option<KeyboardConfig>,
 | 
			
		||||
  _context: LazyCell<Context>,
 | 
			
		||||
  _keymap: LazyCell<Keymap>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::new_without_default)]
 | 
			
		||||
impl EVDEVSource {
 | 
			
		||||
  pub fn new() -> EVDEVSource {
 | 
			
		||||
  pub fn new(options: SourceCreationOptions) -> EVDEVSource {
 | 
			
		||||
    Self {
 | 
			
		||||
      devices: Vec::new(),
 | 
			
		||||
      _context: LazyCell::new(),
 | 
			
		||||
      _keymap: LazyCell::new(),
 | 
			
		||||
      _keyboard_rmlvo: options.evdev_keyboard_rmlvo,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  pub fn initialize(&mut self) -> Result<()> {
 | 
			
		||||
impl Source for EVDEVSource {
 | 
			
		||||
  fn initialize(&mut self) -> Result<()> {
 | 
			
		||||
    let context = Context::new().expect("unable to obtain xkb context");
 | 
			
		||||
    let keymap = Keymap::new(&context).expect("unable to create xkb keymap");
 | 
			
		||||
    let keymap = Keymap::new(&context, self._keyboard_rmlvo.clone()).expect("unable to create xkb keymap");
 | 
			
		||||
 | 
			
		||||
    match get_devices(&keymap) {
 | 
			
		||||
      Ok(devices) => self.devices = devices,
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +100,7 @@ impl EVDEVSource {
 | 
			
		|||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn eventloop(&self, event_callback: EVDEVSourceCallback) {
 | 
			
		||||
  fn eventloop(&self, event_callback: SourceCallback) {
 | 
			
		||||
    if self.devices.is_empty() {
 | 
			
		||||
      panic!("can't start eventloop without evdev devices");
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,12 +17,16 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use log::info;
 | 
			
		||||
 | 
			
		||||
pub mod event;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "windows")]
 | 
			
		||||
pub mod win32;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
#[cfg(not(feature = "wayland"))]
 | 
			
		||||
pub mod x11;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
| 
						 | 
				
			
			@ -34,3 +38,73 @@ pub mod mac;
 | 
			
		|||
#[cfg(target_os = "macos")]
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate lazy_static;
 | 
			
		||||
 | 
			
		||||
pub type SourceCallback = Box<dyn Fn(event::InputEvent)>;
 | 
			
		||||
 | 
			
		||||
pub trait Source {
 | 
			
		||||
  fn initialize(&mut self) -> Result<()>;
 | 
			
		||||
  fn eventloop(&self, event_callback: SourceCallback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct SourceCreationOptions {
 | 
			
		||||
  // Only relevant in X11 Linux systems, use the EVDEV backend instead of X11.
 | 
			
		||||
  use_evdev: bool,
 | 
			
		||||
  
 | 
			
		||||
  // Can be used to overwrite the keymap configuration
 | 
			
		||||
  // used by espanso to inject key presses.
 | 
			
		||||
  evdev_keyboard_rmlvo: Option<KeyboardConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This struct identifies the keyboard layout that
 | 
			
		||||
// should be used by EVDEV when loading the keymap.
 | 
			
		||||
// For more information: https://xkbcommon.org/doc/current/structxkb__rule__names.html
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct KeyboardConfig {
 | 
			
		||||
  pub rules: Option<String>,
 | 
			
		||||
  pub model: Option<String>,
 | 
			
		||||
  pub layout: Option<String>,
 | 
			
		||||
  pub variant: Option<String>,
 | 
			
		||||
  pub options: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for SourceCreationOptions {
 | 
			
		||||
  fn default() -> Self {
 | 
			
		||||
    Self {
 | 
			
		||||
      use_evdev: false,
 | 
			
		||||
      evdev_keyboard_rmlvo: None,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "windows")]
 | 
			
		||||
pub fn get_source(_options: SourceCreationOptions) -> Result<Box<dyn Source>> {
 | 
			
		||||
  info!("using Win32Source");
 | 
			
		||||
  Ok(Box::new(win32::Win32Source::new()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
pub fn get_source(_options: SourceCreationOptions) -> Result<Box<dyn Source>> {
 | 
			
		||||
  info!("using CocoaSource");
 | 
			
		||||
  Ok(Box::new(mac::CocoaSource::new()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
#[cfg(not(feature = "wayland"))]
 | 
			
		||||
pub fn get_source(options: SourceCreationOptions) -> Result<Box<dyn Source>> {
 | 
			
		||||
  if options.use_evdev {
 | 
			
		||||
    info!("using EVDEVSource");
 | 
			
		||||
    Ok(Box::new(evdev::EVDEVSource::new(options)))
 | 
			
		||||
  } else {
 | 
			
		||||
    info!("using X11Source");
 | 
			
		||||
    Ok(Box::new(x11::X11Source::new()))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
#[cfg(feature = "wayland")]
 | 
			
		||||
pub fn get_source(options: SourceCreationOptions) -> Result<Box<dyn Source>> {
 | 
			
		||||
  info!("using EVDEVSource");
 | 
			
		||||
  Ok(Box::new(evdev::EVDEVSource::new(options)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,7 +86,6 @@ extern "C" fn native_callback(raw_event: RawInputEvent) {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type CocoaSourceCallback = Box<dyn Fn(InputEvent)>;
 | 
			
		||||
pub struct CocoaSource {
 | 
			
		||||
  receiver: LazyCell<Receiver<InputEvent>>,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +98,11 @@ impl CocoaSource {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn initialize(&mut self) -> Result<()> {
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Source for CocoaSource {
 | 
			
		||||
  fn initialize(&mut self) -> Result<()> {
 | 
			
		||||
    let (sender, receiver) = channel();
 | 
			
		||||
 | 
			
		||||
    // Set the global sender
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +123,7 @@ impl CocoaSource {
 | 
			
		|||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn eventloop(&self, event_callback: CocoaSourceCallback) {
 | 
			
		||||
  fn eventloop(&self, event_callback: SourceCallback) {
 | 
			
		||||
    if let Some(receiver) = self.receiver.borrow() {
 | 
			
		||||
      loop {
 | 
			
		||||
        let event = receiver.recv();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -75,10 +75,9 @@ extern "C" {
 | 
			
		|||
  pub fn detect_destroy(window: *const c_void) -> i32;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type Win32SourceCallback = Box<dyn Fn(InputEvent)>;
 | 
			
		||||
pub struct Win32Source {
 | 
			
		||||
  handle: *mut c_void,
 | 
			
		||||
  callback: LazyCell<Win32SourceCallback>,
 | 
			
		||||
  callback: LazyCell<SourceCallback>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::new_without_default)]
 | 
			
		||||
| 
						 | 
				
			
			@ -89,8 +88,10 @@ impl Win32Source {
 | 
			
		|||
      callback: LazyCell::new(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  pub fn initialize(&mut self) -> Result<()> {
 | 
			
		||||
impl Source for Win32Source {
 | 
			
		||||
  fn initialize(&mut self) -> Result<()> {
 | 
			
		||||
    let mut error_code = 0;
 | 
			
		||||
    let handle = unsafe { detect_initialize(self as *const Win32Source, &mut error_code) };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +109,7 @@ impl Win32Source {
 | 
			
		|||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn eventloop(&self, event_callback: Win32SourceCallback) {
 | 
			
		||||
  fn eventloop(&self, event_callback: SourceCallback) {
 | 
			
		||||
    if self.handle.is_null() {
 | 
			
		||||
      panic!("Attempt to start Win32Source eventloop without initialization");
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ use log::{error, trace, warn};
 | 
			
		|||
use anyhow::Result;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
use crate::event::Status::*;
 | 
			
		||||
use crate::{Source, SourceCallback, event::Status::*};
 | 
			
		||||
use crate::event::Variant::*;
 | 
			
		||||
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
 | 
			
		||||
use crate::event::{Key::*, MouseButton, MouseEvent};
 | 
			
		||||
| 
						 | 
				
			
			@ -70,10 +70,9 @@ extern "C" {
 | 
			
		|||
  pub fn detect_destroy(window: *const c_void) -> i32;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type X11SourceCallback = Box<dyn Fn(InputEvent)>;
 | 
			
		||||
pub struct X11Source {
 | 
			
		||||
  handle: *mut c_void,
 | 
			
		||||
  callback: LazyCell<X11SourceCallback>,
 | 
			
		||||
  callback: LazyCell<SourceCallback>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::new_without_default)]
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +88,11 @@ impl X11Source {
 | 
			
		|||
    unsafe { detect_check_x11() != 0 }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn initialize(&mut self) -> Result<()> {
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Source for X11Source {
 | 
			
		||||
  fn initialize(&mut self) -> Result<()> {
 | 
			
		||||
    let mut error_code = 0;
 | 
			
		||||
    let handle = unsafe { detect_initialize(self as *const X11Source, &mut error_code) };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +114,7 @@ impl X11Source {
 | 
			
		|||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn eventloop(&self, event_callback: X11SourceCallback) {
 | 
			
		||||
  fn eventloop(&self, event_callback: SourceCallback) {
 | 
			
		||||
    if self.handle.is_null() {
 | 
			
		||||
      panic!("Attempt to start X11Source eventloop without initialization");
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,11 @@ authors = ["Federico Terzi <federico-terzi@users.noreply.github.com>"]
 | 
			
		|||
edition = "2018"
 | 
			
		||||
build="build.rs"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
# If the wayland feature is enabled, all X11 dependencies will be dropped
 | 
			
		||||
# and only EVDEV-based methods will be supported.
 | 
			
		||||
wayland = []
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
log = "0.4.14"
 | 
			
		||||
lazycell = "1.3.0"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,27 +35,21 @@ fn cc_config() {
 | 
			
		|||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
fn cc_config() {
 | 
			
		||||
  // println!("cargo:rerun-if-changed=src/x11/native.cpp");
 | 
			
		||||
  // println!("cargo:rerun-if-changed=src/x11/native.h");
 | 
			
		||||
  // println!("cargo:rerun-if-changed=src/evdev/native.cpp");
 | 
			
		||||
  // println!("cargo:rerun-if-changed=src/evdev/native.h");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/evdev/native.h");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/evdev/native.c");
 | 
			
		||||
  // cc::Build::new()
 | 
			
		||||
  //   .cpp(true)
 | 
			
		||||
  //   .include("src/x11/native.h")
 | 
			
		||||
  //   .file("src/x11/native.cpp")
 | 
			
		||||
  //   .compile("espansoinject");
 | 
			
		||||
  cc::Build::new()
 | 
			
		||||
    .include("src/evdev/native.h")
 | 
			
		||||
    .file("src/evdev/native.c")
 | 
			
		||||
    .compile("espansoinjectev");
 | 
			
		||||
 | 
			
		||||
  println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/");
 | 
			
		||||
  // println!("cargo:rustc-link-lib=static=espansoinject");
 | 
			
		||||
  println!("cargo:rustc-link-lib=static=espansoinjectev");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=X11");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=Xtst");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=xkbcommon");
 | 
			
		||||
 | 
			
		||||
  if cfg!(not(feature = "wayland")) {
 | 
			
		||||
    println!("cargo:rustc-link-lib=dylib=X11");
 | 
			
		||||
    println!("cargo:rustc-link-lib=dylib=Xtst");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@
 | 
			
		|||
 */
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use log::info;
 | 
			
		||||
 | 
			
		||||
pub mod keys;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +26,7 @@ pub mod keys;
 | 
			
		|||
mod win32;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
#[cfg(not(feature = "wayland"))]
 | 
			
		||||
mod x11;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +79,7 @@ impl Default for InjectionOptions {
 | 
			
		|||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct InjectorCreationOptions {
 | 
			
		||||
  // Only relevant in Linux systems
 | 
			
		||||
  // Only relevant in X11 Linux systems, use the EVDEV backend instead of X11.
 | 
			
		||||
  use_evdev: bool,
 | 
			
		||||
 | 
			
		||||
  // Overwrite the list of modifiers to be scanned when 
 | 
			
		||||
| 
						 | 
				
			
			@ -116,19 +118,33 @@ impl Default for InjectorCreationOptions {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "windows")]
 | 
			
		||||
pub fn get_injector(_options: InjectorOptions) -> impl Injector {
 | 
			
		||||
  win32::Win32Injector::new()
 | 
			
		||||
pub fn get_injector(_options: InjectorOptions) -> Result<Box<dyn Injector>> {
 | 
			
		||||
  info!("using Win32Injector");
 | 
			
		||||
  Ok(Box::new(win32::Win32Injector::new()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
pub fn get_injector(_options: InjectorOptions) -> impl Injector {
 | 
			
		||||
  mac::MacInjector::new()
 | 
			
		||||
pub fn get_injector(_options: InjectorOptions) -> Result<Box<dyn Injector>> {
 | 
			
		||||
  info!("using MacInjector");
 | 
			
		||||
  Ok(Box::new(mac::MacInjector::new()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
pub fn get_injector(options: InjectorCreationOptions) -> Result<impl Injector> {
 | 
			
		||||
  // TODO: differenciate based on the options
 | 
			
		||||
  //x11::X11Injector::new()
 | 
			
		||||
  evdev::EVDEVInjector::new(options)
 | 
			
		||||
#[cfg(not(feature = "wayland"))]
 | 
			
		||||
pub fn get_injector(options: InjectorCreationOptions) -> Result<Box<dyn Injector>> {
 | 
			
		||||
  if options.use_evdev {
 | 
			
		||||
    info!("using EVDEVInjector");
 | 
			
		||||
    Ok(Box::new(evdev::EVDEVInjector::new(options)?))
 | 
			
		||||
  } else {
 | 
			
		||||
    info!("using X11Injector");
 | 
			
		||||
    Ok(Box::new(x11::X11Injector::new()?))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
#[cfg(feature = "wayland")]
 | 
			
		||||
pub fn get_injector(options: InjectorCreationOptions) -> Result<Box<dyn Injector>> {
 | 
			
		||||
  info!("using EVDEVInjector");
 | 
			
		||||
  Ok(Box::new(evdev::EVDEVInjector::new(options)?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ use ffi::{Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay,
 | 
			
		|||
use log::error;
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use crate::linux::raw_keys::{convert_key_to_sym, convert_to_sym_array};
 | 
			
		||||
use crate::linux::raw_keys::{convert_to_sym_array};
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
use crate::{keys, InjectionOptions, Injector};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,6 @@
 | 
			
		|||
use icons::TrayIcon;
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
 | 
			
		||||
pub mod event;
 | 
			
		||||
pub mod icons;
 | 
			
		||||
pub mod menu;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,3 +13,52 @@ pub mod linux;
 | 
			
		|||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
pub mod mac;
 | 
			
		||||
 | 
			
		||||
pub trait UIRemote {
 | 
			
		||||
  fn update_tray_icon(&self, icon: TrayIcon);
 | 
			
		||||
  fn show_notification(&self, message: &str);
 | 
			
		||||
  fn show_context_menu(&self, menu: &menu::Menu);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type UIEventCallback = Box<dyn Fn(event::UIEvent)>;
 | 
			
		||||
pub trait UIEventLoop {
 | 
			
		||||
  fn initialize(&mut self);
 | 
			
		||||
  fn run(&self, event_callback: UIEventCallback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct UIOptions {
 | 
			
		||||
  pub show_icon: bool,
 | 
			
		||||
  pub icon_paths: Vec<(TrayIcon, String)>,
 | 
			
		||||
  pub notification_icon_path: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for UIOptions {
 | 
			
		||||
  fn default() -> Self {
 | 
			
		||||
    Self {
 | 
			
		||||
      show_icon: true,
 | 
			
		||||
      icon_paths: Vec::new(),
 | 
			
		||||
      notification_icon_path: None,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "windows")]
 | 
			
		||||
pub fn create_ui(_options: UIOptions) -> Result<Box<dyn Injector>> {
 | 
			
		||||
  // TODO: refactor
 | 
			
		||||
  Ok(Box::new(win32::Win32Injector::new()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
pub fn create_ui(_options: UIOptions) -> Result<Box<dyn Injector>> {
 | 
			
		||||
  // TODO: refactor
 | 
			
		||||
  Ok(Box::new(mac::MacInjector::new()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn UIEventLoop>)> {
 | 
			
		||||
  // TODO: here we could avoid panicking and instead return a good result
 | 
			
		||||
  let (remote, eventloop) = linux::create(linux::LinuxUIOptions {
 | 
			
		||||
    notification_icon_path: options.notification_icon_path.expect("missing notification icon path")
 | 
			
		||||
  });
 | 
			
		||||
  Ok((Box::new(remote), Box::new(eventloop)))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,6 +3,8 @@ use notify_rust::Notification;
 | 
			
		|||
use std::sync::mpsc;
 | 
			
		||||
use std::sync::mpsc::{Receiver, Sender};
 | 
			
		||||
 | 
			
		||||
use crate::{UIEventLoop, UIRemote};
 | 
			
		||||
 | 
			
		||||
pub struct LinuxUIOptions {
 | 
			
		||||
  pub notification_icon_path: String,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -30,8 +32,14 @@ impl LinuxRemote {
 | 
			
		|||
  pub fn stop(&self) -> anyhow::Result<()> {
 | 
			
		||||
    Ok(self.tx.send(())?)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  pub fn show_notification(&self, message: &str) {
 | 
			
		||||
impl UIRemote for LinuxRemote {
 | 
			
		||||
  fn update_tray_icon(&self, _: crate::icons::TrayIcon) {
 | 
			
		||||
    // NOOP on linux
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn show_notification(&self, message: &str) {
 | 
			
		||||
    if let Err(error) = Notification::new()
 | 
			
		||||
      .summary("Espanso")
 | 
			
		||||
      .body(message)
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +49,10 @@ impl LinuxRemote {
 | 
			
		|||
      error!("Unable to show notification: {}", error);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn show_context_menu(&self, _: &crate::menu::Menu) {
 | 
			
		||||
    // NOOP on linux
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct LinuxEventLoop {
 | 
			
		||||
| 
						 | 
				
			
			@ -51,12 +63,14 @@ impl LinuxEventLoop {
 | 
			
		|||
  pub fn new(rx: Receiver<()>) -> Self {
 | 
			
		||||
    Self { rx }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  pub fn initialize(&self) {
 | 
			
		||||
impl UIEventLoop for LinuxEventLoop {
 | 
			
		||||
  fn initialize(&mut self) {
 | 
			
		||||
    // NOOP on linux
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn run(&self) {
 | 
			
		||||
  fn run(&self, _: crate::UIEventCallback) {
 | 
			
		||||
    // We don't run an event loop on Linux as there is no tray icon or application window needed.
 | 
			
		||||
    // Thad said, we still need a way to block this method, and thus we use a channel
 | 
			
		||||
    self.rx.recv().expect("Unable to block the LinuxEventLoop");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,8 +110,6 @@ pub fn create(options: Win32UIOptions) -> (Win32Remote, Win32EventLoop) {
 | 
			
		|||
  (remote, eventloop)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type Win32UIEventCallback = Box<dyn Fn(UIEvent)>;
 | 
			
		||||
 | 
			
		||||
pub struct Win32EventLoop {
 | 
			
		||||
  handle: Arc<AtomicPtr<c_void>>,
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,11 @@ readme = "README.md"
 | 
			
		|||
homepage = "https://github.com/federico-terzi/espanso"
 | 
			
		||||
edition = "2018"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
# If the wayland feature is enabled, all X11 dependencies will be dropped
 | 
			
		||||
# and only EVDEV-based methods will be supported.
 | 
			
		||||
wayland = ["espanso-detect/wayland", "espanso-inject/wayland"]
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
espanso-detect = { path = "../espanso-detect" } 
 | 
			
		||||
espanso-ui = { path = "../espanso-ui" } 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
use espanso_detect::event::{InputEvent, Status};
 | 
			
		||||
use espanso_detect::{event::{InputEvent, Status}, get_source};
 | 
			
		||||
use espanso_inject::{get_injector, Injector, keys};
 | 
			
		||||
use espanso_ui::{event::UIEvent::*, icons::TrayIcon, menu::*};
 | 
			
		||||
use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode};
 | 
			
		||||
| 
						 | 
				
			
			@ -48,18 +48,17 @@ fn main() {
 | 
			
		|||
  //   show_icon: true,
 | 
			
		||||
  //   icon_paths: &icon_paths,
 | 
			
		||||
  // });
 | 
			
		||||
  let (remote, mut eventloop) = espanso_ui::linux::create(espanso_ui::linux::LinuxUIOptions {
 | 
			
		||||
    notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png".to_string(),
 | 
			
		||||
  });
 | 
			
		||||
  let (remote, mut eventloop) = espanso_ui::create_ui(espanso_ui::UIOptions {
 | 
			
		||||
    notification_icon_path: Some(r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png".to_string()),
 | 
			
		||||
    ..Default::default()
 | 
			
		||||
  }).unwrap();
 | 
			
		||||
 | 
			
		||||
  eventloop.initialize();
 | 
			
		||||
 | 
			
		||||
  let handle = std::thread::spawn(move || {
 | 
			
		||||
    let injector = get_injector(Default::default()).unwrap();
 | 
			
		||||
    //let mut source = espanso_detect::win32::Win32Source::new();
 | 
			
		||||
    let mut source = espanso_detect::x11::X11Source::new();
 | 
			
		||||
    //let mut source = espanso_detect::mac::CocoaSource::new();
 | 
			
		||||
    source.initialize();
 | 
			
		||||
    let mut source = get_source(Default::default()).unwrap();
 | 
			
		||||
    source.initialize().unwrap();
 | 
			
		||||
    source.eventloop(Box::new(move |event: InputEvent| {
 | 
			
		||||
      println!("ev {:?}", event);
 | 
			
		||||
      match event {
 | 
			
		||||
| 
						 | 
				
			
			@ -77,30 +76,29 @@ fn main() {
 | 
			
		|||
    }));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  eventloop.run();
 | 
			
		||||
  // eventloop.run(Box::new(move |event| {
 | 
			
		||||
  //   println!("ui {:?}", event);
 | 
			
		||||
  //   let menu = Menu::from(vec![
 | 
			
		||||
  //     MenuItem::Simple(SimpleMenuItem::new("open", "Open")),
 | 
			
		||||
  //     MenuItem::Separator,
 | 
			
		||||
  //     MenuItem::Sub(SubMenuItem::new(
 | 
			
		||||
  //       "Sub",
 | 
			
		||||
  //       vec![
 | 
			
		||||
  //         MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")),
 | 
			
		||||
  //         MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")),
 | 
			
		||||
  //       ],
 | 
			
		||||
  //     )),
 | 
			
		||||
  //   ])
 | 
			
		||||
  //   .unwrap();
 | 
			
		||||
  //   match event {
 | 
			
		||||
  //     TrayIconClick => {
 | 
			
		||||
  //       remote.show_context_menu(&menu);
 | 
			
		||||
  //     }
 | 
			
		||||
  //     ContextMenuClick(raw_id) => {
 | 
			
		||||
  //       //remote.update_tray_icon(TrayIcon::Disabled);
 | 
			
		||||
  //       remote.show_notification("Hello there!");
 | 
			
		||||
  //       println!("item {:?}", menu.get_item_id(raw_id));
 | 
			
		||||
  //     }
 | 
			
		||||
  //   }
 | 
			
		||||
  // }));
 | 
			
		||||
  eventloop.run(Box::new(move |event| {
 | 
			
		||||
    println!("ui {:?}", event);
 | 
			
		||||
    let menu = Menu::from(vec![
 | 
			
		||||
      MenuItem::Simple(SimpleMenuItem::new("open", "Open")),
 | 
			
		||||
      MenuItem::Separator,
 | 
			
		||||
      MenuItem::Sub(SubMenuItem::new(
 | 
			
		||||
        "Sub",
 | 
			
		||||
        vec![
 | 
			
		||||
          MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")),
 | 
			
		||||
          MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")),
 | 
			
		||||
        ],
 | 
			
		||||
      )),
 | 
			
		||||
    ])
 | 
			
		||||
    .unwrap();
 | 
			
		||||
    match event {
 | 
			
		||||
      TrayIconClick => {
 | 
			
		||||
        remote.show_context_menu(&menu);
 | 
			
		||||
      }
 | 
			
		||||
      ContextMenuClick(raw_id) => {
 | 
			
		||||
        //remote.update_tray_icon(TrayIcon::Disabled);
 | 
			
		||||
        remote.show_notification("Hello there!");
 | 
			
		||||
        println!("item {:?}", menu.get_item_id(raw_id));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user