diff --git a/src/config/mod.rs b/src/config/mod.rs index 1eceda6..9f0bd2d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -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, diff --git a/src/context/windows.rs b/src/context/windows.rs index eda25a0..10ba531 100644 --- a/src/context/windows.rs +++ b/src/context/windows.rs @@ -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; diff --git a/src/main.rs b/src/main.rs index 8695d5a..2da67c2 100644 --- a/src/main.rs +++ b/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) } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 9101019..7818dce 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -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(event_channel: &Sender, stream: Result) { + 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 = 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(command: IPCCommand, stream: Result) -> 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) -> impl IPCServer { +pub fn get_ipc_server(_: ConfigSet, event_channel: Sender) -> 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) -> 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) } \ No newline at end of file diff --git a/src/protocol/unix.rs b/src/protocol/unix.rs index cb2517b..30ce59c 100644 --- a/src/protocol/unix.rs +++ b/src/protocol/unix.rs @@ -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 = 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) } } \ No newline at end of file diff --git a/src/protocol/windows.rs b/src/protocol/windows.rs new file mode 100644 index 0000000..8ae826a --- /dev/null +++ b/src/protocol/windows.rs @@ -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 . + */ + +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, +} + +impl WindowsIPCServer { + pub fn new(config_set: ConfigSet, event_channel: Sender) -> 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) + } +} \ No newline at end of file