mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-08 04:20:01 +03:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4d6f05802 | ||
|
|
79fa42b74e | ||
|
|
851b6d60eb | ||
|
|
06725f4b14 | ||
|
|
603646c799 | ||
|
|
b2179e88de |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -675,7 +675,7 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "lan-mouse"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"env_logger",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "lan-mouse"
|
||||
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/ferdinandschober/lan-mouse"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Lan Mouse Share
|
||||
# Lan Mouse
|
||||
|
||||

|
||||
|
||||
@@ -19,7 +19,7 @@ which must be located in the current working directory when
|
||||
executing lan-mouse.
|
||||
|
||||
### Example config
|
||||
A minimal config file could look like this:
|
||||
A config file could look like this:
|
||||
|
||||
```toml
|
||||
# example configuration
|
||||
@@ -43,16 +43,13 @@ ips = ["192.168.178.156"]
|
||||
# at least one ip address needs to be specified.
|
||||
host_name = "thorium"
|
||||
# ips for ethernet and wifi
|
||||
ips = ["192.168.178.189"]
|
||||
ips = ["192.168.178.189", "192.168.178.172"]
|
||||
# optional port
|
||||
port = 4242
|
||||
```
|
||||
|
||||
Where `left` can be either `left`, `right`, `top` or `bottom`.
|
||||
|
||||
> :warning: Note that clients from the config
|
||||
> file are currently ignored when using the gtk frontend!
|
||||
|
||||
|
||||
## Build and Run
|
||||
Build in release mode:
|
||||
|
||||
14
de.feschber.LanMouse.yml
Normal file
14
de.feschber.LanMouse.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
app-id: de.feschber.LanMouse
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: '22.08'
|
||||
sdk: org.freedesktop.Sdk
|
||||
command: target/release/lan-mouse
|
||||
modules:
|
||||
- name: hello
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- cargo build --release
|
||||
- install -D lan-mouse /app/bin/lan-mouse
|
||||
sources:
|
||||
- type: file
|
||||
path: target/release/lan-mouse
|
||||
@@ -9,7 +9,7 @@
|
||||
</item>
|
||||
</menu>
|
||||
<template class="LanMouseWindow" parent="GtkApplicationWindow">
|
||||
<property name="width-request">800</property>
|
||||
<property name="width-request">600</property>
|
||||
<property name="title" translatable="yes">Lan Mouse</property>
|
||||
<property name="show-menubar">True</property>
|
||||
<child type="titlebar">
|
||||
@@ -33,7 +33,7 @@
|
||||
<property name="child">
|
||||
<object class="AdwClamp">
|
||||
<property name="maximum-size">600</property>
|
||||
<property name="tightening-threshold">300</property>
|
||||
<property name="tightening-threshold">0</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
|
||||
@@ -165,64 +165,73 @@ enum VirtualInput {
|
||||
impl VirtualInput {
|
||||
fn consume_event(&self, event: Event) -> Result<(),()> {
|
||||
match event {
|
||||
Event::Pointer(e) => match e {
|
||||
PointerEvent::Motion {
|
||||
time,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.motion(time, relative_x, relative_y);
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.pointer_motion(relative_y, relative_y);
|
||||
}
|
||||
},
|
||||
PointerEvent::Button {
|
||||
time,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let state: ButtonState = state.try_into()?;
|
||||
match self {
|
||||
Event::Pointer(e) => {
|
||||
match e {
|
||||
PointerEvent::Motion {
|
||||
time,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.button(time, button, state);
|
||||
pointer.motion(time, relative_x, relative_y);
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.button(button, state as u32);
|
||||
fake_input.pointer_motion(relative_y, relative_y);
|
||||
}
|
||||
},
|
||||
PointerEvent::Button {
|
||||
time,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let state: ButtonState = state.try_into()?;
|
||||
match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.button(time, button, state);
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.button(button, state as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PointerEvent::Axis { time, axis, value } => {
|
||||
let axis: Axis = (axis as u32).try_into()?;
|
||||
match self {
|
||||
PointerEvent::Axis { time, axis, value } => {
|
||||
let axis: Axis = (axis as u32).try_into()?;
|
||||
match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.axis(time, axis, value);
|
||||
pointer.frame();
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.axis(axis as u32, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
PointerEvent::Frame {} => match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.axis(time, axis, value);
|
||||
pointer.frame();
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.axis(axis as u32, value);
|
||||
}
|
||||
}
|
||||
VirtualInput::Kde { fake_input: _ } => {}
|
||||
},
|
||||
}
|
||||
PointerEvent::Frame {} => match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
match self {
|
||||
VirtualInput::Wlroots { pointer, .. } => {
|
||||
// insert a frame event after each mouse event
|
||||
pointer.frame();
|
||||
}
|
||||
VirtualInput::Kde { fake_input: _ } => {}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
Event::Keyboard(e) => match e {
|
||||
KeyboardEvent::Key { time, key, state } => match self {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{client::{ClientHandle, Position, ClientEvent}, producer::EventProducer};
|
||||
use mio::{event::Source, unix::SourceFd};
|
||||
|
||||
use std::{os::fd::RawFd, vec::Drain, io::ErrorKind};
|
||||
use std::{os::fd::RawFd, vec::Drain, io::ErrorKind, env};
|
||||
use memmap::MmapOptions;
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
@@ -196,7 +196,13 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
|
||||
let mut buf = BufWriter::new(f);
|
||||
for _ in 0..height {
|
||||
for _ in 0..width {
|
||||
buf.write_all(&0x00000000u32.to_ne_bytes()).unwrap();
|
||||
if env::var("LM_DEBUG_LAYER_SHELL").ok().is_some() {
|
||||
// AARRGGBB
|
||||
buf.write_all(&0xFF11d116u32.to_ne_bytes()).unwrap();
|
||||
} else {
|
||||
// AARRGGBB
|
||||
buf.write_all(&0x00000000u32.to_ne_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -522,17 +528,23 @@ impl EventProducer for WaylandEventProducer {
|
||||
}
|
||||
|
||||
fn notify(&mut self, client_event: ClientEvent) {
|
||||
if let ClientEvent::Create(handle, pos) = client_event {
|
||||
self.state.add_client(handle, pos);
|
||||
}
|
||||
if let ClientEvent::Destroy(handle) = client_event {
|
||||
loop {
|
||||
// remove all windows corresponding to this client
|
||||
if let Some(i) = self.state.client_for_window.iter().position(|(_,c)| *c == handle) {
|
||||
self.state.client_for_window.remove(i);
|
||||
self.state.focused = None;
|
||||
} else {
|
||||
break
|
||||
match client_event {
|
||||
ClientEvent::Create(handle, pos) => {
|
||||
self.state.add_client(handle, pos);
|
||||
}
|
||||
ClientEvent::Destroy(handle) => {
|
||||
loop {
|
||||
// remove all windows corresponding to this client
|
||||
if let Some(i) = self
|
||||
.state
|
||||
.client_for_window
|
||||
.iter()
|
||||
.position(|(_,c)| *c == handle) {
|
||||
self.state.client_for_window.remove(i);
|
||||
self.state.focused = None;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,7 +598,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
surface_y: _,
|
||||
} => {
|
||||
// get client corresponding to the focused surface
|
||||
log::trace!("produce: enter()");
|
||||
{
|
||||
if let Some((window, client)) = app
|
||||
.client_for_window
|
||||
@@ -606,7 +617,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
app.pending_events.push((*client, Event::Release()));
|
||||
}
|
||||
wl_pointer::Event::Leave { .. } => {
|
||||
log::trace!("produce: leave()");
|
||||
app.ungrab();
|
||||
}
|
||||
wl_pointer::Event::Button {
|
||||
@@ -615,7 +625,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
log::trace!("produce: button()");
|
||||
let (_, client) = app.focused.as_ref().unwrap();
|
||||
app.pending_events.push((
|
||||
*client,
|
||||
@@ -627,7 +636,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
));
|
||||
}
|
||||
wl_pointer::Event::Axis { time, axis, value } => {
|
||||
log::trace!("produce: scroll()");
|
||||
let (_, client) = app.focused.as_ref().unwrap();
|
||||
app.pending_events.push((
|
||||
*client,
|
||||
@@ -639,12 +647,9 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
));
|
||||
}
|
||||
wl_pointer::Event::Frame {} => {
|
||||
log::trace!("produce: frame()");
|
||||
let (_, client) = app.focused.as_ref().unwrap();
|
||||
app.pending_events.push((
|
||||
*client,
|
||||
Event::Pointer(PointerEvent::Frame {}),
|
||||
));
|
||||
// TODO properly handle frame events
|
||||
// we simply insert a frame event on the client side
|
||||
// after each event for now
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -737,7 +742,6 @@ impl Dispatch<ZwpRelativePointerV1, ()> for State {
|
||||
dy_unaccel: surface_y,
|
||||
} = event
|
||||
{
|
||||
log::trace!("produce: motion()");
|
||||
if let Some((_window, client)) = &app.focused {
|
||||
let time = (((utime_hi as u64) << 32 | utime_lo as u64) / 1000) as u32;
|
||||
app.pending_events.push((
|
||||
|
||||
213
src/client.rs
213
src/client.rs
@@ -1,4 +1,4 @@
|
||||
use std::{net::SocketAddr, collections::{HashSet, hash_set::Iter}, fmt::Display, time::{Instant, Duration}, iter::Cloned};
|
||||
use std::{net::{SocketAddr, IpAddr}, collections::HashSet, fmt::Display, time::Instant};
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
@@ -10,6 +10,12 @@ pub enum Position {
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl Default for Position {
|
||||
fn default() -> Self {
|
||||
Self::Left
|
||||
}
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn opposite(&self) -> Self {
|
||||
match self {
|
||||
@@ -34,7 +40,9 @@ impl Display for Position {
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Client {
|
||||
/// handle to refer to the client.
|
||||
/// hostname of this client
|
||||
pub hostname: Option<String>,
|
||||
/// unique handle to refer to the client.
|
||||
/// This way any event consumer / producer backend does not
|
||||
/// need to know anything about a client other than its handle.
|
||||
pub handle: ClientHandle,
|
||||
@@ -46,6 +54,8 @@ pub struct Client {
|
||||
/// e.g. Laptops usually have at least an ethernet and a wifi port
|
||||
/// which have different ip addresses
|
||||
pub addrs: HashSet<SocketAddr>,
|
||||
/// both active_addr and addrs can be None / empty so port needs to be stored seperately
|
||||
pub port: u16,
|
||||
/// position of a client on screen
|
||||
pub pos: Position,
|
||||
}
|
||||
@@ -53,159 +63,118 @@ pub struct Client {
|
||||
pub enum ClientEvent {
|
||||
Create(ClientHandle, Position),
|
||||
Destroy(ClientHandle),
|
||||
UpdatePos(ClientHandle, Position),
|
||||
AddAddr(ClientHandle, SocketAddr),
|
||||
RemoveAddr(ClientHandle, SocketAddr),
|
||||
}
|
||||
|
||||
pub type ClientHandle = u32;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientState {
|
||||
pub client: Client,
|
||||
pub active: bool,
|
||||
pub last_ping: Option<Instant>,
|
||||
pub last_seen: Option<Instant>,
|
||||
pub last_replied: Option<Instant>,
|
||||
}
|
||||
|
||||
pub struct ClientManager {
|
||||
/// probably not beneficial to use a hashmap here
|
||||
clients: Vec<Client>,
|
||||
last_ping: Vec<(ClientHandle, Option<Instant>)>,
|
||||
last_seen: Vec<(ClientHandle, Option<Instant>)>,
|
||||
last_replied: Vec<(ClientHandle, Option<Instant>)>,
|
||||
next_client_id: u32,
|
||||
clients: Vec<Option<ClientState>>, // HashMap likely not beneficial
|
||||
}
|
||||
|
||||
impl ClientManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
clients: vec![],
|
||||
next_client_id: 0,
|
||||
last_ping: vec![],
|
||||
last_seen: vec![],
|
||||
last_replied: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// add a new client to this manager
|
||||
pub fn add_client(&mut self, addrs: HashSet<SocketAddr>, pos: Position) -> ClientHandle {
|
||||
let handle = self.next_id();
|
||||
pub fn add_client(
|
||||
&mut self,
|
||||
hostname: Option<String>,
|
||||
addrs: HashSet<IpAddr>,
|
||||
port: u16,
|
||||
pos: Position,
|
||||
) -> ClientHandle {
|
||||
// get a new client_handle
|
||||
let handle = self.free_id();
|
||||
|
||||
// we dont know, which IP is initially active
|
||||
let active_addr = None;
|
||||
|
||||
// map ip addresses to socket addresses
|
||||
let addrs = HashSet::from_iter(
|
||||
addrs
|
||||
.into_iter()
|
||||
.map(|ip| SocketAddr::new(ip, port))
|
||||
);
|
||||
|
||||
// store the client
|
||||
let client = Client { handle, active_addr, addrs, pos };
|
||||
self.clients.push(client);
|
||||
self.last_ping.push((handle, None));
|
||||
self.last_seen.push((handle, None));
|
||||
self.last_replied.push((handle, None));
|
||||
let client = Client { hostname, handle, active_addr, addrs, port, pos };
|
||||
|
||||
// client was never seen, nor pinged
|
||||
let client_state = ClientState {
|
||||
client,
|
||||
last_ping: None,
|
||||
last_seen: None,
|
||||
last_replied: None,
|
||||
active: false,
|
||||
};
|
||||
|
||||
if handle as usize >= self.clients.len() {
|
||||
assert_eq!(handle as usize, self.clients.len());
|
||||
self.clients.push(Some(client_state));
|
||||
} else {
|
||||
self.clients[handle as usize] = Some(client_state);
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
/// add a socket address to the given client
|
||||
pub fn add_addr(&mut self, client: ClientHandle, addr: SocketAddr) {
|
||||
if let Some(client) = self.get_mut(client) {
|
||||
client.addrs.insert(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// remove socket address from the given client
|
||||
pub fn remove_addr(&mut self, client: ClientHandle, addr: SocketAddr) {
|
||||
if let Some(client) = self.get_mut(client) {
|
||||
client.addrs.remove(&addr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_default_addr(&mut self, client: ClientHandle, addr: SocketAddr) {
|
||||
if let Some(client) = self.get_mut(client) {
|
||||
client.active_addr = Some(addr)
|
||||
}
|
||||
}
|
||||
|
||||
/// update the position of a client
|
||||
pub fn update_pos(&mut self, client: ClientHandle, pos: Position) {
|
||||
if let Some(client) = self.get_mut(client) {
|
||||
client.pos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_active_addr(&self, client: ClientHandle) -> Option<SocketAddr> {
|
||||
self.get(client)?.active_addr
|
||||
}
|
||||
|
||||
pub fn get_addrs(&self, client: ClientHandle) -> Option<Cloned<Iter<'_, SocketAddr>>> {
|
||||
Some(self.get(client)?.addrs.iter().cloned())
|
||||
}
|
||||
|
||||
pub fn last_ping(&self, client: ClientHandle) -> Option<Duration> {
|
||||
let last_ping = self.last_ping
|
||||
.iter()
|
||||
.find(|(c,_)| *c == client)?.1;
|
||||
last_ping.map(|p| p.elapsed())
|
||||
}
|
||||
|
||||
pub fn last_seen(&self, client: ClientHandle) -> Option<Duration> {
|
||||
let last_seen = self.last_seen
|
||||
.iter()
|
||||
.find(|(c, _)| *c == client)?.1;
|
||||
last_seen.map(|t| t.elapsed())
|
||||
}
|
||||
|
||||
pub fn last_replied(&self, client: ClientHandle) -> Option<Duration> {
|
||||
let last_replied = self.last_replied
|
||||
.iter()
|
||||
.find(|(c, _)| *c == client)?.1;
|
||||
last_replied.map(|t| t.elapsed())
|
||||
}
|
||||
|
||||
pub fn reset_last_ping(&mut self, client: ClientHandle) {
|
||||
if let Some(c) = self.last_ping
|
||||
.iter_mut()
|
||||
.find(|(c, _)| *c == client) {
|
||||
c.1 = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_last_seen(&mut self, client: ClientHandle) {
|
||||
if let Some(c) = self.last_seen
|
||||
.iter_mut()
|
||||
.find(|(c, _)| *c == client) {
|
||||
c.1 = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_last_replied(&mut self, client: ClientHandle) {
|
||||
if let Some(c) = self.last_replied
|
||||
.iter_mut()
|
||||
.find(|(c, _)| *c == client) {
|
||||
c.1 = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
/// find a client by its address
|
||||
pub fn get_client(&self, addr: SocketAddr) -> Option<ClientHandle> {
|
||||
// since there shouldn't be more than a handful of clients at any given
|
||||
// time this is likely faster than using a HashMap
|
||||
self.clients
|
||||
.iter()
|
||||
.find(|c| c.addrs.contains(&addr))
|
||||
.map(|c| c.handle)
|
||||
.position(|c| if let Some(c) = c {
|
||||
c.active && c.client.addrs.contains(&addr)
|
||||
} else {
|
||||
false
|
||||
})
|
||||
.map(|p| p as ClientHandle)
|
||||
}
|
||||
|
||||
pub fn remove_client(&mut self, client: ClientHandle) {
|
||||
if let Some(i) = self.clients.iter().position(|c| c.handle == client) {
|
||||
self.clients.remove(i);
|
||||
self.last_ping.remove(i);
|
||||
self.last_seen.remove(i);
|
||||
self.last_replied.remove(i);
|
||||
/// remove a client from the list
|
||||
pub fn remove_client(&mut self, client: ClientHandle) -> Option<ClientState> {
|
||||
// remove id from occupied ids
|
||||
self.clients.get_mut(client as usize)?.take()
|
||||
}
|
||||
|
||||
/// get a free slot in the client list
|
||||
fn free_id(&mut self) -> ClientHandle {
|
||||
for i in 0..u32::MAX {
|
||||
if self.clients.get(i as usize).is_none()
|
||||
|| self.clients.get(i as usize).unwrap().is_none() {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
panic!("Out of client ids");
|
||||
}
|
||||
|
||||
fn next_id(&mut self) -> ClientHandle {
|
||||
let handle = self.next_client_id;
|
||||
self.next_client_id += 1;
|
||||
handle
|
||||
// returns an immutable reference to the client state corresponding to `client`
|
||||
pub fn get<'a>(&'a self, client: ClientHandle) -> Option<&'a ClientState> {
|
||||
self.clients.get(client as usize)?.as_ref()
|
||||
}
|
||||
|
||||
fn get<'a>(&'a self, client: ClientHandle) -> Option<&'a Client> {
|
||||
/// returns a mutable reference to the client state corresponding to `client`
|
||||
pub fn get_mut<'a>(&'a mut self, client: ClientHandle) -> Option<&'a mut ClientState> {
|
||||
self.clients.get_mut(client as usize)?.as_mut()
|
||||
}
|
||||
|
||||
pub fn enumerate(&self) -> Vec<(Client, bool)> {
|
||||
self.clients
|
||||
.iter()
|
||||
.find(|c| c.handle == client)
|
||||
}
|
||||
|
||||
fn get_mut<'a>(&'a mut self, client: ClientHandle) -> Option<&'a mut Client> {
|
||||
self.clients
|
||||
.iter_mut()
|
||||
.find(|c| c.handle == client)
|
||||
.filter_map(|s|s.as_ref())
|
||||
.map(|s| (s.client.clone(), s.active))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use core::fmt;
|
||||
use std::collections::HashSet;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::net::IpAddr;
|
||||
use std::{error::Error, fs};
|
||||
|
||||
use std::env;
|
||||
use toml;
|
||||
|
||||
use crate::client::Position;
|
||||
use crate::dns;
|
||||
|
||||
pub const DEFAULT_PORT: u16 = 4242;
|
||||
|
||||
@@ -136,40 +135,16 @@ impl Config {
|
||||
Ok(Config { frontend, clients, port })
|
||||
}
|
||||
|
||||
pub fn get_clients(&self) -> Vec<(HashSet<SocketAddr>, Option<String>, Position)> {
|
||||
pub fn get_clients(&self) -> Vec<(HashSet<IpAddr>, Option<String>, u16, Position)> {
|
||||
self.clients.iter().map(|(c,p)| {
|
||||
let port = c.port.unwrap_or(DEFAULT_PORT);
|
||||
// add ips from config
|
||||
let config_ips: Vec<IpAddr> = if let Some(ips) = c.ips.as_ref() {
|
||||
ips.iter().cloned().collect()
|
||||
let ips: HashSet<IpAddr> = if let Some(ips) = c.ips.as_ref() {
|
||||
HashSet::from_iter(ips.iter().cloned())
|
||||
} else {
|
||||
vec![]
|
||||
HashSet::new()
|
||||
};
|
||||
let host_name = c.host_name.clone();
|
||||
// add ips from dns lookup
|
||||
let dns_ips = match host_name.as_ref() {
|
||||
None => vec![],
|
||||
Some(host_name) => match dns::resolve(host_name) {
|
||||
Err(e) => {
|
||||
log::warn!("{host_name}: could not resolve host: {e}");
|
||||
vec![]
|
||||
}
|
||||
Ok(l) if l.is_empty() => {
|
||||
log::warn!("{host_name}: could not resolve host");
|
||||
vec![]
|
||||
}
|
||||
Ok(l) => l,
|
||||
}
|
||||
};
|
||||
if config_ips.is_empty() && dns_ips.is_empty() {
|
||||
log::error!("no ips found for client {p:?}, ignoring!");
|
||||
log::error!("You can manually specify ip addresses via the `ips` config option");
|
||||
}
|
||||
let ips = config_ips.into_iter().chain(dns_ips.into_iter());
|
||||
|
||||
// map ip addresses to socket addresses
|
||||
let addrs: HashSet<SocketAddr> = ips.map(|ip| SocketAddr::new(ip, port)).collect();
|
||||
(addrs, host_name, *p)
|
||||
}).filter(|(a, _, _)| !a.is_empty()).collect()
|
||||
(ips, host_name, port, *p)
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
19
src/dns.rs
19
src/dns.rs
@@ -1,9 +1,20 @@
|
||||
use anyhow::Result;
|
||||
use std::{error::Error, net::IpAddr};
|
||||
|
||||
use trust_dns_resolver::Resolver;
|
||||
|
||||
pub fn resolve(host: &str) -> Result<Vec<IpAddr>, Box<dyn Error>> {
|
||||
log::info!("resolving {host} ...");
|
||||
let response = Resolver::from_system_conf()?.lookup_ip(host)?;
|
||||
Ok(response.iter().collect())
|
||||
pub(crate) struct DnsResolver {
|
||||
resolver: Resolver,
|
||||
}
|
||||
impl DnsResolver {
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
let resolver = Resolver::from_system_conf()?;
|
||||
Ok(Self { resolver })
|
||||
}
|
||||
|
||||
pub(crate) fn resolve(&self, host: &str) -> Result<Vec<IpAddr>, Box<dyn Error>> {
|
||||
log::info!("resolving {host} ...");
|
||||
let response = self.resolver.lookup_ip(host)?;
|
||||
Ok(response.iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::{error::Error, io::Result, collections::HashSet, time::Duration};
|
||||
use std::{error::Error, io::Result, collections::HashSet, time::{Duration, Instant}, net::IpAddr};
|
||||
use log;
|
||||
use mio::{Events, Poll, Interest, Token, net::UdpSocket};
|
||||
use mio::{Events, Poll, Interest, Token, net::UdpSocket, event::Source};
|
||||
#[cfg(not(windows))]
|
||||
use mio_signals::{Signals, Signal, SignalSet};
|
||||
|
||||
use std::{net::SocketAddr, io::ErrorKind};
|
||||
|
||||
use crate::{client::{ClientEvent, ClientManager, Position}, consumer::EventConsumer, producer::EventProducer, frontend::{FrontendEvent, FrontendAdapter}, dns};
|
||||
use crate::{client::{ClientEvent, ClientManager, Position, ClientHandle}, consumer::EventConsumer, producer::EventProducer, frontend::{FrontendEvent, FrontendListener, FrontendNotify}, dns::{self, DnsResolver}};
|
||||
use super::Event;
|
||||
|
||||
/// keeps track of state to prevent a feedback loop
|
||||
@@ -22,11 +22,13 @@ pub struct Server {
|
||||
socket: UdpSocket,
|
||||
producer: Box<dyn EventProducer>,
|
||||
consumer: Box<dyn EventConsumer>,
|
||||
resolver: DnsResolver,
|
||||
#[cfg(not(windows))]
|
||||
signals: Signals,
|
||||
frontend: FrontendAdapter,
|
||||
frontend: FrontendListener,
|
||||
client_manager: ClientManager,
|
||||
state: State,
|
||||
next_token: usize,
|
||||
}
|
||||
|
||||
const UDP_RX: Token = Token(0);
|
||||
@@ -35,17 +37,22 @@ const PRODUCER_RX: Token = Token(2);
|
||||
#[cfg(not(windows))]
|
||||
const SIGNAL: Token = Token(3);
|
||||
|
||||
const MAX_TOKEN: usize = 4;
|
||||
|
||||
impl Server {
|
||||
pub fn new(
|
||||
port: u16,
|
||||
mut producer: Box<dyn EventProducer>,
|
||||
consumer: Box<dyn EventConsumer>,
|
||||
mut frontend: FrontendAdapter,
|
||||
) -> Result<Self> {
|
||||
mut frontend: FrontendListener,
|
||||
) -> anyhow::Result<Self> {
|
||||
// bind the udp socket
|
||||
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port);
|
||||
let mut socket = UdpSocket::bind(listen_addr)?;
|
||||
|
||||
// create dns resolver
|
||||
let resolver = dns::DnsResolver::new()?;
|
||||
|
||||
// register event sources
|
||||
let poll = Poll::new()?;
|
||||
|
||||
@@ -55,7 +62,7 @@ impl Server {
|
||||
|
||||
#[cfg(not(windows))]
|
||||
poll.registry().register(&mut signals, SIGNAL, Interest::READABLE)?;
|
||||
poll.registry().register(&mut socket, UDP_RX, Interest::READABLE | Interest::WRITABLE)?;
|
||||
poll.registry().register(&mut socket, UDP_RX, Interest::READABLE)?;
|
||||
poll.registry().register(&mut producer, PRODUCER_RX, Interest::READABLE)?;
|
||||
poll.registry().register(&mut frontend, FRONTEND_RX, Interest::READABLE)?;
|
||||
|
||||
@@ -63,10 +70,12 @@ impl Server {
|
||||
let client_manager = ClientManager::new();
|
||||
Ok(Server {
|
||||
poll, socket, consumer, producer,
|
||||
resolver,
|
||||
#[cfg(not(windows))]
|
||||
signals, frontend,
|
||||
client_manager,
|
||||
state: State::Receiving,
|
||||
next_token: MAX_TOKEN,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -83,34 +92,105 @@ impl Server {
|
||||
match event.token() {
|
||||
UDP_RX => self.handle_udp_rx(),
|
||||
PRODUCER_RX => self.handle_producer_rx(),
|
||||
FRONTEND_RX => if self.handle_frontend_rx() { return Ok(()) },
|
||||
FRONTEND_RX => self.handle_frontend_incoming(),
|
||||
#[cfg(not(windows))]
|
||||
SIGNAL => if self.handle_signal() { return Ok(()) },
|
||||
_ => panic!("what happened here?")
|
||||
_ => if self.handle_frontend_event(event.token()) { return Ok(()) },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_client(&mut self, addr: HashSet<SocketAddr>, pos: Position) {
|
||||
let client = self.client_manager.add_client(addr, pos);
|
||||
pub fn add_client(&mut self, hostname: Option<String>, mut addr: HashSet<IpAddr>, port: u16, pos: Position) -> ClientHandle {
|
||||
let ips = if let Some(hostname) = hostname.as_ref() {
|
||||
HashSet::from_iter(self.resolver.resolve(hostname.as_str()).ok().iter().flatten().cloned())
|
||||
} else {
|
||||
HashSet::new()
|
||||
};
|
||||
addr.extend(ips.iter());
|
||||
log::info!("adding client [{}]{} @ {:?}", pos, hostname.as_deref().unwrap_or(""), &ips);
|
||||
let client = self.client_manager.add_client(hostname.clone(), addr, port, pos);
|
||||
log::debug!("add_client {client}");
|
||||
self.producer.notify(ClientEvent::Create(client, pos));
|
||||
self.consumer.notify(ClientEvent::Create(client, pos));
|
||||
let notify = FrontendNotify::NotifyClientCreate(client, hostname, port, pos);
|
||||
if let Err(e) = self.frontend.notify_all(notify) {
|
||||
log::error!("{e}");
|
||||
};
|
||||
client
|
||||
}
|
||||
|
||||
pub fn remove_client(&mut self, host: String, port: u16) {
|
||||
if let Ok(ips) = dns::resolve(host.as_str()) {
|
||||
if let Some(ip) = ips.iter().next() {
|
||||
let addr = SocketAddr::new(*ip, port);
|
||||
if let Some(handle) = self.client_manager.get_client(addr) {
|
||||
log::debug!("remove_client {handle}");
|
||||
self.client_manager.remove_client(handle);
|
||||
self.producer.notify(ClientEvent::Destroy(handle));
|
||||
self.consumer.notify(ClientEvent::Destroy(handle));
|
||||
pub fn activate_client(&mut self, client: ClientHandle, active: bool) {
|
||||
if let Some(state) = self.client_manager.get_mut(client) {
|
||||
state.active = active;
|
||||
if state.active {
|
||||
self.producer.notify(ClientEvent::Create(client, state.client.pos));
|
||||
self.consumer.notify(ClientEvent::Create(client, state.client.pos));
|
||||
} else {
|
||||
self.producer.notify(ClientEvent::Destroy(client));
|
||||
self.consumer.notify(ClientEvent::Destroy(client));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_client(&mut self, client: ClientHandle) -> Option<ClientHandle> {
|
||||
self.producer.notify(ClientEvent::Destroy(client));
|
||||
self.consumer.notify(ClientEvent::Destroy(client));
|
||||
if let Some(client) = self.client_manager.remove_client(client).map(|s| s.client.handle) {
|
||||
let notify = FrontendNotify::NotifyClientDelete(client);
|
||||
log::debug!("{notify:?}");
|
||||
if let Err(e) = self.frontend.notify_all(notify) {
|
||||
log::error!("{e}");
|
||||
}
|
||||
Some(client)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_client(
|
||||
&mut self,
|
||||
client: ClientHandle,
|
||||
hostname: Option<String>,
|
||||
port: u16,
|
||||
pos: Position,
|
||||
) {
|
||||
// retrieve state
|
||||
let Some(state) = self.client_manager.get_mut(client) else {
|
||||
return
|
||||
};
|
||||
|
||||
// update pos
|
||||
state.client.pos = pos;
|
||||
if state.active {
|
||||
self.producer.notify(ClientEvent::Destroy(client));
|
||||
self.consumer.notify(ClientEvent::Destroy(client));
|
||||
self.producer.notify(ClientEvent::Create(client, pos));
|
||||
self.consumer.notify(ClientEvent::Create(client, pos));
|
||||
}
|
||||
|
||||
// update port
|
||||
if state.client.port != port {
|
||||
state.client.port = port;
|
||||
state.client.addrs = state.client.addrs
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut a| { a.set_port(port); a })
|
||||
.collect();
|
||||
state.client.active_addr.map(|a| { SocketAddr::new(a.ip(), port) });
|
||||
}
|
||||
|
||||
// update hostname
|
||||
if state.client.hostname != hostname {
|
||||
state.client.addrs = HashSet::new();
|
||||
state.client.active_addr = None;
|
||||
state.client.hostname = hostname;
|
||||
if let Some(hostname) = state.client.hostname.as_ref() {
|
||||
if let Ok(ips) = self.resolver.resolve(hostname.as_str()) {
|
||||
let addrs = ips.iter().map(|i| SocketAddr::new(*i, port));
|
||||
state.client.addrs = HashSet::from_iter(addrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("client updated: {:?}", state);
|
||||
}
|
||||
|
||||
fn handle_udp_rx(&mut self) {
|
||||
@@ -129,7 +209,6 @@ impl Server {
|
||||
continue
|
||||
}
|
||||
};
|
||||
log::trace!("{:20} <-<-<-<------ {addr}", event.to_string());
|
||||
|
||||
// get handle for addr
|
||||
let handle = match self.client_manager.get_client(addr) {
|
||||
@@ -139,10 +218,19 @@ impl Server {
|
||||
continue
|
||||
}
|
||||
};
|
||||
log::trace!("{:20} <-<-<-<------ {addr} ({handle})", event.to_string());
|
||||
let state = match self.client_manager.get_mut(handle) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
log::error!("unknown handle");
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
// reset ttl for client and set addr as new default for this client
|
||||
self.client_manager.reset_last_seen(handle);
|
||||
self.client_manager.set_default_addr(handle, addr);
|
||||
// reset ttl for client and
|
||||
state.last_seen = Some(Instant::now());
|
||||
// set addr as new default for this client
|
||||
state.client.active_addr = Some(addr);
|
||||
match (event, addr) {
|
||||
(Event::Pong(), _) => {},
|
||||
(Event::Ping(), addr) => {
|
||||
@@ -153,32 +241,31 @@ impl Server {
|
||||
// since its very likely, that we wont get a release event
|
||||
self.producer.release();
|
||||
}
|
||||
(event, addr) => {
|
||||
match self.state {
|
||||
State::Sending => {
|
||||
// in sending state, we dont want to process
|
||||
// any events to avoid feedback loops,
|
||||
// therefore we tell the event producer
|
||||
// to release the pointer and move on
|
||||
// first event -> release pointer
|
||||
if let Event::Release() = event {
|
||||
log::debug!("releasing pointer ...");
|
||||
self.producer.release();
|
||||
self.state = State::Receiving;
|
||||
}
|
||||
(event, addr) => match self.state {
|
||||
State::Sending => {
|
||||
// in sending state, we dont want to process
|
||||
// any events to avoid feedback loops,
|
||||
// therefore we tell the event producer
|
||||
// to release the pointer and move on
|
||||
// first event -> release pointer
|
||||
if let Event::Release() = event {
|
||||
log::debug!("releasing pointer ...");
|
||||
self.producer.release();
|
||||
self.state = State::Receiving;
|
||||
}
|
||||
State::Receiving => {
|
||||
// consume event
|
||||
self.consumer.consume(event, handle);
|
||||
}
|
||||
State::Receiving => {
|
||||
// consume event
|
||||
self.consumer.consume(event, handle);
|
||||
|
||||
// let the server know we are still alive once every second
|
||||
let last_replied = self.client_manager.last_replied(handle);
|
||||
if last_replied.is_none()
|
||||
|| last_replied.is_some() && last_replied.unwrap() > Duration::from_secs(1) {
|
||||
self.client_manager.reset_last_replied(handle);
|
||||
if let Err(e) = Self::send_event(&self.socket, Event::Pong(), addr) {
|
||||
log::error!("udp send: {}", e);
|
||||
}
|
||||
// let the server know we are still alive once every second
|
||||
let last_replied = state.last_replied;
|
||||
if last_replied.is_none()
|
||||
|| last_replied.is_some()
|
||||
&& last_replied.unwrap().elapsed() > Duration::from_secs(1) {
|
||||
state.last_replied = Some(Instant::now());
|
||||
if let Err(e) = Self::send_event(&self.socket, Event::Pong(), addr) {
|
||||
log::error!("udp send: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,10 +283,18 @@ impl Server {
|
||||
if let Event::Release() = e {
|
||||
self.state = State::Sending;
|
||||
}
|
||||
|
||||
log::trace!("producer: ({c}) {e:?}");
|
||||
let state = match self.client_manager.get_mut(c) {
|
||||
Some(state) => state,
|
||||
None => {
|
||||
log::warn!("unknown client!");
|
||||
continue
|
||||
}
|
||||
};
|
||||
// otherwise we should have an address to send to
|
||||
// transmit events to the corrensponding client
|
||||
if let Some(addr) = self.client_manager.get_active_addr(c) {
|
||||
log::trace!("{:20} ------>->->-> {addr}", e.to_string());
|
||||
if let Some(addr) = state.client.active_addr {
|
||||
if let Err(e) = Self::send_event(&self.socket, e, addr) {
|
||||
log::error!("udp send: {}", e);
|
||||
}
|
||||
@@ -208,41 +303,38 @@ impl Server {
|
||||
// if client last responded > 2 seconds ago
|
||||
// and we have not sent a ping since 500 milliseconds,
|
||||
// send a ping
|
||||
let last_seen = self.client_manager.last_seen(c);
|
||||
let last_ping = self.client_manager.last_ping(c);
|
||||
if last_seen.is_some() && last_seen.unwrap() < Duration::from_secs(2) {
|
||||
if state.last_seen.is_some()
|
||||
&& state.last_seen.unwrap().elapsed() < Duration::from_secs(2) {
|
||||
continue
|
||||
}
|
||||
|
||||
// client last seen > 500ms ago
|
||||
if last_ping.is_some() && last_ping.unwrap() < Duration::from_millis(500) {
|
||||
if state.last_ping.is_some()
|
||||
&& state.last_ping.unwrap().elapsed() < Duration::from_millis(500) {
|
||||
continue
|
||||
}
|
||||
|
||||
// release mouse if client didnt respond to the first ping
|
||||
if last_ping.is_some() && last_ping.unwrap() < Duration::from_secs(1) {
|
||||
if state.last_ping.is_some()
|
||||
&& state.last_ping.unwrap().elapsed() < Duration::from_secs(1) {
|
||||
should_release = true;
|
||||
}
|
||||
|
||||
// last ping > 500ms ago -> ping all interfaces
|
||||
self.client_manager.reset_last_ping(c);
|
||||
if let Some(iter) = self.client_manager.get_addrs(c) {
|
||||
for addr in iter {
|
||||
log::debug!("pinging {addr}");
|
||||
if let Err(e) = Self::send_event(&self.socket, Event::Ping(), addr) {
|
||||
if e.kind() != ErrorKind::WouldBlock {
|
||||
log::error!("udp send: {}", e);
|
||||
}
|
||||
}
|
||||
// send additional release event, in case client is still in sending mode
|
||||
if let Err(e) = Self::send_event(&self.socket, Event::Release(), addr) {
|
||||
if e.kind() != ErrorKind::WouldBlock {
|
||||
log::error!("udp send: {}", e);
|
||||
}
|
||||
state.last_ping = Some(Instant::now());
|
||||
for addr in state.client.addrs.iter() {
|
||||
log::debug!("pinging {addr}");
|
||||
if let Err(e) = Self::send_event(&self.socket, Event::Ping(), *addr) {
|
||||
if e.kind() != ErrorKind::WouldBlock {
|
||||
log::error!("udp send: {}", e);
|
||||
}
|
||||
}
|
||||
// send additional release event, in case client is still in sending mode
|
||||
if let Err(e) = Self::send_event(&self.socket, Event::Release(), *addr) {
|
||||
if e.kind() != ErrorKind::WouldBlock {
|
||||
log::error!("udp send: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO should repeat dns lookup
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,32 +346,70 @@ impl Server {
|
||||
|
||||
}
|
||||
|
||||
fn handle_frontend_rx(&mut self) -> bool {
|
||||
fn handle_frontend_incoming(&mut self) {
|
||||
loop {
|
||||
match self.frontend.read_event() {
|
||||
Ok(event) => match event {
|
||||
FrontendEvent::AddClient(host, port, pos) => {
|
||||
if let Ok(ips) = dns::resolve(host.as_str()) {
|
||||
let addrs = ips.iter().map(|i| SocketAddr::new(*i, port));
|
||||
self.add_client(HashSet::from_iter(addrs), pos);
|
||||
}
|
||||
let token = self.fresh_token();
|
||||
let poll = &mut self.poll;
|
||||
match self.frontend.handle_incoming(|s, i| {
|
||||
poll.registry().register(s, token, i)?;
|
||||
Ok(token)
|
||||
}) {
|
||||
Err(e) if e.kind() == ErrorKind::WouldBlock => break,
|
||||
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
break
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
// notify new frontend connections of current clients
|
||||
self.enumerate();
|
||||
}
|
||||
|
||||
fn handle_frontend_event(&mut self, token: Token) -> bool {
|
||||
loop {
|
||||
let event = match self.frontend.read_event(token) {
|
||||
Ok(event) => event,
|
||||
Err(e) if e.kind() == ErrorKind::WouldBlock => return false,
|
||||
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if let Some(event) = event {
|
||||
log::debug!("frontend: {event:?}");
|
||||
match event {
|
||||
FrontendEvent::AddClient(hostname, port, pos) => {
|
||||
self.add_client(hostname, HashSet::new(), port, pos);
|
||||
}
|
||||
FrontendEvent::DelClient(host, port) => self.remove_client(host, port),
|
||||
FrontendEvent::ActivateClient(client, active) => {
|
||||
self.activate_client(client, active);
|
||||
}
|
||||
FrontendEvent::DelClient(client) => {
|
||||
self.remove_client(client);
|
||||
}
|
||||
FrontendEvent::UpdateClient(client, hostname, port, pos) => {
|
||||
self.update_client(client, hostname, port, pos);
|
||||
}
|
||||
FrontendEvent::Enumerate() => self.enumerate(),
|
||||
FrontendEvent::Shutdown() => {
|
||||
log::info!("terminating gracefully...");
|
||||
return true;
|
||||
},
|
||||
FrontendEvent::ChangePort(_) => todo!(),
|
||||
FrontendEvent::AddIp(_, _) => todo!(),
|
||||
}
|
||||
Err(e) if e.kind() == ErrorKind::WouldBlock => return false,
|
||||
Err(e) => {
|
||||
log::error!("frontend: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate(&mut self) {
|
||||
let clients = self.client_manager.enumerate();
|
||||
if let Err(e) = self.frontend.notify_all(FrontendNotify::Enumerate(clients)) {
|
||||
log::error!("{e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn handle_signal(&mut self) -> bool {
|
||||
#[cfg(windows)]
|
||||
@@ -306,6 +436,7 @@ impl Server {
|
||||
}
|
||||
|
||||
fn send_event(sock: &UdpSocket, e: Event, addr: SocketAddr) -> Result<usize> {
|
||||
log::trace!("{:20} ------>->->-> {addr}", e.to_string());
|
||||
let data: Vec<u8> = (&e).into();
|
||||
// We are currently abusing a blocking send to get the lowest possible latency.
|
||||
// It may be better to set the socket to non-blocking and only send when ready.
|
||||
@@ -319,4 +450,16 @@ impl Server {
|
||||
Err(e) => Err(Box::new(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn fresh_token(&mut self) -> Token {
|
||||
let token = self.next_token as usize;
|
||||
self.next_token += 1;
|
||||
Token(token)
|
||||
}
|
||||
|
||||
pub fn register_frontend(&mut self, source: &mut dyn Source, interests: Interest) -> Result<Token> {
|
||||
let token = self.fresh_token();
|
||||
self.poll.registry().register(source, token, interests)?;
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
||||
163
src/frontend.rs
163
src/frontend.rs
@@ -1,20 +1,25 @@
|
||||
use std::io::{Read, Result};
|
||||
use std::net::IpAddr;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Read, Result, Write};
|
||||
use std::str;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::{env, path::{Path, PathBuf}};
|
||||
|
||||
use mio::Interest;
|
||||
use mio::{Registry, Token, event::Source};
|
||||
|
||||
#[cfg(unix)]
|
||||
use mio::net::UnixStream;
|
||||
#[cfg(unix)]
|
||||
use mio::net::UnixListener;
|
||||
#[cfg(windows)]
|
||||
use mio::net::TcpStream;
|
||||
#[cfg(windows)]
|
||||
use mio::net::TcpListener;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::client::{Client, Position};
|
||||
use crate::client::{Position, ClientHandle, Client};
|
||||
|
||||
/// cli frontend
|
||||
pub mod cli;
|
||||
@@ -25,29 +30,40 @@ pub mod gtk;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum FrontendEvent {
|
||||
ChangePort(u16),
|
||||
AddClient(String, u16, Position),
|
||||
DelClient(String, u16),
|
||||
AddIp(String, Option<IpAddr>),
|
||||
/// add a new client
|
||||
AddClient(Option<String>, u16, Position),
|
||||
/// activate/deactivate client
|
||||
ActivateClient(ClientHandle, bool),
|
||||
/// update a client (hostname, port, position)
|
||||
UpdateClient(ClientHandle, Option<String>, u16, Position),
|
||||
/// remove a client
|
||||
DelClient(ClientHandle),
|
||||
/// request an enumertaion of all clients
|
||||
Enumerate(),
|
||||
/// service shutdown
|
||||
Shutdown(),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum FrontendNotify {
|
||||
NotifyClientCreate(Client),
|
||||
NotifyClientCreate(ClientHandle, Option<String>, u16, Position),
|
||||
NotifyClientUpdate(ClientHandle, Option<String>, u16, Position),
|
||||
NotifyClientDelete(ClientHandle),
|
||||
Enumerate(Vec<(Client, bool)>),
|
||||
NotifyError(String),
|
||||
}
|
||||
|
||||
pub struct FrontendAdapter {
|
||||
pub struct FrontendListener {
|
||||
#[cfg(windows)]
|
||||
listener: TcpListener,
|
||||
#[cfg(unix)]
|
||||
listener: UnixListener,
|
||||
#[cfg(unix)]
|
||||
socket_path: PathBuf,
|
||||
frontend_connections: HashMap<Token, FrontendConnection>,
|
||||
}
|
||||
|
||||
impl FrontendAdapter {
|
||||
impl FrontendListener {
|
||||
pub fn new() -> std::result::Result<Self, Box<dyn std::error::Error>> {
|
||||
#[cfg(unix)]
|
||||
let socket_path = Path::new(env::var("XDG_RUNTIME_DIR")?.as_str()).join("lan-mouse-socket.sock");
|
||||
@@ -67,28 +83,57 @@ impl FrontendAdapter {
|
||||
listener,
|
||||
#[cfg(unix)]
|
||||
socket_path,
|
||||
frontend_connections: HashMap::new(),
|
||||
};
|
||||
|
||||
Ok(adapter)
|
||||
}
|
||||
|
||||
pub fn read_event(&mut self) -> Result<FrontendEvent>{
|
||||
#[cfg(unix)]
|
||||
pub fn handle_incoming<F>(&mut self, register_frontend: F) -> Result<()>
|
||||
where F: Fn(&mut UnixStream, Interest) -> Result<Token> {
|
||||
let (mut stream, _) = self.listener.accept()?;
|
||||
let mut buf = [0u8; 128]; // FIXME
|
||||
stream.read(&mut buf)?;
|
||||
let json = str::from_utf8(&buf)
|
||||
.unwrap()
|
||||
.trim_end_matches(char::from(0)); // remove trailing 0-bytes
|
||||
log::debug!("{json}");
|
||||
let event = serde_json::from_str(json).unwrap();
|
||||
log::debug!("{:?}", event);
|
||||
Ok(event)
|
||||
let token = register_frontend(&mut stream, Interest::READABLE)?;
|
||||
let con = FrontendConnection::new(stream);
|
||||
self.frontend_connections.insert(token, con);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn notify(&self, _event: FrontendNotify) { }
|
||||
#[cfg(windows)]
|
||||
pub fn handle_incoming<F>(&mut self, register_frontend: F) -> Result<()>
|
||||
where F: Fn(&mut TcpStream, Interest) -> Result<Token> {
|
||||
let (mut stream, _) = self.listener.accept()?;
|
||||
let token = register_frontend(&mut stream, Interest::READABLE)?;
|
||||
let con = FrontendConnection::new(stream);
|
||||
self.frontend_connections.insert(token, con);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_event(&mut self, token: Token) -> Result<Option<FrontendEvent>> {
|
||||
if let Some(con) = self.frontend_connections.get_mut(&token) {
|
||||
con.handle_event()
|
||||
} else {
|
||||
panic!("unknown token");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn notify_all(&mut self, notify: FrontendNotify) -> Result<()> {
|
||||
// encode event
|
||||
let json = serde_json::to_string(¬ify).unwrap();
|
||||
let payload = json.as_bytes();
|
||||
let len = payload.len().to_ne_bytes();
|
||||
log::debug!("json: {json}, len: {}", payload.len());
|
||||
|
||||
for con in self.frontend_connections.values_mut() {
|
||||
// write len + payload
|
||||
con.stream.write(&len)?;
|
||||
con.stream.write(payload)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for FrontendAdapter {
|
||||
impl Source for FrontendListener {
|
||||
fn register(
|
||||
&mut self,
|
||||
registry: &Registry,
|
||||
@@ -113,9 +158,79 @@ impl Source for FrontendAdapter {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Drop for FrontendAdapter {
|
||||
impl Drop for FrontendListener {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("remove socket: {:?}", self.socket_path);
|
||||
std::fs::remove_file(&self.socket_path).unwrap();
|
||||
let _ = std::fs::remove_file(&self.socket_path);
|
||||
}
|
||||
}
|
||||
|
||||
enum ReceiveState {
|
||||
Len, Data,
|
||||
}
|
||||
|
||||
pub struct FrontendConnection {
|
||||
#[cfg(unix)]
|
||||
stream: UnixStream,
|
||||
#[cfg(windows)]
|
||||
stream: TcpStream,
|
||||
state: ReceiveState,
|
||||
len: usize,
|
||||
len_buf: [u8; std::mem::size_of::<usize>()],
|
||||
recieve_buf: [u8; 256], // FIXME
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl FrontendConnection {
|
||||
#[cfg(unix)]
|
||||
pub fn new(stream: UnixStream) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
state: ReceiveState::Len,
|
||||
len: 0,
|
||||
len_buf: [0u8; std::mem::size_of::<usize>()],
|
||||
recieve_buf: [0u8; 256],
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn new(stream: TcpStream) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
state: ReceiveState::Len,
|
||||
len: 0,
|
||||
len_buf: [0u8; std::mem::size_of::<usize>()],
|
||||
recieve_buf: [0u8; 256],
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self) -> Result<Option<FrontendEvent>> {
|
||||
match self.state {
|
||||
ReceiveState::Len => {
|
||||
// we receive sizeof(usize) Bytes
|
||||
let n = self.stream.read(&mut self.len_buf)?;
|
||||
self.pos += n;
|
||||
if self.pos == self.len_buf.len() {
|
||||
self.state = ReceiveState::Data;
|
||||
self.len = usize::from_ne_bytes(self.len_buf);
|
||||
self.pos = 0;
|
||||
}
|
||||
Ok(None)
|
||||
},
|
||||
ReceiveState::Data => {
|
||||
// read at most as many bytes as the length of the next event
|
||||
let n = self.stream.read(&mut self.recieve_buf[..self.len])?;
|
||||
self.pos += n;
|
||||
if n == self.len {
|
||||
self.state = ReceiveState::Len;
|
||||
self.pos = 0;
|
||||
Ok(Some(serde_json::from_slice(&self.recieve_buf[..self.len])?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use std::{thread::{self, JoinHandle}, io::Write};
|
||||
use anyhow::{anyhow, Result, Context};
|
||||
use std::{thread::{self, JoinHandle}, io::{Write, Read, ErrorKind}, str::SplitWhitespace};
|
||||
#[cfg(windows)]
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
@@ -10,90 +10,188 @@ use std::net::TcpStream;
|
||||
|
||||
use crate::{client::Position, config::DEFAULT_PORT};
|
||||
|
||||
use super::FrontendEvent;
|
||||
use super::{FrontendEvent, FrontendNotify};
|
||||
|
||||
pub fn start() -> Result<JoinHandle<()>> {
|
||||
pub fn start() -> Result<(JoinHandle<()>, JoinHandle<()>)> {
|
||||
#[cfg(unix)]
|
||||
let socket_path = Path::new(env::var("XDG_RUNTIME_DIR")?.as_str()).join("lan-mouse-socket.sock");
|
||||
Ok(thread::Builder::new()
|
||||
|
||||
#[cfg(unix)]
|
||||
let Ok(mut tx) = UnixStream::connect(&socket_path) else {
|
||||
return Err(anyhow!("Could not connect to lan-mouse-socket"));
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let Ok(mut tx) = TcpStream::connect("127.0.0.1:5252".parse::<SocketAddrV4>().unwrap()) else {
|
||||
return Err(anyhow!("Could not connect to lan-mouse-socket"));
|
||||
};
|
||||
|
||||
let mut rx = tx.try_clone()?;
|
||||
|
||||
let reader = thread::Builder::new()
|
||||
.name("cli-frontend".to_string())
|
||||
.spawn(move || {
|
||||
// all further prompts
|
||||
prompt();
|
||||
loop {
|
||||
eprint!("lan-mouse > ");
|
||||
std::io::stderr().flush().unwrap();
|
||||
let mut buf = String::new();
|
||||
match std::io::stdin().read_line(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(len) => {
|
||||
if let Some(event) = parse_cmd(buf, len) {
|
||||
#[cfg(unix)]
|
||||
let Ok(mut stream) = UnixStream::connect(&socket_path) else {
|
||||
log::error!("Could not connect to lan-mouse-socket");
|
||||
continue;
|
||||
};
|
||||
#[cfg(windows)]
|
||||
let Ok(mut stream) = TcpStream::connect("127.0.0.1:5252".parse::<SocketAddrV4>().unwrap()) else {
|
||||
log::error!("Could not connect to lan-mouse-server");
|
||||
continue;
|
||||
};
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
if let Err(e) = stream.write(json.as_bytes()) {
|
||||
log::error!("error sending message: {e}");
|
||||
};
|
||||
if event == FrontendEvent::Shutdown() {
|
||||
break;
|
||||
if let Some(events) = parse_cmd(buf, len) {
|
||||
for event in events.iter() {
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
let bytes = json.as_bytes();
|
||||
let len = bytes.len().to_ne_bytes();
|
||||
if let Err(e) = tx.write(&len) {
|
||||
log::error!("error sending message: {e}");
|
||||
};
|
||||
if let Err(e) = tx.write(bytes) {
|
||||
log::error!("error sending message: {e}");
|
||||
};
|
||||
if *event == FrontendEvent::Shutdown() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// prompt is printed after the server response is received
|
||||
} else {
|
||||
prompt();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
log::error!("error reading from stdin: {e}");
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})?)
|
||||
})?;
|
||||
|
||||
let writer = thread::Builder::new()
|
||||
.name("cli-frontend-notify".to_string())
|
||||
.spawn(move || {
|
||||
loop {
|
||||
// read len
|
||||
let mut len = [0u8; 8];
|
||||
match rx.read_exact(&mut len) {
|
||||
Ok(()) => (),
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
|
||||
Err(e) => break log::error!("{e}"),
|
||||
};
|
||||
let len = usize::from_ne_bytes(len);
|
||||
|
||||
// read payload
|
||||
let mut buf: Vec<u8> = vec![0u8; len];
|
||||
match rx.read_exact(&mut buf[..len]) {
|
||||
Ok(()) => (),
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
|
||||
Err(e) => break log::error!("{e}"),
|
||||
};
|
||||
|
||||
let notify: FrontendNotify = match serde_json::from_slice(&buf) {
|
||||
Ok(n) => n,
|
||||
Err(e) => break log::error!("{e}"),
|
||||
};
|
||||
match notify {
|
||||
FrontendNotify::NotifyClientCreate(client, host, port, pos) => {
|
||||
log::info!("new client ({client}): {}:{port} - {pos}", host.as_deref().unwrap_or(""));
|
||||
},
|
||||
FrontendNotify::NotifyClientUpdate(client, host, port, pos) => {
|
||||
log::info!("client ({client}) updated: {}:{port} - {pos}", host.as_deref().unwrap_or(""));
|
||||
},
|
||||
FrontendNotify::NotifyClientDelete(client) => {
|
||||
log::info!("client ({client}) deleted.");
|
||||
},
|
||||
FrontendNotify::NotifyError(e) => {
|
||||
log::warn!("{e}");
|
||||
},
|
||||
FrontendNotify::Enumerate(clients) => {
|
||||
for (client, active) in clients.into_iter() {
|
||||
log::info!("client ({}) [{}]: active: {}, associated addresses: [{}]",
|
||||
client.handle,
|
||||
client.hostname.as_deref().unwrap_or(""),
|
||||
if active { "yes" } else { "no" },
|
||||
client.addrs.into_iter().map(|a| a.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
prompt();
|
||||
}
|
||||
})?;
|
||||
Ok((reader, writer))
|
||||
}
|
||||
|
||||
fn parse_cmd(s: String, len: usize) -> Option<FrontendEvent> {
|
||||
fn prompt() {
|
||||
eprint!("lan-mouse > ");
|
||||
std::io::stderr().flush().unwrap();
|
||||
}
|
||||
|
||||
fn parse_cmd(s: String, len: usize) -> Option<Vec<FrontendEvent>> {
|
||||
if len == 0 {
|
||||
return Some(FrontendEvent::Shutdown())
|
||||
return Some(vec![FrontendEvent::Shutdown()])
|
||||
}
|
||||
let mut l = s.split_whitespace();
|
||||
let cmd = l.next()?;
|
||||
match cmd {
|
||||
"connect" => {
|
||||
let host = l.next()?.to_owned();
|
||||
let pos = match l.next()? {
|
||||
"right" => Position::Right,
|
||||
"top" => Position::Top,
|
||||
"bottom" => Position::Bottom,
|
||||
_ => Position::Left,
|
||||
};
|
||||
let port = match l.next() {
|
||||
Some(p) => match p.parse() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => DEFAULT_PORT,
|
||||
};
|
||||
Some(FrontendEvent::AddClient(host, port, pos))
|
||||
}
|
||||
"disconnect" => {
|
||||
let host = l.next()?.to_owned();
|
||||
let port = match l.next()?.parse() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(FrontendEvent::DelClient(host, port))
|
||||
let res = match cmd {
|
||||
"help" => {
|
||||
log::info!("list list clients");
|
||||
log::info!("connect <host> left|right|top|bottom [port] add a new client");
|
||||
log::info!("disconnect <client> remove a client");
|
||||
log::info!("activate <client> activate a client");
|
||||
log::info!("deactivate <client> deactivate a client");
|
||||
log::info!("exit exit lan-mouse");
|
||||
None
|
||||
}
|
||||
"exit" => return Some(vec![FrontendEvent::Shutdown()]),
|
||||
"list" => return Some(vec![FrontendEvent::Enumerate()]),
|
||||
"connect" => Some(parse_connect(l)),
|
||||
"disconnect" => Some(parse_disconnect(l)),
|
||||
"activate" => Some(parse_activate(l)),
|
||||
"deactivate" => Some(parse_deactivate(l)),
|
||||
_ => {
|
||||
log::error!("unknown command: {s}");
|
||||
None
|
||||
}
|
||||
};
|
||||
match res {
|
||||
Some(Ok(e)) => Some(vec![e, FrontendEvent::Enumerate()]),
|
||||
Some(Err(e)) => {
|
||||
log::warn!("{e}");
|
||||
None
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_connect(mut l: SplitWhitespace) -> Result<FrontendEvent> {
|
||||
let usage = "usage: connect <host> left|right|top|bottom [port]";
|
||||
let host = l.next().context(usage)?.to_owned();
|
||||
let pos = match l.next().context(usage)? {
|
||||
"right" => Position::Right,
|
||||
"top" => Position::Top,
|
||||
"bottom" => Position::Bottom,
|
||||
_ => Position::Left,
|
||||
};
|
||||
let port = if let Some(p) = l.next() {
|
||||
p.parse()?
|
||||
} else {
|
||||
DEFAULT_PORT
|
||||
};
|
||||
Ok(FrontendEvent::AddClient(Some(host), port, pos))
|
||||
}
|
||||
|
||||
fn parse_disconnect(mut l: SplitWhitespace) -> Result<FrontendEvent> {
|
||||
let client = l.next().context("usage: disconnect <client_id>")?.parse()?;
|
||||
Ok(FrontendEvent::DelClient(client))
|
||||
}
|
||||
|
||||
fn parse_activate(mut l: SplitWhitespace) -> Result<FrontendEvent> {
|
||||
let client = l.next().context("usage: activate <client_id>")?.parse()?;
|
||||
Ok(FrontendEvent::ActivateClient(client, true))
|
||||
}
|
||||
fn parse_deactivate(mut l: SplitWhitespace) -> Result<FrontendEvent> {
|
||||
let client = l.next().context("usage: deactivate <client_id>")?.parse()?;
|
||||
Ok(FrontendEvent::ActivateClient(client, false))
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@ mod window;
|
||||
mod client_object;
|
||||
mod client_row;
|
||||
|
||||
use std::{io::Result, thread::{self, JoinHandle}};
|
||||
use std::{io::{Result, Read, ErrorKind}, thread::{self, JoinHandle}, env, process, path::Path, os::unix::net::UnixStream, str};
|
||||
|
||||
use crate::frontend::gtk::window::Window;
|
||||
use crate::{frontend::gtk::window::Window, config::DEFAULT_PORT};
|
||||
|
||||
use gtk::{prelude::*, IconTheme, gdk::Display, gio::{SimpleAction, SimpleActionGroup}, glib::clone, CssProvider};
|
||||
use gtk::{prelude::*, IconTheme, gdk::Display, gio::{SimpleAction, SimpleActionGroup}, glib::{clone, MainContext, Priority}, CssProvider, subclass::prelude::ObjectSubclassIsExt};
|
||||
use adw::Application;
|
||||
use gtk::{gio, glib, prelude::ApplicationExt};
|
||||
|
||||
use self::client_object::ClientObject;
|
||||
|
||||
use super::FrontendNotify;
|
||||
|
||||
pub fn start() -> Result<JoinHandle<glib::ExitCode>> {
|
||||
thread::Builder::new()
|
||||
.name("gtk-thread".into())
|
||||
@@ -49,45 +51,137 @@ fn load_icons() {
|
||||
}
|
||||
|
||||
fn build_ui(app: &Application) {
|
||||
let xdg_runtime_dir = match env::var("XDG_RUNTIME_DIR") {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let socket_path = Path::new(xdg_runtime_dir.as_str())
|
||||
.join("lan-mouse-socket.sock");
|
||||
let Ok(mut rx) = UnixStream::connect(&socket_path) else {
|
||||
log::error!("Could not connect to lan-mouse-socket @ {socket_path:?}");
|
||||
process::exit(1);
|
||||
};
|
||||
let tx = match rx.try_clone() {
|
||||
Ok(sock) => sock,
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let (sender, receiver) = MainContext::channel::<FrontendNotify>(Priority::default());
|
||||
|
||||
gio::spawn_blocking(move || {
|
||||
match loop {
|
||||
// read length
|
||||
let mut len = [0u8; 8];
|
||||
match rx.read_exact(&mut len) {
|
||||
Ok(_) => (),
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
|
||||
Err(e) => break Err(e),
|
||||
};
|
||||
let len = usize::from_ne_bytes(len);
|
||||
|
||||
// read payload
|
||||
let mut buf = vec![0u8; len];
|
||||
match rx.read_exact(&mut buf) {
|
||||
Ok(_) => (),
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
|
||||
Err(e) => break Err(e),
|
||||
};
|
||||
|
||||
// parse json
|
||||
let json = str::from_utf8(&buf)
|
||||
.unwrap();
|
||||
match serde_json::from_str(json) {
|
||||
Ok(notify) => sender.send(notify).unwrap(),
|
||||
Err(e) => log::error!("{e}"),
|
||||
}
|
||||
} {
|
||||
Ok(()) => {},
|
||||
Err(e) => log::error!("{e}"),
|
||||
}
|
||||
});
|
||||
|
||||
let window = Window::new(app);
|
||||
let action_client_activate = SimpleAction::new(
|
||||
"activate-client",
|
||||
Some(&i32::static_variant_type()),
|
||||
window.imp().stream.borrow_mut().replace(tx);
|
||||
receiver.attach(None, clone!(@weak window => @default-return glib::ControlFlow::Break,
|
||||
move |notify| {
|
||||
match notify {
|
||||
FrontendNotify::NotifyClientCreate(client, hostname, port, position) => {
|
||||
window.new_client(client, hostname, port, position, false);
|
||||
},
|
||||
FrontendNotify::NotifyClientUpdate(client, hostname, port, position) => {
|
||||
log::info!("client updated: {client}, {}:{port}, {position}", hostname.unwrap_or("".to_string()));
|
||||
}
|
||||
FrontendNotify::NotifyError(e) => {
|
||||
// TODO
|
||||
log::error!("{e}");
|
||||
},
|
||||
FrontendNotify::NotifyClientDelete(client) => {
|
||||
window.delete_client(client);
|
||||
}
|
||||
FrontendNotify::Enumerate(clients) => {
|
||||
for (client, active) in clients {
|
||||
if window.client_idx(client.handle).is_some() {
|
||||
continue
|
||||
}
|
||||
window.new_client(
|
||||
client.handle,
|
||||
client.hostname,
|
||||
client.addrs
|
||||
.iter()
|
||||
.next()
|
||||
.map(|s| s.port())
|
||||
.unwrap_or(DEFAULT_PORT),
|
||||
client.pos,
|
||||
active,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
glib::ControlFlow::Continue
|
||||
}
|
||||
));
|
||||
|
||||
let action_request_client_update = SimpleAction::new(
|
||||
"request-client-update",
|
||||
Some(&u32::static_variant_type()),
|
||||
);
|
||||
|
||||
// remove client
|
||||
let action_client_delete = SimpleAction::new(
|
||||
"delete-client",
|
||||
Some(&i32::static_variant_type()),
|
||||
"request-client-delete",
|
||||
Some(&u32::static_variant_type()),
|
||||
);
|
||||
action_client_activate.connect_activate(clone!(@weak window => move |_action, param| {
|
||||
log::debug!("activate-client");
|
||||
|
||||
// update client state
|
||||
action_request_client_update.connect_activate(clone!(@weak window => move |_action, param| {
|
||||
log::debug!("request-client-update");
|
||||
let index = param.unwrap()
|
||||
.get::<i32>()
|
||||
.get::<u32>()
|
||||
.unwrap();
|
||||
let Some(client) = window.clients().item(index as u32) else {
|
||||
return;
|
||||
};
|
||||
let client = client.downcast_ref::<ClientObject>().unwrap();
|
||||
window.update_client(client);
|
||||
window.request_client_update(client);
|
||||
}));
|
||||
|
||||
action_client_delete.connect_activate(clone!(@weak window => move |_action, param| {
|
||||
log::debug!("delete-client");
|
||||
let index = param.unwrap()
|
||||
.get::<i32>()
|
||||
let idx = param.unwrap()
|
||||
.get::<u32>()
|
||||
.unwrap();
|
||||
let Some(client) = window.clients().item(index as u32) else {
|
||||
return;
|
||||
};
|
||||
let client = client.downcast_ref::<ClientObject>().unwrap();
|
||||
window.update_client(client);
|
||||
window.clients().remove(index as u32);
|
||||
if window.clients().n_items() == 0 {
|
||||
window.set_placeholder_visible(true);
|
||||
}
|
||||
window.request_client_delete(idx);
|
||||
}));
|
||||
|
||||
let actions = SimpleActionGroup::new();
|
||||
window.insert_action_group("win", Some(&actions));
|
||||
actions.add_action(&action_client_activate);
|
||||
actions.add_action(&action_request_client_update);
|
||||
actions.add_action(&action_client_delete);
|
||||
window.present();
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@ mod imp;
|
||||
use gtk::glib::{self, Object};
|
||||
use adw::subclass::prelude::*;
|
||||
|
||||
use crate::client::ClientHandle;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ClientObject(ObjectSubclass<imp::ClientObject>);
|
||||
}
|
||||
|
||||
impl ClientObject {
|
||||
pub fn new(hostname: String, port: u32, active: bool, position: String) -> Self {
|
||||
pub fn new(handle: ClientHandle, hostname: Option<String>, port: u32, position: String, active: bool) -> Self {
|
||||
Object::builder()
|
||||
.property("handle", handle)
|
||||
.property("hostname", hostname)
|
||||
.property("port", port)
|
||||
.property("active", active)
|
||||
@@ -24,7 +27,8 @@ impl ClientObject {
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ClientData {
|
||||
pub hostname: String,
|
||||
pub handle: ClientHandle,
|
||||
pub hostname: Option<String>,
|
||||
pub port: u32,
|
||||
pub active: bool,
|
||||
pub position: String,
|
||||
|
||||
@@ -5,11 +5,14 @@ use gtk::glib;
|
||||
use gtk::prelude::*;
|
||||
use gtk::subclass::prelude::*;
|
||||
|
||||
use crate::client::ClientHandle;
|
||||
|
||||
use super::ClientData;
|
||||
|
||||
#[derive(Properties, Default)]
|
||||
#[properties(wrapper_type = super::ClientObject)]
|
||||
pub struct ClientObject {
|
||||
#[property(name = "handle", get, set, type = ClientHandle, member = handle)]
|
||||
#[property(name = "hostname", get, set, type = String, member = hostname)]
|
||||
#[property(name = "port", get, set, type = u32, member = port, maximum = u16::MAX as u32)]
|
||||
#[property(name = "active", get, set, type = bool, member = active)]
|
||||
|
||||
@@ -31,8 +31,19 @@ impl ClientRow {
|
||||
|
||||
let hostname_binding = client_object
|
||||
.bind_property("hostname", &self.imp().hostname.get(), "text")
|
||||
.transform_to(|_, v: Option<String>| {
|
||||
if let Some(hostname) = v {
|
||||
Some(hostname)
|
||||
} else {
|
||||
Some("".to_string())
|
||||
}
|
||||
})
|
||||
.transform_from(|_, v: String| {
|
||||
if v == "" { Some("hostname".into()) } else { Some(v) }
|
||||
if v.as_str().trim() == "" {
|
||||
Some(None)
|
||||
} else {
|
||||
Some(Some(v))
|
||||
}
|
||||
})
|
||||
.bidirectional()
|
||||
.sync_create()
|
||||
@@ -40,18 +51,34 @@ impl ClientRow {
|
||||
|
||||
let title_binding = client_object
|
||||
.bind_property("hostname", self, "title")
|
||||
.transform_to(|_, v: Option<String>| {
|
||||
if let Some(hostname) = v {
|
||||
Some(hostname)
|
||||
} else {
|
||||
Some("<span font_style=\"italic\" font_weight=\"light\" foreground=\"darkgrey\">no hostname!</span>".to_string())
|
||||
}
|
||||
})
|
||||
.sync_create()
|
||||
.build();
|
||||
|
||||
let port_binding = client_object
|
||||
.bind_property("port", &self.imp().port.get(), "text")
|
||||
.transform_from(|_, v: String| {
|
||||
if v == "" {
|
||||
Some(4242)
|
||||
Some(DEFAULT_PORT as u32)
|
||||
} else {
|
||||
Some(v.parse::<u16>().unwrap_or(DEFAULT_PORT) as u32)
|
||||
}
|
||||
})
|
||||
.transform_to(|_, v: u32| {
|
||||
if v == 4242 {
|
||||
Some("".to_string())
|
||||
} else {
|
||||
Some(v.to_string())
|
||||
}
|
||||
})
|
||||
.bidirectional()
|
||||
.sync_create()
|
||||
.build();
|
||||
|
||||
let subtitle_binding = client_object
|
||||
|
||||
@@ -54,8 +54,8 @@ impl ObjectImpl for ClientRow {
|
||||
impl ClientRow {
|
||||
#[template_callback]
|
||||
fn handle_client_set_state(&self, state: bool, switch: &Switch) -> bool {
|
||||
let idx = self.obj().index();
|
||||
switch.activate_action("win.activate-client", Some(&idx.to_variant())).unwrap();
|
||||
let idx = self.obj().index() as u32;
|
||||
switch.activate_action("win.request-client-update", Some(&idx.to_variant())).unwrap();
|
||||
switch.set_state(state);
|
||||
|
||||
true // dont run default handler
|
||||
@@ -64,8 +64,10 @@ impl ClientRow {
|
||||
#[template_callback]
|
||||
fn handle_client_delete(&self, button: &Button) {
|
||||
log::debug!("delete button pressed");
|
||||
let idx = self.obj().index();
|
||||
button.activate_action("win.delete-client", Some(&idx.to_variant())).unwrap();
|
||||
let idx = self.obj().index() as u32;
|
||||
button
|
||||
.activate_action("win.request-client-delete", Some(&idx.to_variant()))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
mod imp;
|
||||
|
||||
use std::{path::{Path, PathBuf}, env, process, os::unix::net::UnixStream, io::Write};
|
||||
use std::io::Write;
|
||||
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::{glib, gio, NoSelection};
|
||||
use glib::{clone, Object};
|
||||
|
||||
use crate::{frontend::{gtk::client_object::ClientObject, FrontendEvent}, config::DEFAULT_PORT, client::Position};
|
||||
use crate::{frontend::{gtk::client_object::ClientObject, FrontendEvent}, client::{Position, ClientHandle}, config::DEFAULT_PORT};
|
||||
|
||||
use super::client_row::ClientRow;
|
||||
|
||||
@@ -67,16 +67,44 @@ impl Window {
|
||||
row
|
||||
}
|
||||
|
||||
fn new_client(&self) {
|
||||
let client = ClientObject::new(String::from(""), DEFAULT_PORT as u32, false, "left".into());
|
||||
pub fn new_client(&self, handle: ClientHandle, hostname: Option<String>, port: u16, position: Position, active: bool) {
|
||||
let client = ClientObject::new(handle, hostname, port as u32, position.to_string(), active);
|
||||
self.clients().append(&client);
|
||||
self.set_placeholder_visible(false);
|
||||
}
|
||||
|
||||
pub fn update_client(&self, client: &ClientObject) {
|
||||
pub fn client_idx(&self, handle: ClientHandle) -> Option<usize> {
|
||||
self.clients()
|
||||
.iter::<ClientObject>()
|
||||
.position(|c| {
|
||||
if let Ok(c) = c {
|
||||
c.handle() == handle
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|p| p as usize)
|
||||
}
|
||||
|
||||
pub fn delete_client(&self, handle: ClientHandle) {
|
||||
let Some(idx) = self.client_idx(handle) else {
|
||||
log::warn!("could not find client with handle {handle}");
|
||||
return;
|
||||
};
|
||||
|
||||
self.clients().remove(idx as u32);
|
||||
if self.clients().n_items() == 0 {
|
||||
self.set_placeholder_visible(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_client_create(&self) {
|
||||
let event = FrontendEvent::AddClient(None, DEFAULT_PORT, Position::default());
|
||||
self.request(event);
|
||||
}
|
||||
|
||||
pub fn request_client_update(&self, client: &ClientObject) {
|
||||
let data = client.get_data();
|
||||
let socket_path = self.imp().socket_path.borrow();
|
||||
let socket_path = socket_path.as_ref().unwrap().as_path();
|
||||
let host_name = data.hostname;
|
||||
let position = match data.position.as_str() {
|
||||
"left" => Position::Left,
|
||||
"right" => Position::Right,
|
||||
@@ -87,18 +115,37 @@ impl Window {
|
||||
return
|
||||
}
|
||||
};
|
||||
let port = data.port;
|
||||
let event = if client.active() {
|
||||
FrontendEvent::DelClient(host_name, port as u16)
|
||||
} else {
|
||||
FrontendEvent::AddClient(host_name, port as u16, position)
|
||||
};
|
||||
let hostname = data.hostname;
|
||||
let port = data.port as u16;
|
||||
let event = FrontendEvent::UpdateClient(client.handle(), hostname, port, position);
|
||||
self.request(event);
|
||||
|
||||
let event = FrontendEvent::ActivateClient(client.handle(), !client.active());
|
||||
self.request(event);
|
||||
}
|
||||
|
||||
pub fn request_client_delete(&self, idx: u32) {
|
||||
if let Some(obj) = self.clients().item(idx) {
|
||||
let client_object: &ClientObject = obj
|
||||
.downcast_ref()
|
||||
.expect("Expected object of type `ClientObject`.");
|
||||
let handle = client_object.handle();
|
||||
let event = FrontendEvent::DelClient(handle);
|
||||
self.request(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn request(&self, event: FrontendEvent) {
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
let Ok(mut stream) = UnixStream::connect(socket_path) else {
|
||||
log::error!("Could not connect to lan-mouse-socket @ {socket_path:?}");
|
||||
return;
|
||||
log::debug!("requesting {json}");
|
||||
let mut stream = self.imp().stream.borrow_mut();
|
||||
let stream = stream.as_mut().unwrap();
|
||||
let bytes = json.as_bytes();
|
||||
let len = bytes.len().to_ne_bytes();
|
||||
if let Err(e) = stream.write(&len) {
|
||||
log::error!("error sending message: {e}");
|
||||
};
|
||||
if let Err(e) = stream.write(json.as_bytes()) {
|
||||
if let Err(e) = stream.write(bytes) {
|
||||
log::error!("error sending message: {e}");
|
||||
};
|
||||
}
|
||||
@@ -107,21 +154,7 @@ impl Window {
|
||||
self.imp()
|
||||
.add_client_button
|
||||
.connect_clicked(clone!(@weak self as window => move |_| {
|
||||
window.new_client();
|
||||
window.set_placeholder_visible(false);
|
||||
window.request_client_create();
|
||||
}));
|
||||
}
|
||||
|
||||
fn connect_stream(&self) {
|
||||
let xdg_runtime_dir = match env::var("XDG_RUNTIME_DIR") {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let socket_path = Path::new(xdg_runtime_dir.as_str())
|
||||
.join("lan-mouse-socket.sock");
|
||||
self.imp().socket_path.borrow_mut().replace(PathBuf::from(socket_path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{cell::{Cell, RefCell}, path::PathBuf};
|
||||
use std::{cell::{Cell, RefCell}, os::unix::net::UnixStream};
|
||||
|
||||
use glib::subclass::InitializingObject;
|
||||
use adw::{prelude::*, ActionRow};
|
||||
@@ -16,7 +16,7 @@ pub struct Window {
|
||||
#[template_child]
|
||||
pub client_placeholder: TemplateChild<ActionRow>,
|
||||
pub clients: RefCell<Option<gio::ListStore>>,
|
||||
pub socket_path: RefCell<Option<PathBuf>>,
|
||||
pub stream: RefCell<Option<UnixStream>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
@@ -54,7 +54,6 @@ impl ObjectImpl for Window {
|
||||
obj.setup_icon();
|
||||
obj.setup_clients();
|
||||
obj.setup_callbacks();
|
||||
obj.connect_stream();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
src/main.rs
26
src/main.rs
@@ -4,7 +4,7 @@ use env_logger::Env;
|
||||
use lan_mouse::{
|
||||
consumer, producer,
|
||||
config::{Config, Frontend::{Cli, Gtk}}, event::server::Server,
|
||||
frontend::{FrontendAdapter, cli},
|
||||
frontend::{FrontendListener, cli},
|
||||
};
|
||||
|
||||
#[cfg(all(unix, feature = "gtk"))]
|
||||
@@ -31,7 +31,7 @@ pub fn run() -> Result<(), Box<dyn Error>> {
|
||||
let consumer = consumer::create()?;
|
||||
|
||||
// create frontend communication adapter
|
||||
let frontend_adapter = FrontendAdapter::new()?;
|
||||
let frontend_adapter = FrontendListener::new()?;
|
||||
|
||||
// start sending and receiving events
|
||||
let mut event_server = Server::new(config.port, producer, consumer, frontend_adapter)?;
|
||||
@@ -45,24 +45,10 @@ pub fn run() -> Result<(), Box<dyn Error>> {
|
||||
Cli => { cli::start()?; }
|
||||
};
|
||||
|
||||
// this currently causes issues, because the clients from
|
||||
// the config arent communicated to gtk yet.
|
||||
if config.frontend == Gtk {
|
||||
log::warn!("clients defined in config currently have no effect with the gtk frontend");
|
||||
} else {
|
||||
// add clients from config
|
||||
config.get_clients().into_iter().for_each(|(c, h, p)| {
|
||||
let host_name = match h {
|
||||
Some(h) => format!(" '{}'", h),
|
||||
None => "".to_owned(),
|
||||
};
|
||||
if c.len() == 0 {
|
||||
log::warn!("ignoring client{} with 0 assigned ips!", host_name);
|
||||
}
|
||||
log::info!("adding client [{}]{} @ {:?}", p, host_name, c);
|
||||
event_server.add_client(c, p);
|
||||
});
|
||||
}
|
||||
// add clients from config
|
||||
config.get_clients().into_iter().for_each(|(c, h, port, p)| {
|
||||
event_server.add_client(h, c, port, p);
|
||||
});
|
||||
|
||||
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
|
||||
// run event loop
|
||||
|
||||
Reference in New Issue
Block a user