Add IPC on Unix systems

This commit is contained in:
Federico Terzi 2019-09-14 20:13:09 +02:00
parent 041f53842f
commit 2b4835171a
7 changed files with 143 additions and 20 deletions

View File

@ -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 { let message = if status {
"espanso enabled" "espanso enabled"
}else{ }else{

View File

@ -14,6 +14,8 @@ pub enum ActionType {
Toggle = 1, Toggle = 1,
Exit = 2, Exit = 2,
IconClick = 3, IconClick = 3,
Enable = 4,
Disable = 5,
} }
impl From<i32> for ActionType { impl From<i32> for ActionType {
@ -22,6 +24,8 @@ impl From<i32> for ActionType {
1 => ActionType::Toggle, 1 => ActionType::Toggle,
2 => ActionType::Exit, 2 => ActionType::Exit,
3 => ActionType::IconClick, 3 => ActionType::IconClick,
4 => ActionType::Enable,
5 => ActionType::Disable,
_ => ActionType::Noop, _ => ActionType::Noop,
} }
} }

View File

@ -6,7 +6,7 @@ use std::sync::mpsc;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::time::Duration; use std::time::Duration;
use clap::{App, Arg, SubCommand}; use clap::{App, Arg, SubCommand, ArgMatches};
use fs2::FileExt; use fs2::FileExt;
use log::{error, info, LevelFilter}; use log::{error, info, LevelFilter};
use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger}; use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger};
@ -50,6 +50,17 @@ fn main() {
.short("v") .short("v")
.multiple(true) .multiple(true)
.help("Sets the level of verbosity")) .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") .subcommand(SubCommand::with_name("dump")
.about("Prints all current configuration options.")) .about("Prints all current configuration options."))
.subcommand(SubCommand::with_name("detect") .subcommand(SubCommand::with_name("detect")
@ -111,6 +122,11 @@ fn main() {
status_main(); status_main();
return; 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 /// 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); daemon_background(receive_channel, config_set);
}); });
let ipc_manager = protocol::get_ipc_manager(send_channel.clone()); let ipc_server = protocol::get_ipc_server(send_channel.clone());
ipc_manager.start_server(); ipc_server.start();
context.eventloop(); 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<File> { fn acquire_lock() -> Option<File> {
let espanso_dir = context::get_data_dir(); let espanso_dir = context::get_data_dir();
let lock_file_path = espanso_dir.join("espanso.lock"); let lock_file_path = espanso_dir.join("espanso.lock");

View File

@ -12,7 +12,7 @@ pub struct Match {
pub trait MatchReceiver { pub trait MatchReceiver {
fn on_match(&self, m: &Match); fn on_match(&self, m: &Match);
fn on_toggle(&self, status: bool); fn on_enable_update(&self, status: bool);
} }
pub trait Matcher : KeyEventReceiver { pub trait Matcher : KeyEventReceiver {

View File

@ -37,7 +37,14 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
let mut is_enabled = self.is_enabled.borrow_mut(); let mut is_enabled = self.is_enabled.borrow_mut();
*is_enabled = !(*is_enabled); *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 => { ActionType::Toggle => {
self.toggle(); self.toggle();
}, },
ActionType::Enable => {
self.set_enabled(true);
},
ActionType::Disable => {
self.set_enabled(false);
},
_ => {} _ => {}
} }
} }

View File

@ -12,14 +12,20 @@ mod windows;
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
mod unix; mod unix;
pub trait IPCManager { pub trait IPCServer {
fn start_server(&self); fn start(&self);
}
pub trait IPCClient {
fn send_command(&self, command: IPCCommand);
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct IPCCommand { pub struct IPCCommand {
id: String, pub id: String,
payload: String,
#[serde(default)]
pub payload: String,
} }
impl IPCCommand { impl IPCCommand {
@ -28,6 +34,15 @@ impl IPCCommand {
"exit" => { "exit" => {
Some(Event::Action(ActionType::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 _ => None
} }
} }
@ -35,6 +50,11 @@ impl IPCCommand {
// UNIX IMPLEMENTATION // UNIX IMPLEMENTATION
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
pub fn get_ipc_manager(event_channel: Sender<Event>) -> impl IPCManager { pub fn get_ipc_server(event_channel: Sender<Event>) -> impl IPCServer {
unix::UnixIPCManager::new(event_channel) unix::UnixIPCServer::new(event_channel)
}
#[cfg(not(target_os = "windows"))]
pub fn get_ipc_client() -> impl IPCClient {
unix::UnixIPCClient::new()
} }

View File

@ -1,4 +1,5 @@
use std::io::{BufRead, BufReader, Read}; use std::io::{BufRead, BufReader, Read};
use std::io::Write;
use std::os::unix::net::{UnixStream,UnixListener}; use std::os::unix::net::{UnixStream,UnixListener};
use std::thread; use std::thread;
use log::{info, error}; use log::{info, error};
@ -11,19 +12,20 @@ use crate::event::*;
const UNIX_SOCKET_NAME : &str = "espanso.sock"; const UNIX_SOCKET_NAME : &str = "espanso.sock";
pub struct UnixIPCManager { pub struct UnixIPCServer {
event_channel: Sender<Event>, event_channel: Sender<Event>,
} }
impl UnixIPCManager { impl UnixIPCServer {
pub fn new(event_channel: Sender<Event>) -> UnixIPCManager { pub fn new(event_channel: Sender<Event>) -> UnixIPCServer {
UnixIPCManager{event_channel} UnixIPCServer {event_channel}
} }
} }
impl super::IPCManager for UnixIPCManager { impl super::IPCServer for UnixIPCServer {
fn start_server(&self) { fn start(&self) {
std::thread::spawn(|| { let event_channel = self.event_channel.clone();
std::thread::spawn(move || {
let espanso_dir = context::get_data_dir(); let espanso_dir = context::get_data_dir();
let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME); let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME);
@ -44,7 +46,7 @@ impl super::IPCManager for UnixIPCManager {
Ok(command) => { Ok(command) => {
let event = command.to_event(); let event = command.to_event();
if let Some(event) = event { if let Some(event) = event {
// TODO: send event to event channel event_channel.send(event).expect("Broken event channel");
} }
}, },
Err(e) => { Err(e) => {
@ -61,3 +63,36 @@ 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);
}
}
}
}