2019-09-15 16:29:11 +00:00
|
|
|
/*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
2019-09-15 11:03:21 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
|
|
|
|
2019-09-14 08:03:25 +00:00
|
|
|
use std::thread;
|
|
|
|
use std::fs::{File, OpenOptions};
|
2019-09-05 21:06:43 +00:00
|
|
|
use std::path::Path;
|
2019-09-10 20:53:45 +00:00
|
|
|
use std::process::exit;
|
2019-09-14 08:03:25 +00:00
|
|
|
use std::sync::mpsc;
|
|
|
|
use std::sync::mpsc::Receiver;
|
2019-09-13 21:30:34 +00:00
|
|
|
use std::time::Duration;
|
2019-09-14 08:03:25 +00:00
|
|
|
|
2019-09-14 18:13:09 +00:00
|
|
|
use clap::{App, Arg, SubCommand, ArgMatches};
|
2019-09-13 22:38:45 +00:00
|
|
|
use fs2::FileExt;
|
2019-09-16 09:47:25 +00:00
|
|
|
use log::{info, warn, LevelFilter};
|
2019-09-14 20:54:16 +00:00
|
|
|
use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger};
|
2019-09-14 08:03:25 +00:00
|
|
|
|
|
|
|
use crate::config::ConfigSet;
|
|
|
|
use crate::config::runtime::RuntimeConfigManager;
|
|
|
|
use crate::engine::Engine;
|
|
|
|
use crate::event::*;
|
|
|
|
use crate::event::manager::{DefaultEventManager, EventManager};
|
|
|
|
use crate::matcher::scrolling::ScrollingMatcher;
|
|
|
|
use crate::system::SystemManager;
|
|
|
|
use crate::ui::UIManager;
|
2019-09-14 10:19:11 +00:00
|
|
|
use crate::protocol::*;
|
2019-09-14 20:54:16 +00:00
|
|
|
use std::io::{BufReader, BufRead};
|
2019-08-30 16:32:10 +00:00
|
|
|
|
2019-09-07 11:35:45 +00:00
|
|
|
mod ui;
|
2019-09-12 20:14:41 +00:00
|
|
|
mod event;
|
2019-09-15 13:39:18 +00:00
|
|
|
mod check;
|
2019-09-07 11:35:45 +00:00
|
|
|
mod bridge;
|
2019-08-31 14:07:45 +00:00
|
|
|
mod engine;
|
2019-09-01 20:00:31 +00:00
|
|
|
mod config;
|
2019-09-07 11:35:45 +00:00
|
|
|
mod system;
|
2019-09-20 20:33:14 +00:00
|
|
|
mod sysdaemon;
|
2019-09-12 20:14:41 +00:00
|
|
|
mod context;
|
2019-09-07 11:35:45 +00:00
|
|
|
mod matcher;
|
|
|
|
mod keyboard;
|
2019-09-14 10:19:11 +00:00
|
|
|
mod protocol;
|
2019-09-06 22:38:13 +00:00
|
|
|
mod clipboard;
|
2019-09-15 11:03:21 +00:00
|
|
|
mod extension;
|
2019-08-30 12:33:40 +00:00
|
|
|
|
2019-09-05 21:06:43 +00:00
|
|
|
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
2019-09-14 20:54:16 +00:00
|
|
|
const LOG_FILE: &str = "espanso.log";
|
2019-09-05 21:06:43 +00:00
|
|
|
|
2019-08-30 12:33:40 +00:00
|
|
|
fn main() {
|
2019-09-05 21:06:43 +00:00
|
|
|
let matches = App::new("espanso")
|
|
|
|
.version(VERSION)
|
|
|
|
.author("Federico Terzi")
|
|
|
|
.about("Cross-platform Text Expander written in Rust")
|
|
|
|
.arg(Arg::with_name("config")
|
|
|
|
.short("c")
|
|
|
|
.long("config")
|
|
|
|
.value_name("FILE")
|
2019-09-07 11:35:45 +00:00
|
|
|
.help("Sets a custom config directory. If not specified, reads the default $HOME/.espanso/default.yaml file, creating it if not present.")
|
2019-09-05 21:06:43 +00:00
|
|
|
.takes_value(true))
|
|
|
|
.arg(Arg::with_name("v")
|
|
|
|
.short("v")
|
|
|
|
.multiple(true)
|
|
|
|
.help("Sets the level of verbosity"))
|
2019-09-14 18:13:09 +00:00
|
|
|
.subcommand(SubCommand::with_name("cmd")
|
|
|
|
.about("Send a command to the espanso daemon.")
|
|
|
|
.subcommand(SubCommand::with_name("exit")
|
|
|
|
.about("Terminate the daemon."))
|
|
|
|
.subcommand(SubCommand::with_name("enable")
|
|
|
|
.about("Enable the espanso replacement engine."))
|
|
|
|
.subcommand(SubCommand::with_name("disable")
|
|
|
|
.about("Disable the espanso replacement engine."))
|
|
|
|
.subcommand(SubCommand::with_name("toggle")
|
|
|
|
.about("Toggle the status of the espanso replacement engine."))
|
|
|
|
)
|
2019-09-13 22:10:52 +00:00
|
|
|
.subcommand(SubCommand::with_name("dump")
|
|
|
|
.about("Prints all current configuration options."))
|
2019-09-13 21:30:34 +00:00
|
|
|
.subcommand(SubCommand::with_name("detect")
|
2019-09-13 22:10:52 +00:00
|
|
|
.about("Tool to detect current window properties, to simplify filters creation."))
|
|
|
|
.subcommand(SubCommand::with_name("daemon")
|
|
|
|
.about("Start the daemon without spawning a new process."))
|
2019-09-20 20:33:14 +00:00
|
|
|
.subcommand(SubCommand::with_name("register")
|
|
|
|
.about("MacOS only. Register espanso in the system daemon manager."))
|
|
|
|
.subcommand(SubCommand::with_name("unregister")
|
|
|
|
.about("MacOS only. Unregister espanso from the system daemon manager."))
|
2019-09-14 20:54:16 +00:00
|
|
|
.subcommand(SubCommand::with_name("log")
|
|
|
|
.about("Print the latest daemon logs."))
|
2019-09-14 08:03:25 +00:00
|
|
|
.subcommand(SubCommand::with_name("start")
|
|
|
|
.about("Start the daemon spawning a new process in the background."))
|
2019-09-14 19:38:47 +00:00
|
|
|
.subcommand(SubCommand::with_name("stop")
|
|
|
|
.about("Stop the espanso daemon."))
|
|
|
|
.subcommand(SubCommand::with_name("restart")
|
|
|
|
.about("Restart the espanso daemon."))
|
2019-09-13 22:38:45 +00:00
|
|
|
.subcommand(SubCommand::with_name("status")
|
|
|
|
.about("Check if the espanso daemon is running or not."))
|
2019-09-05 21:06:43 +00:00
|
|
|
.get_matches();
|
|
|
|
|
2019-09-14 08:30:51 +00:00
|
|
|
let log_level = matches.occurrences_of("v") as i32;
|
2019-09-07 15:59:34 +00:00
|
|
|
|
2019-09-14 08:30:51 +00:00
|
|
|
// Load the configuration
|
|
|
|
let mut config_set = match matches.value_of("config") {
|
2019-09-07 15:59:34 +00:00
|
|
|
None => {
|
2019-09-14 08:30:51 +00:00
|
|
|
if log_level > 1 {
|
|
|
|
println!("loading configuration from default location...");
|
|
|
|
}
|
2019-09-07 15:59:34 +00:00
|
|
|
ConfigSet::load_default()
|
|
|
|
},
|
|
|
|
Some(path) => {
|
2019-09-14 08:30:51 +00:00
|
|
|
if log_level > 1 {
|
|
|
|
println!("loading configuration from custom location: {}", path);
|
|
|
|
}
|
2019-09-07 15:59:34 +00:00
|
|
|
ConfigSet::load(Path::new(path))
|
|
|
|
},
|
2019-09-10 20:53:45 +00:00
|
|
|
}.unwrap_or_else(|e| {
|
2019-09-14 08:30:51 +00:00
|
|
|
println!("{}", e);
|
2019-09-10 20:53:45 +00:00
|
|
|
exit(1);
|
|
|
|
});
|
2019-09-05 21:06:43 +00:00
|
|
|
|
2019-09-14 08:30:51 +00:00
|
|
|
config_set.default.log_level = log_level;
|
|
|
|
|
|
|
|
// Match the correct subcommand
|
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
if let Some(matches) = matches.subcommand_matches("cmd") {
|
|
|
|
cmd_main(config_set, matches);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(_) = matches.subcommand_matches("dump") {
|
2019-09-07 11:35:45 +00:00
|
|
|
println!("{:#?}", config_set);
|
2019-09-05 21:06:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
if let Some(_) = matches.subcommand_matches("detect") {
|
2019-09-13 21:30:34 +00:00
|
|
|
detect_main();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
if let Some(_) = matches.subcommand_matches("daemon") {
|
2019-09-13 22:10:52 +00:00
|
|
|
daemon_main(config_set);
|
|
|
|
return;
|
|
|
|
}
|
2019-09-13 22:38:45 +00:00
|
|
|
|
2019-09-20 20:33:14 +00:00
|
|
|
if let Some(_) = matches.subcommand_matches("register") {
|
|
|
|
register_main(config_set);
|
2019-09-16 22:11:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-20 20:33:14 +00:00
|
|
|
if let Some(_) = matches.subcommand_matches("unregister") {
|
|
|
|
unregister_main(config_set);
|
2019-09-16 22:11:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-14 20:54:16 +00:00
|
|
|
if let Some(_) = matches.subcommand_matches("log") {
|
|
|
|
log_main();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
if let Some(_) = matches.subcommand_matches("start") {
|
2019-09-14 08:03:25 +00:00
|
|
|
start_main(config_set);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
if let Some(_) = matches.subcommand_matches("status") {
|
2019-09-13 22:38:45 +00:00
|
|
|
status_main();
|
|
|
|
return;
|
|
|
|
}
|
2019-09-14 18:13:09 +00:00
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
if let Some(_) = matches.subcommand_matches("stop") {
|
|
|
|
stop_main(config_set);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(_) = matches.subcommand_matches("restart") {
|
|
|
|
restart_main(config_set);
|
2019-09-14 18:13:09 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-09-15 13:39:18 +00:00
|
|
|
|
|
|
|
// Defaults to start subcommand
|
|
|
|
start_main(config_set);
|
2019-09-05 21:06:43 +00:00
|
|
|
}
|
2019-08-30 12:33:40 +00:00
|
|
|
|
2019-09-14 08:03:25 +00:00
|
|
|
/// Daemon subcommand, start the event loop and spawn a background thread worker
|
2019-09-13 21:30:34 +00:00
|
|
|
fn daemon_main(config_set: ConfigSet) {
|
2019-09-13 22:38:45 +00:00
|
|
|
// Try to acquire lock file
|
|
|
|
let lock_file = acquire_lock();
|
|
|
|
if lock_file.is_none() {
|
2019-09-14 08:30:51 +00:00
|
|
|
println!("espanso is already running.");
|
2019-09-13 22:38:45 +00:00
|
|
|
exit(3);
|
|
|
|
}
|
|
|
|
|
2019-09-15 13:39:18 +00:00
|
|
|
precheck_guard();
|
|
|
|
|
2019-09-14 08:30:51 +00:00
|
|
|
// Initialize log
|
|
|
|
let log_level = match config_set.default.log_level {
|
|
|
|
0 => LevelFilter::Warn,
|
|
|
|
1 => LevelFilter::Info,
|
|
|
|
2 | _ => LevelFilter::Debug,
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut log_outputs: Vec<Box<dyn SharedLogger>> = Vec::new();
|
|
|
|
|
|
|
|
// Initialize terminal output
|
|
|
|
let terminal_out = TermLogger::new(log_level,
|
|
|
|
simplelog::Config::default(), TerminalMode::Mixed);
|
|
|
|
if let Some(terminal_out) = terminal_out {
|
|
|
|
log_outputs.push(terminal_out);
|
|
|
|
}
|
|
|
|
|
2019-09-14 20:54:16 +00:00
|
|
|
// Initialize log file output
|
|
|
|
let espanso_dir = context::get_data_dir();
|
|
|
|
let log_file_path = espanso_dir.join(LOG_FILE);
|
|
|
|
let log_file = OpenOptions::new()
|
|
|
|
.read(true)
|
|
|
|
.write(true)
|
|
|
|
.create(true)
|
|
|
|
.truncate(true)
|
|
|
|
.open(log_file_path)
|
|
|
|
.expect("Cannot create log file.");
|
|
|
|
let file_out = WriteLogger::new(LevelFilter::Info, simplelog::Config::default(), log_file);
|
|
|
|
log_outputs.push(file_out);
|
|
|
|
|
2019-09-14 08:30:51 +00:00
|
|
|
CombinedLogger::init(
|
|
|
|
log_outputs
|
|
|
|
).expect("Error opening log destination");
|
|
|
|
|
2019-09-14 20:54:16 +00:00
|
|
|
// Activate logging for panics
|
|
|
|
log_panics::init();
|
|
|
|
|
2019-09-16 09:47:25 +00:00
|
|
|
info!("espanso version {}", VERSION);
|
2019-09-13 22:10:52 +00:00
|
|
|
info!("starting daemon...");
|
|
|
|
|
2019-09-12 20:14:41 +00:00
|
|
|
let (send_channel, receive_channel) = mpsc::channel();
|
|
|
|
|
2019-09-14 10:19:11 +00:00
|
|
|
let context = context::new(send_channel.clone());
|
2019-09-07 14:13:13 +00:00
|
|
|
|
2019-09-16 08:16:37 +00:00
|
|
|
let config_set_copy = config_set.clone();
|
2019-09-14 20:54:16 +00:00
|
|
|
thread::Builder::new().name("daemon_background".to_string()).spawn(move || {
|
2019-09-16 08:16:37 +00:00
|
|
|
daemon_background(receive_channel, config_set_copy);
|
2019-09-14 20:54:16 +00:00
|
|
|
}).expect("Unable to spawn daemon background thread");
|
2019-09-07 14:13:13 +00:00
|
|
|
|
2019-09-16 08:16:37 +00:00
|
|
|
let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone());
|
2019-09-14 18:13:09 +00:00
|
|
|
ipc_server.start();
|
2019-09-14 10:19:11 +00:00
|
|
|
|
2019-09-12 20:14:41 +00:00
|
|
|
context.eventloop();
|
2019-09-07 14:13:13 +00:00
|
|
|
}
|
2019-09-06 14:06:41 +00:00
|
|
|
|
2019-09-14 08:03:25 +00:00
|
|
|
/// Background thread worker for the daemon
|
2019-09-13 21:30:34 +00:00
|
|
|
fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet) {
|
2019-09-07 11:35:45 +00:00
|
|
|
let system_manager = system::get_manager();
|
2019-09-07 14:13:13 +00:00
|
|
|
let config_manager = RuntimeConfigManager::new(config_set, system_manager);
|
2019-09-07 11:35:45 +00:00
|
|
|
|
2019-09-07 14:13:13 +00:00
|
|
|
let ui_manager = ui::get_uimanager();
|
2019-09-09 13:15:01 +00:00
|
|
|
ui_manager.notify("espanso is running!");
|
2019-09-06 22:38:13 +00:00
|
|
|
|
2019-09-07 14:13:13 +00:00
|
|
|
let clipboard_manager = clipboard::get_manager();
|
2019-08-30 16:32:10 +00:00
|
|
|
|
2019-09-15 11:03:21 +00:00
|
|
|
let keyboard_manager = keyboard::get_manager();
|
|
|
|
|
|
|
|
let extensions = extension::get_extensions();
|
2019-08-31 14:07:45 +00:00
|
|
|
|
2019-09-15 11:03:21 +00:00
|
|
|
let engine = Engine::new(&keyboard_manager,
|
2019-09-07 14:13:13 +00:00
|
|
|
&clipboard_manager,
|
2019-09-08 11:37:58 +00:00
|
|
|
&config_manager,
|
2019-09-15 11:03:21 +00:00
|
|
|
&ui_manager,
|
|
|
|
extensions,
|
2019-09-08 11:37:58 +00:00
|
|
|
);
|
2019-08-31 14:07:45 +00:00
|
|
|
|
2019-09-12 20:14:41 +00:00
|
|
|
let matcher = ScrollingMatcher::new(&config_manager, &engine);
|
|
|
|
|
|
|
|
let event_manager = DefaultEventManager::new(
|
|
|
|
receive_channel,
|
2019-09-12 21:53:17 +00:00
|
|
|
vec!(&matcher),
|
2019-09-13 12:43:48 +00:00
|
|
|
vec!(&engine, &matcher),
|
2019-09-12 20:14:41 +00:00
|
|
|
);
|
|
|
|
|
2019-09-13 22:10:52 +00:00
|
|
|
info!("espanso is running!");
|
|
|
|
|
2019-09-12 20:14:41 +00:00
|
|
|
event_manager.eventloop();
|
2019-09-13 21:30:34 +00:00
|
|
|
}
|
|
|
|
|
2019-09-14 08:03:25 +00:00
|
|
|
/// start subcommand, spawn a background espanso process.
|
|
|
|
fn start_main(config_set: ConfigSet) {
|
|
|
|
// Try to acquire lock file
|
|
|
|
let lock_file = acquire_lock();
|
|
|
|
if lock_file.is_none() {
|
|
|
|
println!("espanso is already running.");
|
|
|
|
exit(3);
|
|
|
|
}
|
|
|
|
release_lock(lock_file.unwrap());
|
|
|
|
|
2019-09-15 13:39:18 +00:00
|
|
|
precheck_guard();
|
|
|
|
|
2019-09-16 22:11:31 +00:00
|
|
|
start_daemon(config_set);
|
2019-09-16 08:16:37 +00:00
|
|
|
}
|
2019-09-14 08:03:25 +00:00
|
|
|
|
2019-09-16 08:16:37 +00:00
|
|
|
#[cfg(target_os = "windows")]
|
2019-09-16 22:11:31 +00:00
|
|
|
fn start_daemon(_: ConfigSet) {
|
2019-09-16 08:56:14 +00:00
|
|
|
unsafe {
|
|
|
|
let res = bridge::windows::start_daemon_process();
|
|
|
|
if res < 0 {
|
|
|
|
println!("Error starting daemon process");
|
|
|
|
}
|
|
|
|
}
|
2019-09-16 08:16:37 +00:00
|
|
|
}
|
2019-09-14 08:03:25 +00:00
|
|
|
|
2019-09-16 22:11:31 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
fn start_daemon(config_set: ConfigSet) {
|
|
|
|
if config_set.default.use_system_agent {
|
|
|
|
use std::process::Command;
|
|
|
|
|
|
|
|
let res = Command::new("launchctl")
|
|
|
|
.args(&["start", "com.federicoterzi.espanso"])
|
|
|
|
.status();
|
|
|
|
|
|
|
|
if let Ok(status) = res {
|
|
|
|
if status.success() {
|
|
|
|
println!("Daemon started correctly!")
|
|
|
|
}else{
|
|
|
|
println!("Error starting launchd daemon with status: {}", status);
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
println!("Error starting launchd daemon: {}", res.unwrap_err());
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
fork_daemon(config_set);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
fn start_daemon(config_set: ConfigSet) {
|
|
|
|
if config_set.default.use_system_agent {
|
|
|
|
// TODO: systemd
|
|
|
|
}else{
|
|
|
|
fork_daemon(config_set);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-16 08:16:37 +00:00
|
|
|
#[cfg(not(target_os = "windows"))]
|
2019-09-16 22:11:31 +00:00
|
|
|
fn fork_daemon(config_set: ConfigSet) {
|
2019-09-16 08:16:37 +00:00
|
|
|
unsafe {
|
|
|
|
let pid = libc::fork();
|
|
|
|
if pid < 0 {
|
2019-09-16 09:47:25 +00:00
|
|
|
println!("Unable to fork.");
|
2019-09-16 08:16:37 +00:00
|
|
|
exit(4);
|
|
|
|
}
|
|
|
|
if pid > 0 { // Parent process exit
|
|
|
|
println!("daemon started!");
|
|
|
|
exit(0);
|
|
|
|
}
|
2019-09-15 14:04:27 +00:00
|
|
|
|
2019-09-16 08:16:37 +00:00
|
|
|
// Spawned process
|
|
|
|
|
|
|
|
// Create a new SID for the child process
|
|
|
|
let sid = libc::setsid();
|
|
|
|
if sid < 0 {
|
|
|
|
exit(5);
|
2019-09-14 08:03:25 +00:00
|
|
|
}
|
|
|
|
|
2019-09-16 08:16:37 +00:00
|
|
|
// 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);
|
|
|
|
}
|
2019-09-14 08:03:25 +00:00
|
|
|
}
|
2019-09-16 08:16:37 +00:00
|
|
|
|
|
|
|
daemon_main(config_set);
|
2019-09-14 08:03:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// status subcommand, print the current espanso status
|
2019-09-13 22:38:45 +00:00
|
|
|
fn status_main() {
|
|
|
|
let lock_file = acquire_lock();
|
|
|
|
if let Some(lock_file) = lock_file {
|
|
|
|
println!("espanso is not running");
|
|
|
|
|
|
|
|
release_lock(lock_file);
|
|
|
|
}else{
|
|
|
|
println!("espanso is running");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
|
|
|
|
/// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-14 20:54:16 +00:00
|
|
|
/// Kill the daemon if running and start it again
|
2019-09-14 19:38:47 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-09-13 21:30:34 +00:00
|
|
|
/// Cli tool used to analyze active windows to extract useful information
|
|
|
|
/// to create configuration filters.
|
|
|
|
fn detect_main() {
|
|
|
|
let system_manager = system::get_manager();
|
|
|
|
|
|
|
|
println!("Listening for changes, now focus the window you want to analyze.");
|
|
|
|
println!("You can terminate with CTRL+C\n");
|
|
|
|
|
|
|
|
let mut last_title : String = "".to_owned();
|
|
|
|
let mut last_class : String = "".to_owned();
|
|
|
|
let mut last_exec : String = "".to_owned();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let curr_title = system_manager.get_current_window_title().unwrap_or_default();
|
|
|
|
let curr_class = system_manager.get_current_window_class().unwrap_or_default();
|
|
|
|
let curr_exec = system_manager.get_current_window_executable().unwrap_or_default();
|
|
|
|
|
|
|
|
// Check if a change occurred
|
|
|
|
if curr_title != last_title || curr_class != last_class || curr_exec != last_exec {
|
|
|
|
println!("Detected change, current window has properties:");
|
|
|
|
println!("==> Title: '{}'", curr_title);
|
|
|
|
println!("==> Class: '{}'", curr_class);
|
|
|
|
println!("==> Executable: '{}'", curr_exec);
|
2019-09-14 10:19:11 +00:00
|
|
|
println!();
|
2019-09-13 21:30:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
last_title = curr_title;
|
|
|
|
last_class = curr_class;
|
|
|
|
last_exec = curr_exec;
|
|
|
|
|
|
|
|
thread::sleep(Duration::from_millis(500));
|
|
|
|
}
|
2019-09-13 22:38:45 +00:00
|
|
|
}
|
|
|
|
|
2019-09-14 20:54:16 +00:00
|
|
|
/// Send the given command to the espanso daemon
|
2019-09-14 19:38:47 +00:00
|
|
|
fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) {
|
|
|
|
let command = if let Some(_) = matches.subcommand_matches("exit") {
|
2019-09-14 18:13:09 +00:00
|
|
|
Some(IPCCommand {
|
|
|
|
id: String::from("exit"),
|
|
|
|
payload: String::from(""),
|
|
|
|
})
|
2019-09-14 19:38:47 +00:00
|
|
|
}else if let Some(_) = matches.subcommand_matches("toggle") {
|
2019-09-14 18:13:09 +00:00
|
|
|
Some(IPCCommand {
|
|
|
|
id: String::from("toggle"),
|
|
|
|
payload: String::from(""),
|
|
|
|
})
|
2019-09-14 19:38:47 +00:00
|
|
|
}else if let Some(_) = matches.subcommand_matches("enable") {
|
2019-09-14 18:13:09 +00:00
|
|
|
Some(IPCCommand {
|
|
|
|
id: String::from("enable"),
|
|
|
|
payload: String::from(""),
|
|
|
|
})
|
2019-09-14 19:38:47 +00:00
|
|
|
}else if let Some(_) = matches.subcommand_matches("disable") {
|
2019-09-14 18:13:09 +00:00
|
|
|
Some(IPCCommand {
|
|
|
|
id: String::from("disable"),
|
|
|
|
payload: String::from(""),
|
|
|
|
})
|
|
|
|
}else{
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(command) = command {
|
2019-09-14 19:38:47 +00:00
|
|
|
let res = send_command(config_set, command);
|
2019-09-14 18:13:09 +00:00
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
if res.is_ok() {
|
|
|
|
exit(0);
|
|
|
|
}else{
|
|
|
|
println!("{}", res.unwrap_err());
|
|
|
|
}
|
2019-09-14 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2019-09-14 19:38:47 +00:00
|
|
|
fn send_command(config_set: ConfigSet, command: IPCCommand) -> Result<(), String> {
|
2019-09-16 08:16:37 +00:00
|
|
|
let ipc_client = protocol::get_ipc_client(config_set);
|
2019-09-14 19:38:47 +00:00
|
|
|
ipc_client.send_command(command)
|
|
|
|
}
|
|
|
|
|
2019-09-14 20:54:16 +00:00
|
|
|
fn log_main() {
|
|
|
|
let espanso_dir = context::get_data_dir();
|
|
|
|
let log_file_path = espanso_dir.join(LOG_FILE);
|
|
|
|
|
|
|
|
if !log_file_path.exists() {
|
|
|
|
println!("No log file found.");
|
|
|
|
exit(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
let log_file = File::open(log_file_path);
|
|
|
|
if let Ok(log_file) = log_file {
|
|
|
|
let reader = BufReader::new(log_file);
|
|
|
|
for line in reader.lines() {
|
|
|
|
if let Ok(line) = line {
|
|
|
|
println!("{}", line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}else{
|
|
|
|
println!("Error reading log file");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-20 20:33:14 +00:00
|
|
|
fn register_main(config_set: ConfigSet) {
|
|
|
|
sysdaemon::register(config_set);
|
2019-09-16 22:11:31 +00:00
|
|
|
}
|
|
|
|
|
2019-09-20 20:33:14 +00:00
|
|
|
fn unregister_main(config_set: ConfigSet) {
|
|
|
|
sysdaemon::unregister(config_set);
|
2019-09-16 22:11:31 +00:00
|
|
|
}
|
|
|
|
|
2019-09-13 22:38:45 +00:00
|
|
|
fn acquire_lock() -> Option<File> {
|
|
|
|
let espanso_dir = context::get_data_dir();
|
|
|
|
let lock_file_path = espanso_dir.join("espanso.lock");
|
|
|
|
let file = OpenOptions::new()
|
|
|
|
.read(true)
|
|
|
|
.write(true)
|
|
|
|
.create(true)
|
|
|
|
.open(lock_file_path)
|
|
|
|
.expect("Cannot create reference to lock file.");
|
|
|
|
|
|
|
|
let res = file.try_lock_exclusive();
|
|
|
|
|
|
|
|
if let Ok(_) = res {
|
|
|
|
return Some(file)
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn release_lock(lock_file: File) {
|
|
|
|
lock_file.unlock().unwrap()
|
2019-09-15 13:39:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Used to make sure all the required dependencies are present before starting espanso.
|
|
|
|
fn precheck_guard() {
|
|
|
|
let satisfied = check::check_dependencies();
|
|
|
|
if !satisfied {
|
|
|
|
println!();
|
|
|
|
println!("Pre-check was not successful, espanso could not be started.");
|
|
|
|
exit(5);
|
|
|
|
}
|
2019-08-30 12:33:40 +00:00
|
|
|
}
|