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_disabled() -> bool{ false }
fn default_log_level() -> i32 { 0 }
fn default_ipc_server_port() -> i32 { 34982 }
fn default_config_caching_interval() -> i32 { 800 }
fn default_toggle_interval() -> u32 { 230 }
fn default_backspace_limit() -> i32 { 3 }
@ -71,6 +72,9 @@ pub struct Configs {
#[serde(default = "default_log_level")]
pub log_level: i32,
#[serde(default = "default_ipc_server_port")]
pub ipc_server_port: i32,
#[serde(default = "default_config_caching_interval")]
pub config_caching_interval: i32,

View File

@ -19,7 +19,7 @@
use std::sync::mpsc::Sender;
use crate::bridge::windows::*;
use crate::event::{Event, KeyEvent, KeyModifier, ActionEvent, ActionType};
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
use crate::event::KeyModifier::*;
use std::ffi::c_void;
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 config_set_copy = config_set.clone();
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");
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();
context.eventloop();
@ -288,40 +289,46 @@ fn start_main(config_set: ConfigSet) {
precheck_guard();
if cfg!(target_os = "windows") {
// TODO: start windows detached
}else{
unsafe {
let pid = libc::fork();
if pid < 0 {
error!("Unable to fork.");
exit(4);
}
if pid > 0 { // Parent process exit
println!("daemon started!");
exit(0);
}
detach_daemon();
}
// Spawned process
#[cfg(target_os = "windows")]
fn detach_daemon() {
// TODO
}
// Create a new SID for the child process
let sid = libc::setsid();
if sid < 0 {
exit(5);
}
// Detach stdout and stderr
let null_path = std::ffi::CString::new("/dev/null").expect("CString unwrap failed");
let fd = libc::open(null_path.as_ptr(), libc::O_RDWR, 0);
if fd != -1 {
libc::dup2(fd, libc::STDIN_FILENO);
libc::dup2(fd, libc::STDOUT_FILENO);
libc::dup2(fd, libc::STDERR_FILENO);
}
#[cfg(not(target_os = "windows"))]
fn detach_daemon() {
unsafe {
let pid = libc::fork();
if pid < 0 {
error!("Unable to fork.");
exit(4);
}
if pid > 0 { // Parent process exit
println!("daemon started!");
exit(0);
}
daemon_main(config_set);
// Spawned process
// Create a new SID for the child process
let sid = libc::setsid();
if sid < 0 {
exit(5);
}
// Detach stdout and stderr
let null_path = std::ffi::CString::new("/dev/null").expect("CString unwrap failed");
let fd = libc::open(null_path.as_ptr(), libc::O_RDWR, 0);
if fd != -1 {
libc::dup2(fd, libc::STDIN_FILENO);
libc::dup2(fd, libc::STDOUT_FILENO);
libc::dup2(fd, libc::STDERR_FILENO);
}
}
daemon_main(config_set);
}
/// 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> {
let ipc_client = protocol::get_ipc_client();
let ipc_client = protocol::get_ipc_client(config_set);
ipc_client.send_command(command)
}

View File

@ -21,6 +21,10 @@ use serde::{Deserialize, Serialize};
use std::sync::mpsc::Sender;
use crate::event::Event;
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")]
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
#[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)
}
#[cfg(not(target_os = "windows"))]
pub fn get_ipc_client() -> impl IPCClient {
pub fn get_ipc_client(_: ConfigSet) -> impl IPCClient {
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::event::*;
use crate::protocol::{process_event, send_command};
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());
for stream in listener.incoming() {
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);
break;
}
}
process_event(&event_channel, stream);
}
}).expect("Unable to spawn IPC server thread");
}
@ -102,21 +78,7 @@ impl super::IPCClient for UnixIPCClient {
// Open the stream
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)
}
}