use clap::{Args, Parser, Subcommand}; use futures::StreamExt; use std::{net::IpAddr, time::Duration}; use thiserror::Error; use lan_mouse_ipc::{ ClientHandle, ConnectionError, FrontendEvent, FrontendRequest, IpcError, Position, connect_async, }; #[derive(Debug, Error)] pub enum CliError { /// is the service running? #[error("could not connect: `{0}` - is the service running?")] ServiceNotRunning(#[from] ConnectionError), #[error("error communicating with service: {0}")] Ipc(#[from] IpcError), } #[derive(Parser, Clone, Debug, PartialEq, Eq)] #[command(name = "lan-mouse-cli", about = "LanMouse CLI interface")] pub struct CliArgs { #[command(subcommand)] command: CliSubcommand, } #[derive(Args, Clone, Debug, PartialEq, Eq)] struct Client { #[arg(long)] hostname: Option, #[arg(long)] port: Option, #[arg(long)] ips: Option>, #[arg(long)] enter_hook: Option, } #[derive(Clone, Subcommand, Debug, PartialEq, Eq)] enum CliSubcommand { /// add a new client AddClient(Client), /// remove an existing client RemoveClient { id: ClientHandle }, /// activate a client Activate { id: ClientHandle }, /// deactivate a client Deactivate { id: ClientHandle }, /// list configured clients List, /// change hostname SetHost { id: ClientHandle, host: Option, }, /// change port SetPort { id: ClientHandle, port: u16 }, /// set position SetPosition { id: ClientHandle, pos: Position }, /// set ips SetIps { id: ClientHandle, ips: Vec }, /// re-enable capture EnableCapture, /// re-enable emulation EnableEmulation, /// authorize a public key AuthorizeKey { description: String, sha256_fingerprint: String, }, /// deauthorize a public key RemoveAuthorizedKey { sha256_fingerprint: String }, /// save configuration to file SaveConfig, } pub async fn run(args: CliArgs) -> Result<(), CliError> { execute(args.command).await?; Ok(()) } async fn execute(cmd: CliSubcommand) -> Result<(), CliError> { let (mut rx, mut tx) = connect_async(Some(Duration::from_millis(500))).await?; match cmd { CliSubcommand::AddClient(Client { hostname, port, ips, enter_hook, }) => { tx.request(FrontendRequest::Create).await?; while let Some(e) = rx.next().await { if let FrontendEvent::Created(handle, _, _) = e? { if let Some(hostname) = hostname { tx.request(FrontendRequest::UpdateHostname(handle, Some(hostname))) .await?; } if let Some(port) = port { tx.request(FrontendRequest::UpdatePort(handle, port)) .await?; } if let Some(ips) = ips { tx.request(FrontendRequest::UpdateFixIps(handle, ips)) .await?; } if let Some(enter_hook) = enter_hook { tx.request(FrontendRequest::UpdateEnterHook(handle, Some(enter_hook))) .await?; } break; } } } CliSubcommand::RemoveClient { id } => tx.request(FrontendRequest::Delete(id)).await?, CliSubcommand::Activate { id } => tx.request(FrontendRequest::Activate(id, true)).await?, CliSubcommand::Deactivate { id } => { tx.request(FrontendRequest::Activate(id, false)).await? } CliSubcommand::List => { tx.request(FrontendRequest::Enumerate()).await?; while let Some(e) = rx.next().await { if let FrontendEvent::Enumerate(clients) = e? { for (handle, config, state) in clients { let host = config.hostname.unwrap_or("unknown".to_owned()); let port = config.port; let pos = config.pos; let active = state.active; let ips = state.ips; println!( "id {handle}: {host}:{port} ({pos}) active: {active}, ips: {ips:?}" ); } break; } } } CliSubcommand::SetHost { id, host } => { tx.request(FrontendRequest::UpdateHostname(id, host)) .await? } CliSubcommand::SetPort { id, port } => { tx.request(FrontendRequest::UpdatePort(id, port)).await? } CliSubcommand::SetPosition { id, pos } => { tx.request(FrontendRequest::UpdatePosition(id, pos)).await? } CliSubcommand::SetIps { id, ips } => { tx.request(FrontendRequest::UpdateFixIps(id, ips)).await? } CliSubcommand::EnableCapture => tx.request(FrontendRequest::EnableCapture).await?, CliSubcommand::EnableEmulation => tx.request(FrontendRequest::EnableEmulation).await?, CliSubcommand::AuthorizeKey { description, sha256_fingerprint, } => { tx.request(FrontendRequest::AuthorizeKey( description, sha256_fingerprint, )) .await? } CliSubcommand::RemoveAuthorizedKey { sha256_fingerprint } => { tx.request(FrontendRequest::RemoveAuthorizedKey(sha256_fingerprint)) .await? } CliSubcommand::SaveConfig => tx.request(FrontendRequest::SaveConfiguration).await?, } Ok(()) }