mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-07 20:09:59 +03:00
Compare commits
46 Commits
v0.7.3
...
fix-usize-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81e2c59e8f | ||
|
|
5fd3b719d6 | ||
|
|
e6d4585bb2 | ||
|
|
1c082d5c0c | ||
|
|
cd98acbd08 | ||
|
|
11e1919588 | ||
|
|
152bceaa86 | ||
|
|
60180d841c | ||
|
|
5802a0be0b | ||
|
|
1737727d61 | ||
|
|
ba46037a1f | ||
|
|
da768b6fb8 | ||
|
|
799b45104a | ||
|
|
e9738fc024 | ||
|
|
9969f997d3 | ||
|
|
b8cc9e2197 | ||
|
|
ba6abafe75 | ||
|
|
effb9ce0fa | ||
|
|
1f0d386d4a | ||
|
|
973360a774 | ||
|
|
e21ab02a6e | ||
|
|
18a3c10f8e | ||
|
|
c76d9ef7af | ||
|
|
5318f5a02d | ||
|
|
1e4312b3ce | ||
|
|
77aa96e09a | ||
|
|
636c5924bf | ||
|
|
3e96b42067 | ||
|
|
279e582698 | ||
|
|
9edd2f7f3b | ||
|
|
43c16a537b | ||
|
|
36855a1a17 | ||
|
|
e537cdbc7e | ||
|
|
5b76c3bcda | ||
|
|
81f65dcd3d | ||
|
|
f0099ee535 | ||
|
|
633d2c346e | ||
|
|
ccb201ea53 | ||
|
|
f7edfecba9 | ||
|
|
141ea2809d | ||
|
|
058097c618 | ||
|
|
f9eeb254d3 | ||
|
|
9ca7e2378c | ||
|
|
cc7984c066 | ||
|
|
1a2645cfbc | ||
|
|
e52febf457 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +1,2 @@
|
||||
github: [feschber]
|
||||
ko_fi: feschber
|
||||
|
||||
22
.github/workflows/cachix.yml
vendored
22
.github/workflows/cachix.yml
vendored
@@ -3,8 +3,14 @@ name: Binary Cache
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
jobs:
|
||||
nix:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-13
|
||||
- macos-14
|
||||
name: "Build"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -20,5 +26,15 @@ jobs:
|
||||
name: lan-mouse
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Build lan-mouse
|
||||
run: nix build --print-build-logs
|
||||
- name: Build lan-mouse (x86_64-linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: nix build --print-build-logs --show-trace .#packages.x86_64-linux.lan-mouse
|
||||
|
||||
- name: Build lan-mouse (x86_64-darwin)
|
||||
if: matrix.os == 'macos-13'
|
||||
run: nix build --print-build-logs --show-trace .#packages.x86_64-darwin.lan-mouse
|
||||
|
||||
- name: Build lan-mouse (aarch64-darwin)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: nix build --print-build-logs --show-trace .#packages.aarch64-darwin.lan-mouse
|
||||
|
||||
|
||||
2
.github/workflows/pre-release.yml
vendored
2
.github/workflows/pre-release.yml
vendored
@@ -78,7 +78,7 @@ jobs:
|
||||
path: lan-mouse-windows.zip
|
||||
|
||||
macos-release-build:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: install dependencies
|
||||
|
||||
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@@ -92,7 +92,7 @@ jobs:
|
||||
target/debug/*.dll
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: install dependencies
|
||||
|
||||
2
.github/workflows/tagged-release.yml
vendored
2
.github/workflows/tagged-release.yml
vendored
@@ -74,7 +74,7 @@ jobs:
|
||||
path: lan-mouse-windows.zip
|
||||
|
||||
macos-release-build:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: install dependencies
|
||||
|
||||
156
Cargo.lock
generated
156
Cargo.lock
generated
@@ -1131,6 +1131,51 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna 0.4.0",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-resolver"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"hickory-proto",
|
||||
"ipconfig",
|
||||
"lru-cache",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
@@ -1142,6 +1187,17 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"windows 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
@@ -1228,7 +1284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lan-mouse"
|
||||
version = "0.7.3"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
@@ -1236,29 +1292,33 @@ dependencies = [
|
||||
"async-trait",
|
||||
"clap",
|
||||
"core-graphics",
|
||||
"endi",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"glib-build-tools",
|
||||
"gtk4",
|
||||
"hickory-resolver",
|
||||
"hostname 0.4.0",
|
||||
"keycode",
|
||||
"libadwaita",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"reis",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slab",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"toml",
|
||||
"trust-dns-resolver",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-misc",
|
||||
"wayland-protocols-wlr",
|
||||
"windows",
|
||||
"windows 0.54.0",
|
||||
"x11",
|
||||
]
|
||||
|
||||
@@ -1427,6 +1487,27 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
@@ -1711,7 +1792,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
|
||||
dependencies = [
|
||||
"hostname",
|
||||
"hostname 0.3.1",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
@@ -2069,52 +2150,6 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-proto"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna 0.4.0",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-resolver"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"ipconfig",
|
||||
"lru-cache",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
@@ -2309,13 +2344,32 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
dependencies = [
|
||||
"windows-core 0.52.0",
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-core 0.54.0",
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
|
||||
24
Cargo.toml
24
Cargo.toml
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "lan-mouse"
|
||||
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
|
||||
version = "0.7.3"
|
||||
version = "0.8.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/ferdinandschober/lan-mouse"
|
||||
@@ -14,7 +14,7 @@ lto = "fat"
|
||||
|
||||
[dependencies]
|
||||
tempfile = "3.8"
|
||||
trust-dns-resolver = "0.23"
|
||||
hickory-resolver = "0.24.1"
|
||||
memmap = "0.7"
|
||||
toml = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -22,7 +22,7 @@ anyhow = "1.0.71"
|
||||
log = "0.4.20"
|
||||
env_logger = "0.11.3"
|
||||
serde_json = "1.0.107"
|
||||
tokio = {version = "1.32.0", features = ["io-util", "macros", "net", "rt", "sync", "signal"] }
|
||||
tokio = {version = "1.32.0", features = ["io-util", "io-std", "macros", "net", "process", "rt", "sync", "signal"] }
|
||||
async-trait = "0.1.73"
|
||||
futures-core = "0.3.28"
|
||||
futures = "0.3.28"
|
||||
@@ -32,6 +32,10 @@ adw = { package = "libadwaita", version = "0.6.0", features = ["v1_1"], optional
|
||||
async-channel = { version = "2.1.1", optional = true }
|
||||
keycode = "0.4.0"
|
||||
once_cell = "1.19.0"
|
||||
num_enum = "0.7.2"
|
||||
hostname = "0.4.0"
|
||||
slab = "0.4.9"
|
||||
endi = "1.1.0"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.148"
|
||||
@@ -49,10 +53,18 @@ reis = { version = "0.2", features = [ "tokio" ], optional = true }
|
||||
core-graphics = { version = "0.23", features = ["highsierra"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.54.0", features = [ "Win32_UI_Input_KeyboardAndMouse" ] }
|
||||
windows = { version = "0.54.0", features = [
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Threading",
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
] }
|
||||
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.19.0"
|
||||
glib-build-tools = { version = "0.19.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["wayland", "x11", "xdg_desktop_portal", "libei", "gtk"]
|
||||
@@ -60,4 +72,4 @@ wayland = ["dep:wayland-client", "dep:wayland-protocols", "dep:wayland-protocols
|
||||
x11 = ["dep:x11"]
|
||||
xdg_desktop_portal = ["dep:ashpd"]
|
||||
libei = ["dep:reis", "dep:ashpd"]
|
||||
gtk = ["dep:gtk", "dep:adw", "dep:async-channel"]
|
||||
gtk = ["dep:gtk", "dep:adw", "dep:async-channel", "dep:glib-build-tools"]
|
||||
|
||||
62
README.md
62
README.md
@@ -39,13 +39,13 @@ For an alternative (with slightly different goals) you may check out [Input Leap
|
||||
The following table shows support for input emulation (to emulate events received from other clients) and
|
||||
input capture (to send events *to* other clients) on different operating systems:
|
||||
|
||||
| Backend | input emulation | input capture |
|
||||
| OS / Desktop Environment | input emulation | input capture |
|
||||
|---------------------------|--------------------------|--------------------------------------|
|
||||
| Wayland (wlroots) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Wayland (KDE) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Wayland (Gnome) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Wayland (Gnome) | :heavy_check_mark: | :heavy_check_mark: (starting at GNOME 45) |
|
||||
| Windows | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| X11 | :heavy_check_mark: | WIP |
|
||||
| Windows | :heavy_check_mark: | WIP |
|
||||
| MacOS | :heavy_check_mark: | WIP |
|
||||
|
||||
> [!Important]
|
||||
@@ -58,13 +58,13 @@ input capture (to send events *to* other clients) on different operating systems
|
||||
> Otherwise input capture will not work.
|
||||
|
||||
## Installation
|
||||
### Install with cargo
|
||||
### Install via cargo
|
||||
```sh
|
||||
cargo install lan-mouse
|
||||
```
|
||||
|
||||
### 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).
|
||||
Precompiled release binaries for Windows, MacOS and Linux are available in 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).
|
||||
|
||||
@@ -83,7 +83,9 @@ paru -S lan-mouse-bin
|
||||
- flake: [README.md](./nix/README.md)
|
||||
|
||||
|
||||
### Building from Source
|
||||
### Manual Installation
|
||||
|
||||
First make sure to [install the necessary dependencies](#installing-dependencies).
|
||||
|
||||
Build in release mode:
|
||||
```sh
|
||||
@@ -138,56 +140,61 @@ For a detailed list of available features, checkout the [Cargo.toml](./Cargo.tom
|
||||
|
||||
|
||||
## Installing Dependencies
|
||||
<details>
|
||||
<summary>MacOS</summary>
|
||||
|
||||
#### Macos
|
||||
```sh
|
||||
brew install libadwaita
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Ubuntu and derivatives</summary>
|
||||
|
||||
#### Ubuntu and derivatives
|
||||
```sh
|
||||
sudo apt install libadwaita-1-dev libgtk-4-dev libx11-dev libxtst-dev
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Arch and derivatives</summary>
|
||||
|
||||
#### Arch and derivatives
|
||||
```sh
|
||||
sudo pacman -S libadwaita gtk libx11 libxtst
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Fedora and derivatives</summary>
|
||||
|
||||
#### Fedora and derivatives
|
||||
```sh
|
||||
sudo dnf install libadwaita-devel libXtst-devel libX11-devel
|
||||
```
|
||||
</details>
|
||||
<details>
|
||||
<summary>Windows</summary>
|
||||
|
||||
#### 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)
|
||||
- First install [Rust](https://www.rust-lang.org/tools/install).
|
||||
|
||||
- Then follow the instructions at [gtk-rs.org](https://gtk-rs.org/gtk4-rs/stable/latest/book/installation_windows.html)
|
||||
|
||||
*TLDR:*
|
||||
|
||||
Build gtk from source
|
||||
|
||||
- The following commands should be run in an admin power shell instance:
|
||||
- The following commands should be run in an **admin power shell** instance:
|
||||
```sh
|
||||
# install chocolatey
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
|
||||
# install python 3.11 (Version is important, as 3.12 does not work currently) -> Has been fixed recently
|
||||
choco install python --version=3.11.0
|
||||
|
||||
# install git
|
||||
choco install git
|
||||
|
||||
# install msys2
|
||||
choco install msys2
|
||||
|
||||
# install Visual Studio 2022
|
||||
choco install visualstudio2022-workload-vctools
|
||||
# install gvsbuild dependencies
|
||||
choco install python git msys2 visualstudio2022-workload-vctools
|
||||
```
|
||||
|
||||
- The following commands should be run in a regular power shell instance:
|
||||
- The following commands should be run in a **regular power shell** instance:
|
||||
|
||||
```sh
|
||||
# install gvsbuild with python
|
||||
@@ -203,11 +210,12 @@ pipx install gvsbuild
|
||||
gvsbuild build gtk4 libadwaita librsvg
|
||||
```
|
||||
|
||||
Make sure to add the directory `C:\gtk-build\gtk\x64\release\bin`
|
||||
[to the `PATH` environment variable]((https://learn.microsoft.com/en-us/previous-versions/office/developer/sharepoint-2010/ee537574(v=office.14))). Otherwise the project will fail to build.
|
||||
- **Make sure to add the directory** `C:\gtk-build\gtk\x64\release\bin`
|
||||
[**to the `PATH` environment variable**]((https://learn.microsoft.com/en-us/previous-versions/office/developer/sharepoint-2010/ee537574(v=office.14))). Otherwise the project will fail to build.
|
||||
|
||||
To avoid building GTK from source, it is possible to disable
|
||||
the gtk frontend (see conditional compilation below).
|
||||
</details>
|
||||
|
||||
## Usage
|
||||
### Gtk Frontend
|
||||
|
||||
1
build.rs
1
build.rs
@@ -1,5 +1,6 @@
|
||||
fn main() {
|
||||
// composite_templates
|
||||
#[cfg(feature = "gtk")]
|
||||
glib_build_tools::compile_resources(
|
||||
&["resources"],
|
||||
"resources/resources.gresource.xml",
|
||||
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1710806803,
|
||||
"narHash": "sha256-qrxvLS888pNJFwJdK+hf1wpRCSQcqA6W5+Ox202NDa0=",
|
||||
"lastModified": 1716293225,
|
||||
"narHash": "sha256-pU9ViBVE3XYb70xZx+jK6SEVphvt7xMTbm6yDIF4xPs=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b06025f1533a1e07b6db3e75151caa155d1c7eb3",
|
||||
"rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -48,11 +48,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710987136,
|
||||
"narHash": "sha256-Q8GRdlAIKZ8tJUXrbcRO1pA33AdoPfTUirsSnmGQnOU=",
|
||||
"lastModified": 1716257780,
|
||||
"narHash": "sha256-R+NjvJzKEkTVCmdrKRfPE4liX/KMGVqGUwwS5H8ET8A=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "97596b54ac34ad8184ca1eef44b1ec2e5c2b5f9e",
|
||||
"rev": "4e5e3d2c5c9b2721bd266f9e43c14e96811b89d2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
}: let
|
||||
inherit (nixpkgs) lib;
|
||||
genSystems = lib.genAttrs [
|
||||
"aarch64-darwin"
|
||||
"x86_64-darwin"
|
||||
"x86_64-linux"
|
||||
];
|
||||
pkgsFor = system:
|
||||
@@ -49,6 +51,8 @@
|
||||
gtk4
|
||||
libadwaita
|
||||
xorg.libXtst
|
||||
] ++ lib.optionals stdenv.isDarwin [
|
||||
darwin.apple_sdk_11_0.frameworks.CoreGraphics
|
||||
];
|
||||
|
||||
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
||||
|
||||
@@ -34,6 +34,9 @@ enable lan-mouse
|
||||
enable = true;
|
||||
# systemd = false;
|
||||
# package = inputs.lan-mouse.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||
# Optional configuration in nix syntax, see config.toml for available options
|
||||
# settings = { };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
rustPlatform,
|
||||
lib,
|
||||
pkgs,
|
||||
}:
|
||||
}: let
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ../Cargo.toml);
|
||||
pname = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "lan-mouse";
|
||||
version = "0.7.0";
|
||||
pname = pname;
|
||||
version = version;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
@@ -18,10 +22,12 @@ rustPlatform.buildRustPackage {
|
||||
gtk4
|
||||
libadwaita
|
||||
xorg.libXtst
|
||||
] ++ lib.optionals stdenv.isDarwin [
|
||||
darwin.apple_sdk_11_0.frameworks.CoreGraphics
|
||||
];
|
||||
|
||||
src = builtins.path {
|
||||
name = "lan-mouse";
|
||||
name = pname;
|
||||
path = lib.cleanSource ../.;
|
||||
};
|
||||
|
||||
@@ -36,7 +42,7 @@ rustPlatform.buildRustPackage {
|
||||
Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple pcs with a single set of mouse and keyboard. This is also known as a Software KVM switch.
|
||||
The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details).
|
||||
'';
|
||||
mainProgram = "lan-mouse";
|
||||
mainProgram = pname;
|
||||
platforms = platforms.all;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ self: {
|
||||
with lib; let
|
||||
cfg = config.programs.lan-mouse;
|
||||
defaultPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
tomlFormat = pkgs.formats.toml {};
|
||||
in {
|
||||
options.programs.lan-mouse = with types; {
|
||||
enable = mkEnableOption "Whether or not to enable lan-mouse.";
|
||||
@@ -23,7 +24,23 @@ in {
|
||||
systemd = mkOption {
|
||||
type = types.bool;
|
||||
default = pkgs.stdenv.isLinux;
|
||||
description = "Whether to enable to systemd service for lan-mouse.";
|
||||
description = "Whether to enable to systemd service for lan-mouse on linux.";
|
||||
};
|
||||
launchd = mkOption {
|
||||
type = types.bool;
|
||||
default = pkgs.stdenv.isDarwin;
|
||||
description = "Whether to enable to launchd service for lan-mouse on macOS.";
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
inherit (tomlFormat) type;
|
||||
default = {};
|
||||
example = builtins.fromTOML (builtins.readFile (self + /config.toml));
|
||||
description = ''
|
||||
Optional configuration written to {file}`$XDG_CONFIG_HOME/lan-mouse/config.toml`.
|
||||
|
||||
See <https://github.com/feschber/lan-mouse/> for
|
||||
available options and documentation.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
@@ -43,8 +60,23 @@ in {
|
||||
];
|
||||
};
|
||||
|
||||
launchd.agents.lan-mouse = lib.mkIf cfg.launchd {
|
||||
enable = true;
|
||||
config = {
|
||||
ProgramArguments = [
|
||||
"${cfg.package}/bin/lan-mouse"
|
||||
"--daemon"
|
||||
];
|
||||
KeepAlive = true;
|
||||
};
|
||||
};
|
||||
|
||||
home.packages = [
|
||||
cfg.package
|
||||
];
|
||||
|
||||
xdg.configFile."lan-mouse/config.toml" = lib.mkIf (cfg.settings != {}) {
|
||||
source = tomlFormat.generate "config.toml" cfg.settings;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,20 @@
|
||||
<property name="tooltip-text" translatable="yes">enable</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton" id="dns_button">
|
||||
<signal name="clicked" handler="handle_request_dns" swapped="true"/>
|
||||
<!--<property name="icon-name">network-wired-disconnected-symbolic</property>-->
|
||||
<property name="icon-name">network-wired-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="tooltip-text" translatable="yes">resolve host</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="suffix">
|
||||
<object class="GtkSpinner" id="dns_loading_indicator">
|
||||
</object>
|
||||
</child>
|
||||
<!-- host -->
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
@@ -66,6 +80,7 @@
|
||||
<property name="valign">center</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="name">delete-button</property>
|
||||
<style><class name="error"/></style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
<gresource prefix="/de/feschber/LanMouse">
|
||||
<file compressed="true" preprocess="xml-stripblanks">window.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">client_row.ui</file>
|
||||
<file compressed="true">style.css</file>
|
||||
<file compressed="true">style-dark.css</file>
|
||||
</gresource>
|
||||
<gresource prefix="/de/feschber/LanMouse/icons">
|
||||
<file compressed="true" preprocess="xml-stripblanks">de.feschber.LanMouse.svg</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">de.feschber.LanMouse.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#delete-button {
|
||||
color: @red_1;
|
||||
}
|
||||
|
||||
#port-edit-cancel {
|
||||
color: @red_1;
|
||||
}
|
||||
|
||||
#port-edit-apply {
|
||||
color: @green_1;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#delete-button {
|
||||
color: @red_3;
|
||||
}
|
||||
|
||||
#port-edit-cancel {
|
||||
color: @red_3;
|
||||
}
|
||||
|
||||
#port-edit-apply {
|
||||
color: @green_3;
|
||||
}
|
||||
@@ -84,6 +84,7 @@
|
||||
<property name="valign">center</property>
|
||||
<property name="visible">false</property>
|
||||
<property name="name">port-edit-apply</property>
|
||||
<style><class name="success"/></style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@@ -93,6 +94,26 @@
|
||||
<property name="valign">center</property>
|
||||
<property name="visible">false</property>
|
||||
<property name="name">port-edit-cancel</property>
|
||||
<style><class name="error"/></style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title">hostname</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="hostname_label">
|
||||
<property name="label"><span font_style="italic" font_weight="light" foreground="darkgrey">could not determine hostname</span></property>
|
||||
<property name="use-markup">true</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="copy-hostname-button">
|
||||
<property name="icon-name">edit-copy-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="handle_copy_hostname" swapped="true"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -25,7 +25,7 @@ pub mod x11;
|
||||
/// fallback input capture (does not produce events)
|
||||
pub mod dummy;
|
||||
|
||||
pub async fn create() -> Box<dyn InputCapture> {
|
||||
pub async fn create() -> Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>> {
|
||||
#[cfg(target_os = "macos")]
|
||||
match macos::MacOSInputCapture::new() {
|
||||
Ok(p) => return Box::new(p),
|
||||
|
||||
@@ -46,7 +46,7 @@ enum ProducerEvent {
|
||||
pub struct LibeiInputCapture<'a> {
|
||||
input_capture: Pin<Box<InputCapture<'a>>>,
|
||||
libei_task: JoinHandle<Result<()>>,
|
||||
event_rx: tokio::sync::mpsc::Receiver<(u32, Event)>,
|
||||
event_rx: tokio::sync::mpsc::Receiver<(ClientHandle, Event)>,
|
||||
notify_tx: tokio::sync::mpsc::Sender<ProducerEvent>,
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ async fn update_barriers(
|
||||
.set_pointer_barriers(session, &barriers, zones.zone_set())
|
||||
.await?;
|
||||
let response = response.response()?;
|
||||
log::info!("{response:?}");
|
||||
log::debug!("{response:?}");
|
||||
Ok(id_map)
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ impl<'a> Drop for LibeiInputCapture<'a> {
|
||||
async fn create_session<'a>(
|
||||
input_capture: &'a InputCapture<'a>,
|
||||
) -> Result<(Session<'a>, BitFlags<Capabilities>)> {
|
||||
log::info!("creating input capture session");
|
||||
log::debug!("creating input capture session");
|
||||
let (session, capabilities) = loop {
|
||||
match input_capture
|
||||
.create_session(
|
||||
@@ -154,7 +154,7 @@ async fn connect_to_eis(
|
||||
input_capture: &InputCapture<'_>,
|
||||
session: &Session<'_>,
|
||||
) -> Result<(ei::Context, EiConvertEventStream)> {
|
||||
log::info!("connect_to_eis");
|
||||
log::debug!("connect_to_eis");
|
||||
let fd = input_capture.connect_to_eis(session).await?;
|
||||
|
||||
// create unix stream from fd
|
||||
@@ -183,7 +183,7 @@ async fn connect_to_eis(
|
||||
async fn libei_event_handler(
|
||||
mut ei_event_stream: EiConvertEventStream,
|
||||
context: ei::Context,
|
||||
event_tx: Sender<(u32, Event)>,
|
||||
event_tx: Sender<(ClientHandle, Event)>,
|
||||
current_client: Rc<Cell<Option<ClientHandle>>>,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
@@ -270,7 +270,7 @@ impl<'a> LibeiInputCapture<'a> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::info!("enabling session");
|
||||
log::debug!("enabling session");
|
||||
input_capture.enable(&session).await?;
|
||||
|
||||
loop {
|
||||
@@ -396,7 +396,7 @@ async fn handle_ei_event(
|
||||
ei_event: EiEvent,
|
||||
current_client: Option<ClientHandle>,
|
||||
context: &ei::Context,
|
||||
event_tx: &Sender<(u32, Event)>,
|
||||
event_tx: &Sender<(ClientHandle, Event)>,
|
||||
) {
|
||||
match ei_event {
|
||||
EiEvent::SeatAdded(s) => {
|
||||
@@ -466,15 +466,38 @@ async fn handle_ei_event(
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
EiEvent::ScrollDelta(_) => {}
|
||||
EiEvent::ScrollDelta(delta) => {
|
||||
if let Some(handle) = current_client {
|
||||
let mut events = vec![];
|
||||
if delta.dy != 0. {
|
||||
events.push(PointerEvent::Axis {
|
||||
time: 0,
|
||||
axis: 0,
|
||||
value: delta.dy as f64,
|
||||
});
|
||||
}
|
||||
if delta.dx != 0. {
|
||||
events.push(PointerEvent::Axis {
|
||||
time: 0,
|
||||
axis: 1,
|
||||
value: delta.dx as f64,
|
||||
});
|
||||
}
|
||||
for event in events {
|
||||
event_tx
|
||||
.send((handle, Event::Pointer(event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
EiEvent::ScrollStop(_) => {}
|
||||
EiEvent::ScrollCancel(_) => {}
|
||||
EiEvent::ScrollDiscrete(scroll) => {
|
||||
if scroll.discrete_dy != 0 {
|
||||
let event = PointerEvent::Axis {
|
||||
time: 0,
|
||||
let event = PointerEvent::AxisDiscrete120 {
|
||||
axis: 0,
|
||||
value: scroll.discrete_dy as f64,
|
||||
value: scroll.discrete_dy,
|
||||
};
|
||||
if let Some(current_client) = current_client {
|
||||
event_tx
|
||||
@@ -484,10 +507,9 @@ async fn handle_ei_event(
|
||||
}
|
||||
}
|
||||
if scroll.discrete_dx != 0 {
|
||||
let event = PointerEvent::Axis {
|
||||
time: 0,
|
||||
let event = PointerEvent::AxisDiscrete120 {
|
||||
axis: 1,
|
||||
value: scroll.discrete_dx as f64,
|
||||
value: scroll.discrete_dx,
|
||||
};
|
||||
if let Some(current_client) = current_client {
|
||||
event_tx
|
||||
|
||||
@@ -111,6 +111,7 @@ struct State {
|
||||
qh: QueueHandle<Self>,
|
||||
pending_events: VecDeque<(ClientHandle, Event)>,
|
||||
output_info: Vec<(WlOutput, OutputInfo)>,
|
||||
scroll_discrete_pending: bool,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
@@ -351,6 +352,7 @@ impl WaylandInputCapture {
|
||||
read_guard: None,
|
||||
pending_events: VecDeque::new(),
|
||||
output_info: vec![],
|
||||
scroll_discrete_pending: false,
|
||||
};
|
||||
|
||||
// dispatch registry to () again, in order to read all wl_outputs
|
||||
@@ -752,12 +754,30 @@ impl Dispatch<WlPointer, ()> for State {
|
||||
}
|
||||
wl_pointer::Event::Axis { time, axis, value } => {
|
||||
let (_, client) = app.focused.as_ref().unwrap();
|
||||
if app.scroll_discrete_pending {
|
||||
// each axisvalue120 event is coupled with
|
||||
// a corresponding axis event, which needs to
|
||||
// be ignored to not duplicate the scrolling
|
||||
app.scroll_discrete_pending = false;
|
||||
} else {
|
||||
app.pending_events.push_back((
|
||||
*client,
|
||||
Event::Pointer(PointerEvent::Axis {
|
||||
time,
|
||||
axis: u32::from(axis) as u8,
|
||||
value,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
wl_pointer::Event::AxisValue120 { axis, value120 } => {
|
||||
let (_, client) = app.focused.as_ref().unwrap();
|
||||
app.scroll_discrete_pending = true;
|
||||
app.pending_events.push_back((
|
||||
*client,
|
||||
Event::Pointer(PointerEvent::Axis {
|
||||
time,
|
||||
Event::Pointer(PointerEvent::AxisDiscrete120 {
|
||||
axis: u32::from(axis) as u8,
|
||||
value,
|
||||
value: value120,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,35 +1,612 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::Result;
|
||||
use core::task::{Context, Poll};
|
||||
use futures::Stream;
|
||||
use std::{io, pin::Pin};
|
||||
use once_cell::unsync::Lazy;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ptr::{addr_of, addr_of_mut};
|
||||
|
||||
use futures::executor::block_on;
|
||||
use std::default::Default;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::mpsc;
|
||||
use std::task::ready;
|
||||
use std::{io, pin::Pin, thread};
|
||||
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
||||
use windows::core::{w, PCWSTR};
|
||||
use windows::Win32::Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, RECT, WPARAM};
|
||||
use windows::Win32::Graphics::Gdi::{
|
||||
EnumDisplayDevicesW, EnumDisplaySettingsW, DEVMODEW, DISPLAY_DEVICEW,
|
||||
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, ENUM_CURRENT_SETTINGS,
|
||||
};
|
||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
CallNextHookEx, CreateWindowExW, DispatchMessageW, GetMessageW, PostThreadMessageW,
|
||||
RegisterClassW, SetWindowsHookExW, TranslateMessage, EDD_GET_DEVICE_INTERFACE_NAME, HHOOK,
|
||||
HMENU, HOOKPROC, KBDLLHOOKSTRUCT, LLKHF_EXTENDED, MSG, MSLLHOOKSTRUCT, WH_KEYBOARD_LL,
|
||||
WH_MOUSE_LL, WINDOW_STYLE, WM_DISPLAYCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN,
|
||||
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN,
|
||||
WM_RBUTTONUP, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW,
|
||||
WNDPROC,
|
||||
};
|
||||
|
||||
use crate::client::Position;
|
||||
use crate::event::{
|
||||
KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
|
||||
};
|
||||
use crate::scancode::Linux;
|
||||
use crate::{
|
||||
capture::InputCapture,
|
||||
client::{ClientEvent, ClientHandle},
|
||||
event::Event,
|
||||
scancode,
|
||||
};
|
||||
|
||||
pub struct WindowsInputCapture {}
|
||||
pub struct WindowsInputCapture {
|
||||
event_rx: Receiver<(ClientHandle, Event)>,
|
||||
msg_thread: Option<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
ClientEvent = 0,
|
||||
Release = 1,
|
||||
Exit = 2,
|
||||
}
|
||||
|
||||
unsafe fn signal_message_thread(event_type: EventType) {
|
||||
if let Some(event_tid) = get_event_tid() {
|
||||
PostThreadMessageW(event_tid, WM_USER, WPARAM(event_type as usize), LPARAM(0)).unwrap();
|
||||
} else {
|
||||
log::warn!("lost event");
|
||||
}
|
||||
}
|
||||
|
||||
impl InputCapture for WindowsInputCapture {
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
fn notify(&mut self, event: ClientEvent) -> io::Result<()> {
|
||||
unsafe {
|
||||
EVENT_BUFFER.push(event);
|
||||
signal_message_thread(EventType::ClientEvent);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
unsafe { signal_message_thread(EventType::Release) };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
static mut EVENT_BUFFER: Vec<ClientEvent> = Vec::new();
|
||||
static mut ACTIVE_CLIENT: Option<ClientHandle> = None;
|
||||
static mut CLIENT_FOR_POS: Lazy<HashMap<Position, ClientHandle>> = Lazy::new(HashMap::new);
|
||||
static mut EVENT_TX: Option<Sender<(ClientHandle, Event)>> = None;
|
||||
static mut EVENT_THREAD_ID: AtomicU32 = AtomicU32::new(0);
|
||||
unsafe fn set_event_tid(tid: u32) {
|
||||
EVENT_THREAD_ID.store(tid, Ordering::SeqCst);
|
||||
}
|
||||
unsafe fn get_event_tid() -> Option<u32> {
|
||||
match EVENT_THREAD_ID.load(Ordering::SeqCst) {
|
||||
0 => None,
|
||||
id => Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
static mut ENTRY_POINT: (i32, i32) = (0, 0);
|
||||
|
||||
fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
|
||||
let mouse_low_level: MSLLHOOKSTRUCT =
|
||||
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(lparam) };
|
||||
match wparam {
|
||||
WPARAM(p) if p == WM_LBUTTONDOWN as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_LEFT,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_MBUTTONDOWN as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_MIDDLE,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_RBUTTONDOWN as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_RIGHT,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_LBUTTONUP as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_LEFT,
|
||||
state: 0,
|
||||
}),
|
||||
WPARAM(p) if p == WM_MBUTTONUP as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_MIDDLE,
|
||||
state: 0,
|
||||
}),
|
||||
WPARAM(p) if p == WM_RBUTTONUP as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_RIGHT,
|
||||
state: 0,
|
||||
}),
|
||||
WPARAM(p) if p == WM_MOUSEMOVE as usize => unsafe {
|
||||
let (x, y) = (mouse_low_level.pt.x, mouse_low_level.pt.y);
|
||||
let (ex, ey) = ENTRY_POINT;
|
||||
let (dx, dy) = (x - ex, y - ey);
|
||||
Some(PointerEvent::Motion {
|
||||
time: 0,
|
||||
relative_x: dx as f64,
|
||||
relative_y: dy as f64,
|
||||
})
|
||||
},
|
||||
WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::AxisDiscrete120 {
|
||||
axis: 0,
|
||||
value: -(mouse_low_level.mouseData as i32 >> 16),
|
||||
}),
|
||||
WPARAM(p) if p == WM_XBUTTONDOWN as usize || p == WM_XBUTTONUP as usize => {
|
||||
let hb = mouse_low_level.mouseData >> 16;
|
||||
let button = match hb {
|
||||
1 => BTN_BACK,
|
||||
2 => BTN_FORWARD,
|
||||
_ => {
|
||||
log::warn!("unknown mouse button");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button,
|
||||
state: if p == WM_XBUTTONDOWN as usize { 1 } else { 0 },
|
||||
})
|
||||
}
|
||||
w => {
|
||||
log::warn!("unknown mouse event: {w:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn to_key_event(wparam: WPARAM, lparam: LPARAM) -> Option<KeyboardEvent> {
|
||||
let kybrdllhookstruct: KBDLLHOOKSTRUCT =
|
||||
*std::mem::transmute::<LPARAM, *const KBDLLHOOKSTRUCT>(lparam);
|
||||
let mut scan_code = kybrdllhookstruct.scanCode;
|
||||
log::trace!("scan_code: {scan_code}");
|
||||
if kybrdllhookstruct.flags.contains(LLKHF_EXTENDED) {
|
||||
scan_code |= 0xE000;
|
||||
}
|
||||
let Ok(win_scan_code) = scancode::Windows::try_from(scan_code) else {
|
||||
log::warn!("failed to translate to windows scancode: {scan_code}");
|
||||
return None;
|
||||
};
|
||||
log::trace!("windows_scan: {win_scan_code:?}");
|
||||
let Ok(linux_scan_code): Result<Linux, ()> = win_scan_code.try_into() else {
|
||||
log::warn!("failed to translate into linux scancode: {win_scan_code:?}");
|
||||
return None;
|
||||
};
|
||||
log::trace!("windows_scan: {linux_scan_code:?}");
|
||||
let scan_code = linux_scan_code as u32;
|
||||
match wparam {
|
||||
WPARAM(p) if p == WM_KEYDOWN as usize => Some(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key: scan_code,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_KEYUP as usize => Some(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key: scan_code,
|
||||
state: 0,
|
||||
}),
|
||||
WPARAM(p) if p == WM_SYSKEYDOWN as usize => Some(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key: scan_code,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_SYSKEYUP as usize => Some(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key: scan_code,
|
||||
state: 1,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// clamp point to display bounds
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `prev_point`: coordinates, the cursor was before entering, within bounds of a display
|
||||
/// * `entry_point`: point to clamp
|
||||
///
|
||||
/// returns: (i32, i32), the corrected entry point
|
||||
///
|
||||
fn clamp_to_display_bounds(prev_point: (i32, i32), point: (i32, i32)) -> (i32, i32) {
|
||||
/* find display where movement came from */
|
||||
let display_regions = unsafe { get_display_regions() };
|
||||
let display = display_regions
|
||||
.iter()
|
||||
.find(|&d| is_within_dp_region(prev_point, d))
|
||||
.unwrap();
|
||||
|
||||
/* clamp to bounds (inclusive) */
|
||||
let (x, y) = point;
|
||||
let (min_x, max_x) = (display.left, display.right - 1);
|
||||
let (min_y, max_y) = (display.top, display.bottom - 1);
|
||||
(x.clamp(min_x, max_x), y.clamp(min_y, max_y))
|
||||
}
|
||||
|
||||
unsafe fn send_blocking(event: Event) {
|
||||
if let Some(active) = ACTIVE_CLIENT {
|
||||
block_on(async move {
|
||||
let _ = EVENT_TX.as_ref().unwrap().send((active, event)).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn check_client_activation(wparam: WPARAM, lparam: LPARAM) -> bool {
|
||||
if wparam.0 != WM_MOUSEMOVE as usize {
|
||||
return ACTIVE_CLIENT.is_some();
|
||||
}
|
||||
let mouse_low_level: MSLLHOOKSTRUCT =
|
||||
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(lparam) };
|
||||
static mut PREV_POS: Option<(i32, i32)> = None;
|
||||
let curr_pos = (mouse_low_level.pt.x, mouse_low_level.pt.y);
|
||||
let prev_pos = PREV_POS.unwrap_or(curr_pos);
|
||||
PREV_POS.replace(curr_pos);
|
||||
|
||||
/* next event is the first actual event */
|
||||
let ret = ACTIVE_CLIENT.is_some();
|
||||
|
||||
/* client already active, no need to check */
|
||||
if ACTIVE_CLIENT.is_some() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check if a client was activated */
|
||||
let Some(pos) = entered_barrier(prev_pos, curr_pos, get_display_regions()) else {
|
||||
return ret;
|
||||
};
|
||||
|
||||
/* check if a client is registered for the barrier */
|
||||
let Some(client) = CLIENT_FOR_POS.get(&pos) else {
|
||||
return ret;
|
||||
};
|
||||
|
||||
/* update active client and entry point */
|
||||
ACTIVE_CLIENT.replace(*client);
|
||||
ENTRY_POINT = clamp_to_display_bounds(prev_pos, curr_pos);
|
||||
|
||||
/* notify main thread */
|
||||
log::debug!("ENTERED @ {prev_pos:?} -> {curr_pos:?}");
|
||||
send_blocking(Event::Enter());
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
unsafe extern "system" fn mouse_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||
let active = check_client_activation(wparam, lparam);
|
||||
|
||||
/* no client was active */
|
||||
if !active {
|
||||
return CallNextHookEx(HHOOK::default(), ncode, wparam, lparam);
|
||||
}
|
||||
|
||||
/* get active client if any */
|
||||
let Some(client) = ACTIVE_CLIENT else {
|
||||
return LRESULT(1);
|
||||
};
|
||||
|
||||
/* convert to lan-mouse event */
|
||||
let Some(pointer_event) = to_mouse_event(wparam, lparam) else {
|
||||
return LRESULT(1);
|
||||
};
|
||||
let event = (client, Event::Pointer(pointer_event));
|
||||
|
||||
/* notify mainthread (drop events if sending too fast) */
|
||||
if let Err(e) = EVENT_TX.as_ref().unwrap().try_send(event) {
|
||||
log::warn!("e: {e}");
|
||||
}
|
||||
|
||||
/* don't pass event to applications */
|
||||
LRESULT(1)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn kybrd_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||
/* get active client if any */
|
||||
let Some(client) = ACTIVE_CLIENT else {
|
||||
return CallNextHookEx(HHOOK::default(), ncode, wparam, lparam);
|
||||
};
|
||||
|
||||
/* convert to key event */
|
||||
let Some(key_event) = to_key_event(wparam, lparam) else {
|
||||
return LRESULT(1);
|
||||
};
|
||||
let event = (client, Event::Keyboard(key_event));
|
||||
|
||||
if let Err(e) = EVENT_TX.as_ref().unwrap().try_send(event) {
|
||||
log::warn!("e: {e}");
|
||||
}
|
||||
|
||||
/* don't pass event to applications */
|
||||
LRESULT(1)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn window_proc(
|
||||
_hwnd: HWND,
|
||||
uint: u32,
|
||||
_wparam: WPARAM,
|
||||
_lparam: LPARAM,
|
||||
) -> LRESULT {
|
||||
match uint {
|
||||
x if x == WM_DISPLAYCHANGE => {
|
||||
log::debug!("display resolution changed");
|
||||
DISPLAY_RESOLUTION_CHANGED = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
LRESULT(1)
|
||||
}
|
||||
|
||||
fn enumerate_displays() -> Vec<RECT> {
|
||||
unsafe {
|
||||
let mut display_rects = vec![];
|
||||
let mut devices = vec![];
|
||||
for i in 0.. {
|
||||
let mut device: DISPLAY_DEVICEW = std::mem::zeroed();
|
||||
device.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as u32;
|
||||
let ret = EnumDisplayDevicesW(None, i, &mut device, EDD_GET_DEVICE_INTERFACE_NAME);
|
||||
if ret == FALSE {
|
||||
break;
|
||||
}
|
||||
if device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP != 0 {
|
||||
log::info!("{:?}", device.DeviceName);
|
||||
devices.push(device.DeviceName);
|
||||
}
|
||||
}
|
||||
for device in devices {
|
||||
let mut dev_mode: DEVMODEW = std::mem::zeroed();
|
||||
dev_mode.dmSize = std::mem::size_of::<DEVMODEW>() as u16;
|
||||
let ret = EnumDisplaySettingsW(
|
||||
PCWSTR::from_raw(&device as *const _),
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
&mut dev_mode,
|
||||
);
|
||||
if ret == FALSE {
|
||||
log::warn!("no display mode");
|
||||
}
|
||||
|
||||
let pos = dev_mode.Anonymous1.Anonymous2.dmPosition;
|
||||
let (x, y) = (pos.x, pos.y);
|
||||
let (width, height) = (dev_mode.dmPelsWidth, dev_mode.dmPelsHeight);
|
||||
|
||||
display_rects.push(RECT {
|
||||
left: x,
|
||||
right: x + width as i32,
|
||||
top: y,
|
||||
bottom: y + height as i32,
|
||||
});
|
||||
}
|
||||
display_rects
|
||||
}
|
||||
}
|
||||
|
||||
static mut DISPLAY_RESOLUTION_CHANGED: bool = true;
|
||||
|
||||
unsafe fn get_display_regions() -> &'static Vec<RECT> {
|
||||
static mut DISPLAYS: Vec<RECT> = vec![];
|
||||
if DISPLAY_RESOLUTION_CHANGED {
|
||||
DISPLAYS = enumerate_displays();
|
||||
DISPLAY_RESOLUTION_CHANGED = false;
|
||||
log::debug!("displays: {DISPLAYS:?}");
|
||||
}
|
||||
&*addr_of!(DISPLAYS)
|
||||
}
|
||||
|
||||
fn is_within_dp_region(point: (i32, i32), display: &RECT) -> bool {
|
||||
[
|
||||
Position::Left,
|
||||
Position::Right,
|
||||
Position::Top,
|
||||
Position::Bottom,
|
||||
]
|
||||
.iter()
|
||||
.all(|&pos| is_within_dp_boundary(point, display, pos))
|
||||
}
|
||||
fn is_within_dp_boundary(point: (i32, i32), display: &RECT, pos: Position) -> bool {
|
||||
let (x, y) = point;
|
||||
match pos {
|
||||
Position::Left => display.left <= x,
|
||||
Position::Right => display.right > x,
|
||||
Position::Top => display.top <= y,
|
||||
Position::Bottom => display.bottom > y,
|
||||
}
|
||||
}
|
||||
|
||||
/// returns whether the given position is within the display bounds with respect to the given
|
||||
/// barrier position
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `x`:
|
||||
/// * `y`:
|
||||
/// * `displays`:
|
||||
/// * `pos`:
|
||||
///
|
||||
/// returns: bool
|
||||
///
|
||||
fn in_bounds(point: (i32, i32), displays: &[RECT], pos: Position) -> bool {
|
||||
displays
|
||||
.iter()
|
||||
.any(|d| is_within_dp_boundary(point, d, pos))
|
||||
}
|
||||
|
||||
fn in_display_region(point: (i32, i32), displays: &[RECT]) -> bool {
|
||||
displays.iter().any(|d| is_within_dp_region(point, d))
|
||||
}
|
||||
|
||||
fn moved_across_boundary(
|
||||
prev_pos: (i32, i32),
|
||||
curr_pos: (i32, i32),
|
||||
displays: &[RECT],
|
||||
pos: Position,
|
||||
) -> bool {
|
||||
/* was within bounds, but is not anymore */
|
||||
in_display_region(prev_pos, displays) && !in_bounds(curr_pos, displays, pos)
|
||||
}
|
||||
|
||||
fn entered_barrier(
|
||||
prev_pos: (i32, i32),
|
||||
curr_pos: (i32, i32),
|
||||
displays: &[RECT],
|
||||
) -> Option<Position> {
|
||||
[
|
||||
Position::Left,
|
||||
Position::Right,
|
||||
Position::Top,
|
||||
Position::Bottom,
|
||||
]
|
||||
.into_iter()
|
||||
.find(|&pos| moved_across_boundary(prev_pos, curr_pos, displays, pos))
|
||||
}
|
||||
|
||||
fn get_msg() -> Option<MSG> {
|
||||
unsafe {
|
||||
let mut msg = std::mem::zeroed();
|
||||
let ret = GetMessageW(addr_of_mut!(msg), HWND::default(), 0, 0);
|
||||
match ret.0 {
|
||||
0 => None,
|
||||
x if x > 0 => Some(msg),
|
||||
_ => panic!("error in GetMessageW"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn message_thread(ready_tx: mpsc::Sender<()>) {
|
||||
unsafe {
|
||||
set_event_tid(GetCurrentThreadId());
|
||||
ready_tx.send(()).expect("channel closed");
|
||||
let mouse_proc: HOOKPROC = Some(mouse_proc);
|
||||
let kybrd_proc: HOOKPROC = Some(kybrd_proc);
|
||||
let window_proc: WNDPROC = Some(window_proc);
|
||||
|
||||
/* register hooks */
|
||||
let _ = SetWindowsHookExW(WH_MOUSE_LL, mouse_proc, HINSTANCE::default(), 0).unwrap();
|
||||
let _ = SetWindowsHookExW(WH_KEYBOARD_LL, kybrd_proc, HINSTANCE::default(), 0).unwrap();
|
||||
|
||||
let instance = GetModuleHandleW(None).unwrap();
|
||||
let window_class: WNDCLASSW = WNDCLASSW {
|
||||
lpfnWndProc: window_proc,
|
||||
hInstance: instance.into(),
|
||||
lpszClassName: w!("lan-mouse-message-window-class"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ret = RegisterClassW(&window_class);
|
||||
if ret == 0 {
|
||||
panic!("RegisterClassW");
|
||||
}
|
||||
|
||||
/* window is used ro receive WM_DISPLAYCHANGE messages */
|
||||
let ret = CreateWindowExW(
|
||||
Default::default(),
|
||||
w!("lan-mouse-message-window-class"),
|
||||
w!("lan-mouse-msg-window"),
|
||||
WINDOW_STYLE::default(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
HWND::default(),
|
||||
HMENU::default(),
|
||||
instance,
|
||||
None,
|
||||
);
|
||||
if ret.0 == 0 {
|
||||
panic!("CreateWindowExW");
|
||||
}
|
||||
|
||||
/* run message loop */
|
||||
loop {
|
||||
// mouse / keybrd proc do not actually return a message
|
||||
let Some(msg) = get_msg() else {
|
||||
break;
|
||||
};
|
||||
if msg.hwnd.0 == 0 {
|
||||
/* messages sent via PostThreadMessage */
|
||||
match msg.wParam.0 {
|
||||
x if x == EventType::Exit as usize => break,
|
||||
x if x == EventType::Release as usize => {
|
||||
ACTIVE_CLIENT.take();
|
||||
}
|
||||
x if x == EventType::ClientEvent as usize => {
|
||||
while let Some(event) = EVENT_BUFFER.pop() {
|
||||
update_clients(event)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
/* other messages for window_procs */
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_clients(client_event: ClientEvent) {
|
||||
match client_event {
|
||||
ClientEvent::Create(handle, pos) => {
|
||||
unsafe { CLIENT_FOR_POS.insert(pos, handle) };
|
||||
}
|
||||
ClientEvent::Destroy(handle) => unsafe {
|
||||
for pos in [
|
||||
Position::Left,
|
||||
Position::Right,
|
||||
Position::Top,
|
||||
Position::Bottom,
|
||||
] {
|
||||
if ACTIVE_CLIENT == Some(handle) {
|
||||
ACTIVE_CLIENT.take();
|
||||
}
|
||||
if CLIENT_FOR_POS.get(&pos).copied() == Some(handle) {
|
||||
CLIENT_FOR_POS.remove(&pos);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsInputCapture {
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
Err(anyhow!("not implemented"))
|
||||
unsafe {
|
||||
let (tx, rx) = channel(10);
|
||||
EVENT_TX.replace(tx);
|
||||
let (ready_tx, ready_rx) = mpsc::channel();
|
||||
let msg_thread = Some(thread::spawn(|| message_thread(ready_tx)));
|
||||
/* wait for thread to set its id */
|
||||
ready_rx.recv().expect("channel closed");
|
||||
Ok(Self {
|
||||
msg_thread,
|
||||
event_rx: rx,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for WindowsInputCapture {
|
||||
type Item = io::Result<(ClientHandle, Event)>;
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match ready!(self.event_rx.poll_recv(cx)) {
|
||||
None => Poll::Ready(None),
|
||||
Some(e) => Poll::Ready(Some(Ok(e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowsInputCapture {
|
||||
fn drop(&mut self) {
|
||||
unsafe { signal_message_thread(EventType::Exit) };
|
||||
let _ = self.msg_thread.take().unwrap().join();
|
||||
}
|
||||
}
|
||||
|
||||
42
src/capture_test.rs
Normal file
42
src/capture_test.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use crate::capture;
|
||||
use crate::client::{ClientEvent, Position};
|
||||
use crate::event::{Event, KeyboardEvent};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::StreamExt;
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
pub fn run() -> Result<()> {
|
||||
log::info!("running input capture test");
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()?;
|
||||
|
||||
runtime.block_on(LocalSet::new().run_until(input_capture_test()))
|
||||
}
|
||||
|
||||
async fn input_capture_test() -> Result<()> {
|
||||
log::info!("creating input capture");
|
||||
let mut input_capture = capture::create().await;
|
||||
log::info!("creating clients");
|
||||
input_capture.notify(ClientEvent::Create(0, Position::Left))?;
|
||||
input_capture.notify(ClientEvent::Create(1, Position::Right))?;
|
||||
input_capture.notify(ClientEvent::Create(2, Position::Top))?;
|
||||
input_capture.notify(ClientEvent::Create(3, Position::Bottom))?;
|
||||
loop {
|
||||
let (client, event) = input_capture
|
||||
.next()
|
||||
.await
|
||||
.ok_or(anyhow!("capture stream closed"))??;
|
||||
let pos = match client {
|
||||
0 => Position::Left,
|
||||
1 => Position::Right,
|
||||
2 => Position::Top,
|
||||
_ => Position::Bottom,
|
||||
};
|
||||
log::info!("position: {pos}, event: {event}");
|
||||
if let Event::Keyboard(KeyboardEvent::Key { key: 1, .. }) = event {
|
||||
input_capture.release()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
174
src/client.rs
174
src/client.rs
@@ -1,10 +1,15 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
net::{IpAddr, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slab::Slab;
|
||||
|
||||
use crate::config::DEFAULT_PORT;
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum Position {
|
||||
@@ -20,6 +25,33 @@ impl Default for Position {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PositionParseError {
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl Display for PositionParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "not a valid position: {}", self.string)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for PositionParseError {}
|
||||
|
||||
impl FromStr for Position {
|
||||
type Err = PositionParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"left" => Ok(Self::Left),
|
||||
"right" => Ok(Self::Right),
|
||||
"top" => Ok(Self::Top),
|
||||
"bottom" => Ok(Self::Bottom),
|
||||
_ => Err(PositionParseError { string: s.into() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn opposite(&self) -> Self {
|
||||
match self {
|
||||
@@ -61,23 +93,29 @@ impl TryFrom<&str> for Position {
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Client {
|
||||
pub struct ClientConfig {
|
||||
/// hostname of this client
|
||||
pub hostname: Option<String>,
|
||||
/// fix ips, determined by the user
|
||||
pub fix_ips: Vec<IpAddr>,
|
||||
/// unique handle to refer to the client.
|
||||
/// This way any emulation / capture backend does not
|
||||
/// need to know anything about a client other than its handle.
|
||||
pub handle: ClientHandle,
|
||||
/// 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 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,
|
||||
/// enter hook
|
||||
pub cmd: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ClientConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: DEFAULT_PORT,
|
||||
hostname: Default::default(),
|
||||
fix_ips: Default::default(),
|
||||
pos: Default::default(),
|
||||
cmd: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -86,12 +124,10 @@ pub enum ClientEvent {
|
||||
Destroy(ClientHandle),
|
||||
}
|
||||
|
||||
pub type ClientHandle = u32;
|
||||
pub type ClientHandle = u64;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct ClientState {
|
||||
/// information about the client
|
||||
pub client: Client,
|
||||
/// events should be sent to and received from the client
|
||||
pub active: bool,
|
||||
/// `active` address of the client, used to send data to.
|
||||
@@ -100,12 +136,18 @@ pub struct ClientState {
|
||||
pub active_addr: Option<SocketAddr>,
|
||||
/// tracks whether or not the client is responding to pings
|
||||
pub alive: bool,
|
||||
/// 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 ips: HashSet<IpAddr>,
|
||||
/// keys currently pressed by this client
|
||||
pub pressed_keys: HashSet<u32>,
|
||||
/// dns resolving in progress
|
||||
pub resolving: bool,
|
||||
}
|
||||
|
||||
pub struct ClientManager {
|
||||
clients: Vec<Option<ClientState>>, // HashMap likely not beneficial
|
||||
clients: Slab<(ClientConfig, ClientState)>,
|
||||
}
|
||||
|
||||
impl Default for ClientManager {
|
||||
@@ -116,50 +158,15 @@ impl Default for ClientManager {
|
||||
|
||||
impl ClientManager {
|
||||
pub fn new() -> Self {
|
||||
Self { clients: vec![] }
|
||||
let clients = Slab::new();
|
||||
Self { clients }
|
||||
}
|
||||
|
||||
/// add a new client to this manager
|
||||
pub fn add_client(
|
||||
&mut self,
|
||||
hostname: Option<String>,
|
||||
ips: HashSet<IpAddr>,
|
||||
port: u16,
|
||||
pos: Position,
|
||||
active: bool,
|
||||
) -> ClientHandle {
|
||||
// get a new client_handle
|
||||
let handle = self.free_id();
|
||||
|
||||
// store fix ip addresses
|
||||
let fix_ips = ips.iter().cloned().collect();
|
||||
|
||||
// store the client
|
||||
let client = Client {
|
||||
hostname,
|
||||
fix_ips,
|
||||
handle,
|
||||
ips,
|
||||
port,
|
||||
pos,
|
||||
};
|
||||
|
||||
// client was never seen, nor pinged
|
||||
let client_state = ClientState {
|
||||
client,
|
||||
active,
|
||||
active_addr: None,
|
||||
alive: false,
|
||||
pressed_keys: HashSet::new(),
|
||||
};
|
||||
|
||||
if handle as usize >= self.clients.len() {
|
||||
assert_eq!(handle as usize, self.clients.len());
|
||||
self.clients.push(Some(client_state));
|
||||
} else {
|
||||
self.clients[handle as usize] = Some(client_state);
|
||||
}
|
||||
handle
|
||||
pub fn add_client(&mut self) -> ClientHandle {
|
||||
let client_config = Default::default();
|
||||
let client_state = Default::default();
|
||||
self.clients.insert((client_config, client_state)) as ClientHandle
|
||||
}
|
||||
|
||||
/// find a client by its address
|
||||
@@ -168,49 +175,54 @@ impl ClientManager {
|
||||
// time this is likely faster than using a HashMap
|
||||
self.clients
|
||||
.iter()
|
||||
.position(|c| {
|
||||
if let Some(c) = c {
|
||||
c.active && c.client.ips.contains(&addr.ip())
|
||||
.find_map(|(k, (_, s))| {
|
||||
if s.active && s.ips.contains(&addr.ip()) {
|
||||
Some(k)
|
||||
} else {
|
||||
false
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|p| p as ClientHandle)
|
||||
}
|
||||
|
||||
pub fn find_client(&self, pos: Position) -> Option<ClientHandle> {
|
||||
self.clients
|
||||
.iter()
|
||||
.find_map(|(k, (c, s))| {
|
||||
if s.active && c.pos == pos {
|
||||
Some(k)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|p| p as ClientHandle)
|
||||
}
|
||||
|
||||
/// remove a client from the list
|
||||
pub fn remove_client(&mut self, client: ClientHandle) -> Option<ClientState> {
|
||||
pub fn remove_client(&mut self, client: ClientHandle) -> Option<(ClientConfig, ClientState)> {
|
||||
// remove id from occupied ids
|
||||
self.clients.get_mut(client as usize)?.take()
|
||||
}
|
||||
|
||||
/// get a free slot in the client list
|
||||
fn free_id(&mut self) -> ClientHandle {
|
||||
for i in 0..u32::MAX {
|
||||
if self.clients.get(i as usize).is_none()
|
||||
|| self.clients.get(i as usize).unwrap().is_none()
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
panic!("Out of client ids");
|
||||
self.clients.try_remove(client as usize)
|
||||
}
|
||||
|
||||
// returns an immutable reference to the client state corresponding to `client`
|
||||
pub fn get(&self, client: ClientHandle) -> Option<&ClientState> {
|
||||
self.clients.get(client as usize)?.as_ref()
|
||||
pub fn get(&self, handle: ClientHandle) -> Option<&(ClientConfig, ClientState)> {
|
||||
self.clients.get(handle as usize)
|
||||
}
|
||||
|
||||
/// returns a mutable reference to the client state corresponding to `client`
|
||||
pub fn get_mut(&mut self, client: ClientHandle) -> Option<&mut ClientState> {
|
||||
self.clients.get_mut(client as usize)?.as_mut()
|
||||
pub fn get_mut(&mut self, handle: ClientHandle) -> Option<&mut (ClientConfig, ClientState)> {
|
||||
self.clients.get_mut(handle as usize)
|
||||
}
|
||||
|
||||
pub fn get_client_states(&self) -> impl Iterator<Item = &ClientState> {
|
||||
self.clients.iter().filter_map(|x| x.as_ref())
|
||||
pub fn get_client_states(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (ClientHandle, &(ClientConfig, ClientState))> {
|
||||
self.clients.iter().map(|(k, v)| (k as ClientHandle, v))
|
||||
}
|
||||
|
||||
pub fn get_client_states_mut(&mut self) -> impl Iterator<Item = &mut ClientState> {
|
||||
self.clients.iter_mut().filter_map(|x| x.as_mut())
|
||||
pub fn get_client_states_mut(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = (ClientHandle, &mut (ClientConfig, ClientState))> {
|
||||
self.clients.iter_mut().map(|(k, v)| (k as ClientHandle, v))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub struct TomlClient {
|
||||
pub ips: Option<Vec<IpAddr>>,
|
||||
pub port: Option<u16>,
|
||||
pub activate_on_startup: Option<bool>,
|
||||
pub enter_hook: Option<String>,
|
||||
}
|
||||
|
||||
impl ConfigToml {
|
||||
@@ -59,6 +60,14 @@ struct CliArgs {
|
||||
/// run only the service as a daemon without the frontend
|
||||
#[arg(short, long)]
|
||||
daemon: bool,
|
||||
|
||||
/// test input capture
|
||||
#[arg(long)]
|
||||
test_capture: bool,
|
||||
|
||||
/// test input emulation
|
||||
#[arg(long)]
|
||||
test_emulation: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -74,6 +83,8 @@ pub struct Config {
|
||||
pub clients: Vec<(TomlClient, Position)>,
|
||||
pub daemon: bool,
|
||||
pub release_bind: Vec<scancode::Linux>,
|
||||
pub test_capture: bool,
|
||||
pub test_emulation: bool,
|
||||
}
|
||||
|
||||
pub struct ConfigClient {
|
||||
@@ -82,6 +93,7 @@ pub struct ConfigClient {
|
||||
pub port: u16,
|
||||
pub pos: Position,
|
||||
pub active: bool,
|
||||
pub enter_hook: Option<String>,
|
||||
}
|
||||
|
||||
const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] =
|
||||
@@ -169,6 +181,8 @@ impl Config {
|
||||
}
|
||||
|
||||
let daemon = args.daemon;
|
||||
let test_capture = args.test_capture;
|
||||
let test_emulation = args.test_emulation;
|
||||
|
||||
Ok(Config {
|
||||
daemon,
|
||||
@@ -176,6 +190,8 @@ impl Config {
|
||||
clients,
|
||||
port,
|
||||
release_bind,
|
||||
test_capture,
|
||||
test_emulation,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -194,12 +210,14 @@ impl Config {
|
||||
None => c.host_name.clone(),
|
||||
};
|
||||
let active = c.activate_on_startup.unwrap_or(false);
|
||||
let enter_hook = c.enter_hook.clone();
|
||||
ConfigClient {
|
||||
ips,
|
||||
hostname,
|
||||
port,
|
||||
pos: *pos,
|
||||
active,
|
||||
enter_hook,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use std::{error::Error, net::IpAddr};
|
||||
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
use hickory_resolver::TokioAsyncResolver;
|
||||
|
||||
pub struct DnsResolver {
|
||||
resolver: TokioAsyncResolver,
|
||||
|
||||
@@ -163,6 +163,18 @@ impl InputEmulation for LibeiEmulation {
|
||||
d.frame(self.serial, now);
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
if !self.has_scroll {
|
||||
return;
|
||||
}
|
||||
if let Some((d, s)) = self.scroll.as_mut() {
|
||||
match axis {
|
||||
0 => s.scroll_discrete(0, value),
|
||||
_ => s.scroll_discrete(value, 0),
|
||||
}
|
||||
d.frame(self.serial, now);
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::Frame {} => {}
|
||||
},
|
||||
Event::Keyboard(k) => match k {
|
||||
|
||||
@@ -220,7 +220,7 @@ impl InputEmulation for MacOSEmulation {
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
let value = value as i32 / 10; // FIXME: high precision scroll events
|
||||
let value = value as i32;
|
||||
let (count, wheel1, wheel2, wheel3) = match axis {
|
||||
0 => (1, value, 0, 0), // 0 = vertical => 1 scroll wheel device (y axis)
|
||||
1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
|
||||
@@ -231,7 +231,32 @@ impl InputEmulation for MacOSEmulation {
|
||||
};
|
||||
let event = match CGEvent::new_scroll_event(
|
||||
self.event_source.clone(),
|
||||
ScrollEventUnit::LINE,
|
||||
ScrollEventUnit::PIXEL,
|
||||
count,
|
||||
wheel1,
|
||||
wheel2,
|
||||
wheel3,
|
||||
) {
|
||||
Ok(e) => e,
|
||||
Err(()) => {
|
||||
log::warn!("scroll event creation failed!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
let (count, wheel1, wheel2, wheel3) = match axis {
|
||||
0 => (1, value, 0, 0), // 0 = vertical => 1 scroll wheel device (y axis)
|
||||
1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
|
||||
_ => {
|
||||
log::warn!("invalid scroll event: {axis}, {value}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let event = match CGEvent::new_scroll_event(
|
||||
self.event_source.clone(),
|
||||
ScrollEventUnit::PIXEL,
|
||||
count,
|
||||
wheel1,
|
||||
wheel2,
|
||||
|
||||
@@ -8,14 +8,18 @@ use async_trait::async_trait;
|
||||
use std::ops::BitOrAssign;
|
||||
use std::time::Duration;
|
||||
use tokio::task::AbortHandle;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{SendInput, INPUT_0, KEYEVENTF_EXTENDEDKEY};
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
SendInput, INPUT_0, KEYEVENTF_EXTENDEDKEY, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP,
|
||||
};
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
INPUT, INPUT_KEYBOARD, INPUT_MOUSE, KEYBDINPUT, KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE,
|
||||
MOUSEEVENTF_HWHEEL, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN,
|
||||
MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP,
|
||||
MOUSEEVENTF_WHEEL, MOUSEINPUT,
|
||||
};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{XBUTTON1, XBUTTON2};
|
||||
|
||||
use crate::event::{BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT};
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle},
|
||||
event::Event,
|
||||
@@ -55,7 +59,8 @@ impl InputEmulation for WindowsEmulation {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => scroll(axis, value),
|
||||
} => scroll(axis, value as i32),
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => scroll(axis, value),
|
||||
PointerEvent::Frame {} => {}
|
||||
},
|
||||
Event::Keyboard(keyboard_event) => match keyboard_event {
|
||||
@@ -145,23 +150,32 @@ fn rel_mouse(dx: i32, dy: i32) {
|
||||
fn mouse_button(button: u32, state: u32) {
|
||||
let dw_flags = match state {
|
||||
0 => match button {
|
||||
0x110 => MOUSEEVENTF_LEFTUP,
|
||||
0x111 => MOUSEEVENTF_RIGHTUP,
|
||||
0x112 => MOUSEEVENTF_MIDDLEUP,
|
||||
BTN_LEFT => MOUSEEVENTF_LEFTUP,
|
||||
BTN_RIGHT => MOUSEEVENTF_RIGHTUP,
|
||||
BTN_MIDDLE => MOUSEEVENTF_MIDDLEUP,
|
||||
BTN_BACK => MOUSEEVENTF_XUP,
|
||||
BTN_FORWARD => MOUSEEVENTF_XUP,
|
||||
_ => return,
|
||||
},
|
||||
1 => match button {
|
||||
0x110 => MOUSEEVENTF_LEFTDOWN,
|
||||
0x111 => MOUSEEVENTF_RIGHTDOWN,
|
||||
0x112 => MOUSEEVENTF_MIDDLEDOWN,
|
||||
BTN_LEFT => MOUSEEVENTF_LEFTDOWN,
|
||||
BTN_RIGHT => MOUSEEVENTF_RIGHTDOWN,
|
||||
BTN_MIDDLE => MOUSEEVENTF_MIDDLEDOWN,
|
||||
BTN_BACK => MOUSEEVENTF_XDOWN,
|
||||
BTN_FORWARD => MOUSEEVENTF_XDOWN,
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let mouse_data = match button {
|
||||
BTN_BACK => XBUTTON1 as u32,
|
||||
BTN_FORWARD => XBUTTON2 as u32,
|
||||
_ => 0,
|
||||
};
|
||||
let mi = MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0, // no movement
|
||||
mouseData: 0,
|
||||
mouseData: mouse_data,
|
||||
dwFlags: dw_flags,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
@@ -169,7 +183,7 @@ fn mouse_button(button: u32, state: u32) {
|
||||
send_mouse_input(mi);
|
||||
}
|
||||
|
||||
fn scroll(axis: u8, value: f64) {
|
||||
fn scroll(axis: u8, value: i32) {
|
||||
let event_type = match axis {
|
||||
0 => MOUSEEVENTF_WHEEL,
|
||||
1 => MOUSEEVENTF_HWHEEL,
|
||||
@@ -178,7 +192,7 @@ fn scroll(axis: u8, value: f64) {
|
||||
let mi = MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: (-value * 15.0) as i32 as u32,
|
||||
mouseData: -value as u32,
|
||||
dwFlags: event_type,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
|
||||
@@ -180,6 +180,12 @@ impl VirtualInput {
|
||||
self.pointer.axis(time, axis, value);
|
||||
self.pointer.frame();
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
let axis: Axis = (axis as u32).try_into()?;
|
||||
self.pointer
|
||||
.axis_discrete(0, axis, value as f64 / 6., value / 120);
|
||||
self.pointer.frame();
|
||||
}
|
||||
PointerEvent::Frame {} => self.pointer.frame(),
|
||||
}
|
||||
self.pointer.frame();
|
||||
|
||||
@@ -125,6 +125,9 @@ impl InputEmulation for X11Emulation {
|
||||
} => {
|
||||
self.emulate_scroll(axis, value);
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
self.emulate_scroll(axis, value as f64);
|
||||
}
|
||||
PointerEvent::Frame {} => {}
|
||||
},
|
||||
Event::Keyboard(KeyboardEvent::Key {
|
||||
|
||||
@@ -62,59 +62,73 @@ impl<'a> DesktopPortalEmulation<'a> {
|
||||
impl<'a> InputEmulation for DesktopPortalEmulation<'a> {
|
||||
async fn consume(&mut self, event: crate::event::Event, _client: crate::client::ClientHandle) {
|
||||
match event {
|
||||
Pointer(p) => {
|
||||
match p {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_motion(&self.session, relative_x, relative_y)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
Pointer(p) => match p {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_motion(&self.session, relative_x, relative_y)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let state = match state {
|
||||
0 => KeyState::Released,
|
||||
_ => KeyState::Pressed,
|
||||
};
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_button(&self.session, button as i32, state)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
let axis = match axis {
|
||||
0 => Axis::Vertical,
|
||||
_ => Axis::Horizontal,
|
||||
};
|
||||
// TODO smooth scrolling
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_axis_discrete(&self.session, axis, value as i32)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
PointerEvent::Frame {} => {}
|
||||
}
|
||||
}
|
||||
PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let state = match state {
|
||||
0 => KeyState::Released,
|
||||
_ => KeyState::Pressed,
|
||||
};
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_button(&self.session, button as i32, state)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
let axis = match axis {
|
||||
0 => Axis::Vertical,
|
||||
_ => Axis::Horizontal,
|
||||
};
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_axis_discrete(&self.session, axis, value)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
let axis = match axis {
|
||||
0 => Axis::Vertical,
|
||||
_ => Axis::Horizontal,
|
||||
};
|
||||
let (dx, dy) = match axis {
|
||||
Axis::Vertical => (0., value),
|
||||
Axis::Horizontal => (value, 0.),
|
||||
};
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_axis(&self.session, dx, dy, true)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
PointerEvent::Frame {} => {}
|
||||
},
|
||||
Keyboard(k) => {
|
||||
match k {
|
||||
KeyboardEvent::Key {
|
||||
|
||||
48
src/emulation_test.rs
Normal file
48
src/emulation_test.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use crate::client::{ClientEvent, Position};
|
||||
use crate::emulate;
|
||||
use crate::event::{Event, PointerEvent};
|
||||
use anyhow::Result;
|
||||
use std::f64::consts::PI;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
pub fn run() -> Result<()> {
|
||||
log::info!("running input emulation test");
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()?;
|
||||
|
||||
runtime.block_on(LocalSet::new().run_until(input_emulation_test()))
|
||||
}
|
||||
|
||||
const FREQUENCY_HZ: f64 = 1.0;
|
||||
const RADIUS: f64 = 100.0;
|
||||
|
||||
async fn input_emulation_test() -> Result<()> {
|
||||
let mut emulation = emulate::create().await;
|
||||
emulation
|
||||
.notify(ClientEvent::Create(0, Position::Left))
|
||||
.await;
|
||||
let start = Instant::now();
|
||||
let mut offset = (0, 0);
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = emulation.dispatch() => {}
|
||||
_ = tokio::time::sleep(Duration::from_millis(1)) => {
|
||||
let elapsed = start.elapsed();
|
||||
let elapsed_sec_f64 = elapsed.as_secs_f64();
|
||||
let second_fraction = elapsed_sec_f64 - elapsed_sec_f64 as u64 as f64;
|
||||
let radians = second_fraction * 2. * PI * FREQUENCY_HZ;
|
||||
let new_offset_f = (radians.cos() * RADIUS * 2., (radians * 2.).sin() * RADIUS);
|
||||
let new_offset = (new_offset_f.0 as i32, new_offset_f.1 as i32);
|
||||
if new_offset != offset {
|
||||
let relative_motion = (new_offset.0 - offset.0, new_offset.1 - offset.1);
|
||||
offset = new_offset;
|
||||
let (relative_x, relative_y) = (relative_motion.0 as f64, relative_motion.1 as f64);
|
||||
emulation.consume(Event::Pointer(PointerEvent::Motion {time: 0, relative_x, relative_y }), 0).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/event.rs
60
src/event.rs
@@ -1,3 +1,4 @@
|
||||
use crate::scancode;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
error::Error,
|
||||
@@ -28,6 +29,10 @@ pub enum PointerEvent {
|
||||
axis: u8,
|
||||
value: f64,
|
||||
},
|
||||
AxisDiscrete120 {
|
||||
axis: u8,
|
||||
value: i32,
|
||||
},
|
||||
Frame {},
|
||||
}
|
||||
|
||||
@@ -83,12 +88,29 @@ impl Display for PointerEvent {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => write!(f, "button({button}, {state})"),
|
||||
} => {
|
||||
let str = match *button {
|
||||
BTN_LEFT => Some("left"),
|
||||
BTN_RIGHT => Some("right"),
|
||||
BTN_MIDDLE => Some("middle"),
|
||||
BTN_FORWARD => Some("forward"),
|
||||
BTN_BACK => Some("back"),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(button) = str {
|
||||
write!(f, "button({button}, {state})")
|
||||
} else {
|
||||
write!(f, "button({button}, {state}")
|
||||
}
|
||||
}
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => write!(f, "scroll({axis}, {value})"),
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
write!(f, "scroll-120 ({axis}, {value})")
|
||||
}
|
||||
PointerEvent::Frame {} => write!(f, "frame()"),
|
||||
}
|
||||
}
|
||||
@@ -101,7 +123,14 @@ impl Display for KeyboardEvent {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
} => write!(f, "key({key}, {state})"),
|
||||
} => {
|
||||
let scan = scancode::Linux::try_from(*key);
|
||||
if let Ok(scan) = scan {
|
||||
write!(f, "key({scan:?}, {state})")
|
||||
} else {
|
||||
write!(f, "key({key}, {state})")
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
@@ -149,6 +178,7 @@ impl PointerEvent {
|
||||
Self::Motion { .. } => PointerEventType::Motion,
|
||||
Self::Button { .. } => PointerEventType::Button,
|
||||
Self::Axis { .. } => PointerEventType::Axis,
|
||||
Self::AxisDiscrete120 { .. } => PointerEventType::AxisDiscrete120,
|
||||
Self::Frame { .. } => PointerEventType::Frame,
|
||||
}
|
||||
}
|
||||
@@ -167,6 +197,7 @@ enum PointerEventType {
|
||||
Motion,
|
||||
Button,
|
||||
Axis,
|
||||
AxisDiscrete120,
|
||||
Frame,
|
||||
}
|
||||
enum KeyboardEventType {
|
||||
@@ -191,6 +222,7 @@ impl TryFrom<u8> for PointerEventType {
|
||||
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::AxisDiscrete120 as u8 => Ok(Self::AxisDiscrete120),
|
||||
x if x == Self::Frame as u8 => Ok(Self::Frame),
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid pointer event type {}", value),
|
||||
@@ -291,6 +323,11 @@ impl From<&PointerEvent> for Vec<u8> {
|
||||
let value = value.to_be_bytes();
|
||||
[&time[..], &axis[..], &value[..]].concat()
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
let axis = axis.to_be_bytes();
|
||||
let value = value.to_be_bytes();
|
||||
[&axis[..], &value[..]].concat()
|
||||
}
|
||||
PointerEvent::Frame {} => {
|
||||
vec![]
|
||||
}
|
||||
@@ -399,6 +436,25 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
};
|
||||
Ok(Self::Axis { time, axis, value })
|
||||
}
|
||||
PointerEventType::AxisDiscrete120 => {
|
||||
let axis = match data.get(2) {
|
||||
Some(d) => *d,
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 1 Byte at index 2".into(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
let value = match data.get(3..7) {
|
||||
Some(d) => i32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 3".into(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
Ok(Self::AxisDiscrete120 { axis, value })
|
||||
}
|
||||
PointerEventType::Frame => Ok(Self::Frame {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{cmp::min, io::ErrorKind, str, time::Duration};
|
||||
use std::{cmp::min, io::ErrorKind, net::IpAddr, str, time::Duration};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::{
|
||||
@@ -23,7 +23,7 @@ use tokio::net::TcpStream;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
client::{Client, ClientHandle, Position},
|
||||
client::{ClientConfig, ClientHandle, ClientState, Position},
|
||||
config::{Config, Frontend},
|
||||
};
|
||||
|
||||
@@ -84,34 +84,49 @@ pub fn wait_for_service() -> Result<std::net::TcpStream> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum FrontendEvent {
|
||||
/// add a new client
|
||||
AddClient(Option<String>, u16, Position),
|
||||
pub enum FrontendRequest {
|
||||
/// activate/deactivate client
|
||||
ActivateClient(ClientHandle, bool),
|
||||
Activate(ClientHandle, bool),
|
||||
/// add a new client
|
||||
Create,
|
||||
/// change the listen port (recreate udp listener)
|
||||
ChangePort(u16),
|
||||
/// remove a client
|
||||
DelClient(ClientHandle),
|
||||
/// request an enumertaion of all clients
|
||||
Delete(ClientHandle),
|
||||
/// request an enumeration of all clients
|
||||
Enumerate(),
|
||||
/// resolve dns
|
||||
ResolveDns(ClientHandle),
|
||||
/// service shutdown
|
||||
Shutdown(),
|
||||
/// update a client (hostname, port, position)
|
||||
UpdateClient(ClientHandle, Option<String>, u16, Position),
|
||||
Terminate(),
|
||||
/// update hostname
|
||||
UpdateHostname(ClientHandle, Option<String>),
|
||||
/// update port
|
||||
UpdatePort(ClientHandle, u16),
|
||||
/// update position
|
||||
UpdatePosition(ClientHandle, Position),
|
||||
/// update fix-ips
|
||||
UpdateFixIps(ClientHandle, Vec<IpAddr>),
|
||||
/// request the state of the given client
|
||||
GetState(ClientHandle),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum FrontendNotify {
|
||||
NotifyClientActivate(ClientHandle, bool),
|
||||
NotifyClientCreate(Client),
|
||||
NotifyClientUpdate(Client),
|
||||
NotifyClientDelete(ClientHandle),
|
||||
pub enum FrontendEvent {
|
||||
/// a client was created
|
||||
Created(ClientHandle, ClientConfig, ClientState),
|
||||
/// no such client
|
||||
NoSuchClient(ClientHandle),
|
||||
/// state changed
|
||||
State(ClientHandle, ClientConfig, ClientState),
|
||||
/// the client was deleted
|
||||
Deleted(ClientHandle),
|
||||
/// new port, reason of failure (if failed)
|
||||
NotifyPortChange(u16, Option<String>),
|
||||
/// Client State, active
|
||||
Enumerate(Vec<(Client, bool)>),
|
||||
NotifyError(String),
|
||||
PortChanged(u16, Option<String>),
|
||||
/// list of all clients, used for initial state synchronization
|
||||
Enumerate(Vec<(ClientHandle, ClientConfig, ClientState)>),
|
||||
/// an error occured
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub struct FrontendListener {
|
||||
@@ -217,13 +232,12 @@ impl FrontendListener {
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
pub(crate) async fn notify_all(&mut self, notify: FrontendNotify) -> Result<()> {
|
||||
pub(crate) async fn broadcast_event(&mut self, notify: FrontendEvent) -> Result<()> {
|
||||
// encode event
|
||||
let json = serde_json::to_string(¬ify).unwrap();
|
||||
let payload = json.as_bytes();
|
||||
let len = payload.len().to_be_bytes();
|
||||
log::debug!("json: {json}, len: {}", payload.len());
|
||||
|
||||
log::debug!("broadcasting event to streams: {json}");
|
||||
let mut keep = vec![];
|
||||
// TODO do simultaneously
|
||||
for tx in self.tx_streams.iter_mut() {
|
||||
@@ -255,7 +269,7 @@ impl Drop for FrontendListener {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub async fn read_event(stream: &mut ReadHalf<UnixStream>) -> Result<FrontendEvent> {
|
||||
pub async fn wait_for_request(stream: &mut ReadHalf<UnixStream>) -> Result<FrontendRequest> {
|
||||
let len = stream.read_u64().await?;
|
||||
assert!(len <= 256);
|
||||
let mut buf = [0u8; 256];
|
||||
@@ -264,7 +278,7 @@ pub async fn read_event(stream: &mut ReadHalf<UnixStream>) -> Result<FrontendEve
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub async fn read_event(stream: &mut ReadHalf<TcpStream>) -> Result<FrontendEvent> {
|
||||
pub async fn wait_for_request(stream: &mut ReadHalf<TcpStream>) -> Result<FrontendRequest> {
|
||||
let len = stream.read_u64().await?;
|
||||
let mut buf = [0u8; 256];
|
||||
stream.read_exact(&mut buf[..len as usize]).await?;
|
||||
|
||||
@@ -1,242 +1,325 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
||||
use std::{
|
||||
io::{ErrorKind, Read, Write},
|
||||
str::SplitWhitespace,
|
||||
thread,
|
||||
use anyhow::{anyhow, Result};
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
|
||||
task::LocalSet,
|
||||
};
|
||||
|
||||
use crate::{client::Position, config::DEFAULT_PORT};
|
||||
#[cfg(windows)]
|
||||
use tokio::net::tcp::{ReadHalf, WriteHalf};
|
||||
#[cfg(unix)]
|
||||
use tokio::net::unix::{ReadHalf, WriteHalf};
|
||||
|
||||
use super::{FrontendEvent, FrontendNotify};
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::{
|
||||
client::{ClientConfig, ClientHandle, ClientState},
|
||||
config::DEFAULT_PORT,
|
||||
};
|
||||
|
||||
use self::command::{Command, CommandType};
|
||||
|
||||
use super::{FrontendEvent, FrontendRequest};
|
||||
|
||||
mod command;
|
||||
|
||||
pub fn run() -> Result<()> {
|
||||
let Ok(mut tx) = super::wait_for_service() else {
|
||||
let Ok(stream) = super::wait_for_service() else {
|
||||
return Err(anyhow!("Could not connect to lan-mouse-socket"));
|
||||
};
|
||||
|
||||
let mut rx = tx.try_clone()?;
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()?;
|
||||
runtime.block_on(LocalSet::new().run_until(async move {
|
||||
stream.set_nonblocking(true)?;
|
||||
#[cfg(unix)]
|
||||
let mut stream = tokio::net::UnixStream::from_std(stream)?;
|
||||
#[cfg(windows)]
|
||||
let mut stream = tokio::net::TcpStream::from_std(stream)?;
|
||||
let (rx, tx) = stream.split();
|
||||
|
||||
let reader = thread::Builder::new()
|
||||
.name("cli-frontend".to_string())
|
||||
.spawn(move || {
|
||||
// all further prompts
|
||||
prompt();
|
||||
loop {
|
||||
let mut buf = String::new();
|
||||
match std::io::stdin().read_line(&mut buf) {
|
||||
Ok(0) => return,
|
||||
Ok(len) => {
|
||||
if let Some(events) = parse_cmd(buf, len) {
|
||||
for event in events.iter() {
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
let bytes = json.as_bytes();
|
||||
let len = bytes.len().to_be_bytes();
|
||||
if let Err(e) = tx.write(&len) {
|
||||
log::error!("error sending message: {e}");
|
||||
};
|
||||
if let Err(e) = tx.write(bytes) {
|
||||
log::error!("error sending message: {e}");
|
||||
};
|
||||
if *event == FrontendEvent::Shutdown() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// prompt is printed after the server response is received
|
||||
} else {
|
||||
prompt();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() != ErrorKind::UnexpectedEof {
|
||||
log::error!("error reading from stdin: {e}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
let _ = thread::Builder::new()
|
||||
.name("cli-frontend-notify".to_string())
|
||||
.spawn(move || {
|
||||
loop {
|
||||
// read len
|
||||
let mut len = [0u8; 8];
|
||||
match rx.read_exact(&mut len) {
|
||||
Ok(()) => (),
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
|
||||
Err(e) => break log::error!("{e}"),
|
||||
};
|
||||
let len = usize::from_be_bytes(len);
|
||||
|
||||
// read payload
|
||||
let mut buf: Vec<u8> = vec![0u8; len];
|
||||
match rx.read_exact(&mut buf[..len]) {
|
||||
Ok(()) => (),
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
|
||||
Err(e) => break log::error!("{e}"),
|
||||
};
|
||||
|
||||
let notify: FrontendNotify = match serde_json::from_slice(&buf) {
|
||||
Ok(n) => n,
|
||||
Err(e) => break log::error!("{e}"),
|
||||
};
|
||||
match notify {
|
||||
FrontendNotify::NotifyClientActivate(handle, active) => {
|
||||
if active {
|
||||
log::info!("client {handle} activated");
|
||||
} else {
|
||||
log::info!("client {handle} deactivated");
|
||||
}
|
||||
}
|
||||
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.");
|
||||
}
|
||||
FrontendNotify::NotifyError(e) => {
|
||||
log::warn!("{e}");
|
||||
}
|
||||
FrontendNotify::Enumerate(clients) => {
|
||||
for (client, active) in clients.into_iter() {
|
||||
log::info!(
|
||||
"client ({}) [{}]: active: {}, associated addresses: [{}]",
|
||||
client.handle,
|
||||
client.hostname.as_deref().unwrap_or(""),
|
||||
if active { "yes" } else { "no" },
|
||||
client
|
||||
.ips
|
||||
.into_iter()
|
||||
.map(|a| a.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
FrontendNotify::NotifyPortChange(port, msg) => match msg {
|
||||
Some(msg) => log::info!("could not change port: {msg}"),
|
||||
None => log::info!("port changed: {port}"),
|
||||
},
|
||||
}
|
||||
prompt();
|
||||
}
|
||||
})?;
|
||||
match reader.join() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let msg = match (e.downcast_ref::<&str>(), e.downcast_ref::<String>()) {
|
||||
(Some(&s), _) => s,
|
||||
(_, Some(s)) => s,
|
||||
_ => "no panic info",
|
||||
};
|
||||
log::error!("reader thread paniced: {msg}");
|
||||
}
|
||||
}
|
||||
let mut cli = Cli::new(rx, tx);
|
||||
cli.run().await
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prompt() {
|
||||
struct Cli<'a> {
|
||||
clients: Vec<(ClientHandle, ClientConfig, ClientState)>,
|
||||
rx: ReadHalf<'a>,
|
||||
tx: WriteHalf<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Cli<'a> {
|
||||
fn new(rx: ReadHalf<'a>, tx: WriteHalf<'a>) -> Cli<'a> {
|
||||
Self {
|
||||
clients: vec![],
|
||||
rx,
|
||||
tx,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) -> Result<()> {
|
||||
let stdin = tokio::io::stdin();
|
||||
let stdin = BufReader::new(stdin);
|
||||
let mut stdin = stdin.lines();
|
||||
|
||||
/* initial state sync */
|
||||
let request = FrontendRequest::Enumerate();
|
||||
self.send_request(request).await?;
|
||||
|
||||
self.clients = loop {
|
||||
let event = self.await_event().await?;
|
||||
if let FrontendEvent::Enumerate(clients) = event {
|
||||
break clients;
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
prompt()?;
|
||||
tokio::select! {
|
||||
line = stdin.next_line() => {
|
||||
let Some(line) = line? else {
|
||||
break Ok(());
|
||||
};
|
||||
let cmd: Command = match line.parse() {
|
||||
Ok(cmd) => cmd,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
self.execute(cmd).await?;
|
||||
}
|
||||
event = self.await_event() => {
|
||||
let event = event?;
|
||||
self.handle_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_client(&mut self, handle: ClientHandle) -> Result<()> {
|
||||
self.send_request(FrontendRequest::GetState(handle)).await?;
|
||||
loop {
|
||||
let event = self.await_event().await?;
|
||||
self.handle_event(event.clone());
|
||||
if let FrontendEvent::State(_, _, _) | FrontendEvent::NoSuchClient(_) = event {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute(&mut self, cmd: Command) -> Result<()> {
|
||||
match cmd {
|
||||
Command::None => {}
|
||||
Command::Connect(pos, host, port) => {
|
||||
let request = FrontendRequest::Create;
|
||||
self.send_request(request).await?;
|
||||
let handle = loop {
|
||||
let event = self.await_event().await?;
|
||||
match event {
|
||||
FrontendEvent::Created(h, c, s) => {
|
||||
self.clients.push((h, c, s));
|
||||
break h;
|
||||
}
|
||||
_ => {
|
||||
self.handle_event(event);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
for request in [
|
||||
FrontendRequest::UpdateHostname(handle, Some(host.clone())),
|
||||
FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT)),
|
||||
FrontendRequest::UpdatePosition(handle, pos),
|
||||
] {
|
||||
self.send_request(request).await?;
|
||||
}
|
||||
self.update_client(handle).await?;
|
||||
}
|
||||
Command::Disconnect(id) => {
|
||||
self.send_request(FrontendRequest::Delete(id)).await?;
|
||||
loop {
|
||||
let event = self.await_event().await?;
|
||||
self.handle_event(event.clone());
|
||||
if let FrontendEvent::Deleted(_) = event {
|
||||
self.handle_event(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Activate(id) => {
|
||||
self.send_request(FrontendRequest::Activate(id, true))
|
||||
.await?;
|
||||
self.update_client(id).await?;
|
||||
}
|
||||
Command::Deactivate(id) => {
|
||||
self.send_request(FrontendRequest::Activate(id, false))
|
||||
.await?;
|
||||
self.update_client(id).await?;
|
||||
}
|
||||
Command::List => {
|
||||
self.send_request(FrontendRequest::Enumerate()).await?;
|
||||
loop {
|
||||
let event = self.await_event().await?;
|
||||
self.handle_event(event.clone());
|
||||
if let FrontendEvent::Enumerate(_) = event {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::SetHost(handle, host) => {
|
||||
let request = FrontendRequest::UpdateHostname(handle, Some(host.clone()));
|
||||
self.send_request(request).await?;
|
||||
self.update_client(handle).await?;
|
||||
}
|
||||
Command::SetPort(handle, port) => {
|
||||
let request = FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT));
|
||||
self.send_request(request).await?;
|
||||
self.update_client(handle).await?;
|
||||
}
|
||||
Command::Help => {
|
||||
for cmd_type in [
|
||||
CommandType::List,
|
||||
CommandType::Connect,
|
||||
CommandType::Disconnect,
|
||||
CommandType::Activate,
|
||||
CommandType::Deactivate,
|
||||
CommandType::SetHost,
|
||||
CommandType::SetPort,
|
||||
] {
|
||||
eprintln!("{}", cmd_type.usage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_mut(
|
||||
&mut self,
|
||||
handle: ClientHandle,
|
||||
) -> Option<&mut (ClientHandle, ClientConfig, ClientState)> {
|
||||
self.clients.iter_mut().find(|(h, _, _)| *h == handle)
|
||||
}
|
||||
|
||||
fn remove(
|
||||
&mut self,
|
||||
handle: ClientHandle,
|
||||
) -> Option<(ClientHandle, ClientConfig, ClientState)> {
|
||||
let idx = self.clients.iter().position(|(h, _, _)| *h == handle);
|
||||
idx.map(|i| self.clients.swap_remove(i))
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: FrontendEvent) {
|
||||
match event {
|
||||
FrontendEvent::Created(h, c, s) => {
|
||||
eprint!("client added ({h}): ");
|
||||
print_config(&c);
|
||||
eprint!(" ");
|
||||
print_state(&s);
|
||||
eprintln!();
|
||||
self.clients.push((h, c, s));
|
||||
}
|
||||
FrontendEvent::NoSuchClient(h) => {
|
||||
eprintln!("no such client: {h}");
|
||||
}
|
||||
FrontendEvent::State(h, c, s) => {
|
||||
if let Some((_, config, state)) = self.find_mut(h) {
|
||||
let old_host = config.hostname.clone().unwrap_or("\"\"".into());
|
||||
let new_host = c.hostname.clone().unwrap_or("\"\"".into());
|
||||
if old_host != new_host {
|
||||
eprintln!(
|
||||
"client {h}: hostname updated ({} -> {})",
|
||||
old_host, new_host
|
||||
);
|
||||
}
|
||||
if config.port != c.port {
|
||||
eprintln!("client {h} changed port: {} -> {}", config.port, c.port);
|
||||
}
|
||||
if config.fix_ips != c.fix_ips {
|
||||
eprintln!("client {h} ips updated: {:?}", c.fix_ips)
|
||||
}
|
||||
*config = c;
|
||||
if state.active ^ s.active {
|
||||
eprintln!(
|
||||
"client {h} {}",
|
||||
if s.active { "activated" } else { "deactivated" }
|
||||
);
|
||||
}
|
||||
*state = s;
|
||||
}
|
||||
}
|
||||
FrontendEvent::Deleted(h) => {
|
||||
if let Some((h, c, _)) = self.remove(h) {
|
||||
eprint!("client {h} removed (");
|
||||
print_config(&c);
|
||||
eprintln!(")");
|
||||
}
|
||||
}
|
||||
FrontendEvent::PortChanged(p, e) => {
|
||||
if let Some(e) = e {
|
||||
eprintln!("failed to change port: {e}");
|
||||
} else {
|
||||
eprintln!("changed port to {p}");
|
||||
}
|
||||
}
|
||||
FrontendEvent::Enumerate(clients) => {
|
||||
self.clients = clients;
|
||||
self.print_clients();
|
||||
}
|
||||
FrontendEvent::Error(e) => {
|
||||
eprintln!("ERROR: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_clients(&mut self) {
|
||||
for (h, c, s) in self.clients.iter() {
|
||||
eprint!("client {h}: ");
|
||||
print_config(c);
|
||||
eprint!(" ");
|
||||
print_state(s);
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_request(&mut self, request: FrontendRequest) -> io::Result<()> {
|
||||
let json = serde_json::to_string(&request).unwrap();
|
||||
let bytes = json.as_bytes();
|
||||
let len = bytes.len();
|
||||
self.tx.write_u64(len as u64).await?;
|
||||
self.tx.write_all(bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn await_event(&mut self) -> Result<FrontendEvent> {
|
||||
let len = self.rx.read_u64().await?;
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
self.rx.read_exact(&mut buf).await?;
|
||||
let event: FrontendEvent = serde_json::from_slice(&buf)?;
|
||||
Ok(event)
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt() -> io::Result<()> {
|
||||
eprint!("lan-mouse > ");
|
||||
std::io::stderr().flush().unwrap();
|
||||
std::io::stderr().flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_cmd(s: String, len: usize) -> Option<Vec<FrontendEvent>> {
|
||||
if len == 0 {
|
||||
return Some(vec![FrontendEvent::Shutdown()]);
|
||||
}
|
||||
let mut l = s.split_whitespace();
|
||||
let cmd = l.next()?;
|
||||
let res = match cmd {
|
||||
"help" => {
|
||||
log::info!("list list clients");
|
||||
log::info!("connect <host> left|right|top|bottom [port] add a new client");
|
||||
log::info!("disconnect <client> remove a client");
|
||||
log::info!("activate <client> activate a client");
|
||||
log::info!("deactivate <client> deactivate a client");
|
||||
log::info!("exit exit lan-mouse");
|
||||
log::info!("setport <port> change port");
|
||||
None
|
||||
}
|
||||
"exit" => return Some(vec![FrontendEvent::Shutdown()]),
|
||||
"list" => return Some(vec![FrontendEvent::Enumerate()]),
|
||||
"connect" => Some(parse_connect(l)),
|
||||
"disconnect" => Some(parse_disconnect(l)),
|
||||
"activate" => Some(parse_activate(l)),
|
||||
"deactivate" => Some(parse_deactivate(l)),
|
||||
"setport" => Some(parse_port(l)),
|
||||
_ => {
|
||||
log::error!("unknown command: {s}");
|
||||
None
|
||||
}
|
||||
};
|
||||
match res {
|
||||
Some(Ok(e)) => Some(e),
|
||||
Some(Err(e)) => {
|
||||
log::warn!("{e}");
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
fn print_config(c: &ClientConfig) {
|
||||
eprint!(
|
||||
"{}:{} ({}), ips: {:?}",
|
||||
c.hostname.clone().unwrap_or("(no hostname)".into()),
|
||||
c.port,
|
||||
c.pos,
|
||||
c.fix_ips
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_connect(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
|
||||
let usage = "usage: connect <host> left|right|top|bottom [port]";
|
||||
let host = l.next().context(usage)?.to_owned();
|
||||
let pos = match l.next().context(usage)? {
|
||||
"right" => Position::Right,
|
||||
"top" => Position::Top,
|
||||
"bottom" => Position::Bottom,
|
||||
_ => Position::Left,
|
||||
};
|
||||
let port = if let Some(p) = l.next() {
|
||||
p.parse()?
|
||||
} else {
|
||||
DEFAULT_PORT
|
||||
};
|
||||
Ok(vec![
|
||||
FrontendEvent::AddClient(Some(host), port, pos),
|
||||
FrontendEvent::Enumerate(),
|
||||
])
|
||||
}
|
||||
|
||||
fn parse_disconnect(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
|
||||
let client = l.next().context("usage: disconnect <client_id>")?.parse()?;
|
||||
Ok(vec![
|
||||
FrontendEvent::DelClient(client),
|
||||
FrontendEvent::Enumerate(),
|
||||
])
|
||||
}
|
||||
|
||||
fn parse_activate(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
|
||||
let client = l.next().context("usage: activate <client_id>")?.parse()?;
|
||||
Ok(vec![
|
||||
FrontendEvent::ActivateClient(client, true),
|
||||
FrontendEvent::Enumerate(),
|
||||
])
|
||||
}
|
||||
|
||||
fn parse_deactivate(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
|
||||
let client = l.next().context("usage: deactivate <client_id>")?.parse()?;
|
||||
Ok(vec![
|
||||
FrontendEvent::ActivateClient(client, false),
|
||||
FrontendEvent::Enumerate(),
|
||||
])
|
||||
}
|
||||
|
||||
fn parse_port(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
|
||||
let port = l.next().context("usage: setport <port>")?.parse()?;
|
||||
Ok(vec![FrontendEvent::ChangePort(port)])
|
||||
fn print_state(s: &ClientState) {
|
||||
eprint!("active: {}, dns: {:?}", s.active, s.ips);
|
||||
}
|
||||
|
||||
153
src/frontend/cli/command.rs
Normal file
153
src/frontend/cli/command.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
str::{FromStr, SplitWhitespace},
|
||||
};
|
||||
|
||||
use crate::client::{ClientHandle, Position};
|
||||
|
||||
pub(super) enum CommandType {
|
||||
NoCommand,
|
||||
Help,
|
||||
Connect,
|
||||
Disconnect,
|
||||
Activate,
|
||||
Deactivate,
|
||||
List,
|
||||
SetHost,
|
||||
SetPort,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct InvalidCommand {
|
||||
cmd: String,
|
||||
}
|
||||
|
||||
impl Display for InvalidCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "invalid command: \"{}\"", self.cmd)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CommandType {
|
||||
type Err = InvalidCommand;
|
||||
|
||||
fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
|
||||
match s {
|
||||
"connect" => Ok(Self::Connect),
|
||||
"disconnect" => Ok(Self::Disconnect),
|
||||
"activate" => Ok(Self::Activate),
|
||||
"deactivate" => Ok(Self::Deactivate),
|
||||
"list" => Ok(Self::List),
|
||||
"set-host" => Ok(Self::SetHost),
|
||||
"set-port" => Ok(Self::SetPort),
|
||||
"help" => Ok(Self::Help),
|
||||
_ => Err(InvalidCommand { cmd: s.to_string() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum Command {
|
||||
None,
|
||||
Help,
|
||||
Connect(Position, String, Option<u16>),
|
||||
Disconnect(ClientHandle),
|
||||
Activate(ClientHandle),
|
||||
Deactivate(ClientHandle),
|
||||
List,
|
||||
SetHost(ClientHandle, String),
|
||||
SetPort(ClientHandle, Option<u16>),
|
||||
}
|
||||
|
||||
impl CommandType {
|
||||
pub(super) fn usage(&self) -> &'static str {
|
||||
match self {
|
||||
CommandType::Help => "help",
|
||||
CommandType::NoCommand => "",
|
||||
CommandType::Connect => "connect left|right|top|bottom <host> [<port>]",
|
||||
CommandType::Disconnect => "disconnect <id>",
|
||||
CommandType::Activate => "activate <id>",
|
||||
CommandType::Deactivate => "deactivate <id>",
|
||||
CommandType::List => "list",
|
||||
CommandType::SetHost => "set-host <id> <host>",
|
||||
CommandType::SetPort => "set-port <id> <host>",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) enum CommandParseError {
|
||||
Usage(CommandType),
|
||||
Invalid(InvalidCommand),
|
||||
}
|
||||
|
||||
impl Display for CommandParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Usage(cmd) => write!(f, "usage: {}", cmd.usage()),
|
||||
Self::Invalid(cmd) => write!(f, "{}", cmd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Command {
|
||||
type Err = CommandParseError;
|
||||
|
||||
fn from_str(cmd: &str) -> Result<Self, Self::Err> {
|
||||
let mut args = cmd.split_whitespace();
|
||||
let cmd_type: CommandType = match args.next() {
|
||||
Some(c) => c.parse().map_err(CommandParseError::Invalid),
|
||||
None => Ok(CommandType::NoCommand),
|
||||
}?;
|
||||
match cmd_type {
|
||||
CommandType::Help => Ok(Command::Help),
|
||||
CommandType::NoCommand => Ok(Command::None),
|
||||
CommandType::Connect => parse_connect_cmd(args),
|
||||
CommandType::Disconnect => parse_disconnect_cmd(args),
|
||||
CommandType::Activate => parse_activate_cmd(args),
|
||||
CommandType::Deactivate => parse_deactivate_cmd(args),
|
||||
CommandType::List => Ok(Command::List),
|
||||
CommandType::SetHost => parse_set_host(args),
|
||||
CommandType::SetPort => parse_set_port(args),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_connect_cmd(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::Connect);
|
||||
let pos = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||
let host = args.next().ok_or(USAGE)?.to_string();
|
||||
let port = args.next().and_then(|p| p.parse().ok());
|
||||
Ok(Command::Connect(pos, host, port))
|
||||
}
|
||||
|
||||
fn parse_disconnect_cmd(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::Disconnect);
|
||||
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||
Ok(Command::Disconnect(id))
|
||||
}
|
||||
|
||||
fn parse_activate_cmd(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::Activate);
|
||||
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||
Ok(Command::Activate(id))
|
||||
}
|
||||
|
||||
fn parse_deactivate_cmd(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::Deactivate);
|
||||
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||
Ok(Command::Deactivate(id))
|
||||
}
|
||||
|
||||
fn parse_set_host(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::SetHost);
|
||||
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||
let host = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||
Ok(Command::SetHost(id, host))
|
||||
}
|
||||
|
||||
fn parse_set_port(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::SetPort);
|
||||
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||
let port = args.next().and_then(|p| p.parse().ok());
|
||||
Ok(Command::SetPort(id, port))
|
||||
}
|
||||
@@ -8,18 +8,18 @@ use std::{
|
||||
process, str,
|
||||
};
|
||||
|
||||
use crate::frontend::gtk::window::Window;
|
||||
use crate::frontend::{gtk::window::Window, FrontendRequest};
|
||||
|
||||
use adw::Application;
|
||||
use endi::{Endian, ReadBytes};
|
||||
use gtk::{
|
||||
gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, CssProvider,
|
||||
IconTheme,
|
||||
gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, IconTheme,
|
||||
};
|
||||
use gtk::{gio, glib, prelude::ApplicationExt};
|
||||
|
||||
use self::client_object::ClientObject;
|
||||
|
||||
use super::FrontendNotify;
|
||||
use super::FrontendEvent;
|
||||
|
||||
pub fn run() -> glib::ExitCode {
|
||||
log::debug!("running gtk frontend");
|
||||
@@ -51,23 +51,12 @@ fn gtk_main() -> glib::ExitCode {
|
||||
.build();
|
||||
|
||||
app.connect_startup(|_| load_icons());
|
||||
app.connect_startup(|_| load_css());
|
||||
app.connect_activate(build_ui);
|
||||
|
||||
let args: Vec<&'static str> = vec![];
|
||||
app.run_with_args(&args)
|
||||
}
|
||||
|
||||
fn load_css() {
|
||||
let provider = CssProvider::new();
|
||||
provider.load_from_resource("de/feschber/LanMouse/style.css");
|
||||
gtk::style_context_add_provider_for_display(
|
||||
&Display::default().expect("Could not connect to a display."),
|
||||
&provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
}
|
||||
|
||||
fn load_icons() {
|
||||
let display = &Display::default().expect("Could not connect to a display.");
|
||||
let icon_theme = IconTheme::for_display(display);
|
||||
@@ -97,16 +86,14 @@ fn build_ui(app: &Application) {
|
||||
gio::spawn_blocking(move || {
|
||||
match loop {
|
||||
// read length
|
||||
let mut len = [0u8; 8];
|
||||
match rx.read_exact(&mut len) {
|
||||
Ok(_) => (),
|
||||
let len = match rx.read_u64(Endian::Big) {
|
||||
Ok(l) => l,
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
|
||||
Err(e) => break Err(e),
|
||||
};
|
||||
let len = usize::from_be_bytes(len);
|
||||
|
||||
// read payload
|
||||
let mut buf = vec![0u8; len];
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
match rx.read_exact(&mut buf) {
|
||||
Ok(_) => (),
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
|
||||
@@ -125,38 +112,38 @@ fn build_ui(app: &Application) {
|
||||
}
|
||||
});
|
||||
|
||||
let window = Window::new(app);
|
||||
window.imp().stream.borrow_mut().replace(tx);
|
||||
let window = Window::new(app, tx);
|
||||
window.request(FrontendRequest::Enumerate());
|
||||
|
||||
glib::spawn_future_local(clone!(@weak window => async move {
|
||||
loop {
|
||||
let notify = receiver.recv().await.unwrap_or_else(|_| process::exit(1));
|
||||
match notify {
|
||||
FrontendNotify::NotifyClientActivate(handle, active) => {
|
||||
window.activate_client(handle, active);
|
||||
}
|
||||
FrontendNotify::NotifyClientCreate(client) => {
|
||||
window.new_client(client, false);
|
||||
FrontendEvent::Created(handle, client, state) => {
|
||||
window.new_client(handle, client, state);
|
||||
},
|
||||
FrontendNotify::NotifyClientUpdate(client) => {
|
||||
window.update_client(client);
|
||||
}
|
||||
FrontendNotify::NotifyError(e) => {
|
||||
window.show_toast(e.as_str());
|
||||
},
|
||||
FrontendNotify::NotifyClientDelete(client) => {
|
||||
FrontendEvent::Deleted(client) => {
|
||||
window.delete_client(client);
|
||||
}
|
||||
FrontendNotify::Enumerate(clients) => {
|
||||
for (client, active) in clients {
|
||||
if window.client_idx(client.handle).is_some() {
|
||||
window.activate_client(client.handle, active);
|
||||
window.update_client(client);
|
||||
FrontendEvent::State(handle, config, state) => {
|
||||
window.update_client_config(handle, config);
|
||||
window.update_client_state(handle, state);
|
||||
}
|
||||
FrontendEvent::NoSuchClient(_) => { }
|
||||
FrontendEvent::Error(e) => {
|
||||
window.show_toast(e.as_str());
|
||||
},
|
||||
FrontendEvent::Enumerate(clients) => {
|
||||
for (handle, client, state) in clients {
|
||||
if window.client_idx(handle).is_some() {
|
||||
window.update_client_config(handle, client);
|
||||
window.update_client_state(handle, state);
|
||||
} else {
|
||||
window.new_client(client, active);
|
||||
window.new_client(handle, client, state);
|
||||
}
|
||||
}
|
||||
},
|
||||
FrontendNotify::NotifyPortChange(port, msg) => {
|
||||
FrontendEvent::PortChanged(port, msg) => {
|
||||
match msg {
|
||||
None => window.show_toast(format!("port changed: {port}").as_str()),
|
||||
Some(msg) => window.show_toast(msg.as_str()),
|
||||
|
||||
@@ -3,20 +3,29 @@ mod imp;
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::glib::{self, Object};
|
||||
|
||||
use crate::client::{Client, ClientHandle};
|
||||
use crate::client::{ClientConfig, ClientHandle, ClientState};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ClientObject(ObjectSubclass<imp::ClientObject>);
|
||||
}
|
||||
|
||||
impl ClientObject {
|
||||
pub fn new(client: Client, active: bool) -> Self {
|
||||
pub fn new(handle: ClientHandle, client: ClientConfig, state: ClientState) -> Self {
|
||||
Object::builder()
|
||||
.property("handle", client.handle)
|
||||
.property("handle", handle)
|
||||
.property("hostname", client.hostname)
|
||||
.property("port", client.port as u32)
|
||||
.property("position", client.pos.to_string())
|
||||
.property("active", active)
|
||||
.property("active", state.active)
|
||||
.property(
|
||||
"ips",
|
||||
state
|
||||
.ips
|
||||
.iter()
|
||||
.map(|ip| ip.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.property("resolving", state.resolving)
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -32,4 +41,6 @@ pub struct ClientData {
|
||||
pub port: u32,
|
||||
pub active: bool,
|
||||
pub position: String,
|
||||
pub resolving: bool,
|
||||
pub ips: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ pub struct ClientObject {
|
||||
#[property(name = "port", get, set, type = u32, member = port, maximum = u16::MAX as u32)]
|
||||
#[property(name = "active", get, set, type = bool, member = active)]
|
||||
#[property(name = "position", get, set, type = String, member = position)]
|
||||
#[property(name = "resolving", get, set, type = bool, member = resolving)]
|
||||
#[property(name = "ips", get, set, type = Vec<String>, member = ips)]
|
||||
pub data: RefCell<ClientData>,
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,27 @@ impl ClientRow {
|
||||
.sync_create()
|
||||
.build();
|
||||
|
||||
let resolve_binding = client_object
|
||||
.bind_property(
|
||||
"resolving",
|
||||
&self.imp().dns_loading_indicator.get(),
|
||||
"spinning",
|
||||
)
|
||||
.sync_create()
|
||||
.build();
|
||||
|
||||
let ip_binding = client_object
|
||||
.bind_property("ips", &self.imp().dns_button.get(), "tooltip-text")
|
||||
.transform_to(|_, ips: Vec<String>| {
|
||||
if ips.is_empty() {
|
||||
Some("no ip addresses associated with this client".into())
|
||||
} else {
|
||||
Some(ips.join("\n"))
|
||||
}
|
||||
})
|
||||
.sync_create()
|
||||
.build();
|
||||
|
||||
bindings.push(active_binding);
|
||||
bindings.push(switch_position_binding);
|
||||
bindings.push(hostname_binding);
|
||||
@@ -116,6 +137,8 @@ impl ClientRow {
|
||||
bindings.push(port_binding);
|
||||
bindings.push(subtitle_binding);
|
||||
bindings.push(position_binding);
|
||||
bindings.push(resolve_binding);
|
||||
bindings.push(ip_binding);
|
||||
}
|
||||
|
||||
pub fn unbind(&self) {
|
||||
|
||||
@@ -14,6 +14,8 @@ pub struct ClientRow {
|
||||
#[template_child]
|
||||
pub enable_switch: TemplateChild<gtk::Switch>,
|
||||
#[template_child]
|
||||
pub dns_button: TemplateChild<gtk::Button>,
|
||||
#[template_child]
|
||||
pub hostname: TemplateChild<gtk::Entry>,
|
||||
#[template_child]
|
||||
pub port: TemplateChild<gtk::Entry>,
|
||||
@@ -23,6 +25,8 @@ pub struct ClientRow {
|
||||
pub delete_row: TemplateChild<ActionRow>,
|
||||
#[template_child]
|
||||
pub delete_button: TemplateChild<gtk::Button>,
|
||||
#[template_child]
|
||||
pub dns_loading_indicator: TemplateChild<gtk::Spinner>,
|
||||
pub bindings: RefCell<Vec<Binding>>,
|
||||
}
|
||||
|
||||
@@ -58,6 +62,7 @@ impl ObjectImpl for ClientRow {
|
||||
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
|
||||
SIGNALS.get_or_init(|| {
|
||||
vec![
|
||||
Signal::builder("request-dns").build(),
|
||||
Signal::builder("request-update")
|
||||
.param_types([bool::static_type()])
|
||||
.build(),
|
||||
@@ -76,6 +81,11 @@ impl ClientRow {
|
||||
true // dont run default handler
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn handle_request_dns(&self, _: Button) {
|
||||
self.obj().emit_by_name::<()>("request-dns", &[]);
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn handle_client_delete(&self, _button: &Button) {
|
||||
log::debug!("delete button pressed -> requesting delete");
|
||||
|
||||
@@ -2,19 +2,26 @@ mod imp;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::net::TcpStream;
|
||||
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use endi::{Endian, WriteBytes};
|
||||
use glib::{clone, Object};
|
||||
use gtk::{
|
||||
gio,
|
||||
glib::{self, closure_local},
|
||||
NoSelection,
|
||||
ListBox, NoSelection,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
client::{Client, ClientHandle, Position},
|
||||
client::{ClientConfig, ClientHandle, ClientState, Position},
|
||||
config::DEFAULT_PORT,
|
||||
frontend::{gtk::client_object::ClientObject, FrontendEvent},
|
||||
frontend::{gtk::client_object::ClientObject, FrontendRequest},
|
||||
};
|
||||
|
||||
use super::client_row::ClientRow;
|
||||
@@ -27,8 +34,14 @@ glib::wrapper! {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub(crate) fn new(app: &adw::Application) -> Self {
|
||||
Object::builder().property("application", app).build()
|
||||
pub(crate) fn new(
|
||||
app: &adw::Application,
|
||||
#[cfg(unix)] tx: UnixStream,
|
||||
#[cfg(windows)] tx: TcpStream,
|
||||
) -> Self {
|
||||
let window: Self = Object::builder().property("application", app).build();
|
||||
window.imp().stream.borrow_mut().replace(tx);
|
||||
window
|
||||
}
|
||||
|
||||
pub fn clients(&self) -> gio::ListStore {
|
||||
@@ -39,6 +52,10 @@ impl Window {
|
||||
.expect("Could not get clients")
|
||||
}
|
||||
|
||||
fn client_by_idx(&self, idx: u32) -> Option<ClientObject> {
|
||||
self.clients().item(idx).map(|o| o.downcast().unwrap())
|
||||
}
|
||||
|
||||
fn setup_clients(&self) {
|
||||
let model = gio::ListStore::new::<ClientObject>();
|
||||
self.imp().clients.replace(Some(model));
|
||||
@@ -50,16 +67,24 @@ impl Window {
|
||||
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);
|
||||
if let Some(client) = window.client_by_idx(row.index() as u32) {
|
||||
window.request_client_activate(&client, active);
|
||||
window.request_client_update(&client);
|
||||
window.request_client_state(&client);
|
||||
}
|
||||
}));
|
||||
row.connect_closure("request-delete", false, closure_local!(@strong window => move |row: ClientRow| {
|
||||
let index = row.index() as u32;
|
||||
window.request_client_delete(index);
|
||||
if let Some(client) = window.client_by_idx(row.index() as u32) {
|
||||
window.request_client_delete(&client);
|
||||
}
|
||||
}));
|
||||
row.connect_closure("request-dns", false, closure_local!(@strong window => move
|
||||
|row: ClientRow| {
|
||||
if let Some(client) = window.client_by_idx(row.index() as u32) {
|
||||
window.request_client_update(&client);
|
||||
window.request_dns(&client);
|
||||
window.request_client_state(&client);
|
||||
}
|
||||
}));
|
||||
row.upcast()
|
||||
})
|
||||
@@ -87,10 +112,11 @@ impl Window {
|
||||
row
|
||||
}
|
||||
|
||||
pub fn new_client(&self, client: Client, active: bool) {
|
||||
let client = ClientObject::new(client, active);
|
||||
pub fn new_client(&self, handle: ClientHandle, client: ClientConfig, state: ClientState) {
|
||||
let client = ClientObject::new(handle, client, state.clone());
|
||||
self.clients().append(&client);
|
||||
self.set_placeholder_visible(false);
|
||||
self.update_dns_state(handle, !state.ips.is_empty());
|
||||
}
|
||||
|
||||
pub fn client_idx(&self, handle: ClientHandle) -> Option<usize> {
|
||||
@@ -115,9 +141,9 @@ 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);
|
||||
pub fn update_client_config(&self, handle: ClientHandle, client: ClientConfig) {
|
||||
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();
|
||||
@@ -137,75 +163,110 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_client(&self, handle: ClientHandle, active: bool) {
|
||||
pub fn update_client_state(&self, handle: ClientHandle, state: ClientState) {
|
||||
let Some(idx) = self.client_idx(handle) else {
|
||||
log::warn!("could not find client with handle {handle}");
|
||||
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}");
|
||||
|
||||
if state.active != data.active {
|
||||
client_object.set_active(state.active);
|
||||
log::debug!("set active to {}", state.active);
|
||||
}
|
||||
|
||||
if state.resolving != data.resolving {
|
||||
client_object.set_resolving(state.resolving);
|
||||
log::debug!("resolving {}: {}", data.handle, state.resolving);
|
||||
}
|
||||
|
||||
self.update_dns_state(handle, !state.ips.is_empty());
|
||||
let ips = state
|
||||
.ips
|
||||
.into_iter()
|
||||
.map(|ip| ip.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
client_object.set_ips(ips);
|
||||
}
|
||||
|
||||
pub fn request_client_create(&self) {
|
||||
let event = FrontendEvent::AddClient(None, DEFAULT_PORT, Position::default());
|
||||
self.imp().set_port(DEFAULT_PORT);
|
||||
self.request(event);
|
||||
pub fn update_dns_state(&self, handle: ClientHandle, resolved: bool) {
|
||||
let Some(idx) = self.client_idx(handle) else {
|
||||
log::warn!("could not find client with handle {}", handle);
|
||||
return;
|
||||
};
|
||||
let list_box: ListBox = self.imp().client_list.get();
|
||||
let row = list_box.row_at_index(idx as i32).unwrap();
|
||||
let client_row: ClientRow = row.downcast().expect("expected ClientRow Object");
|
||||
if resolved {
|
||||
client_row.imp().dns_button.set_css_classes(&["success"])
|
||||
} else {
|
||||
client_row.imp().dns_button.set_css_classes(&["warning"])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_port_change(&self) {
|
||||
let port = self.imp().port_entry.get().text().to_string();
|
||||
if let Ok(port) = port.as_str().parse::<u16>() {
|
||||
self.request(FrontendEvent::ChangePort(port));
|
||||
self.request(FrontendRequest::ChangePort(port));
|
||||
} else {
|
||||
self.request(FrontendEvent::ChangePort(DEFAULT_PORT));
|
||||
self.request(FrontendRequest::ChangePort(DEFAULT_PORT));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_client_update(&self, client: &ClientObject, active: bool) {
|
||||
let data = client.get_data();
|
||||
let position = match Position::try_from(data.position.as_str()) {
|
||||
Ok(pos) => pos,
|
||||
_ => {
|
||||
log::error!("invalid position: {}", data.position);
|
||||
return;
|
||||
}
|
||||
};
|
||||
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(), active);
|
||||
log::debug!("requesting activate: {event:?}");
|
||||
pub fn request_client_state(&self, client: &ClientObject) {
|
||||
let handle = client.handle();
|
||||
let event = FrontendRequest::GetState(handle);
|
||||
self.request(event);
|
||||
}
|
||||
|
||||
pub fn request_client_delete(&self, idx: u32) {
|
||||
if let Some(obj) = self.clients().item(idx) {
|
||||
let client_object: &ClientObject = obj
|
||||
.downcast_ref()
|
||||
.expect("Expected object of type `ClientObject`.");
|
||||
let handle = client_object.handle();
|
||||
let event = FrontendEvent::DelClient(handle);
|
||||
pub fn request_client_create(&self) {
|
||||
let event = FrontendRequest::Create;
|
||||
self.request(event);
|
||||
}
|
||||
|
||||
pub fn request_dns(&self, client: &ClientObject) {
|
||||
let data = client.get_data();
|
||||
let event = FrontendRequest::ResolveDns(data.handle);
|
||||
self.request(event);
|
||||
}
|
||||
|
||||
pub fn request_client_update(&self, client: &ClientObject) {
|
||||
let handle = client.handle();
|
||||
let data = client.get_data();
|
||||
let position = Position::try_from(data.position.as_str()).expect("invalid position");
|
||||
let hostname = data.hostname;
|
||||
let port = data.port as u16;
|
||||
|
||||
for event in [
|
||||
FrontendRequest::UpdateHostname(handle, hostname),
|
||||
FrontendRequest::UpdatePosition(handle, position),
|
||||
FrontendRequest::UpdatePort(handle, port),
|
||||
] {
|
||||
self.request(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn request(&self, event: FrontendEvent) {
|
||||
pub fn request_client_activate(&self, client: &ClientObject, active: bool) {
|
||||
let handle = client.handle();
|
||||
let event = FrontendRequest::Activate(handle, active);
|
||||
self.request(event);
|
||||
}
|
||||
|
||||
pub fn request_client_delete(&self, client: &ClientObject) {
|
||||
let handle = client.handle();
|
||||
let event = FrontendRequest::Delete(handle);
|
||||
self.request(event);
|
||||
}
|
||||
|
||||
pub fn request(&self, event: FrontendRequest) {
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
log::debug!("requesting {json}");
|
||||
log::debug!("requesting: {json}");
|
||||
let mut stream = self.imp().stream.borrow_mut();
|
||||
let stream = stream.as_mut().unwrap();
|
||||
let bytes = json.as_bytes();
|
||||
let len = bytes.len().to_be_bytes();
|
||||
if let Err(e) = stream.write(&len) {
|
||||
if let Err(e) = stream.write_u64(Endian::Big, bytes.len() as u64) {
|
||||
log::error!("error sending message: {e}");
|
||||
};
|
||||
if let Err(e) = stream.write(bytes) {
|
||||
|
||||
@@ -8,7 +8,8 @@ use std::os::unix::net::UnixStream;
|
||||
use adw::subclass::prelude::*;
|
||||
use adw::{prelude::*, ActionRow, ToastOverlay};
|
||||
use glib::subclass::InitializingObject;
|
||||
use gtk::{gio, glib, Button, CompositeTemplate, Entry, ListBox};
|
||||
use gtk::glib::clone;
|
||||
use gtk::{gdk, gio, glib, Button, CompositeTemplate, Entry, Label, ListBox};
|
||||
|
||||
use crate::config::DEFAULT_PORT;
|
||||
|
||||
@@ -26,6 +27,8 @@ pub struct Window {
|
||||
#[template_child]
|
||||
pub port_entry: TemplateChild<Entry>,
|
||||
#[template_child]
|
||||
pub hostname_label: TemplateChild<Label>,
|
||||
#[template_child]
|
||||
pub toast_overlay: TemplateChild<ToastOverlay>,
|
||||
pub clients: RefCell<Option<gio::ListStore>>,
|
||||
#[cfg(unix)]
|
||||
@@ -61,6 +64,22 @@ impl Window {
|
||||
self.obj().request_client_create();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn handle_copy_hostname(&self, button: &Button) {
|
||||
if let Ok(hostname) = hostname::get() {
|
||||
let display = gdk::Display::default().unwrap();
|
||||
let clipboard = display.clipboard();
|
||||
clipboard.set_text(hostname.to_str().expect("hostname: invalid utf8"));
|
||||
button.set_icon_name("emblem-ok-symbolic");
|
||||
button.set_css_classes(&["success"]);
|
||||
glib::spawn_future_local(clone!(@weak button => async move {
|
||||
glib::timeout_future_seconds(1).await;
|
||||
button.set_icon_name("edit-copy-symbolic");
|
||||
button.set_css_classes(&[]);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn handle_port_changed(&self, _entry: &Entry) {
|
||||
self.port_edit_apply.set_visible(true);
|
||||
@@ -95,6 +114,10 @@ impl Window {
|
||||
|
||||
impl ObjectImpl for Window {
|
||||
fn constructed(&self) {
|
||||
if let Ok(hostname) = hostname::get() {
|
||||
self.hostname_label
|
||||
.set_text(hostname.to_str().expect("hostname: invalid utf8"));
|
||||
}
|
||||
self.parent_constructed();
|
||||
self.set_port(DEFAULT_PORT);
|
||||
let obj = self.obj();
|
||||
|
||||
@@ -7,5 +7,7 @@ pub mod server;
|
||||
pub mod capture;
|
||||
pub mod emulate;
|
||||
|
||||
pub mod capture_test;
|
||||
pub mod emulation_test;
|
||||
pub mod frontend;
|
||||
pub mod scancode;
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use std::process::{self, Child, Command};
|
||||
|
||||
use env_logger::Env;
|
||||
use lan_mouse::{config::Config, frontend, server::Server};
|
||||
use lan_mouse::{capture_test, config::Config, emulation_test, frontend, server::Server};
|
||||
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
@@ -31,7 +31,11 @@ pub fn run() -> Result<()> {
|
||||
log::debug!("{config:?}");
|
||||
log::info!("release bind: {:?}", config.release_bind);
|
||||
|
||||
if config.daemon {
|
||||
if config.test_capture {
|
||||
capture_test::run()?;
|
||||
} else if config.test_emulation {
|
||||
emulation_test::run()?;
|
||||
} else if config.daemon {
|
||||
// if daemon is specified we run the service
|
||||
run_service(&config)?;
|
||||
} else {
|
||||
|
||||
211
src/scancode.rs
211
src/scancode.rs
@@ -1,10 +1,13 @@
|
||||
use num_enum::TryFromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/*
|
||||
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
|
||||
* https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
|
||||
* https://kbd-project.org/docs/scancodes/scancodes-1.html
|
||||
*/
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
pub enum Windows {
|
||||
Shutdown = 0xE05E,
|
||||
SystemSleep = 0xE05F,
|
||||
@@ -119,15 +122,15 @@ pub enum Windows {
|
||||
KeyF21 = 0x006C,
|
||||
KeyF22 = 0x006D,
|
||||
KeyF23 = 0x006E,
|
||||
KeyF24 = 0x0076,
|
||||
KeyF24 = 0x0076, // KeyLANG5
|
||||
KeypadComma = 0x007E,
|
||||
KeyInternational1 = 0x0073,
|
||||
KeyInternational2 = 0x0070,
|
||||
KeyInternational3 = 0x007D,
|
||||
KeyInternational3 = 0x007D, // typo in doc -> its Int'l 3 not Int'l 2
|
||||
#[allow(dead_code)]
|
||||
KeyInternational4 = 0x0079, // FIXME unused
|
||||
KeyInternational4 = 0x0079,
|
||||
#[allow(dead_code)]
|
||||
KeyInternational5 = 0x007B, // FIXME unused
|
||||
KeyInternational5 = 0x007B,
|
||||
// KeyInternational6 = 0x005C,
|
||||
KeyLANG1 = 0x0072,
|
||||
KeyLANG2 = 0x0071,
|
||||
@@ -140,6 +143,7 @@ pub enum Windows {
|
||||
KeyLeftGUI = 0xE05B,
|
||||
KeyRightCtrl = 0xE01D,
|
||||
KeyRightShift = 0x0036,
|
||||
KeyFakeRightShift = 0xE036,
|
||||
KeyRightAlt = 0xE038,
|
||||
KeyRightGUI = 0xE05C,
|
||||
KeyScanNextTrack = 0xE019,
|
||||
@@ -167,7 +171,7 @@ pub enum Windows {
|
||||
* https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
|
||||
*/
|
||||
#[repr(u32)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq, TryFromPrimitive)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Linux {
|
||||
KeyReserved = 0,
|
||||
@@ -292,7 +296,7 @@ pub enum Linux {
|
||||
KeyPause = 119,
|
||||
KeyScale = 120, /* AL Compiz Scale (Expose) */
|
||||
KeyKpcomma = 121,
|
||||
KeyHangeul = 122,
|
||||
KeyHanguel = 122,
|
||||
// KEY_HANGUEL = KeyHangeul,
|
||||
KeyHanja = 123,
|
||||
KeyYen = 124,
|
||||
@@ -428,18 +432,6 @@ pub enum Linux {
|
||||
KeyCount = 249,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for Linux {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
if value >= Self::KeyCount as u32 {
|
||||
return Err(());
|
||||
}
|
||||
let code: Linux = unsafe { std::mem::transmute(value) };
|
||||
Ok(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Linux> for Windows {
|
||||
type Error = ();
|
||||
|
||||
@@ -529,16 +521,16 @@ impl TryFrom<Linux> for Windows {
|
||||
Linux::KeyKp3 => Ok(Self::Keypad3PageDn),
|
||||
Linux::KeyKp0 => Ok(Self::Keypad0Insert),
|
||||
Linux::KeyKpDot => Ok(Self::KeypadDot),
|
||||
Linux::KeyZenkakuhankaku => Ok(Self::KeyLANG1), // TODO unsure
|
||||
Linux::Key102nd => Ok(Self::KeyNonUSSlashBar), // TODO unsure
|
||||
Linux::KeyZenkakuhankaku => Ok(Self::KeyF24), // KeyLANG5
|
||||
Linux::Key102nd => Ok(Self::KeyNonUSSlashBar), // TODO unsure
|
||||
Linux::KeyF11 => Ok(Self::KeyF11),
|
||||
Linux::KeyF12 => Ok(Self::KeyF12),
|
||||
Linux::KeyRo => Ok(Self::ErrorRollOver), // TODO unsure
|
||||
Linux::KeyKatakana => Ok(Self::KeyLANG1), // TODO unsure
|
||||
Linux::KeyHiragana => Ok(Self::KeyLANG2), // TODO unsure
|
||||
Linux::KeyHenkan => Ok(Self::KeyLANG3), // TODO unsure
|
||||
Linux::KeyKatakanahiragana => Ok(Self::KeyLANG4), // TODO unsure
|
||||
Linux::KeyMuhenkan => Ok(Self::KeyLANG4), // TODO unsure
|
||||
Linux::KeyRo => Ok(Self::KeyInternational1),
|
||||
Linux::KeyKatakana => Ok(Self::KeyLANG3),
|
||||
Linux::KeyHiragana => Ok(Self::KeyLANG4),
|
||||
Linux::KeyHenkan => Ok(Self::KeyInternational4),
|
||||
Linux::KeyKatakanahiragana => Ok(Self::KeyInternational2),
|
||||
Linux::KeyMuhenkan => Ok(Self::KeyInternational5),
|
||||
Linux::KeyKpJpComma => Ok(Self::KeypadComma),
|
||||
Linux::KeyKpEnter => Ok(Self::KeypadEnter),
|
||||
Linux::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
||||
@@ -566,9 +558,9 @@ impl TryFrom<Linux> for Windows {
|
||||
Linux::KeyPause => Ok(Self::KeyPause),
|
||||
Linux::KeyScale => Err(()), // TODO
|
||||
Linux::KeyKpcomma => Ok(Self::KeypadComma),
|
||||
Linux::KeyHangeul => Ok(Self::KeyInternational1), // TODO unsure
|
||||
Linux::KeyHanja => Ok(Self::KeyInternational2), // TODO unsure
|
||||
Linux::KeyYen => Ok(Self::KeyInternational3), // TODO unsure
|
||||
Linux::KeyHanguel => Ok(Self::KeyLANG1), // FIXME should be 00F2?
|
||||
Linux::KeyHanja => Ok(Self::KeyLANG2), // FIXME should be 00F1?
|
||||
Linux::KeyYen => Ok(Self::KeyInternational3),
|
||||
Linux::KeyLeftMeta => Ok(Self::KeyLeftGUI),
|
||||
Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
|
||||
Linux::KeyCompose => Ok(Self::KeyApplication),
|
||||
@@ -698,3 +690,162 @@ impl TryFrom<Linux> for Windows {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<Windows> for Linux {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Windows) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Windows::Shutdown => Ok(Self::KeyPower),
|
||||
Windows::SystemSleep => Ok(Self::KeySleep),
|
||||
Windows::SystemWakeUp => Ok(Self::KeyWakeup),
|
||||
Windows::ErrorRollOver => Ok(Self::KeyRo),
|
||||
Windows::KeyA => Ok(Self::KeyA),
|
||||
Windows::KeyB => Ok(Self::KeyB),
|
||||
Windows::KeyC => Ok(Self::KeyC),
|
||||
Windows::KeyD => Ok(Self::KeyD),
|
||||
Windows::KeyE => Ok(Self::KeyE),
|
||||
Windows::KeyF => Ok(Self::KeyF),
|
||||
Windows::KeyG => Ok(Self::KeyG),
|
||||
Windows::KeyH => Ok(Self::KeyH),
|
||||
Windows::KeyI => Ok(Self::KeyI),
|
||||
Windows::KeyJ => Ok(Self::KeyJ),
|
||||
Windows::KeyK => Ok(Self::KeyK),
|
||||
Windows::KeyL => Ok(Self::KeyL),
|
||||
Windows::KeyM => Ok(Self::KeyM),
|
||||
Windows::KeyN => Ok(Self::KeyN),
|
||||
Windows::KeyO => Ok(Self::KeyO),
|
||||
Windows::KeyP => Ok(Self::KeyP),
|
||||
Windows::KeyQ => Ok(Self::KeyQ),
|
||||
Windows::KeyR => Ok(Self::KeyR),
|
||||
Windows::KeyS => Ok(Self::KeyS),
|
||||
Windows::KeyT => Ok(Self::KeyT),
|
||||
Windows::KeyU => Ok(Self::KeyU),
|
||||
Windows::KeyV => Ok(Self::KeyV),
|
||||
Windows::KeyW => Ok(Self::KeyW),
|
||||
Windows::KeyX => Ok(Self::KeyX),
|
||||
Windows::KeyY => Ok(Self::KeyY),
|
||||
Windows::KeyZ => Ok(Self::KeyZ),
|
||||
Windows::Key1 => Ok(Self::Key1),
|
||||
Windows::Key2 => Ok(Self::Key2),
|
||||
Windows::Key3 => Ok(Self::Key3),
|
||||
Windows::Key4 => Ok(Self::Key4),
|
||||
Windows::Key5 => Ok(Self::Key5),
|
||||
Windows::Key6 => Ok(Self::Key6),
|
||||
Windows::Key7 => Ok(Self::Key7),
|
||||
Windows::Key8 => Ok(Self::Key8),
|
||||
Windows::Key9 => Ok(Self::Key9),
|
||||
Windows::Key0 => Ok(Self::Key0),
|
||||
Windows::KeyEnter => Ok(Self::KeyEnter),
|
||||
Windows::KeyEsc => Ok(Self::KeyEsc),
|
||||
Windows::KeyDelete => Ok(Self::KeyBackspace),
|
||||
Windows::KeyTab => Ok(Self::KeyTab),
|
||||
Windows::KeySpace => Ok(Self::KeySpace),
|
||||
Windows::KeyMinus => Ok(Self::KeyMinus),
|
||||
Windows::KeyEqual => Ok(Self::KeyEqual),
|
||||
Windows::KeyLeftBrace => Ok(Self::KeyLeftbrace),
|
||||
Windows::KeyRightBrace => Ok(Self::KeyRightbrace),
|
||||
Windows::KeyBackslash => Ok(Self::KeyBackslash),
|
||||
Windows::KeySemiColon => Ok(Self::KeySemicolon),
|
||||
Windows::KeyApostrophe => Ok(Self::KeyApostrophe),
|
||||
Windows::KeyGrave => Ok(Self::KeyGrave),
|
||||
Windows::KeyComma => Ok(Self::KeyComma),
|
||||
Windows::KeyDot => Ok(Self::KeyDot),
|
||||
Windows::KeySlash => Ok(Self::KeySlash),
|
||||
Windows::KeyCapsLock => Ok(Self::KeyCapsLock),
|
||||
Windows::KeyF1 => Ok(Self::KeyF1),
|
||||
Windows::KeyF2 => Ok(Self::KeyF2),
|
||||
Windows::KeyF3 => Ok(Self::KeyF3),
|
||||
Windows::KeyF4 => Ok(Self::KeyF4),
|
||||
Windows::KeyF5 => Ok(Self::KeyF5),
|
||||
Windows::KeyF6 => Ok(Self::KeyF6),
|
||||
Windows::KeyF7 => Ok(Self::KeyF7),
|
||||
Windows::KeyF8 => Ok(Self::KeyF8),
|
||||
Windows::KeyF9 => Ok(Self::KeyF9),
|
||||
Windows::KeyF10 => Ok(Self::KeyF10),
|
||||
Windows::KeyF11 => Ok(Self::KeyF11),
|
||||
Windows::KeyF12 => Ok(Self::KeyF12),
|
||||
Windows::KeyPrintScreen => Ok(Self::KeySysrq),
|
||||
Windows::KeyScrollLock => Ok(Self::KeyScrollLock),
|
||||
Windows::KeyPause => Ok(Self::KeyPause),
|
||||
Windows::KeyInsert => Ok(Self::KeyInsert),
|
||||
Windows::KeyHome => Ok(Self::KeyHome),
|
||||
Windows::KeyPageUp => Ok(Self::KeyPageup),
|
||||
Windows::KeyDeleteForward => Ok(Self::KeyDelete),
|
||||
Windows::KeyEnd => Ok(Self::KeyEnd),
|
||||
Windows::KeyPageDown => Ok(Self::KeyPagedown),
|
||||
Windows::KeyRight => Ok(Self::KeyRight),
|
||||
Windows::KeyLeft => Ok(Self::KeyLeft),
|
||||
Windows::KeyDown => Ok(Self::KeyDown),
|
||||
Windows::KeyUp => Ok(Self::KeyUp),
|
||||
Windows::KeypadNumLock => Ok(Self::KeyNumlock),
|
||||
Windows::KeypadSlash => Ok(Self::KeyKpslash),
|
||||
Windows::KeypadStar => Ok(Self::KeyKpAsterisk),
|
||||
Windows::KeypadDash => Ok(Self::KeyKpMinus),
|
||||
Windows::KeypadPlus => Ok(Self::KeyKpplus),
|
||||
Windows::KeypadEnter => Ok(Self::KeyKpEnter),
|
||||
Windows::Keypad1End => Ok(Self::KeyKp1),
|
||||
Windows::Keypad2DownArrow => Ok(Self::KeyKp2),
|
||||
Windows::Keypad3PageDn => Ok(Self::KeyKp3),
|
||||
Windows::Keypad4LeftArrow => Ok(Self::KeyKp4),
|
||||
Windows::Keypad5 => Ok(Self::KeyKp5),
|
||||
Windows::Keypad6RightArrow => Ok(Self::KeyKp6),
|
||||
Windows::Keypad7Home => Ok(Self::KeyKp7),
|
||||
Windows::Keypad8UpArrow => Ok(Self::KeyKp8),
|
||||
Windows::Keypad9PageUp => Ok(Self::KeyKp9),
|
||||
Windows::Keypad0Insert => Ok(Self::KeyKp0),
|
||||
Windows::KeypadDot => Ok(Self::KeyKpDot),
|
||||
Windows::KeyNonUSSlashBar => Ok(Self::Key102nd),
|
||||
Windows::KeyApplication => Ok(Self::KeyMenu),
|
||||
Windows::KeypadEquals => Ok(Self::KeyKpequal),
|
||||
Windows::KeyF13 => Ok(Self::KeyF13),
|
||||
Windows::KeyF14 => Ok(Self::KeyF14),
|
||||
Windows::KeyF15 => Ok(Self::KeyF15),
|
||||
Windows::KeyF16 => Ok(Self::KeyF16),
|
||||
Windows::KeyF17 => Ok(Self::KeyF17),
|
||||
Windows::KeyF18 => Ok(Self::KeyF18),
|
||||
Windows::KeyF19 => Ok(Self::KeyF19),
|
||||
Windows::KeyF20 => Ok(Self::KeyF20),
|
||||
Windows::KeyF21 => Ok(Self::KeyF21),
|
||||
Windows::KeyF22 => Ok(Self::KeyF22),
|
||||
Windows::KeyF23 => Ok(Self::KeyF23),
|
||||
Windows::KeyF24 => Ok(Self::KeyF24),
|
||||
Windows::KeypadComma => Ok(Self::KeyKpcomma),
|
||||
Windows::KeyInternational1 => Ok(Self::KeyRo),
|
||||
Windows::KeyInternational2 => Ok(Self::KeyKatakanahiragana),
|
||||
Windows::KeyInternational3 => Ok(Self::KeyYen),
|
||||
Windows::KeyInternational4 => Ok(Self::KeyHenkan),
|
||||
Windows::KeyInternational5 => Ok(Self::KeyMuhenkan),
|
||||
Windows::KeyLANG1 => Ok(Self::KeyHanguel),
|
||||
Windows::KeyLANG2 => Ok(Self::KeyHanja),
|
||||
Windows::KeyLANG3 => Ok(Self::KeyKatakana),
|
||||
Windows::KeyLANG4 => Ok(Self::KeyHiragana),
|
||||
Windows::KeyLeftCtrl => Ok(Self::KeyLeftCtrl),
|
||||
Windows::KeyLeftShift => Ok(Self::KeyLeftShift),
|
||||
Windows::KeyLeftAlt => Ok(Self::KeyLeftAlt),
|
||||
Windows::KeyLeftGUI => Ok(Self::KeyLeftMeta),
|
||||
Windows::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
||||
Windows::KeyRightShift => Ok(Self::KeyRightShift),
|
||||
Windows::KeyFakeRightShift => Ok(Self::KeyRightShift),
|
||||
Windows::KeyRightAlt => Ok(Self::KeyRightalt),
|
||||
Windows::KeyRightGUI => Ok(Self::KeyRightmeta),
|
||||
Windows::KeyScanNextTrack => Ok(Self::KeyNextsong),
|
||||
Windows::KeyScanPreviousTrack => Ok(Self::KeyPrevioussong),
|
||||
Windows::KeyStop => Ok(Self::KeyStopcd),
|
||||
Windows::KeyPlayPause => Ok(Self::KeyPlaypause),
|
||||
Windows::KeyMute => Ok(Self::KeyMute),
|
||||
Windows::KeyVolumeUp => Ok(Self::KeyVolumeUp),
|
||||
Windows::KeyVolumeDown => Ok(Self::KeyVolumeDown),
|
||||
Windows::ALConsumerControlConfiguration => Err(()),
|
||||
Windows::ALEmailReader => Ok(Self::KeyMail),
|
||||
Windows::ALCalculator => Ok(Self::KeyCalc),
|
||||
Windows::ALLocalMachineBrowser => Ok(Self::KeyFile),
|
||||
Windows::ACSearch => Ok(Self::KeyWww),
|
||||
Windows::ACHome => Ok(Self::KeyHomepage),
|
||||
Windows::ACBack => Ok(Self::KeyBack),
|
||||
Windows::ACForward => Ok(Self::KeyForward),
|
||||
Windows::ACStop => Ok(Self::KeyStop),
|
||||
Windows::ACRefresh => Ok(Self::KeyRefresh),
|
||||
Windows::ACBookmarks => Ok(Self::KeyBookmarks),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use log;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashSet,
|
||||
rc::Rc,
|
||||
};
|
||||
use tokio::signal;
|
||||
|
||||
use crate::{capture, emulate};
|
||||
use crate::{
|
||||
client::{ClientHandle, ClientManager},
|
||||
client::{ClientConfig, ClientHandle, ClientManager, ClientState},
|
||||
config::Config,
|
||||
dns,
|
||||
frontend::{FrontendEvent, FrontendListener},
|
||||
frontend::{FrontendListener, FrontendRequest},
|
||||
server::capture_task::CaptureEvent,
|
||||
};
|
||||
|
||||
@@ -50,13 +50,22 @@ impl Server {
|
||||
let state = Rc::new(Cell::new(State::Receiving));
|
||||
let port = Rc::new(Cell::new(config.port));
|
||||
for config_client in config.get_clients() {
|
||||
client_manager.borrow_mut().add_client(
|
||||
config_client.hostname,
|
||||
config_client.ips,
|
||||
config_client.port,
|
||||
config_client.pos,
|
||||
config_client.active,
|
||||
);
|
||||
let client = ClientConfig {
|
||||
hostname: config_client.hostname,
|
||||
fix_ips: config_client.ips.into_iter().collect(),
|
||||
port: config_client.port,
|
||||
pos: config_client.pos,
|
||||
cmd: config_client.enter_hook,
|
||||
};
|
||||
let state = ClientState {
|
||||
active: config_client.active,
|
||||
ips: HashSet::from_iter(client.fix_ips.iter().cloned()),
|
||||
..Default::default()
|
||||
};
|
||||
let mut client_manager = client_manager.borrow_mut();
|
||||
let handle = client_manager.add_client();
|
||||
let c = client_manager.get_mut(handle).expect("invalid handle");
|
||||
*c = (client, state);
|
||||
}
|
||||
let release_bind = config.release_bind.clone();
|
||||
Self {
|
||||
@@ -78,18 +87,16 @@ impl Server {
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
};
|
||||
let (emulate, capture) = tokio::join!(emulate::create(), capture::create());
|
||||
|
||||
let (timer_tx, timer_rx) = tokio::sync::mpsc::channel(1);
|
||||
let (frontend_notify_tx, frontend_notify_rx) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
// udp task
|
||||
let (mut udp_task, sender_tx, receiver_rx, port_tx) =
|
||||
network_task::new(self.clone(), frontend_notify_tx).await?;
|
||||
network_task::new(self.clone(), frontend_notify_tx.clone()).await?;
|
||||
|
||||
// input capture
|
||||
let (mut capture_task, capture_channel) = capture_task::new(
|
||||
capture,
|
||||
self.clone(),
|
||||
sender_tx.clone(),
|
||||
timer_tx.clone(),
|
||||
@@ -98,7 +105,6 @@ impl Server {
|
||||
|
||||
// input emulation
|
||||
let (mut emulation_task, emulate_channel) = emulation_task::new(
|
||||
emulate,
|
||||
self.clone(),
|
||||
receiver_rx,
|
||||
sender_tx.clone(),
|
||||
@@ -108,7 +114,8 @@ impl Server {
|
||||
|
||||
// create dns resolver
|
||||
let resolver = dns::DnsResolver::new().await?;
|
||||
let (mut resolver_task, resolve_tx) = resolver_task::new(resolver, self.clone());
|
||||
let (mut resolver_task, resolve_tx) =
|
||||
resolver_task::new(resolver, self.clone(), frontend_notify_tx);
|
||||
|
||||
// frontend listener
|
||||
let (mut frontend_task, frontend_tx) = frontend_task::new(
|
||||
@@ -134,9 +141,9 @@ impl Server {
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get_client_states()
|
||||
.filter_map(|s| {
|
||||
.filter_map(|(h, (c, s))| {
|
||||
if s.active {
|
||||
Some((s.client.handle, s.client.hostname.clone()))
|
||||
Some((h, c.hostname.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -144,7 +151,7 @@ impl Server {
|
||||
.collect::<Vec<_>>();
|
||||
for (handle, hostname) in active {
|
||||
frontend_tx
|
||||
.send(FrontendEvent::ActivateClient(handle, true))
|
||||
.send(FrontendRequest::Activate(handle, true))
|
||||
.await?;
|
||||
if let Some(hostname) = hostname {
|
||||
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
||||
@@ -178,7 +185,7 @@ impl Server {
|
||||
|
||||
let _ = emulate_channel.send(EmulationEvent::Terminate).await;
|
||||
let _ = capture_channel.send(CaptureEvent::Terminate).await;
|
||||
let _ = frontend_tx.send(FrontendEvent::Shutdown()).await;
|
||||
let _ = frontend_tx.send(FrontendRequest::Terminate()).await;
|
||||
|
||||
if !capture_task.is_finished() {
|
||||
if let Err(e) = capture_task.await {
|
||||
|
||||
@@ -2,10 +2,10 @@ use anyhow::{anyhow, Result};
|
||||
use futures::StreamExt;
|
||||
use std::{collections::HashSet, net::SocketAddr};
|
||||
|
||||
use tokio::{sync::mpsc::Sender, task::JoinHandle};
|
||||
use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle};
|
||||
|
||||
use crate::{
|
||||
capture::InputCapture,
|
||||
capture::{self, InputCapture},
|
||||
client::{ClientEvent, ClientHandle},
|
||||
event::{Event, KeyboardEvent},
|
||||
scancode,
|
||||
@@ -25,7 +25,6 @@ pub enum CaptureEvent {
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
mut capture: Box<dyn InputCapture>,
|
||||
server: Server,
|
||||
sender_tx: Sender<(Event, SocketAddr)>,
|
||||
timer_tx: Sender<()>,
|
||||
@@ -33,6 +32,7 @@ pub fn new(
|
||||
) -> (JoinHandle<Result<()>>, Sender<CaptureEvent>) {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||
let task = tokio::task::spawn_local(async move {
|
||||
let mut capture = capture::create().await;
|
||||
let mut pressed_keys = HashSet::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -84,8 +84,8 @@ async fn handle_capture_event(
|
||||
pressed_keys: &mut HashSet<scancode::Linux>,
|
||||
release_bind: &[scancode::Linux],
|
||||
) -> Result<()> {
|
||||
let (c, mut e) = event;
|
||||
log::trace!("({c}) {e:?}");
|
||||
let (handle, mut e) = event;
|
||||
log::trace!("({handle}) {e:?}");
|
||||
|
||||
if let Event::Keyboard(KeyboardEvent::Key { key, state, .. }) = e {
|
||||
update_pressed_keys(pressed_keys, key, state);
|
||||
@@ -107,8 +107,8 @@ async fn handle_capture_event(
|
||||
|
||||
// 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,
|
||||
let client_state = match client_manager.get_mut(handle) {
|
||||
Some((_, s)) => s,
|
||||
None => {
|
||||
// should not happen
|
||||
log::warn!("unknown client!");
|
||||
@@ -123,10 +123,8 @@ async fn handle_capture_event(
|
||||
// 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);
|
||||
server.active_client.replace(Some(handle));
|
||||
log::trace!("Active client => {}", handle);
|
||||
start_timer = true;
|
||||
log::trace!("STATE ===> AwaitingLeave");
|
||||
enter = true;
|
||||
@@ -142,6 +140,9 @@ async fn handle_capture_event(
|
||||
if start_timer {
|
||||
let _ = timer_tx.try_send(());
|
||||
}
|
||||
if enter {
|
||||
spawn_hook_command(server, handle);
|
||||
}
|
||||
if let Some(addr) = addr {
|
||||
if enter {
|
||||
let _ = sender_tx.send((Event::Enter(), addr)).await;
|
||||
@@ -150,3 +151,34 @@ async fn handle_capture_event(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_hook_command(server: &Server, handle: ClientHandle) {
|
||||
let Some(cmd) = server
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get(handle)
|
||||
.and_then(|(c, _)| c.cmd.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
tokio::task::spawn_local(async move {
|
||||
log::info!("spawning command!");
|
||||
let mut child = match Command::new("sh").arg("-c").arg(cmd.as_str()).spawn() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("could not execute cmd: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match child.wait().await {
|
||||
Ok(s) => {
|
||||
if s.success() {
|
||||
log::info!("{cmd} exited successfully");
|
||||
} else {
|
||||
log::warn!("{cmd} exited with {s}");
|
||||
}
|
||||
}
|
||||
Err(e) => log::warn!("{cmd}: {e}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use tokio::{
|
||||
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle},
|
||||
emulate::InputEmulation,
|
||||
emulate::{self, InputEmulation},
|
||||
event::{Event, KeyboardEvent},
|
||||
scancode,
|
||||
server::State,
|
||||
@@ -27,7 +27,6 @@ pub enum EmulationEvent {
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
mut emulate: Box<dyn InputEmulation>,
|
||||
server: Server,
|
||||
mut udp_rx: Receiver<Result<(Event, SocketAddr)>>,
|
||||
sender_tx: Sender<(Event, SocketAddr)>,
|
||||
@@ -36,6 +35,7 @@ pub fn new(
|
||||
) -> (JoinHandle<Result<()>>, Sender<EmulationEvent>) {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||
let emulate_task = tokio::task::spawn_local(async move {
|
||||
let mut emulate = emulate::create().await;
|
||||
let mut last_ignored = None;
|
||||
|
||||
loop {
|
||||
@@ -65,7 +65,7 @@ pub fn new(
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get_client_states()
|
||||
.map(|s| s.client.handle)
|
||||
.map(|(h, _)| h)
|
||||
.collect::<Vec<_>>();
|
||||
for client in clients {
|
||||
release_keys(&server, &mut emulate, client).await;
|
||||
@@ -108,7 +108,7 @@ async fn handle_udp_rx(
|
||||
{
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let client_state = match client_manager.get_mut(handle) {
|
||||
Some(s) => s,
|
||||
Some((_, s)) => s,
|
||||
None => {
|
||||
log::error!("unknown handle");
|
||||
return;
|
||||
@@ -156,13 +156,12 @@ async fn handle_udp_rx(
|
||||
}) = 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;
|
||||
};
|
||||
let client_state = if let Some((_, s)) = client_manager.get_mut(handle) {
|
||||
s
|
||||
} else {
|
||||
log::error!("unknown handle");
|
||||
return;
|
||||
};
|
||||
if state == 0 {
|
||||
// ignore release event if key not pressed
|
||||
ignore_event = !client_state.pressed_keys.remove(&key);
|
||||
@@ -177,7 +176,7 @@ async fn handle_udp_rx(
|
||||
if !ignore_event {
|
||||
// consume event
|
||||
emulate.consume(event, handle).await;
|
||||
log::trace!("{event:?} => emulate");
|
||||
log::trace!("{event} => emulate");
|
||||
}
|
||||
}
|
||||
State::AwaitingLeave => {
|
||||
@@ -213,7 +212,7 @@ async fn release_keys(
|
||||
.borrow_mut()
|
||||
.get_mut(client)
|
||||
.iter_mut()
|
||||
.flat_map(|s| s.pressed_keys.drain())
|
||||
.flat_map(|(_, s)| s.pressed_keys.drain())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for key in keys {
|
||||
|
||||
@@ -18,7 +18,7 @@ use tokio::{
|
||||
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle, Position},
|
||||
frontend::{self, FrontendEvent, FrontendListener, FrontendNotify},
|
||||
frontend::{self, FrontendEvent, FrontendListener, FrontendRequest},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -27,13 +27,13 @@ use super::{
|
||||
|
||||
pub(crate) fn new(
|
||||
mut frontend: FrontendListener,
|
||||
mut notify_rx: Receiver<FrontendNotify>,
|
||||
mut notify_rx: Receiver<FrontendEvent>,
|
||||
server: Server,
|
||||
capture_notify: Sender<CaptureEvent>,
|
||||
emulate_notify: Sender<EmulationEvent>,
|
||||
capture: Sender<CaptureEvent>,
|
||||
emulate: Sender<EmulationEvent>,
|
||||
resolve_ch: Sender<DnsRequest>,
|
||||
port_tx: Sender<u16>,
|
||||
) -> (JoinHandle<Result<()>>, Sender<FrontendEvent>) {
|
||||
) -> (JoinHandle<Result<()>>, Sender<FrontendRequest>) {
|
||||
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 {
|
||||
@@ -51,13 +51,13 @@ pub(crate) fn new(
|
||||
}
|
||||
event = event_rx.recv() => {
|
||||
let frontend_event = event.ok_or(anyhow!("frontend channel closed"))?;
|
||||
if handle_frontend_event(&server, &capture_notify, &emulate_notify, &resolve_ch, &mut frontend, &port_tx, frontend_event).await {
|
||||
if handle_frontend_event(&server, &capture, &emulate, &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;
|
||||
let _ = frontend.broadcast_event(notify).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ pub(crate) fn new(
|
||||
}
|
||||
|
||||
async fn handle_frontend_stream(
|
||||
frontend_tx: &Sender<FrontendEvent>,
|
||||
frontend_tx: &Sender<FrontendRequest>,
|
||||
#[cfg(unix)] mut stream: ReadHalf<UnixStream>,
|
||||
#[cfg(windows)] mut stream: ReadHalf<TcpStream>,
|
||||
) {
|
||||
@@ -75,12 +75,11 @@ async fn handle_frontend_stream(
|
||||
|
||||
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;
|
||||
let request = frontend::wait_for_request(&mut stream).await;
|
||||
match request {
|
||||
Ok(request) => {
|
||||
let _ = tx.send(request).await;
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(e) = e.downcast_ref::<io::Error>() {
|
||||
@@ -98,245 +97,259 @@ async fn handle_frontend_stream(
|
||||
|
||||
async fn handle_frontend_event(
|
||||
server: &Server,
|
||||
capture_tx: &Sender<CaptureEvent>,
|
||||
emulate_tx: &Sender<EmulationEvent>,
|
||||
capture: &Sender<CaptureEvent>,
|
||||
emulate: &Sender<EmulationEvent>,
|
||||
resolve_tx: &Sender<DnsRequest>,
|
||||
frontend: &mut FrontendListener,
|
||||
port_tx: &Sender<u16>,
|
||||
event: FrontendEvent,
|
||||
event: FrontendRequest,
|
||||
) -> 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))
|
||||
match event {
|
||||
FrontendRequest::Create => {
|
||||
let handle = add_client(server, frontend).await;
|
||||
resolve_dns(server, resolve_tx, handle).await;
|
||||
}
|
||||
FrontendEvent::ActivateClient(handle, active) => {
|
||||
activate_client(server, capture_tx, emulate_tx, handle, active).await;
|
||||
Some(FrontendNotify::NotifyClientActivate(handle, active))
|
||||
FrontendRequest::Activate(handle, active) => {
|
||||
if active {
|
||||
activate_client(server, capture, emulate, handle).await;
|
||||
} else {
|
||||
deactivate_client(server, capture, emulate, handle).await;
|
||||
}
|
||||
}
|
||||
FrontendEvent::ChangePort(port) => {
|
||||
FrontendRequest::ChangePort(port) => {
|
||||
let _ = port_tx.send(port).await;
|
||||
None
|
||||
}
|
||||
FrontendEvent::DelClient(handle) => {
|
||||
remove_client(server, capture_tx, emulate_tx, frontend, handle).await;
|
||||
Some(FrontendNotify::NotifyClientDelete(handle))
|
||||
FrontendRequest::Delete(handle) => {
|
||||
remove_client(server, capture, emulate, handle).await;
|
||||
broadcast(frontend, FrontendEvent::Deleted(handle)).await;
|
||||
}
|
||||
FrontendEvent::Enumerate() => {
|
||||
FrontendRequest::Enumerate() => {
|
||||
let clients = server
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get_client_states()
|
||||
.map(|s| (s.client.clone(), s.active))
|
||||
.map(|(h, (c, s))| (h, c.clone(), s.clone()))
|
||||
.collect();
|
||||
Some(FrontendNotify::Enumerate(clients))
|
||||
broadcast(frontend, FrontendEvent::Enumerate(clients)).await;
|
||||
}
|
||||
FrontendEvent::Shutdown() => {
|
||||
FrontendRequest::GetState(handle) => {
|
||||
broadcast_client(server, frontend, handle).await;
|
||||
}
|
||||
FrontendRequest::Terminate() => {
|
||||
log::info!("terminating gracefully...");
|
||||
return true;
|
||||
}
|
||||
FrontendEvent::UpdateClient(handle, hostname, port, pos) => {
|
||||
update_client(
|
||||
server,
|
||||
capture_tx,
|
||||
emulate_tx,
|
||||
resolve_tx,
|
||||
(handle, hostname, port, pos),
|
||||
)
|
||||
.await;
|
||||
|
||||
let client = server
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get(handle)
|
||||
.unwrap()
|
||||
.client
|
||||
.clone();
|
||||
Some(FrontendNotify::NotifyClientUpdate(client))
|
||||
FrontendRequest::UpdateFixIps(handle, fix_ips) => {
|
||||
update_fix_ips(server, handle, fix_ips).await;
|
||||
resolve_dns(server, resolve_tx, handle).await;
|
||||
}
|
||||
FrontendRequest::UpdateHostname(handle, hostname) => {
|
||||
update_hostname(server, resolve_tx, handle, hostname).await;
|
||||
resolve_dns(server, resolve_tx, handle).await;
|
||||
}
|
||||
FrontendRequest::UpdatePort(handle, port) => {
|
||||
update_port(server, handle, port).await;
|
||||
}
|
||||
FrontendRequest::UpdatePosition(handle, pos) => {
|
||||
update_pos(server, handle, capture, emulate, pos).await;
|
||||
}
|
||||
FrontendRequest::ResolveDns(handle) => {
|
||||
resolve_dns(server, resolve_tx, handle).await;
|
||||
}
|
||||
};
|
||||
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}");
|
||||
|
||||
async fn resolve_dns(server: &Server, resolve_tx: &Sender<DnsRequest>, handle: ClientHandle) {
|
||||
let hostname = server
|
||||
.client_manager
|
||||
.borrow()
|
||||
.get(handle)
|
||||
.and_then(|(c, _)| c.hostname.clone());
|
||||
if let Some(hostname) = hostname {
|
||||
let _ = resolver_tx.send(DnsRequest { hostname, handle }).await;
|
||||
let _ = resolve_tx
|
||||
.send(DnsRequest {
|
||||
hostname: hostname.clone(),
|
||||
handle,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn broadcast(frontend: &mut FrontendListener, event: FrontendEvent) {
|
||||
if let Err(e) = frontend.broadcast_event(event).await {
|
||||
log::error!("error notifying frontend: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_client(server: &Server, frontend: &mut FrontendListener) -> ClientHandle {
|
||||
let handle = server.client_manager.borrow_mut().add_client();
|
||||
log::info!("added client {handle}");
|
||||
|
||||
let (c, s) = server.client_manager.borrow().get(handle).unwrap().clone();
|
||||
broadcast(frontend, FrontendEvent::Created(handle, c, s)).await;
|
||||
handle
|
||||
}
|
||||
|
||||
pub async fn deactivate_client(
|
||||
server: &Server,
|
||||
capture: &Sender<CaptureEvent>,
|
||||
emulate: &Sender<EmulationEvent>,
|
||||
handle: ClientHandle,
|
||||
) {
|
||||
match server.client_manager.borrow_mut().get_mut(handle) {
|
||||
Some((_, s)) => {
|
||||
s.active = false;
|
||||
}
|
||||
None => return,
|
||||
};
|
||||
|
||||
let event = ClientEvent::Destroy(handle);
|
||||
let _ = capture.send(CaptureEvent::ClientEvent(event)).await;
|
||||
let _ = emulate.send(EmulationEvent::ClientEvent(event)).await;
|
||||
}
|
||||
|
||||
pub async fn activate_client(
|
||||
server: &Server,
|
||||
capture_notify_tx: &Sender<CaptureEvent>,
|
||||
emulate_notify_tx: &Sender<EmulationEvent>,
|
||||
client: ClientHandle,
|
||||
active: bool,
|
||||
capture: &Sender<CaptureEvent>,
|
||||
emulate: &Sender<EmulationEvent>,
|
||||
handle: ClientHandle,
|
||||
) {
|
||||
let (client, pos) = match server.client_manager.borrow_mut().get_mut(client) {
|
||||
Some(state) => {
|
||||
state.active = active;
|
||||
(state.client.handle, state.client.pos)
|
||||
}
|
||||
/* deactivate potential other client at this position */
|
||||
let pos = match server.client_manager.borrow().get(handle) {
|
||||
Some((client, _)) => client.pos,
|
||||
None => return,
|
||||
};
|
||||
if active {
|
||||
let _ = capture_notify_tx
|
||||
.send(CaptureEvent::ClientEvent(ClientEvent::Create(client, pos)))
|
||||
.await;
|
||||
let _ = emulate_notify_tx
|
||||
.send(EmulationEvent::ClientEvent(ClientEvent::Create(
|
||||
client, pos,
|
||||
)))
|
||||
.await;
|
||||
} else {
|
||||
let _ = capture_notify_tx
|
||||
.send(CaptureEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||
.await;
|
||||
let _ = emulate_notify_tx
|
||||
.send(EmulationEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||
.await;
|
||||
|
||||
let other = server.client_manager.borrow_mut().find_client(pos);
|
||||
if let Some(other) = other {
|
||||
if other != handle {
|
||||
deactivate_client(server, capture, emulate, other).await;
|
||||
}
|
||||
}
|
||||
|
||||
/* activate the client */
|
||||
if let Some((_, s)) = server.client_manager.borrow_mut().get_mut(handle) {
|
||||
s.active = true;
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
/* notify emulation, capture and frontends */
|
||||
let event = ClientEvent::Create(handle, pos);
|
||||
let _ = capture.send(CaptureEvent::ClientEvent(event)).await;
|
||||
let _ = emulate.send(EmulationEvent::ClientEvent(event)).await;
|
||||
}
|
||||
|
||||
pub async fn remove_client(
|
||||
server: &Server,
|
||||
capture_notify_tx: &Sender<CaptureEvent>,
|
||||
emulate_notify_tx: &Sender<EmulationEvent>,
|
||||
frontend: &mut FrontendListener,
|
||||
client: ClientHandle,
|
||||
) -> Option<ClientHandle> {
|
||||
let Some((client, active)) = server
|
||||
capture: &Sender<CaptureEvent>,
|
||||
emulate: &Sender<EmulationEvent>,
|
||||
handle: ClientHandle,
|
||||
) {
|
||||
let Some(active) = server
|
||||
.client_manager
|
||||
.borrow_mut()
|
||||
.remove_client(client)
|
||||
.map(|s| (s.client.handle, s.active))
|
||||
.remove_client(handle)
|
||||
.map(|(_, s)| s.active)
|
||||
else {
|
||||
return None;
|
||||
return;
|
||||
};
|
||||
|
||||
if active {
|
||||
let _ = capture_notify_tx
|
||||
.send(CaptureEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||
.await;
|
||||
let _ = emulate_notify_tx
|
||||
.send(EmulationEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||
.await;
|
||||
let destroy = ClientEvent::Destroy(handle);
|
||||
let _ = capture.send(CaptureEvent::ClientEvent(destroy)).await;
|
||||
let _ = emulate.send(EmulationEvent::ClientEvent(destroy)).await;
|
||||
}
|
||||
|
||||
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(
|
||||
async fn update_fix_ips(server: &Server, handle: ClientHandle, fix_ips: Vec<IpAddr>) {
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let Some((c, _)) = client_manager.get_mut(handle) else {
|
||||
return;
|
||||
};
|
||||
|
||||
c.fix_ips = fix_ips;
|
||||
}
|
||||
|
||||
async fn update_hostname(
|
||||
server: &Server,
|
||||
capture_notify_tx: &Sender<CaptureEvent>,
|
||||
emulate_notify_tx: &Sender<EmulationEvent>,
|
||||
resolve_tx: &Sender<DnsRequest>,
|
||||
client_update: (ClientHandle, Option<String>, u16, Position),
|
||||
handle: ClientHandle,
|
||||
hostname: Option<String>,
|
||||
) {
|
||||
let (handle, hostname, port, pos) = client_update;
|
||||
let mut changed = false;
|
||||
let (hostname, handle, active) = {
|
||||
// retrieve state
|
||||
let hostname = {
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let Some(state) = client_manager.get_mut(handle) else {
|
||||
let Some((c, s)) = client_manager.get_mut(handle) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// update pos
|
||||
if state.client.pos != pos {
|
||||
state.client.pos = pos;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// update port
|
||||
if state.client.port != port {
|
||||
state.client.port = port;
|
||||
state.active_addr = state.active_addr.map(|a| SocketAddr::new(a.ip(), port));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// update hostname
|
||||
if state.client.hostname != hostname {
|
||||
state.client.ips = HashSet::new();
|
||||
state.active_addr = None;
|
||||
state.client.hostname = hostname;
|
||||
changed = true;
|
||||
if c.hostname != hostname {
|
||||
c.hostname = hostname;
|
||||
s.ips = HashSet::from_iter(c.fix_ips.iter().cloned());
|
||||
s.active_addr = None;
|
||||
c.hostname.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
log::debug!("client updated: {:?}", state);
|
||||
(
|
||||
state.client.hostname.clone(),
|
||||
state.client.handle,
|
||||
state.active,
|
||||
)
|
||||
};
|
||||
|
||||
// resolve dns if something changed
|
||||
if changed {
|
||||
// resolve dns
|
||||
if let Some(hostname) = hostname {
|
||||
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
||||
}
|
||||
}
|
||||
|
||||
// update state in event input emulator & input capture
|
||||
if changed && active {
|
||||
// update state
|
||||
let _ = capture_notify_tx
|
||||
.send(CaptureEvent::ClientEvent(ClientEvent::Destroy(handle)))
|
||||
.await;
|
||||
let _ = emulate_notify_tx
|
||||
.send(EmulationEvent::ClientEvent(ClientEvent::Destroy(handle)))
|
||||
.await;
|
||||
let _ = capture_notify_tx
|
||||
.send(CaptureEvent::ClientEvent(ClientEvent::Create(handle, pos)))
|
||||
.await;
|
||||
let _ = emulate_notify_tx
|
||||
.send(EmulationEvent::ClientEvent(ClientEvent::Create(
|
||||
handle, pos,
|
||||
)))
|
||||
.await;
|
||||
// resolve to update ips in state
|
||||
if let Some(hostname) = hostname {
|
||||
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_port(server: &Server, handle: ClientHandle, port: u16) {
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let Some((c, s)) = client_manager.get_mut(handle) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if c.port != port {
|
||||
c.port = port;
|
||||
s.active_addr = s.active_addr.map(|a| SocketAddr::new(a.ip(), port));
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_pos(
|
||||
server: &Server,
|
||||
handle: ClientHandle,
|
||||
capture: &Sender<CaptureEvent>,
|
||||
emulate: &Sender<EmulationEvent>,
|
||||
pos: Position,
|
||||
) {
|
||||
let (changed, active) = {
|
||||
let mut client_manager = server.client_manager.borrow_mut();
|
||||
let Some((c, s)) = client_manager.get_mut(handle) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let changed = c.pos != pos;
|
||||
c.pos = pos;
|
||||
(changed, s.active)
|
||||
};
|
||||
|
||||
// update state in event input emulator & input capture
|
||||
if changed {
|
||||
if active {
|
||||
let destroy = ClientEvent::Destroy(handle);
|
||||
let _ = capture.send(CaptureEvent::ClientEvent(destroy)).await;
|
||||
let _ = emulate.send(EmulationEvent::ClientEvent(destroy)).await;
|
||||
}
|
||||
let create = ClientEvent::Create(handle, pos);
|
||||
let _ = capture.send(CaptureEvent::ClientEvent(create)).await;
|
||||
let _ = emulate.send(EmulationEvent::ClientEvent(create)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn broadcast_client(server: &Server, frontend: &mut FrontendListener, handle: ClientHandle) {
|
||||
let client = server.client_manager.borrow().get(handle).cloned();
|
||||
if let Some((config, state)) = client {
|
||||
broadcast(frontend, FrontendEvent::State(handle, config, state)).await;
|
||||
} else {
|
||||
broadcast(frontend, FrontendEvent::NoSuchClient(handle)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ use tokio::{
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{event::Event, frontend::FrontendNotify};
|
||||
use crate::{event::Event, frontend::FrontendEvent};
|
||||
|
||||
use super::Server;
|
||||
|
||||
pub async fn new(
|
||||
server: Server,
|
||||
frontend_notify_tx: Sender<FrontendNotify>,
|
||||
frontend_notify_tx: Sender<FrontendEvent>,
|
||||
) -> Result<(
|
||||
JoinHandle<()>,
|
||||
Sender<(Event, SocketAddr)>,
|
||||
@@ -55,12 +55,12 @@ pub async fn new(
|
||||
Ok(new_socket) => {
|
||||
socket = new_socket;
|
||||
server.port.replace(port);
|
||||
let _ = frontend_notify_tx.send(FrontendNotify::NotifyPortChange(port, None)).await;
|
||||
let _ = frontend_notify_tx.send(FrontendEvent::PortChanged(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(
|
||||
let _ = frontend_notify_tx.send(FrontendEvent::PortChanged(
|
||||
port,
|
||||
Some(format!("could not change port: {e}")),
|
||||
)).await;
|
||||
|
||||
@@ -34,8 +34,8 @@ pub fn new(
|
||||
// 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)
|
||||
.filter(|(_, (_, s))| !s.pressed_keys.is_empty())
|
||||
.map(|(h, _)| h)
|
||||
.collect()
|
||||
} else {
|
||||
// if sending we care about the active client
|
||||
@@ -46,17 +46,15 @@ pub fn new(
|
||||
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()]
|
||||
.flat_map(|&h| client_manager.get(h))
|
||||
.flat_map(|(c, s)| {
|
||||
if s.alive && s.active_addr.is_some() {
|
||||
vec![s.active_addr.unwrap()]
|
||||
} else {
|
||||
state
|
||||
.client
|
||||
.ips
|
||||
s.ips
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|ip| SocketAddr::new(ip, state.client.port))
|
||||
.map(|ip| SocketAddr::new(ip, c.port))
|
||||
.collect()
|
||||
}
|
||||
})
|
||||
@@ -64,8 +62,8 @@ pub fn new(
|
||||
};
|
||||
|
||||
// reset alive
|
||||
for state in client_manager.get_client_states_mut() {
|
||||
state.alive = false;
|
||||
for (_, (_, s)) in client_manager.get_client_states_mut() {
|
||||
s.alive = false;
|
||||
}
|
||||
|
||||
(ping_clients, ping_addrs)
|
||||
@@ -102,8 +100,8 @@ pub fn new(
|
||||
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),
|
||||
.filter_map(|&h| match client_manager.get(h) {
|
||||
Some((_, s)) if !s.alive => Some(h),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
@@ -112,9 +110,9 @@ pub fn new(
|
||||
// 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 {
|
||||
for h in unresponsive_clients {
|
||||
log::warn!("device not responding, releasing keys!");
|
||||
let _ = emulate_notify.send(EmulationEvent::ReleaseKeys(c)).await;
|
||||
let _ = emulate_notify.send(EmulationEvent::ReleaseKeys(h)).await;
|
||||
}
|
||||
} else {
|
||||
// release pointer if the active client has not responded
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
||||
|
||||
use tokio::{sync::mpsc::Sender, task::JoinHandle};
|
||||
|
||||
use crate::{client::ClientHandle, dns::DnsResolver};
|
||||
use crate::{client::ClientHandle, dns::DnsResolver, frontend::FrontendEvent};
|
||||
|
||||
use super::Server;
|
||||
|
||||
@@ -12,7 +12,11 @@ pub struct DnsRequest {
|
||||
pub handle: ClientHandle,
|
||||
}
|
||||
|
||||
pub fn new(resolver: DnsResolver, server: Server) -> (JoinHandle<()>, Sender<DnsRequest>) {
|
||||
pub fn new(
|
||||
resolver: DnsResolver,
|
||||
mut server: Server,
|
||||
mut frontend: Sender<FrontendEvent>,
|
||||
) -> (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 {
|
||||
@@ -20,21 +24,45 @@ pub fn new(resolver: DnsResolver, server: Server) -> (JoinHandle<()>, Sender<Dns
|
||||
Some(r) => (r.hostname, r.handle),
|
||||
None => break,
|
||||
};
|
||||
|
||||
/* update resolving status */
|
||||
if let Some((_, s)) = server.client_manager.borrow_mut().get_mut(handle) {
|
||||
s.resolving = true;
|
||||
}
|
||||
notify_state_change(&mut frontend, &mut server, handle).await;
|
||||
|
||||
let ips = match resolver.resolve(&host).await {
|
||||
Ok(ips) => ips,
|
||||
Err(e) => {
|
||||
log::warn!("could not resolve host '{host}': {e}");
|
||||
continue;
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
if let Some(state) = server.client_manager.borrow_mut().get_mut(handle) {
|
||||
let mut addrs = HashSet::from_iter(state.client.fix_ips.iter().cloned());
|
||||
|
||||
/* update ips and resolving state */
|
||||
if let Some((c, s)) = server.client_manager.borrow_mut().get_mut(handle) {
|
||||
let mut addrs = HashSet::from_iter(c.fix_ips.iter().cloned());
|
||||
for ip in ips {
|
||||
addrs.insert(ip);
|
||||
}
|
||||
state.client.ips = addrs;
|
||||
s.ips = addrs;
|
||||
s.resolving = false;
|
||||
}
|
||||
notify_state_change(&mut frontend, &mut server, handle).await;
|
||||
}
|
||||
});
|
||||
(resolver_task, dns_tx)
|
||||
}
|
||||
|
||||
async fn notify_state_change(
|
||||
frontend: &mut Sender<FrontendEvent>,
|
||||
server: &mut Server,
|
||||
handle: ClientHandle,
|
||||
) {
|
||||
let state = server.client_manager.borrow_mut().get_mut(handle).cloned();
|
||||
if let Some((config, state)) = state {
|
||||
let _ = frontend
|
||||
.send(FrontendEvent::State(handle, config, state))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user