feat(core): implement env-path cli module on macOS
This commit is contained in:
parent
8fb3826298
commit
8f99c6ce16
70
espanso/src/cli/env_path.rs
Normal file
70
espanso/src/cli/env_path.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 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/>.
|
||||
*/
|
||||
|
||||
use crate::exit_code::{ADD_TO_PATH_FAILURE, ADD_TO_PATH_SUCCESS};
|
||||
|
||||
use super::{CliModule, CliModuleArgs};
|
||||
use log::error;
|
||||
|
||||
pub fn new() -> CliModule {
|
||||
CliModule {
|
||||
enable_logs: true,
|
||||
disable_logs_terminal_output: true,
|
||||
requires_paths: true,
|
||||
log_mode: super::LogMode::AppendOnly,
|
||||
subcommand: "env-path".to_string(),
|
||||
entry: env_path_main,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn env_path_main(args: CliModuleArgs) -> i32 {
|
||||
let paths = args.paths.expect("missing paths argument");
|
||||
let cli_args = args.cli_args.expect("missing cli_args");
|
||||
|
||||
let elevated_priviledge_prompt = cli_args.is_present("prompt");
|
||||
|
||||
if cli_args.subcommand_matches("register").is_some() {
|
||||
if let Err(error) = crate::path::add_espanso_to_path(elevated_priviledge_prompt) {
|
||||
error_print_and_log(&format!(
|
||||
"Unable to add 'espanso' command to PATH: {}",
|
||||
error
|
||||
));
|
||||
return ADD_TO_PATH_FAILURE;
|
||||
}
|
||||
} else if cli_args.subcommand_matches("unregister").is_some() {
|
||||
if let Err(error) = crate::path::remove_espanso_from_path(elevated_priviledge_prompt) {
|
||||
error_print_and_log(&format!(
|
||||
"Unable to remove 'espanso' command from PATH: {}",
|
||||
error
|
||||
));
|
||||
return ADD_TO_PATH_FAILURE;
|
||||
}
|
||||
} else {
|
||||
eprintln!("Please specify a subcommand, either `espanso env-path register` to add the 'espanso' command or `espanso env-path unregister` to remove it");
|
||||
return ADD_TO_PATH_FAILURE;
|
||||
}
|
||||
|
||||
ADD_TO_PATH_SUCCESS
|
||||
}
|
||||
|
||||
fn error_print_and_log(msg: &str) {
|
||||
error!("{}", msg);
|
||||
eprintln!("{}", msg);
|
||||
}
|
|
@ -130,6 +130,8 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
|
|||
preferences.set_completed_wizard(true);
|
||||
}
|
||||
|
||||
// TODO: initialize config directory if not present
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
|
|
|
@ -17,16 +17,9 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{path::PathBuf, sync::Mutex};
|
||||
use std::{path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
exit_code::{
|
||||
MIGRATE_ALREADY_NEW_FORMAT, MIGRATE_CLEAN_FAILURE, MIGRATE_DIRTY_FAILURE,
|
||||
MIGRATE_LEGACY_INSTANCE_RUNNING, MIGRATE_SUCCESS, MIGRATE_UNEXPECTED_FAILURE,
|
||||
MIGRATE_USER_ABORTED,
|
||||
},
|
||||
lock::acquire_legacy_lock,
|
||||
};
|
||||
use crate::{exit_code::{MIGRATE_ALREADY_NEW_FORMAT, MIGRATE_CLEAN_FAILURE, MIGRATE_DIRTY_FAILURE, MIGRATE_LEGACY_INSTANCE_RUNNING, MIGRATE_SUCCESS, MIGRATE_USER_ABORTED, configure_custom_panic_hook, update_panic_exit_code}, lock::acquire_legacy_lock};
|
||||
|
||||
use super::{CliModule, CliModuleArgs};
|
||||
use colored::*;
|
||||
|
@ -35,10 +28,6 @@ use fs_extra::dir::CopyOptions;
|
|||
use log::{error, info};
|
||||
use tempdir::TempDir;
|
||||
|
||||
lazy_static! {
|
||||
static ref CURRENT_PANIC_EXIT_CODE: Mutex<i32> = Mutex::new(MIGRATE_UNEXPECTED_FAILURE);
|
||||
}
|
||||
|
||||
pub fn new() -> CliModule {
|
||||
CliModule {
|
||||
enable_logs: true,
|
||||
|
@ -188,48 +177,6 @@ fn find_available_backup_dir() -> PathBuf {
|
|||
panic!("could not generate valid backup directory");
|
||||
}
|
||||
|
||||
fn configure_custom_panic_hook() {
|
||||
let previous_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
(*previous_hook)(info);
|
||||
|
||||
// Part of this code is taken from the "rust-log-panics" crate
|
||||
let thread = std::thread::current();
|
||||
let thread = thread.name().unwrap_or("<unnamed>");
|
||||
|
||||
let msg = match info.payload().downcast_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match info.payload().downcast_ref::<String>() {
|
||||
Some(s) => &**s,
|
||||
None => "Box<Any>",
|
||||
},
|
||||
};
|
||||
|
||||
match info.location() {
|
||||
Some(location) => {
|
||||
eprintln!(
|
||||
"ERROR: '{}' panicked at '{}': {}:{}",
|
||||
thread,
|
||||
msg,
|
||||
location.file(),
|
||||
location.line(),
|
||||
);
|
||||
}
|
||||
None => eprintln!("ERROR: '{}' panicked at '{}'", thread, msg,),
|
||||
}
|
||||
|
||||
let exit_code = CURRENT_PANIC_EXIT_CODE.lock().unwrap();
|
||||
std::process::exit(*exit_code);
|
||||
}));
|
||||
}
|
||||
|
||||
fn update_panic_exit_code(exit_code: i32) {
|
||||
let mut lock = CURRENT_PANIC_EXIT_CODE
|
||||
.lock()
|
||||
.expect("unable to update panic exit code");
|
||||
*lock = exit_code;
|
||||
}
|
||||
|
||||
fn error_print_and_log(msg: &str) {
|
||||
error!("{}", msg);
|
||||
eprintln!("{}", msg);
|
||||
|
|
|
@ -22,6 +22,7 @@ use espanso_config::{config::ConfigStore, matches::store::MatchStore};
|
|||
use espanso_path::Paths;
|
||||
|
||||
pub mod daemon;
|
||||
pub mod env_path;
|
||||
pub mod launcher;
|
||||
pub mod log;
|
||||
pub mod migrate;
|
||||
|
|
|
@ -35,4 +35,56 @@ pub const MIGRATE_LEGACY_INSTANCE_RUNNING: i32 = 2;
|
|||
pub const MIGRATE_USER_ABORTED: i32 = 3;
|
||||
pub const MIGRATE_CLEAN_FAILURE: i32 = 50;
|
||||
pub const MIGRATE_DIRTY_FAILURE: i32 = 51;
|
||||
pub const MIGRATE_UNEXPECTED_FAILURE: i32 = 101;
|
||||
pub const MIGRATE_UNEXPECTED_FAILURE: i32 = 101;
|
||||
|
||||
pub const ADD_TO_PATH_SUCCESS: i32 = 0;
|
||||
pub const ADD_TO_PATH_FAILURE: i32 = 1;
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref CURRENT_PANIC_EXIT_CODE: Mutex<i32> = Mutex::new(MIGRATE_UNEXPECTED_FAILURE);
|
||||
}
|
||||
|
||||
pub fn configure_custom_panic_hook() {
|
||||
let previous_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
(*previous_hook)(info);
|
||||
|
||||
// Part of this code is taken from the "rust-log-panics" crate
|
||||
let thread = std::thread::current();
|
||||
let thread = thread.name().unwrap_or("<unnamed>");
|
||||
|
||||
let msg = match info.payload().downcast_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match info.payload().downcast_ref::<String>() {
|
||||
Some(s) => &**s,
|
||||
None => "Box<Any>",
|
||||
},
|
||||
};
|
||||
|
||||
match info.location() {
|
||||
Some(location) => {
|
||||
eprintln!(
|
||||
"ERROR: '{}' panicked at '{}': {}:{}",
|
||||
thread,
|
||||
msg,
|
||||
location.file(),
|
||||
location.line(),
|
||||
);
|
||||
}
|
||||
None => eprintln!("ERROR: '{}' panicked at '{}'", thread, msg,),
|
||||
}
|
||||
|
||||
let exit_code = CURRENT_PANIC_EXIT_CODE.lock().unwrap();
|
||||
std::process::exit(*exit_code);
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn update_panic_exit_code(exit_code: i32) {
|
||||
let mut lock = CURRENT_PANIC_EXIT_CODE
|
||||
.lock()
|
||||
.expect("unable to update panic exit code");
|
||||
*lock = exit_code;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ mod icon;
|
|||
mod ipc;
|
||||
mod lock;
|
||||
mod logging;
|
||||
mod path;
|
||||
mod preferences;
|
||||
mod util;
|
||||
|
||||
|
@ -58,6 +59,7 @@ lazy_static! {
|
|||
cli::daemon::new(),
|
||||
cli::modulo::new(),
|
||||
cli::migrate::new(),
|
||||
cli::env_path::new(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -121,6 +123,25 @@ fn main() {
|
|||
.takes_value(true)
|
||||
.help("Specify a custom path for the espanso runtime directory"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("env-path")
|
||||
.arg(
|
||||
Arg::with_name("prompt")
|
||||
.long("prompt")
|
||||
.required(false)
|
||||
.takes_value(false)
|
||||
.help("Prompt for permissions if the operation requires elevated privileges."),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("register")
|
||||
.about("Add 'espanso' command to PATH"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("unregister")
|
||||
.about("Remove 'espanso' command from PATH"),
|
||||
)
|
||||
.about("Add or remove the 'espanso' command from the PATH (macOS and Windows only)"),
|
||||
)
|
||||
// .subcommand(SubCommand::with_name("cmd")
|
||||
// .about("Send a command to the espanso daemon.")
|
||||
// .subcommand(SubCommand::with_name("exit")
|
||||
|
|
128
espanso/src/path/macos.rs
Normal file
128
espanso/src/path/macos.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 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/>.
|
||||
*/
|
||||
|
||||
use std::io::ErrorKind;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{error, warn};
|
||||
|
||||
pub fn is_espanso_in_path() -> bool {
|
||||
PathBuf::from("/usr/local/bin/espanso").is_file()
|
||||
}
|
||||
|
||||
pub fn add_espanso_to_path(prompt_when_necessary: bool) -> Result<()> {
|
||||
let target_link_dir = PathBuf::from("/usr/local/bin");
|
||||
let exec_path = std::env::current_exe()?;
|
||||
|
||||
if !target_link_dir.is_dir() {
|
||||
return Err(PathError::UsrLocalBinDirDoesNotExist.into());
|
||||
}
|
||||
|
||||
let target_link_path = target_link_dir.join("espanso");
|
||||
|
||||
if let Err(error) = std::os::unix::fs::symlink(&exec_path, &target_link_path) {
|
||||
match error.kind() {
|
||||
ErrorKind::PermissionDenied => {
|
||||
if prompt_when_necessary {
|
||||
warn!("target link file can't be accessed with current permissions, requesting elevated ones through AppleScript.");
|
||||
|
||||
let params = format!(
|
||||
r##"do shell script "mkdir -p /usr/local/bin && ln -sf '{}' '{}'" with administrator privileges"##,
|
||||
exec_path.to_string_lossy(),
|
||||
target_link_path.to_string_lossy(),
|
||||
);
|
||||
|
||||
let mut child = std::process::Command::new("osascript").args(&[
|
||||
"-e",
|
||||
¶ms,
|
||||
]).spawn()?;
|
||||
|
||||
let result = child.wait()?;
|
||||
if !result.success() {
|
||||
return Err(PathError::ElevationRequestFailure.into());
|
||||
}
|
||||
} else {
|
||||
return Err(PathError::SymlinkError(error).into());
|
||||
}
|
||||
}
|
||||
other_error => {
|
||||
return Err(PathError::SymlinkError(error).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_espanso_from_path(prompt_when_necessary: bool) -> Result<()> {
|
||||
let target_link_path = PathBuf::from("/usr/local/bin/espanso");
|
||||
|
||||
if !target_link_path.is_file() {
|
||||
return Err(PathError::SymlinkNotFound.into());
|
||||
}
|
||||
|
||||
if let Err(error) = std::fs::remove_file(&target_link_path) {
|
||||
match error.kind() {
|
||||
ErrorKind::PermissionDenied => {
|
||||
if prompt_when_necessary {
|
||||
warn!("target link file can't be accessed with current permissions, requesting elevated ones through AppleScript.");
|
||||
|
||||
let params = format!(
|
||||
r##"do shell script "rm '{}'" with administrator privileges"##,
|
||||
target_link_path.to_string_lossy(),
|
||||
);
|
||||
|
||||
let mut child = std::process::Command::new("osascript").args(&[
|
||||
"-e",
|
||||
¶ms,
|
||||
]).spawn()?;
|
||||
|
||||
let result = child.wait()?;
|
||||
if !result.success() {
|
||||
return Err(PathError::ElevationRequestFailure.into());
|
||||
}
|
||||
} else {
|
||||
return Err(PathError::SymlinkError(error).into());
|
||||
}
|
||||
}
|
||||
other_error => {
|
||||
return Err(PathError::SymlinkError(error).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PathError {
|
||||
#[error("/usr/local/bin directory doesn't exist")]
|
||||
UsrLocalBinDirDoesNotExist,
|
||||
|
||||
#[error("symlink error: `{0}`")]
|
||||
SymlinkError(std::io::Error),
|
||||
|
||||
#[error("elevation request failed")]
|
||||
ElevationRequestFailure,
|
||||
|
||||
#[error("symlink does not exist, so there is nothing to remove.")]
|
||||
SymlinkNotFound,
|
||||
}
|
30
espanso/src/path/mod.rs
Normal file
30
espanso/src/path/mod.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 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/>.
|
||||
*/
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::add_espanso_to_path;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::remove_espanso_from_path;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::is_espanso_in_path;
|
||||
|
||||
// TODO: add Linux stub
|
Loading…
Reference in New Issue
Block a user