Add windows IPC implementation and general refactor

This commit is contained in:
Federico Terzi 2021-02-17 19:09:28 +01:00
parent ee611c3a03
commit a57092517e
11 changed files with 217 additions and 55 deletions

10
Cargo.lock generated
View File

@ -260,6 +260,7 @@ dependencies = [
"anyhow", "anyhow",
"crossbeam", "crossbeam",
"log", "log",
"named_pipe",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -393,6 +394,15 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "named_pipe"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad9c443cce91fc3e12f017290db75dde490d685cdaaf508d7159d7cf41f0eb2b"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "notify-rust" name = "notify-rust"
version = "4.2.2" version = "4.2.2"

View File

@ -43,7 +43,7 @@ pub type SourceCallback = Box<dyn Fn(event::InputEvent)>;
pub trait Source { pub trait Source {
fn initialize(&mut self) -> Result<()>; fn initialize(&mut self) -> Result<()>;
fn eventloop(&self, event_callback: SourceCallback); fn eventloop(&self, event_callback: SourceCallback) -> Result<()>;
} }
#[allow(dead_code)] #[allow(dead_code)]

View File

@ -26,7 +26,7 @@ use widestring::U16CStr;
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};
@ -109,13 +109,14 @@ impl Source for Win32Source {
Ok(()) Ok(())
} }
fn eventloop(&self, event_callback: SourceCallback) { fn eventloop(&self, event_callback: SourceCallback) -> Result<()> {
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");
} }
if self.callback.fill(event_callback).is_err() { if self.callback.fill(event_callback).is_err() {
panic!("Unable to set Win32Source event callback"); error!("Unable to set Win32Source event callback");
return Err(Win32SourceError::Unknown().into())
} }
extern "C" fn callback(_self: *mut Win32Source, event: RawInputEvent) { extern "C" fn callback(_self: *mut Win32Source, event: RawInputEvent) {
@ -132,8 +133,11 @@ impl Source for Win32Source {
let error_code = unsafe { detect_eventloop(self.handle, callback) }; let error_code = unsafe { detect_eventloop(self.handle, callback) };
if error_code <= 0 { if error_code <= 0 {
panic!("Win32Source eventloop returned a negative error code"); error!("Win32Source eventloop returned a negative error code");
return Err(Win32SourceError::Unknown().into())
} }
Ok(())
} }
} }

View File

