Create macOS install/uninstall subcommands
This commit is contained in:
parent
2af285d01d
commit
8e6af972ad
|
@ -104,6 +104,18 @@ extern "C" void register_context_menu_click_callback(ContextMenuClickCallback ca
|
|||
*/
|
||||
int32_t check_accessibility();
|
||||
|
||||
/*
|
||||
* Prompt to authorize the accessibility features.
|
||||
* @return
|
||||
*/
|
||||
int32_t prompt_accessibility();
|
||||
|
||||
/*
|
||||
* Open Security & Privacy settings panel
|
||||
* @return
|
||||
*/
|
||||
void open_settings_panel();
|
||||
|
||||
/*
|
||||
* Return the active NSRunningApplication path
|
||||
*/
|
||||
|
|
|
@ -251,6 +251,16 @@ int32_t show_context_menu(MenuItem * items, int32_t count) {
|
|||
// 10.9+ only, see this url for compatibility:
|
||||
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
|
||||
int32_t check_accessibility() {
|
||||
NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @NO};
|
||||
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
|
||||
}
|
||||
|
||||
int32_t prompt_accessibility() {
|
||||
NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @YES};
|
||||
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
|
||||
}
|
||||
|
||||
void open_settings_panel() {
|
||||
NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ extern {
|
|||
|
||||
// System
|
||||
pub fn check_accessibility() -> i32;
|
||||
pub fn prompt_accessibility() -> i32;
|
||||
pub fn open_settings_panel();
|
||||
pub fn get_active_app_bundle(buffer: *mut c_char, size: i32) -> i32;
|
||||
pub fn get_active_app_identifier(buffer: *mut c_char, size: i32) -> i32;
|
||||
|
||||
|
|
|
@ -49,8 +49,7 @@ pub fn check_dependencies() -> bool {
|
|||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn check_dependencies() -> bool {
|
||||
// TODO: check accessibility
|
||||
|
||||
// Nothing to do here
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ fn default_filter_exec() -> String{ "".to_owned() }
|
|||
fn default_disabled() -> bool{ false }
|
||||
fn default_log_level() -> i32 { 0 }
|
||||
fn default_ipc_server_port() -> i32 { 34982 }
|
||||
fn default_use_system_agent() -> bool { true }
|
||||
fn default_config_caching_interval() -> i32 { 800 }
|
||||
fn default_toggle_interval() -> u32 { 230 }
|
||||
fn default_backspace_limit() -> i32 { 3 }
|
||||
|
@ -75,6 +76,9 @@ pub struct Configs {
|
|||
#[serde(default = "default_ipc_server_port")]
|
||||
pub ipc_server_port: i32,
|
||||
|
||||
#[serde(default = "default_use_system_agent")]
|
||||
pub use_system_agent: bool,
|
||||
|
||||
#[serde(default = "default_config_caching_interval")]
|
||||
pub config_caching_interval: i32,
|
||||
|
||||
|
@ -126,6 +130,8 @@ impl Configs {
|
|||
validate_field!(result, self.toggle_key, KeyModifier::default());
|
||||
validate_field!(result, self.toggle_interval, default_toggle_interval());
|
||||
validate_field!(result, self.backspace_limit, default_backspace_limit());
|
||||
validate_field!(result, self.ipc_server_port, default_ipc_server_port());
|
||||
validate_field!(result, self.use_system_agent, default_use_system_agent());
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ impl MacContext {
|
|||
pub fn new(send_channel: Sender<Event>) -> Box<MacContext> {
|
||||
// Check accessibility
|
||||
unsafe {
|
||||
let res = check_accessibility();
|
||||
let res = prompt_accessibility();
|
||||
|
||||
if res == 0 {
|
||||
error!("Accessibility must be enabled to make espanso work on MacOS.");
|
||||
|
|
106
src/install.rs
Normal file
106
src/install.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// This functions are used to register/unregister espanso from the system daemon manager.
|
||||
|
||||
use crate::config::ConfigSet;
|
||||
use std::fs::create_dir_all;
|
||||
use std::process::{Command, ExitStatus};
|
||||
|
||||
// INSTALLATION
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn install(config_set: ConfigSet) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[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 install(config_set: ConfigSet) {
|
||||
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());
|
||||
|
||||
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 uninstall(config_set: ConfigSet) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn install(config_set: ConfigSet) {
|
||||
println!("Windows does not support system daemon integration.")
|
||||
}
|
61
src/main.rs
61
src/main.rs
|
@ -51,6 +51,7 @@ mod bridge;
|
|||
mod engine;
|
||||
mod config;
|
||||
mod system;
|
||||
mod install;
|
||||
mod context;
|
||||
mod matcher;
|
||||
mod keyboard;
|
||||
|
@ -93,6 +94,10 @@ fn main() {
|
|||
.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."))
|
||||
.subcommand(SubCommand::with_name("install")
|
||||
.about("MacOS and Linux only. Register espanso in the system daemon manager."))
|
||||
.subcommand(SubCommand::with_name("uninstall")
|
||||
.about("MacOS and Linux only. Unregister espanso from the system daemon manager."))
|
||||
.subcommand(SubCommand::with_name("log")
|
||||
.about("Print the latest daemon logs."))
|
||||
.subcommand(SubCommand::with_name("start")
|
||||
|
@ -150,6 +155,16 @@ fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("install") {
|
||||
install_main(config_set);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("uninstall") {
|
||||
uninstall_main(config_set);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("log") {
|
||||
log_main();
|
||||
return;
|
||||
|
@ -290,11 +305,11 @@ fn start_main(config_set: ConfigSet) {
|
|||
|
||||
precheck_guard();
|
||||
|
||||
detach_daemon(config_set);
|
||||
start_daemon(config_set);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn detach_daemon(_: ConfigSet) {
|
||||
fn start_daemon(_: ConfigSet) {
|
||||
unsafe {
|
||||
let res = bridge::windows::start_daemon_process();
|
||||
if res < 0 {
|
||||
|
@ -303,8 +318,40 @@ fn detach_daemon(_: ConfigSet) {
|
|||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn detach_daemon(config_set: ConfigSet) {
|
||||
fn fork_daemon(config_set: ConfigSet) {
|
||||
unsafe {
|
||||
let pid = libc::fork();
|
||||
if pid < 0 {
|
||||
|
@ -496,6 +543,14 @@ fn log_main() {
|
|||
}
|
||||
}
|
||||
|
||||
fn install_main(config_set: ConfigSet) {
|
||||
install::install(config_set);
|
||||
}
|
||||
|
||||
fn uninstall_main(config_set: ConfigSet) {
|
||||
install::uninstall(config_set);
|
||||
}
|
||||
|
||||
fn acquire_lock() -> Option<File> {
|
||||
let espanso_dir = context::get_data_dir();
|
||||
let lock_file_path = espanso_dir.join("espanso.lock");
|
||||
|
|
|
@ -6,9 +6,14 @@
|
|||
<string>com.federicoterzi.espanso</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Users/freddy/Documents/espanso</string>
|
||||
<string>{{{espanso_path}}}</string>
|
||||
<string>daemon</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/tmp/espanso.err</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/tmp/espanso.out</string>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
Loading…
Reference in New Issue
Block a user