Frontend improvement (#27)

* removed redundant dns lookups
* frontend now correctly reflects the state of the backend
* config.toml is loaded when starting gtk frontend
This commit is contained in:
Ferdinand Schober
2023-09-25 11:55:22 +02:00
committed by Ferdinand Schober
parent 603646c799
commit 06725f4b14
17 changed files with 908 additions and 432 deletions

View File

@@ -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(&notify).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);
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)
}
}
}
}
}