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:
Ferdinand Schober
2023-02-18 04:03:10 +01:00
committed by GitHub
parent deb1548e21
commit a2d2e904f8
9 changed files with 205 additions and 83 deletions

16
DOC.md
View File

@@ -68,16 +68,10 @@ on the server level.
## Device State - Active and Inactive ## Device State - Active and Inactive
To solve this problem, each device can be in exactly two states: To solve this problem, each device can be in exactly two states:
Events can only be sent to active clients. Either events are sent or received.
Events can only be received from inactive clients.
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.

View File

@@ -1,11 +1,11 @@
port = 42069 port = 42069
backend = "wlroots" backend = "wlroots"
# [client.right] # [right]
# host_name = "localhost" # host_name = "localhost"
# port = 42068 # port = 42068
[client.left] [left]
host_name = "Osmium" host_name = "Osmium"
ip = "192.168.178.114" ip = "192.168.178.114"
port = 42069 port = 42069

View File

@@ -1,4 +1,4 @@
use std::net::SocketAddr; use std::{net::SocketAddr, error::Error, fmt::Display};
use crate::{config, dns}; use crate::{config, dns};
@@ -35,20 +35,29 @@ pub struct ClientManager {
pub type ClientHandle = u32; 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 { 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 { let ip = match client.ip {
Some(ip) => ip, Some(ip) => ip,
None => match &client.host_name { None => match &client.host_name {
Some(host_name) => match dns::resolve(host_name) { Some(host_name) => dns::resolve(host_name)?,
Ok(ip) => ip, None => return Err(Box::new(ClientConfigError{})),
Err(e) => panic!("{}", e),
},
None => panic!("neither ip nor hostname specified"),
}, },
}; };
let addr = SocketAddr::new(ip, client.port.unwrap_or(42069)); let addr = SocketAddr::new(ip, client.port.unwrap_or(42069));
self.register_client(addr, pos); self.register_client(addr, pos);
Ok(())
} }
fn new_id(&mut self) -> ClientHandle { fn new_id(&mut self) -> ClientHandle {
@@ -56,7 +65,7 @@ impl ClientManager {
self.next_id 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 { let mut client_manager = ClientManager {
next_id: 0, next_id: 0,
@@ -64,25 +73,11 @@ impl ClientManager {
}; };
// add clients from config // add clients from config
for client in vec![ for (client, pos) in config.clients.iter() {
&config.client.left, client_manager.add_client(&client, *pos)?;
&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);
}
} }
client_manager Ok(client_manager)
} }
pub fn register_client(&mut self, addr: SocketAddr, pos: Position) { pub fn register_client(&mut self, addr: SocketAddr, pos: Position) {

View File

@@ -1,17 +1,17 @@
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use core::fmt;
use std::net::IpAddr; use std::net::IpAddr;
use std::{error::Error, fs}; use std::{error::Error, fs};
use std::env;
use toml; use toml;
#[derive(Serialize, Deserialize, Debug)] use crate::client::Position;
pub struct Config {
pub client: Clients,
pub port: Option<u16>,
pub backend: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Clients { pub struct ConfigToml {
pub port: Option<u16>,
pub backend: Option<String>,
pub left: Option<Client>, pub left: Option<Client>,
pub right: Option<Client>, pub right: Option<Client>,
pub top: Option<Client>, pub top: Option<Client>,
@@ -25,9 +25,94 @@ pub struct Client {
pub port: Option<u16>, pub port: Option<u16>,
} }
impl Config { #[derive(Debug, Clone)]
pub fn new(path: &str) -> Result<Config, Box<dyn Error>> { 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)?; let config = fs::read_to_string(path)?;
Ok(toml::from_str::<_>(&config)?) 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 })
}
}

View File

@@ -1,4 +1,4 @@
use std::{thread::{JoinHandle, self}, sync::mpsc::Receiver}; use std::{thread::{JoinHandle, self}, sync::mpsc::Receiver, error::Error};
#[cfg(unix)] #[cfg(unix)]
use std::env; use std::env;
@@ -14,11 +14,11 @@ enum Backend {
Libei, 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)] #[cfg(windows)]
let _backend = backend; let _backend = backend;
thread::Builder::new() Ok(thread::Builder::new()
.name("event consumer".into()) .name("event consumer".into())
.spawn(move || { .spawn(move || {
#[cfg(windows)] #[cfg(windows)]
@@ -72,6 +72,5 @@ pub fn start(consume_rx: Receiver<(Event, ClientHandle)>, clients: Vec<Client>,
consumer::x11::run(consume_rx, clients); consumer::x11::run(consume_rx, clients);
}, },
} }
}) })?)
.unwrap()
} }

View File

