lan_mouse_ipc/
lib.rs

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