Add systemd integration on Linux. Fix #80

This commit is contained in:
Federico Terzi 2019-10-11 23:35:17 +02:00
parent 640fac5bf5
commit fc74483369
7 changed files with 180 additions and 11 deletions

View File

@ -84,6 +84,16 @@ void register_keypress_callback(KeypressCallback callback) {
keypress_callback = callback; keypress_callback = callback;
} }
int32_t check_x11() {
Display *check_disp = XOpenDisplay(NULL);
if (!check_disp) {
return -1;
}
XCloseDisplay(check_disp);
return 1;
}
int32_t initialize(void * _context_instance) { int32_t initialize(void * _context_instance) {
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
@ -395,4 +405,4 @@ int32_t is_current_window_terminal() {
} }
return 0; return 0;
} }

View File

@ -24,6 +24,11 @@
extern void * context_instance; extern void * context_instance;
/*
* Check if the X11 context is available
*/
extern "C" int32_t check_x11();
/* /*
* Initialize the X11 context and parameters * Initialize the X11 context and parameters
*/ */

View File

@ -22,6 +22,7 @@ use std::os::raw::{c_void, c_char};
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link(name="linuxbridge", kind="static")] #[link(name="linuxbridge", kind="static")]
extern { extern {
pub fn check_x11() -> i32;
pub fn initialize(s: *const c_void) -> i32; pub fn initialize(s: *const c_void) -> i32;
pub fn eventloop(); pub fn eventloop();
pub fn cleanup(); pub fn cleanup();

View File

@ -23,8 +23,9 @@ use crate::event::*;
use crate::event::KeyModifier::*; use crate::event::KeyModifier::*;
use crate::bridge::linux::*; use crate::bridge::linux::*;
use std::process::exit; use std::process::exit;
use log::error; use log::{error, info};
use std::ffi::CStr; use std::ffi::CStr;
use std::{thread, time};
#[repr(C)] #[repr(C)]
pub struct LinuxContext { pub struct LinuxContext {
@ -33,6 +34,16 @@ pub struct LinuxContext {
impl LinuxContext { impl LinuxContext {
pub fn new(send_channel: Sender<Event>) -> Box<LinuxContext> { pub fn new(send_channel: Sender<Event>) -> Box<LinuxContext> {
// Check if the X11 context is available
let x11_available = unsafe {
check_x11()
};
if x11_available < 0 {
error!("Error, can't connect to X11 context");
std::process::exit(100);
}
let context = Box::new(LinuxContext { let context = Box::new(LinuxContext {
send_channel, send_channel,
}); });

View File

@ -102,9 +102,9 @@ fn main() {
.subcommand(SubCommand::with_name("daemon") .subcommand(SubCommand::with_name("daemon")
.about("Start the daemon without spawning a new process.")) .about("Start the daemon without spawning a new process."))
.subcommand(SubCommand::with_name("register") .subcommand(SubCommand::with_name("register")
.about("MacOS only. Register espanso in the system daemon manager.")) .about("MacOS and Linux only. Register espanso in the system daemon manager."))
.subcommand(SubCommand::with_name("unregister") .subcommand(SubCommand::with_name("unregister")
.about("MacOS only. Unregister espanso from the system daemon manager.")) .about("MacOS and Linux only. Unregister espanso from the system daemon manager."))
.subcommand(SubCommand::with_name("log") .subcommand(SubCommand::with_name("log")
.about("Print the latest daemon logs.")) .about("Print the latest daemon logs."))
.subcommand(SubCommand::with_name("start") .subcommand(SubCommand::with_name("start")
@ -382,10 +382,10 @@ fn start_daemon(config_set: ConfigSet) {
if status.success() { if status.success() {
println!("Daemon started correctly!") println!("Daemon started correctly!")
}else{ }else{
println!("Error starting launchd daemon with status: {}", status); eprintln!("Error starting launchd daemon with status: {}", status);
} }
}else{ }else{
println!("Error starting launchd daemon: {}", res.unwrap_err()); eprintln!("Error starting launchd daemon: {}", res.unwrap_err());
} }
}else{ }else{
fork_daemon(config_set); fork_daemon(config_set);
@ -394,7 +394,50 @@ fn start_daemon(config_set: ConfigSet) {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn start_daemon(config_set: ConfigSet) { fn start_daemon(config_set: ConfigSet) {
fork_daemon(config_set); if config_set.default.use_system_agent {
use std::process::Command;
// Make sure espanso is currently registered in systemd
let res = Command::new("systemctl")
.args(&["--user", "is-enabled", "espanso.service"])
.status();
if !res.unwrap().success() {
use std::io::{self, BufRead};
eprintln!("espanso must be registered to systemd (user level) first.");
eprint!("Do you want to proceed? [Y/n]: ");
let mut line = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut line).unwrap();
let answer = line.trim().to_lowercase();
if answer != "n" {
register_main(config_set);
}else{
eprintln!("Please register espanso to systemd with this command:");
eprintln!(" espanso register");
// TODO: enable flag to use non-managed daemon mode
std::process::exit(4);
}
}
// Start the espanso service
let res = Command::new("systemctl")
.args(&["--user", "start", "espanso.service"])
.status();
if let Ok(status) = res {
if status.success() {
println!("Daemon started correctly!")
}else{
eprintln!("Error starting systemd daemon with status: {}", status);
}
}else{
eprintln!("Error starting systemd daemon: {}", res.unwrap_err());
}
}else{
fork_daemon(config_set);
}
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]

View File

@ -0,0 +1,11 @@
[Unit]
Description=espanso daemon
[Service]
ExecStart={{{espanso_path}}} daemon
Restart=on-failure
RestartSec=3
[Install]
WantedBy=default.target

View File

@ -102,13 +102,101 @@ pub fn unregister(_config_set: ConfigSet) {
// LINUX // LINUX
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn register(_config_set: ConfigSet) { const LINUX_SERVICE_CONTENT : &str = include_str!("res/linux/systemd.service");
println!("Linux does not support automatic system daemon integration."); #[cfg(target_os = "linux")]
const LINUX_SERVICE_FILENAME : &str = "espanso.service";
#[cfg(target_os = "linux")]
pub fn register(config_set: ConfigSet) {
use std::fs::create_dir_all;
use std::process::{Command, ExitStatus};
// Check if espanso service is already registered
let res = Command::new("systemctl")
.args(&["--user", "is-enabled", "espanso"])
.output();
if let Ok(res) = res {
let output = String::from_utf8_lossy(res.stdout.as_slice());
let output = output.trim();
if res.status.success() && output == "enabled" {
eprintln!("espanso service is already registered to systemd");
eprintln!("If you want to register it again, please uninstall it first with:");
eprintln!(" espanso unregister");
std::process::exit(5);
}
}
// User level systemd services should be placed in this directory:
// $XDG_CONFIG_HOME/systemd/user/, usually: ~/.config/systemd/user/
let config_dir = dirs::config_dir().expect("Could not get configuration directory");
let systemd_dir = config_dir.join("systemd");
let user_dir = systemd_dir.join("user");
// Make sure the directory exists
if !user_dir.exists() {
create_dir_all(user_dir.clone()).expect("Could not create systemd user directory");
}
let service_file = user_dir.join(LINUX_SERVICE_FILENAME);
if !service_file.exists() {
println!("Creating service entry: {}", service_file.to_str().unwrap_or_default());
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
println!("Entry will point to: {}", espanso_path.to_str().unwrap_or_default());
let service_content = String::from(LINUX_SERVICE_CONTENT)
.replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default());
std::fs::write(service_file.clone(), service_content).expect("Unable to write service file");
println!("Service file created correctly!")
}
println!("Enabling espanso for systemd...");
let res = Command::new("systemctl")
.args(&["--user", "enable", "espanso"])
.status();
if let Ok(status) = res {
if status.success() {
println!("Service registered correctly!")
}
}else{
println!("Error loading espanso service");
}
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn unregister(_config_set: ConfigSet) { pub fn unregister(config_set: ConfigSet) {
println!("Linux does not support automatic system daemon integration."); use std::process::{Command, ExitStatus};
// Disable the service first
let res = Command::new("systemctl")
.args(&["--user", "disable", "espanso"])
.status();
// Then delete the espanso.service entry
let config_dir = dirs::config_dir().expect("Could not get configuration directory");
let systemd_dir = config_dir.join("systemd");
let user_dir = systemd_dir.join("user");
let service_file = user_dir.join(LINUX_SERVICE_FILENAME);
if service_file.exists() {
let res = std::fs::remove_file(&service_file);
match res {
Ok(_) => {
println!("Deleted entry at {}", service_file.to_string_lossy());
println!("Service unregistered successfully!");
},
Err(e) => {
println!("Error, could not delete service entry at {} with error {}",
service_file.to_string_lossy(), e);
},
}
}else{
eprintln!("Error, could not find espanso service file");
}
} }
// WINDOWS // WINDOWS