Add ipc support on Windows. Fix #28

This commit is contained in:
Federico Terzi 2019-09-16 10:16:37 +02:00
parent b84418cea8
commit 6e6f644472
6 changed files with 190 additions and 76 deletions

View File

@ -45,6 +45,7 @@ fn default_filter_class() -> String{ "".to_owned() }
fn default_filter_exec() -> String{ "".to_owned() } fn default_filter_exec() -> String{ "".to_owned() }
fn default_disabled() -> bool{ false } fn default_disabled() -> bool{ false }
fn default_log_level() -> i32 { 0 } fn default_log_level() -> i32 { 0 }
fn default_ipc_server_port() -> i32 { 34982 }
fn default_config_caching_interval() -> i32 { 800 } fn default_config_caching_interval() -> i32 { 800 }
fn default_toggle_interval() -> u32 { 230 } fn default_toggle_interval() -> u32 { 230 }
fn default_backspace_limit() -> i32 { 3 } fn default_backspace_limit() -> i32 { 3 }
@ -71,6 +72,9 @@ pub struct Configs {
#[serde(default = "default_log_level")] #[serde(default = "default_log_level")]
pub log_level: i32, pub log_level: i32,
#[serde(default = "default_ipc_server_port")]
pub ipc_server_port: i32,
#[serde(default = "default_config_caching_interval")] #[serde(default = "default_config_caching_interval")]
pub config_caching_interval: i32, pub config_caching_interval: i32,

View File

@ -19,7 +19,7 @@
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use crate::bridge::windows::*; use crate::bridge::windows::*;
use crate::event::{Event, KeyEvent, KeyModifier, ActionEvent, ActionType}; use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
use crate::event::KeyModifier::*; use crate::event::KeyModifier::*;
use std::ffi::c_void; use std::ffi::c_void;
use std::fs::create_dir_all; use std::fs::create_dir_all;

View File

@ -232,11 +232,12 @@ fn daemon_main(config_set: ConfigSet) {
let context = context::new(send_channel.clone()); let context = context::new(send_channel.clone());
let config_set_copy = config_set.clone();
thread::Builder::new().name("daemon_background".to_string()).spawn(move || { thread::Builder::new().name("daemon_background".to_string()).spawn(move || {
daemon_background(receive_channel, config_set); daemon_background(receive_channel, config_set_copy);
}).expect("Unable to spawn daemon background thread"); }).expect("Unable to spawn daemon background thread");
let ipc_server = protocol::get_ipc_server(send_channel.clone()); let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone());
ipc_server.start(); ipc_server.start();
context.eventloop(); context.eventloop();
@ -288,9 +289,16 @@ fn start_main(config_set: ConfigSet) {
precheck_guard(); precheck_guard();
if cfg!(target_os = "windows") { detach_daemon();
// TODO: start windows detached }
}else{
#[cfg(target_os = "windows")]
fn detach_daemon() {
// TODO
}
#[cfg(not(target_os = "windows"))]
fn detach_daemon() {
unsafe { unsafe {
let pid = libc::fork(); let pid = libc::fork();
if pid < 0 { if pid < 0 {
@ -321,7 +329,6 @@ fn start_main(config_set: ConfigSet) {
} }
daemon_main(config_set); daemon_main(config_set);
}
} }
/// status subcommand, print the current espanso status /// status subcommand, print the current espanso status
@ -454,7 +461,7 @@ fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) {
} }
fn send_command(config_set: ConfigSet, command: IPCCommand) -> Result<(), String> { fn send_command(config_set: ConfigSet, command: IPCCommand) -> Result<(), String> {
let ipc_client = protocol::get_ipc_client(); let ipc_client = protocol::get_ipc_client(config_set);
ipc_client.send_command(command) ipc_client.send_command(command)
} }

View File

@ -21,6 +21,10 @@ use serde::{Deserialize, Serialize};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use crate::event::Event; use crate::event::Event;
use crate::event::ActionType; use crate::event::ActionType;
use std::io::{BufReader, Read, Write};
use std::error::Error;
use log::error;
use crate::config::ConfigSet;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod windows; mod windows;
@ -64,13 +68,71 @@ impl IPCCommand {
} }
} }
fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Result<R, E>) {
match stream {
Ok(stream) => {
let mut json_str= String::new();
let mut buf_reader = BufReader::new(stream);
let res = buf_reader.read_to_string(&mut json_str);
if res.is_ok() {
let command : Result<IPCCommand, serde_json::Error> = serde_json::from_str(&json_str);
match command {
Ok(command) => {
let event = command.to_event();
if let Some(event) = event {
event_channel.send(event).expect("Broken event channel");
}
},
Err(e) => {
error!("Error deserializing JSON command: {}", e);
},
}
}
}
Err(err) => {
println!("Error: {}", err);
}
}
}
fn send_command<W: Write, E: Error>(command: IPCCommand, stream: Result<W, E>) -> Result<(), String>{
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: {}", e);
});
return Ok(())
}
},
Err(e) => {
return Err(format!("Can't connect to daemon: {}", e))
}
}
Err("Can't send command".to_owned())
}
// UNIX IMPLEMENTATION // UNIX IMPLEMENTATION
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
pub fn get_ipc_server(event_channel: Sender<Event>) -> impl IPCServer { pub fn get_ipc_server(_: ConfigSet, event_channel: Sender<Event>) -> impl IPCServer {
unix::UnixIPCServer::new(event_channel) unix::UnixIPCServer::new(event_channel)
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
pub fn get_ipc_client() -> impl IPCClient { pub fn get_ipc_client(_: ConfigSet) -> impl IPCClient {
unix::UnixIPCClient::new() unix::UnixIPCClient::new()
} }
// WINDOWS IMPLEMENTATION
#[cfg(target_os = "windows")]
pub fn get_ipc_server(config_set: ConfigSet, event_channel: Sender<Event>) -> impl IPCServer {
windows::WindowsIPCServer::new(config_set, event_channel)
}
#[cfg(target_os = "windows")]
pub fn get_ipc_client(config_set: ConfigSet) -> impl IPCClient {
windows::WindowsIPCClient::new(config_set)
}

View File

@ -26,6 +26,7 @@ use super::IPCCommand;
use crate::context; use crate::context;
use crate::event::*; use crate::event::*;
use crate::protocol::{process_event, send_command};
const UNIX_SOCKET_NAME : &str = "espanso.sock"; const UNIX_SOCKET_NAME : &str = "espanso.sock";
@ -54,32 +55,7 @@ impl super::IPCServer for UnixIPCServer {
info!("Binded to IPC unix socket: {}", unix_socket.as_path().display()); info!("Binded to IPC unix socket: {}", unix_socket.as_path().display());
for stream in listener.incoming() { for stream in listener.incoming() {
match stream { process_event(&event_channel, stream);
Ok(stream) => {
let mut json_str= String::new();
let mut buf_reader = BufReader::new(stream);
let res = buf_reader.read_to_string(&mut json_str);
if res.is_ok() {
let command : Result<IPCCommand, serde_json::Error> = serde_json::from_str(&json_str);
match command {
Ok(command) => {
let event = command.to_event();
if let Some(event) = event {
event_channel.send(event).expect("Broken event channel");
}
},
Err(e) => {
error!("Error deserializing JSON command: {}", e);
},
}
}
}
Err(err) => {
println!("Error: {}", err);
break;
}
}
} }
}).expect("Unable to spawn IPC server thread"); }).expect("Unable to spawn IPC server thread");
} }
@ -102,21 +78,7 @@ impl super::IPCClient for UnixIPCClient {
// Open the stream // Open the stream
let stream = UnixStream::connect(unix_socket); let 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: {}", e);
});
return Ok(())
}
},
Err(e) => {
return Err(format!("Can't connect to daemon: {}", e))
}
}
Err("Can't send command".to_owned()) send_command(command, stream)
} }
} }

