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" | edition = "2018" | ||||||
| build="build.rs" | 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] | [dependencies] | ||||||
| log = "0.4.14" | log = "0.4.14" | ||||||
| lazycell = "1.3.0" | 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/x11/native.h"); | ||||||
|   println!("cargo:rerun-if-changed=src/evdev/native.cpp"); |   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"); | ||||||
|   cc::Build::new() | 
 | ||||||
|     .cpp(true) |   if cfg!(not(feature = "wayland")) { | ||||||
|     .include("src/x11/native.h") |     cc::Build::new() | ||||||
|     .file("src/x11/native.cpp") |       .cpp(true) | ||||||
|     .compile("espansodetect"); |       .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() |   cc::Build::new() | ||||||
|     .cpp(true) |     .cpp(true) | ||||||
|     .include("src/evdev/native.h") |     .include("src/evdev/native.h") | ||||||
|     .file("src/evdev/native.cpp") |     .file("src/evdev/native.cpp") | ||||||
|     .compile("espansodetectevdev"); |     .compile("espansodetectevdev"); | ||||||
|  | 
 | ||||||
|   println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/"); |   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=static=espansodetectevdev"); | ||||||
|   println!("cargo:rustc-link-lib=dylib=X11"); |  | ||||||
|   println!("cargo:rustc-link-lib=dylib=Xtst"); |  | ||||||
|   println!("cargo:rustc-link-lib=dylib=xkbcommon"); |   println!("cargo:rustc-link-lib=dylib=xkbcommon"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,26 +1,32 @@ | ||||||
| // This code is a port of the libxkbcommon "interactive-evdev.c" example
 | // This code is a port of the libxkbcommon "interactive-evdev.c" example
 | ||||||
| // https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
 | // https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
 | ||||||
| 
 | 
 | ||||||
|  | use std::ffi::CString; | ||||||
|  | 
 | ||||||
| use scopeguard::ScopeGuard; | use scopeguard::ScopeGuard; | ||||||
| 
 | 
 | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use thiserror::Error; | use thiserror::Error; | ||||||
| 
 | 
 | ||||||
| use super::{ | use crate::KeyboardConfig; | ||||||
|   context::Context, | 
 | ||||||
|   ffi::{xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, XKB_KEYMAP_COMPILE_NO_FLAGS}, | 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 { | pub struct Keymap { | ||||||
|   keymap: *mut xkb_keymap, |   keymap: *mut xkb_keymap, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl 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 { |     let raw_keymap = unsafe { | ||||||
|       xkb_keymap_new_from_names( |       xkb_keymap_new_from_names( | ||||||
|         context.get_handle(), |         context.get_handle(), | ||||||
|         std::ptr::null(), |         names_ptr, | ||||||
|         XKB_KEYMAP_COMPILE_NO_FLAGS, |         XKB_KEYMAP_COMPILE_NO_FLAGS, | ||||||
|       ) |       ) | ||||||
|     }; |     }; | ||||||
|  | @ -40,6 +46,22 @@ impl Keymap { | ||||||
|   pub fn get_handle(&self) -> *mut xkb_keymap { |   pub fn get_handle(&self) -> *mut xkb_keymap { | ||||||
|     self.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 { | impl Drop for Keymap { | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ use libc::{ | ||||||
| use log::{error, trace}; | use log::{error, trace}; | ||||||
| use thiserror::Error; | use thiserror::Error; | ||||||
| 
 | 
 | ||||||
| use crate::event::Status::*; | use crate::{KeyboardConfig, Source, SourceCallback, SourceCreationOptions, event::Status::*}; | ||||||
| use crate::event::Variant::*; | use crate::event::Variant::*; | ||||||
| use crate::event::{InputEvent, Key, KeyboardEvent, Variant}; | use crate::event::{InputEvent, Key, KeyboardEvent, Variant}; | ||||||
| use crate::event::{Key::*, MouseButton, MouseEvent}; | use crate::event::{Key::*, MouseButton, MouseEvent}; | ||||||
|  | @ -49,27 +49,30 @@ const BTN_MIDDLE: u16 = 0x112; | ||||||
| const BTN_SIDE: u16 = 0x113; | const BTN_SIDE: u16 = 0x113; | ||||||
| const BTN_EXTRA: u16 = 0x114; | const BTN_EXTRA: u16 = 0x114; | ||||||
| 
 | 
 | ||||||
| pub type EVDEVSourceCallback = Box<dyn Fn(InputEvent)>; |  | ||||||
| pub struct EVDEVSource { | pub struct EVDEVSource { | ||||||
|   devices: Vec<Device>, |   devices: Vec<Device>, | ||||||
| 
 | 
 | ||||||
|  |   _keyboard_rmlvo: Option<KeyboardConfig>, | ||||||
|   _context: LazyCell<Context>, |   _context: LazyCell<Context>, | ||||||
|   _keymap: LazyCell<Keymap>, |   _keymap: LazyCell<Keymap>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[allow(clippy::new_without_default)] | #[allow(clippy::new_without_default)] | ||||||
| impl EVDEVSource { | impl EVDEVSource { | ||||||
|   pub fn new() -> EVDEVSource { |   pub fn new(options: SourceCreationOptions) -> EVDEVSource { | ||||||
|     Self { |     Self { | ||||||
|       devices: Vec::new(), |       devices: Vec::new(), | ||||||
|       _context: LazyCell::new(), |       _context: LazyCell::new(), | ||||||
|       _keymap: 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 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) { |     match get_devices(&keymap) { | ||||||
|       Ok(devices) => self.devices = devices, |       Ok(devices) => self.devices = devices, | ||||||
|  | @ -97,7 +100,7 @@ impl EVDEVSource { | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   pub fn eventloop(&self, event_callback: EVDEVSourceCallback) { |   fn eventloop(&self, event_callback: SourceCallback) { | ||||||
|     if self.devices.is_empty() { |     if self.devices.is_empty() { | ||||||
|       panic!("can't start eventloop without evdev devices"); |       panic!("can't start eventloop without evdev devices"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -17,12 +17,16 @@ | ||||||
|  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 |  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | use anyhow::Result; | ||||||
|  | use log::info; | ||||||
|  | 
 | ||||||
| pub mod event; | pub mod event; | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "windows")] | #[cfg(target_os = "windows")] | ||||||
| pub mod win32; | pub mod win32; | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
|  | #[cfg(not(feature = "wayland"))] | ||||||
| pub mod x11; | pub mod x11; | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
|  | @ -34,3 +38,73 @@ pub mod mac; | ||||||
| #[cfg(target_os = "macos")] | #[cfg(target_os = "macos")] | ||||||
| #[macro_use] | #[macro_use] | ||||||
| extern crate lazy_static; | 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 { | pub struct CocoaSource { | ||||||
|   receiver: LazyCell<Receiver<InputEvent>>, |   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(); |     let (sender, receiver) = channel(); | ||||||
| 
 | 
 | ||||||
|     // Set the global sender
 |     // Set the global sender
 | ||||||
|  | @ -120,7 +123,7 @@ impl CocoaSource { | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   pub fn eventloop(&self, event_callback: CocoaSourceCallback) { |   fn eventloop(&self, event_callback: SourceCallback) { | ||||||
|     if let Some(receiver) = self.receiver.borrow() { |     if let Some(receiver) = self.receiver.borrow() { | ||||||
|       loop { |       loop { | ||||||
|         let event = receiver.recv(); |         let event = receiver.recv(); | ||||||
|  |  | ||||||
|  | @ -75,10 +75,9 @@ extern "C" { | ||||||
|   pub fn detect_destroy(window: *const c_void) -> i32; |   pub fn detect_destroy(window: *const c_void) -> i32; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub type Win32SourceCallback = Box<dyn Fn(InputEvent)>; |  | ||||||
| pub struct Win32Source { | pub struct Win32Source { | ||||||
|   handle: *mut c_void, |   handle: *mut c_void, | ||||||
|   callback: LazyCell<Win32SourceCallback>, |   callback: LazyCell<SourceCallback>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[allow(clippy::new_without_default)] | #[allow(clippy::new_without_default)] | ||||||
|  | @ -89,8 +88,10 @@ impl Win32Source { | ||||||
|       callback: LazyCell::new(), |       callback: LazyCell::new(), | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|   pub fn initialize(&mut self) -> Result<()> { | impl Source for Win32Source { | ||||||
|  |   fn initialize(&mut self) -> Result<()> { | ||||||
|     let mut error_code = 0; |     let mut error_code = 0; | ||||||
|     let handle = unsafe { detect_initialize(self as *const Win32Source, &mut error_code) }; |     let handle = unsafe { detect_initialize(self as *const Win32Source, &mut error_code) }; | ||||||
| 
 | 
 | ||||||
|  | @ -108,7 +109,7 @@ impl Win32Source { | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   pub fn eventloop(&self, event_callback: Win32SourceCallback) { |   fn eventloop(&self, event_callback: SourceCallback) { | ||||||
|     if self.handle.is_null() { |     if self.handle.is_null() { | ||||||
|       panic!("Attempt to start Win32Source eventloop without initialization"); |       panic!("Attempt to start Win32Source eventloop without initialization"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ use log::{error, trace, warn}; | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use thiserror::Error; | use thiserror::Error; | ||||||
| 
 | 
 | ||||||
| use crate::event::Status::*; | use crate::{Source, SourceCallback, event::Status::*}; | ||||||
| use crate::event::Variant::*; | use crate::event::Variant::*; | ||||||
| use crate::event::{InputEvent, Key, KeyboardEvent, Variant}; | use crate::event::{InputEvent, Key, KeyboardEvent, Variant}; | ||||||
| use crate::event::{Key::*, MouseButton, MouseEvent}; | use crate::event::{Key::*, MouseButton, MouseEvent}; | ||||||
|  | @ -70,10 +70,9 @@ extern "C" { | ||||||
|   pub fn detect_destroy(window: *const c_void) -> i32; |   pub fn detect_destroy(window: *const c_void) -> i32; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub type X11SourceCallback = Box<dyn Fn(InputEvent)>; |  | ||||||
| pub struct X11Source { | pub struct X11Source { | ||||||
|   handle: *mut c_void, |   handle: *mut c_void, | ||||||
|   callback: LazyCell<X11SourceCallback>, |   callback: LazyCell<SourceCallback>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[allow(clippy::new_without_default)] | #[allow(clippy::new_without_default)] | ||||||
|  | @ -89,7 +88,11 @@ impl X11Source { | ||||||
|     unsafe { detect_check_x11() != 0 } |     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 mut error_code = 0; | ||||||
|     let handle = unsafe { detect_initialize(self as *const X11Source, &mut error_code) }; |     let handle = unsafe { detect_initialize(self as *const X11Source, &mut error_code) }; | ||||||
| 
 | 
 | ||||||
|  | @ -111,7 +114,7 @@ impl X11Source { | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   pub fn eventloop(&self, event_callback: X11SourceCallback) { |   fn eventloop(&self, event_callback: SourceCallback) { | ||||||
|     if self.handle.is_null() { |     if self.handle.is_null() { | ||||||
|       panic!("Attempt to start X11Source eventloop without initialization"); |       panic!("Attempt to start X11Source eventloop without initialization"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,11 @@ authors = ["Federico Terzi <federico-terzi@users.noreply.github.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| build="build.rs" | 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] | [dependencies] | ||||||
| log = "0.4.14" | log = "0.4.14" | ||||||
| lazycell = "1.3.0" | lazycell = "1.3.0" | ||||||
|  |  | ||||||
|  | @ -35,27 +35,21 @@ fn cc_config() { | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
| fn cc_config() { | fn cc_config() { | ||||||
|   // println!("cargo:rerun-if-changed=src/x11/native.cpp");
 |  | ||||||
|   // println!("cargo:rerun-if-changed=src/x11/native.h");
 |  | ||||||
|   // println!("cargo:rerun-if-changed=src/evdev/native.cpp");
 |  | ||||||
|   // println!("cargo:rerun-if-changed=src/evdev/native.h");
 |  | ||||||
|   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"); |   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() |   cc::Build::new() | ||||||
|     .include("src/evdev/native.h") |     .include("src/evdev/native.h") | ||||||
|     .file("src/evdev/native.c") |     .file("src/evdev/native.c") | ||||||
|     .compile("espansoinjectev"); |     .compile("espansoinjectev"); | ||||||
|  | 
 | ||||||
|   println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/"); |   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=static=espansoinjectev"); | ||||||
|   println!("cargo:rustc-link-lib=dylib=X11"); |  | ||||||
|   println!("cargo:rustc-link-lib=dylib=Xtst"); |  | ||||||
|   println!("cargo:rustc-link-lib=dylib=xkbcommon"); |   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")] | #[cfg(target_os = "macos")] | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
|  | use log::info; | ||||||
| 
 | 
 | ||||||
| pub mod keys; | pub mod keys; | ||||||
| 
 | 
 | ||||||
|  | @ -25,6 +26,7 @@ pub mod keys; | ||||||
| mod win32; | mod win32; | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
|  | #[cfg(not(feature = "wayland"))] | ||||||
| mod x11; | mod x11; | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
|  | @ -77,7 +79,7 @@ impl Default for InjectionOptions { | ||||||
| 
 | 
 | ||||||
| #[allow(dead_code)] | #[allow(dead_code)] | ||||||
| pub struct InjectorCreationOptions { | pub struct InjectorCreationOptions { | ||||||
|   // Only relevant in Linux systems
 |   // Only relevant in X11 Linux systems, use the EVDEV backend instead of X11.
 | ||||||
|   use_evdev: bool, |   use_evdev: bool, | ||||||
| 
 | 
 | ||||||
|   // Overwrite the list of modifiers to be scanned when 
 |   // Overwrite the list of modifiers to be scanned when 
 | ||||||
|  | @ -116,19 +118,33 @@ impl Default for InjectorCreationOptions { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "windows")] | #[cfg(target_os = "windows")] | ||||||
| pub fn get_injector(_options: InjectorOptions) -> impl Injector { | pub fn get_injector(_options: InjectorOptions) -> Result<Box<dyn Injector>> { | ||||||
|   win32::Win32Injector::new() |   info!("using Win32Injector"); | ||||||
|  |   Ok(Box::new(win32::Win32Injector::new())) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "macos")] | #[cfg(target_os = "macos")] | ||||||
| pub fn get_injector(_options: InjectorOptions) -> impl Injector { | pub fn get_injector(_options: InjectorOptions) -> Result<Box<dyn Injector>> { | ||||||
|   mac::MacInjector::new() |   info!("using MacInjector"); | ||||||
|  |   Ok(Box::new(mac::MacInjector::new())) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
| pub fn get_injector(options: InjectorCreationOptions) -> Result<impl Injector> { | #[cfg(not(feature = "wayland"))] | ||||||
|   // TODO: differenciate based on the options
 | pub fn get_injector(options: InjectorCreationOptions) -> Result<Box<dyn Injector>> { | ||||||
|   //x11::X11Injector::new()
 |   if options.use_evdev { | ||||||
|   evdev::EVDEVInjector::new(options) |     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 log::error; | ||||||
| 
 | 
 | ||||||
| use anyhow::Result; | 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 thiserror::Error; | ||||||
| 
 | 
 | ||||||
| use crate::{keys, InjectionOptions, Injector}; | use crate::{keys, InjectionOptions, Injector}; | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | use icons::TrayIcon; | ||||||
|  | use anyhow::Result; | ||||||
|  | 
 | ||||||
| pub mod event; | pub mod event; | ||||||
| pub mod icons; | pub mod icons; | ||||||
| pub mod menu; | pub mod menu; | ||||||
|  | @ -10,3 +13,52 @@ pub mod linux; | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "macos")] | #[cfg(target_os = "macos")] | ||||||
| pub mod mac; | 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; | ||||||
| use std::sync::mpsc::{Receiver, Sender}; | use std::sync::mpsc::{Receiver, Sender}; | ||||||
| 
 | 
 | ||||||
|  | use crate::{UIEventLoop, UIRemote}; | ||||||
|  | 
 | ||||||
| pub struct LinuxUIOptions { | pub struct LinuxUIOptions { | ||||||
|   pub notification_icon_path: String, |   pub notification_icon_path: String, | ||||||
| } | } | ||||||
|  | @ -30,8 +32,14 @@ impl LinuxRemote { | ||||||
|   pub fn stop(&self) -> anyhow::Result<()> { |   pub fn stop(&self) -> anyhow::Result<()> { | ||||||
|     Ok(self.tx.send(())?) |     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() |     if let Err(error) = Notification::new() | ||||||
|       .summary("Espanso") |       .summary("Espanso") | ||||||
|       .body(message) |       .body(message) | ||||||
|  | @ -41,6 +49,10 @@ impl LinuxRemote { | ||||||
|       error!("Unable to show notification: {}", error); |       error!("Unable to show notification: {}", error); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   fn show_context_menu(&self, _: &crate::menu::Menu) { | ||||||
|  |     // NOOP on linux
 | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct LinuxEventLoop { | pub struct LinuxEventLoop { | ||||||
|  | @ -51,12 +63,14 @@ impl LinuxEventLoop { | ||||||
|   pub fn new(rx: Receiver<()>) -> Self { |   pub fn new(rx: Receiver<()>) -> Self { | ||||||
|     Self { rx } |     Self { rx } | ||||||
|   } |   } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|   pub fn initialize(&self) { | impl UIEventLoop for LinuxEventLoop { | ||||||
|  |   fn initialize(&mut self) { | ||||||
|     // NOOP on linux
 |     // 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.
 |     // 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
 |     // 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"); |     self.rx.recv().expect("Unable to block the LinuxEventLoop"); | ||||||
|  |  | ||||||
|  | @ -110,8 +110,6 @@ pub fn create(options: Win32UIOptions) -> (Win32Remote, Win32EventLoop) { | ||||||
|   (remote, eventloop) |   (remote, eventloop) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub type Win32UIEventCallback = Box<dyn Fn(UIEvent)>; |  | ||||||
| 
 |  | ||||||
| pub struct Win32EventLoop { | pub struct Win32EventLoop { | ||||||
|   handle: Arc<AtomicPtr<c_void>>, |   handle: Arc<AtomicPtr<c_void>>, | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,11 @@ readme = "README.md" | ||||||
| homepage = "https://github.com/federico-terzi/espanso" | homepage = "https://github.com/federico-terzi/espanso" | ||||||
| edition = "2018" | 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] | [dependencies] | ||||||
| espanso-detect = { path = "../espanso-detect" }  | espanso-detect = { path = "../espanso-detect" }  | ||||||
| espanso-ui = { path = "../espanso-ui" }  | espanso-ui = { path = "../espanso-ui" }  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| use std::time::Duration; | 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_inject::{get_injector, Injector, keys}; | ||||||
| use espanso_ui::{event::UIEvent::*, icons::TrayIcon, menu::*}; | use espanso_ui::{event::UIEvent::*, icons::TrayIcon, menu::*}; | ||||||
| use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode}; | use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode}; | ||||||
|  | @ -48,18 +48,17 @@ fn main() { | ||||||
|   //   show_icon: true,
 |   //   show_icon: true,
 | ||||||
|   //   icon_paths: &icon_paths,
 |   //   icon_paths: &icon_paths,
 | ||||||
|   // });
 |   // });
 | ||||||
|   let (remote, mut eventloop) = espanso_ui::linux::create(espanso_ui::linux::LinuxUIOptions { |   let (remote, mut eventloop) = espanso_ui::create_ui(espanso_ui::UIOptions { | ||||||
|     notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png".to_string(), |     notification_icon_path: Some(r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png".to_string()), | ||||||
|   }); |     ..Default::default() | ||||||
|  |   }).unwrap(); | ||||||
| 
 | 
 | ||||||
|   eventloop.initialize(); |   eventloop.initialize(); | ||||||
| 
 | 
 | ||||||
|   let handle = std::thread::spawn(move || { |   let handle = std::thread::spawn(move || { | ||||||
|     let injector = get_injector(Default::default()).unwrap(); |     let injector = get_injector(Default::default()).unwrap(); | ||||||
|     //let mut source = espanso_detect::win32::Win32Source::new();
 |     let mut source = get_source(Default::default()).unwrap(); | ||||||
|     let mut source = espanso_detect::x11::X11Source::new(); |     source.initialize().unwrap(); | ||||||
|     //let mut source = espanso_detect::mac::CocoaSource::new();
 |  | ||||||
|     source.initialize(); |  | ||||||
|     source.eventloop(Box::new(move |event: InputEvent| { |     source.eventloop(Box::new(move |event: InputEvent| { | ||||||
|       println!("ev {:?}", event); |       println!("ev {:?}", event); | ||||||
|       match event { |       match event { | ||||||
|  | @ -77,30 +76,29 @@ fn main() { | ||||||
|     })); |     })); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   eventloop.run(); |   eventloop.run(Box::new(move |event| { | ||||||
|   // eventloop.run(Box::new(move |event| {
 |     println!("ui {:?}", event); | ||||||
|   //   println!("ui {:?}", event);
 |     let menu = Menu::from(vec![ | ||||||
|   //   let menu = Menu::from(vec![
 |       MenuItem::Simple(SimpleMenuItem::new("open", "Open")), | ||||||
|   //     MenuItem::Simple(SimpleMenuItem::new("open", "Open")),
 |       MenuItem::Separator, | ||||||
|   //     MenuItem::Separator,
 |       MenuItem::Sub(SubMenuItem::new( | ||||||
|   //     MenuItem::Sub(SubMenuItem::new(
 |         "Sub", | ||||||
|   //       "Sub",
 |         vec![ | ||||||
|   //       vec![
 |           MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")), | ||||||
|   //         MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")),
 |           MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")), | ||||||
|   //         MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")),
 |         ], | ||||||
|   //       ],
 |       )), | ||||||
|   //     )),
 |     ]) | ||||||
|   //   ])
 |     .unwrap(); | ||||||
|   //   .unwrap();
 |     match event { | ||||||
|   //   match event {
 |       TrayIconClick => { | ||||||
|   //     TrayIconClick => {
 |         remote.show_context_menu(&menu); | ||||||
|   //       remote.show_context_menu(&menu);
 |       } | ||||||
|   //     }
 |       ContextMenuClick(raw_id) => { | ||||||
|   //     ContextMenuClick(raw_id) => {
 |         //remote.update_tray_icon(TrayIcon::Disabled);
 | ||||||
|   //       //remote.update_tray_icon(TrayIcon::Disabled);
 |         remote.show_notification("Hello there!"); | ||||||
|   //       remote.show_notification("Hello there!");
 |         println!("item {:?}", menu.get_item_id(raw_id)); | ||||||
|   //       println!("item {:?}", menu.get_item_id(raw_id));
 |       } | ||||||
|   //     }
 |     } | ||||||
|   //   }
 |   })); | ||||||
|   // }));
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user