Add windows IPC implementation and general refactor
This commit is contained in:
		
							parent
							
								
									ee611c3a03
								
							
						
					
					
						commit
						a57092517e
					
				
							
								
								
									
										10
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
					@ -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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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(())
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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()))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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();
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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(())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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::*;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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| {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user