Skip to main content

lan_mouse_ipc/
lib.rs

1use std::{
2    collections::{HashMap, HashSet},
3    env::VarError,
4    fmt::Display,
5    io,
6    net::{IpAddr, SocketAddr},
7    str::FromStr,
8};
9use thiserror::Error;
10
11#[cfg(unix)]
12use std::{
13    env,
14    path::{Path, PathBuf},
15};
16
17use serde::{Deserialize, Serialize};
18
19mod connect;
20mod connect_async;
21mod listen;
22
23pub use connect::{FrontendEventReader, FrontendRequestWriter, connect};
24pub use connect_async::{AsyncFrontendEventReader, AsyncFrontendRequestWriter, connect_async};
25pub use listen::AsyncFrontendListener;
26
27#[derive(Debug, Error)]
28pub enum ConnectionError {
29    #[error(transparent)]
30    SocketPath(#[from] SocketPathError),
31    #[error(transparent)]
32    Io(#[from] io::Error),
33    #[error("connection timed out")]
34    Timeout,
35}
36
37#[derive(Debug, Error)]
38pub enum IpcListenerCreationError {
39    #[error("could not determine socket-path: `{0}`")]
40    SocketPath(#[from] SocketPathError),
41    #[error("service already running!")]
42    AlreadyRunning,
43    #[error("failed to bind lan-mouse socket: `{0}`")]
44    Bind(io::Error),
45}
46
47#[derive(Debug, Error)]
48pub enum IpcError {
49    #[error("io error occured: `{0}`")]
50    Io(#[from] io::Error),
51    #[error("invalid json: `{0}`")]
52    Json(#[from] serde_json::Error),
53    #[error(transparent)]
54    Connection(#[from] ConnectionError),
55    #[error(transparent)]
56    Listen(#[from] IpcListenerCreationError),
57}
58
59pub const DEFAULT_PORT: u16 = 4242;
60
61#[derive(Debug, Default, Eq, Hash, PartialEq, Clone, Copy, Serialize, Deserialize)]
62#[serde(rename_all = "lowercase")]
63pub enum Position {
64    #[default]
65    Left,
66    Right,
67    Top,
68    Bottom,
69}
70
71impl Position {
72    pub fn opposite(&self) -> Self {
73        match self {
74            Position::Left => Position::Right,
75            Position::Right => Position::Left,
76            Position::Top => Position::Bottom,
77            Position::Bottom => Position::Top,
78        }
79    }
80}
81
82#[derive(Debug, Error)]
83#[error("not a valid position: {pos}")]
84pub struct PositionParseError {
85    pos: String,
86}
87
88impl FromStr for Position {
89    type Err = PositionParseError;
90
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        match s {
93            "left" => Ok(Self::Left),
94            "right" => Ok(Self::Right),
95            "top" => Ok(Self::Top),
96            "bottom" => Ok(Self::Bottom),
97            _ => Err(PositionParseError { pos: s.into() }),
98        }
99    }
100}
101
102impl Display for Position {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        write!(
105            f,
106            "{}",
107            match self {
108                Position::Left => "left",
109                Position::Right => "right",
110                Position::Top => "top",
111                Position::Bottom => "bottom",
112            }
113        )
114    }
115}
116
117impl TryFrom<&str> for Position {
118    type Error = ();
119
120    fn try_from(s: &str) -> Result<Self, Self::Error> {
121        match s {
122            "left" => Ok(Position::Left),
123            "right" => Ok(Position::Right),
124            "top" => Ok(Position::Top),
125            "bottom" => Ok(Position::Bottom),
126            _ => Err(()),
127        }
128    }
129}
130
131#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
132pub struct ClientConfig {
133    /// hostname of this client
134    pub hostname: Option<String>,
135    /// fix ips, determined by the user
136    pub fix_ips: Vec<IpAddr>,
137    /// both active_addr and addrs can be None / empty so port needs to be stored seperately
138    pub port: u16,
139    /// position of a client on screen
140    pub pos: Position,
141    /// enter hook
142    pub cmd: Option<String>,
143}
144
145impl Default for ClientConfig {
146    fn default() -> Self {
147        Self {
148            port: DEFAULT_PORT,
149            hostname: Default::default(),
150            fix_ips: Default::default(),
151            pos: Default::default(),
152            cmd: None,
153        }
154    }
155}
156
157pub type ClientHandle = u64;
158
159#[derive(Debug, Default, Clone, Serialize, Deserialize)]
160pub struct ClientState {
161    /// events should be sent to and received from the client
162    pub active: bool,
163    /// `active` address of the client, used to send data to.
164    /// This should generally be the socket address where data
165    /// was last received from.
166    pub active_addr: Option<SocketAddr>,
167    /// tracks whether or not the client is available for emulation
168    pub alive: bool,
169    /// ips from dns
170    pub dns_ips: Vec<IpAddr>,
171    /// all ip addresses associated with a particular client
172    /// e.g. Laptops usually have at least an ethernet and a wifi port
173    /// which have different ip addresses
174    pub ips: HashSet<IpAddr>,
175    /// client has pressed keys
176    pub has_pressed_keys: bool,
177    /// dns resolving in progress
178    pub resolving: bool,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub enum FrontendEvent {
183    /// a client was created
184    Created(ClientHandle, ClientConfig, ClientState),
185    /// no such client
186    NoSuchClient(ClientHandle),
187    /// state changed
188    State(ClientHandle, ClientConfig, ClientState),
189    /// the client was deleted
190    Deleted(ClientHandle),
191    /// new port, reason of failure (if failed)
192    PortChanged(u16, Option<String>),
193    /// list of all clients, used for initial state synchronization
194    Enumerate(Vec<(ClientHandle, ClientConfig, ClientState)>),
195    /// an error occured
196    Error(String),
197    /// capture status
198    CaptureStatus(Status),
199    /// emulation status
200    EmulationStatus(Status),
201    /// authorized public key fingerprints have been updated
202    AuthorizedUpdated(HashMap<String, String>),
203    /// public key fingerprint of this device
204    PublicKeyFingerprint(String),
205    /// new device connected
206    DeviceConnected {
207        addr: SocketAddr,
208        fingerprint: String,
209    },
210    /// incoming device entered the screen
211    DeviceEntered {
212        fingerprint: String,
213        addr: SocketAddr,
214        pos: Position,
215    },
216    /// incoming disconnected
217    IncomingDisconnected(SocketAddr),
218    /// failed connection attempt (approval for fingerprint required)
219    ConnectionAttempt { fingerprint: String },
220}
221
222#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
223pub enum FrontendRequest {
224    /// activate/deactivate client
225    Activate(ClientHandle, bool),
226    /// add a new client
227    Create,
228    /// change the listen port (recreate udp listener)
229    ChangePort(u16),
230    /// remove a client
231    Delete(ClientHandle),
232    /// request an enumeration of all clients
233    Enumerate(),
234    /// resolve dns
235    ResolveDns(ClientHandle),
236    /// update hostname
237    UpdateHostname(ClientHandle, Option<String>),
238    /// update port
239    UpdatePort(ClientHandle, u16),
240    /// update position
241    UpdatePosition(ClientHandle, Position),
242    /// update fix-ips
243    UpdateFixIps(ClientHandle, Vec<IpAddr>),
244    /// request reenabling input capture
245    EnableCapture,
246    /// request reenabling input emulation
247    EnableEmulation,
248    /// synchronize all state
249    Sync,
250    /// authorize fingerprint (description, fingerprint)
251    AuthorizeKey(String, String),
252    /// remove fingerprint (fingerprint)
253    RemoveAuthorizedKey(String),
254    /// change the hook command
255    UpdateEnterHook(u64, Option<String>),
256    /// save config file
257    SaveConfiguration,
258}
259
260#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Serialize, Deserialize)]
261pub enum Status {
262    #[default]
263    Disabled,
264    Enabled,
265}
266
267impl From<Status> for bool {
268    fn from(status: Status) -> Self {
269        match status {
270            Status::Enabled => true,
271            Status::Disabled => false,
272        }
273    }
274}
275
276#[cfg(unix)]
277const LAN_MOUSE_SOCKET_NAME: &str = "lan-mouse-socket.sock";
278
279#[derive(Debug, Error)]
280pub enum SocketPathError {
281    #[error("could not determine $XDG_RUNTIME_DIR: `{0}`")]
282    XdgRuntimeDirNotFound(VarError),
283    #[error("could not determine $HOME: `{0}`")]
284    HomeDirNotFound(VarError),
285}
286
287#[cfg(all(unix, not(target_os = "macos")))]
288pub fn default_socket_path() -> Result<PathBuf, SocketPathError> {
289    let xdg_runtime_dir =
290        env::var("XDG_RUNTIME_DIR").map_err(SocketPathError::XdgRuntimeDirNotFound)?;
291    Ok(Path::new(xdg_runtime_dir.as_str()).join(LAN_MOUSE_SOCKET_NAME))
292}
293
294#[cfg(all(unix, target_os = "macos"))]
295pub fn default_socket_path() -> Result<PathBuf, SocketPathError> {
296    let home = env::var("HOME").map_err(SocketPathError::HomeDirNotFound)?;
297    Ok(Path::new(home.as_str())
298        .join("Library")
299        .join("Caches")
300        .join(LAN_MOUSE_SOCKET_NAME))
301}