Add systemd integration on Linux. Fix #80
This commit is contained in:
parent
640fac5bf5
commit
fc74483369
|
@ -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;
|
||||||
}
|
}
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -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"))]
|
||||||
|
|
11
src/res/linux/systemd.service
Normal file
11
src/res/linux/systemd.service
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[Unit]
|
||||||
|
Description=espanso daemon
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart={{{espanso_path}}} daemon
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user