use std::{ collections::{HashMap, HashSet}, env::VarError, fmt::Display, io, net::{IpAddr, SocketAddr}, str::FromStr, }; use thiserror::Error; #[cfg(unix)] use std::{ env, path::{Path, PathBuf}, }; use serde::{Deserialize, Serialize}; mod connect; mod connect_async; mod listen; pub use connect::{FrontendEventReader, FrontendRequestWriter, connect}; pub use connect_async::{AsyncFrontendEventReader, AsyncFrontendRequestWriter, connect_async}; pub use listen::AsyncFrontendListener; #[derive(Debug, Error)] pub enum ConnectionError { #[error(transparent)] SocketPath(#[from] SocketPathError), #[error(transparent)] Io(#[from] io::Error), #[error("connection timed out")] Timeout, } #[derive(Debug, Error)] pub enum IpcListenerCreationError { #[error("could not determine socket-path: `{0}`")] SocketPath(#[from] SocketPathError), #[error("service already running!")] AlreadyRunning, #[error("failed to bind lan-mouse socket: `{0}`")] Bind(io::Error), } #[derive(Debug, Error)] pub enum IpcError { #[error("io error occured: `{0}`")] Io(#[from] io::Error), #[error("invalid json: `{0}`")] Json(#[from] serde_json::Error), #[error(transparent)] Connection(#[from] ConnectionError), #[error(transparent)] Listen(#[from] IpcListenerCreationError), } pub const DEFAULT_PORT: u16 = 4242; #[derive(Debug, Default, Eq, Hash, PartialEq, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Position { #[default] Left, Right, Top, Bottom, } impl Position { pub fn opposite(&self) -> Self { match self { Position::Left => Position::Right, Position::Right => Position::Left, Position::Top => Position::Bottom, Position::Bottom => Position::Top, } } } #[derive(Debug, Error)] #[error("not a valid position: {pos}")] pub struct PositionParseError { pos: String, } impl FromStr for Position { type Err = PositionParseError; fn from_str(s: &str) -> Result { match s { "left" => Ok(Self::Left), "right" => Ok(Self::Right), "top" => Ok(Self::Top), "bottom" => Ok(Self::Bottom), _ => Err(PositionParseError { pos: s.into() }), } } } impl Display for Position { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Position::Left => "left", Position::Right => "right", Position::Top => "top", Position::Bottom => "bottom", } ) } } impl TryFrom<&str> for Position { type Error = (); fn try_from(s: &str) -> Result { match s { "left" => Ok(Position::Left), "right" => Ok(Position::Right), "top" => Ok(Position::Top), "bottom" => Ok(Position::Bottom), _ => Err(()), } } } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] pub struct ClientConfig { /// hostname of this client pub hostname: Option, /// fix ips, determined by the user pub fix_ips: Vec, /// both active_addr and addrs can be None / empty so port needs to be stored seperately pub port: u16, /// position of a client on screen pub pos: Position, /// enter hook pub cmd: Option, } impl Default for ClientConfig { fn default() -> Self { Self { port: DEFAULT_PORT, hostname: Default::default(), fix_ips: Default::default(), pos: Default::default(), cmd: None, } } } pub type ClientHandle = u64; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct ClientState { /// events should be sent to and received from the client pub active: bool, /// `active` address of the client, used to send data to. /// This should generally be the socket address where data /// was last received from. pub active_addr: Option, /// tracks whether or not the client is available for emulation pub alive: bool, /// ips from dns pub dns_ips: Vec, /// all ip addresses associated with a particular client /// e.g. Laptops usually have at least an ethernet and a wifi port /// which have different ip addresses pub ips: HashSet, /// client has pressed keys pub has_pressed_keys: bool, /// dns resolving in progress pub resolving: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum FrontendEvent { /// a client was created Created(ClientHandle, ClientConfig, ClientState), /// no such client NoSuchClient(ClientHandle), /// state changed State(ClientHandle, ClientConfig, ClientState), /// the client was deleted Deleted(ClientHandle), /// new port, reason of failure (if failed) PortChanged(u16, Option), /// list of all clients, used for initial state synchronization Enumerate(Vec<(ClientHandle, ClientConfig, ClientState)>), /// an error occured Error(String), /// capture status CaptureStatus(Status), /// emulation status EmulationStatus(Status), /// authorized public key fingerprints have been updated AuthorizedUpdated(HashMap), /// public key fingerprint of this device PublicKeyFingerprint(String), /// new device connected DeviceConnected { addr: SocketAddr, fingerprint: String, }, /// incoming device entered the screen DeviceEntered { fingerprint: String, addr: SocketAddr, pos: Position, }, /// incoming disconnected IncomingDisconnected(SocketAddr), /// failed connection attempt (approval for fingerprint required) ConnectionAttempt { fingerprint: String }, } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] pub enum FrontendRequest { /// activate/deactivate client Activate(ClientHandle, bool), /// add a new client Create, /// change the listen port (recreate udp listener) ChangePort(u16), /// remove a client Delete(ClientHandle), /// request an enumeration of all clients Enumerate(), /// resolve dns ResolveDns(ClientHandle), /// update hostname UpdateHostname(ClientHandle, Option), /// update port UpdatePort(ClientHandle, u16), /// update position UpdatePosition(ClientHandle, Position), /// update fix-ips UpdateFixIps(ClientHandle, Vec), /// request reenabling input capture EnableCapture, /// request reenabling input emulation EnableEmulation, /// synchronize all state Sync, /// authorize fingerprint (description, fingerprint) AuthorizeKey(String, String), /// remove fingerprint (fingerprint) RemoveAuthorizedKey(String), /// change the hook command UpdateEnterHook(u64, Option), /// save config file SaveConfiguration, } #[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] pub enum Status { #[default] Disabled, Enabled, } impl From for bool { fn from(status: Status) -> Self { match status { Status::Enabled => true, Status::Disabled => false, } } } #[cfg(unix)] const LAN_MOUSE_SOCKET_NAME: &str = "lan-mouse-socket.sock"; #[derive(Debug, Error)] pub enum SocketPathError { #[error("could not determine $XDG_RUNTIME_DIR: `{0}`")] XdgRuntimeDirNotFound(VarError), #[error("could not determine $HOME: `{0}`")] HomeDirNotFound(VarError), } #[cfg(all(unix, not(target_os = "macos")))] pub fn default_socket_path() -> Result { let xdg_runtime_dir = env::var("XDG_RUNTIME_DIR").map_err(SocketPathError::XdgRuntimeDirNotFound)?; Ok(Path::new(xdg_runtime_dir.as_str()).join(LAN_MOUSE_SOCKET_NAME)) } #[cfg(all(unix, target_os = "macos"))] pub fn default_socket_path() -> Result { let home = env::var("HOME").map_err(SocketPathError::HomeDirNotFound)?; Ok(Path::new(home.as_str()) .join("Library") .join("Caches") .join(LAN_MOUSE_SOCKET_NAME)) }