From 2b4835171a7953c94116ffe4773b0e8bab9bcf0b Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sat, 14 Sep 2019 20:13:09 +0200 Subject: [PATCH] Add IPC on Unix systems --- src/engine.rs | 2 +- src/event/mod.rs | 4 +++ src/main.rs | 57 +++++++++++++++++++++++++++++++++++++--- src/matcher/mod.rs | 2 +- src/matcher/scrolling.rs | 15 ++++++++++- src/protocol/mod.rs | 32 +++++++++++++++++----- src/protocol/unix.rs | 51 +++++++++++++++++++++++++++++------ 7 files changed, 143 insertions(+), 20 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index f5c8599..db2cfc8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -95,7 +95,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa } } - fn on_toggle(&self, status: bool) { + fn on_enable_update(&self, status: bool) { let message = if status { "espanso enabled" }else{ diff --git a/src/event/mod.rs b/src/event/mod.rs index 441e98e..ecbaccb 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -14,6 +14,8 @@ pub enum ActionType { Toggle = 1, Exit = 2, IconClick = 3, + Enable = 4, + Disable = 5, } impl From for ActionType { @@ -22,6 +24,8 @@ impl From for ActionType { 1 => ActionType::Toggle, 2 => ActionType::Exit, 3 => ActionType::IconClick, + 4 => ActionType::Enable, + 5 => ActionType::Disable, _ => ActionType::Noop, } } diff --git a/src/main.rs b/src/main.rs index 8441d81..0c861ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::sync::mpsc; use std::sync::mpsc::Receiver; use std::time::Duration; -use clap::{App, Arg, SubCommand}; +use clap::{App, Arg, SubCommand, ArgMatches}; use fs2::FileExt; use log::{error, info, LevelFilter}; use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger}; @@ -50,6 +50,17 @@ fn main() { .short("v") .multiple(true) .help("Sets the level of verbosity")) + .subcommand(SubCommand::with_name("cmd") + .about("Send a command to the espanso daemon.") + .subcommand(SubCommand::with_name("exit") + .about("Terminate the daemon.")) + .subcommand(SubCommand::with_name("enable") + .about("Enable the espanso replacement engine.")) + .subcommand(SubCommand::with_name("disable") + .about("Disable the espanso replacement engine.")) + .subcommand(SubCommand::with_name("toggle") + .about("Toggle the status of the espanso replacement engine.")) + ) .subcommand(SubCommand::with_name("dump") .about("Prints all current configuration options.")) .subcommand(SubCommand::with_name("detect") @@ -111,6 +122,11 @@ fn main() { status_main(); return; } + + if let Some(matches) = matches.subcommand_matches("cmd") { + cmd_main(matches); + return; + } } /// Daemon subcommand, start the event loop and spawn a background thread worker @@ -153,8 +169,8 @@ fn daemon_main(config_set: ConfigSet) { daemon_background(receive_channel, config_set); }); - let ipc_manager = protocol::get_ipc_manager(send_channel.clone()); - ipc_manager.start_server(); + let ipc_server = protocol::get_ipc_server(send_channel.clone()); + ipc_server.start(); context.eventloop(); } @@ -273,6 +289,41 @@ fn detect_main() { } } +fn cmd_main(matches: &ArgMatches) { + let command = if let Some(matches) = matches.subcommand_matches("exit") { + Some(IPCCommand { + id: String::from("exit"), + payload: String::from(""), + }) + }else if let Some(matches) = matches.subcommand_matches("toggle") { + Some(IPCCommand { + id: String::from("toggle"), + payload: String::from(""), + }) + }else if let Some(matches) = matches.subcommand_matches("enable") { + Some(IPCCommand { + id: String::from("enable"), + payload: String::from(""), + }) + }else if let Some(matches) = matches.subcommand_matches("disable") { + Some(IPCCommand { + id: String::from("disable"), + payload: String::from(""), + }) + }else{ + None + }; + + if let Some(command) = command { + let ipc_client = protocol::get_ipc_client(); + ipc_client.send_command(command); + + exit(0); + } + + exit(1); +} + fn acquire_lock() -> Option { let espanso_dir = context::get_data_dir(); let lock_file_path = espanso_dir.join("espanso.lock"); diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 8759990..e033b01 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -12,7 +12,7 @@ pub struct Match { pub trait MatchReceiver { fn on_match(&self, m: &Match); - fn on_toggle(&self, status: bool); + fn on_enable_update(&self, status: bool); } pub trait Matcher : KeyEventReceiver { diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index 3de456c..797314a 100644 --- a/src/matcher/scrolling.rs +++ b/src/matcher/scrolling.rs @@ -37,7 +37,14 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> { let mut is_enabled = self.is_enabled.borrow_mut(); *is_enabled = !(*is_enabled); - self.receiver.on_toggle(*is_enabled); + self.receiver.on_enable_update(*is_enabled); + } + + fn set_enabled(&self, enabled: bool) { + let mut is_enabled = self.is_enabled.borrow_mut(); + *is_enabled = enabled; + + self.receiver.on_enable_update(*is_enabled); } } @@ -128,6 +135,12 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver for Scroll ActionType::Toggle => { self.toggle(); }, + ActionType::Enable => { + self.set_enabled(true); + }, + ActionType::Disable => { + self.set_enabled(false); + }, _ => {} } } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 67684f2..bdc0bea 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -12,14 +12,20 @@ mod windows; #[cfg(not(target_os = "windows"))] mod unix; -pub trait IPCManager { - fn start_server(&self); +pub trait IPCServer { + fn start(&self); +} + +pub trait IPCClient { + fn send_command(&self, command: IPCCommand); } #[derive(Serialize, Deserialize, Debug)] pub struct IPCCommand { - id: String, - payload: String, + pub id: String, + + #[serde(default)] + pub payload: String, } impl IPCCommand { @@ -28,6 +34,15 @@ impl IPCCommand { "exit" => { Some(Event::Action(ActionType::Exit)) }, + "toggle" => { + Some(Event::Action(ActionType::Toggle)) + }, + "enable" => { + Some(Event::Action(ActionType::Enable)) + }, + "disable" => { + Some(Event::Action(ActionType::Disable)) + }, _ => None } } @@ -35,6 +50,11 @@ impl IPCCommand { // UNIX IMPLEMENTATION #[cfg(not(target_os = "windows"))] -pub fn get_ipc_manager(event_channel: Sender) -> impl IPCManager { - unix::UnixIPCManager::new(event_channel) +pub fn get_ipc_server(event_channel: Sender) -> impl IPCServer { + unix::UnixIPCServer::new(event_channel) +} + +#[cfg(not(target_os = "windows"))] +pub fn get_ipc_client() -> impl IPCClient { + unix::UnixIPCClient::new() } \ No newline at end of file diff --git a/src/protocol/unix.rs b/src/protocol/unix.rs index 83d6734..56960ff 100644 --- a/src/protocol/unix.rs +++ b/src/protocol/unix.rs @@ -1,4 +1,5 @@ use std::io::{BufRead, BufReader, Read}; +use std::io::Write; use std::os::unix::net::{UnixStream,UnixListener}; use std::thread; use log::{info, error}; @@ -11,19 +12,20 @@ use crate::event::*; const UNIX_SOCKET_NAME : &str = "espanso.sock"; -pub struct UnixIPCManager { +pub struct UnixIPCServer { event_channel: Sender, } -impl UnixIPCManager { - pub fn new(event_channel: Sender) -> UnixIPCManager { - UnixIPCManager{event_channel} +impl UnixIPCServer { + pub fn new(event_channel: Sender) -> UnixIPCServer { + UnixIPCServer {event_channel} } } -impl super::IPCManager for UnixIPCManager { - fn start_server(&self) { - std::thread::spawn(|| { +impl super::IPCServer for UnixIPCServer { + fn start(&self) { + let event_channel = self.event_channel.clone(); + std::thread::spawn(move || { let espanso_dir = context::get_data_dir(); let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME); @@ -44,7 +46,7 @@ impl super::IPCManager for UnixIPCManager { Ok(command) => { let event = command.to_event(); if let Some(event) = event { - // TODO: send event to event channel + event_channel.send(event).expect("Broken event channel"); } }, Err(e) => { @@ -60,4 +62,37 @@ impl super::IPCManager for UnixIPCManager { } }); } +} + +pub struct UnixIPCClient { + +} + +impl UnixIPCClient { + pub fn new() -> UnixIPCClient { + UnixIPCClient{} + } +} + +impl super::IPCClient for UnixIPCClient { + fn send_command(&self, command: IPCCommand) { + let espanso_dir = context::get_data_dir(); + let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME); + + // Open the stream + let mut stream = UnixStream::connect(unix_socket); + match stream { + Ok(mut stream) => { + let json_str = serde_json::to_string(&command); + if let Ok(json_str) = json_str { + stream.write_all(json_str.as_bytes()).unwrap_or_else(|e| { + println!("Can't write to IPC socket"); + }); + } + }, + Err(e) => { + println!("Can't connect to daemon: {}", e); + } + } + } } \ No newline at end of file