/* * 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 . */ // This functions are used to register/unregister espanso from the system daemon manager. use crate::config::ConfigSet; use crate::sysdaemon::VerifyResult::{EnabledAndValid, EnabledButInvalidPath, NotEnabled}; // INSTALLATION #[cfg(target_os = "macos")] const MAC_PLIST_CONTENT: &str = include_str!("res/mac/com.federicoterzi.espanso.plist"); #[cfg(target_os = "macos")] const MAC_PLIST_FILENAME: &str = "com.federicoterzi.espanso.plist"; #[cfg(target_os = "macos")] pub fn register(_config_set: ConfigSet) { use std::fs::create_dir_all; use std::process::{Command, ExitStatus}; let home_dir = dirs::home_dir().expect("Could not get user home directory"); let library_dir = home_dir.join("Library"); let agents_dir = library_dir.join("LaunchAgents"); // Make sure agents directory exists if !agents_dir.exists() { create_dir_all(agents_dir.clone()).expect("Could not create LaunchAgents directory"); } let plist_file = agents_dir.join(MAC_PLIST_FILENAME); if !plist_file.exists() { println!( "Creating LaunchAgents entry: {}", plist_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 plist_content = String::from(MAC_PLIST_CONTENT).replace( "{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default(), ); // Copy the user PATH variable and inject it in the Plist file so that // it gets loaded by Launchd. // To see why this is necessary: https://github.com/federico-terzi/espanso/issues/233 let user_path = std::env::var("PATH").unwrap_or("".to_owned()); let plist_content = plist_content.replace("{{{PATH}}}", &user_path); std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file"); println!("Entry created correctly!") } println!("Reloading entry..."); let res = Command::new("launchctl") .args(&["unload", "-w", plist_file.to_str().unwrap_or_default()]) .output(); let res = Command::new("launchctl") .args(&["load", "-w", plist_file.to_str().unwrap_or_default()]) .status(); if let Ok(status) = res { if status.success() { println!("Entry loaded correctly!") } } else { println!("Error loading new entry"); } } #[cfg(target_os = "macos")] pub fn unregister(_config_set: ConfigSet) { use std::fs::create_dir_all; use std::process::{Command, ExitStatus}; let home_dir = dirs::home_dir().expect("Could not get user home directory"); let library_dir = home_dir.join("Library"); let agents_dir = library_dir.join("LaunchAgents"); let plist_file = agents_dir.join(MAC_PLIST_FILENAME); if plist_file.exists() { let _res = Command::new("launchctl") .args(&["unload", "-w", plist_file.to_str().unwrap_or_default()]) .output(); std::fs::remove_file(&plist_file).expect("Could not remove espanso entry"); println!("Entry removed correctly!") } else { println!("espanso is not installed"); } } // LINUX #[cfg(target_os = "linux")] const LINUX_SERVICE_CONTENT: &str = include_str!("res/linux/systemd.service"); #[cfg(target_os = "linux")] const LINUX_SERVICE_FILENAME: &str = "espanso.service"; #[cfg(target_os = "linux")] pub fn register(_: ConfigSet) { use std::fs::create_dir_all; use std::process::Command; // 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() { if 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); } } else { if output == "disabled" { use dialoguer::Confirmation; if !Confirmation::new() .with_text("espanso is already registered but currently disabled. Do you want to override it?") .default(false) .show_default(true) .interact().expect("Unable to read user answer") { std::process::exit(6); } } } } // 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"); } } pub enum VerifyResult { EnabledAndValid, EnabledButInvalidPath, NotEnabled, } #[cfg(target_os = "linux")] pub fn verify() -> VerifyResult { use regex::Regex; use std::process::Command; // 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" { return NotEnabled; } } lazy_static! { static ref EXEC_PATH_REGEX: Regex = Regex::new("ExecStart=(?P.*?)\\s").unwrap(); } // Check if the currently registered path is valid let res = Command::new("systemctl") .args(&["--user", "cat", "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() { let caps = EXEC_PATH_REGEX.captures(output).unwrap(); let path = caps.get(1).map_or("", |m| m.as_str()); let espanso_path = std::env::current_exe().expect("Could not get espanso executable path"); if espanso_path.to_string_lossy() != path { return EnabledButInvalidPath; } } } EnabledAndValid } #[cfg(target_os = "linux")] pub fn unregister(_: ConfigSet) { use std::process::Command; // Disable the service first Command::new("systemctl") .args(&["--user", "disable", "espanso"]) .status() .expect("Unable to invoke systemctl"); // 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 #[cfg(target_os = "windows")] pub fn register(_config_set: ConfigSet) { println!("Windows does not support automatic system daemon integration.") } #[cfg(target_os = "windows")] pub fn unregister(_config_set: ConfigSet) { println!("Windows does not support automatic system daemon integration.") }