Compare commits

...

17 Commits

Author SHA1 Message Date
Ferdinand Schober
81e2c59e8f fix sizeof usize assumed to be 8
closes #141
2024-06-09 00:08:24 +02:00
虢豳
5fd3b719d6 Extract package name and version from Cargo.toml (#136)
* chore: nix flake update

* feat: Extract package name and version from Cargo.toml
2024-05-22 08:06:44 +02:00
Ferdinand Schober
e6d4585bb2 chore: Release lan-mouse version 0.8.0 2024-05-17 18:06:11 +02:00
Ferdinand Schober
1c082d5c0c fix win -> lin scancode translations for intl keys 2024-05-17 17:39:52 +02:00
Ferdinand Schober
cd98acbd08 fix right shift not working 2024-05-16 22:36:51 +02:00
Ferdinand Schober
11e1919588 fix most international keybindings 2024-05-16 22:29:41 +02:00
Ferdinand Schober
152bceaa86 fix dns resolving 2024-05-13 08:12:55 +02:00
Ferdinand Schober
60180d841c fix formatting 2024-05-12 15:50:03 +02:00
Ferdinand Schober
5802a0be0b fix discrete scrolling on wlroots 2024-05-12 15:49:39 +02:00
Ferdinand Schober
1737727d61 fix cast 2024-05-12 15:07:29 +02:00
Ferdinand Schober
ba46037a1f fix scrolling in windows 2024-05-12 14:55:56 +02:00
Ferdinand Schober
da768b6fb8 ignore Axis events corresponding to v120 events 2024-05-12 13:32:14 +02:00
Ferdinand Schober
799b45104a enter hook command (#130)
new configuration option `enter_hook` can now be used to spawn a command when a client is entered
2024-05-12 13:01:07 +02:00
Ferdinand Schober
e9738fc024 explicit state synchronisation (#129)
prevents unnecessary state updates and makes synchronization less error prone
2024-05-12 00:49:53 +02:00
Ferdinand Schober
9969f997d3 macos-latest on longer runs on intel macs 2024-05-07 11:46:01 +02:00
Ferdinand Schober
b8cc9e2197 hotfix: race condition when activating clients 2024-05-07 11:18:15 +02:00
Ferdinand Schober
ba6abafe75 windows: fix resolution with scaling enabled (#124) 2024-05-07 00:07:30 +02:00
22 changed files with 288 additions and 261 deletions

View File

@@ -7,7 +7,7 @@ jobs:
matrix: matrix:
os: os:
- ubuntu-latest - ubuntu-latest
- macos-latest - macos-13
- macos-14 - macos-14
name: "Build" name: "Build"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -31,7 +31,7 @@ jobs:
run: nix build --print-build-logs --show-trace .#packages.x86_64-linux.lan-mouse run: nix build --print-build-logs --show-trace .#packages.x86_64-linux.lan-mouse
- name: Build lan-mouse (x86_64-darwin) - name: Build lan-mouse (x86_64-darwin)
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-13'
run: nix build --print-build-logs --show-trace .#packages.x86_64-darwin.lan-mouse run: nix build --print-build-logs --show-trace .#packages.x86_64-darwin.lan-mouse
- name: Build lan-mouse (aarch64-darwin) - name: Build lan-mouse (aarch64-darwin)

View File

@@ -78,7 +78,7 @@ jobs:
path: lan-mouse-windows.zip path: lan-mouse-windows.zip
macos-release-build: macos-release-build:
runs-on: macos-latest runs-on: macos-13
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: install dependencies - name: install dependencies

View File

@@ -92,7 +92,7 @@ jobs:
target/debug/*.dll target/debug/*.dll
build-macos: build-macos:
runs-on: macos-latest runs-on: macos-13
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: install dependencies - name: install dependencies

View File

@@ -74,7 +74,7 @@ jobs:
path: lan-mouse-windows.zip path: lan-mouse-windows.zip
macos-release-build: macos-release-build:
runs-on: macos-latest runs-on: macos-13
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: install dependencies - name: install dependencies

3
Cargo.lock generated
View File

@@ -1284,7 +1284,7 @@ dependencies = [
[[package]] [[package]]
name = "lan-mouse" name = "lan-mouse"
version = "0.7.3" version = "0.8.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ashpd", "ashpd",
@@ -1292,6 +1292,7 @@ dependencies = [
"async-trait", "async-trait",
"clap", "clap",
"core-graphics", "core-graphics",
"endi",
"env_logger", "env_logger",
"futures", "futures",
"futures-core", "futures-core",

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "lan-mouse" name = "lan-mouse"
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks" description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
version = "0.7.3" version = "0.8.0"
edition = "2021" edition = "2021"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
repository = "https://github.com/ferdinandschober/lan-mouse" repository = "https://github.com/ferdinandschober/lan-mouse"
@@ -22,7 +22,7 @@ anyhow = "1.0.71"
log = "0.4.20" log = "0.4.20"
env_logger = "0.11.3" env_logger = "0.11.3"
serde_json = "1.0.107" serde_json = "1.0.107"
tokio = {version = "1.32.0", features = ["io-util", "io-std", "macros", "net", "rt", "sync", "signal"] } tokio = {version = "1.32.0", features = ["io-util", "io-std", "macros", "net", "process", "rt", "sync", "signal"] }
async-trait = "0.1.73" async-trait = "0.1.73"
futures-core = "0.3.28" futures-core = "0.3.28"
futures = "0.3.28" futures = "0.3.28"
@@ -35,6 +35,7 @@ once_cell = "1.19.0"
num_enum = "0.7.2" num_enum = "0.7.2"
hostname = "0.4.0" hostname = "0.4.0"
slab = "0.4.9" slab = "0.4.9"
endi = "1.1.0"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2.148" libc = "0.2.148"

12
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1710806803, "lastModified": 1716293225,
"narHash": "sha256-qrxvLS888pNJFwJdK+hf1wpRCSQcqA6W5+Ox202NDa0=", "narHash": "sha256-pU9ViBVE3XYb70xZx+jK6SEVphvt7xMTbm6yDIF4xPs=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b06025f1533a1e07b6db3e75151caa155d1c7eb3", "rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -48,11 +48,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1710987136, "lastModified": 1716257780,
"narHash": "sha256-Q8GRdlAIKZ8tJUXrbcRO1pA33AdoPfTUirsSnmGQnOU=", "narHash": "sha256-R+NjvJzKEkTVCmdrKRfPE4liX/KMGVqGUwwS5H8ET8A=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "97596b54ac34ad8184ca1eef44b1ec2e5c2b5f9e", "rev": "4e5e3d2c5c9b2721bd266f9e43c14e96811b89d2",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -2,10 +2,14 @@
rustPlatform, rustPlatform,
lib, lib,
pkgs, pkgs,
}: }: let
cargoToml = builtins.fromTOML (builtins.readFile ../Cargo.toml);
pname = cargoToml.package.name;
version = cargoToml.package.version;
in
rustPlatform.buildRustPackage { rustPlatform.buildRustPackage {
pname = "lan-mouse"; pname = pname;
version = "0.7.0"; version = version;
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
pkg-config pkg-config
@@ -23,7 +27,7 @@ rustPlatform.buildRustPackage {
]; ];
src = builtins.path { src = builtins.path {
name = "lan-mouse"; name = pname;
path = lib.cleanSource ../.; path = lib.cleanSource ../.;
}; };
@@ -38,7 +42,7 @@ rustPlatform.buildRustPackage {
Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple pcs with a single set of mouse and keyboard. This is also known as a Software KVM switch. Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple pcs with a single set of mouse and keyboard. This is also known as a Software KVM switch.
The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details). The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details).
''; '';
mainProgram = "lan-mouse"; mainProgram = pname;
platforms = platforms.all; platforms = platforms.all;
}; };
} }

View File

@@ -111,6 +111,7 @@ struct State {
qh: QueueHandle<Self>, qh: QueueHandle<Self>,
pending_events: VecDeque<(ClientHandle, Event)>, pending_events: VecDeque<(ClientHandle, Event)>,
output_info: Vec<(WlOutput, OutputInfo)>, output_info: Vec<(WlOutput, OutputInfo)>,
scroll_discrete_pending: bool,
} }
struct Inner { struct Inner {
@@ -351,6 +352,7 @@ impl WaylandInputCapture {
read_guard: None, read_guard: None,
pending_events: VecDeque::new(), pending_events: VecDeque::new(),
output_info: vec![], output_info: vec![],
scroll_discrete_pending: false,
}; };
// dispatch registry to () again, in order to read all wl_outputs // dispatch registry to () again, in order to read all wl_outputs
@@ -752,17 +754,25 @@ impl Dispatch<WlPointer, ()> for State {
} }
wl_pointer::Event::Axis { time, axis, value } => { wl_pointer::Event::Axis { time, axis, value } => {
let (_, client) = app.focused.as_ref().unwrap(); let (_, client) = app.focused.as_ref().unwrap();
app.pending_events.push_back(( if app.scroll_discrete_pending {
*client, // each axisvalue120 event is coupled with
Event::Pointer(PointerEvent::Axis { // a corresponding axis event, which needs to
time, // be ignored to not duplicate the scrolling
axis: u32::from(axis) as u8, app.scroll_discrete_pending = false;
value, } else {
}), app.pending_events.push_back((
)); *client,
Event::Pointer(PointerEvent::Axis {
time,
axis: u32::from(axis) as u8,
value,
}),
));
}
} }
wl_pointer::Event::AxisValue120 { axis, value120 } => { wl_pointer::Event::AxisValue120 { axis, value120 } => {
let (_, client) = app.focused.as_ref().unwrap(); let (_, client) = app.focused.as_ref().unwrap();
app.scroll_discrete_pending = true;
app.pending_events.push_back(( app.pending_events.push_back((
*client, *client,
Event::Pointer(PointerEvent::AxisDiscrete120 { Event::Pointer(PointerEvent::AxisDiscrete120 {

View File

@@ -13,23 +13,23 @@ use std::sync::mpsc;
use std::task::ready; use std::task::ready;
use std::{io, pin::Pin, thread}; use std::{io, pin::Pin, thread};
use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::mpsc::{channel, Receiver, Sender};
use windows::core::w; use windows::core::{w, PCWSTR};
use windows::Win32::Foundation::{ use windows::Win32::Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, RECT, WPARAM};
BOOL, FALSE, HINSTANCE, HWND, LPARAM, LRESULT, RECT, TRUE, WPARAM,
};
use windows::Win32::Graphics::Gdi::{ use windows::Win32::Graphics::Gdi::{
EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITORINFO, EnumDisplayDevicesW, EnumDisplaySettingsW, DEVMODEW, DISPLAY_DEVICEW,
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, ENUM_CURRENT_SETTINGS,
}; };
use windows::Win32::System::LibraryLoader::GetModuleHandleW; use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::Threading::GetCurrentThreadId; use windows::Win32::System::Threading::GetCurrentThreadId;
use windows::Win32::UI::WindowsAndMessaging::{ use windows::Win32::UI::WindowsAndMessaging::{
CallNextHookEx, CreateWindowExW, DispatchMessageW, GetMessageW, PostThreadMessageW, CallNextHookEx, CreateWindowExW, DispatchMessageW, GetMessageW, PostThreadMessageW,
RegisterClassW, SetWindowsHookExW, TranslateMessage, HHOOK, HMENU, HOOKPROC, KBDLLHOOKSTRUCT, RegisterClassW, SetWindowsHookExW, TranslateMessage, EDD_GET_DEVICE_INTERFACE_NAME, HHOOK,
LLKHF_EXTENDED, MSG, MSLLHOOKSTRUCT, WH_KEYBOARD_LL, WH_MOUSE_LL, WINDOW_STYLE, HMENU, HOOKPROC, KBDLLHOOKSTRUCT, LLKHF_EXTENDED, MSG, MSLLHOOKSTRUCT, WH_KEYBOARD_LL,
WM_DISPLAYCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WH_MOUSE_LL, WINDOW_STYLE, WM_DISPLAYCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN,
WM_MBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SYSKEYDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN,
WM_SYSKEYUP, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WNDPROC, WM_RBUTTONUP, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW,
WNDPROC,
}; };
use crate::client::Position; use crate::client::Position;
@@ -141,7 +141,7 @@ fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
}, },
WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::AxisDiscrete120 { WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::AxisDiscrete120 {
axis: 0, axis: 0,
value: -(mouse_low_level.mouseData as i32), value: -(mouse_low_level.mouseData as i32 >> 16),
}), }),
WPARAM(p) if p == WM_XBUTTONDOWN as usize || p == WM_XBUTTONUP as usize => { WPARAM(p) if p == WM_XBUTTONDOWN as usize || p == WM_XBUTTONUP as usize => {
let hb = mouse_low_level.mouseData >> 16; let hb = mouse_low_level.mouseData >> 16;
@@ -347,37 +347,44 @@ unsafe extern "system" fn window_proc(
LRESULT(1) LRESULT(1)
} }
unsafe extern "system" fn monitor_enum_proc(
hmon: HMONITOR,
_hdc: HDC,
_lprect: *mut RECT,
lparam: LPARAM,
) -> BOOL {
let monitors = lparam.0 as *mut Vec<HMONITOR>;
(*monitors).push(hmon);
TRUE // continue enumeration
}
fn enumerate_displays() -> Vec<RECT> { fn enumerate_displays() -> Vec<RECT> {
unsafe { unsafe {
let mut display_rects = vec![]; let mut display_rects = vec![];
let mut monitors: Vec<HMONITOR> = Vec::new(); let mut devices = vec![];
let ret = EnumDisplayMonitors( for i in 0.. {
HDC::default(), let mut device: DISPLAY_DEVICEW = std::mem::zeroed();
None, device.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as u32;
Some(monitor_enum_proc), let ret = EnumDisplayDevicesW(None, i, &mut device, EDD_GET_DEVICE_INTERFACE_NAME);
LPARAM(&mut monitors as *mut Vec<HMONITOR> as isize), if ret == FALSE {
); break;
if ret != TRUE {
panic!("could not enumerate displays");
}
for monitor in monitors {
let mut monitor_info: MONITORINFO = std::mem::zeroed();
monitor_info.cbSize = std::mem::size_of::<MONITORINFO>() as u32;
if GetMonitorInfoW(monitor, &mut monitor_info) == FALSE {
panic!();
} }
display_rects.push(monitor_info.rcMonitor); if device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP != 0 {
log::info!("{:?}", device.DeviceName);
devices.push(device.DeviceName);
}
}
for device in devices {
let mut dev_mode: DEVMODEW = std::mem::zeroed();
dev_mode.dmSize = std::mem::size_of::<DEVMODEW>() as u16;
let ret = EnumDisplaySettingsW(
PCWSTR::from_raw(&device as *const _),
ENUM_CURRENT_SETTINGS,
&mut dev_mode,
);
if ret == FALSE {
log::warn!("no display mode");
}
let pos = dev_mode.Anonymous1.Anonymous2.dmPosition;
let (x, y) = (pos.x, pos.y);
let (width, height) = (dev_mode.dmPelsWidth, dev_mode.dmPelsHeight);
display_rects.push(RECT {
left: x,
right: x + width as i32,
top: y,
bottom: y + height as i32,
});
} }
display_rects display_rects
} }

View File

@@ -102,6 +102,8 @@ pub struct ClientConfig {
pub port: u16, pub port: u16,
/// position of a client on screen /// position of a client on screen
pub pos: Position, pub pos: Position,
/// enter hook
pub cmd: Option<String>,
} }
impl Default for ClientConfig { impl Default for ClientConfig {
@@ -111,6 +113,7 @@ impl Default for ClientConfig {
hostname: Default::default(), hostname: Default::default(),
fix_ips: Default::default(), fix_ips: Default::default(),
pos: Default::default(), pos: Default::default(),
cmd: None,
} }
} }
} }

View File

@@ -31,6 +31,7 @@ pub struct TomlClient {
pub ips: Option<Vec<IpAddr>>, pub ips: Option<Vec<IpAddr>>,
pub port: Option<u16>, pub port: Option<u16>,
pub activate_on_startup: Option<bool>, pub activate_on_startup: Option<bool>,
pub enter_hook: Option<String>,
} }
impl ConfigToml { impl ConfigToml {
@@ -92,6 +93,7 @@ pub struct ConfigClient {
pub port: u16, pub port: u16,
pub pos: Position, pub pos: Position,
pub active: bool, pub active: bool,
pub enter_hook: Option<String>,
} }
const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] = const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] =
@@ -208,12 +210,14 @@ impl Config {
None => c.host_name.clone(), None => c.host_name.clone(),
}; };
let active = c.activate_on_startup.unwrap_or(false); let active = c.activate_on_startup.unwrap_or(false);
let enter_hook = c.enter_hook.clone();
ConfigClient { ConfigClient {
ips, ips,
hostname, hostname,
port, port,
pos: *pos, pos: *pos,
active, active,
enter_hook,
} }
}) })
.collect() .collect()

View File

@@ -182,7 +182,8 @@ impl VirtualInput {
} }
PointerEvent::AxisDiscrete120 { axis, value } => { PointerEvent::AxisDiscrete120 { axis, value } => {
let axis: Axis = (axis as u32).try_into()?; let axis: Axis = (axis as u32).try_into()?;
self.pointer.axis(0, axis, (value / 15) as f64); self.pointer
.axis_discrete(0, axis, value as f64 / 6., value / 120);
self.pointer.frame(); self.pointer.frame();
} }
PointerEvent::Frame {} => self.pointer.frame(), PointerEvent::Frame {} => self.pointer.frame(),

View File

@@ -107,16 +107,18 @@ pub enum FrontendRequest {
UpdatePosition(ClientHandle, Position), UpdatePosition(ClientHandle, Position),
/// update fix-ips /// update fix-ips
UpdateFixIps(ClientHandle, Vec<IpAddr>), UpdateFixIps(ClientHandle, Vec<IpAddr>),
/// request the state of the given client
GetState(ClientHandle),
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FrontendEvent { pub enum FrontendEvent {
/// a client was created /// a client was created
Created(ClientHandle, ClientConfig, ClientState), Created(ClientHandle, ClientConfig, ClientState),
/// a client was updated /// no such client
Updated(ClientHandle, ClientConfig), NoSuchClient(ClientHandle),
/// state changed /// state changed
StateChange(ClientHandle, ClientState), State(ClientHandle, ClientConfig, ClientState),
/// the client was deleted /// the client was deleted
Deleted(ClientHandle), Deleted(ClientHandle),
/// new port, reason of failure (if failed) /// new port, reason of failure (if failed)
@@ -235,9 +237,7 @@ impl FrontendListener {
let json = serde_json::to_string(&notify).unwrap(); let json = serde_json::to_string(&notify).unwrap();
let payload = json.as_bytes(); let payload = json.as_bytes();
let len = payload.len().to_be_bytes(); let len = payload.len().to_be_bytes();
log::debug!("json: {json}, len: {}", payload.len()); log::debug!("broadcasting event to streams: {json}");
log::debug!("broadcasting event to streams: {:?}", self.tx_streams);
let mut keep = vec![]; let mut keep = vec![];
// TODO do simultaneously // TODO do simultaneously
for tx in self.tx_streams.iter_mut() { for tx in self.tx_streams.iter_mut() {

View File

@@ -100,6 +100,18 @@ impl<'a> Cli<'a> {
} }
} }
async fn update_client(&mut self, handle: ClientHandle) -> Result<()> {
self.send_request(FrontendRequest::GetState(handle)).await?;
loop {
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::State(_, _, _) | FrontendEvent::NoSuchClient(_) = event {
break;
}
}
Ok(())
}
async fn execute(&mut self, cmd: Command) -> Result<()> { async fn execute(&mut self, cmd: Command) -> Result<()> {
match cmd { match cmd {
Command::None => {} Command::None => {}
@@ -125,14 +137,8 @@ impl<'a> Cli<'a> {
FrontendRequest::UpdatePosition(handle, pos), FrontendRequest::UpdatePosition(handle, pos),
] { ] {
self.send_request(request).await?; self.send_request(request).await?;
loop {
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::Updated(_, _) = event {
break;
}
}
} }
self.update_client(handle).await?;
} }
Command::Disconnect(id) => { Command::Disconnect(id) => {
self.send_request(FrontendRequest::Delete(id)).await?; self.send_request(FrontendRequest::Delete(id)).await?;
@@ -148,26 +154,12 @@ impl<'a> Cli<'a> {
Command::Activate(id) => { Command::Activate(id) => {
self.send_request(FrontendRequest::Activate(id, true)) self.send_request(FrontendRequest::Activate(id, true))
.await?; .await?;
loop { self.update_client(id).await?;
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::StateChange(_, _) = event {
self.handle_event(event);
break;
}
}
} }
Command::Deactivate(id) => { Command::Deactivate(id) => {
self.send_request(FrontendRequest::Activate(id, false)) self.send_request(FrontendRequest::Activate(id, false))
.await?; .await?;
loop { self.update_client(id).await?;
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::StateChange(_, _) = event {
self.handle_event(event);
break;
}
}
} }
Command::List => { Command::List => {
self.send_request(FrontendRequest::Enumerate()).await?; self.send_request(FrontendRequest::Enumerate()).await?;
@@ -182,25 +174,12 @@ impl<'a> Cli<'a> {
Command::SetHost(handle, host) => { Command::SetHost(handle, host) => {
let request = FrontendRequest::UpdateHostname(handle, Some(host.clone())); let request = FrontendRequest::UpdateHostname(handle, Some(host.clone()));
self.send_request(request).await?; self.send_request(request).await?;
loop { self.update_client(handle).await?;
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::Updated(_, _) = event {
self.handle_event(event);
break;
}
}
} }
Command::SetPort(handle, port) => { Command::SetPort(handle, port) => {
let request = FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT)); let request = FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT));
self.send_request(request).await?; self.send_request(request).await?;
loop { self.update_client(handle).await?;
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::Updated(_, _) = event {
break;
}
}
} }
Command::Help => { Command::Help => {
for cmd_type in [ for cmd_type in [
@@ -244,8 +223,11 @@ impl<'a> Cli<'a> {
eprintln!(); eprintln!();
self.clients.push((h, c, s)); self.clients.push((h, c, s));
} }
FrontendEvent::Updated(h, c) => { FrontendEvent::NoSuchClient(h) => {
if let Some((_, config, _)) = self.find_mut(h) { eprintln!("no such client: {h}");
}
FrontendEvent::State(h, c, s) => {
if let Some((_, config, state)) = self.find_mut(h) {
let old_host = config.hostname.clone().unwrap_or("\"\"".into()); let old_host = config.hostname.clone().unwrap_or("\"\"".into());
let new_host = c.hostname.clone().unwrap_or("\"\"".into()); let new_host = c.hostname.clone().unwrap_or("\"\"".into());
if old_host != new_host { if old_host != new_host {
@@ -261,10 +243,6 @@ impl<'a> Cli<'a> {
eprintln!("client {h} ips updated: {:?}", c.fix_ips) eprintln!("client {h} ips updated: {:?}", c.fix_ips)
} }
*config = c; *config = c;
}
}
FrontendEvent::StateChange(h, s) => {
if let Some((_, _, state)) = self.find_mut(h) {
if state.active ^ s.active { if state.active ^ s.active {
eprintln!( eprintln!(
"client {h} {}", "client {h} {}",

View File

@@ -11,6 +11,7 @@ use std::{
use crate::frontend::{gtk::window::Window, FrontendRequest}; use crate::frontend::{gtk::window::Window, FrontendRequest};
use adw::Application; use adw::Application;
use endi::{Endian, ReadBytes};
use gtk::{ use gtk::{
gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, IconTheme, gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, IconTheme,
}; };
@@ -85,16 +86,14 @@ fn build_ui(app: &Application) {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
match loop { match loop {
// read length // read length
let mut len = [0u8; 8]; let len = match rx.read_u64(Endian::Big) {
match rx.read_exact(&mut len) { Ok(l) => l,
Ok(_) => (),
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()), Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
Err(e) => break Err(e), Err(e) => break Err(e),
}; };
let len = usize::from_be_bytes(len);
// read payload // read payload
let mut buf = vec![0u8; len]; let mut buf = vec![0u8; len as usize];
match rx.read_exact(&mut buf) { match rx.read_exact(&mut buf) {
Ok(_) => (), Ok(_) => (),
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()), Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
@@ -126,12 +125,11 @@ fn build_ui(app: &Application) {
FrontendEvent::Deleted(client) => { FrontendEvent::Deleted(client) => {
window.delete_client(client); window.delete_client(client);
} }
FrontendEvent::Updated(handle, client) => { FrontendEvent::State(handle, config, state) => {
window.update_client_config(handle, client); window.update_client_config(handle, config);
}
FrontendEvent::StateChange(handle, state) => {
window.update_client_state(handle, state); window.update_client_state(handle, state);
} }
FrontendEvent::NoSuchClient(_) => { }
FrontendEvent::Error(e) => { FrontendEvent::Error(e) => {
window.show_toast(e.as_str()); window.show_toast(e.as_str());
}, },

View File

@@ -10,6 +10,7 @@ use std::net::TcpStream;
use adw::prelude::*; use adw::prelude::*;
use adw::subclass::prelude::*; use adw::subclass::prelude::*;
use endi::{Endian, WriteBytes};
use glib::{clone, Object}; use glib::{clone, Object};
use gtk::{ use gtk::{
gio, gio,
@@ -51,6 +52,10 @@ impl Window {
.expect("Could not get clients") .expect("Could not get clients")
} }
fn client_by_idx(&self, idx: u32) -> Option<ClientObject> {
self.clients().item(idx).map(|o| o.downcast().unwrap())
}
fn setup_clients(&self) { fn setup_clients(&self) {
let model = gio::ListStore::new::<ClientObject>(); let model = gio::ListStore::new::<ClientObject>();
self.imp().clients.replace(Some(model)); self.imp().clients.replace(Some(model));
@@ -62,27 +67,24 @@ impl Window {
let client_object = obj.downcast_ref().expect("Expected object of type `ClientObject`."); let client_object = obj.downcast_ref().expect("Expected object of type `ClientObject`.");
let row = window.create_client_row(client_object); let row = window.create_client_row(client_object);
row.connect_closure("request-update", false, closure_local!(@strong window => move |row: ClientRow, active: bool| { row.connect_closure("request-update", false, closure_local!(@strong window => move |row: ClientRow, active: bool| {
let index = row.index() as u32; if let Some(client) = window.client_by_idx(row.index() as u32) {
let Some(client) = window.clients().item(index) else { window.request_client_activate(&client, active);
return; window.request_client_update(&client);
}; window.request_client_state(&client);
let client = client.downcast_ref::<ClientObject>().unwrap(); }
window.request_client_update(client);
window.request_client_activate(client, active)
})); }));
row.connect_closure("request-delete", false, closure_local!(@strong window => move |row: ClientRow| { row.connect_closure("request-delete", false, closure_local!(@strong window => move |row: ClientRow| {
let index = row.index() as u32; if let Some(client) = window.client_by_idx(row.index() as u32) {
window.request_client_delete(index); window.request_client_delete(&client);
}
})); }));
row.connect_closure("request-dns", false, closure_local!(@strong window => move row.connect_closure("request-dns", false, closure_local!(@strong window => move
|row: ClientRow| { |row: ClientRow| {
let index = row.index() as u32; if let Some(client) = window.client_by_idx(row.index() as u32) {
let Some(client) = window.clients().item(index) else { window.request_client_update(&client);
return; window.request_dns(&client);
}; window.request_client_state(&client);
let client = client.downcast_ref::<ClientObject>().unwrap(); }
window.request_client_update(client);
window.request_dns(index);
})); }));
row.upcast() row.upcast()
}) })
@@ -177,7 +179,7 @@ impl Window {
if state.resolving != data.resolving { if state.resolving != data.resolving {
client_object.set_resolving(state.resolving); client_object.set_resolving(state.resolving);
log::debug!("resolving {}: {}", data.handle, state.active); log::debug!("resolving {}: {}", data.handle, state.resolving);
} }
self.update_dns_state(handle, !state.ips.is_empty()); self.update_dns_state(handle, !state.ips.is_empty());
@@ -204,20 +206,6 @@ impl Window {
} }
} }
pub fn request_dns(&self, idx: u32) {
let client_object = self.clients().item(idx).unwrap();
let client_object: &ClientObject = client_object.downcast_ref().unwrap();
let data = client_object.get_data();
let event = FrontendRequest::ResolveDns(data.handle);
self.request(event);
}
pub fn request_client_create(&self) {
let event = FrontendRequest::Create;
self.imp().set_port(DEFAULT_PORT);
self.request(event);
}
pub fn request_port_change(&self) { pub fn request_port_change(&self) {
let port = self.imp().port_entry.get().text().to_string(); let port = self.imp().port_entry.get().text().to_string();
if let Ok(port) = port.as_str().parse::<u16>() { if let Ok(port) = port.as_str().parse::<u16>() {
@@ -227,6 +215,23 @@ impl Window {
} }
} }
pub fn request_client_state(&self, client: &ClientObject) {
let handle = client.handle();
let event = FrontendRequest::GetState(handle);
self.request(event);
}
pub fn request_client_create(&self) {
let event = FrontendRequest::Create;
self.request(event);
}
pub fn request_dns(&self, client: &ClientObject) {
let data = client.get_data();
let event = FrontendRequest::ResolveDns(data.handle);
self.request(event);
}
pub fn request_client_update(&self, client: &ClientObject) { pub fn request_client_update(&self, client: &ClientObject) {
let handle = client.handle(); let handle = client.handle();
let data = client.get_data(); let data = client.get_data();
@@ -239,38 +244,29 @@ impl Window {
FrontendRequest::UpdatePosition(handle, position), FrontendRequest::UpdatePosition(handle, position),
FrontendRequest::UpdatePort(handle, port), FrontendRequest::UpdatePort(handle, port),
] { ] {
log::debug!("requesting: {event:?}");
self.request(event); self.request(event);
} }
} }
pub fn request_client_activate(&self, client: &ClientObject, active: bool) { pub fn request_client_activate(&self, client: &ClientObject, active: bool) {
let handle = client.handle(); let handle = client.handle();
let event = FrontendRequest::Activate(handle, active); let event = FrontendRequest::Activate(handle, active);
log::debug!("requesting: {event:?}");
self.request(event); self.request(event);
} }
pub fn request_client_delete(&self, idx: u32) { pub fn request_client_delete(&self, client: &ClientObject) {
if let Some(obj) = self.clients().item(idx) { let handle = client.handle();
let client_object: &ClientObject = obj let event = FrontendRequest::Delete(handle);
.downcast_ref() self.request(event);
.expect("Expected object of type `ClientObject`.");
let handle = client_object.handle();
let event = FrontendRequest::Delete(handle);
self.request(event);
}
} }
pub fn request(&self, event: FrontendRequest) { pub fn request(&self, event: FrontendRequest) {
let json = serde_json::to_string(&event).unwrap(); let json = serde_json::to_string(&event).unwrap();
log::debug!("requesting {json}"); log::debug!("requesting: {json}");
let mut stream = self.imp().stream.borrow_mut(); let mut stream = self.imp().stream.borrow_mut();
let stream = stream.as_mut().unwrap(); let stream = stream.as_mut().unwrap();
let bytes = json.as_bytes(); let bytes = json.as_bytes();
let len = bytes.len().to_be_bytes(); if let Err(e) = stream.write_u64(Endian::Big, bytes.len() as u64) {
if let Err(e) = stream.write(&len) {
log::error!("error sending message: {e}"); log::error!("error sending message: {e}");
}; };
if let Err(e) = stream.write(bytes) { if let Err(e) = stream.write(bytes) {

View File

@@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
/* /*
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input * https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
* https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
* https://kbd-project.org/docs/scancodes/scancodes-1.html
*/ */
#[repr(u32)] #[repr(u32)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)] #[derive(Debug, Clone, Copy, TryFromPrimitive)]
@@ -120,15 +122,15 @@ pub enum Windows {
KeyF21 = 0x006C, KeyF21 = 0x006C,
KeyF22 = 0x006D, KeyF22 = 0x006D,
KeyF23 = 0x006E, KeyF23 = 0x006E,
KeyF24 = 0x0076, KeyF24 = 0x0076, // KeyLANG5
KeypadComma = 0x007E, KeypadComma = 0x007E,
KeyInternational1 = 0x0073, KeyInternational1 = 0x0073,
KeyInternational2 = 0x0070, KeyInternational2 = 0x0070,
KeyInternational3 = 0x007D, KeyInternational3 = 0x007D, // typo in doc -> its Int'l 3 not Int'l 2
#[allow(dead_code)] #[allow(dead_code)]
KeyInternational4 = 0x0079, // FIXME unused KeyInternational4 = 0x0079,
#[allow(dead_code)] #[allow(dead_code)]
KeyInternational5 = 0x007B, // FIXME unused KeyInternational5 = 0x007B,
// KeyInternational6 = 0x005C, // KeyInternational6 = 0x005C,
KeyLANG1 = 0x0072, KeyLANG1 = 0x0072,
KeyLANG2 = 0x0071, KeyLANG2 = 0x0071,
@@ -141,6 +143,7 @@ pub enum Windows {
KeyLeftGUI = 0xE05B, KeyLeftGUI = 0xE05B,
KeyRightCtrl = 0xE01D, KeyRightCtrl = 0xE01D,
KeyRightShift = 0x0036, KeyRightShift = 0x0036,
KeyFakeRightShift = 0xE036,
KeyRightAlt = 0xE038, KeyRightAlt = 0xE038,
KeyRightGUI = 0xE05C, KeyRightGUI = 0xE05C,
KeyScanNextTrack = 0xE019, KeyScanNextTrack = 0xE019,
@@ -293,7 +296,7 @@ pub enum Linux {
KeyPause = 119, KeyPause = 119,
KeyScale = 120, /* AL Compiz Scale (Expose) */ KeyScale = 120, /* AL Compiz Scale (Expose) */
KeyKpcomma = 121, KeyKpcomma = 121,
KeyHangeul = 122, KeyHanguel = 122,
// KEY_HANGUEL = KeyHangeul, // KEY_HANGUEL = KeyHangeul,
KeyHanja = 123, KeyHanja = 123,
KeyYen = 124, KeyYen = 124,
@@ -518,16 +521,16 @@ impl TryFrom<Linux> for Windows {
Linux::KeyKp3 => Ok(Self::Keypad3PageDn), Linux::KeyKp3 => Ok(Self::Keypad3PageDn),
Linux::KeyKp0 => Ok(Self::Keypad0Insert), Linux::KeyKp0 => Ok(Self::Keypad0Insert),
Linux::KeyKpDot => Ok(Self::KeypadDot), Linux::KeyKpDot => Ok(Self::KeypadDot),
Linux::KeyZenkakuhankaku => Ok(Self::KeyLANG1), // TODO unsure Linux::KeyZenkakuhankaku => Ok(Self::KeyF24), // KeyLANG5
Linux::Key102nd => Ok(Self::KeyNonUSSlashBar), // TODO unsure Linux::Key102nd => Ok(Self::KeyNonUSSlashBar), // TODO unsure
Linux::KeyF11 => Ok(Self::KeyF11), Linux::KeyF11 => Ok(Self::KeyF11),
Linux::KeyF12 => Ok(Self::KeyF12), Linux::KeyF12 => Ok(Self::KeyF12),
Linux::KeyRo => Ok(Self::ErrorRollOver), // TODO unsure Linux::KeyRo => Ok(Self::KeyInternational1),
Linux::KeyKatakana => Ok(Self::KeyLANG1), // TODO unsure Linux::KeyKatakana => Ok(Self::KeyLANG3),
Linux::KeyHiragana => Ok(Self::KeyLANG2), // TODO unsure Linux::KeyHiragana => Ok(Self::KeyLANG4),
Linux::KeyHenkan => Ok(Self::KeyLANG3), // TODO unsure Linux::KeyHenkan => Ok(Self::KeyInternational4),
Linux::KeyKatakanahiragana => Ok(Self::KeyLANG4), // TODO unsure Linux::KeyKatakanahiragana => Ok(Self::KeyInternational2),
Linux::KeyMuhenkan => Ok(Self::KeyLANG4), // TODO unsure Linux::KeyMuhenkan => Ok(Self::KeyInternational5),
Linux::KeyKpJpComma => Ok(Self::KeypadComma), Linux::KeyKpJpComma => Ok(Self::KeypadComma),
Linux::KeyKpEnter => Ok(Self::KeypadEnter), Linux::KeyKpEnter => Ok(Self::KeypadEnter),
Linux::KeyRightCtrl => Ok(Self::KeyRightCtrl), Linux::KeyRightCtrl => Ok(Self::KeyRightCtrl),
@@ -555,9 +558,9 @@ impl TryFrom<Linux> for Windows {
Linux::KeyPause => Ok(Self::KeyPause), Linux::KeyPause => Ok(Self::KeyPause),
Linux::KeyScale => Err(()), // TODO Linux::KeyScale => Err(()), // TODO
Linux::KeyKpcomma => Ok(Self::KeypadComma), Linux::KeyKpcomma => Ok(Self::KeypadComma),
Linux::KeyHangeul => Ok(Self::KeyInternational1), // TODO unsure Linux::KeyHanguel => Ok(Self::KeyLANG1), // FIXME should be 00F2?
Linux::KeyHanja => Ok(Self::KeyInternational2), // TODO unsure Linux::KeyHanja => Ok(Self::KeyLANG2), // FIXME should be 00F1?
Linux::KeyYen => Ok(Self::KeyInternational3), // TODO unsure Linux::KeyYen => Ok(Self::KeyInternational3),
Linux::KeyLeftMeta => Ok(Self::KeyLeftGUI), Linux::KeyLeftMeta => Ok(Self::KeyLeftGUI),
Linux::KeyRightmeta => Ok(Self::KeyRightGUI), Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
Linux::KeyCompose => Ok(Self::KeyApplication), Linux::KeyCompose => Ok(Self::KeyApplication),
@@ -807,21 +810,22 @@ impl TryFrom<Windows> for Linux {
Windows::KeyF23 => Ok(Self::KeyF23), Windows::KeyF23 => Ok(Self::KeyF23),
Windows::KeyF24 => Ok(Self::KeyF24), Windows::KeyF24 => Ok(Self::KeyF24),
Windows::KeypadComma => Ok(Self::KeyKpcomma), Windows::KeypadComma => Ok(Self::KeyKpcomma),
Windows::KeyInternational1 => Ok(Self::KeyHangeul), Windows::KeyInternational1 => Ok(Self::KeyRo),
Windows::KeyInternational2 => Ok(Self::KeyHanja), Windows::KeyInternational2 => Ok(Self::KeyKatakanahiragana),
Windows::KeyInternational3 => Ok(Self::KeyYen), Windows::KeyInternational3 => Ok(Self::KeyYen),
Windows::KeyInternational4 => Err(()), Windows::KeyInternational4 => Ok(Self::KeyHenkan),
Windows::KeyInternational5 => Err(()), Windows::KeyInternational5 => Ok(Self::KeyMuhenkan),
Windows::KeyLANG1 => Ok(Self::KeyKatakana), Windows::KeyLANG1 => Ok(Self::KeyHanguel),
Windows::KeyLANG2 => Ok(Self::KeyHiragana), Windows::KeyLANG2 => Ok(Self::KeyHanja),
Windows::KeyLANG3 => Ok(Self::KeyHenkan), Windows::KeyLANG3 => Ok(Self::KeyKatakana),
Windows::KeyLANG4 => Ok(Self::KeyKatakanahiragana), Windows::KeyLANG4 => Ok(Self::KeyHiragana),
Windows::KeyLeftCtrl => Ok(Self::KeyLeftCtrl), Windows::KeyLeftCtrl => Ok(Self::KeyLeftCtrl),
Windows::KeyLeftShift => Ok(Self::KeyLeftShift), Windows::KeyLeftShift => Ok(Self::KeyLeftShift),
Windows::KeyLeftAlt => Ok(Self::KeyLeftAlt), Windows::KeyLeftAlt => Ok(Self::KeyLeftAlt),
Windows::KeyLeftGUI => Ok(Self::KeyLeftMeta), Windows::KeyLeftGUI => Ok(Self::KeyLeftMeta),
Windows::KeyRightCtrl => Ok(Self::KeyRightCtrl), Windows::KeyRightCtrl => Ok(Self::KeyRightCtrl),
Windows::KeyRightShift => Ok(Self::KeyRightShift), Windows::KeyRightShift => Ok(Self::KeyRightShift),
Windows::KeyFakeRightShift => Ok(Self::KeyRightShift),
Windows::KeyRightAlt => Ok(Self::KeyRightalt), Windows::KeyRightAlt => Ok(Self::KeyRightalt),
Windows::KeyRightGUI => Ok(Self::KeyRightmeta), Windows::KeyRightGUI => Ok(Self::KeyRightmeta),
Windows::KeyScanNextTrack => Ok(Self::KeyNextsong), Windows::KeyScanNextTrack => Ok(Self::KeyNextsong),

View File

@@ -55,6 +55,7 @@ impl Server {
fix_ips: config_client.ips.into_iter().collect(), fix_ips: config_client.ips.into_iter().collect(),
port: config_client.port, port: config_client.port,
pos: config_client.pos, pos: config_client.pos,
cmd: config_client.enter_hook,
}; };
let state = ClientState { let state = ClientState {
active: config_client.active, active: config_client.active,

View File

@@ -2,7 +2,7 @@ use anyhow::{anyhow, Result};
use futures::StreamExt; use futures::StreamExt;
use std::{collections::HashSet, net::SocketAddr}; use std::{collections::HashSet, net::SocketAddr};
use tokio::{sync::mpsc::Sender, task::JoinHandle}; use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle};
use crate::{ use crate::{
capture::{self, InputCapture}, capture::{self, InputCapture},
@@ -140,6 +140,9 @@ async fn handle_capture_event(
if start_timer { if start_timer {
let _ = timer_tx.try_send(()); let _ = timer_tx.try_send(());
} }
if enter {
spawn_hook_command(server, handle);
}
if let Some(addr) = addr { if let Some(addr) = addr {
if enter { if enter {
let _ = sender_tx.send((Event::Enter(), addr)).await; let _ = sender_tx.send((Event::Enter(), addr)).await;
@@ -148,3 +151,34 @@ async fn handle_capture_event(
} }
Ok(()) Ok(())
} }
fn spawn_hook_command(server: &Server, handle: ClientHandle) {
let Some(cmd) = server
.client_manager
.borrow()
.get(handle)
.and_then(|(c, _)| c.cmd.clone())
else {
return;
};
tokio::task::spawn_local(async move {
log::info!("spawning command!");
let mut child = match Command::new("sh").arg("-c").arg(cmd.as_str()).spawn() {
Ok(c) => c,
Err(e) => {
log::warn!("could not execute cmd: {e}");
return;
}
};
match child.wait().await {
Ok(s) => {
if s.success() {
log::info!("{cmd} exited successfully");
} else {
log::warn!("{cmd} exited with {s}");
}
}
Err(e) => log::warn!("{cmd}: {e}"),
}
});
}

View File

@@ -107,20 +107,22 @@ async fn handle_frontend_event(
log::debug!("frontend: {event:?}"); log::debug!("frontend: {event:?}");
match event { match event {
FrontendRequest::Create => { FrontendRequest::Create => {
add_client(server, frontend).await; let handle = add_client(server, frontend).await;
resolve_dns(server, resolve_tx, handle).await;
} }
FrontendRequest::Activate(handle, active) => { FrontendRequest::Activate(handle, active) => {
if active { if active {
activate_client(server, frontend, capture, emulate, handle).await; activate_client(server, capture, emulate, handle).await;
} else { } else {
deactivate_client(server, frontend, capture, emulate, handle).await; deactivate_client(server, capture, emulate, handle).await;
} }
} }
FrontendRequest::ChangePort(port) => { FrontendRequest::ChangePort(port) => {
let _ = port_tx.send(port).await; let _ = port_tx.send(port).await;
} }
FrontendRequest::Delete(handle) => { FrontendRequest::Delete(handle) => {
remove_client(server, frontend, capture, emulate, handle).await; remove_client(server, capture, emulate, handle).await;
broadcast(frontend, FrontendEvent::Deleted(handle)).await;
} }
FrontendRequest::Enumerate() => { FrontendRequest::Enumerate() => {
let clients = server let clients = server
@@ -131,65 +133,74 @@ async fn handle_frontend_event(
.collect(); .collect();
broadcast(frontend, FrontendEvent::Enumerate(clients)).await; broadcast(frontend, FrontendEvent::Enumerate(clients)).await;
} }
FrontendRequest::GetState(handle) => {
broadcast_client(server, frontend, handle).await;
}
FrontendRequest::Terminate() => { FrontendRequest::Terminate() => {
log::info!("terminating gracefully..."); log::info!("terminating gracefully...");
return true; return true;
} }
FrontendRequest::UpdateFixIps(handle, fix_ips) => { FrontendRequest::UpdateFixIps(handle, fix_ips) => {
update_fix_ips(server, resolve_tx, handle, fix_ips).await; update_fix_ips(server, handle, fix_ips).await;
broadcast_client_update(server, frontend, handle).await; resolve_dns(server, resolve_tx, handle).await;
} }
FrontendRequest::UpdateHostname(handle, hostname) => { FrontendRequest::UpdateHostname(handle, hostname) => {
update_hostname(server, resolve_tx, handle, hostname).await; update_hostname(server, resolve_tx, handle, hostname).await;
broadcast_client_update(server, frontend, handle).await; resolve_dns(server, resolve_tx, handle).await;
} }
FrontendRequest::UpdatePort(handle, port) => { FrontendRequest::UpdatePort(handle, port) => {
update_port(server, handle, port).await; update_port(server, handle, port).await;
broadcast_client_update(server, frontend, handle).await;
} }
FrontendRequest::UpdatePosition(handle, pos) => { FrontendRequest::UpdatePosition(handle, pos) => {
update_pos(server, handle, capture, emulate, pos).await; update_pos(server, handle, capture, emulate, pos).await;
broadcast_client_update(server, frontend, handle).await;
} }
FrontendRequest::ResolveDns(handle) => { FrontendRequest::ResolveDns(handle) => {
let hostname = server resolve_dns(server, resolve_tx, handle).await;
.client_manager
.borrow()
.get(handle)
.and_then(|(c, _)| c.hostname.clone());
if let Some(hostname) = hostname {
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
}
} }
}; };
false false
} }
async fn resolve_dns(server: &Server, resolve_tx: &Sender<DnsRequest>, handle: ClientHandle) {
let hostname = server
.client_manager
.borrow()
.get(handle)
.and_then(|(c, _)| c.hostname.clone());
if let Some(hostname) = hostname {
let _ = resolve_tx
.send(DnsRequest {
hostname: hostname.clone(),
handle,
})
.await;
}
}
async fn broadcast(frontend: &mut FrontendListener, event: FrontendEvent) { async fn broadcast(frontend: &mut FrontendListener, event: FrontendEvent) {
if let Err(e) = frontend.broadcast_event(event).await { if let Err(e) = frontend.broadcast_event(event).await {
log::error!("error notifying frontend: {e}"); log::error!("error notifying frontend: {e}");
} }
} }
pub async fn add_client(server: &Server, frontend: &mut FrontendListener) { pub async fn add_client(server: &Server, frontend: &mut FrontendListener) -> ClientHandle {
let handle = server.client_manager.borrow_mut().add_client(); let handle = server.client_manager.borrow_mut().add_client();
log::info!("added client {handle}"); log::info!("added client {handle}");
let (c, s) = server.client_manager.borrow().get(handle).unwrap().clone(); let (c, s) = server.client_manager.borrow().get(handle).unwrap().clone();
broadcast(frontend, FrontendEvent::Created(handle, c, s)).await; broadcast(frontend, FrontendEvent::Created(handle, c, s)).await;
handle
} }
pub async fn deactivate_client( pub async fn deactivate_client(
server: &Server, server: &Server,
frontend: &mut FrontendListener,
capture: &Sender<CaptureEvent>, capture: &Sender<CaptureEvent>,
emulate: &Sender<EmulationEvent>, emulate: &Sender<EmulationEvent>,
handle: ClientHandle, handle: ClientHandle,
) { ) {
let state = match server.client_manager.borrow_mut().get_mut(handle) { match server.client_manager.borrow_mut().get_mut(handle) {
Some((_, s)) => { Some((_, s)) => {
s.active = false; s.active = false;
s.clone()
} }
None => return, None => return,
}; };
@@ -197,13 +208,10 @@ pub async fn deactivate_client(
let event = ClientEvent::Destroy(handle); let event = ClientEvent::Destroy(handle);
let _ = capture.send(CaptureEvent::ClientEvent(event)).await; let _ = capture.send(CaptureEvent::ClientEvent(event)).await;
let _ = emulate.send(EmulationEvent::ClientEvent(event)).await; let _ = emulate.send(EmulationEvent::ClientEvent(event)).await;
let event = FrontendEvent::StateChange(handle, state);
broadcast(frontend, event).await;
} }
pub async fn activate_client( pub async fn activate_client(
server: &Server, server: &Server,
frontend: &mut FrontendListener,
capture: &Sender<CaptureEvent>, capture: &Sender<CaptureEvent>,
emulate: &Sender<EmulationEvent>, emulate: &Sender<EmulationEvent>,
handle: ClientHandle, handle: ClientHandle,
@@ -217,14 +225,13 @@ pub async fn activate_client(
let other = server.client_manager.borrow_mut().find_client(pos); let other = server.client_manager.borrow_mut().find_client(pos);
if let Some(other) = other { if let Some(other) = other {
if other != handle { if other != handle {
deactivate_client(server, frontend, capture, emulate, other).await; deactivate_client(server, capture, emulate, other).await;
} }
} }
/* activate the client */ /* activate the client */
let state = if let Some((_, s)) = server.client_manager.borrow_mut().get_mut(handle) { if let Some((_, s)) = server.client_manager.borrow_mut().get_mut(handle) {
s.active = true; s.active = true;
s.clone()
} else { } else {
return; return;
}; };
@@ -233,13 +240,10 @@ pub async fn activate_client(
let event = ClientEvent::Create(handle, pos); let event = ClientEvent::Create(handle, pos);
let _ = capture.send(CaptureEvent::ClientEvent(event)).await; let _ = capture.send(CaptureEvent::ClientEvent(event)).await;
let _ = emulate.send(EmulationEvent::ClientEvent(event)).await; let _ = emulate.send(EmulationEvent::ClientEvent(event)).await;
let event = FrontendEvent::StateChange(handle, state);
broadcast(frontend, event).await;
} }
pub async fn remove_client( pub async fn remove_client(
server: &Server, server: &Server,
frontend: &mut FrontendListener,
capture: &Sender<CaptureEvent>, capture: &Sender<CaptureEvent>,
emulate: &Sender<EmulationEvent>, emulate: &Sender<EmulationEvent>,
handle: ClientHandle, handle: ClientHandle,
@@ -258,30 +262,15 @@ pub async fn remove_client(
let _ = capture.send(CaptureEvent::ClientEvent(destroy)).await; let _ = capture.send(CaptureEvent::ClientEvent(destroy)).await;
let _ = emulate.send(EmulationEvent::ClientEvent(destroy)).await; let _ = emulate.send(EmulationEvent::ClientEvent(destroy)).await;
} }
let event = FrontendEvent::Deleted(handle);
broadcast(frontend, event).await;
} }
async fn update_fix_ips( async fn update_fix_ips(server: &Server, handle: ClientHandle, fix_ips: Vec<IpAddr>) {
server: &Server, let mut client_manager = server.client_manager.borrow_mut();
resolve_tx: &Sender<DnsRequest>, let Some((c, _)) = client_manager.get_mut(handle) else {
handle: ClientHandle, return;
fix_ips: Vec<IpAddr>,
) {
let hostname = {
let mut client_manager = server.client_manager.borrow_mut();
let Some((c, _)) = client_manager.get_mut(handle) else {
return;
};
c.fix_ips = fix_ips;
c.hostname.clone()
}; };
if let Some(hostname) = hostname { c.fix_ips = fix_ips;
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
}
} }
async fn update_hostname( async fn update_hostname(
@@ -356,11 +345,11 @@ async fn update_pos(
} }
} }
async fn broadcast_client_update( async fn broadcast_client(server: &Server, frontend: &mut FrontendListener, handle: ClientHandle) {
server: &Server, let client = server.client_manager.borrow().get(handle).cloned();
frontend: &mut FrontendListener, if let Some((config, state)) = client {
handle: ClientHandle, broadcast(frontend, FrontendEvent::State(handle, config, state)).await;
) { } else {
let (client, _) = server.client_manager.borrow().get(handle).unwrap().clone(); broadcast(frontend, FrontendEvent::NoSuchClient(handle)).await;
broadcast(frontend, FrontendEvent::Updated(handle, client)).await; }
} }

View File

@@ -35,7 +35,7 @@ pub fn new(
Ok(ips) => ips, Ok(ips) => ips,
Err(e) => { Err(e) => {
log::warn!("could not resolve host '{host}': {e}"); log::warn!("could not resolve host '{host}': {e}");
continue; vec![]
} }
}; };
@@ -59,14 +59,10 @@ async fn notify_state_change(
server: &mut Server, server: &mut Server,
handle: ClientHandle, handle: ClientHandle,
) { ) {
let state = server let state = server.client_manager.borrow_mut().get_mut(handle).cloned();
.client_manager if let Some((config, state)) = state {
.borrow_mut()
.get_mut(handle)
.map(|(_, s)| s.clone());
if let Some(state) = state {
let _ = frontend let _ = frontend
.send(FrontendEvent::StateChange(handle, state)) .send(FrontendEvent::State(handle, config, state))
.await; .await;
} }
} }