79
src/protocol/windows.rs Normal file
View File

@ -0,0 +1,79 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::io::{BufReader, Read};
use std::io::Write;
use log::{info, error, warn};
use std::sync::mpsc::Sender;
use std::net::{TcpListener, TcpStream, SocketAddr};
use super::IPCCommand;
use crate::context;
use crate::event::*;
use crate::protocol::{process_event, send_command};
use crate::config::ConfigSet;
pub struct WindowsIPCServer {
config_set: ConfigSet,
event_channel: Sender<Event>,
}
impl WindowsIPCServer {
pub fn new(config_set: ConfigSet, event_channel: Sender<Event>) -> WindowsIPCServer {
WindowsIPCServer {config_set, event_channel}
}
}
impl super::IPCServer for WindowsIPCServer {
fn start(&self) {
let event_channel = self.event_channel.clone();
let server_port = self.config_set.default.ipc_server_port;
std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || {
let listener = TcpListener::bind(
format!("127.0.0.1:{}", server_port)
).expect("Error binding to IPC server port");
info!("Binded to IPC tcp socket: {}", listener.local_addr().unwrap().to_string());
for stream in listener.incoming() {
process_event(&event_channel, stream);
}
}).expect("Unable to spawn IPC server thread");
}
}
pub struct WindowsIPCClient {
config_set: ConfigSet,
}
impl WindowsIPCClient {
pub fn new(config_set: ConfigSet) -> WindowsIPCClient {
WindowsIPCClient{config_set}
}
}
impl super::IPCClient for WindowsIPCClient {
fn send_command(&self, command: IPCCommand) -> Result<(), String> {
let stream = TcpStream::connect(
("127.0.0.1", self.config_set.default.ipc_server_port as u16)
);
send_command(command, stream)
}
}