use crate::capture_test::TestCaptureArgs; use crate::emulation_test::TestEmulationArgs; use clap::{Parser, Subcommand, ValueEnum}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::env::{self, VarError}; use std::fmt::Display; use std::fs; use std::net::IpAddr; use std::path::{Path, PathBuf}; use std::{collections::HashSet, io}; use thiserror::Error; use toml; use lan_mouse_cli::CliArgs; use lan_mouse_ipc::{DEFAULT_PORT, Position}; use input_event::scancode::{ self, Linux::{KeyLeftAlt, KeyLeftCtrl, KeyLeftMeta, KeyLeftShift}, }; use shadow_rs::shadow; shadow!(build); const CONFIG_FILE_NAME: &str = "config.toml"; const CERT_FILE_NAME: &str = "lan-mouse.pem"; fn default_path() -> Result { #[cfg(unix)] let default_path = { let xdg_config_home = env::var("XDG_CONFIG_HOME").unwrap_or(format!("{}/.config", env::var("HOME")?)); format!("{xdg_config_home}/lan-mouse/") }; #[cfg(not(unix))] let default_path = { let app_data = env::var("LOCALAPPDATA").unwrap_or(format!("{}/.config", env::var("USERPROFILE")?)); format!("{app_data}\\lan-mouse\\") }; Ok(PathBuf::from(default_path)) } #[derive(Serialize, Deserialize, Debug, Default)] struct ConfigToml { capture_backend: Option, emulation_backend: Option, port: Option, release_bind: Option>, cert_path: Option, clients: Option>, authorized_fingerprints: Option>, } #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] struct TomlClient { hostname: Option, host_name: Option, ips: Option>, port: Option, position: Option, activate_on_startup: Option, enter_hook: Option, } impl ConfigToml { fn new(path: &Path) -> Result { let config = fs::read_to_string(path)?; Ok(toml::from_str::<_>(&config)?) } } #[derive(Parser, Debug)] #[command(author, version=build::CLAP_LONG_VERSION, about, long_about = None)] struct Args { /// the listen port for lan-mouse #[arg(short, long)] port: Option, /// non-default config file location #[arg(short, long)] config: Option, /// capture backend override #[arg(long)] capture_backend: Option, /// emulation backend override #[arg(long)] emulation_backend: Option, /// path to non-default certificate location #[arg(long)] cert_path: Option, /// subcommands #[command(subcommand)] command: Option, } #[derive(Subcommand, Clone, Debug, Eq, PartialEq)] pub enum Command { /// test input emulation TestEmulation(TestEmulationArgs), /// test input capture TestCapture(TestCaptureArgs), /// Lan Mouse commandline interface Cli(CliArgs), /// run in daemon mode Daemon, } #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)] pub enum CaptureBackend { #[cfg(all(unix, feature = "libei_capture", not(target_os = "macos")))] #[serde(rename = "input-capture-portal")] InputCapturePortal, #[cfg(all(unix, feature = "layer_shell_capture", not(target_os = "macos")))] #[serde(rename = "layer-shell")] LayerShell, #[cfg(all(unix, feature = "x11_capture", not(target_os = "macos")))] #[serde(rename = "x11")] X11, #[cfg(windows)] #[serde(rename = "windows")] Windows, #[cfg(target_os = "macos")] #[serde(rename = "macos")] MacOs, #[serde(rename = "dummy")] Dummy, } impl Display for CaptureBackend { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { #[cfg(all(unix, feature = "libei_capture", not(target_os = "macos")))] CaptureBackend::InputCapturePortal => write!(f, "input-capture-portal"), #[cfg(all(unix, feature = "layer_shell_capture", not(target_os = "macos")))] CaptureBackend::LayerShell => write!(f, "layer-shell"), #[cfg(all(unix, feature = "x11_capture", not(target_os = "macos")))] CaptureBackend::X11 => write!(f, "X11"), #[cfg(windows)] CaptureBackend::Windows => write!(f, "windows"), #[cfg(target_os = "macos")] CaptureBackend::MacOs => write!(f, "MacOS"), CaptureBackend::Dummy => write!(f, "dummy"), } } } impl From for input_capture::Backend { fn from(backend: CaptureBackend) -> Self { match backend { #[cfg(all(unix, feature = "libei_capture", not(target_os = "macos")))] CaptureBackend::InputCapturePortal => Self::InputCapturePortal, #[cfg(all(unix, feature = "layer_shell_capture", not(target_os = "macos")))] CaptureBackend::LayerShell => Self::LayerShell, #[cfg(all(unix, feature = "x11_capture", not(target_os = "macos")))] CaptureBackend::X11 => Self::X11, #[cfg(windows)] CaptureBackend::Windows => Self::Windows, #[cfg(target_os = "macos")] CaptureBackend::MacOs => Self::MacOs, CaptureBackend::Dummy => Self::Dummy, } } } #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)] pub enum EmulationBackend { #[cfg(all(unix, feature = "wlroots_emulation", not(target_os = "macos")))] #[serde(rename = "wlroots")] Wlroots, #[cfg(all(unix, feature = "libei_emulation", not(target_os = "macos")))] #[serde(rename = "libei")] Libei, #[cfg(all(unix, feature = "rdp_emulation", not(target_os = "macos")))] #[serde(rename = "xdp")] Xdp, #[cfg(all(unix, feature = "x11_emulation", not(target_os = "macos")))] #[serde(rename = "x11")] X11, #[cfg(windows)] #[serde(rename = "windows")] Windows, #[cfg(target_os = "macos")] #[serde(rename = "macos")] MacOs, #[serde(rename = "dummy")] Dummy, } impl From for input_emulation::Backend { fn from(backend: EmulationBackend) -> Self { match backend { #[cfg(all(unix, feature = "wlroots_emulation", not(target_os = "macos")))] EmulationBackend::Wlroots => Self::Wlroots, #[cfg(all(unix, feature = "libei_emulation", not(target_os = "macos")))] EmulationBackend::Libei => Self::Libei, #[cfg(all(unix, feature = "rdp_emulation", not(target_os = "macos")))] EmulationBackend::Xdp => Self::Xdp, #[cfg(all(unix, feature = "x11_emulation", not(target_os = "macos")))] EmulationBackend::X11 => Self::X11, #[cfg(windows)] EmulationBackend::Windows => Self::Windows, #[cfg(target_os = "macos")] EmulationBackend::MacOs => Self::MacOs, EmulationBackend::Dummy => Self::Dummy, } } } impl Display for EmulationBackend { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { #[cfg(all(unix, feature = "wlroots_emulation", not(target_os = "macos")))] EmulationBackend::Wlroots => write!(f, "wlroots"), #[cfg(all(unix, feature = "libei_emulation", not(target_os = "macos")))] EmulationBackend::Libei => write!(f, "libei"), #[cfg(all(unix, feature = "rdp_emulation", not(target_os = "macos")))] EmulationBackend::Xdp => write!(f, "xdg-desktop-portal"), #[cfg(all(unix, feature = "x11_emulation", not(target_os = "macos")))] EmulationBackend::X11 => write!(f, "X11"), #[cfg(windows)] EmulationBackend::Windows => write!(f, "windows"), #[cfg(target_os = "macos")] EmulationBackend::MacOs => write!(f, "macos"), EmulationBackend::Dummy => write!(f, "dummy"), } } } #[derive(Debug)] pub struct Config { /// command line arguments args: Args, /// path to the certificate file used cert_path: PathBuf, /// path to the config file used config_path: PathBuf, /// the (optional) toml config and it's path config_toml: Option, } pub struct ConfigClient { pub ips: HashSet, pub hostname: Option, pub port: u16, pub pos: Position, pub active: bool, pub enter_hook: Option, } impl From for ConfigClient { fn from(toml: TomlClient) -> Self { let active = toml.activate_on_startup.unwrap_or(false); let enter_hook = toml.enter_hook; let hostname = toml.hostname; let ips = HashSet::from_iter(toml.ips.into_iter().flatten()); let port = toml.port.unwrap_or(DEFAULT_PORT); let pos = toml.position.unwrap_or_default(); Self { ips, hostname, port, pos, active, enter_hook, } } } impl From for TomlClient { fn from(client: ConfigClient) -> Self { let hostname = client.hostname; let host_name = None; let mut ips = client.ips.into_iter().collect::>(); ips.sort(); let ips = Some(ips); let port = if client.port == DEFAULT_PORT { None } else { Some(client.port) }; let position = Some(client.pos); let activate_on_startup = if client.active { Some(true) } else { None }; let enter_hook = client.enter_hook; Self { hostname, host_name, ips, port, position, activate_on_startup, enter_hook, } } } #[derive(Debug, Error)] pub enum ConfigError { #[error(transparent)] Toml(#[from] toml::de::Error), #[error(transparent)] Io(#[from] io::Error), #[error(transparent)] Var(#[from] VarError), } const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] = [KeyLeftCtrl, KeyLeftShift, KeyLeftMeta, KeyLeftAlt]; impl Config { pub fn new() -> Result { let args = Args::parse(); // --config overrules default location let config_path = args .config .clone() .unwrap_or(default_path()?.join(CONFIG_FILE_NAME)); let config_toml = match ConfigToml::new(&config_path) { Err(e) => { log::warn!("{config_path:?}: {e}"); log::warn!("Continuing without config file ..."); None } Ok(c) => Some(c), }; // --cert-path overrules default location let cert_path = args .cert_path .clone() .or(config_toml.as_ref().and_then(|c| c.cert_path.clone())) .unwrap_or(default_path()?.join(CERT_FILE_NAME)); Ok(Config { args, cert_path, config_path, config_toml, }) } /// the command to run pub fn command(&self) -> Option { self.args.command.clone() } pub fn config_path(&self) -> &Path { &self.config_path } /// public key fingerprints authorized for connection pub fn authorized_fingerprints(&self) -> HashMap { self.config_toml .as_ref() .and_then(|c| c.authorized_fingerprints.clone()) .unwrap_or_default() } /// path to certificate pub fn cert_path(&self) -> &Path { &self.cert_path } /// optional input-capture backend override pub fn capture_backend(&self) -> Option { self.args .capture_backend .or(self.config_toml.as_ref().and_then(|c| c.capture_backend)) } /// optional input-emulation backend override pub fn emulation_backend(&self) -> Option { self.args .emulation_backend .or(self.config_toml.as_ref().and_then(|c| c.emulation_backend)) } /// the port to use (initially) pub fn port(&self) -> u16 { self.args .port .or(self.config_toml.as_ref().and_then(|c| c.port)) .unwrap_or(DEFAULT_PORT) } /// list of configured clients pub fn clients(&self) -> Vec { self.config_toml .as_ref() .map(|c| c.clients.clone()) .unwrap_or_default() .into_iter() .flatten() .map(From::::from) .collect() } /// release bind for returning control to the host pub fn release_bind(&self) -> Vec { self.config_toml .as_ref() .and_then(|c| c.release_bind.clone()) .unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned())) } /// set configured clients pub fn set_clients(&mut self, clients: Vec) { if self.config_toml.is_none() { self.config_toml = Default::default(); } self.config_toml.as_mut().expect("config").clients = clients.into_iter().map(|c| c.into()).collect::>(); } /// set authorized keys pub fn set_authorized_keys(&mut self, fingerprints: HashMap) { if self.config_toml.is_none() { self.config_toml = Default::default(); } self.config_toml .as_mut() .expect("config") .authorized_fingerprints = Some(fingerprints); } }