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 pub hostname: Option<String>,
135 pub fix_ips: Vec<IpAddr>,
137 pub port: u16,
139 pub pos: Position,
141 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 pub active: bool,
163 pub active_addr: Option<SocketAddr>,
167 pub alive: bool,
169 pub dns_ips: Vec<IpAddr>,
171 pub ips: HashSet<IpAddr>,
175 pub has_pressed_keys: bool,
177 pub resolving: bool,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub enum FrontendEvent {
183 Created(ClientHandle, ClientConfig, ClientState),
185 NoSuchClient(ClientHandle),
187 State(ClientHandle, ClientConfig, ClientState),
189 Deleted(ClientHandle),
191 PortChanged(u16, Option<String>),
193 Enumerate(Vec<(ClientHandle, ClientConfig, ClientState)>),
195 Error(String),
197 CaptureStatus(Status),
199 EmulationStatus(Status),
201 AuthorizedUpdated(HashMap<String, String>),
203 PublicKeyFingerprint(String),
205 DeviceConnected {
207 addr: SocketAddr,
208 fingerprint: String,
209 },
210 DeviceEntered {
212 fingerprint: String,
213 addr: SocketAddr,
214 pos: Position,
215 },
216 IncomingDisconnected(SocketAddr),
218 ConnectionAttempt { fingerprint: String },
220}
221
222#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
223pub enum FrontendRequest {
224 Activate(ClientHandle, bool),
226 Create,
228 ChangePort(u16),
230 Delete(ClientHandle),
232 Enumerate(),
234 ResolveDns(ClientHandle),
236 UpdateHostname(ClientHandle, Option<String>),
238 UpdatePort(ClientHandle, u16),
240 UpdatePosition(ClientHandle, Position),
242 UpdateFixIps(ClientHandle, Vec<IpAddr>),
244 EnableCapture,
246 EnableEmulation,
248 Sync,
250 AuthorizeKey(String, String),
252 RemoveAuthorizedKey(String),
254 UpdateEnterHook(u64, Option<String>),
256 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}