Initial Hotplug support (#8)

This allows to dynamically add clients when an event is received from an unknown IP address.

The user is asked to confirm any unknown connection from new clients.

Currently the back-ends for event producing and consuming are not yet notified, so events will not be received and sent to the newly created clients.
This commit is contained in:
Ferdinand Schober
2023-06-10 15:26:48 +02:00
committed by GitHub
parent 0feb1350a9
commit 225ef818a2
8 changed files with 134 additions and 37 deletions

7
Cargo.lock generated
View File

@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "async-trait"
version = "0.1.57"
@@ -233,6 +239,7 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
name = "lan-mouse"
version = "0.1.0"
dependencies = [
"anyhow",
"memmap",
"serde",
"serde_derive",

View File

@@ -16,6 +16,7 @@ memmap = "0.7"
toml = "0.5"
serde = "1.0"
serde_derive = "1.0"
anyhow = "1.0.71"
[target.'cfg(unix)'.dependencies]
wayland-client = { version="0.30.0", optional = true }
@@ -30,13 +31,8 @@ winapi = { version = "0.3.9", features = ["winuser"] }
[features]
default = [ "wayland", "x11", "xdg_desktop_portal", "libei" ]
wayland = [
"dep:wayland-client",
"dep:wayland-protocols",
"dep:wayland-protocols-wlr",
"dep:wayland-protocols-misc",
"dep:wayland-protocols-plasma" ]
x11 = [ "dep:x11" ]
default = ["wayland", "x11", "xdg_desktop_portal", "libei"]
wayland = ["dep:wayland-client", "dep:wayland-protocols", "dep:wayland-protocols-wlr", "dep:wayland-protocols-misc", "dep:wayland-protocols-plasma"]
x11 = ["dep:x11"]
xdg_desktop_portal = []
libei = []

View File

@@ -69,7 +69,7 @@ impl App {
(_, _, Ok(fake_input)) => {
fake_input.authenticate(
"lan-mouse".into(),
"Allow remote clients to control this devices".into(),
"Allow remote clients to control this device".into(),
);
VirtualInputManager::Kde { fake_input }
}

View File

@@ -1,4 +1,4 @@
use std::{net::SocketAddr, error::Error, fmt::Display};
use std::{net::SocketAddr, error::Error, fmt::Display, sync::{Arc, atomic::{AtomicBool, Ordering, AtomicU32}, RwLock}};
use crate::{config, dns};
@@ -29,8 +29,9 @@ pub enum ClientEvent {
}
pub struct ClientManager {
next_id: u32,
clients: Vec<Client>,
next_id: AtomicU32,
clients: RwLock<Vec<Client>>,
subscribers: RwLock<Vec<Arc<AtomicBool>>>,
}
pub type ClientHandle = u32;
@@ -47,7 +48,7 @@ impl Display for ClientConfigError {
impl Error for ClientConfigError {}
impl ClientManager {
fn add_client(&mut self, client: &config::Client, pos: Position) -> Result<(), Box<dyn Error>> {
fn add_client(&self, client: &config::Client, pos: Position) -> Result<(), Box<dyn Error>> {
let ip = match client.ip {
Some(ip) => ip,
None => match &client.host_name {
@@ -60,16 +61,24 @@ impl ClientManager {
Ok(())
}
fn new_id(&mut self) -> ClientHandle {
self.next_id += 1;
self.next_id
fn notify(&self) {
for subscriber in self.subscribers.read().unwrap().iter() {
subscriber.store(true, Ordering::SeqCst);
}
}
fn new_id(&self) -> ClientHandle {
let id = self.next_id.load(Ordering::Acquire);
self.next_id.store(id + 1, Ordering::Release);
id as ClientHandle
}
pub fn new(config: &config::Config) -> Result<Self, Box<dyn Error>> {
let mut client_manager = ClientManager {
next_id: 0,
clients: Vec::new(),
let client_manager = ClientManager {
next_id: AtomicU32::new(0),
clients: RwLock::new(Vec::new()),
subscribers: RwLock::new(vec![]),
};
// add clients from config
@@ -80,13 +89,18 @@ impl ClientManager {
Ok(client_manager)
}
pub fn register_client(&mut self, addr: SocketAddr, pos: Position) {
pub fn register_client(&self, addr: SocketAddr, pos: Position) {
let handle = self.new_id();
let client = Client { addr, pos, handle };
self.clients.push(client);
self.clients.write().unwrap().push(client);
self.notify();
}
pub fn get_clients(&self) -> Vec<Client> {
self.clients.clone()
self.clients.read().unwrap().clone()
}
pub fn subscribe(&self, subscriber: Arc<AtomicBool>) {
self.subscribers.write().unwrap().push(subscriber);
}
}

View File

@@ -1,3 +1,5 @@
use anyhow::Result;
use std::{
collections::HashMap,
error::Error,
@@ -10,7 +12,7 @@ use std::{
thread::{self, JoinHandle},
};
use crate::client::{ClientHandle, ClientManager};
use crate::{client::{ClientHandle, ClientManager}, ioutils::{ask_confirmation, ask_position}};
use super::Event;
@@ -30,25 +32,25 @@ impl Server {
}
pub fn run(
self,
client_manager: &mut ClientManager,
&self,
client_manager: Arc<ClientManager>,
produce_rx: Receiver<(Event, ClientHandle)>,
consume_tx: SyncSender<(Event, ClientHandle)>,
) -> Result<(JoinHandle<()>, JoinHandle<()>), Box<dyn Error>> {
) -> Result<(JoinHandle<Result<()>>, JoinHandle<Result<()>>), Box<dyn Error>> {
let udp_socket = UdpSocket::bind(self.listen_addr)?;
let rx = udp_socket.try_clone()?;
let tx = udp_socket;
let sending = self.sending.clone();
let clients_updated = Arc::new(AtomicBool::new(true));
client_manager.subscribe(clients_updated.clone());
let client_manager_clone = client_manager.clone();
let mut client_for_socket = HashMap::new();
for client in client_manager.get_clients() {
println!("{}: {}", client.handle, client.addr);
client_for_socket.insert(client.addr, client.handle);
}
let receiver = thread::Builder::new()
.name("event receiver".into())
.spawn(move || {
let mut client_for_socket = HashMap::new();
loop {
let (event, addr) = match Server::receive_event(&rx) {
Ok(e) => e,
@@ -58,10 +60,30 @@ impl Server {
}
};
if let Ok(_) = clients_updated.compare_exchange(
true,
false,
Ordering::SeqCst,
Ordering::SeqCst,
) {
clients_updated.store(false, Ordering::SeqCst);
client_for_socket.clear();
println!("updating clients: ");
for client in client_manager_clone.get_clients() {
println!("{}: {}", client.handle, client.addr);
client_for_socket.insert(client.addr, client.handle);
}
}
let client_handle = match client_for_socket.get(&addr) {
Some(c) => *c,
None => {
println!("Allow connection from {:?}? [Y/n]", addr);
eprint!("Allow connection from {:?}? ", addr);
if ask_confirmation(false)? {
client_manager_clone.register_client(addr, ask_position()?);
} else {
eprintln!("rejecting client: {:?}?", addr);
}
continue;
}
};

49
src/ioutils.rs Normal file
View File

@@ -0,0 +1,49 @@
use std::io::{self, Write};
use crate::client::Position;
pub fn ask_confirmation(default: bool) -> Result<bool, io::Error> {
eprint!("{}", if default {" [Y,n] "} else { " [y,N] "});
io::stderr().flush()?;
let answer = loop {
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
let answer = buffer.to_lowercase();
let answer = answer.trim();
match answer {
"" => break default,
"y" => break true,
"n" => break false,
_ => {
eprint!("Enter y for Yes or n for No: ");
io::stderr().flush()?;
continue
}
}
};
Ok(answer)
}
pub fn ask_position() -> Result<Position, io::Error> {
eprint!("Enter position - top (t) | bottom (b) | left(l) | right(r): ");
io::stderr().flush()?;
let pos = loop {
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
let answer = buffer.to_lowercase();
let answer = answer.trim();
match answer {
"t" | "top" => break Position::Top,
"b" | "bottom" => break Position::Bottom,
"l" | "left" => break Position::Right,
"r" | "right" => break Position::Left,
_ => {
eprint!("Invalid position: {answer} - enter top (t) | bottom (b) | left(l) | right(r): ");
io::stderr().flush()?;
continue
}
};
};
Ok(pos)
}

View File

@@ -8,3 +8,4 @@ pub mod consumer;
pub mod producer;
pub mod backend;
pub mod ioutils;

View File

@@ -1,4 +1,4 @@
use std::{sync::mpsc, process, env};
use std::{sync::{mpsc, Arc}, process, env};
use lan_mouse::{
client::ClientManager,
@@ -32,7 +32,7 @@ pub fn main() {
let (consume_tx, consume_rx) = mpsc::sync_channel(128);
// create client manager
let mut client_manager = match ClientManager::new(&config) {
let client_manager = match ClientManager::new(&config) {
Err(e) => {
eprintln!("{e}");
process::exit(1);
@@ -73,7 +73,7 @@ pub fn main() {
process::exit(1);
}
};
let (receiver, sender) = match event_server.run(&mut client_manager, produce_rx, consume_tx) {
let (receiver, sender) = match event_server.run(Arc::new(client_manager), produce_rx, consume_tx) {
Ok((r,s)) => (r,s),
Err(e) => {
eprintln!("{e}");
@@ -84,7 +84,11 @@ pub fn main() {
request_thread.join().unwrap();
// stop receiving events and terminate event-consumer
receiver.join().unwrap();
if let Err(e) = receiver.join().unwrap() {
eprint!("{e}");
process::exit(1);
}
if let Some(thread) = event_consumer {
thread.join().unwrap();
}
@@ -93,5 +97,9 @@ pub fn main() {
if let Some(thread) = event_producer {
thread.join().unwrap();
}
sender.join().unwrap();
if let Err(e) = sender.join().unwrap() {
eprint!("{e}");
process::exit(1);
}
}