Add ipc support on Windows. Fix #28
This commit is contained in:
parent
b84418cea8
commit
6e6f644472
|
@ -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,
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
71
src/main.rs
71
src/main.rs
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
79
src/protocol/windows.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user