@ -122,13 +122,13 @@ impl Default for InjectorCreationOptions {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn get_injector(_options: InjectorOptions) -> Result<Box<dyn Injector>> { pub fn get_injector(_options: InjectorCreationOptions) -> Result<Box<dyn Injector>> {
info!("using Win32Injector"); info!("using Win32Injector");
Ok(Box::new(win32::Win32Injector::new())) Ok(Box::new(win32::Win32Injector::new()))
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub fn get_injector(_options: InjectorOptions) -> Result<Box<dyn Injector>> { pub fn get_injector(_options: InjectorCreationOptions) -> Result<Box<dyn Injector>> {
info!("using MacInjector"); info!("using MacInjector");
Ok(Box::new(mac::MacInjector::new())) Ok(Box::new(mac::MacInjector::new()))
} }

View File

@ -25,7 +25,7 @@ use raw_keys::convert_key_to_vkey;
use anyhow::Result; use anyhow::Result;
use thiserror::Error; use thiserror::Error;
use crate::{keys, Injector}; use crate::{InjectionOptions, Injector, keys};
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link(name = "espansoinject", kind = "static")] #[link(name = "espansoinject", kind = "static")]
@ -60,7 +60,7 @@ impl Win32Injector {
} }
impl Injector for Win32Injector { impl Injector for Win32Injector {
fn send_string(&self, string: &str) -> Result<()> { fn send_string(&self, string: &str, _: InjectionOptions) -> Result<()> {
let wide_string = widestring::WideCString::from_str(string)?; let wide_string = widestring::WideCString::from_str(string)?;
unsafe { unsafe {
inject_string(wide_string.as_ptr()); inject_string(wide_string.as_ptr());
@ -68,32 +68,32 @@ impl Injector for Win32Injector {
Ok(()) Ok(())
} }
fn send_keys(&self, keys: &[keys::Key], delay: i32) -> Result<()> { fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
let virtual_keys = Self::convert_to_vk_array(keys)?; let virtual_keys = Self::convert_to_vk_array(keys)?;
if delay == 0 { if options.delay == 0 {
unsafe { unsafe {
inject_separate_vkeys(virtual_keys.as_ptr(), virtual_keys.len() as i32); inject_separate_vkeys(virtual_keys.as_ptr(), virtual_keys.len() as i32);
} }
} else { } else {
unsafe { unsafe {
inject_separate_vkeys_with_delay(virtual_keys.as_ptr(), virtual_keys.len() as i32, delay); inject_separate_vkeys_with_delay(virtual_keys.as_ptr(), virtual_keys.len() as i32, options.delay);
} }
} }
Ok(()) Ok(())
} }
fn send_key_combination(&self, keys: &[keys::Key], delay: i32) -> Result<()> { fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
let virtual_keys = Self::convert_to_vk_array(keys)?; let virtual_keys = Self::convert_to_vk_array(keys)?;
if delay == 0 { if options.delay == 0 {
unsafe { unsafe {
inject_vkeys_combination(virtual_keys.as_ptr(), virtual_keys.len() as i32); inject_vkeys_combination(virtual_keys.as_ptr(), virtual_keys.len() as i32);
} }
} else { } else {
unsafe { unsafe {
inject_vkeys_combination_with_delay(virtual_keys.as_ptr(), virtual_keys.len() as i32, delay); inject_vkeys_combination_with_delay(virtual_keys.as_ptr(), virtual_keys.len() as i32, options.delay);
} }
} }

View File

@ -13,5 +13,4 @@ serde_json = "1.0.62"
crossbeam = "0.8.0" crossbeam = "0.8.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
named_pipe = "0.4.1"
[target.'cfg(unix)'.dependencies]

View File

@ -51,6 +51,19 @@ pub fn client<Event: Serialize>(id: &str, parent_dir: &Path) -> Result<impl IPCC
Ok(client) Ok(client)
} }
#[cfg(target_os = "windows")]
pub fn server<Event: Send + Sync + DeserializeOwned>(id: &str, _: &Path) -> Result<(impl IPCServer<Event>, Receiver<Event>)> {
let (sender, receiver) = unbounded();
let server = windows::WinIPCServer::new(id, sender)?;
Ok((server, receiver))
}
#[cfg(target_os = "windows")]
pub fn client<Event: Serialize>(id: &str, _: &Path) -> Result<impl IPCClient<Event>> {
let client = windows::WinIPCClient::new(id)?;
Ok(client)
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum IPCServerError { pub enum IPCServerError {
#[error("stream ended")] #[error("stream ended")]
@ -78,6 +91,7 @@ mod tests {
server.accept_one().unwrap(); server.accept_one().unwrap();
}); });
std::thread::sleep(std::time::Duration::from_secs(1));
let client = client::<Event>("testespansoipc", &std::env::temp_dir()).unwrap(); let client = client::<Event>("testespansoipc", &std::env::temp_dir()).unwrap();
client.send(Event::Foo("hello".to_string())).unwrap(); client.send(Event::Foo("hello".to_string())).unwrap();

View File

@ -16,3 +16,104 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* 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 crossbeam::channel::Sender;
use log::{error, info};
use named_pipe::{PipeClient, PipeOptions};
use serde::{de::DeserializeOwned, Serialize};
use std::{
io::{BufReader, Read, Write},
};
use crate::{IPCClient, IPCServer, IPCServerError};
const CLIENT_TIMEOUT: u32 = 2000;
pub struct WinIPCServer<Event> {
options: PipeOptions,
sender: Sender<Event>,
}
impl<Event> WinIPCServer<Event> {
pub fn new(id: &str, sender: Sender<Event>) -> Result<Self> {
let pipe_name = format!("\\\\.\\pipe\\{}", id);
let options = PipeOptions::new(&pipe_name);
info!(
"binded to named pipe: {}",
pipe_name
);
Ok(Self { options, sender })
}
}
impl<Event: Send + Sync + DeserializeOwned> IPCServer<Event> for WinIPCServer<Event> {
fn run(&self) -> anyhow::Result<()> {
loop {
self.accept_one()?;
}
}
fn accept_one(&self) -> Result<()> {
let server = self.options.single()?;
let connection = server.wait();
match connection {
Ok(stream) => {
let mut json_str = String::new();
let mut buf_reader = BufReader::new(stream);
let result = buf_reader.read_to_string(&mut json_str);
match result {
Ok(_) => {
let event: Result<Event, serde_json::Error> = serde_json::from_str(&json_str);
match event {
Ok(event) => {
if self.sender.send(event).is_err() {
return Err(IPCServerError::SendFailed().into());
}
}
Err(error) => {
error!("received malformed event from ipc stream: {}", error);
}
}
}
Err(error) => {
error!("error reading ipc stream: {}", error);
}
}
}
Err(err) => {
return Err(IPCServerError::StreamEnded(err).into());
}
};
Ok(())
}
}
pub struct WinIPCClient {
pipe_name: String,
}
impl WinIPCClient {
pub fn new(id: &str) -> Result<Self> {
let pipe_name = format!("\\\\.\\pipe\\{}", id);
Ok(Self { pipe_name })
}
}
impl<Event: Serialize> IPCClient<Event> for WinIPCClient {
fn send(&self, event: Event) -> Result<()> {
let mut stream = PipeClient::connect_ms(&self.pipe_name, CLIENT_TIMEOUT)?;
let json_event = serde_json::to_string(&event)?;
stream.write_all(json_event.as_bytes())?;
Ok(())
}
}

View File

@ -1,5 +1,6 @@
use icons::TrayIcon; use icons::TrayIcon;
use anyhow::Result; use anyhow::Result;
use thiserror::Error;
pub mod event; pub mod event;
pub mod icons; pub mod icons;
@ -22,8 +23,8 @@ pub trait UIRemote {
pub type UIEventCallback = Box<dyn Fn(event::UIEvent)>; pub type UIEventCallback = Box<dyn Fn(event::UIEvent)>;
pub trait UIEventLoop { pub trait UIEventLoop {
fn initialize(&mut self); fn initialize(&mut self) -> Result<()>;
fn run(&self, event_callback: UIEventCallback); fn run(&self, event_callback: UIEventCallback) -> Result<()>;
} }
pub struct UIOptions { pub struct UIOptions {
@ -43,22 +44,33 @@ impl Default for UIOptions {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn create_ui(_options: UIOptions) -> Result<Box<dyn Injector>> { pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn UIEventLoop>)> {
// TODO: refactor let (remote, eventloop) = win32::create(win32::Win32UIOptions {
Ok(Box::new(win32::Win32Injector::new())) show_icon: options.show_icon,
icon_paths: &options.icon_paths,
notification_icon_path: options.notification_icon_path.ok_or_else(|| UIError::MissingOption("notification icon".to_string()))?,
})?;
Ok((Box::new(remote), Box::new(eventloop)))
} }
#[cfg(target_os = "macos")] #[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>)> { 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 { let (remote, eventloop) = linux::create(linux::LinuxUIOptions {
notification_icon_path: options.notification_icon_path.expect("missing notification icon path") notification_icon_path: options.notification_icon_path.expect("missing notification icon path")
}); });
Ok((Box::new(remote), Box::new(eventloop))) Ok((Box::new(remote), Box::new(eventloop)))
} }
#[cfg(target_os = "linux")]
pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn UIEventLoop>)> {
let (remote, eventloop) = linux::create(linux::LinuxUIOptions {
notification_icon_path: options.notification_icon_path.ok_or(UIError::MissingOption("notification icon".to_string()))?,
});
Ok((Box::new(remote), Box::new(eventloop)))
}
#[derive(Error, Debug)]
pub enum UIError {
#[error("missing required option for ui: `{0}`")]
MissingOption(String),
}

