diff --git a/README.md b/README.md index b0335e6..c2230ab 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,22 @@ Restart the agent after changing config: launchctl kickstart -k "gui/$(id -u)/com.github.hacksore.betterdisplay-kvm" ``` +Stop the LaunchAgent when you want to run the daemon manually: + +```bash +launchctl bootout "gui/$(id -u)/com.github.hacksore.betterdisplay-kvm" +betterdisplay-kvm --status +cargo run -- --launch +``` + +Start the LaunchAgent again when you are done: + +```bash +launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.github.hacksore.betterdisplay-kvm.plist" +launchctl enable "gui/$(id -u)/com.github.hacksore.betterdisplay-kvm" +launchctl kickstart -k "gui/$(id -u)/com.github.hacksore.betterdisplay-kvm" +``` + The generated plist lives at: ```text diff --git a/src/app.rs b/src/app.rs index 68251ed..c28a870 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use crate::utils::{ResolvedConfig, get_betterdisplay_path, load_config, setup_logger}; +use crate::utils::{ResolvedConfig, load_config, prime_betterdisplay_path_cache, setup_logger}; use log::{debug, error, info}; use std::panic; @@ -20,13 +20,11 @@ impl App { info!("betterdisplay-kvm starting..."); - let betterdisplay_path = get_betterdisplay_path(); - debug!("Found betterdisplaycli at: {:?}", betterdisplay_path); - // Set up panic hook to capture panics and log them Self::setup_panic_hook(); debug!("Starting betterdisplay-kvm with config: {:?}", config); + prime_betterdisplay_path_cache(); Ok(Self { config }) } diff --git a/src/utils.rs b/src/utils.rs index abc817c..38bd308 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,12 +11,15 @@ use std::{ io::{self, IsTerminal}, os::unix::fs::PermissionsExt, path::{Path, PathBuf}, - process::{self, Command, Output}, + process::Command, + process::{self, Output}, + sync::OnceLock, }; pub const DEFAULT_DEVICE_ID: &str = "046d:c547"; pub const LAUNCH_AGENT_LABEL: &str = "com.github.hacksore.betterdisplay-kvm"; const BIN_NAME: &str = "betterdisplay-kvm"; +static BETTERDISPLAY_PATH_CACHE: OnceLock> = OnceLock::new(); #[derive(Debug, Serialize, Deserialize)] pub struct AppConfig { @@ -55,44 +58,60 @@ pub struct ResolvedConfig { pub ddc_alt: bool, } -pub fn get_betterdisplay_path() -> PathBuf { - if let Ok(override_path) = std::env::var("BETTERDISPLAYCLI_PATH") { - let p = PathBuf::from(override_path); - if p.exists() { - return p; +fn resolve_betterdisplay_path( + override_path: Option, + candidates: &[PathBuf], +) -> anyhow::Result { + if let Some(path) = override_path { + if path.is_file() { + return Ok(path); } + return Err(anyhow::anyhow!( + "BETTERDISPLAYCLI_PATH is set but does not point to a file: {}", + path.display() + )); } - let common_candidates = [ - "/opt/homebrew/bin/betterdisplaycli", - "/usr/local/bin/betterdisplaycli", - "/usr/bin/betterdisplaycli", - "/bin/betterdisplaycli", - ]; - for candidate in common_candidates { - let p = Path::new(candidate); - if p.exists() { - return p.to_path_buf(); + for candidate in candidates { + if candidate.is_file() { + return Ok(candidate.clone()); } } - if let Some(path_var) = std::env::var_os("PATH") { - for dir in std::env::split_paths(&path_var) { - let p = dir.join("betterdisplaycli"); - if p.exists() { - return p; - } - } + Err(anyhow::anyhow!( + "Could not locate 'betterdisplaycli'. Set BETTERDISPLAYCLI_PATH or install to /opt/homebrew/bin or /usr/local/bin." + )) +} + +fn detect_betterdisplay_path() -> anyhow::Result { + let override_path = std::env::var_os("BETTERDISPLAYCLI_PATH").map(PathBuf::from); + let common_candidates = [ + Path::new("/opt/homebrew/bin/betterdisplaycli").to_path_buf(), + Path::new("/usr/local/bin/betterdisplaycli").to_path_buf(), + Path::new("/usr/bin/betterdisplaycli").to_path_buf(), + Path::new("/bin/betterdisplaycli").to_path_buf(), + ]; + + resolve_betterdisplay_path(override_path, &common_candidates) +} + +pub fn get_betterdisplay_path() -> anyhow::Result { + match BETTERDISPLAY_PATH_CACHE + .get_or_init(|| detect_betterdisplay_path().map_err(|err| err.to_string())) + { + Ok(path) => Ok(path.clone()), + Err(err) => Err(anyhow::anyhow!("{}", err)), } +} - error!( - "Could not locate 'betterdisplaycli'. Set BETTERDISPLAYCLI_PATH or install to /opt/homebrew/bin or /usr/local/bin." - ); - process::exit(1); +pub fn prime_betterdisplay_path_cache() { + if let Err(err) = get_betterdisplay_path() { + debug!("betterdisplaycli path preflight failed: {}", err); + } } pub fn set_input(input_code: u16, use_ddc_alt: bool) -> anyhow::Result<()> { - let betterdisplay_path = get_betterdisplay_path(); + let betterdisplay_path = get_betterdisplay_path()?; // TODO: figure out how to make this path dynamic or configurable let mut cmd = Command::new(betterdisplay_path);