mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-10 06:40:56 +03:00
287 lines
8.7 KiB
Rust
287 lines
8.7 KiB
Rust
use anyhow::{anyhow, Result};
|
|
use std::{cmp::min, io::ErrorKind, net::IpAddr, str, time::Duration};
|
|
|
|
#[cfg(unix)]
|
|
use std::{
|
|
env,
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use tokio::io::ReadHalf;
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt, WriteHalf};
|
|
|
|
#[cfg(unix)]
|
|
use tokio::net::UnixListener;
|
|
#[cfg(unix)]
|
|
use tokio::net::UnixStream;
|
|
|
|
#[cfg(windows)]
|
|
use tokio::net::TcpListener;
|
|
#[cfg(windows)]
|
|
use tokio::net::TcpStream;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
client::{ClientConfig, ClientHandle, ClientState, Position},
|
|
config::{Config, Frontend},
|
|
};
|
|
|
|
/// cli frontend
|
|
pub mod cli;
|
|
|
|
/// gtk frontend
|
|
#[cfg(feature = "gtk")]
|
|
pub mod gtk;
|
|
|
|
pub fn run_frontend(config: &Config) -> Result<()> {
|
|
match config.frontend {
|
|
#[cfg(feature = "gtk")]
|
|
Frontend::Gtk => {
|
|
gtk::run();
|
|
}
|
|
#[cfg(not(feature = "gtk"))]
|
|
Frontend::Gtk => panic!("gtk frontend requested but feature not enabled!"),
|
|
Frontend::Cli => {
|
|
cli::run()?;
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn exponential_back_off(duration: &mut Duration) -> &Duration {
|
|
let new = duration.saturating_mul(2);
|
|
*duration = min(new, Duration::from_secs(1));
|
|
duration
|
|
}
|
|
|
|
/// wait for the lan-mouse socket to come online
|
|
#[cfg(unix)]
|
|
pub fn wait_for_service() -> Result<std::os::unix::net::UnixStream> {
|
|
let socket_path = FrontendListener::socket_path()?;
|
|
let mut duration = Duration::from_millis(1);
|
|
loop {
|
|
use std::os::unix::net::UnixStream;
|
|
if let Ok(stream) = UnixStream::connect(&socket_path) {
|
|
break Ok(stream);
|
|
}
|
|
// a signaling mechanism or inotify could be used to
|
|
// improve this
|
|
std::thread::sleep(*exponential_back_off(&mut duration));
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub fn wait_for_service() -> Result<std::net::TcpStream> {
|
|
let mut duration = Duration::from_millis(1);
|
|
loop {
|
|
use std::net::TcpStream;
|
|
if let Ok(stream) = TcpStream::connect("127.0.0.1:5252") {
|
|
break Ok(stream);
|
|
}
|
|
std::thread::sleep(*exponential_back_off(&mut duration));
|
|
}
|
|
}
|
|
|
|
#[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),
|
|
/// service shutdown
|
|
Terminate(),
|
|
/// update hostname
|
|
UpdateHostname(ClientHandle, Option<String>),
|
|
/// update port
|
|
UpdatePort(ClientHandle, u16),
|
|
/// update position
|
|
UpdatePosition(ClientHandle, Position),
|
|
/// update fix-ips
|
|
UpdateFixIps(ClientHandle, Vec<IpAddr>),
|
|
/// request the state of the given client
|
|
GetState(ClientHandle),
|
|
}
|
|
|
|
#[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<String>),
|
|
/// list of all clients, used for initial state synchronization
|
|
Enumerate(Vec<(ClientHandle, ClientConfig, ClientState)>),
|
|
/// an error occured
|
|
Error(String),
|
|
}
|
|
|
|
pub struct FrontendListener {
|
|
#[cfg(windows)]
|
|
listener: TcpListener,
|
|
#[cfg(unix)]
|
|
listener: UnixListener,
|
|
#[cfg(unix)]
|
|
socket_path: PathBuf,
|
|
#[cfg(unix)]
|
|
tx_streams: Vec<WriteHalf<UnixStream>>,
|
|
#[cfg(windows)]
|
|
tx_streams: Vec<WriteHalf<TcpStream>>,
|
|
}
|
|
|
|
impl FrontendListener {
|
|
#[cfg(all(unix, not(target_os = "macos")))]
|
|
pub fn socket_path() -> Result<PathBuf> {
|
|
let xdg_runtime_dir = match env::var("XDG_RUNTIME_DIR") {
|
|
Ok(d) => d,
|
|
Err(e) => return Err(anyhow!("could not find XDG_RUNTIME_DIR: {e}")),
|
|
};
|
|
let xdg_runtime_dir = Path::new(xdg_runtime_dir.as_str());
|
|
Ok(xdg_runtime_dir.join("lan-mouse-socket.sock"))
|
|
}
|
|
|
|
#[cfg(all(unix, target_os = "macos"))]
|
|
pub fn socket_path() -> Result<PathBuf> {
|
|
let home = match env::var("HOME") {
|
|
Ok(d) => d,
|
|
Err(e) => return Err(anyhow!("could not find HOME: {e}")),
|
|
};
|
|
let home = Path::new(home.as_str());
|
|
let path = home
|
|
.join("Library")
|
|
.join("Caches")
|
|
.join("lan-mouse-socket.sock");
|
|
Ok(path)
|
|
}
|
|
|
|
pub async fn new() -> Option<Result<Self>> {
|
|
#[cfg(unix)]
|
|
let (socket_path, listener) = {
|
|
let socket_path = match Self::socket_path() {
|
|
Ok(path) => path,
|
|
Err(e) => return Some(Err(e)),
|
|
};
|
|
|
|
log::debug!("remove socket: {:?}", socket_path);
|
|
if socket_path.exists() {
|
|
// try to connect to see if some other instance
|
|
// of lan-mouse is already running
|
|
match UnixStream::connect(&socket_path).await {
|
|
// connected -> lan-mouse is already running
|
|
Ok(_) => return None,
|
|
// lan-mouse is not running but a socket was left behind
|
|
Err(e) => {
|
|
log::debug!("{socket_path:?}: {e} - removing left behind socket");
|
|
let _ = std::fs::remove_file(&socket_path);
|
|
}
|
|
}
|
|
}
|
|
let listener = match UnixListener::bind(&socket_path) {
|
|
Ok(ls) => ls,
|
|
// some other lan-mouse instance has bound the socket in the meantime
|
|
Err(e) if e.kind() == ErrorKind::AddrInUse => return None,
|
|
Err(e) => return Some(Err(anyhow!("failed to bind lan-mouse-socket: {e}"))),
|
|
};
|
|
(socket_path, listener)
|
|
};
|
|
|
|
#[cfg(windows)]
|
|
let listener = match TcpListener::bind("127.0.0.1:5252").await {
|
|
Ok(ls) => ls,
|
|
// some other lan-mouse instance has bound the socket in the meantime
|
|
Err(e) if e.kind() == ErrorKind::AddrInUse => return None,
|
|
Err(e) => return Some(Err(anyhow!("failed to bind lan-mouse-socket: {e}"))),
|
|
};
|
|
|
|
let adapter = Self {
|
|
listener,
|
|
#[cfg(unix)]
|
|
socket_path,
|
|
tx_streams: vec![],
|
|
};
|
|
|
|
Some(Ok(adapter))
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
pub async fn accept(&mut self) -> Result<ReadHalf<UnixStream>> {
|
|
let stream = self.listener.accept().await?.0;
|
|
let (rx, tx) = tokio::io::split(stream);
|
|
self.tx_streams.push(tx);
|
|
Ok(rx)
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub async fn accept(&mut self) -> Result<ReadHalf<TcpStream>> {
|
|
let stream = self.listener.accept().await?.0;
|
|
let (rx, tx) = tokio::io::split(stream);
|
|
self.tx_streams.push(tx);
|
|
Ok(rx)
|
|
}
|
|
|
|
pub(crate) async fn broadcast_event(&mut self, notify: FrontendEvent) -> Result<()> {
|
|
// encode event
|
|
let json = serde_json::to_string(¬ify).unwrap();
|
|
let payload = json.as_bytes();
|
|
let len = payload.len().to_be_bytes();
|
|
log::debug!("broadcasting event to streams: {json}");
|
|
let mut keep = vec![];
|
|
// TODO do simultaneously
|
|
for tx in self.tx_streams.iter_mut() {
|
|
// write len + payload
|
|
if tx.write(&len).await.is_err() {
|
|
keep.push(false);
|
|
continue;
|
|
}
|
|
if tx.write(payload).await.is_err() {
|
|
keep.push(false);
|
|
continue;
|
|
}
|
|
keep.push(true);
|
|
}
|
|
|
|
// could not find a better solution because async
|
|
let mut keep = keep.into_iter();
|
|
self.tx_streams.retain(|_| keep.next().unwrap());
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
impl Drop for FrontendListener {
|
|
fn drop(&mut self) {
|
|
log::debug!("remove socket: {:?}", self.socket_path);
|
|
let _ = std::fs::remove_file(&self.socket_path);
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
pub async fn wait_for_request(stream: &mut ReadHalf<UnixStream>) -> Result<FrontendRequest> {
|
|
let len = stream.read_u64().await?;
|
|
assert!(len <= 256);
|
|
let mut buf = [0u8; 256];
|
|
stream.read_exact(&mut buf[..len as usize]).await?;
|
|
Ok(serde_json::from_slice(&buf[..len as usize])?)
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub async fn wait_for_request(stream: &mut ReadHalf<TcpStream>) -> Result<FrontendRequest> {
|
|
let len = stream.read_u64().await?;
|
|
let mut buf = [0u8; 256];
|
|
stream.read_exact(&mut buf[..len as usize]).await?;
|
|
Ok(serde_json::from_slice(&buf[..len as usize])?)
|
|
}
|