use anyhow::Result; use clap::Parser; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::env; use std::net::IpAddr; use std::{error::Error, fs}; use toml; use crate::client::Position; use crate::scancode; use crate::scancode::Linux::{KeyLeftAlt, KeyLeftCtrl, KeyLeftMeta, KeyLeftShift}; pub const DEFAULT_PORT: u16 = 4242; #[derive(Serialize, Deserialize, Debug)] pub struct ConfigToml { pub port: Option, pub frontend: Option, pub release_bind: Option>, pub left: Option, pub right: Option, pub top: Option, pub bottom: Option, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct TomlClient { pub hostname: Option, pub host_name: Option, pub ips: Option>, pub port: Option, pub activate_on_startup: Option, pub enter_hook: Option, } impl ConfigToml { pub fn new(path: &str) -> Result> { let config = fs::read_to_string(path)?; log::info!("using config: \"{path}\""); Ok(toml::from_str::<_>(&config)?) } } #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct CliArgs { /// the listen port for lan-mouse #[arg(short, long)] port: Option, /// the frontend to use [cli | gtk] #[arg(short, long)] frontend: Option, /// non-default config file location #[arg(short, long)] config: Option, /// run only the service as a daemon without the frontend #[arg(short, long)] daemon: bool, /// test input capture #[arg(long)] test_capture: bool, /// test input emulation #[arg(long)] test_emulation: bool, } #[derive(Debug, PartialEq, Eq)] pub enum Frontend { Gtk, Cli, } #[derive(Debug)] pub struct Config { pub frontend: Frontend, pub port: u16, pub clients: Vec<(TomlClient, Position)>, pub daemon: bool, pub release_bind: Vec, pub test_capture: bool, pub test_emulation: bool, } pub struct ConfigClient { pub ips: HashSet, pub hostname: Option, pub port: u16, pub pos: Position, pub active: bool, pub enter_hook: Option, } const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] = [KeyLeftCtrl, KeyLeftShift, KeyLeftMeta, KeyLeftAlt]; impl Config { pub fn new() -> Result { let args = CliArgs::parse(); let config_file = "config.toml"; #[cfg(unix)] let config_path = { let xdg_config_home = env::var("XDG_CONFIG_HOME").unwrap_or(format!("{}/.config", env::var("HOME")?)); format!("{xdg_config_home}/lan-mouse/{config_file}") }; #[cfg(not(unix))] let config_path = { let app_data = env::var("LOCALAPPDATA").unwrap_or(format!("{}/.config", env::var("USERPROFILE")?)); format!("{app_data}\\lan-mouse\\{config_file}") }; // --config overrules default location let config_path = args.config.unwrap_or(config_path); let config_toml = match ConfigToml::new(config_path.as_str()) { Err(e) => { log::warn!("{config_path}: {e}"); log::warn!("Continuing without config file ..."); None } Ok(c) => Some(c), }; let frontend = match args.frontend { None => match &config_toml { Some(c) => c.frontend.clone(), None => None, }, frontend => frontend, }; let frontend = match frontend { #[cfg(feature = "gtk")] None => Frontend::Gtk, #[cfg(not(feature = "gtk"))] None => Frontend::Cli, Some(s) => match s.as_str() { "cli" => Frontend::Cli, "gtk" => Frontend::Gtk, _ => Frontend::Cli, }, }; let port = match args.port { Some(port) => port, None => match &config_toml { Some(c) => c.port.unwrap_or(DEFAULT_PORT), None => DEFAULT_PORT, }, }; log::debug!("{config_toml:?}"); let release_bind = config_toml .as_ref() .and_then(|c| c.release_bind.clone()) .unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned())); let mut clients: Vec<(TomlClient, Position)> = vec![]; if let Some(config_toml) = config_toml { if let Some(c) = config_toml.right { clients.push((c, Position::Right)) } if let Some(c) = config_toml.left { clients.push((c, Position::Left)) } if let Some(c) = config_toml.top { clients.push((c, Position::Top)) } if let Some(c) = config_toml.bottom { clients.push((c, Position::Bottom)) } } let daemon = args.daemon; let test_capture = args.test_capture; let test_emulation = args.test_emulation; Ok(Config { daemon, frontend, clients, port, release_bind, test_capture, test_emulation, }) } pub fn get_clients(&self) -> Vec { self.clients .iter() .map(|(c, pos)| { let port = c.port.unwrap_or(DEFAULT_PORT); let ips: HashSet = if let Some(ips) = c.ips.as_ref() { HashSet::from_iter(ips.iter().cloned()) } else { HashSet::new() }; let hostname = match &c.hostname { Some(h) => Some(h.clone()), None => c.host_name.clone(), }; let active = c.activate_on_startup.unwrap_or(false); let enter_hook = c.enter_hook.clone(); ConfigClient { ips, hostname, port, pos: *pos, active, enter_hook, } }) .collect() } }