@@ -20,13 +20,13 @@ pub struct Server {
} }
impl Server { impl Server {
pub fn new(port: u16) -> Self { pub fn new(port: u16) -> Result<Self, Box<dyn Error>> {
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port); let listen_addr = SocketAddr::new("0.0.0.0".parse()?, port);
let sending = Arc::new(AtomicBool::new(false)); let sending = Arc::new(AtomicBool::new(false));
Server { Ok(Server {
listen_addr, listen_addr,
sending, sending,
} })
} }
pub fn run( pub fn run(
@@ -36,7 +36,7 @@ impl Server {
consume_tx: SyncSender<(Event, ClientHandle)>, consume_tx: SyncSender<(Event, ClientHandle)>,
) -> Result<(JoinHandle<()>, JoinHandle<()>), Box<dyn Error>> { ) -> Result<(JoinHandle<()>, JoinHandle<()>), Box<dyn Error>> {
let udp_socket = UdpSocket::bind(self.listen_addr)?; 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 tx = udp_socket;
let sending = self.sending.clone(); let sending = self.sending.clone();
@@ -92,8 +92,7 @@ impl Server {
.expect("event consumer unavailable"); .expect("event consumer unavailable");
} }
} }
}) })?;
.unwrap();
let sending = self.sending.clone(); let sending = self.sending.clone();
@@ -124,8 +123,7 @@ impl Server {
} }
} }
} }
}) })?;
.unwrap();
Ok((receiver, sender)) Ok((receiver, sender))
} }

View File

@@ -1,4 +1,4 @@
use std::sync::mpsc; use std::{sync::mpsc, process, env};
use lan_mouse::{ use lan_mouse::{
client::ClientManager, client::ClientManager,
@@ -6,12 +6,24 @@ use lan_mouse::{
config, event, request, config, event, request,
}; };
fn usage() {
eprintln!("usage: {} [--backend <backend>] [--port <port>]",
env::args().next().unwrap_or("lan-mouse".into()));
}
pub fn main() { pub fn main() {
// parse config file // 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 // port or default
let port = config.port.unwrap_or(42069); let port = config.port;
// event channel for producing events // event channel for producing events
let (produce_tx, produce_rx) = mpsc::sync_channel(128); 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); let (consume_tx, consume_rx) = mpsc::sync_channel(128);
// create client manager // 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 // start receiving client connection requests
let (request_server, request_thread) = request::Server::listen(port).unwrap(); let (request_server, request_thread) = request::Server::listen(port).unwrap();
// start producing and consuming events // start producing and consuming events
let event_producer = producer::start(produce_tx, client_manager.get_clients(), request_server); let event_producer = match producer::start(produce_tx, client_manager.get_clients(), request_server) {
let event_consumer = consumer::start(consume_rx, client_manager.get_clients(), config.backend); 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 // start sending and receiving events
let event_server = event::server::Server::new(port); let event_server = match event::server::Server::new(port) {
let (receiver, sender) = event_server Ok(s) => s,
.run(&mut client_manager, produce_rx, consume_tx) Err(e) => {
.unwrap(); 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(); request_thread.join().unwrap();
// stop receiving events and terminate event-consumer
receiver.join().unwrap(); receiver.join().unwrap();
sender.join().unwrap(); if let Some(thread) = event_consumer {
thread.join().unwrap();
}
event_producer.join().unwrap(); // stop producing events and terminate event-sender
event_consumer.join().unwrap(); if let Some(thread) = event_producer {
thread.join().unwrap();
}
sender.join().unwrap();
} }

View File

@@ -1,6 +1,6 @@
#[cfg(unix)] #[cfg(unix)]
use std::env; 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}; use crate::{client::{Client, ClientHandle}, event::Event, request::Server};
@@ -16,8 +16,8 @@ pub fn start(
produce_tx: SyncSender<(Event, ClientHandle)>, produce_tx: SyncSender<(Event, ClientHandle)>,
clients: Vec<Client>, clients: Vec<Client>,
request_server: Server, request_server: Server,
) -> JoinHandle<()> { ) -> Result<JoinHandle<()>, Box<dyn Error>> {
thread::Builder::new() Ok(thread::Builder::new()
.name("event producer".into()) .name("event producer".into())
.spawn(move || { .spawn(move || {
#[cfg(windows)] #[cfg(windows)]
@@ -48,6 +48,5 @@ pub fn start(
producer::wayland::run(produce_tx, request_server, clients); producer::wayland::run(produce_tx, request_server, clients);
} }
} }
}) })?)
.unwrap()
} }

View File

@@ -60,11 +60,13 @@ impl Server {
pub fn listen(port: u16) -> Result<(Server, JoinHandle<()>), Box<dyn Error>> { 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 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 = Server { data };
let server_copy = server.clone(); let server_copy = server.clone();
let thread = thread::spawn(move || { let listen_socket = TcpListener::bind(listen_addr)?;
let listen_socket = TcpListener::bind(listen_addr).unwrap(); let thread = thread::Builder::new()
.name("tcp server".into())
.spawn(move || {
for stream in listen_socket.incoming() { for stream in listen_socket.incoming() {
match stream { match stream {
Ok(stream) => { Ok(stream) => {
@@ -75,7 +77,7 @@ impl Server {
} }
} }
} }
}); })?;
Ok((server_copy, thread)) Ok((server_copy, thread))
} }