mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-08 04:20:01 +03:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4db2366b7 | ||
|
|
c9deb6eba4 | ||
|
|
5cc8cda19d | ||
|
|
8084b52cfc | ||
|
|
1f4821a16d | ||
|
|
82926d8272 | ||
|
|
006831b9f1 | ||
|
|
e5b770a799 | ||
|
|
017bc43176 | ||
|
|
36001c6fb2 | ||
|
|
2803db7073 | ||
|
|
622b04b36c | ||
|
|
61ff05c95a | ||
|
|
ecab3a360d | ||
|
|
6674af8e63 | ||
|
|
b3caba99ab | ||
|
|
fad48c2504 | ||
|
|
f28f75418c | ||
|
|
e2c47d3096 | ||
|
|
f19944515a | ||
|
|
535cd055b9 | ||
|
|
118c0dfc73 | ||
|
|
7897db6047 | ||
|
|
347256e966 | ||
|
|
0017dbc634 | ||
|
|
d90eb0cd0f | ||
|
|
2e52660714 | ||
|
|
8c276f88b7 | ||
|
|
13597b3587 | ||
|
|
b59808742a | ||
|
|
6c99f9bea3 | ||
|
|
d54b3a08e1 | ||
|
|
767fc8bd6b | ||
|
|
fa15048ad8 | ||
|
|
eb366bcd34 | ||
|
|
91176b1267 | ||
|
|
a6f386ea83 | ||
|
|
40b0cdd52e | ||
|
|
1553ed4212 | ||
|
|
4561c20610 | ||
|
|
6cdb607b11 | ||
|
|
f5827bb31c | ||
|
|
64e3bf3ff4 |
71
Cargo.lock
generated
71
Cargo.lock
generated
@@ -80,6 +80,21 @@ version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
|
||||
|
||||
[[package]]
|
||||
name = "arraydeque"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0ffd3d69bd89910509a5d31d1f1353f38ccffdd116dd0099bbd6627f7bd8ad8"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
|
||||
dependencies = [
|
||||
"nodrop",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.6.7"
|
||||
@@ -397,7 +412,7 @@ version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
@@ -540,7 +555,7 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
@@ -961,7 +976,7 @@ version = "0.18.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72793962ceece3863c2965d7f10c8786323b17c7adea75a515809fa20ab799a5"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro-crate 2.0.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
@@ -1104,6 +1119,15 @@ version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -1224,9 +1248,33 @@ version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "keycode"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b07873c3182aec8a0eb1a5a4e7b197d42e9d167ba78497a6ee932a82d94673ed"
|
||||
dependencies = [
|
||||
"arraydeque",
|
||||
"arrayvec",
|
||||
"bitflags 1.3.2",
|
||||
"keycode_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keycode_macro"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e521ea802f5b3c7194e169d75cab431b0ff08d022f2b6047b08754b4988b89df"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lan-mouse"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
@@ -1239,6 +1287,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"glib-build-tools",
|
||||
"gtk4",
|
||||
"keycode",
|
||||
"libadwaita",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -1421,6 +1470,12 @@ dependencies = [
|
||||
"memoffset 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
@@ -1959,7 +2014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"version-compare",
|
||||
@@ -2218,6 +2273,12 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "lan-mouse"
|
||||
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/ferdinandschober/lan-mouse"
|
||||
@@ -30,6 +30,7 @@ clap = { version="4.4.11", features = ["derive"] }
|
||||
gtk = { package = "gtk4", version = "0.7.2", features = ["v4_2"], optional = true }
|
||||
adw = { package = "libadwaita", version = "0.5.2", features = ["v1_1"], optional = true }
|
||||
async-channel = { version = "2.1.1", optional = true }
|
||||
keycode = "0.4.0"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.148"
|
||||
|
||||
151
README.md
151
README.md
@@ -14,7 +14,7 @@ The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg h
|
||||
</picture>
|
||||
|
||||
|
||||
Goal of this project is to be an open-source replacement for proprietary tools like [Synergy](https://symless.com/synergy), [Share Mouse](https://www.sharemouse.com/de/).
|
||||
Goal of this project is to be an open-source replacement for proprietary tools like [Synergy 2/3](https://symless.com/synergy), [Share Mouse](https://www.sharemouse.com/de/).
|
||||
|
||||
Focus lies on performance and a clean, manageable implementation that can easily be expanded to support additional backends like e.g. Android, iOS, ... .
|
||||
|
||||
@@ -22,6 +22,18 @@ Focus lies on performance and a clean, manageable implementation that can easily
|
||||
|
||||
For an alternative (with slightly different goals) you may check out [Input Leap](https://github.com/input-leap).
|
||||
|
||||
|
||||
> [!WARNING]
|
||||
> Since this tool has gained a bit of popularity over the past couple of days:
|
||||
>
|
||||
> All network traffic is currently **unencrypted** and sent in **plaintext**.
|
||||
>
|
||||
> A malicious actor with access to the network could read input data or send input events with spoofed IPs to take control over a device.
|
||||
>
|
||||
> Therefore you should only use this tool in your local network with trusted devices for now
|
||||
> and I take no responsibility for any leakage of data!
|
||||
|
||||
|
||||
## OS Support
|
||||
|
||||
The following table shows support for input emulation (to emulate events received from other clients) and
|
||||
@@ -34,13 +46,86 @@ input capture (to send events *to* other clients) on different operating systems
|
||||
| Wayland (Gnome) | :heavy_check_mark: | WIP |
|
||||
| X11 | :heavy_check_mark: | WIP |
|
||||
| Windows | :heavy_check_mark: | WIP |
|
||||
| MacOS | ( :heavy_check_mark: ) | WIP |
|
||||
| MacOS | :heavy_check_mark: | WIP |
|
||||
|
||||
Keycode translation is not yet implemented so on MacOS only mouse emulation works as of right now.
|
||||
> [!Important]
|
||||
> If you are using [Wayfire](https://github.com/WayfireWM/wayfire), make sure to use a recent version (must be newer than October 23rd) and **add `shortcuts-inhibit` to the list of plugins in your wayfire config!**
|
||||
> Otherwise input capture will not work.
|
||||
|
||||
## Build and Run
|
||||
## Installation
|
||||
|
||||
### Download from Releases
|
||||
The easiest way to install Lan Mouse is to download precompiled release binaries from the [releases section](https://github.com/feschber/lan-mouse/releases).
|
||||
|
||||
For Windows, the depenedencies are included in the .zip file, for other operating systems see [Installing Dependencies](#installing-dependencies).
|
||||
|
||||
### Arch Linux
|
||||
Lan Mouse is available on the AUR:
|
||||
```sh
|
||||
# git version (includes latest changes)
|
||||
paru -S lan-mouse-git
|
||||
|
||||
# alternatively
|
||||
paru -S lan-mouse-bin
|
||||
```
|
||||
|
||||
|
||||
### Building from Source
|
||||
|
||||
Build in release mode:
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Run directly:
|
||||
```sh
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
Install the files:
|
||||
```sh
|
||||
# install lan-mouse
|
||||
sudo cp target/release/lan-mouse /usr/local/bin/
|
||||
|
||||
# install app icon
|
||||
sudo mkdir -p /usr/local/share/icons/hicolor/scalable/apps
|
||||
sudo cp resources/de.feschber.LanMouse.svg /usr/local/share/icons/hicolor/scalable/apps
|
||||
|
||||
# update icon cache
|
||||
gtk-update-icon-cache /usr/local/share/icons/hicolor/
|
||||
|
||||
# install desktop entry
|
||||
sudo mkdir -p /usr/local/share/applications
|
||||
sudo cp de.feschber.LanMouse.dekstop /usr/local/share/applications
|
||||
|
||||
# when using firewalld: install firewall rule
|
||||
sudo cp firewall/lan-mouse.xml /etc/firewalld/services
|
||||
# -> enable the service in firewalld settings
|
||||
```
|
||||
|
||||
### Conditional Compilation
|
||||
|
||||
Currently only x11, wayland, windows and MacOS are supported backends.
|
||||
Depending on the toolchain used, support for other platforms is omitted
|
||||
automatically (it does not make sense to build a Windows `.exe` with
|
||||
support for x11 and wayland backends).
|
||||
|
||||
However one might still want to omit support for e.g. wayland, x11 or libei on
|
||||
a Linux system.
|
||||
|
||||
This is possible through
|
||||
[cargo features](https://doc.rust-lang.org/cargo/reference/features.html).
|
||||
|
||||
E.g. if only wayland support is needed, the following command produces
|
||||
an executable with just support for wayland:
|
||||
```sh
|
||||
cargo build --no-default-features --features wayland
|
||||
```
|
||||
For a detailed list of available features, checkout the [Cargo.toml](./Cargo.toml)
|
||||
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
### Install Dependencies
|
||||
#### Macos
|
||||
```sh
|
||||
brew install libadwaita
|
||||
@@ -62,6 +147,9 @@ sudo dnf install libadwaita-devel libXtst-devel libX11-devel
|
||||
```
|
||||
|
||||
#### Windows
|
||||
> [!NOTE]
|
||||
> This is only necessary when building lan-mouse from source. The windows release comes with precompiled gtk dlls.
|
||||
|
||||
Follow the instructions at [gtk-rs.org](https://gtk-rs.org/gtk4-rs/stable/latest/book/installation_windows.html)
|
||||
|
||||
*TLDR:*
|
||||
@@ -104,37 +192,6 @@ Make sure to add the directory `C:\gtk-build\gtk\x64\release\bin`
|
||||
To avoid building GTK from source, it is possible to disable
|
||||
the gtk frontend (see conditional compilation below).
|
||||
|
||||
### Build and run
|
||||
Build in release mode:
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Run directly:
|
||||
```sh
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
### Conditional Compilation
|
||||
|
||||
Currently only x11, wayland, windows and MacOS are supported backends.
|
||||
Depending on the toolchain used, support for other platforms is omitted
|
||||
automatically (it does not make sense to build a Windows `.exe` with
|
||||
support for x11 and wayland backends).
|
||||
|
||||
However one might still want to omit support for e.g. wayland, x11 or libei on
|
||||
a Linux system.
|
||||
|
||||
This is possible through
|
||||
[cargo features](https://doc.rust-lang.org/cargo/reference/features.html).
|
||||
|
||||
E.g. if only wayland support is needed, the following command produces
|
||||
an executable with just support for wayland:
|
||||
```sh
|
||||
cargo build --no-default-features --features wayland
|
||||
```
|
||||
For a detailed list of available features, checkout the [Cargo.toml](./Cargo.toml)
|
||||
|
||||
## Usage
|
||||
### Gtk Frontend
|
||||
By default the gtk frontend will open when running `lan-mouse`.
|
||||
@@ -168,6 +225,17 @@ To do so, add `--daemon` to the commandline args:
|
||||
$ cargo run --release -- --daemon
|
||||
```
|
||||
|
||||
In order to start lan-mouse with a graphical session automatically,
|
||||
the [systemd-service](service/lan-mouse.service) can be used:
|
||||
|
||||
Copy the file to `~/.config/systemd/user/` and enable the service:
|
||||
|
||||
```sh
|
||||
cp service/lan-mouse.service ~/.config/systemd/user
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now lan-mouse.service
|
||||
```
|
||||
|
||||
## Configuration
|
||||
To automatically load clients on startup, the file `$XDG_CONFIG_HOME/lan-mouse/config.toml` is parsed.
|
||||
`$XDG_CONFIG_HOME` defaults to `~/.config/`.
|
||||
@@ -188,7 +256,9 @@ port = 4242
|
||||
# define a client on the right side with host name "iridium"
|
||||
[right]
|
||||
# hostname
|
||||
host_name = "iridium"
|
||||
hostname = "iridium"
|
||||
# activate this client immediately when lan-mouse is started
|
||||
activate_on_startup = true
|
||||
# optional list of (known) ip addresses
|
||||
ips = ["192.168.178.156"]
|
||||
|
||||
@@ -196,7 +266,7 @@ ips = ["192.168.178.156"]
|
||||
[left]
|
||||
# The hostname is optional: When no hostname is specified,
|
||||
# at least one ip address needs to be specified.
|
||||
host_name = "thorium"
|
||||
hostname = "thorium"
|
||||
# ips for ethernet and wifi
|
||||
ips = ["192.168.178.189", "192.168.178.172"]
|
||||
# optional port
|
||||
@@ -210,11 +280,12 @@ Where `left` can be either `left`, `right`, `top` or `bottom`.
|
||||
- [x] respect xdg-config-home for config file location.
|
||||
- [x] IP Address switching
|
||||
- [x] Liveness tracking Automatically ungrab mouse when client unreachable
|
||||
- [ ] Liveness tracking: Automatically release keys, when server offline
|
||||
- [x] Liveness tracking: Automatically release keys, when server offline
|
||||
- [ ] Libei Input Capture
|
||||
- [ ] X11 Input Capture
|
||||
- [ ] Windows Input Capture
|
||||
- [ ] MacOS Input Capture
|
||||
- [ ] MaxOS KeyCode Translation
|
||||
- [ ] MacOS KeyCode Translation
|
||||
- [ ] Latency measurement and visualization
|
||||
- [ ] Bandwidth usage measurement and visualization
|
||||
- [ ] Clipboard support
|
||||
|
||||
@@ -8,7 +8,7 @@ port = 4242
|
||||
# define a client on the right side with host name "iridium"
|
||||
[right]
|
||||
# hostname
|
||||
host_name = "iridium"
|
||||
hostname = "iridium"
|
||||
# optional list of (known) ip addresses
|
||||
ips = ["192.168.178.156"]
|
||||
|
||||
@@ -16,7 +16,7 @@ ips = ["192.168.178.156"]
|
||||
[left]
|
||||
# The hostname is optional: When no hostname is specified,
|
||||
# at least one ip address needs to be specified.
|
||||
host_name = "thorium"
|
||||
hostname = "thorium"
|
||||
# ips for ethernet and wifi
|
||||
ips = ["192.168.178.189", "192.168.178.172"]
|
||||
# optional port
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Categories=Utility;
|
||||
Comment[en_US]=mouse & keyboard sharing via LAN
|
||||
Comment[en_US]=Mouse & Keyboard sharing via LAN
|
||||
Comment=Mouse & Keyboard sharing via LAN
|
||||
Comment[de_DE]=Maus- und Tastaturfreigabe über LAN
|
||||
Exec=lan-mouse
|
||||
Icon=mouse-icon.svg
|
||||
Icon=de.feschber.LanMouse
|
||||
Name[en_US]=Lan Mouse
|
||||
Name[de_DE]=Lan Maus
|
||||
Name=Lan Mouse
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Version=0.5.0
|
||||
8
firewall/lan-mouse.xml
Normal file
8
firewall/lan-mouse.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- for packaging: /usr/lib/firewalld/services/lan-mouse.xml -->
|
||||
<!-- configure manually: /etc/firewalld/services/lan-mouse.xml -->
|
||||
<service>
|
||||
<short>LAN Mouse</short>
|
||||
<description>mouse and keyboard sharing via LAN</description>
|
||||
<port port="4242" protocol="udp"/>
|
||||
</service>
|
||||
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
@@ -7,6 +7,6 @@
|
||||
<file compressed="true">style-dark.css</file>
|
||||
</gresource>
|
||||
<gresource prefix="/de/feschber/LanMouse/icons">
|
||||
<file compressed="true" preprocess="xml-stripblanks">mouse-icon.svg</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">de.feschber.LanMouse.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<object class="AdwStatusPage">
|
||||
<property name="title" translatable="yes">Lan Mouse</property>
|
||||
<property name="description" translatable="yes">easily use your mouse and keyboard on multiple computers</property>
|
||||
<property name="icon-name">mouse-icon</property>
|
||||
<property name="icon-name">de.feschber.LanMouse</property>
|
||||
<property name="child">
|
||||
<object class="AdwClamp">
|
||||
<property name="maximum-size">600</property>
|
||||
|
||||
13
service/lan-mouse.service
Normal file
13
service/lan-mouse.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Lan Mouse
|
||||
# lan mouse needs an active graphical session
|
||||
After=graphical-session.target
|
||||
# make sure the service terminates with the graphical session
|
||||
BindsTo=graphical-session.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/lan-mouse --daemon
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -5,13 +5,20 @@ use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint};
|
||||
use core_graphics::event::{
|
||||
CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, EventField, ScrollEventUnit,
|
||||
CGEvent, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField, ScrollEventUnit,
|
||||
};
|
||||
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
||||
use keycode::{KeyMap, KeyMapping};
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::time::Duration;
|
||||
use tokio::task::AbortHandle;
|
||||
|
||||
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
|
||||
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
|
||||
|
||||
pub struct MacOSConsumer {
|
||||
pub event_source: CGEventSource,
|
||||
repeat_task: Option<AbortHandle>,
|
||||
button_state: ButtonState,
|
||||
}
|
||||
|
||||
@@ -59,6 +66,7 @@ impl MacOSConsumer {
|
||||
Ok(Self {
|
||||
event_source,
|
||||
button_state,
|
||||
repeat_task: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,6 +74,37 @@ impl MacOSConsumer {
|
||||
let event: CGEvent = CGEvent::new(self.event_source.clone()).ok()?;
|
||||
Some(event.location())
|
||||
}
|
||||
|
||||
async fn spawn_repeat_task(&mut self, key: u16) {
|
||||
// there can only be one repeating key and it's
|
||||
// always the last to be pressed
|
||||
self.kill_repeat_task();
|
||||
let event_source = self.event_source.clone();
|
||||
let repeat_task = tokio::task::spawn_local(async move {
|
||||
tokio::time::sleep(DEFAULT_REPEAT_DELAY).await;
|
||||
loop {
|
||||
key_event(event_source.clone(), key, 1);
|
||||
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await;
|
||||
}
|
||||
});
|
||||
self.repeat_task = Some(repeat_task.abort_handle());
|
||||
}
|
||||
fn kill_repeat_task(&mut self) {
|
||||
if let Some(task) = self.repeat_task.take() {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_event(event_source: CGEventSource, key: u16, state: u8) {
|
||||
let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) {
|
||||
Ok(e) => e,
|
||||
Err(_) => {
|
||||
log::warn!("unable to create key event");
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -209,22 +248,24 @@ impl EventConsumer for MacOSConsumer {
|
||||
PointerEvent::Frame { .. } => {}
|
||||
},
|
||||
Event::Keyboard(keyboard_event) => match keyboard_event {
|
||||
KeyboardEvent::Key { .. } => {
|
||||
/*
|
||||
let code = CGKeyCode::from_le(key as u16);
|
||||
let event = match CGEvent::new_keyboard_event(
|
||||
self.event_source.clone(),
|
||||
code,
|
||||
match state { 1 => true, _ => false }
|
||||
) {
|
||||
Ok(e) => e,
|
||||
KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
} => {
|
||||
let code = match KeyMap::from_key_mapping(KeyMapping::Evdev(key as u16)) {
|
||||
Ok(k) => k.mac as CGKeyCode,
|
||||
Err(_) => {
|
||||
log::warn!("unable to create key event");
|
||||
return
|
||||
log::warn!("unable to map key event");
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.post(CGEventTapLocation::HID);
|
||||
*/
|
||||
match state {
|
||||
// pressed
|
||||
1 => self.spawn_repeat_task(code).await,
|
||||
_ => self.kill_repeat_task(),
|
||||
}
|
||||
key_event(self.event_source.clone(), code, state)
|
||||
}
|
||||
KeyboardEvent::Modifiers { .. } => {}
|
||||
},
|
||||
|
||||
@@ -5,6 +5,8 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use std::time::Duration;
|
||||
use tokio::task::AbortHandle;
|
||||
use winapi::um::winuser::{SendInput, KEYEVENTF_EXTENDEDKEY};
|
||||
use winapi::{
|
||||
self,
|
||||
@@ -21,11 +23,16 @@ use crate::{
|
||||
event::Event,
|
||||
};
|
||||
|
||||
pub struct WindowsConsumer {}
|
||||
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
|
||||
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
|
||||
|
||||
pub struct WindowsConsumer {
|
||||
repeat_task: Option<AbortHandle>,
|
||||
}
|
||||
|
||||
impl WindowsConsumer {
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {})
|
||||
Ok(Self { repeat_task: None })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +65,15 @@ impl EventConsumer for WindowsConsumer {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
} => key_event(key, state),
|
||||
} => {
|
||||
match state {
|
||||
// pressed
|
||||
0 => self.kill_repeat_task(),
|
||||
1 => self.spawn_repeat_task(key).await,
|
||||
_ => {}
|
||||
}
|
||||
key_event(key, state)
|
||||
}
|
||||
KeyboardEvent::Modifiers { .. } => {}
|
||||
},
|
||||
_ => {}
|
||||
@@ -72,6 +87,27 @@ impl EventConsumer for WindowsConsumer {
|
||||
async fn destroy(&mut self) {}
|
||||
}
|
||||
|
||||
impl WindowsConsumer {
|
||||
async fn spawn_repeat_task(&mut self, key: u32) {
|
||||
// there can only be one repeating key and it's
|
||||
// always the last to be pressed
|
||||
self.kill_repeat_task();
|
||||
let repeat_task = tokio::task::spawn_local(async move {
|
||||
tokio::time::sleep(DEFAULT_REPEAT_DELAY).await;
|
||||
loop {
|
||||
key_event(key, 1);
|
||||
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await;
|
||||
}
|
||||
});
|
||||
self.repeat_task = Some(repeat_task.abort_handle());
|
||||
}
|
||||
fn kill_repeat_task(&mut self) {
|
||||
if let Some(task) = self.repeat_task.take() {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_mouse_input(mi: MOUSEINPUT) {
|
||||
unsafe {
|
||||
let mut input = INPUT {
|
||||
|
||||
@@ -24,9 +24,13 @@ impl Default for DummyProducer {
|
||||
}
|
||||
|
||||
impl EventProducer for DummyProducer {
|
||||
fn notify(&mut self, _: ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for DummyProducer {
|
||||
|
||||
@@ -3,7 +3,11 @@ use std::{io, task::Poll};
|
||||
|
||||
use futures_core::Stream;
|
||||
|
||||
use crate::{client::ClientHandle, event::Event, producer::EventProducer};
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle},
|
||||
event::Event,
|
||||
producer::EventProducer,
|
||||
};
|
||||
|
||||
pub struct LibeiProducer {}
|
||||
|
||||
@@ -14,9 +18,13 @@ impl LibeiProducer {
|
||||
}
|
||||
|
||||
impl EventProducer for LibeiProducer {
|
||||
fn notify(&mut self, _event: crate::client::ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for LibeiProducer {
|
||||
|
||||
@@ -23,7 +23,11 @@ impl Stream for MacOSProducer {
|
||||
}
|
||||
|
||||
impl EventProducer for MacOSProducer {
|
||||
fn notify(&mut self, _event: ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ struct Window {
|
||||
buffer: wl_buffer::WlBuffer,
|
||||
surface: wl_surface::WlSurface,
|
||||
layer_surface: ZwlrLayerSurfaceV1,
|
||||
pos: Position,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -179,6 +180,7 @@ impl Window {
|
||||
surface.set_input_region(None);
|
||||
surface.commit();
|
||||
Window {
|
||||
pos,
|
||||
buffer,
|
||||
surface,
|
||||
layer_surface,
|
||||
@@ -381,6 +383,24 @@ impl WaylandEventProducer {
|
||||
|
||||
Ok(WaylandEventProducer(inner))
|
||||
}
|
||||
|
||||
fn add_client(&mut self, handle: ClientHandle, pos: Position) {
|
||||
self.0.get_mut().state.add_client(handle, pos);
|
||||
}
|
||||
|
||||
fn delete_client(&mut self, handle: ClientHandle) {
|
||||
let inner = self.0.get_mut();
|
||||
// remove all windows corresponding to this client
|
||||
while let Some(i) = inner
|
||||
.state
|
||||
.client_for_window
|
||||
.iter()
|
||||
.position(|(_, c)| *c == handle)
|
||||
{
|
||||
inner.state.client_for_window.remove(i);
|
||||
inner.state.focused = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
@@ -475,6 +495,19 @@ impl State {
|
||||
self.client_for_window.push((window, client));
|
||||
});
|
||||
}
|
||||
|
||||
fn update_windows(&mut self) {
|
||||
log::debug!("updating windows");
|
||||
log::debug!("output info: {:?}", self.output_info);
|
||||
let clients: Vec<_> = self
|
||||
.client_for_window
|
||||
.drain(..)
|
||||
.map(|(w, c)| (c, w.pos))
|
||||
.collect();
|
||||
for (client, pos) in clients {
|
||||
self.add_client(client, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
@@ -527,50 +560,42 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_events(&mut self) {
|
||||
fn flush_events(&mut self) -> io::Result<()> {
|
||||
// flush outgoing events
|
||||
match self.queue.flush() {
|
||||
Ok(_) => (),
|
||||
Err(e) => match e {
|
||||
WaylandError::Io(e) => {
|
||||
log::error!("error writing to wayland socket: {e}")
|
||||
return Err(e);
|
||||
}
|
||||
WaylandError::Protocol(e) => {
|
||||
panic!("wayland protocol violation: {e}")
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl EventProducer for WaylandEventProducer {
|
||||
fn notify(&mut self, client_event: ClientEvent) {
|
||||
fn notify(&mut self, client_event: ClientEvent) -> io::Result<()> {
|
||||
match client_event {
|
||||
ClientEvent::Create(handle, pos) => {
|
||||
self.0.get_mut().state.add_client(handle, pos);
|
||||
self.add_client(handle, pos);
|
||||
}
|
||||
ClientEvent::Destroy(handle) => {
|
||||
let inner = self.0.get_mut();
|
||||
// remove all windows corresponding to this client
|
||||
while let Some(i) = inner
|
||||
.state
|
||||
.client_for_window
|
||||
.iter()
|
||||
.position(|(_, c)| *c == handle)
|
||||
{
|
||||
inner.state.client_for_window.remove(i);
|
||||
inner.state.focused = None;
|
||||
}
|
||||
self.delete_client(handle);
|
||||
}
|
||||
}
|
||||
let inner = self.0.get_mut();
|
||||
inner.flush_events();
|
||||
inner.flush_events()
|
||||
}
|
||||
|
||||
fn release(&mut self) {
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
log::debug!("releasing pointer");
|
||||
let inner = self.0.get_mut();
|
||||
inner.state.ungrab();
|
||||
inner.flush_events();
|
||||
inner.flush_events()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,7 +626,11 @@ impl Stream for WaylandEventProducer {
|
||||
inner.dispatch_events();
|
||||
|
||||
// flush outgoing events
|
||||
inner.flush_events();
|
||||
if let Err(e) = inner.flush_events() {
|
||||
if e.kind() != ErrorKind::WouldBlock {
|
||||
return Poll::Ready(Some(Err(e)));
|
||||
}
|
||||
}
|
||||
|
||||
// prepare for the next read
|
||||
match inner.prepare_read() {
|
||||
@@ -683,6 +712,16 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
app.pending_events.push_back((*client, Event::Enter()));
|
||||
}
|
||||
wl_pointer::Event::Leave { .. } => {
|
||||
/* There are rare cases, where when a window is opened in
|
||||
* just the wrong moment, the pointer is released, while
|
||||
* still grabbed.
|
||||
* In that case, the pointer must be ungrabbed, otherwise
|
||||
* it is impossible to grab it again (since the pointer
|
||||
* lock, relative pointer,... objects are still in place)
|
||||
*/
|
||||
if app.pointer_lock.is_some() {
|
||||
log::warn!("compositor released mouse");
|
||||
}
|
||||
app.ungrab();
|
||||
}
|
||||
wl_pointer::Event::Button {
|
||||
@@ -923,6 +962,21 @@ impl Dispatch<ZxdgOutputV1, WlOutput> for State {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_output::WlOutput, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &wl_output::WlOutput,
|
||||
event: <wl_output::WlOutput as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_output::Event::Done = event {
|
||||
state.update_windows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't emit any events
|
||||
delegate_noop!(State: wl_region::WlRegion);
|
||||
delegate_noop!(State: wl_shm_pool::WlShmPool);
|
||||
@@ -933,7 +987,6 @@ delegate_noop!(State: ZwpKeyboardShortcutsInhibitManagerV1);
|
||||
delegate_noop!(State: ZwpPointerConstraintsV1);
|
||||
|
||||
// ignore events
|
||||
delegate_noop!(State: ignore wl_output::WlOutput);
|
||||
delegate_noop!(State: ignore ZxdgOutputManagerV1);
|
||||
delegate_noop!(State: ignore wl_shm::WlShm);
|
||||
delegate_noop!(State: ignore wl_buffer::WlBuffer);
|
||||
|
||||
@@ -12,9 +12,13 @@ use crate::{
|
||||
pub struct WindowsProducer {}
|
||||
|
||||
impl EventProducer for WindowsProducer {
|
||||
fn notify(&mut self, _: ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsProducer {
|
||||
|
||||
@@ -18,9 +18,13 @@ impl X11Producer {
|
||||
}
|
||||
|
||||
impl EventProducer for X11Producer {
|
||||
fn notify(&mut self, _: ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for X11Producer {
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::{
|
||||
collections::HashSet,
|
||||
fmt::Display,
|
||||
net::{IpAddr, SocketAddr},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -47,6 +46,20 @@ impl Display for Position {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Position {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
match s {
|
||||
"left" => Ok(Position::Left),
|
||||
"right" => Ok(Position::Right),
|
||||
"top" => Ok(Position::Top),
|
||||
"bottom" => Ok(Position::Bottom),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Client {
|
||||
/// hostname of this client
|
||||
@@ -57,21 +70,17 @@ pub struct Client {
|
||||
/// This way any event consumer / producer backend does not
|
||||
/// need to know anything about a client other than its handle.
|
||||
pub handle: ClientHandle,
|
||||
/// `active` address of the client, used to send data to.
|
||||
/// This should generally be the socket address where data
|
||||
/// was last received from.
|
||||
pub active_addr: Option<SocketAddr>,
|
||||
/// all socket addresses associated with a particular client
|
||||
/// all ip addresses associated with a particular client
|
||||
/// e.g. Laptops usually have at least an ethernet and a wifi port
|
||||
/// which have different ip addresses
|
||||
pub addrs: HashSet<SocketAddr>,
|
||||
pub ips: HashSet<IpAddr>,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ClientEvent {
|
||||
Create(ClientHandle, Position),
|
||||
Destroy(ClientHandle),
|
||||
@@ -81,11 +90,18 @@ pub type ClientHandle = u32;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientState {
|
||||
/// information about the client
|
||||
pub client: Client,
|
||||
/// events should be sent to and received from the client
|
||||
pub active: bool,
|
||||
pub last_ping: Option<Instant>,
|
||||
pub last_seen: Option<Instant>,
|
||||
pub last_replied: Option<Instant>,
|
||||
/// `active` address of the client, used to send data to.
|
||||
/// This should generally be the socket address where data
|
||||
/// was last received from.
|
||||
pub active_addr: Option<SocketAddr>,
|
||||
/// tracks whether or not the client is responding to pings
|
||||
pub alive: bool,
|
||||
/// keys currently pressed by this client
|
||||
pub pressed_keys: HashSet<u32>,
|
||||
}
|
||||
|
||||
pub struct ClientManager {
|
||||
@@ -110,26 +126,20 @@ impl ClientManager {
|
||||
ips: HashSet<IpAddr>,
|
||||
port: u16,
|
||||
pos: Position,
|
||||
active: bool,
|
||||
) -> ClientHandle {
|
||||
// get a new client_handle
|
||||
let handle = self.free_id();
|
||||
|
||||
// we dont know, which IP is initially active
|
||||
let active_addr = None;
|
||||
|
||||
// store fix ip addresses
|
||||
let fix_ips = ips.iter().cloned().collect();
|
||||
|
||||
// map ip addresses to socket addresses
|
||||
let addrs = HashSet::from_iter(ips.into_iter().map(|ip| SocketAddr::new(ip, port)));
|
||||
|
||||
// store the client
|
||||
let client = Client {
|
||||
hostname,
|
||||
fix_ips,
|
||||
handle,
|
||||
active_addr,
|
||||
addrs,
|
||||
ips,
|
||||
port,
|
||||
pos,
|
||||
};
|
||||
@@ -137,10 +147,10 @@ impl ClientManager {
|
||||
// client was never seen, nor pinged
|
||||
let client_state = ClientState {
|
||||
client,
|
||||
last_ping: None,
|
||||
last_seen: None,
|
||||
last_replied: None,
|
||||
active: false,
|
||||
active,
|
||||
active_addr: None,
|
||||
alive: false,
|
||||
pressed_keys: HashSet::new(),
|
||||
};
|
||||
|
||||
if handle as usize >= self.clients.len() {
|
||||
@@ -160,7 +170,7 @@ impl ClientManager {
|
||||
.iter()
|
||||
.position(|c| {
|
||||
if let Some(c) = c {
|
||||
c.active && c.client.addrs.contains(&addr)
|
||||
c.active && c.client.ips.contains(&addr.ip())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -15,17 +15,19 @@ pub const DEFAULT_PORT: u16 = 4242;
|
||||
pub struct ConfigToml {
|
||||
pub port: Option<u16>,
|
||||
pub frontend: Option<String>,
|
||||
pub left: Option<Client>,
|
||||
pub right: Option<Client>,
|
||||
pub top: Option<Client>,
|
||||
pub bottom: Option<Client>,
|
||||
pub left: Option<TomlClient>,
|
||||
pub right: Option<TomlClient>,
|
||||
pub top: Option<TomlClient>,
|
||||
pub bottom: Option<TomlClient>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
pub struct Client {
|
||||
pub struct TomlClient {
|
||||
pub hostname: Option<String>,
|
||||
pub host_name: Option<String>,
|
||||
pub ips: Option<Vec<IpAddr>>,
|
||||
pub port: Option<u16>,
|
||||
pub activate_on_startup: Option<bool>,
|
||||
}
|
||||
|
||||
impl ConfigToml {
|
||||
@@ -66,10 +68,18 @@ pub enum Frontend {
|
||||
pub struct Config {
|
||||
pub frontend: Frontend,
|
||||
pub port: u16,
|
||||
pub clients: Vec<(Client, Position)>,
|
||||
pub clients: Vec<(TomlClient, Position)>,
|
||||
pub daemon: bool,
|
||||
}
|
||||
|
||||
pub struct ConfigClient {
|
||||
pub ips: HashSet<IpAddr>,
|
||||
pub hostname: Option<String>,
|
||||
pub port: u16,
|
||||
pub pos: Position,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Result<Self> {
|
||||
let args = CliArgs::parse();
|
||||
@@ -93,7 +103,7 @@ impl Config {
|
||||
|
||||
let config_toml = match ConfigToml::new(config_path.as_str()) {
|
||||
Err(e) => {
|
||||
log::error!("{config_path}: {e}");
|
||||
log::warn!("{config_path}: {e}");
|
||||
log::warn!("Continuing without config file ...");
|
||||
None
|
||||
}
|
||||
@@ -128,7 +138,7 @@ impl Config {
|
||||
},
|
||||
};
|
||||
|
||||
let mut clients: Vec<(Client, Position)> = vec![];
|
||||
let mut clients: Vec<(TomlClient, Position)> = vec![];
|
||||
|
||||
if let Some(config_toml) = config_toml {
|
||||
if let Some(c) = config_toml.right {
|
||||
@@ -155,18 +165,28 @@ impl Config {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_clients(&self) -> Vec<(HashSet<IpAddr>, Option<String>, u16, Position)> {
|
||||
pub fn get_clients(&self) -> Vec<ConfigClient> {
|
||||
self.clients
|
||||
.iter()
|
||||
.map(|(c, p)| {
|
||||
.map(|(c, pos)| {
|
||||
let port = c.port.unwrap_or(DEFAULT_PORT);
|
||||
let ips: HashSet<IpAddr> = if let Some(ips) = c.ips.as_ref() {
|
||||
HashSet::from_iter(ips.iter().cloned())
|
||||
} else {
|
||||
HashSet::new()
|
||||
};
|
||||
let host_name = c.host_name.clone();
|
||||
(ips, host_name, port, *p)
|
||||
let hostname = match &c.hostname {
|
||||
Some(h) => Some(h.clone()),
|
||||
None => c.host_name.clone(),
|
||||
};
|
||||
let active = c.activate_on_startup.unwrap_or(false);
|
||||
ConfigClient {
|
||||
ips,
|
||||
hostname,
|
||||
port,
|
||||
pos: *pos,
|
||||
active,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
77
src/event.rs
77
src/event.rs
@@ -1,3 +1,4 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
@@ -10,7 +11,7 @@ pub const BTN_MIDDLE: u32 = 0x112;
|
||||
pub const BTN_BACK: u32 = 0x113;
|
||||
pub const BTN_FORWARD: u32 = 0x114;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum PointerEvent {
|
||||
Motion {
|
||||
time: u32,
|
||||
@@ -30,7 +31,7 @@ pub enum PointerEvent {
|
||||
Frame {},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum KeyboardEvent {
|
||||
Key {
|
||||
time: u32,
|
||||
@@ -45,7 +46,7 @@ pub enum KeyboardEvent {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub enum Event {
|
||||
/// pointer event (motion / button / axis)
|
||||
Pointer(PointerEvent),
|
||||
@@ -65,6 +66,9 @@ pub enum Event {
|
||||
/// response to a ping event: this event signals that a client
|
||||
/// is still alive but must otherwise be ignored
|
||||
Pong(),
|
||||
/// explicit disconnect request. The client will no longer
|
||||
/// send events until the next Enter event. All of its keys should be released.
|
||||
Disconnect(),
|
||||
}
|
||||
|
||||
impl Display for PointerEvent {
|
||||
@@ -120,6 +124,7 @@ impl Display for Event {
|
||||
Event::Leave() => write!(f, "leave"),
|
||||
Event::Ping() => write!(f, "ping"),
|
||||
Event::Pong() => write!(f, "pong"),
|
||||
Event::Disconnect() => write!(f, "disconnect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +138,7 @@ impl Event {
|
||||
Self::Leave() => EventType::Leave,
|
||||
Self::Ping() => EventType::Ping,
|
||||
Self::Pong() => EventType::Pong,
|
||||
Self::Disconnect() => EventType::Disconnect,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,18 +180,19 @@ enum EventType {
|
||||
Leave,
|
||||
Ping,
|
||||
Pong,
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PointerEventType {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
match value {
|
||||
x if x == Self::Motion as u8 => Ok(Self::Motion),
|
||||
x if x == Self::Button as u8 => Ok(Self::Button),
|
||||
x if x == Self::Axis as u8 => Ok(Self::Axis),
|
||||
x if x == Self::Frame as u8 => Ok(Self::Frame),
|
||||
_ => Err(Box::new(ProtocolError {
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid pointer event type {}", value),
|
||||
})),
|
||||
}
|
||||
@@ -193,13 +200,13 @@ impl TryFrom<u8> for PointerEventType {
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for KeyboardEventType {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
match value {
|
||||
x if x == Self::Key as u8 => Ok(Self::Key),
|
||||
x if x == Self::Modifiers as u8 => Ok(Self::Modifiers),
|
||||
_ => Err(Box::new(ProtocolError {
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid keyboard event type {}", value),
|
||||
})),
|
||||
}
|
||||
@@ -216,6 +223,7 @@ impl From<&Event> for Vec<u8> {
|
||||
Event::Leave() => vec![],
|
||||
Event::Ping() => vec![],
|
||||
Event::Pong() => vec![],
|
||||
Event::Disconnect() => vec![],
|
||||
};
|
||||
[event_id, event_data].concat()
|
||||
}
|
||||
@@ -234,9 +242,9 @@ impl fmt::Display for ProtocolError {
|
||||
impl Error for ProtocolError {}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Event {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: Vec<u8>) -> Result<Self> {
|
||||
let event_id = u8::from_be_bytes(value[..1].try_into()?);
|
||||
match event_id {
|
||||
i if i == (EventType::Pointer as u8) => Ok(Event::Pointer(value.try_into()?)),
|
||||
@@ -245,7 +253,8 @@ impl TryFrom<Vec<u8>> for Event {
|
||||
i if i == (EventType::Leave as u8) => Ok(Event::Leave()),
|
||||
i if i == (EventType::Ping as u8) => Ok(Event::Ping()),
|
||||
i if i == (EventType::Pong as u8) => Ok(Event::Pong()),
|
||||
_ => Err(Box::new(ProtocolError {
|
||||
i if i == (EventType::Disconnect as u8) => Ok(Event::Disconnect()),
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid event_id {}", event_id),
|
||||
})),
|
||||
}
|
||||
@@ -291,9 +300,9 @@ impl From<&PointerEvent> for Vec<u8> {
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
fn try_from(data: Vec<u8>) -> Result<Self> {
|
||||
match data.get(1) {
|
||||
Some(id) => {
|
||||
let event_type = match id.to_owned().try_into() {
|
||||
@@ -305,7 +314,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
@@ -313,7 +322,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let relative_x = match data.get(6..14) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
@@ -321,7 +330,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let relative_y = match data.get(14..22) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
@@ -336,7 +345,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
@@ -344,7 +353,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let button = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
@@ -352,7 +361,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let state = match data.get(10..14) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
@@ -367,7 +376,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
@@ -375,7 +384,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let axis = match data.get(6) {
|
||||
Some(d) => *d,
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 1 Byte at index 6".into(),
|
||||
}));
|
||||
}
|
||||
@@ -383,7 +392,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let value = match data.get(7..15) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 7".into(),
|
||||
}));
|
||||
}
|
||||
@@ -393,7 +402,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
PointerEventType::Frame => Ok(Self::Frame {}),
|
||||
}
|
||||
}
|
||||
None => Err(Box::new(ProtocolError {
|
||||
None => Err(anyhow!(ProtocolError {
|
||||
msg: "Expected an element at index 0".into(),
|
||||
})),
|
||||
}
|
||||
@@ -434,9 +443,9 @@ impl From<&KeyboardEvent> for Vec<u8> {
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
fn try_from(data: Vec<u8>) -> Result<Self> {
|
||||
match data.get(1) {
|
||||
Some(id) => {
|
||||
let event_type = match id.to_owned().try_into() {
|
||||
@@ -448,7 +457,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
@@ -456,7 +465,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let key = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
@@ -464,7 +473,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let state = match data.get(10) {
|
||||
Some(d) => *d,
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 1 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
@@ -475,7 +484,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let mods_depressed = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
@@ -483,7 +492,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let mods_latched = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
@@ -491,7 +500,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let mods_locked = match data.get(10..14) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
@@ -499,7 +508,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let group = match data.get(14..18) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 18".into(),
|
||||
}))
|
||||
}
|
||||
@@ -513,7 +522,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Err(Box::new(ProtocolError {
|
||||
None => Err(anyhow!(ProtocolError {
|
||||
msg: "Expected an element at index 0".into(),
|
||||
})),
|
||||
}
|
||||
|
||||
@@ -103,11 +103,13 @@ pub enum FrontendEvent {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum FrontendNotify {
|
||||
NotifyClientCreate(ClientHandle, Option<String>, u16, Position),
|
||||
NotifyClientUpdate(ClientHandle, Option<String>, u16, Position),
|
||||
NotifyClientActivate(ClientHandle, bool),
|
||||
NotifyClientCreate(Client),
|
||||
NotifyClientUpdate(Client),
|
||||
NotifyClientDelete(ClientHandle),
|
||||
/// new port, reason of failure (if failed)
|
||||
NotifyPortChange(u16, Option<String>),
|
||||
/// Client State, active
|
||||
Enumerate(Vec<(Client, bool)>),
|
||||
NotifyError(String),
|
||||
}
|
||||
@@ -223,7 +225,6 @@ impl FrontendListener {
|
||||
log::debug!("json: {json}, len: {}", payload.len());
|
||||
|
||||
let mut keep = vec![];
|
||||
|
||||
// TODO do simultaneously
|
||||
for tx in self.tx_streams.iter_mut() {
|
||||
// write len + payload
|
||||
|
||||
@@ -83,17 +83,26 @@ pub fn run() -> Result<()> {
|
||||
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::NotifyClientActivate(handle, active) => {
|
||||
if active {
|
||||
log::info!("client {handle} activated");
|
||||
} else {
|
||||
log::info!("client {handle} deactivated");
|
||||
}
|
||||
}
|
||||
FrontendNotify::NotifyClientUpdate(client, host, port, pos) => {
|
||||
log::info!(
|
||||
"client ({client}) updated: {}:{port} - {pos}",
|
||||
host.as_deref().unwrap_or("")
|
||||
);
|
||||
FrontendNotify::NotifyClientCreate(client) => {
|
||||
let handle = client.handle;
|
||||
let port = client.port;
|
||||
let pos = client.pos;
|
||||
let hostname = client.hostname.as_deref().unwrap_or("");
|
||||
log::info!("new client ({handle}): {hostname}:{port} - {pos}");
|
||||
}
|
||||
FrontendNotify::NotifyClientUpdate(client) => {
|
||||
let handle = client.handle;
|
||||
let port = client.port;
|
||||
let pos = client.pos;
|
||||
let hostname = client.hostname.as_deref().unwrap_or("");
|
||||
log::info!("client ({handle}) updated: {hostname}:{port} - {pos}");
|
||||
}
|
||||
FrontendNotify::NotifyClientDelete(client) => {
|
||||
log::info!("client ({client}) deleted.");
|
||||
@@ -109,7 +118,7 @@ pub fn run() -> Result<()> {
|
||||
client.hostname.as_deref().unwrap_or(""),
|
||||
if active { "yes" } else { "no" },
|
||||
client
|
||||
.addrs
|
||||
.ips
|
||||
.into_iter()
|
||||
.map(|a| a.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
|
||||
@@ -8,16 +8,12 @@ use std::{
|
||||
process, str,
|
||||
};
|
||||
|
||||
use crate::{config::DEFAULT_PORT, frontend::gtk::window::Window};
|
||||
use crate::frontend::gtk::window::Window;
|
||||
|
||||
use adw::Application;
|
||||
use gtk::{
|
||||
gdk::Display,
|
||||
gio::{SimpleAction, SimpleActionGroup},
|
||||
glib::clone,
|
||||
prelude::*,
|
||||
subclass::prelude::ObjectSubclassIsExt,
|
||||
CssProvider, IconTheme,
|
||||
gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, CssProvider,
|
||||
IconTheme,
|
||||
};
|
||||
use gtk::{gio, glib, prelude::ApplicationExt};
|
||||
|
||||
@@ -38,7 +34,12 @@ pub fn run() -> glib::ExitCode {
|
||||
#[cfg(not(windows))]
|
||||
let ret = gtk_main();
|
||||
|
||||
log::debug!("frontend exited");
|
||||
if ret == glib::ExitCode::FAILURE {
|
||||
log::error!("frontend exited with failure");
|
||||
} else {
|
||||
log::info!("frontend exited successfully");
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ fn gtk_main() -> glib::ExitCode {
|
||||
gio::resources_register_include!("lan-mouse.gresource").expect("Failed to register resources.");
|
||||
|
||||
let app = Application::builder()
|
||||
.application_id("de.feschber.lan-mouse")
|
||||
.application_id("de.feschber.LanMouse")
|
||||
.build();
|
||||
|
||||
app.connect_startup(|_| load_icons());
|
||||
@@ -68,8 +69,8 @@ fn load_css() {
|
||||
}
|
||||
|
||||
fn load_icons() {
|
||||
let icon_theme =
|
||||
IconTheme::for_display(&Display::default().expect("Could not connect to a display."));
|
||||
let display = &Display::default().expect("Could not connect to a display.");
|
||||
let icon_theme = IconTheme::for_display(display);
|
||||
icon_theme.add_resource_path("/de/feschber/LanMouse/icons");
|
||||
}
|
||||
|
||||
@@ -130,15 +131,17 @@ fn build_ui(app: &Application) {
|
||||
loop {
|
||||
let notify = receiver.recv().await.unwrap();
|
||||
match notify {
|
||||
FrontendNotify::NotifyClientCreate(client, hostname, port, position) => {
|
||||
window.new_client(client, hostname, port, position, false);
|
||||
FrontendNotify::NotifyClientActivate(handle, active) => {
|
||||
window.activate_client(handle, active);
|
||||
}
|
||||
FrontendNotify::NotifyClientCreate(client) => {
|
||||
window.new_client(client, false);
|
||||
},
|
||||
FrontendNotify::NotifyClientUpdate(client, hostname, port, position) => {
|
||||
log::info!("client updated: {client}, {}:{port}, {position}", hostname.unwrap_or("".to_string()));
|
||||
FrontendNotify::NotifyClientUpdate(client) => {
|
||||
window.update_client(client);
|
||||
}
|
||||
FrontendNotify::NotifyError(e) => {
|
||||
// TODO
|
||||
log::error!("{e}");
|
||||
window.show_toast(e.as_str());
|
||||
},
|
||||
FrontendNotify::NotifyClientDelete(client) => {
|
||||
window.delete_client(client);
|
||||
@@ -146,19 +149,11 @@ fn build_ui(app: &Application) {
|
||||
FrontendNotify::Enumerate(clients) => {
|
||||
for (client, active) in clients {
|
||||
if window.client_idx(client.handle).is_some() {
|
||||
continue
|
||||
window.activate_client(client.handle, active);
|
||||
window.update_client(client);
|
||||
} else {
|
||||
window.new_client(client, active);
|
||||
}
|
||||
window.new_client(
|
||||
client.handle,
|
||||
client.hostname,
|
||||
client.addrs
|
||||
.iter()
|
||||
.next()
|
||||
.map(|s| s.port())
|
||||
.unwrap_or(DEFAULT_PORT),
|
||||
client.pos,
|
||||
active,
|
||||
);
|
||||
}
|
||||
},
|
||||
FrontendNotify::NotifyPortChange(port, msg) => {
|
||||
@@ -172,37 +167,5 @@ fn build_ui(app: &Application) {
|
||||
}
|
||||
}));
|
||||
|
||||
let action_request_client_update =
|
||||
SimpleAction::new("request-client-update", Some(&u32::static_variant_type()));
|
||||
|
||||
// remove client
|
||||
let action_client_delete =
|
||||
SimpleAction::new("request-client-delete", Some(&u32::static_variant_type()));
|
||||
|
||||
// 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::<u32>()
|
||||
.unwrap();
|
||||
let Some(client) = window.clients().item(index) else {
|
||||
return;
|
||||
};
|
||||
let client = client.downcast_ref::<ClientObject>().unwrap();
|
||||
window.request_client_update(client);
|
||||
}));
|
||||
|
||||
action_client_delete.connect_activate(clone!(@weak window => move |_action, param| {
|
||||
log::debug!("delete-client");
|
||||
let idx = param.unwrap()
|
||||
.get::<u32>()
|
||||
.unwrap();
|
||||
window.request_client_delete(idx);
|
||||
}));
|
||||
|
||||
let actions = SimpleActionGroup::new();
|
||||
window.insert_action_group("win", Some(&actions));
|
||||
actions.add_action(&action_request_client_update);
|
||||
actions.add_action(&action_client_delete);
|
||||
window.present();
|
||||
}
|
||||
|
||||
@@ -3,26 +3,20 @@ mod imp;
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::glib::{self, Object};
|
||||
|
||||
use crate::client::ClientHandle;
|
||||
use crate::client::{Client, ClientHandle};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ClientObject(ObjectSubclass<imp::ClientObject>);
|
||||
}
|
||||
|
||||
impl ClientObject {
|
||||
pub fn new(
|
||||
handle: ClientHandle,
|
||||
hostname: Option<String>,
|
||||
port: u32,
|
||||
position: String,
|
||||
active: bool,
|
||||
) -> Self {
|
||||
pub fn new(client: Client, active: bool) -> Self {
|
||||
Object::builder()
|
||||
.property("handle", handle)
|
||||
.property("hostname", hostname)
|
||||
.property("port", port)
|
||||
.property("handle", client.handle)
|
||||
.property("hostname", client.hostname)
|
||||
.property("port", client.port as u32)
|
||||
.property("position", client.pos.to_string())
|
||||
.property("active", active)
|
||||
.property("position", position)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,12 @@ impl ClientRow {
|
||||
.sync_create()
|
||||
.build();
|
||||
|
||||
let switch_position_binding = client_object
|
||||
.bind_property("active", &self.imp().enable_switch.get(), "active")
|
||||
.bidirectional()
|
||||
.sync_create()
|
||||
.build();
|
||||
|
||||
let hostname_binding = client_object
|
||||
.bind_property("hostname", &self.imp().hostname.get(), "text")
|
||||
.transform_to(|_, v: Option<String>| {
|
||||
@@ -104,6 +110,7 @@ impl ClientRow {
|
||||
.build();
|
||||
|
||||
bindings.push(active_binding);
|
||||
bindings.push(switch_position_binding);
|
||||
bindings.push(hostname_binding);
|
||||
bindings.push(title_binding);
|
||||
bindings.push(port_binding);
|
||||
|
||||
@@ -4,6 +4,8 @@ use adw::subclass::prelude::*;
|
||||
use adw::{prelude::*, ActionRow, ComboRow};
|
||||
use glib::{subclass::InitializingObject, Binding};
|
||||
use gtk::glib::clone;
|
||||
use gtk::glib::once_cell::sync::Lazy;
|
||||
use gtk::glib::subclass::Signal;
|
||||
use gtk::{glib, Button, CompositeTemplate, Switch};
|
||||
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
@@ -28,6 +30,8 @@ pub struct ClientRow {
|
||||
impl ObjectSubclass for ClientRow {
|
||||
// `NAME` needs to match `class` attribute of template
|
||||
const NAME: &'static str = "ClientRow";
|
||||
const ABSTRACT: bool = false;
|
||||
|
||||
type Type = super::ClientRow;
|
||||
type ParentType = adw::ExpanderRow;
|
||||
|
||||
@@ -49,28 +53,33 @@ impl ObjectImpl for ClientRow {
|
||||
row.handle_client_delete(button);
|
||||
}));
|
||||
}
|
||||
|
||||
fn signals() -> &'static [glib::subclass::Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![
|
||||
Signal::builder("request-update")
|
||||
.param_types([bool::static_type()])
|
||||
.build(),
|
||||
Signal::builder("request-delete").build(),
|
||||
]
|
||||
});
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl ClientRow {
|
||||
#[template_callback]
|
||||
fn handle_client_set_state(&self, state: bool, switch: &Switch) -> bool {
|
||||
let idx = self.obj().index() as u32;
|
||||
switch
|
||||
.activate_action("win.request-client-update", Some(&idx.to_variant()))
|
||||
.unwrap();
|
||||
switch.set_state(state);
|
||||
|
||||
fn handle_client_set_state(&self, state: bool, _switch: &Switch) -> bool {
|
||||
log::debug!("state change -> requesting update");
|
||||
self.obj().emit_by_name::<()>("request-update", &[&state]);
|
||||
true // dont run default handler
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn handle_client_delete(&self, button: &Button) {
|
||||
log::debug!("delete button pressed");
|
||||
let idx = self.obj().index() as u32;
|
||||
button
|
||||
.activate_action("win.request-client-delete", Some(&idx.to_variant()))
|
||||
.unwrap();
|
||||
fn handle_client_delete(&self, _button: &Button) {
|
||||
log::debug!("delete button pressed -> requesting delete");
|
||||
self.obj().emit_by_name::<()>("request-delete", &[]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,14 @@ use std::io::Write;
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use glib::{clone, Object};
|
||||
use gtk::{gio, glib, NoSelection};
|
||||
use gtk::{
|
||||
gio,
|
||||
glib::{self, closure_local},
|
||||
NoSelection,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
client::{ClientHandle, Position},
|
||||
client::{Client, ClientHandle, Position},
|
||||
config::DEFAULT_PORT,
|
||||
frontend::{gtk::client_object::ClientObject, FrontendEvent},
|
||||
};
|
||||
@@ -45,6 +49,18 @@ impl Window {
|
||||
clone!(@weak self as window => @default-panic, move |obj| {
|
||||
let client_object = obj.downcast_ref().expect("Expected object of type `ClientObject`.");
|
||||
let row = window.create_client_row(client_object);
|
||||
row.connect_closure("request-update", false, closure_local!(@strong window => move |row: ClientRow, active: bool| {
|
||||
let index = row.index() as u32;
|
||||
let Some(client) = window.clients().item(index) else {
|
||||
return;
|
||||
};
|
||||
let client = client.downcast_ref::<ClientObject>().unwrap();
|
||||
window.request_client_update(client, active);
|
||||
}));
|
||||
row.connect_closure("request-delete", false, closure_local!(@strong window => move |row: ClientRow| {
|
||||
let index = row.index() as u32;
|
||||
window.request_client_delete(index);
|
||||
}));
|
||||
row.upcast()
|
||||
})
|
||||
);
|
||||
@@ -62,7 +78,7 @@ impl Window {
|
||||
}
|
||||
|
||||
fn setup_icon(&self) {
|
||||
self.set_icon_name(Some("mouse-icon"));
|
||||
self.set_icon_name(Some("de.feschber.LanMouse"));
|
||||
}
|
||||
|
||||
fn create_client_row(&self, client_object: &ClientObject) -> ClientRow {
|
||||
@@ -71,15 +87,8 @@ impl Window {
|
||||
row
|
||||
}
|
||||
|
||||
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);
|
||||
pub fn new_client(&self, client: Client, active: bool) {
|
||||
let client = ClientObject::new(client, active);
|
||||
self.clients().append(&client);
|
||||
self.set_placeholder_visible(false);
|
||||
}
|
||||
@@ -106,6 +115,42 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_client(&self, client: Client) {
|
||||
let Some(idx) = self.client_idx(client.handle) else {
|
||||
log::warn!("could not find client with handle {}", client.handle);
|
||||
return;
|
||||
};
|
||||
let client_object = self.clients().item(idx as u32).unwrap();
|
||||
let client_object: &ClientObject = client_object.downcast_ref().unwrap();
|
||||
let data = client_object.get_data();
|
||||
|
||||
/* only change if it actually has changed, otherwise
|
||||
* the update signal is triggered */
|
||||
if data.hostname != client.hostname {
|
||||
client_object.set_hostname(client.hostname.unwrap_or("".into()));
|
||||
}
|
||||
if data.port != client.port as u32 {
|
||||
client_object.set_port(client.port as u32);
|
||||
}
|
||||
if data.position != client.pos.to_string() {
|
||||
client_object.set_position(client.pos.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_client(&self, handle: ClientHandle, active: bool) {
|
||||
let Some(idx) = self.client_idx(handle) else {
|
||||
log::warn!("could not find client with handle {handle}");
|
||||
return;
|
||||
};
|
||||
let client_object = self.clients().item(idx as u32).unwrap();
|
||||
let client_object: &ClientObject = client_object.downcast_ref().unwrap();
|
||||
let data = client_object.get_data();
|
||||
if data.active != active {
|
||||
client_object.set_active(active);
|
||||
log::debug!("set active to {active}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_client_create(&self) {
|
||||
let event = FrontendEvent::AddClient(None, DEFAULT_PORT, Position::default());
|
||||
self.imp().set_port(DEFAULT_PORT);
|
||||
@@ -121,13 +166,10 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_client_update(&self, client: &ClientObject) {
|
||||
pub fn request_client_update(&self, client: &ClientObject, active: bool) {
|
||||
let data = client.get_data();
|
||||
let position = match data.position.as_str() {
|
||||
"left" => Position::Left,
|
||||
"right" => Position::Right,
|
||||
"top" => Position::Top,
|
||||
"bottom" => Position::Bottom,
|
||||
let position = match Position::try_from(data.position.as_str()) {
|
||||
Ok(pos) => pos,
|
||||
_ => {
|
||||
log::error!("invalid position: {}", data.position);
|
||||
return;
|
||||
@@ -135,10 +177,13 @@ impl Window {
|
||||
};
|
||||
let hostname = data.hostname;
|
||||
let port = data.port as u16;
|
||||
|
||||
let event = FrontendEvent::UpdateClient(client.handle(), hostname, port, position);
|
||||
log::debug!("requesting update: {event:?}");
|
||||
self.request(event);
|
||||
|
||||
let event = FrontendEvent::ActivateClient(client.handle(), !client.active());
|
||||
let event = FrontendEvent::ActivateClient(client.handle(), active);
|
||||
log::debug!("requesting activate: {event:?}");
|
||||
self.request(event);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ use std::net::TcpStream;
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use adw::subclass::prelude::*;
|
||||
use adw::{
|
||||
prelude::{EditableExt, WidgetExt},
|
||||
ActionRow, ToastOverlay,
|
||||
};
|
||||
use adw::{prelude::*, ActionRow, ToastOverlay};
|
||||
use glib::subclass::InitializingObject;
|
||||
use gtk::{gio, glib, Button, CompositeTemplate, Entry, ListBox};
|
||||
|
||||
@@ -42,6 +39,8 @@ pub struct Window {
|
||||
impl ObjectSubclass for Window {
|
||||
// `NAME` needs to match `class` attribute of template
|
||||
const NAME: &'static str = "LanMouseWindow";
|
||||
const ABSTRACT: bool = false;
|
||||
|
||||
type Type = super::Window;
|
||||
type ParentType = adw::ApplicationWindow;
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ pub fn run() -> Result<()> {
|
||||
// run a frontend
|
||||
let mut service = start_service()?;
|
||||
frontend::run_frontend(&config)?;
|
||||
log::info!("terminating service");
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// on unix we give the service a chance to terminate gracefully
|
||||
@@ -65,7 +64,9 @@ fn run_service(config: &Config) -> Result<()> {
|
||||
runtime.block_on(LocalSet::new().run_until(async {
|
||||
// run main loop
|
||||
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
|
||||
Server::run(config).await?;
|
||||
|
||||
let server = Server::new(config);
|
||||
server.run().await?;
|
||||
|
||||
log::debug!("service exiting");
|
||||
anyhow::Ok(())
|
||||
|
||||
@@ -54,8 +54,8 @@ pub async fn create() -> Box<dyn EventProducer> {
|
||||
|
||||
pub trait EventProducer: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin {
|
||||
/// notify event producer of configuration changes
|
||||
fn notify(&mut self, event: ClientEvent);
|
||||
fn notify(&mut self, event: ClientEvent) -> io::Result<()>;
|
||||
|
||||
/// release mouse
|
||||
fn release(&mut self);
|
||||
fn release(&mut self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
963
src/server.rs
963
src/server.rs
File diff suppressed because it is too large
Load Diff
240
src/server/consumer_task.rs
Normal file
240
src/server/consumer_task.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio::{
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle},
|
||||
consumer::EventConsumer,
|
||||
event::{Event, KeyboardEvent},
|
||||
scancode,
|
||||
server::State,
|
||||
};
|
||||
|
||||
use super::{ProducerEvent, Server};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ConsumerEvent {
|
||||
/// consumer is notified of a change in client states
|
||||
ClientEvent(ClientEvent),
|
||||
/// consumer must release keys for client
|
||||
ReleaseKeys(ClientHandle),
|
||||
/// termination signal
|
||||
Terminate,
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
mut consumer: Box<dyn EventConsumer>,
|
||||
server: Server,
|
||||
mut udp_rx: Receiver<Result<(Event, SocketAddr)>>,
|
||||
sender_tx: Sender<(Event, SocketAddr)>,
|
||||
producer_tx: Sender<ProducerEvent>,
|
||||
timer_tx: Sender<()>,
|
||||
) -> (JoinHandle<Result<()>>, Sender<ConsumerEvent>) {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||
let consumer_task = tokio::task::spawn_local(async move {
|
||||
let mut last_ignored = None;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
udp_event = udp_rx.recv() => {
|
||||
let udp_event = udp_event.ok_or(anyhow!("receiver closed"))??;
|
||||
handle_udp_rx(&server, &producer_tx, &mut consumer, &sender_tx, &mut last_ignored, udp_event, &timer_tx).await;
|
||||
}
|
||||
consumer_event = rx.recv() => {
|
||||
match consumer_event {
|
||||
Some(e) => match e {
|
||||
ConsumerEvent::ClientEvent(e) => consumer.notify(e).await,
|
||||
ConsumerEvent::ReleaseKeys(c) => release_keys(&server, &mut consumer, c).await,
|
||||
ConsumerEvent::Terminate => break,
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
res = consumer.dispatch() => {
|
||||
res?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// release potentially still pressed keys
|
||||
let clients = server
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get_client_states()
|
||||
.map(|s| s.client.handle)
|
||||
.collect::<Vec<_>>();
|
||||
for client in clients {
|
||||
release_keys(&server, &mut consumer, client).await;
|
||||
}
|
||||
|
||||
// destroy consumer
|
||||
consumer.destroy().await;
|
||||
anyhow::Ok(())
|
||||
});
|
||||
(consumer_task, tx)
|
||||
}
|
||||
|
||||
async fn handle_udp_rx(
|
||||
server: &Server,
|
||||
producer_notify_tx: &Sender<ProducerEvent>,
|
||||
consumer: &mut Box<dyn EventConsumer>,
|
||||
sender_tx: &Sender<(Event, SocketAddr)>,
|
||||
last_ignored: &mut Option<SocketAddr>,
|
||||
event: (Event, SocketAddr),
|
||||
timer_tx: &Sender<()>,
|
||||
) {
|
||||
let (event, addr) = event;
|
||||
|
||||
// get handle for addr
|
||||
let handle = match server.client_manager.borrow().get_client(addr) {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
if last_ignored.is_none() || last_ignored.is_some() && last_ignored.unwrap() != addr {
|
||||
log::warn!("ignoring events from client {addr}");
|
||||
last_ignored.replace(addr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// next event can be logged as ignored again
|
||||
last_ignored.take();
|
||||
|
||||
log::trace!("{:20} <-<-<-<------ {addr} ({handle})", event.to_string());
|
||||
{
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let client_state = match client_manager.get_mut(handle) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
log::error!("unknown handle");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// reset ttl for client and
|
||||
client_state.alive = true;
|
||||
// set addr as new default for this client
|
||||
client_state.active_addr = Some(addr);
|
||||
}
|
||||
|
||||
match (event, addr) {
|
||||
(Event::Pong(), _) => { /* ignore pong events */ }
|
||||
(Event::Ping(), addr) => {
|
||||
let _ = sender_tx.send((Event::Pong(), addr)).await;
|
||||
}
|
||||
(Event::Disconnect(), _) => {
|
||||
release_keys(server, consumer, handle).await;
|
||||
}
|
||||
(event, addr) => {
|
||||
// tell clients that we are ready to receive events
|
||||
if let Event::Enter() = event {
|
||||
let _ = sender_tx.send((Event::Leave(), addr)).await;
|
||||
}
|
||||
|
||||
match server.state.get() {
|
||||
State::Sending => {
|
||||
if let Event::Leave() = event {
|
||||
// ignore additional leave events that may
|
||||
// have been sent for redundancy
|
||||
} else {
|
||||
// upon receiving any event, we go back to receiving mode
|
||||
server.state.replace(State::Receiving);
|
||||
let _ = producer_notify_tx.send(ProducerEvent::Release).await;
|
||||
log::trace!("STATE ===> Receiving");
|
||||
}
|
||||
}
|
||||
State::Receiving => {
|
||||
let mut ignore_event = false;
|
||||
if let Event::Keyboard(KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
}) = event
|
||||
{
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let client_state =
|
||||
if let Some(client_state) = client_manager.get_mut(handle) {
|
||||
client_state
|
||||
} else {
|
||||
log::error!("unknown handle");
|
||||
return;
|
||||
};
|
||||
if state == 0 {
|
||||
// ignore release event if key not pressed
|
||||
ignore_event = !client_state.pressed_keys.remove(&key);
|
||||
} else {
|
||||
// ignore press event if key not released
|
||||
ignore_event = !client_state.pressed_keys.insert(key);
|
||||
let _ = timer_tx.try_send(());
|
||||
}
|
||||
}
|
||||
// ignore double press / release events to
|
||||
// workaround buggy rdp backend.
|
||||
if !ignore_event {
|
||||
// consume event
|
||||
consumer.consume(event, handle).await;
|
||||
log::trace!("{event:?} => consumer");
|
||||
}
|
||||
}
|
||||
State::AwaitingLeave => {
|
||||
// we just entered the deadzone of a client, so
|
||||
// we need to ignore events that may still
|
||||
// be on the way until a leave event occurs
|
||||
// telling us the client registered the enter
|
||||
if let Event::Leave() = event {
|
||||
server.state.replace(State::Sending);
|
||||
log::trace!("STATE ===> Sending");
|
||||
}
|
||||
|
||||
// entering a client that is waiting for a leave
|
||||
// event should still be possible
|
||||
if let Event::Enter() = event {
|
||||
server.state.replace(State::Receiving);
|
||||
let _ = producer_notify_tx.send(ProducerEvent::Release).await;
|
||||
log::trace!("STATE ===> Receiving");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn release_keys(
|
||||
server: &Server,
|
||||
consumer: &mut Box<dyn EventConsumer>,
|
||||
client: ClientHandle,
|
||||
) {
|
||||
let keys = server
|
||||
.client_manager
|
||||
.borrow_mut()
|
||||
.get_mut(client)
|
||||
.iter_mut()
|
||||
.flat_map(|s| s.pressed_keys.drain())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for key in keys {
|
||||
let event = Event::Keyboard(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key,
|
||||
state: 0,
|
||||
});
|
||||
consumer.consume(event, client).await;
|
||||
if let Ok(key) = scancode::Linux::try_from(key) {
|
||||
log::warn!("releasing stuck key: {key:?}");
|
||||
}
|
||||
}
|
||||
|
||||
let modifiers_event = KeyboardEvent::Modifiers {
|
||||
mods_depressed: 0,
|
||||
mods_latched: 0,
|
||||
mods_locked: 0,
|
||||
group: 0,
|
||||
};
|
||||
consumer
|
||||
.consume(Event::Keyboard(modifiers_event), client)
|
||||
.await;
|
||||
}
|
||||
326
src/server/frontend_task.rs
Normal file
326
src/server/frontend_task.rs
Normal file
@@ -0,0 +1,326 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
io::ErrorKind,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
#[cfg(windows)]
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use tokio::{
|
||||
io::ReadHalf,
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle, Position},
|
||||
frontend::{self, FrontendEvent, FrontendListener, FrontendNotify},
|
||||
};
|
||||
|
||||
use super::{
|
||||
consumer_task::ConsumerEvent, producer_task::ProducerEvent, resolver_task::DnsRequest, Server,
|
||||
};
|
||||
|
||||
pub(crate) fn new(
|
||||
mut frontend: FrontendListener,
|
||||
mut notify_rx: Receiver<FrontendNotify>,
|
||||
server: Server,
|
||||
producer_notify: Sender<ProducerEvent>,
|
||||
consumer_notify: Sender<ConsumerEvent>,
|
||||
resolve_ch: Sender<DnsRequest>,
|
||||
port_tx: Sender<u16>,
|
||||
) -> (JoinHandle<Result<()>>, Sender<FrontendEvent>) {
|
||||
let (event_tx, mut event_rx) = tokio::sync::mpsc::channel(32);
|
||||
let event_tx_clone = event_tx.clone();
|
||||
let frontend_task = tokio::task::spawn_local(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
stream = frontend.accept() => {
|
||||
let stream = match stream {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::warn!("error accepting frontend connection: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
handle_frontend_stream(&event_tx_clone, stream).await;
|
||||
}
|
||||
event = event_rx.recv() => {
|
||||
let frontend_event = event.ok_or(anyhow!("frontend channel closed"))?;
|
||||
if handle_frontend_event(&server, &producer_notify, &consumer_notify, &resolve_ch, &mut frontend, &port_tx, frontend_event).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
notify = notify_rx.recv() => {
|
||||
let notify = notify.ok_or(anyhow!("frontend notify closed"))?;
|
||||
let _ = frontend.notify_all(notify).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
});
|
||||
(frontend_task, event_tx)
|
||||
}
|
||||
|
||||
async fn handle_frontend_stream(
|
||||
frontend_tx: &Sender<FrontendEvent>,
|
||||
#[cfg(unix)] mut stream: ReadHalf<UnixStream>,
|
||||
#[cfg(windows)] mut stream: ReadHalf<TcpStream>,
|
||||
) {
|
||||
use std::io;
|
||||
|
||||
let tx = frontend_tx.clone();
|
||||
tokio::task::spawn_local(async move {
|
||||
let _ = tx.send(FrontendEvent::Enumerate()).await;
|
||||
loop {
|
||||
let event = frontend::read_event(&mut stream).await;
|
||||
match event {
|
||||
Ok(event) => {
|
||||
let _ = tx.send(event).await;
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(e) = e.downcast_ref::<io::Error>() {
|
||||
if e.kind() == ErrorKind::UnexpectedEof {
|
||||
return;
|
||||
}
|
||||
}
|
||||
log::error!("error reading frontend event: {e}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn handle_frontend_event(
|
||||
server: &Server,
|
||||
producer_tx: &Sender<ProducerEvent>,
|
||||
consumer_tx: &Sender<ConsumerEvent>,
|
||||
resolve_tx: &Sender<DnsRequest>,
|
||||
frontend: &mut FrontendListener,
|
||||
port_tx: &Sender<u16>,
|
||||
event: FrontendEvent,
|
||||
) -> bool {
|
||||
log::debug!("frontend: {event:?}");
|
||||
let response = match event {
|
||||
FrontendEvent::AddClient(hostname, port, pos) => {
|
||||
let handle = add_client(server, resolve_tx, hostname, HashSet::new(), port, pos).await;
|
||||
|
||||
let client = server
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get(handle)
|
||||
.unwrap()
|
||||
.client
|
||||
.clone();
|
||||
Some(FrontendNotify::NotifyClientCreate(client))
|
||||
}
|
||||
FrontendEvent::ActivateClient(handle, active) => {
|
||||
activate_client(server, producer_tx, consumer_tx, handle, active).await;
|
||||
Some(FrontendNotify::NotifyClientActivate(handle, active))
|
||||
}
|
||||
FrontendEvent::ChangePort(port) => {
|
||||
let _ = port_tx.send(port).await;
|
||||
None
|
||||
}
|
||||
FrontendEvent::DelClient(handle) => {
|
||||
remove_client(server, producer_tx, consumer_tx, frontend, handle).await;
|
||||
Some(FrontendNotify::NotifyClientDelete(handle))
|
||||
}
|
||||
FrontendEvent::Enumerate() => {
|
||||
let clients = server
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get_client_states()
|
||||
.map(|s| (s.client.clone(), s.active))
|
||||
.collect();
|
||||
Some(FrontendNotify::Enumerate(clients))
|
||||
}
|
||||
FrontendEvent::Shutdown() => {
|
||||
log::info!("terminating gracefully...");
|
||||
return true;
|
||||
}
|
||||
FrontendEvent::UpdateClient(handle, hostname, port, pos) => {
|
||||
update_client(
|
||||
server,
|
||||
producer_tx,
|
||||
consumer_tx,
|
||||
resolve_tx,
|
||||
(handle, hostname, port, pos),
|
||||
)
|
||||
.await;
|
||||
|
||||
let client = server
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get(handle)
|
||||
.unwrap()
|
||||
.client
|
||||
.clone();
|
||||
Some(FrontendNotify::NotifyClientUpdate(client))
|
||||
}
|
||||
};
|
||||
let Some(response) = response else {
|
||||
return false;
|
||||
};
|
||||
if let Err(e) = frontend.notify_all(response).await {
|
||||
log::error!("error notifying frontend: {e}");
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn add_client(
|
||||
server: &Server,
|
||||
resolver_tx: &Sender<DnsRequest>,
|
||||
hostname: Option<String>,
|
||||
addr: HashSet<IpAddr>,
|
||||
port: u16,
|
||||
pos: Position,
|
||||
) -> ClientHandle {
|
||||
log::info!(
|
||||
"adding client [{}]{} @ {:?}",
|
||||
pos,
|
||||
hostname.as_deref().unwrap_or(""),
|
||||
&addr
|
||||
);
|
||||
let handle =
|
||||
server
|
||||
.client_manager
|
||||
.borrow_mut()
|
||||
.add_client(hostname.clone(), addr, port, pos, false);
|
||||
|
||||
log::debug!("add_client {handle}");
|
||||
|
||||
if let Some(hostname) = hostname {
|
||||
let _ = resolver_tx.send(DnsRequest { hostname, handle }).await;
|
||||
}
|
||||
|
||||
handle
|
||||
}
|
||||
|
||||
pub async fn activate_client(
|
||||
server: &Server,
|
||||
producer_notify_tx: &Sender<ProducerEvent>,
|
||||
consumer_notify_tx: &Sender<ConsumerEvent>,
|
||||
client: ClientHandle,
|
||||
active: bool,
|
||||
) {
|
||||
let (client, pos) = match server.client_manager.borrow_mut().get_mut(client) {
|
||||
Some(state) => {
|
||||
state.active = active;
|
||||
(state.client.handle, state.client.pos)
|
||||
}
|
||||
None => return,
|
||||
};
|
||||
if active {
|
||||
let _ = producer_notify_tx
|
||||
.send(ProducerEvent::ClientEvent(ClientEvent::Create(client, pos)))
|
||||
.await;
|
||||
let _ = consumer_notify_tx
|
||||
.send(ConsumerEvent::ClientEvent(ClientEvent::Create(client, pos)))
|
||||
.await;
|
||||
} else {
|
||||
let _ = producer_notify_tx
|
||||
.send(ProducerEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||
.await;
|
||||
let _ = consumer_notify_tx
|
||||
.send(ConsumerEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_client(
|
||||
server: &Server,
|
||||
producer_notify_tx: &Sender<ProducerEvent>,
|
||||
consumer_notify_tx: &Sender<ConsumerEvent>,
|
||||
frontend: &mut FrontendListener,
|
||||
client: ClientHandle,
|
||||
) -> Option<ClientHandle> {
|
||||
let _ = producer_notify_tx
|
||||
.send(ProducerEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||
.await;
|
||||
let _ = consumer_notify_tx
|
||||
.send(ConsumerEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||
.await;
|
||||
|
||||
let Some(client) = server
|
||||
.client_manager
|
||||
.borrow_mut()
|
||||
.remove_client(client)
|
||||
.map(|s| s.client.handle)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let notify = FrontendNotify::NotifyClientDelete(client);
|
||||
log::debug!("{notify:?}");
|
||||
if let Err(e) = frontend.notify_all(notify).await {
|
||||
log::error!("error notifying frontend: {e}");
|
||||
}
|
||||
Some(client)
|
||||
}
|
||||
|
||||
async fn update_client(
|
||||
server: &Server,
|
||||
producer_notify_tx: &Sender<ProducerEvent>,
|
||||
consumer_notify_tx: &Sender<ConsumerEvent>,
|
||||
resolve_tx: &Sender<DnsRequest>,
|
||||
client_update: (ClientHandle, Option<String>, u16, Position),
|
||||
) {
|
||||
let (handle, hostname, port, pos) = client_update;
|
||||
let (hostname, handle, active) = {
|
||||
// retrieve state
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let Some(state) = client_manager.get_mut(handle) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// update pos
|
||||
state.client.pos = pos;
|
||||
|
||||
// update port
|
||||
if state.client.port != port {
|
||||
state.client.port = port;
|
||||
state.active_addr = state.active_addr.map(|a| SocketAddr::new(a.ip(), port));
|
||||
}
|
||||
|
||||
// update hostname
|
||||
if state.client.hostname != hostname {
|
||||
state.client.ips = HashSet::new();
|
||||
state.active_addr = None;
|
||||
state.client.hostname = hostname;
|
||||
}
|
||||
|
||||
log::debug!("client updated: {:?}", state);
|
||||
(
|
||||
state.client.hostname.clone(),
|
||||
state.client.handle,
|
||||
state.active,
|
||||
)
|
||||
};
|
||||
|
||||
// resolve dns
|
||||
if let Some(hostname) = hostname {
|
||||
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
||||
}
|
||||
|
||||
// update state in event consumer & producer
|
||||
if active {
|
||||
let _ = producer_notify_tx
|
||||
.send(ProducerEvent::ClientEvent(ClientEvent::Destroy(handle)))
|
||||
.await;
|
||||
let _ = consumer_notify_tx
|
||||
.send(ConsumerEvent::ClientEvent(ClientEvent::Destroy(handle)))
|
||||
.await;
|
||||
let _ = producer_notify_tx
|
||||
.send(ProducerEvent::ClientEvent(ClientEvent::Create(handle, pos)))
|
||||
.await;
|
||||
let _ = consumer_notify_tx
|
||||
.send(ConsumerEvent::ClientEvent(ClientEvent::Create(handle, pos)))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
90
src/server/network_task.rs
Normal file
90
src/server/network_task.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::{
|
||||
net::UdpSocket,
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{event::Event, frontend::FrontendNotify};
|
||||
|
||||
use super::Server;
|
||||
|
||||
pub async fn new(
|
||||
server: Server,
|
||||
frontend_notify_tx: Sender<FrontendNotify>,
|
||||
) -> Result<(
|
||||
JoinHandle<()>,
|
||||
Sender<(Event, SocketAddr)>,
|
||||
Receiver<Result<(Event, SocketAddr)>>,
|
||||
Sender<u16>,
|
||||
)> {
|
||||
// bind the udp socket
|
||||
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), server.port.get());
|
||||
let mut socket = UdpSocket::bind(listen_addr).await?;
|
||||
let (receiver_tx, receiver_rx) = tokio::sync::mpsc::channel(32);
|
||||
let (sender_tx, mut sender_rx) = tokio::sync::mpsc::channel(32);
|
||||
let (port_tx, mut port_rx) = tokio::sync::mpsc::channel(32);
|
||||
|
||||
let udp_task = tokio::task::spawn_local(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
event = receive_event(&socket) => {
|
||||
let _ = receiver_tx.send(event).await;
|
||||
}
|
||||
event = sender_rx.recv() => {
|
||||
let Some((event, addr)) = event else {
|
||||
break;
|
||||
};
|
||||
if let Err(e) = send_event(&socket, event, addr) {
|
||||
log::warn!("udp send failed: {e}");
|
||||
};
|
||||
}
|
||||
port = port_rx.recv() => {
|
||||
let Some(port) = port else {
|
||||
break;
|
||||
};
|
||||
|
||||
if socket.local_addr().unwrap().port() == port {
|
||||
continue;
|
||||
}
|
||||
|
||||
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port);
|
||||
match UdpSocket::bind(listen_addr).await {
|
||||
Ok(new_socket) => {
|
||||
socket = new_socket;
|
||||
server.port.replace(port);
|
||||
let _ = frontend_notify_tx.send(FrontendNotify::NotifyPortChange(port, None)).await;
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("could not change port: {e}");
|
||||
let port = socket.local_addr().unwrap().port();
|
||||
let _ = frontend_notify_tx.send(FrontendNotify::NotifyPortChange(
|
||||
port,
|
||||
Some(format!("could not change port: {e}")),
|
||||
)).await;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok((udp_task, sender_tx, receiver_rx, port_tx))
|
||||
}
|
||||
|
||||
async fn receive_event(socket: &UdpSocket) -> Result<(Event, SocketAddr)> {
|
||||
let mut buf = vec![0u8; 22];
|
||||
let (_amt, src) = socket.recv_from(&mut buf).await?;
|
||||
Ok((Event::try_from(buf)?, src))
|
||||
}
|
||||
|
||||
fn send_event(sock: &UdpSocket, e: Event, addr: SocketAddr) -> Result<usize> {
|
||||
log::trace!("{:20} ------>->->-> {addr}", e.to_string());
|
||||
let data: Vec<u8> = (&e).into();
|
||||
// When udp blocks, we dont want to block the event loop.
|
||||
// Dropping events is better than potentially crashing the event
|
||||
// producer.
|
||||
Ok(sock.try_send_to(&data, addr)?)
|
||||
}
|
||||
131
src/server/ping_task.rs
Normal file
131
src/server/ping_task.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use std::{net::SocketAddr, time::Duration};
|
||||
|
||||
use tokio::{
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{client::ClientHandle, event::Event};
|
||||
|
||||
use super::{consumer_task::ConsumerEvent, producer_task::ProducerEvent, Server, State};
|
||||
|
||||
const MAX_RESPONSE_TIME: Duration = Duration::from_millis(500);
|
||||
|
||||
pub fn new(
|
||||
server: Server,
|
||||
sender_ch: Sender<(Event, SocketAddr)>,
|
||||
consumer_notify: Sender<ConsumerEvent>,
|
||||
producer_notify: Sender<ProducerEvent>,
|
||||
mut timer_rx: Receiver<()>,
|
||||
) -> JoinHandle<()> {
|
||||
// timer task
|
||||
let ping_task = tokio::task::spawn_local(async move {
|
||||
loop {
|
||||
// wait for wake up signal
|
||||
let Some(_): Option<()> = timer_rx.recv().await else {
|
||||
break;
|
||||
};
|
||||
loop {
|
||||
let receiving = server.state.get() == State::Receiving;
|
||||
let (ping_clients, ping_addrs) = {
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
|
||||
let ping_clients: Vec<ClientHandle> = if receiving {
|
||||
// if receiving we care about clients with pressed keys
|
||||
client_manager
|
||||
.get_client_states_mut()
|
||||
.filter(|s| !s.pressed_keys.is_empty())
|
||||
.map(|s| s.client.handle)
|
||||
.collect()
|
||||
} else {
|
||||
// if sending we care about the active client
|
||||
server.active_client.get().iter().cloned().collect()
|
||||
};
|
||||
|
||||
// get relevant socket addrs for clients
|
||||
let ping_addrs: Vec<SocketAddr> = {
|
||||
ping_clients
|
||||
.iter()
|
||||
.flat_map(|&c| client_manager.get(c))
|
||||
.flat_map(|state| {
|
||||
if state.alive && state.active_addr.is_some() {
|
||||
vec![state.active_addr.unwrap()]
|
||||
} else {
|
||||
state
|
||||
.client
|
||||
.ips
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|ip| SocketAddr::new(ip, state.client.port))
|
||||
.collect()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
// reset alive
|
||||
for state in client_manager.get_client_states_mut() {
|
||||
state.alive = false;
|
||||
}
|
||||
|
||||
(ping_clients, ping_addrs)
|
||||
};
|
||||
|
||||
if receiving && ping_clients.is_empty() {
|
||||
// receiving and no client has pressed keys
|
||||
// -> no need to keep pinging
|
||||
break;
|
||||
}
|
||||
|
||||
// ping clients
|
||||
for addr in ping_addrs {
|
||||
if sender_ch.send((Event::Ping(), addr)).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// give clients time to resond
|
||||
if receiving {
|
||||
log::debug!("waiting {MAX_RESPONSE_TIME:?} for response from client with pressed keys ...");
|
||||
} else {
|
||||
log::debug!(
|
||||
"state: {:?} => waiting {MAX_RESPONSE_TIME:?} for client to respond ...",
|
||||
server.state.get()
|
||||
);
|
||||
}
|
||||
|
||||
tokio::time::sleep(MAX_RESPONSE_TIME).await;
|
||||
|
||||
// when anything is received from a client,
|
||||
// the alive flag gets set
|
||||
let unresponsive_clients: Vec<_> = {
|
||||
let client_manager = server.client_manager.borrow();
|
||||
ping_clients
|
||||
.iter()
|
||||
.filter_map(|&c| match client_manager.get(c) {
|
||||
Some(state) if !state.alive => Some(c),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
// we may not be receiving anymore but we should respond
|
||||
// to the original state and not the "new" one
|
||||
if receiving {
|
||||
for c in unresponsive_clients {
|
||||
log::warn!("device not responding, releasing keys!");
|
||||
let _ = consumer_notify.send(ConsumerEvent::ReleaseKeys(c)).await;
|
||||
}
|
||||
} else {
|
||||
// release pointer if the active client has not responded
|
||||
if !unresponsive_clients.is_empty() {
|
||||
log::warn!("client not responding, releasing pointer!");
|
||||
server.state.replace(State::Receiving);
|
||||
let _ = producer_notify.send(ProducerEvent::Release).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ping_task
|
||||
}
|
||||
132
src/server/producer_task.rs
Normal file
132
src/server/producer_task.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::StreamExt;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio::{sync::mpsc::Sender, task::JoinHandle};
|
||||
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle},
|
||||
event::{Event, KeyboardEvent},
|
||||
producer::EventProducer,
|
||||
server::State,
|
||||
};
|
||||
|
||||
use super::Server;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ProducerEvent {
|
||||
/// producer must release the mouse
|
||||
Release,
|
||||
/// producer is notified of a change in client states
|
||||
ClientEvent(ClientEvent),
|
||||
/// termination signal
|
||||
Terminate,
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
mut producer: Box<dyn EventProducer>,
|
||||
server: Server,
|
||||
sender_tx: Sender<(Event, SocketAddr)>,
|
||||
timer_tx: Sender<()>,
|
||||
) -> (JoinHandle<Result<()>>, Sender<ProducerEvent>) {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||
let task = tokio::task::spawn_local(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
event = producer.next() => {
|
||||
let event = event.ok_or(anyhow!("event producer closed"))??;
|
||||
handle_producer_event(&server, &mut producer, &sender_tx, &timer_tx, event).await?;
|
||||
}
|
||||
e = rx.recv() => {
|
||||
log::debug!("producer notify rx: {e:?}");
|
||||
match e {
|
||||
Some(e) => match e {
|
||||
ProducerEvent::Release => {
|
||||
producer.release()?;
|
||||
server.state.replace(State::Receiving);
|
||||
|
||||
}
|
||||
ProducerEvent::ClientEvent(e) => producer.notify(e)?,
|
||||
ProducerEvent::Terminate => break,
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
});
|
||||
(task, tx)
|
||||
}
|
||||
|
||||
const RELEASE_MODIFIERDS: u32 = 77; // ctrl+shift+super+alt
|
||||
|
||||
async fn handle_producer_event(
|
||||
server: &Server,
|
||||
producer: &mut Box<dyn EventProducer>,
|
||||
sender_tx: &Sender<(Event, SocketAddr)>,
|
||||
timer_tx: &Sender<()>,
|
||||
event: (ClientHandle, Event),
|
||||
) -> Result<()> {
|
||||
let (c, mut e) = event;
|
||||
log::trace!("({c}) {e:?}");
|
||||
|
||||
if let Event::Keyboard(KeyboardEvent::Modifiers { mods_depressed, .. }) = e {
|
||||
if mods_depressed == RELEASE_MODIFIERDS {
|
||||
producer.release()?;
|
||||
server.state.replace(State::Receiving);
|
||||
log::trace!("STATE ===> Receiving");
|
||||
// send an event to release all the modifiers
|
||||
e = Event::Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
let (addr, enter, start_timer) = {
|
||||
let mut enter = false;
|
||||
let mut start_timer = false;
|
||||
|
||||
// get client state for handle
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let client_state = match client_manager.get_mut(c) {
|
||||
Some(state) => state,
|
||||
None => {
|
||||
// should not happen
|
||||
log::warn!("unknown client!");
|
||||
producer.release()?;
|
||||
server.state.replace(State::Receiving);
|
||||
log::trace!("STATE ===> Receiving");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// if we just entered the client we want to send additional enter events until
|
||||
// we get a leave event
|
||||
if let Event::Enter() = e {
|
||||
server.state.replace(State::AwaitingLeave);
|
||||
server
|
||||
.active_client
|
||||
.replace(Some(client_state.client.handle));
|
||||
log::trace!("Active client => {}", client_state.client.handle);
|
||||
start_timer = true;
|
||||
log::trace!("STATE ===> AwaitingLeave");
|
||||
enter = true;
|
||||
} else {
|
||||
// ignore any potential events in receiving mode
|
||||
if server.state.get() == State::Receiving && e != Event::Disconnect() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
(client_state.active_addr, enter, start_timer)
|
||||
};
|
||||
if start_timer {
|
||||
let _ = timer_tx.try_send(());
|
||||
}
|
||||
if let Some(addr) = addr {
|
||||
if enter {
|
||||
let _ = sender_tx.send((Event::Enter(), addr)).await;
|
||||
}
|
||||
let _ = sender_tx.send((e, addr)).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
40
src/server/resolver_task.rs
Normal file
40
src/server/resolver_task.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use tokio::{sync::mpsc::Sender, task::JoinHandle};
|
||||
|
||||
use crate::{client::ClientHandle, dns::DnsResolver};
|
||||
|
||||
use super::Server;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DnsRequest {
|
||||
pub hostname: String,
|
||||
pub handle: ClientHandle,
|
||||
}
|
||||
|
||||
pub fn new(resolver: DnsResolver, server: Server) -> (JoinHandle<()>, Sender<DnsRequest>) {
|
||||
let (dns_tx, mut dns_rx) = tokio::sync::mpsc::channel::<DnsRequest>(32);
|
||||
let resolver_task = tokio::task::spawn_local(async move {
|
||||
loop {
|
||||
let (host, handle) = match dns_rx.recv().await {
|
||||
Some(r) => (r.hostname, r.handle),
|
||||
None => break,
|
||||
};
|
||||
let ips = match resolver.resolve(&host).await {
|
||||
Ok(ips) => ips,
|
||||
Err(e) => {
|
||||
log::warn!("could not resolve host '{host}': {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Some(state) = server.client_manager.borrow_mut().get_mut(handle) {
|
||||
let mut addrs = HashSet::from_iter(state.client.fix_ips.iter().cloned());
|
||||
for ip in ips {
|
||||
addrs.insert(ip);
|
||||
}
|
||||
state.client.ips = addrs;
|
||||
}
|
||||
}
|
||||
});
|
||||
(resolver_task, dns_tx)
|
||||
}
|
||||
Reference in New Issue
Block a user