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();
|
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
|
* 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:
|
// 10.9+ only, see this url for compatibility:
|
||||||
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
|
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
|
||||||
int32_t check_accessibility() {
|
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};
|
NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @YES};
|
||||||
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
|
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
|
// System
|
||||||
pub fn check_accessibility() -> i32;
|
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_bundle(buffer: *mut c_char, size: i32) -> i32;
|
||||||
pub fn get_active_app_identifier(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")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn check_dependencies() -> bool {
|
pub fn check_dependencies() -> bool {
|
||||||
// TODO: check accessibility
|
// Nothing to do here
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ fn default_filter_exec() -> String{ "".to_owned() }
|
||||||
fn default_disabled() -> bool{ false }
|
fn default_disabled() -> bool{ false }
|
||||||
fn default_log_level() -> i32 { 0 }
|
fn default_log_level() -> i32 { 0 }
|
||||||
fn default_ipc_server_port() -> i32 { 34982 }
|
fn default_ipc_server_port() -> i32 { 34982 }
|
||||||
|
fn default_use_system_agent() -> bool { true }
|
||||||
fn default_config_caching_interval() -> i32 { 800 }
|
fn default_config_caching_interval() -> i32 { 800 }
|
||||||
fn default_toggle_interval() -> u32 { 230 }
|
fn default_toggle_interval() -> u32 { 230 }
|
||||||
fn default_backspace_limit() -> i32 { 3 }
|
fn default_backspace_limit() -> i32 { 3 }
|
||||||
|
@ -75,6 +76,9 @@ pub struct Configs {
|
||||||
#[serde(default = "default_ipc_server_port")]
|
#[serde(default = "default_ipc_server_port")]
|
||||||
pub ipc_server_port: i32,
|
pub ipc_server_port: i32,
|
||||||
|
|
||||||
|
#[serde(default = "default_use_system_agent")]
|
||||||
|
pub use_system_agent: bool,
|
||||||
|
|
||||||
#[serde(default = "default_config_caching_interval")]
|
#[serde(default = "default_config_caching_interval")]
|
||||||
pub config_caching_interval: i32,
|
pub config_caching_interval: i32,
|
||||||
|
|
||||||
|
@ -126,6 +130,8 @@ impl Configs {
|
||||||
validate_field!(result, self.toggle_key, KeyModifier::default());
|
validate_field!(result, self.toggle_key, KeyModifier::default());
|
||||||
validate_field!(result, self.toggle_interval, default_toggle_interval());
|
validate_field!(result, self.toggle_interval, default_toggle_interval());
|
||||||
validate_field!(result, self.backspace_limit, default_backspace_limit());
|
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
|
result
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl MacContext {
|
||||||
pub fn new(send_channel: Sender<Event>) -> Box<MacContext> {
|
pub fn new(send_channel: Sender<Event>) -> Box<MacContext> {
|
||||||
// Check accessibility
|
// Check accessibility
|
||||||
unsafe {
|
unsafe {
|
||||||
let res = check_accessibility();
|
let res = prompt_accessibility();
|
||||||
|
|
||||||
if res == 0 {
|
if res == 0 {
|
||||||
error!("Accessibility must be enabled to make espanso work on MacOS.");
|
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 engine;
|
||||||
mod config;
|
mod config;
|
||||||
mod system;
|
mod system;
|
||||||
|
mod install;
|
||||||
mod context;
|
mod context;
|
||||||
mod matcher;
|
mod matcher;
|
||||||
mod keyboard;
|
mod keyboard;
|
||||||
|
@ -93,6 +94,10 @@ fn main() {
|
||||||
.about("Tool to detect current window properties, to simplify filters creation."))
|
.about("Tool to detect current window properties, to simplify filters creation."))
|
||||||
.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("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")
|
.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")
|
||||||
|
@ -150,6 +155,16 @@ fn main() {
|
||||||
return;
|
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") {
|
if let Some(_) = matches.subcommand_matches("log") {
|
||||||
log_main();
|
log_main();
|
||||||
return;
|
return;
|
||||||
|
@ -290,11 +305,11 @@ fn start_main(config_set: ConfigSet) {
|
||||||
|
|
||||||
precheck_guard();
|
precheck_guard();
|
||||||
|
|
||||||
detach_daemon(config_set);
|
start_daemon(config_set);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn detach_daemon(_: ConfigSet) {
|
fn start_daemon(_: ConfigSet) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let res = bridge::windows::start_daemon_process();
|
let res = bridge::windows::start_daemon_process();
|
||||||
if res < 0 {
|
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"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn detach_daemon(config_set: ConfigSet) {
|
fn fork_daemon(config_set: ConfigSet) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let pid = libc::fork();
|
let pid = libc::fork();
|
||||||
if pid < 0 {
|
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> {
|
fn acquire_lock() -> Option<File> {
|
||||||
let espanso_dir = context::get_data_dir();
|
let espanso_dir = context::get_data_dir();
|
||||||
let lock_file_path = espanso_dir.join("espanso.lock");
|
let lock_file_path = espanso_dir.join("espanso.lock");
|
||||||
|
|
|
@ -6,9 +6,14 @@
|
||||||
<string>com.federicoterzi.espanso</string>
|
<string>com.federicoterzi.espanso</string>
|
||||||
<key>ProgramArguments</key>
|
<key>ProgramArguments</key>
|
||||||
<array>
|
<array>
|
||||||
<string>/Users/freddy/Documents/espanso</string>
|
<string>{{{espanso_path}}}</string>
|
||||||
|
<string>daemon</string>
|
||||||
</array>
|
</array>
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/tmp/espanso.err</string>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>/tmp/espanso.out</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
Loading…
Reference in New Issue
Block a user