diff --git a/src/config/mod.rs b/src/config/mod.rs index 0afa216..b3f1df1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -8,7 +8,7 @@ use std::io::Read; use serde::{Serialize, Deserialize}; use crate::event::KeyModifier; use std::collections::HashSet; -use log::{error, LevelFilter}; +use log::{error}; use std::fmt; use std::error::Error; diff --git a/src/keyboard/linux.rs b/src/keyboard/linux.rs index 57049aa..a33537a 100644 --- a/src/keyboard/linux.rs +++ b/src/keyboard/linux.rs @@ -1,5 +1,3 @@ -use std::sync::mpsc; -use std::os::raw::{c_void}; use std::ffi::CString; use crate::bridge::linux::*; diff --git a/src/main.rs b/src/main.rs index 0c861ef..eecceeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::time::Duration; use clap::{App, Arg, SubCommand, ArgMatches}; use fs2::FileExt; -use log::{error, info, LevelFilter}; +use log::{error, info, warn, LevelFilter}; use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger}; use crate::config::ConfigSet; @@ -69,6 +69,10 @@ fn main() { .about("Start the daemon without spawning a new process.")) .subcommand(SubCommand::with_name("start") .about("Start the daemon spawning a new process in the background.")) + .subcommand(SubCommand::with_name("stop") + .about("Stop the espanso daemon.")) + .subcommand(SubCommand::with_name("restart") + .about("Restart the espanso daemon.")) .subcommand(SubCommand::with_name("status") .about("Check if the espanso daemon is running or not.")) .get_matches(); @@ -98,33 +102,43 @@ fn main() { // Match the correct subcommand - if let Some(matches) = matches.subcommand_matches("dump") { + if let Some(matches) = matches.subcommand_matches("cmd") { + cmd_main(config_set, matches); + return; + } + + if let Some(_) = matches.subcommand_matches("dump") { println!("{:#?}", config_set); return; } - if let Some(matches) = matches.subcommand_matches("detect") { + if let Some(_) = matches.subcommand_matches("detect") { detect_main(); return; } - if let Some(matches) = matches.subcommand_matches("daemon") { + if let Some(_) = matches.subcommand_matches("daemon") { daemon_main(config_set); return; } - if let Some(matches) = matches.subcommand_matches("start") { + if let Some(_) = matches.subcommand_matches("start") { start_main(config_set); return; } - if let Some(matches) = matches.subcommand_matches("status") { + if let Some(_) = matches.subcommand_matches("status") { status_main(); return; } - if let Some(matches) = matches.subcommand_matches("cmd") { - cmd_main(matches); + if let Some(_) = matches.subcommand_matches("stop") { + stop_main(config_set); + return; + } + + if let Some(_) = matches.subcommand_matches("restart") { + restart_main(config_set); return; } } @@ -255,6 +269,49 @@ fn status_main() { } } + +/// Stop subcommand, used to stop the daemon. +fn stop_main(config_set: ConfigSet) { + // Try to acquire lock file + let lock_file = acquire_lock(); + if lock_file.is_some() { + println!("espanso daemon is not running."); + release_lock(lock_file.unwrap()); + exit(3); + } + + let res = send_command(config_set, IPCCommand{ + id: "exit".to_owned(), + payload: "".to_owned(), + }); + + if let Err(e) = res { + println!("{}", e); + exit(1); + }else{ + exit(0); + } +} + +fn restart_main(config_set: ConfigSet) { + // Kill the daemon if running + let lock_file = acquire_lock(); + if lock_file.is_none() { + // Terminate the current espanso daemon + send_command(config_set.clone(), IPCCommand{ + id: "exit".to_owned(), + payload: "".to_owned(), + }).unwrap_or_else(|e| warn!("Unable to send IPC command to daemon: {}", e)); + }else{ + release_lock(lock_file.unwrap()); + } + + std::thread::sleep(Duration::from_millis(300)); + + // Restart the daemon + start_main(config_set); +} + /// Cli tool used to analyze active windows to extract useful information /// to create configuration filters. fn detect_main() { @@ -289,23 +346,23 @@ fn detect_main() { } } -fn cmd_main(matches: &ArgMatches) { - let command = if let Some(matches) = matches.subcommand_matches("exit") { +fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) { + let command = if let Some(_) = matches.subcommand_matches("exit") { Some(IPCCommand { id: String::from("exit"), payload: String::from(""), }) - }else if let Some(matches) = matches.subcommand_matches("toggle") { + }else if let Some(_) = matches.subcommand_matches("toggle") { Some(IPCCommand { id: String::from("toggle"), payload: String::from(""), }) - }else if let Some(matches) = matches.subcommand_matches("enable") { + }else if let Some(_) = matches.subcommand_matches("enable") { Some(IPCCommand { id: String::from("enable"), payload: String::from(""), }) - }else if let Some(matches) = matches.subcommand_matches("disable") { + }else if let Some(_) = matches.subcommand_matches("disable") { Some(IPCCommand { id: String::from("disable"), payload: String::from(""), @@ -315,15 +372,23 @@ fn cmd_main(matches: &ArgMatches) { }; if let Some(command) = command { - let ipc_client = protocol::get_ipc_client(); - ipc_client.send_command(command); + let res = send_command(config_set, command); - exit(0); + if res.is_ok() { + exit(0); + }else{ + println!("{}", res.unwrap_err()); + } } exit(1); } +fn send_command(config_set: ConfigSet, command: IPCCommand) -> Result<(), String> { + let ipc_client = protocol::get_ipc_client(); + ipc_client.send_command(command) +} + fn acquire_lock() -> Option { let espanso_dir = context::get_data_dir(); let lock_file_path = espanso_dir.join("espanso.lock"); diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index bdc0bea..ade89e5 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,10 +1,7 @@ use serde::{Deserialize, Serialize}; -use serde_json::Result; use std::sync::mpsc::Sender; use crate::event::Event; -use crate::event::Event::*; use crate::event::ActionType; -use crate::event::ActionType::*; #[cfg(target_os = "windows")] mod windows; @@ -17,7 +14,7 @@ pub trait IPCServer { } pub trait IPCClient { - fn send_command(&self, command: IPCCommand); + fn send_command(&self, command: IPCCommand) -> Result<(), String>; } #[derive(Serialize, Deserialize, Debug)] diff --git a/src/protocol/unix.rs b/src/protocol/unix.rs index 56960ff..cca977c 100644 --- a/src/protocol/unix.rs +++ b/src/protocol/unix.rs @@ -1,13 +1,11 @@ -use std::io::{BufRead, BufReader, Read}; +use std::io::{BufReader, Read}; use std::io::Write; use std::os::unix::net::{UnixStream,UnixListener}; -use std::thread; -use log::{info, error}; +use log::{info, error, warn}; use std::sync::mpsc::Sender; use super::IPCCommand; use crate::context; -use crate::context::get_data_dir; use crate::event::*; const UNIX_SOCKET_NAME : &str = "espanso.sock"; @@ -29,7 +27,9 @@ impl super::IPCServer for UnixIPCServer { let espanso_dir = context::get_data_dir(); let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME); - std::fs::remove_file(unix_socket.clone()); + std::fs::remove_file(unix_socket.clone()).unwrap_or_else(|e| { + warn!("Unable to delete Unix socket: {}", e); + }); let listener = UnixListener::bind(unix_socket.clone()).expect("Can't bind to Unix Socket"); info!("Binded to IPC unix socket: {}", unix_socket.as_path().display()); @@ -39,19 +39,21 @@ impl super::IPCServer for UnixIPCServer { Ok(stream) => { let mut json_str= String::new(); let mut buf_reader = BufReader::new(stream); - buf_reader.read_to_string(&mut json_str); + let res = buf_reader.read_to_string(&mut json_str); - 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); - }, + 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) => { @@ -75,24 +77,27 @@ impl UnixIPCClient { } impl super::IPCClient for UnixIPCClient { - fn send_command(&self, command: IPCCommand) { + fn send_command(&self, command: IPCCommand) -> Result<(), String> { 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); + 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"); + println!("Can't write to IPC socket: {}", e); }); + return Ok(()) } }, Err(e) => { - println!("Can't connect to daemon: {}", e); + return Err(format!("Can't connect to daemon: {}", e)) } } + + Err("Can't send command".to_owned()) } } \ No newline at end of file diff --git a/src/ui/linux.rs b/src/ui/linux.rs index ca88267..ca1ac40 100644 --- a/src/ui/linux.rs +++ b/src/ui/linux.rs @@ -14,8 +14,8 @@ impl super::UIManager for LinuxUIManager { } } - fn show_menu(&self, menu: Vec) { - unimplemented!() + fn show_menu(&self, _menu: Vec) { + // Not implemented on linux } }