View File

@ -32,8 +32,10 @@ use std::{
use lazycell::LazyCell; use lazycell::LazyCell;
use log::{error, trace}; use log::{error, trace};
use widestring::WideCString; use widestring::WideCString;
use anyhow::Result;
use thiserror::Error;
use crate::{event::UIEvent, icons::TrayIcon, menu::Menu}; use crate::{UIEventCallback, UIEventLoop, UIRemote, event::UIEvent, icons::TrayIcon, menu::Menu};
// IMPORTANT: if you change these, also edit the native.h file. // IMPORTANT: if you change these, also edit the native.h file.
const MAX_FILE_PATH: usize = 260; const MAX_FILE_PATH: usize = 260;
@ -83,7 +85,7 @@ pub struct Win32UIOptions<'a> {
pub notification_icon_path: String, pub notification_icon_path: String,
} }
pub fn create(options: Win32UIOptions) -> (Win32Remote, Win32EventLoop) { pub fn create(options: Win32UIOptions) -> Result<(Win32Remote, Win32EventLoop)> {
let handle: Arc<AtomicPtr<c_void>> = Arc::new(AtomicPtr::new(std::ptr::null_mut())); let handle: Arc<AtomicPtr<c_void>> = Arc::new(AtomicPtr::new(std::ptr::null_mut()));
// Validate icons // Validate icons
@ -107,7 +109,7 @@ pub fn create(options: Win32UIOptions) -> (Win32Remote, Win32EventLoop) {
); );
let remote = Win32Remote::new(handle, icon_indexes); let remote = Win32Remote::new(handle, icon_indexes);
(remote, eventloop) Ok((remote, eventloop))
} }
pub struct Win32EventLoop { pub struct Win32EventLoop {
@ -118,7 +120,7 @@ pub struct Win32EventLoop {
notification_icon_path: String, notification_icon_path: String,
// Internal // Internal
_event_callback: LazyCell<Win32UIEventCallback>, _event_callback: LazyCell<UIEventCallback>,
_init_thread_id: LazyCell<ThreadId>, _init_thread_id: LazyCell<ThreadId>,
} }
@ -138,11 +140,14 @@ impl Win32EventLoop {
_init_thread_id: LazyCell::new(), _init_thread_id: LazyCell::new(),
} }
} }
}
pub fn initialize(&mut self) { impl UIEventLoop for Win32EventLoop {
fn initialize(&mut self) -> Result<()> {
let window_handle = self.handle.load(Ordering::Acquire); let window_handle = self.handle.load(Ordering::Acquire);
if !window_handle.is_null() { if !window_handle.is_null() {
panic!("Attempt to initialize Win32EventLoop on non-null window handle"); error!("Attempt to initialize Win32EventLoop on non-null window handle");
return Err(Win32UIError::InvalidHandle().into());
} }
// Convert the icon paths to the raw representation // Convert the icon paths to the raw representation
@ -150,15 +155,13 @@ impl Win32EventLoop {
[[0; MAX_FILE_PATH]; MAX_ICON_COUNT]; [[0; MAX_FILE_PATH]; MAX_ICON_COUNT];
for (i, icon_path) in icon_paths.iter_mut().enumerate().take(self.icons.len()) { for (i, icon_path) in icon_paths.iter_mut().enumerate().take(self.icons.len()) {
let wide_path = let wide_path =
WideCString::from_str(&self.icons[i]).expect("Error while converting icon to wide string"); WideCString::from_str(&self.icons[i])?;
let len = min(wide_path.len(), MAX_FILE_PATH - 1); let len = min(wide_path.len(), MAX_FILE_PATH - 1);
icon_path[0..len].clone_from_slice(&wide_path.as_slice()[..len]); icon_path[0..len].clone_from_slice(&wide_path.as_slice()[..len]);
// TODO: test overflow, correct case
} }
let wide_notification_icon_path = let wide_notification_icon_path =
widestring::WideCString::from_str(&self.notification_icon_path) widestring::WideCString::from_str(&self.notification_icon_path)?;
.expect("Error while converting notification icon to wide string");
let mut wide_notification_icon_path_buffer: [u16; MAX_FILE_PATH] = [0; MAX_FILE_PATH]; let mut wide_notification_icon_path_buffer: [u16; MAX_FILE_PATH] = [0; MAX_FILE_PATH];
wide_notification_icon_path_buffer[..wide_notification_icon_path.as_slice().len()] wide_notification_icon_path_buffer[..wide_notification_icon_path.as_slice().len()]
.clone_from_slice(wide_notification_icon_path.as_slice()); .clone_from_slice(wide_notification_icon_path.as_slice());
@ -174,11 +177,11 @@ impl Win32EventLoop {
let handle = unsafe { ui_initialize(self as *const Win32EventLoop, options, &mut error_code) }; let handle = unsafe { ui_initialize(self as *const Win32EventLoop, options, &mut error_code) };
if handle.is_null() { if handle.is_null() {
match error_code { return match error_code {
-1 => panic!("Unable to initialize Win32EventLoop, error registering window class"), -1 => Err(Win32UIError::EventLoopInitError("Unable to initialize Win32EventLoop, error registering window class".to_string()).into()),
-2 => panic!("Unable to initialize Win32EventLoop, error creating window"), -2 => Err(Win32UIError::EventLoopInitError("Unable to initialize Win32EventLoop, error creating window".to_string()).into()),
-3 => panic!("Unable to initialize Win32EventLoop, initializing notifications"), -3 => Err(Win32UIError::EventLoopInitError("Unable to initialize Win32EventLoop, initializing notifications".to_string()).into()),
_ => panic!("Unable to initialize Win32EventLoop, unknown error"), _ => Err(Win32UIError::EventLoopInitError("Unable to initialize Win32EventLoop, unknown error".to_string()).into()),
} }
} }
@ -189,25 +192,27 @@ impl Win32EventLoop {
._init_thread_id ._init_thread_id
.fill(std::thread::current().id()) .fill(std::thread::current().id())
.expect("Unable to set initialization thread id"); .expect("Unable to set initialization thread id");
Ok(())
} }
pub fn run(&self, event_callback: Win32UIEventCallback) { fn run(&self, event_callback: UIEventCallback) -> Result<()> {
// Make sure the run() method is called in the same thread as initialize() // Make sure the run() method is called in the same thread as initialize()
if let Some(init_id) = self._init_thread_id.borrow() { if let Some(init_id) = self._init_thread_id.borrow() {
if init_id != &std::thread::current().id() { if init_id != &std::thread::current().id() {
panic!("Win32EventLoop run() and initialize() methods should be called in the same thread"); panic!("Win32EventLoop run() and initialize() methods should be called in the same thread");
// TODO: test
} }
} }
let window_handle = self.handle.load(Ordering::Acquire); let window_handle = self.handle.load(Ordering::Acquire);
if window_handle.is_null() { if window_handle.is_null() {
panic!("Attempt to run Win32EventLoop on a null window handle"); error!("Attempt to run Win32EventLoop on a null window handle");
// TODO: test return Err(Win32UIError::InvalidHandle().into())
} }
if self._event_callback.fill(event_callback).is_err() { if self._event_callback.fill(event_callback).is_err() {
panic!("Unable to set Win32EventLoop callback"); error!("Unable to set Win32EventLoop callback");
return Err(Win32UIError::InternalError().into())
} }
extern "C" fn callback(_self: *mut Win32EventLoop, event: RawUIEvent) { extern "C" fn callback(_self: *mut Win32EventLoop, event: RawUIEvent) {
@ -224,8 +229,11 @@ impl Win32EventLoop {
let error_code = unsafe { ui_eventloop(window_handle, callback) }; let error_code = unsafe { ui_eventloop(window_handle, callback) };
if error_code <= 0 { if error_code <= 0 {
panic!("Win32EventLoop exited with <= 0 code") error!("Win32EventLoop exited with <= 0 code");
return Err(Win32UIError::InternalError().into())
} }
Ok(())
} }
} }
@ -262,8 +270,10 @@ impl Win32Remote {
icon_indexes, icon_indexes,
} }
} }
}
pub fn update_tray_icon(&self, icon: TrayIcon) { impl UIRemote for Win32Remote {
fn update_tray_icon(&self, icon: TrayIcon) {
let handle = self.handle.load(Ordering::Acquire); let handle = self.handle.load(Ordering::Acquire);
if handle.is_null() { if handle.is_null() {
error!("Unable to update tray icon, pointer is null"); error!("Unable to update tray icon, pointer is null");
@ -277,7 +287,7 @@ impl Win32Remote {
} }
} }
pub fn show_notification(&self, message: &str) { fn show_notification(&self, message: &str) {
let handle = self.handle.load(Ordering::Acquire); let handle = self.handle.load(Ordering::Acquire);
if handle.is_null() { if handle.is_null() {
error!("Unable to show notification, pointer is null"); error!("Unable to show notification, pointer is null");
@ -296,7 +306,7 @@ impl Win32Remote {
} }
} }
pub fn show_context_menu(&self, menu: &Menu) { fn show_context_menu(&self, menu: &Menu) {
let handle = self.handle.load(Ordering::Acquire); let handle = self.handle.load(Ordering::Acquire);
if handle.is_null() { if handle.is_null() {
error!("Unable to show context menu, pointer is null"); error!("Unable to show context menu, pointer is null");
@ -338,6 +348,18 @@ impl From<RawUIEvent> for Option<UIEvent> {
} }
} }
#[derive(Error, Debug)]
pub enum Win32UIError {
#[error("invalid handle")]
InvalidHandle(),
#[error("event loop initialization failed: `{0}`")]
EventLoopInitError(String),
#[error("internal error")]
InternalError(),
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -53,7 +53,7 @@ fn main() {
..Default::default() ..Default::default()
}).unwrap(); }).unwrap();
eventloop.initialize(); eventloop.initialize().unwrap();
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();
@ -73,7 +73,7 @@ fn main() {
} }
} }
} }
})); })).unwrap();
}); });
eventloop.run(Box::new(move |event| { eventloop.run(Box::new(move |event| {