mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-07 11:59:59 +03:00
support for cmdline args and better error handling (#4)
* support for cmdline args and better error handling * make config.toml optional * dont abuse panic for error handling * update doc * more panics removed * more unwraps removed
This commit is contained in:
committed by
GitHub
parent
deb1548e21
commit
a2d2e904f8
16
DOC.md
16
DOC.md
@@ -68,16 +68,10 @@ on the server level.
|
||||
## Device State - Active and Inactive
|
||||
To solve this problem, each device can be in exactly two states:
|
||||
|
||||
Events can only be sent to active clients.
|
||||
Events can only be received from inactive clients.
|
||||
Either events are sent or received.
|
||||
|
||||
Active denotes that a particular device is controlled by the local pc.
|
||||
This ensures that
|
||||
- a) Events can never result in a feedback loop.
|
||||
- b) As soon as a virtual input enters another client, lan-mouse will stop receiving events,
|
||||
which ensures clients can only be controlled directly and not indirectly through other clients.
|
||||
|
||||
Any event received from an active device is ignored unless it is a state change request.
|
||||
In this case the device is marked as inactive and no further events are sent to the device.
|
||||
|
||||
The received events are then processed until a further state change to active
|
||||
is requested (when the corresponding layer surface is entered).
|
||||
|
||||
**In short:** The invariance "each client either sends or receives events" must
|
||||
always be true.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
port = 42069
|
||||
backend = "wlroots"
|
||||
|
||||
# [client.right]
|
||||
# [right]
|
||||
# host_name = "localhost"
|
||||
# port = 42068
|
||||
|
||||
[client.left]
|
||||
[left]
|
||||
host_name = "Osmium"
|
||||
ip = "192.168.178.114"
|
||||
port = 42069
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::{net::SocketAddr, error::Error, fmt::Display};
|
||||
|
||||
use crate::{config, dns};
|
||||
|
||||
@@ -35,20 +35,29 @@ pub struct ClientManager {
|
||||
|
||||
pub type ClientHandle = u32;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ClientConfigError;
|
||||
|
||||
impl Display for ClientConfigError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "neither ip nor hostname specified")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ClientConfigError {}
|
||||
|
||||
impl ClientManager {
|
||||
fn add_client(&mut self, client: &config::Client, pos: Position) {
|
||||
fn add_client(&mut self, client: &config::Client, pos: Position) -> Result<(), Box<dyn Error>> {
|
||||
let ip = match client.ip {
|
||||
Some(ip) => ip,
|
||||
None => match &client.host_name {
|
||||
Some(host_name) => match dns::resolve(host_name) {
|
||||
Ok(ip) => ip,
|
||||
Err(e) => panic!("{}", e),
|
||||
},
|
||||
None => panic!("neither ip nor hostname specified"),
|
||||
Some(host_name) => dns::resolve(host_name)?,
|
||||
None => return Err(Box::new(ClientConfigError{})),
|
||||
},
|
||||
};
|
||||
let addr = SocketAddr::new(ip, client.port.unwrap_or(42069));
|
||||
self.register_client(addr, pos);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_id(&mut self) -> ClientHandle {
|
||||
@@ -56,7 +65,7 @@ impl ClientManager {
|
||||
self.next_id
|
||||
}
|
||||
|
||||
pub fn new(config: &config::Config) -> Self {
|
||||
pub fn new(config: &config::Config) -> Result<Self, Box<dyn Error>> {
|
||||
|
||||
let mut client_manager = ClientManager {
|
||||
next_id: 0,
|
||||
@@ -64,25 +73,11 @@ impl ClientManager {
|
||||
};
|
||||
|
||||
// add clients from config
|
||||
for client in vec![
|
||||
&config.client.left,
|
||||
&config.client.right,
|
||||
&config.client.top,
|
||||
&config.client.bottom,
|
||||
] {
|
||||
if let Some(client) = client {
|
||||
let pos = match client {
|
||||
client if Some(client) == config.client.left.as_ref() => Position::Left,
|
||||
client if Some(client) == config.client.right.as_ref() => Position::Right,
|
||||
client if Some(client) == config.client.top.as_ref() => Position::Top,
|
||||
client if Some(client) == config.client.bottom.as_ref() => Position::Bottom,
|
||||
_ => panic!(),
|
||||
};
|
||||
client_manager.add_client(client, pos);
|
||||
}
|
||||
for (client, pos) in config.clients.iter() {
|
||||
client_manager.add_client(&client, *pos)?;
|
||||
}
|
||||
|
||||
client_manager
|
||||
Ok(client_manager)
|
||||
}
|
||||
|
||||
pub fn register_client(&mut self, addr: SocketAddr, pos: Position) {
|
||||
|
||||
103
src/config.rs
103
src/config.rs
@@ -1,17 +1,17 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use core::fmt;
|
||||
use std::net::IpAddr;
|
||||
use std::{error::Error, fs};
|
||||
|
||||
use std::env;
|
||||
use toml;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
pub client: Clients,
|
||||
pub port: Option<u16>,
|
||||
pub backend: Option<String>,
|
||||
}
|
||||
use crate::client::Position;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Clients {
|
||||
pub struct ConfigToml {
|
||||
pub port: Option<u16>,
|
||||
pub backend: Option<String>,
|
||||
pub left: Option<Client>,
|
||||
pub right: Option<Client>,
|
||||
pub top: Option<Client>,
|
||||
@@ -25,9 +25,94 @@ pub struct Client {
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(path: &str) -> Result<Config, Box<dyn Error>> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct MissingParameter {
|
||||
arg: &'static str,
|
||||
}
|
||||
|
||||
impl fmt::Display for MissingParameter {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Missing a parameter for argument: {}", self.arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MissingParameter {}
|
||||
|
||||
impl ConfigToml {
|
||||
pub fn new(path: &str) -> Result<ConfigToml, Box<dyn Error>> {
|
||||
let config = fs::read_to_string(path)?;
|
||||
Ok(toml::from_str::<_>(&config)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_arg(key: &'static str) -> Result<Option<String>, MissingParameter> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if arg != key {
|
||||
continue;
|
||||
}
|
||||
match args.get(i+1) {
|
||||
None => return Err(MissingParameter { arg: key }),
|
||||
Some(arg) => return Ok(Some(arg.clone())),
|
||||
};
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub backend: Option<String>,
|
||||
pub port: u16,
|
||||
pub clients: Vec<(Client, Position)>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||
let config_path = "config.toml";
|
||||
let config_toml = match ConfigToml::new(config_path) {
|
||||
Err(e) => {
|
||||
eprintln!("config.toml: {e}");
|
||||
eprintln!("Continuing without config file ...");
|
||||
None
|
||||
},
|
||||
Ok(c) => Some(c),
|
||||
};
|
||||
|
||||
let backend = match find_arg("--backend")? {
|
||||
None => match &config_toml {
|
||||
Some(c) => c.backend.clone(),
|
||||
None => None,
|
||||
},
|
||||
backend => backend,
|
||||
};
|
||||
|
||||
const DEFAULT_PORT: u16 = 4242;
|
||||
|
||||
let port = match find_arg("--port")? {
|
||||
Some(port) => port.parse::<u16>()?,
|
||||
None => match &config_toml {
|
||||
Some(c) => c.port.unwrap_or(DEFAULT_PORT),
|
||||
None => DEFAULT_PORT,
|
||||
}
|
||||
};
|
||||
|
||||
let mut clients: Vec<(Client, Position)> = vec![];
|
||||
|
||||
if let Some(config_toml) = config_toml {
|
||||
if let Some(c) = config_toml.right {
|
||||
clients.push((c, Position::Right))
|
||||
}
|
||||
if let Some(c) = config_toml.left {
|
||||
clients.push((c, Position::Left))
|
||||
}
|
||||
if let Some(c) = config_toml.top {
|
||||
clients.push((c, Position::Top))
|
||||
}
|
||||
if let Some(c) = config_toml.bottom {
|
||||
clients.push((c, Position::Bottom))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Config { backend, clients, port })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{thread::{JoinHandle, self}, sync::mpsc::Receiver};
|
||||
use std::{thread::{JoinHandle, self}, sync::mpsc::Receiver, error::Error};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::env;
|
||||
@@ -14,11 +14,11 @@ enum Backend {
|
||||
Libei,
|
||||
}
|
||||
|
||||
pub fn start(consume_rx: Receiver<(Event, ClientHandle)>, clients: Vec<Client>, backend: Option<String>) -> JoinHandle<()> {
|
||||
pub fn start(consume_rx: Receiver<(Event, ClientHandle)>, clients: Vec<Client>, backend: Option<String>) -> Result<JoinHandle<()>, Box<dyn Error>> {
|
||||
#[cfg(windows)]
|
||||
let _backend = backend;
|
||||
|
||||
thread::Builder::new()
|
||||
Ok(thread::Builder::new()
|
||||
.name("event consumer".into())
|
||||
.spawn(move || {
|
||||
#[cfg(windows)]
|
||||
@@ -72,6 +72,5 @@ pub fn start(consume_rx: Receiver<(Event, ClientHandle)>, clients: Vec<Client>,
|
||||
consumer::x11::run(consume_rx, clients);
|
||||
},
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
})?)
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ pub struct Server {
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(port: u16) -> Self {
|
||||
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port);
|
||||
pub fn new(port: u16) -> Result<Self, Box<dyn Error>> {
|
||||
let listen_addr = SocketAddr::new("0.0.0.0".parse()?, port);
|
||||
let sending = Arc::new(AtomicBool::new(false));
|
||||
Server {
|
||||
Ok(Server {
|
||||
listen_addr,
|
||||
sending,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
@@ -36,7 +36,7 @@ impl Server {
|
||||
consume_tx: SyncSender<(Event, ClientHandle)>,
|
||||
) -> Result<(JoinHandle<()>, JoinHandle<()>), Box<dyn Error>> {
|
||||
let udp_socket = UdpSocket::bind(self.listen_addr)?;
|
||||
let rx = udp_socket.try_clone().unwrap();
|
||||
let rx = udp_socket.try_clone()?;
|
||||
let tx = udp_socket;
|
||||
|
||||
let sending = self.sending.clone();
|
||||
@@ -92,8 +92,7 @@ impl Server {
|
||||
.expect("event consumer unavailable");
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
})?;
|
||||
|
||||
let sending = self.sending.clone();
|
||||
|
||||
@@ -124,8 +123,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
})?;
|
||||
Ok((receiver, sender))
|
||||
}
|
||||
|
||||
|
||||
76
src/main.rs
76
src/main.rs
@@ -1,4 +1,4 @@
|
||||
use std::sync::mpsc;
|
||||
use std::{sync::mpsc, process, env};
|
||||
|
||||
use lan_mouse::{
|
||||
client::ClientManager,
|
||||
@@ -6,12 +6,24 @@ use lan_mouse::{
|
||||
config, event, request,
|
||||
};
|
||||
|
||||
fn usage() {
|
||||
eprintln!("usage: {} [--backend <backend>] [--port <port>]",
|
||||
env::args().next().unwrap_or("lan-mouse".into()));
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
// parse config file
|
||||
let config = config::Config::new("config.toml").unwrap();
|
||||
let config = match config::Config::new() {
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
usage();
|
||||
process::exit(1);
|
||||
}
|
||||
Ok(config) => config,
|
||||
};
|
||||
|
||||
// port or default
|
||||
let port = config.port.unwrap_or(42069);
|
||||
let port = config.port;
|
||||
|
||||
// event channel for producing events
|
||||
let (produce_tx, produce_rx) = mpsc::sync_channel(128);
|
||||
@@ -20,26 +32,64 @@ pub fn main() {
|
||||
let (consume_tx, consume_rx) = mpsc::sync_channel(128);
|
||||
|
||||
// create client manager
|
||||
let mut client_manager = ClientManager::new(&config);
|
||||
let mut client_manager = match ClientManager::new(&config) {
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
}
|
||||
Ok(m) => m,
|
||||
};
|
||||
|
||||
// start receiving client connection requests
|
||||
let (request_server, request_thread) = request::Server::listen(port).unwrap();
|
||||
|
||||
// start producing and consuming events
|
||||
let event_producer = producer::start(produce_tx, client_manager.get_clients(), request_server);
|
||||
let event_consumer = consumer::start(consume_rx, client_manager.get_clients(), config.backend);
|
||||
let event_producer = match producer::start(produce_tx, client_manager.get_clients(), request_server) {
|
||||
Err(e) => {
|
||||
eprintln!("Could not start event producer: {e}");
|
||||
None
|
||||
},
|
||||
Ok(p) => Some(p),
|
||||
};
|
||||
let event_consumer = match consumer::start(consume_rx, client_manager.get_clients(), config.backend) {
|
||||
Err(e) => {
|
||||
eprintln!("Could not start event consumer: {e}");
|
||||
None
|
||||
},
|
||||
Ok(p) => Some(p),
|
||||
};
|
||||
|
||||
if event_consumer.is_none() && event_producer.is_none() {
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
// start sending and receiving events
|
||||
let event_server = event::server::Server::new(port);
|
||||
let (receiver, sender) = event_server
|
||||
.run(&mut client_manager, produce_rx, consume_tx)
|
||||
.unwrap();
|
||||
let event_server = match event::server::Server::new(port) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let (receiver, sender) = match event_server.run(&mut client_manager, produce_rx, consume_tx) {
|
||||
Ok((r,s)) => (r,s),
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
request_thread.join().unwrap();
|
||||
|
||||
// stop receiving events and terminate event-consumer
|
||||
receiver.join().unwrap();
|
||||
sender.join().unwrap();
|
||||
if let Some(thread) = event_consumer {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
event_producer.join().unwrap();
|
||||
event_consumer.join().unwrap();
|
||||
// stop producing events and terminate event-sender
|
||||
if let Some(thread) = event_producer {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
sender.join().unwrap();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#[cfg(unix)]
|
||||
use std::env;
|
||||
use std::{thread::{JoinHandle, self}, sync::mpsc::SyncSender};
|
||||
use std::{thread::{JoinHandle, self}, sync::mpsc::SyncSender, error::Error};
|
||||
|
||||
use crate::{client::{Client, ClientHandle}, event::Event, request::Server};
|
||||
|
||||
@@ -16,8 +16,8 @@ pub fn start(
|
||||
produce_tx: SyncSender<(Event, ClientHandle)>,
|
||||
clients: Vec<Client>,
|
||||
request_server: Server,
|
||||
) -> JoinHandle<()> {
|
||||
thread::Builder::new()
|
||||
) -> Result<JoinHandle<()>, Box<dyn Error>> {
|
||||
Ok(thread::Builder::new()
|
||||
.name("event producer".into())
|
||||
.spawn(move || {
|
||||
#[cfg(windows)]
|
||||
@@ -48,6 +48,5 @@ pub fn start(
|
||||
producer::wayland::run(produce_tx, request_server, clients);
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
})?)
|
||||
}
|
||||
|
||||
@@ -60,11 +60,13 @@ impl Server {
|
||||
|
||||
pub fn listen(port: u16) -> Result<(Server, JoinHandle<()>), Box<dyn Error>> {
|
||||
let data: Arc<RwLock<HashMap<Request, MmapMut>>> = Arc::new(RwLock::new(HashMap::new()));
|
||||
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port);
|
||||
let listen_addr = SocketAddr::new("0.0.0.0".parse()?, port);
|
||||
let server = Server { data };
|
||||
let server_copy = server.clone();
|
||||
let thread = thread::spawn(move || {
|
||||
let listen_socket = TcpListener::bind(listen_addr).unwrap();
|
||||
let listen_socket = TcpListener::bind(listen_addr)?;
|
||||
let thread = thread::Builder::new()
|
||||
.name("tcp server".into())
|
||||
.spawn(move || {
|
||||
for stream in listen_socket.incoming() {
|
||||
match stream {
|
||||
Ok(stream) => {
|
||||
@@ -75,7 +77,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})?;
|
||||
Ok((server_copy, thread))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user