mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-07 20:09:59 +03:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c99f9bea3 | ||
|
|
d54b3a08e1 | ||
|
|
767fc8bd6b | ||
|
|
fa15048ad8 | ||
|
|
eb366bcd34 | ||
|
|
91176b1267 | ||
|
|
a6f386ea83 | ||
|
|
40b0cdd52e | ||
|
|
1553ed4212 | ||
|
|
4561c20610 | ||
|
|
6cdb607b11 | ||
|
|
f5827bb31c | ||
|
|
64e3bf3ff4 | ||
|
|
6a6d9a9fa9 | ||
|
|
0fffd5bb15 | ||
|
|
b0df901fcc | ||
|
|
9c0cc98dc0 | ||
|
|
5c152b0cbe | ||
|
|
e155819542 | ||
|
|
4b6faea93a | ||
|
|
80d8a496bb | ||
|
|
53e1af0780 | ||
|
|
d3fed1b769 | ||
|
|
cdd3a3b818 | ||
|
|
1cefa38543 | ||
|
|
fed8e02d9f | ||
|
|
65a12735e2 | ||
|
|
3484cab28c | ||
|
|
256d2107bd | ||
|
|
3ac738fb52 | ||
|
|
2ac9277fb0 | ||
|
|
d9fa86ef00 | ||
|
|
015facec39 | ||
|
|
66de3e3cbc | ||
|
|
4600db7af8 | ||
|
|
9f23e1a75d | ||
|
|
a24b231e3c | ||
|
|
8de6c9bb87 | ||
|
|
6766886377 | ||
|
|
a6ab109fae | ||
|
|
06c4e92d43 | ||
|
|
f5a0ff4f3a | ||
|
|
eca367cdb4 | ||
|
|
735434438f | ||
|
|
02d1b33e45 | ||
|
|
19143b90a5 | ||
|
|
ad2aeae275 | ||
|
|
0bdb1bc753 | ||
|
|
9140f60c69 | ||
|
|
0fbf9f4dc2 | ||
|
|
f13e25af82 | ||
|
|
48f7ad3592 | ||
|
|
5fc02d471b | ||
|
|
0c275bc2de | ||
|
|
010db79918 | ||
|
|
ebf5a64f20 | ||
|
|
5c8ea25563 | ||
|
|
b472b56b10 | ||
|
|
18edb0dbad |
50
.github/workflows/pre-release.yml
vendored
50
.github/workflows/pre-release.yml
vendored
@@ -30,14 +30,52 @@ jobs:
|
||||
windows-release-build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
# needed for cache restore
|
||||
- name: create gtk dir
|
||||
run: mkdir C:\gtk-build\gtk\x64\release
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: c:/gtk-build/gtk/x64/release/**
|
||||
key: gtk-windows-build
|
||||
restore-keys: gtk-windows-build
|
||||
- name: Update path
|
||||
run: |
|
||||
echo "PKG_CONFIG=C:\gtk-build\gtk\x64\release\bin\pkgconf.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "C:\pkg-config-lite-0.28-1\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "C:\gtk-build\gtk\x64\release\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo $env:GITHUB_PATH
|
||||
echo $env:PATH
|
||||
- name: Install dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# choco install msys2
|
||||
# choco install visualstudio2022-workload-vctools
|
||||
# choco install pkgconfiglite
|
||||
pipx install gvsbuild
|
||||
# see https://github.com/wingtk/gvsbuild/pull/1004
|
||||
Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin"
|
||||
Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin"
|
||||
gvsbuild build --msys-dir=C:\msys64 gtk4 libadwaita librsvg
|
||||
Move-Item "C:\Program Files\Git\usr\notbin" "C:\Program Files\Git\usr\bin"
|
||||
Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin"
|
||||
- uses: actions/checkout@v3
|
||||
- name: Release Build
|
||||
run: cargo build --release
|
||||
- name: Create Archive
|
||||
run: |
|
||||
mkdir "lan-mouse-windows"
|
||||
Get-Childitem -Path "C:\\gtk-build\\gtk\\x64\\release\\bin\\*.dll" -File -Recurse | Copy-Item -Destination "lan-mouse-windows"
|
||||
Copy-Item -Path "target\release\lan-mouse.exe" -Destination "lan-mouse-windows"
|
||||
Compress-Archive -Path "lan-mouse-windows\*" -DestinationPath lan-mouse-windows.zip
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lan-mouse-windows
|
||||
path: target/release/lan-mouse.exe
|
||||
path: lan-mouse-windows.zip
|
||||
|
||||
macos-release-build:
|
||||
runs-on: macos-latest
|
||||
@@ -46,12 +84,14 @@ jobs:
|
||||
- name: install dependencies
|
||||
run: brew install gtk4 libadwaita
|
||||
- name: Release Build
|
||||
run: cargo build --release
|
||||
run: |
|
||||
cargo build --release
|
||||
cp target/release/lan-mouse lan-mouse-macos-intel
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lan-mouse-macos
|
||||
path: target/release/lan-mouse
|
||||
path: lan-mouse-macos-intel
|
||||
|
||||
pre-release:
|
||||
name: "Pre Release"
|
||||
@@ -69,5 +109,5 @@ jobs:
|
||||
title: "Development Build"
|
||||
files: |
|
||||
lan-mouse-linux/lan-mouse
|
||||
lan-mouse-windows/lan-mouse.exe
|
||||
lan-mouse-macos/lan-mouse
|
||||
lan-mouse-macos/lan-mouse-macos-intel
|
||||
lan-mouse-windows/lan-mouse-windows.zip
|
||||
|
||||
50
.github/workflows/rust.yml
vendored
50
.github/workflows/rust.yml
vendored
@@ -25,6 +25,10 @@ jobs:
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
- name: Check Formatting
|
||||
run: cargo fmt --check
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features --all-targets -- --deny warnings
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -37,15 +41,55 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
# needed for cache restore
|
||||
- name: create gtk dir
|
||||
run: mkdir C:\gtk-build\gtk\x64\release
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: c:/gtk-build/gtk/x64/release/**
|
||||
key: gtk-windows-build
|
||||
restore-keys: gtk-windows-build
|
||||
- name: Update path
|
||||
run: |
|
||||
echo "PKG_CONFIG=C:\gtk-build\gtk\x64\release\bin\pkgconf.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "C:\pkg-config-lite-0.28-1\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "C:\gtk-build\gtk\x64\release\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo $env:GITHUB_PATH
|
||||
echo $env:PATH
|
||||
- name: Install dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# choco install msys2
|
||||
# choco install visualstudio2022-workload-vctools
|
||||
# choco install pkgconfiglite
|
||||
pipx install gvsbuild
|
||||
# see https://github.com/wingtk/gvsbuild/pull/1004
|
||||
Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin"
|
||||
Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin"
|
||||
gvsbuild build --msys-dir=C:\msys64 gtk4 libadwaita librsvg
|
||||
Move-Item "C:\Program Files\Git\usr\notbin" "C:\Program Files\Git\usr\bin"
|
||||
Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin"
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
- name: Check Formatting
|
||||
run: cargo fmt --check
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features --all-targets -- --deny warnings
|
||||
- name: Copy Gtk Dlls
|
||||
run: Get-Childitem -Path "C:\\gtk-build\\gtk\\x64\\release\\bin\\*.dll" -File -Recurse | Copy-Item -Destination "target\\debug"
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lan-mouse-windows
|
||||
path: target/debug/lan-mouse.exe
|
||||
path: |
|
||||
target/debug/lan-mouse.exe
|
||||
target/debug/*.dll
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
@@ -57,6 +101,10 @@ jobs:
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
- name: Check Formatting
|
||||
run: cargo fmt --check
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features --all-targets -- --deny warnings
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
50
.github/workflows/tagged-release.yml
vendored
50
.github/workflows/tagged-release.yml
vendored
@@ -26,14 +26,52 @@ jobs:
|
||||
windows-release-build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
# needed for cache restore
|
||||
- name: create gtk dir
|
||||
run: mkdir C:\gtk-build\gtk\x64\release
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: c:/gtk-build/gtk/x64/release/**
|
||||
key: gtk-windows-build
|
||||
restore-keys: gtk-windows-build
|
||||
- name: Update path
|
||||
run: |
|
||||
echo "PKG_CONFIG=C:\gtk-build\gtk\x64\release\bin\pkgconf.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "C:\pkg-config-lite-0.28-1\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "C:\gtk-build\gtk\x64\release\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo $env:GITHUB_PATH
|
||||
echo $env:PATH
|
||||
- name: Install dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# choco install msys2
|
||||
# choco install visualstudio2022-workload-vctools
|
||||
# choco install pkgconfiglite
|
||||
pipx install gvsbuild
|
||||
# see https://github.com/wingtk/gvsbuild/pull/1004
|
||||
Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin"
|
||||
Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin"
|
||||
gvsbuild build --msys-dir=C:\msys64 gtk4 libadwaita librsvg
|
||||
Move-Item "C:\Program Files\Git\usr\notbin" "C:\Program Files\Git\usr\bin"
|
||||
Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin"
|
||||
- uses: actions/checkout@v3
|
||||
- name: Release Build
|
||||
run: cargo build --release
|
||||
- name: Create Archive
|
||||
run: |
|
||||
mkdir "lan-mouse-windows"
|
||||
Get-Childitem -Path "C:\\gtk-build\\gtk\\x64\\release\\bin\\*.dll" -File -Recurse | Copy-Item -Destination "lan-mouse-windows"
|
||||
Copy-Item -Path "target\release\lan-mouse.exe" -Destination "lan-mouse-windows"
|
||||
Compress-Archive -Path "lan-mouse-windows\*" -DestinationPath lan-mouse-windows.zip
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lan-mouse-windows
|
||||
path: target/release/lan-mouse.exe
|
||||
path: lan-mouse-windows.zip
|
||||
|
||||
macos-release-build:
|
||||
runs-on: macos-latest
|
||||
@@ -42,12 +80,14 @@ jobs:
|
||||
- name: install dependencies
|
||||
run: brew install gtk4 libadwaita
|
||||
- name: Release Build
|
||||
run: cargo build --release
|
||||
run: |
|
||||
cargo build --release
|
||||
cp target/release/lan-mouse lan-mouse-macos-intel
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lan-mouse-macos
|
||||
path: target/release/lan-mouse
|
||||
path: lan-mouse-macos-intel
|
||||
|
||||
tagged-release:
|
||||
name: "Tagged Release"
|
||||
@@ -63,5 +103,5 @@ jobs:
|
||||
prerelease: false
|
||||
files: |
|
||||
lan-mouse-linux/lan-mouse
|
||||
lan-mouse-windows/lan-mouse.exe
|
||||
lan-mouse-macos/lan-mouse
|
||||
lan-mouse-macos/lan-mouse-macos-intel
|
||||
lan-mouse-windows/lan-mouse-windows.zip
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
/target
|
||||
.gdbinit
|
||||
.idea/
|
||||
.vs/
|
||||
711
Cargo.lock
generated
711
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
30
Cargo.toml
30
Cargo.toml
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "lan-mouse"
|
||||
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
|
||||
version = "0.4.0"
|
||||
version = "0.5.1"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/ferdinandschober/lan-mouse"
|
||||
@@ -16,46 +16,46 @@ lto = "fat"
|
||||
tempfile = "3.8"
|
||||
trust-dns-resolver = "0.23"
|
||||
memmap = "0.7"
|
||||
toml = "0.7"
|
||||
toml = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
anyhow = "1.0.71"
|
||||
log = "0.4.20"
|
||||
env_logger = "0.10.0"
|
||||
libc = "0.2.148"
|
||||
serde_json = "1.0.107"
|
||||
tokio = {version = "1.32.0", features = ["io-util", "macros", "net", "rt", "sync", "signal"] }
|
||||
async-trait = "0.1.73"
|
||||
futures-core = "0.3.28"
|
||||
futures = "0.3.28"
|
||||
clap = { version="4.4.11", features = ["derive"] }
|
||||
gtk = { package = "gtk4", version = "0.7.2", features = ["v4_2"], optional = true }
|
||||
adw = { package = "libadwaita", version = "0.5.2", features = ["v1_1"], optional = true }
|
||||
async-channel = { version = "2.1.1", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.148"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os="macos")))'.dependencies]
|
||||
wayland-client = { version="0.30.2", optional = true }
|
||||
wayland-protocols = { version="0.30.0", features=["client", "staging", "unstable"], optional = true }
|
||||
wayland-protocols-wlr = { version="0.1.0", features=["client"], optional = true }
|
||||
wayland-protocols-misc = { version="0.1.0", features=["client"], optional = true }
|
||||
wayland-protocols-plasma = { version="0.1.0", features=["client"], optional = true }
|
||||
wayland-client = { version="0.31.1", optional = true }
|
||||
wayland-protocols = { version="0.31.0", features=["client", "staging", "unstable"], optional = true }
|
||||
wayland-protocols-wlr = { version="0.2.0", features=["client"], optional = true }
|
||||
wayland-protocols-misc = { version="0.2.0", features=["client"], optional = true }
|
||||
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
|
||||
ashpd = { version = "0.6.2", default-features = false, features = ["tokio"], optional = true }
|
||||
reis = { git = "https://github.com/ids1024/reis", features = [ "tokio" ], optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
gtk = { package = "gtk4", version = "0.7.2", features = ["v4_6"], optional = true }
|
||||
adw = { package = "libadwaita", version = "0.5.2", features = ["v1_1"], optional = true }
|
||||
|
||||
[target.'cfg(target_os="macos")'.dependencies]
|
||||
core-graphics = { version = "0.23", features = ["highsierra"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["winuser"] }
|
||||
|
||||
[target.'cfg(unix)'.build-dependencies]
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.18.0"
|
||||
|
||||
[features]
|
||||
default = ["wayland", "x11", "xdg_desktop_portal", "libei", "gtk"]
|
||||
wayland = ["dep:wayland-client", "dep:wayland-protocols", "dep:wayland-protocols-wlr", "dep:wayland-protocols-misc", "dep:wayland-protocols-plasma"]
|
||||
wayland = ["dep:wayland-client", "dep:wayland-protocols", "dep:wayland-protocols-wlr", "dep:wayland-protocols-misc" ]
|
||||
x11 = ["dep:x11"]
|
||||
xdg_desktop_portal = ["dep:ashpd"]
|
||||
libei = ["dep:reis", "dep:ashpd"]
|
||||
gtk = ["dep:gtk", "dep:adw"]
|
||||
gtk = ["dep:gtk", "dep:adw", "dep:async-channel"]
|
||||
|
||||
144
README.md
144
README.md
@@ -1,16 +1,20 @@
|
||||
# Lan Mouse
|
||||
- _Now with a gtk frontend_
|
||||
|
||||
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 have partial support as well (see below for more details).
|
||||
The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details).
|
||||
|
||||

|
||||

|
||||
- _Now with a gtk frontend_
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/feschber/lan-mouse/assets/40996949/016a06a9-76db-4951-9dcc-127d012c59df">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/feschber/lan-mouse/assets/40996949/d6318340-f811-4e16-9d6e-d1b79883c709">
|
||||
<img alt="Screenshot of Lan-Mouse" srcset="https://github.com/feschber/lan-mouse/assets/40996949/016a06a9-76db-4951-9dcc-127d012c59df">
|
||||
</picture>
|
||||
|
||||
|
||||
Goal of this project is to be an open-source replacement for proprietary tools like [Synergy](https://symless.com/synergy), [Share Mouse](https://www.sharemouse.com/de/).
|
||||
Goal of this project is to be an open-source replacement for proprietary tools like [Synergy 2/3](https://symless.com/synergy), [Share Mouse](https://www.sharemouse.com/de/).
|
||||
|
||||
Focus lies on performance and a clean, manageable implementation that can easily be expanded to support additional backends like e.g. Android, iOS, ... .
|
||||
|
||||
@@ -18,6 +22,18 @@ Focus lies on performance and a clean, manageable implementation that can easily
|
||||
|
||||
For an alternative (with slightly different goals) you may check out [Input Leap](https://github.com/input-leap).
|
||||
|
||||
|
||||
> [!WARNING]
|
||||
> Since this tool has gained a bit of popularity over the past couple of days:
|
||||
>
|
||||
> All network traffic is currently **unencrypted** and sent in **plaintext**.
|
||||
>
|
||||
> A malicious actor with access to the network could read input data or send input events with spoofed IPs to take control over a device.
|
||||
>
|
||||
> Therefore you should only use this tool in your local network with trusted devices for now
|
||||
> and I take no responsibility for any leakage of data!
|
||||
|
||||
|
||||
## OS Support
|
||||
|
||||
The following table shows support for input emulation (to emulate events received from other clients) and
|
||||
@@ -28,13 +44,83 @@ input capture (to send events *to* other clients) on different operating systems
|
||||
| Wayland (wlroots) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Wayland (KDE) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Wayland (Gnome) | :heavy_check_mark: | WIP |
|
||||
| X11 | (WIP) | WIP |
|
||||
| Windows | ( :heavy_check_mark: ) | WIP |
|
||||
| X11 | :heavy_check_mark: | WIP |
|
||||
| Windows | :heavy_check_mark: | WIP |
|
||||
| MacOS | ( :heavy_check_mark: ) | WIP |
|
||||
|
||||
Keycode translation is not yet implemented so on MacOS only mouse emulation works as of right now.
|
||||
|
||||
> [!Important]
|
||||
> If you are using [Wayfire](https://github.com/WayfireWM/wayfire), make sure to use a recent version (must be newer than October 23rd) and **add `shortcuts-inhibit` to the list of plugins in your wayfire config!**
|
||||
> Otherwise input capture will not work.
|
||||
|
||||
## Build and Run
|
||||
|
||||
### Install Dependencies
|
||||
#### Macos
|
||||
```sh
|
||||
brew install libadwaita
|
||||
```
|
||||
|
||||
#### Ubuntu and derivatives
|
||||
```sh
|
||||
sudo apt install libadwaita-1-dev libgtk-4-dev libx11-dev libxtst-dev
|
||||
```
|
||||
|
||||
#### Arch and derivatives
|
||||
```sh
|
||||
sudo pacman -S libadwaita gtk libx11 libxtst
|
||||
```
|
||||
|
||||
#### Fedora and derivatives
|
||||
```sh
|
||||
sudo dnf install libadwaita-devel libXtst-devel libX11-devel
|
||||
```
|
||||
|
||||
#### Windows
|
||||
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:
|
||||
```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)
|
||||
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
|
||||
```
|
||||
|
||||
- The following commands should be run in a regular power shell instance:
|
||||
|
||||
```sh
|
||||
# install gvsbuild with python
|
||||
python -m pip install --user pipx
|
||||
python -m pipx ensurepath
|
||||
pipx install gvsbuild
|
||||
|
||||
# build gtk + libadwaita
|
||||
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.
|
||||
|
||||
To avoid building GTK from source, it is possible to disable
|
||||
the gtk frontend (see conditional compilation below).
|
||||
|
||||
### Build and run
|
||||
Build in release mode:
|
||||
```sh
|
||||
cargo build --release
|
||||
@@ -47,22 +133,23 @@ cargo run --release
|
||||
|
||||
### Conditional Compilation
|
||||
|
||||
Currently only x11, wayland and windows are supported backends,
|
||||
Currently only x11, wayland, windows and MacOS are supported backends.
|
||||
Depending on the toolchain used, support for other platforms is omitted
|
||||
automatically (it does not make sense to build a Windows `.exe` with
|
||||
support for x11 and wayland backends).
|
||||
|
||||
However one might still want to omit support for e.g. wayland or x11 on
|
||||
However one might still want to omit support for e.g. wayland, x11 or libei on
|
||||
a Linux system.
|
||||
|
||||
This is possible through
|
||||
[cargo features](https://doc.rust-lang.org/cargo/reference/features.html)
|
||||
[cargo features](https://doc.rust-lang.org/cargo/reference/features.html).
|
||||
|
||||
E.g. if only wayland support is needed, the following command produces
|
||||
an executable with just support for wayland:
|
||||
```sh
|
||||
cargo build --no-default-features --features wayland
|
||||
```
|
||||
For a detailed list of available features, checkout the [Cargo.toml](./Cargo.toml)
|
||||
|
||||
## Usage
|
||||
### Gtk Frontend
|
||||
@@ -135,22 +222,20 @@ port = 4242
|
||||
Where `left` can be either `left`, `right`, `top` or `bottom`.
|
||||
|
||||
## Roadmap
|
||||
- [x] Capture the actual mouse events on the server side via a wayland client and send them to the client
|
||||
- [x] Mouse grabbing
|
||||
- [x] Window with absolute position -> wlr\_layer\_shell
|
||||
- [x] DNS resolving
|
||||
- [x] Keyboard support
|
||||
- [x] Scrollwheel support
|
||||
- [x] Button support
|
||||
- [ ] Latency measurement + logging
|
||||
- [ ] Bandwidth usage approximation + logging
|
||||
- [x] Multiple IP addresses -> check which one is reachable
|
||||
- [x] Merge server and client -> Both client and server can send and receive events depending on what mouse is used where
|
||||
- [x] Liveness tracking (automatically ungrab mouse when client unreachable)
|
||||
- [ ] Clipboard support
|
||||
- [x] Graphical frontend (gtk?)
|
||||
- [ ] *Encryption*
|
||||
- [x] Graphical frontend (gtk + libadwaita)
|
||||
- [x] respect xdg-config-home for config file location.
|
||||
- [x] IP Address switching
|
||||
- [x] Liveness tracking Automatically ungrab mouse when client unreachable
|
||||
- [x] Liveness tracking: Automatically release keys, when server offline
|
||||
- [ ] Libei Input Capture
|
||||
- [ ] X11 Input Capture
|
||||
- [ ] Windows Input Capture
|
||||
- [ ] MacOS Input Capture
|
||||
- [ ] MacOS KeyCode Translation
|
||||
- [ ] Latency measurement and visualization
|
||||
- [ ] Bandwidth usage measurement and visualization
|
||||
- [ ] Clipboard support
|
||||
- [ ] *Encryption*
|
||||
|
||||
## Protocol
|
||||
Currently *all* mouse and keyboard events are sent via **UDP** for performance reasons.
|
||||
@@ -222,10 +307,9 @@ it is however not exposed to third party applications.
|
||||
The recommended way to emulate input on KDE is the
|
||||
[freedesktop remote-desktop-portal](https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.RemoteDesktop).
|
||||
|
||||
#### Gnome (TODO)
|
||||
Gnome uses [libei](https://gitlab.freedesktop.org/libinput/libei) for input emulation,
|
||||
which has the goal to become the general approach for emulating Input on wayland.
|
||||
|
||||
#### Gnome
|
||||
Gnome uses [libei](https://gitlab.freedesktop.org/libinput/libei) for input emulation and capture,
|
||||
which has the goal to become the general approach for emulating and capturing Input on Wayland.
|
||||
|
||||
### Input capture
|
||||
|
||||
|
||||
1
build.rs
1
build.rs
@@ -1,6 +1,5 @@
|
||||
fn main() {
|
||||
// composite_templates
|
||||
#[cfg(unix)]
|
||||
glib_build_tools::compile_resources(
|
||||
&["resources"],
|
||||
"resources/resources.gresource.xml",
|
||||
|
||||
13
lan-mouse.desktop
Normal file
13
lan-mouse.desktop
Normal file
@@ -0,0 +1,13 @@
|
||||
[Desktop Entry]
|
||||
Categories=Utility;
|
||||
Comment[en_US]=mouse & keyboard sharing via LAN
|
||||
Comment[de_DE]=Maus- und Tastaturfreigabe über LAN
|
||||
Exec=lan-mouse
|
||||
Icon=mouse-icon.svg
|
||||
Name[en_US]=Lan Mouse
|
||||
Name[de_DE]=Lan Maus
|
||||
Name=Lan Mouse
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Version=0.5.1
|
||||
@@ -14,7 +14,8 @@
|
||||
<property name="title" translatable="yes">Lan Mouse</property>
|
||||
<property name="show-menubar">True</property>
|
||||
<property name="content">
|
||||
<object class="AdwToolbarView">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<child type ="end">
|
||||
@@ -28,7 +29,7 @@
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<child>
|
||||
<object class="AdwToastOverlay" id="toast_overlay">
|
||||
<child>
|
||||
<object class="AdwStatusPage">
|
||||
@@ -138,7 +139,7 @@
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
|
||||
@@ -15,3 +15,6 @@ pub mod libei;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
/// fallback consumer
|
||||
pub mod dummy;
|
||||
|
||||
26
src/backend/consumer/dummy.rs
Normal file
26
src/backend/consumer/dummy.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle},
|
||||
consumer::EventConsumer,
|
||||
event::Event,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DummyConsumer;
|
||||
|
||||
impl DummyConsumer {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventConsumer for DummyConsumer {
|
||||
async fn consume(&mut self, event: Event, client_handle: ClientHandle) {
|
||||
log::info!("received event: ({client_handle}) {event}");
|
||||
}
|
||||
async fn notify(&mut self, client_event: ClientEvent) {
|
||||
log::info!("{client_event:?}");
|
||||
}
|
||||
async fn destroy(&mut self) {}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ impl LibeiConsumer {
|
||||
let context = ei::Context::new(stream)?;
|
||||
context.flush()?;
|
||||
let events = EiEventStream::new(context.clone())?;
|
||||
return Ok(Self {
|
||||
Ok(Self {
|
||||
handshake: false,
|
||||
context,
|
||||
events,
|
||||
@@ -96,7 +96,7 @@ impl LibeiConsumer {
|
||||
capability_mask: 0,
|
||||
sequence: 0,
|
||||
serial: 0,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ impl EventConsumer for LibeiConsumer {
|
||||
};
|
||||
let event = match event {
|
||||
PendingRequestResult::Request(result) => result,
|
||||
PendingRequestResult::ProtocolError(e) => {
|
||||
PendingRequestResult::ParseError(e) => {
|
||||
return Err(anyhow!("libei protocol violation: {e}"))
|
||||
}
|
||||
PendingRequestResult::InvalidObject(e) => return Err(anyhow!("invalid object {e}")),
|
||||
|
||||
@@ -3,9 +3,9 @@ use crate::consumer::EventConsumer;
|
||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use core_graphics::display::CGPoint;
|
||||
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint};
|
||||
use core_graphics::event::{
|
||||
CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, ScrollEventUnit,
|
||||
CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, EventField, ScrollEventUnit,
|
||||
};
|
||||
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
||||
use std::ops::{Index, IndexMut};
|
||||
@@ -78,6 +78,16 @@ impl EventConsumer for MacOSConsumer {
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
// FIXME secondary displays?
|
||||
let (min_x, min_y, max_x, max_y) = unsafe {
|
||||
let display = CGMainDisplayID();
|
||||
let bounds = CGDisplayBounds(display);
|
||||
let min_x = bounds.origin.x;
|
||||
let max_x = bounds.origin.x + bounds.size.width;
|
||||
let min_y = bounds.origin.y;
|
||||
let max_y = bounds.origin.y + bounds.size.height;
|
||||
(min_x as f64, min_y as f64, max_x as f64, max_y as f64)
|
||||
};
|
||||
let mut mouse_location = match self.get_mouse_location() {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
@@ -85,8 +95,9 @@ impl EventConsumer for MacOSConsumer {
|
||||
return;
|
||||
}
|
||||
};
|
||||
mouse_location.x += relative_x;
|
||||
mouse_location.y += relative_y;
|
||||
|
||||
mouse_location.x = (mouse_location.x + relative_x).clamp(min_x, max_x - 1.);
|
||||
mouse_location.y = (mouse_location.y + relative_y).clamp(min_y, max_y - 1.);
|
||||
|
||||
let mut event_type = CGEventType::MouseMoved;
|
||||
if self.button_state.left {
|
||||
@@ -108,6 +119,14 @@ impl EventConsumer for MacOSConsumer {
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.set_integer_value_field(
|
||||
EventField::MOUSE_EVENT_DELTA_X,
|
||||
relative_x as i64,
|
||||
);
|
||||
event.set_integer_value_field(
|
||||
EventField::MOUSE_EVENT_DELTA_Y,
|
||||
relative_y as i64,
|
||||
);
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
PointerEvent::Button {
|
||||
@@ -140,7 +159,7 @@ impl EventConsumer for MacOSConsumer {
|
||||
}
|
||||
};
|
||||
// store button state
|
||||
self.button_state[mouse_button] = if state == 1 { true } else { false };
|
||||
self.button_state[mouse_button] = state == 1;
|
||||
|
||||
let location = self.get_mouse_location().unwrap();
|
||||
let event = match CGEvent::new_mouse_event(
|
||||
@@ -209,9 +228,7 @@ impl EventConsumer for MacOSConsumer {
|
||||
}
|
||||
KeyboardEvent::Modifiers { .. } => {}
|
||||
},
|
||||
Event::Release() => {}
|
||||
Event::Ping() => {}
|
||||
Event::Pong() => {}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use crate::{
|
||||
consumer::EventConsumer,
|
||||
event::{KeyboardEvent, PointerEvent},
|
||||
scancode,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use std::time::Duration;
|
||||
use tokio::task::AbortHandle;
|
||||
use winapi::um::winuser::{SendInput, KEYEVENTF_EXTENDEDKEY};
|
||||
use winapi::{
|
||||
self,
|
||||
um::winuser::{
|
||||
@@ -18,11 +23,16 @@ use crate::{
|
||||
event::Event,
|
||||
};
|
||||
|
||||
pub struct WindowsConsumer {}
|
||||
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
|
||||
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
|
||||
|
||||
pub struct WindowsConsumer {
|
||||
repeat_task: Option<AbortHandle>,
|
||||
}
|
||||
|
||||
impl WindowsConsumer {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self { repeat_task: None })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +65,15 @@ impl EventConsumer for WindowsConsumer {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
} => key_event(key, state),
|
||||
} => {
|
||||
match state {
|
||||
// pressed
|
||||
0 => self.kill_repeat_task(),
|
||||
1 => self.spawn_repeat_task(key).await,
|
||||
_ => {}
|
||||
}
|
||||
key_event(key, state)
|
||||
}
|
||||
KeyboardEvent::Modifiers { .. } => {}
|
||||
},
|
||||
_ => {}
|
||||
@@ -69,6 +87,27 @@ impl EventConsumer for WindowsConsumer {
|
||||
async fn destroy(&mut self) {}
|
||||
}
|
||||
|
||||
impl WindowsConsumer {
|
||||
async fn spawn_repeat_task(&mut self, key: u32) {
|
||||
// there can only be one repeating key and it's
|
||||
// always the last to be pressed
|
||||
self.kill_repeat_task();
|
||||
let repeat_task = tokio::task::spawn_local(async move {
|
||||
tokio::time::sleep(DEFAULT_REPEAT_DELAY).await;
|
||||
loop {
|
||||
key_event(key, 1);
|
||||
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await;
|
||||
}
|
||||
});
|
||||
self.repeat_task = Some(repeat_task.abort_handle());
|
||||
}
|
||||
fn kill_repeat_task(&mut self) {
|
||||
if let Some(task) = self.repeat_task.take() {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_mouse_input(mi: MOUSEINPUT) {
|
||||
unsafe {
|
||||
let mut input = INPUT {
|
||||
@@ -76,8 +115,8 @@ fn send_mouse_input(mi: MOUSEINPUT) {
|
||||
u: std::mem::transmute(mi),
|
||||
};
|
||||
|
||||
winapi::um::winuser::SendInput(
|
||||
1 as u32,
|
||||
SendInput(
|
||||
1_u32,
|
||||
&mut input as LPINPUT,
|
||||
std::mem::size_of::<INPUT>() as i32,
|
||||
);
|
||||
@@ -141,10 +180,17 @@ fn scroll(axis: u8, value: f64) {
|
||||
}
|
||||
|
||||
fn key_event(key: u32, state: u8) {
|
||||
let scancode = match linux_keycode_to_windows_scancode(key) {
|
||||
Some(code) => code,
|
||||
None => return,
|
||||
};
|
||||
let extended = scancode > 0xff;
|
||||
let scancode = scancode & 0xff;
|
||||
let ki = KEYBDINPUT {
|
||||
wVk: 0,
|
||||
wScan: key as u16,
|
||||
wScan: scancode,
|
||||
dwFlags: KEYEVENTF_SCANCODE
|
||||
| if extended { KEYEVENTF_EXTENDEDKEY } else { 0 }
|
||||
| match state {
|
||||
0 => KEYEVENTF_KEYUP,
|
||||
1 => 0u32,
|
||||
@@ -163,6 +209,26 @@ fn send_keyboard_input(ki: KEYBDINPUT) {
|
||||
u: std::mem::zeroed(),
|
||||
};
|
||||
*input.u.ki_mut() = ki;
|
||||
winapi::um::winuser::SendInput(1 as u32, &mut input, std::mem::size_of::<INPUT>() as i32);
|
||||
SendInput(1_u32, &mut input, std::mem::size_of::<INPUT>() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
fn linux_keycode_to_windows_scancode(linux_keycode: u32) -> Option<u16> {
|
||||
let linux_scancode = match scancode::Linux::try_from(linux_keycode) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
log::warn!("unknown keycode: {linux_keycode}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
log::trace!("linux code: {linux_scancode:?}");
|
||||
let windows_scancode = match scancode::Windows::try_from(linux_scancode) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
log::warn!("failed to translate linux code into windows scancode: {linux_scancode:?}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
log::trace!("windows code: {windows_scancode:?}");
|
||||
Some(windows_scancode as u16)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,11 @@ use crate::consumer::EventConsumer;
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use std::os::fd::{AsFd, OwnedFd};
|
||||
use wayland_client::backend::WaylandError;
|
||||
use wayland_client::WEnum;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use wayland_client::globals::BindError;
|
||||
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
|
||||
use wayland_client::protocol::wl_pointer::{Axis, ButtonState};
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
@@ -23,8 +21,6 @@ use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::{
|
||||
zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1 as Vk,
|
||||
};
|
||||
|
||||
use wayland_protocols_plasma::fake_input::client::org_kde_kwin_fake_input::OrgKdeKwinFakeInput;
|
||||
|
||||
use wayland_client::{
|
||||
delegate_noop,
|
||||
globals::{registry_queue_init, GlobalListContents},
|
||||
@@ -34,17 +30,13 @@ use wayland_client::{
|
||||
|
||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||
|
||||
enum VirtualInputManager {
|
||||
Wlroots { vpm: VpManager, vkm: VkManager },
|
||||
Kde { fake_input: OrgKdeKwinFakeInput },
|
||||
}
|
||||
|
||||
struct State {
|
||||
keymap: Option<(u32, OwnedFd, u32)>,
|
||||
input_for_client: HashMap<ClientHandle, VirtualInput>,
|
||||
seat: wl_seat::WlSeat,
|
||||
virtual_input_manager: VirtualInputManager,
|
||||
qh: QueueHandle<Self>,
|
||||
vpm: VpManager,
|
||||
vkm: VkManager,
|
||||
}
|
||||
|
||||
// App State, implements Dispatch event handlers
|
||||
@@ -56,8 +48,8 @@ pub(crate) struct WlrootsConsumer {
|
||||
|
||||
impl WlrootsConsumer {
|
||||
pub fn new() -> Result<Self> {
|
||||
let conn = Connection::connect_to_env().unwrap();
|
||||
let (globals, queue) = registry_queue_init::<State>(&conn).unwrap();
|
||||
let conn = Connection::connect_to_env()?;
|
||||
let (globals, queue) = registry_queue_init::<State>(&conn)?;
|
||||
let qh = queue.handle();
|
||||
|
||||
let seat: wl_seat::WlSeat = match globals.bind(&qh, 7..=8, ()) {
|
||||
@@ -65,30 +57,8 @@ impl WlrootsConsumer {
|
||||
Err(_) => return Err(anyhow!("wl_seat >= v7 not supported")),
|
||||
};
|
||||
|
||||
let vpm: Result<VpManager, BindError> = globals.bind(&qh, 1..=1, ());
|
||||
let vkm: Result<VkManager, BindError> = globals.bind(&qh, 1..=1, ());
|
||||
let fake_input: Result<OrgKdeKwinFakeInput, BindError> = globals.bind(&qh, 4..=4, ());
|
||||
|
||||
let virtual_input_manager = match (vpm, vkm, fake_input) {
|
||||
(Ok(vpm), Ok(vkm), _) => VirtualInputManager::Wlroots { vpm, vkm },
|
||||
(_, _, Ok(fake_input)) => {
|
||||
fake_input.authenticate(
|
||||
"lan-mouse".into(),
|
||||
"Allow remote clients to control this device".into(),
|
||||
);
|
||||
VirtualInputManager::Kde { fake_input }
|
||||
}
|
||||
(Err(e1), Err(e2), Err(e3)) => {
|
||||
log::warn!("zwlr_virtual_pointer_v1: {e1}");
|
||||
log::warn!("zwp_virtual_keyboard_v1: {e2}");
|
||||
log::warn!("org_kde_kwin_fake_input: {e3}");
|
||||
log::error!("neither wlroots nor kde input emulation protocol supported!");
|
||||
return Err(anyhow!("could not create event consumer"));
|
||||
}
|
||||
_ => {
|
||||
panic!()
|
||||
}
|
||||
};
|
||||
let vpm: VpManager = globals.bind(&qh, 1..=1, ())?;
|
||||
let vkm: VkManager = globals.bind(&qh, 1..=1, ())?;
|
||||
|
||||
let input_for_client: HashMap<ClientHandle, VirtualInput> = HashMap::new();
|
||||
|
||||
@@ -98,7 +68,8 @@ impl WlrootsConsumer {
|
||||
keymap: None,
|
||||
input_for_client,
|
||||
seat,
|
||||
virtual_input_manager,
|
||||
vpm,
|
||||
vkm,
|
||||
qh,
|
||||
},
|
||||
queue,
|
||||
@@ -118,29 +89,19 @@ impl WlrootsConsumer {
|
||||
|
||||
impl State {
|
||||
fn add_client(&mut self, client: ClientHandle) {
|
||||
// create virtual input devices
|
||||
match &self.virtual_input_manager {
|
||||
VirtualInputManager::Wlroots { vpm, vkm } => {
|
||||
let pointer: Vp = vpm.create_virtual_pointer(None, &self.qh, ());
|
||||
let keyboard: Vk = vkm.create_virtual_keyboard(&self.seat, &self.qh, ());
|
||||
let pointer: Vp = self.vpm.create_virtual_pointer(None, &self.qh, ());
|
||||
let keyboard: Vk = self.vkm.create_virtual_keyboard(&self.seat, &self.qh, ());
|
||||
|
||||
// TODO: use server side keymap
|
||||
if let Some((format, fd, size)) = self.keymap.as_ref() {
|
||||
keyboard.keymap(*format, fd.as_raw_fd(), *size);
|
||||
} else {
|
||||
panic!("no keymap");
|
||||
}
|
||||
|
||||
let vinput = VirtualInput::Wlroots { pointer, keyboard };
|
||||
|
||||
self.input_for_client.insert(client, vinput);
|
||||
}
|
||||
VirtualInputManager::Kde { fake_input } => {
|
||||
let fake_input = fake_input.clone();
|
||||
let vinput = VirtualInput::Kde { fake_input };
|
||||
self.input_for_client.insert(client, vinput);
|
||||
}
|
||||
// TODO: use server side keymap
|
||||
if let Some((format, fd, size)) = self.keymap.as_ref() {
|
||||
keyboard.keymap(*format, fd.as_fd(), *size);
|
||||
} else {
|
||||
panic!("no keymap");
|
||||
}
|
||||
|
||||
let vinput = VirtualInput { pointer, keyboard };
|
||||
|
||||
self.input_for_client.insert(client, vinput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,9 +155,9 @@ impl EventConsumer for WlrootsConsumer {
|
||||
async fn destroy(&mut self) {}
|
||||
}
|
||||
|
||||
enum VirtualInput {
|
||||
Wlroots { pointer: Vp, keyboard: Vk },
|
||||
Kde { fake_input: OrgKdeKwinFakeInput },
|
||||
struct VirtualInput {
|
||||
pointer: Vp,
|
||||
keyboard: Vk,
|
||||
}
|
||||
|
||||
impl VirtualInput {
|
||||
@@ -208,94 +169,37 @@ impl VirtualInput {
|
||||
time,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.motion(time, relative_x, relative_y);
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.pointer_motion(relative_y, relative_y);
|
||||
}
|
||||
},
|
||||
} => self.pointer.motion(time, relative_x, relative_y),
|
||||
PointerEvent::Button {
|
||||
time,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let state: ButtonState = state.try_into()?;
|
||||
match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.button(time, button, state);
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.button(button, state as u32);
|
||||
}
|
||||
}
|
||||
self.pointer.button(time, button, state);
|
||||
}
|
||||
PointerEvent::Axis { time, axis, value } => {
|
||||
let axis: Axis = (axis as u32).try_into()?;
|
||||
match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.axis(time, axis, value);
|
||||
pointer.frame();
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.axis(axis as u32, value);
|
||||
}
|
||||
}
|
||||
self.pointer.axis(time, axis, value);
|
||||
self.pointer.frame();
|
||||
}
|
||||
PointerEvent::Frame {} => match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer,
|
||||
keyboard: _,
|
||||
} => {
|
||||
pointer.frame();
|
||||
}
|
||||
VirtualInput::Kde { fake_input: _ } => {}
|
||||
},
|
||||
}
|
||||
match self {
|
||||
VirtualInput::Wlroots { pointer, .. } => {
|
||||
// insert a frame event after each mouse event
|
||||
pointer.frame();
|
||||
}
|
||||
_ => {}
|
||||
PointerEvent::Frame {} => self.pointer.frame(),
|
||||
}
|
||||
self.pointer.frame();
|
||||
}
|
||||
Event::Keyboard(e) => match e {
|
||||
KeyboardEvent::Key { time, key, state } => match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer: _,
|
||||
keyboard,
|
||||
} => {
|
||||
keyboard.key(time, key, state as u32);
|
||||
}
|
||||
VirtualInput::Kde { fake_input } => {
|
||||
fake_input.keyboard_key(key, state as u32);
|
||||
}
|
||||
},
|
||||
KeyboardEvent::Key { time, key, state } => {
|
||||
self.keyboard.key(time, key, state as u32);
|
||||
}
|
||||
KeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
} => match self {
|
||||
VirtualInput::Wlroots {
|
||||
pointer: _,
|
||||
keyboard,
|
||||
} => {
|
||||
keyboard.modifiers(mods_depressed, mods_latched, mods_locked, group);
|
||||
}
|
||||
VirtualInput::Kde { fake_input: _ } => {}
|
||||
},
|
||||
} => {
|
||||
self.keyboard
|
||||
.modifiers(mods_depressed, mods_latched, mods_locked, group);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
@@ -307,7 +211,6 @@ delegate_noop!(State: Vp);
|
||||
delegate_noop!(State: Vk);
|
||||
delegate_noop!(State: VpManager);
|
||||
delegate_noop!(State: VkManager);
|
||||
delegate_noop!(State: OrgKdeKwinFakeInput);
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
|
||||
fn event(
|
||||
@@ -330,11 +233,8 @@ impl Dispatch<WlKeyboard, ()> for State {
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
wl_keyboard::Event::Keymap { format, fd, size } => {
|
||||
state.keymap = Some((u32::from(format), fd, size));
|
||||
}
|
||||
_ => {}
|
||||
if let wl_keyboard::Event::Keymap { format, fd, size } = event {
|
||||
state.keymap = Some((u32::from(format), fd, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use std::ptr;
|
||||
use x11::{xlib, xtest};
|
||||
use x11::{
|
||||
xlib::{self, XCloseDisplay},
|
||||
xtest,
|
||||
};
|
||||
|
||||
use crate::{client::ClientHandle, consumer::EventConsumer, event::Event};
|
||||
use crate::{
|
||||
client::ClientHandle,
|
||||
consumer::EventConsumer,
|
||||
event::{
|
||||
Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct X11Consumer {
|
||||
display: *mut xlib::Display,
|
||||
@@ -11,21 +21,80 @@ pub struct X11Consumer {
|
||||
unsafe impl Send for X11Consumer {}
|
||||
|
||||
impl X11Consumer {
|
||||
pub fn new() -> Self {
|
||||
pub fn new() -> Result<Self> {
|
||||
let display = unsafe {
|
||||
match xlib::XOpenDisplay(ptr::null()) {
|
||||
d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => None,
|
||||
display => Some(display),
|
||||
d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => {
|
||||
Err(anyhow!("could not open display"))
|
||||
}
|
||||
display => Ok(display),
|
||||
}
|
||||
};
|
||||
let display = display.expect("could not open display");
|
||||
Self { display }
|
||||
}?;
|
||||
Ok(Self { display })
|
||||
}
|
||||
|
||||
fn relative_motion(&self, dx: i32, dy: i32) {
|
||||
unsafe {
|
||||
xtest::XTestFakeRelativeMotionEvent(self.display, dx, dy, 0, 0);
|
||||
xlib::XFlush(self.display);
|
||||
}
|
||||
}
|
||||
|
||||
fn emulate_mouse_button(&self, button: u32, state: u32) {
|
||||
unsafe {
|
||||
let x11_button = match button {
|
||||
BTN_RIGHT => 3,
|
||||
BTN_MIDDLE => 2,
|
||||
BTN_BACK => 8,
|
||||
BTN_FORWARD => 9,
|
||||
BTN_LEFT => 1,
|
||||
_ => 1,
|
||||
};
|
||||
xtest::XTestFakeButtonEvent(self.display, x11_button, state as i32, 0);
|
||||
};
|
||||
}
|
||||
|
||||
const SCROLL_UP: u32 = 4;
|
||||
const SCROLL_DOWN: u32 = 5;
|
||||
const SCROLL_LEFT: u32 = 6;
|
||||
const SCROLL_RIGHT: u32 = 7;
|
||||
|
||||
fn emulate_scroll(&self, axis: u8, value: f64) {
|
||||
let direction = match axis {
|
||||
1 => {
|
||||
if value < 0.0 {
|
||||
Self::SCROLL_LEFT
|
||||
} else {
|
||||
Self::SCROLL_RIGHT
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if value < 0.0 {
|
||||
Self::SCROLL_UP
|
||||
} else {
|
||||
Self::SCROLL_DOWN
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
xtest::XTestFakeButtonEvent(self.display, direction, 1, 0);
|
||||
xtest::XTestFakeButtonEvent(self.display, direction, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn emulate_key(&self, key: u32, state: u8) {
|
||||
let key = key + 8; // xorg keycodes are shifted by 8
|
||||
unsafe {
|
||||
xtest::XTestFakeKeyEvent(self.display, key, state as i32, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for X11Consumer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
XCloseDisplay(self.display);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,20 +104,41 @@ impl EventConsumer for X11Consumer {
|
||||
async fn consume(&mut self, event: Event, _: ClientHandle) {
|
||||
match event {
|
||||
Event::Pointer(pointer_event) => match pointer_event {
|
||||
crate::event::PointerEvent::Motion {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
self.relative_motion(relative_x as i32, relative_y as i32);
|
||||
}
|
||||
crate::event::PointerEvent::Button { .. } => {}
|
||||
crate::event::PointerEvent::Axis { .. } => {}
|
||||
crate::event::PointerEvent::Frame {} => {}
|
||||
PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
self.emulate_mouse_button(button, state);
|
||||
}
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
self.emulate_scroll(axis, value);
|
||||
}
|
||||
PointerEvent::Frame {} => {}
|
||||
},
|
||||
Event::Keyboard(_) => {}
|
||||
Event::Keyboard(KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
}) => {
|
||||
self.emulate_key(key, state);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
unsafe {
|
||||
xlib::XFlush(self.display);
|
||||
}
|
||||
}
|
||||
|
||||
async fn notify(&mut self, _: crate::client::ClientEvent) {
|
||||
|
||||
@@ -8,7 +8,14 @@ use ashpd::{
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::consumer::EventConsumer;
|
||||
use crate::{
|
||||
client::ClientEvent,
|
||||
consumer::EventConsumer,
|
||||
event::{
|
||||
Event::{Keyboard, Pointer},
|
||||
KeyboardEvent, PointerEvent,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct DesktopPortalConsumer<'a> {
|
||||
proxy: RemoteDesktop<'a>,
|
||||
@@ -17,8 +24,11 @@ pub struct DesktopPortalConsumer<'a> {
|
||||
|
||||
impl<'a> DesktopPortalConsumer<'a> {
|
||||
pub async fn new() -> Result<DesktopPortalConsumer<'a>> {
|
||||
log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ...");
|
||||
let proxy = RemoteDesktop::new().await?;
|
||||
log::debug!("creating session ...");
|
||||
let session = proxy.create_session().await?;
|
||||
log::debug!("selecting devices ...");
|
||||
proxy
|
||||
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
|
||||
.await?;
|
||||
@@ -27,6 +37,7 @@ impl<'a> DesktopPortalConsumer<'a> {
|
||||
.start(&session, &WindowIdentifier::default())
|
||||
.await?
|
||||
.response()?;
|
||||
log::debug!("started session");
|
||||
|
||||
Ok(Self { proxy, session })
|
||||
}
|
||||
@@ -36,9 +47,9 @@ impl<'a> DesktopPortalConsumer<'a> {
|
||||
impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
||||
async fn consume(&mut self, event: crate::event::Event, _client: crate::client::ClientHandle) {
|
||||
match event {
|
||||
crate::event::Event::Pointer(p) => {
|
||||
Pointer(p) => {
|
||||
match p {
|
||||
crate::event::PointerEvent::Motion {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
@@ -51,7 +62,7 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::Button {
|
||||
PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
@@ -68,7 +79,7 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::Axis {
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
@@ -86,12 +97,12 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::Frame {} => {}
|
||||
PointerEvent::Frame {} => {}
|
||||
}
|
||||
}
|
||||
crate::event::Event::Keyboard(k) => {
|
||||
Keyboard(k) => {
|
||||
match k {
|
||||
crate::event::KeyboardEvent::Key {
|
||||
KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
@@ -108,7 +119,7 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
crate::event::KeyboardEvent::Modifiers { .. } => {
|
||||
KeyboardEvent::Modifiers { .. } => {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
@@ -117,7 +128,7 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn notify(&mut self, _client: crate::client::ClientEvent) {}
|
||||
async fn notify(&mut self, _client: ClientEvent) {}
|
||||
|
||||
async fn destroy(&mut self) {
|
||||
log::debug!("closing remote desktop session");
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
pub mod libei;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
pub mod wayland;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
pub mod x11;
|
||||
|
||||
/// fallback event producer
|
||||
pub mod dummy;
|
||||
|
||||
42
src/backend/producer/dummy.rs
Normal file
42
src/backend/producer/dummy.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures_core::Stream;
|
||||
|
||||
use crate::event::Event;
|
||||
use crate::producer::EventProducer;
|
||||
|
||||
use crate::client::{ClientEvent, ClientHandle};
|
||||
|
||||
pub struct DummyProducer {}
|
||||
|
||||
impl DummyProducer {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DummyProducer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventProducer for DummyProducer {
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for DummyProducer {
|
||||
type Item = io::Result<(ClientHandle, Event)>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,30 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{io, task::Poll};
|
||||
|
||||
use futures_core::Stream;
|
||||
|
||||
use crate::{client::ClientHandle, event::Event, producer::EventProducer};
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle},
|
||||
event::Event,
|
||||
producer::EventProducer,
|
||||
};
|
||||
|
||||
pub struct LibeiProducer {}
|
||||
|
||||
impl LibeiProducer {
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {})
|
||||
Err(anyhow!("not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
impl EventProducer for LibeiProducer {
|
||||
fn notify(&mut self, _event: crate::client::ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for LibeiProducer {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::client::{ClientEvent, ClientHandle};
|
||||
use crate::event::Event;
|
||||
use crate::producer::EventProducer;
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures_core::Stream;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{io, pin::Pin};
|
||||
@@ -8,8 +9,8 @@ use std::{io, pin::Pin};
|
||||
pub struct MacOSProducer;
|
||||
|
||||
impl MacOSProducer {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new() -> Result<Self> {
|
||||
Err(anyhow!("not yet implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +23,11 @@ impl Stream for MacOSProducer {
|
||||
}
|
||||
|
||||
impl EventProducer for MacOSProducer {
|
||||
fn notify(&mut self, _event: ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::{
|
||||
collections::VecDeque,
|
||||
env,
|
||||
io::{self, ErrorKind},
|
||||
os::fd::{OwnedFd, RawFd},
|
||||
os::fd::{AsFd, OwnedFd, RawFd},
|
||||
pin::Pin,
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
@@ -125,6 +125,7 @@ struct Window {
|
||||
buffer: wl_buffer::WlBuffer,
|
||||
surface: wl_surface::WlSurface,
|
||||
layer_surface: ZwlrLayerSurfaceV1,
|
||||
pos: Position,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -145,7 +146,7 @@ impl Window {
|
||||
draw(&mut file, (width, height));
|
||||
let pool = g
|
||||
.shm
|
||||
.create_pool(file.as_raw_fd(), (width * height * 4) as i32, qh, ());
|
||||
.create_pool(file.as_fd(), (width * height * 4) as i32, qh, ());
|
||||
let buffer = pool.create_buffer(
|
||||
0,
|
||||
width as i32,
|
||||
@@ -159,7 +160,7 @@ impl Window {
|
||||
|
||||
let layer_surface = g.layer_shell.get_layer_surface(
|
||||
&surface,
|
||||
Some(&output),
|
||||
Some(output),
|
||||
Layer::Overlay,
|
||||
"LAN Mouse Sharing".into(),
|
||||
qh,
|
||||
@@ -179,6 +180,7 @@ impl Window {
|
||||
surface.set_input_region(None);
|
||||
surface.commit();
|
||||
Window {
|
||||
pos,
|
||||
buffer,
|
||||
surface,
|
||||
layer_surface,
|
||||
@@ -221,13 +223,7 @@ fn get_output_configuration(state: &State, pos: Position) -> Vec<(WlOutput, Outp
|
||||
// as an opposite edge of a different output
|
||||
let outputs: Vec<WlOutput> = edges
|
||||
.iter()
|
||||
.filter(|(_, edge)| {
|
||||
opposite_edges
|
||||
.iter()
|
||||
.map(|(_, e)| *e)
|
||||
.find(|e| e == edge)
|
||||
.is_none()
|
||||
})
|
||||
.filter(|(_, edge)| !opposite_edges.iter().map(|(_, e)| *e).any(|e| &e == edge))
|
||||
.map(|(o, _)| o.clone())
|
||||
.collect();
|
||||
state
|
||||
@@ -244,7 +240,7 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
|
||||
for _ in 0..width {
|
||||
if env::var("LM_DEBUG_LAYER_SHELL").ok().is_some() {
|
||||
// AARRGGBB
|
||||
buf.write_all(&0xFF11d116u32.to_ne_bytes()).unwrap();
|
||||
buf.write_all(&0xff11d116u32.to_ne_bytes()).unwrap();
|
||||
} else {
|
||||
// AARRGGBB
|
||||
buf.write_all(&0x00000000u32.to_ne_bytes()).unwrap();
|
||||
@@ -330,7 +326,7 @@ impl WaylandEventProducer {
|
||||
queue.flush()?;
|
||||
|
||||
// prepare reading wayland events
|
||||
let read_guard = queue.prepare_read()?;
|
||||
let read_guard = queue.prepare_read().unwrap(); // there can not yet be events to dispatch
|
||||
let wayland_fd = read_guard.connection_fd().try_clone_to_owned().unwrap();
|
||||
std::mem::drop(read_guard);
|
||||
|
||||
@@ -372,13 +368,39 @@ impl WaylandEventProducer {
|
||||
log::debug!("{:#?}", i.1);
|
||||
}
|
||||
|
||||
let read_guard = queue.prepare_read()?;
|
||||
let read_guard = loop {
|
||||
match queue.prepare_read() {
|
||||
Some(r) => break r,
|
||||
None => {
|
||||
queue.dispatch_pending(&mut state)?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
state.read_guard = Some(read_guard);
|
||||
|
||||
let inner = AsyncFd::new(Inner { queue, state })?;
|
||||
|
||||
Ok(WaylandEventProducer(inner))
|
||||
}
|
||||
|
||||
fn add_client(&mut self, handle: ClientHandle, pos: Position) {
|
||||
self.0.get_mut().state.add_client(handle, pos);
|
||||
}
|
||||
|
||||
fn delete_client(&mut self, handle: ClientHandle) {
|
||||
let inner = self.0.get_mut();
|
||||
// remove all windows corresponding to this client
|
||||
while let Some(i) = inner
|
||||
.state
|
||||
.client_for_window
|
||||
.iter()
|
||||
.position(|(_, c)| *c == handle)
|
||||
{
|
||||
inner.state.client_for_window.remove(i);
|
||||
inner.state.focused = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
@@ -468,11 +490,24 @@ impl State {
|
||||
let outputs = get_output_configuration(self, pos);
|
||||
|
||||
outputs.iter().for_each(|(o, i)| {
|
||||
let window = Window::new(&self, &self.qh, &o, pos, i.size);
|
||||
let window = Window::new(self, &self.qh, o, pos, i.size);
|
||||
let window = Rc::new(window);
|
||||
self.client_for_window.push((window, client));
|
||||
});
|
||||
}
|
||||
|
||||
fn update_windows(&mut self) {
|
||||
log::debug!("updating windows");
|
||||
log::debug!("output info: {:?}", self.output_info);
|
||||
let clients: Vec<_> = self
|
||||
.client_for_window
|
||||
.drain(..)
|
||||
.map(|(w, c)| (c, w.pos))
|
||||
.collect();
|
||||
for (client, pos) in clients {
|
||||
self.add_client(client, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
@@ -490,16 +525,20 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_read(&mut self) {
|
||||
match self.queue.prepare_read() {
|
||||
Ok(r) => self.state.read_guard = Some(r),
|
||||
Err(WaylandError::Io(e)) => {
|
||||
log::error!("error preparing read from wayland socket: {e}")
|
||||
fn prepare_read(&mut self) -> io::Result<()> {
|
||||
loop {
|
||||
match self.queue.prepare_read() {
|
||||
None => match self.queue.dispatch_pending(&mut self.state) {
|
||||
Ok(_) => continue,
|
||||
Err(DispatchError::Backend(WaylandError::Io(e))) => return Err(e),
|
||||
Err(e) => panic!("failed to dispatch wayland events: {e}"),
|
||||
},
|
||||
Some(r) => {
|
||||
self.state.read_guard = Some(r);
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
Err(WaylandError::Protocol(e)) => {
|
||||
panic!("wayland Protocol violation: {e}")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_events(&mut self) {
|
||||
@@ -521,54 +560,42 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_events(&mut self) {
|
||||
fn flush_events(&mut self) -> io::Result<()> {
|
||||
// flush outgoing events
|
||||
match self.queue.flush() {
|
||||
Ok(_) => (),
|
||||
Err(e) => match e {
|
||||
WaylandError::Io(e) => {
|
||||
log::error!("error writing to wayland socket: {e}")
|
||||
return Err(e);
|
||||
}
|
||||
WaylandError::Protocol(e) => {
|
||||
panic!("wayland protocol violation: {e}")
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl EventProducer for WaylandEventProducer {
|
||||
fn notify(&mut self, client_event: ClientEvent) {
|
||||
fn notify(&mut self, client_event: ClientEvent) -> io::Result<()> {
|
||||
match client_event {
|
||||
ClientEvent::Create(handle, pos) => {
|
||||
self.0.get_mut().state.add_client(handle, pos);
|
||||
self.add_client(handle, pos);
|
||||
}
|
||||
ClientEvent::Destroy(handle) => {
|
||||
let inner = self.0.get_mut();
|
||||
loop {
|
||||
// remove all windows corresponding to this client
|
||||
if let Some(i) = inner
|
||||
.state
|
||||
.client_for_window
|
||||
.iter()
|
||||
.position(|(_, c)| *c == handle)
|
||||
{
|
||||
inner.state.client_for_window.remove(i);
|
||||
inner.state.focused = None;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.delete_client(handle);
|
||||
}
|
||||
}
|
||||
let inner = self.0.get_mut();
|
||||
inner.flush_events();
|
||||
inner.flush_events()
|
||||
}
|
||||
|
||||
fn release(&mut self) {
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
log::debug!("releasing pointer");
|
||||
let inner = self.0.get_mut();
|
||||
inner.state.ungrab();
|
||||
inner.flush_events();
|
||||
inner.flush_events()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +603,6 @@ impl Stream for WaylandEventProducer {
|
||||
type Item = io::Result<(ClientHandle, Event)>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
log::trace!("producer.next()");
|
||||
if let Some(event) = self.0.get_mut().state.pending_events.pop_front() {
|
||||
return Poll::Ready(Some(Ok(event)));
|
||||
}
|
||||
@@ -590,17 +616,27 @@ impl Stream for WaylandEventProducer {
|
||||
// read events
|
||||
while inner.read() {
|
||||
// prepare next read
|
||||
inner.prepare_read();
|
||||
match inner.prepare_read() {
|
||||
Ok(_) => {}
|
||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch the events
|
||||
inner.dispatch_events();
|
||||
|
||||
// flush outgoing events
|
||||
inner.flush_events();
|
||||
if let Err(e) = inner.flush_events() {
|
||||
if e.kind() != ErrorKind::WouldBlock {
|
||||
return Poll::Ready(Some(Err(e)));
|
||||
}
|
||||
}
|
||||
|
||||
// prepare for the next read
|
||||
inner.prepare_read();
|
||||
match inner.prepare_read() {
|
||||
Ok(_) => {}
|
||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||
}
|
||||
}
|
||||
|
||||
// clear read readiness for tokio read guard
|
||||
@@ -663,7 +699,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
.find(|(w, _c)| w.surface == surface)
|
||||
{
|
||||
app.focused = Some((window.clone(), *client));
|
||||
app.grab(&surface, pointer, serial.clone(), qh);
|
||||
app.grab(&surface, pointer, serial, qh);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -673,9 +709,16 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
.iter()
|
||||
.find(|(w, _c)| w.surface == surface)
|
||||
.unwrap();
|
||||
app.pending_events.push_back((*client, Event::Release()));
|
||||
app.pending_events.push_back((*client, Event::Enter()));
|
||||
}
|
||||
wl_pointer::Event::Leave { .. } => {
|
||||
/* There are rare cases, where when a window is opened in
|
||||
* just the wrong moment, the pointer is released, while
|
||||
* still grabbed.
|
||||
* In that case, the pointer must be ungrabbed, otherwise
|
||||
* it is impossible to grab it again (since the pointer
|
||||
* lock, relative pointer,... objects are still in place)
|
||||
*/
|
||||
app.ungrab();
|
||||
}
|
||||
wl_pointer::Event::Button {
|
||||
@@ -764,10 +807,6 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
|
||||
}),
|
||||
));
|
||||
}
|
||||
if mods_depressed == 77 {
|
||||
// ctrl shift super alt
|
||||
app.ungrab();
|
||||
}
|
||||
}
|
||||
wl_keyboard::Event::Keymap {
|
||||
format: _,
|
||||
@@ -834,7 +873,7 @@ impl Dispatch<ZwlrLayerSurfaceV1, ()> for State {
|
||||
// client corresponding to the layer_surface
|
||||
let surface = &window.surface;
|
||||
let buffer = &window.buffer;
|
||||
surface.attach(Some(&buffer), 0, 0);
|
||||
surface.attach(Some(buffer), 0, 0);
|
||||
layer_surface.ack_configure(serial);
|
||||
surface.commit();
|
||||
}
|
||||
@@ -869,16 +908,15 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
||||
name,
|
||||
interface,
|
||||
version: _,
|
||||
} => match interface.as_str() {
|
||||
"wl_output" => {
|
||||
} => {
|
||||
if interface.as_str() == "wl_output" {
|
||||
log::debug!("wl_output global");
|
||||
state
|
||||
.g
|
||||
.outputs
|
||||
.push(registry.bind::<wl_output::WlOutput, _, _>(name, 4, qh, ()))
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
wl_registry::Event::GlobalRemove { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
@@ -921,6 +959,21 @@ impl Dispatch<ZxdgOutputV1, WlOutput> for State {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_output::WlOutput, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &wl_output::WlOutput,
|
||||
event: <wl_output::WlOutput as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_output::Event::Done = event {
|
||||
state.update_windows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't emit any events
|
||||
delegate_noop!(State: wl_region::WlRegion);
|
||||
delegate_noop!(State: wl_shm_pool::WlShmPool);
|
||||
@@ -931,7 +984,6 @@ delegate_noop!(State: ZwpKeyboardShortcutsInhibitManagerV1);
|
||||
delegate_noop!(State: ZwpPointerConstraintsV1);
|
||||
|
||||
// ignore events
|
||||
delegate_noop!(State: ignore wl_output::WlOutput);
|
||||
delegate_noop!(State: ignore ZxdgOutputManagerV1);
|
||||
delegate_noop!(State: ignore wl_shm::WlShm);
|
||||
delegate_noop!(State: ignore wl_buffer::WlBuffer);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use core::task::{Context, Poll};
|
||||
use futures::Stream;
|
||||
use std::io::Result;
|
||||
use std::pin::Pin;
|
||||
use std::{io, pin::Pin};
|
||||
|
||||
use crate::{
|
||||
client::{ClientEvent, ClientHandle},
|
||||
@@ -12,19 +12,23 @@ use crate::{
|
||||
pub struct WindowsProducer {}
|
||||
|
||||
impl EventProducer for WindowsProducer {
|
||||
fn notify(&mut self, _: ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsProducer {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {}
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
Err(anyhow!("not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for WindowsProducer {
|
||||
type Item = Result<(ClientHandle, Event)>;
|
||||
type Item = io::Result<(ClientHandle, Event)>;
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::io;
|
||||
use std::task::Poll;
|
||||
|
||||
@@ -11,15 +12,19 @@ use crate::client::{ClientEvent, ClientHandle};
|
||||
pub struct X11Producer {}
|
||||
|
||||
impl X11Producer {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new() -> Result<Self> {
|
||||
Err(anyhow!("not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
impl EventProducer for X11Producer {
|
||||
fn notify(&mut self, _: ClientEvent) {}
|
||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) {}
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for X11Producer {
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::{
|
||||
collections::HashSet,
|
||||
fmt::Display,
|
||||
net::{IpAddr, SocketAddr},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -51,14 +50,12 @@ impl Display for Position {
|
||||
pub struct Client {
|
||||
/// 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 event consumer / producer backend does not
|
||||
/// need to know anything about a client other than its handle.
|
||||
pub handle: ClientHandle,
|
||||
/// `active` address of the client, used to send data to.
|
||||
/// This should generally be the socket address where data
|
||||
/// was last received from.
|
||||
pub active_addr: Option<SocketAddr>,
|
||||
/// all socket addresses associated with a particular client
|
||||
/// e.g. Laptops usually have at least an ethernet and a wifi port
|
||||
/// which have different ip addresses
|
||||
@@ -69,6 +66,7 @@ pub struct Client {
|
||||
pub pos: Position,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ClientEvent {
|
||||
Create(ClientHandle, Position),
|
||||
Destroy(ClientHandle),
|
||||
@@ -78,17 +76,30 @@ pub type ClientHandle = u32;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientState {
|
||||
/// information about the client
|
||||
pub client: Client,
|
||||
/// events should be sent to and received from the client
|
||||
pub active: bool,
|
||||
pub last_ping: Option<Instant>,
|
||||
pub last_seen: Option<Instant>,
|
||||
pub last_replied: Option<Instant>,
|
||||
/// `active` address of the client, used to send data to.
|
||||
/// This should generally be the socket address where data
|
||||
/// was last received from.
|
||||
pub active_addr: Option<SocketAddr>,
|
||||
/// tracks whether or not the client is responding to pings
|
||||
pub alive: bool,
|
||||
/// keys currently pressed by this client
|
||||
pub pressed_keys: HashSet<u32>,
|
||||
}
|
||||
|
||||
pub struct ClientManager {
|
||||
clients: Vec<Option<ClientState>>, // HashMap likely not beneficial
|
||||
}
|
||||
|
||||
impl Default for ClientManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientManager {
|
||||
pub fn new() -> Self {
|
||||
Self { clients: vec![] }
|
||||
@@ -98,24 +109,24 @@ impl ClientManager {
|
||||
pub fn add_client(
|
||||
&mut self,
|
||||
hostname: Option<String>,
|
||||
addrs: HashSet<IpAddr>,
|
||||
ips: HashSet<IpAddr>,
|
||||
port: u16,
|
||||
pos: Position,
|
||||
) -> ClientHandle {
|
||||
// get a new client_handle
|
||||
let handle = self.free_id();
|
||||
|
||||
// we dont know, which IP is initially active
|
||||
let active_addr = None;
|
||||
// store fix ip addresses
|
||||
let fix_ips = ips.iter().cloned().collect();
|
||||
|
||||
// map ip addresses to socket addresses
|
||||
let addrs = HashSet::from_iter(addrs.into_iter().map(|ip| SocketAddr::new(ip, port)));
|
||||
let addrs = HashSet::from_iter(ips.into_iter().map(|ip| SocketAddr::new(ip, port)));
|
||||
|
||||
// store the client
|
||||
let client = Client {
|
||||
hostname,
|
||||
fix_ips,
|
||||
handle,
|
||||
active_addr,
|
||||
addrs,
|
||||
port,
|
||||
pos,
|
||||
@@ -124,10 +135,10 @@ impl ClientManager {
|
||||
// client was never seen, nor pinged
|
||||
let client_state = ClientState {
|
||||
client,
|
||||
last_ping: None,
|
||||
last_seen: None,
|
||||
last_replied: None,
|
||||
active: false,
|
||||
active_addr: None,
|
||||
alive: false,
|
||||
pressed_keys: HashSet::new(),
|
||||
};
|
||||
|
||||
if handle as usize >= self.clients.len() {
|
||||
@@ -174,20 +185,20 @@ impl ClientManager {
|
||||
}
|
||||
|
||||
// returns an immutable reference to the client state corresponding to `client`
|
||||
pub fn get<'a>(&'a self, client: ClientHandle) -> Option<&'a ClientState> {
|
||||
pub fn get(&self, client: ClientHandle) -> Option<&ClientState> {
|
||||
self.clients.get(client as usize)?.as_ref()
|
||||
}
|
||||
|
||||
/// returns a mutable reference to the client state corresponding to `client`
|
||||
pub fn get_mut<'a>(&'a mut self, client: ClientHandle) -> Option<&'a mut ClientState> {
|
||||
pub fn get_mut(&mut self, client: ClientHandle) -> Option<&mut ClientState> {
|
||||
self.clients.get_mut(client as usize)?.as_mut()
|
||||
}
|
||||
|
||||
pub fn enumerate(&self) -> Vec<(Client, bool)> {
|
||||
self.clients
|
||||
.iter()
|
||||
.filter_map(|s| s.as_ref())
|
||||
.map(|s| (s.client.clone(), s.active))
|
||||
.collect()
|
||||
pub fn get_client_states(&self) -> impl Iterator<Item = &ClientState> {
|
||||
self.clients.iter().filter_map(|x| x.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_client_states_mut(&mut self) -> impl Iterator<Item = &mut ClientState> {
|
||||
self.clients.iter_mut().filter_map(|x| x.as_mut())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,9 +109,9 @@ impl Config {
|
||||
};
|
||||
|
||||
let frontend = match frontend {
|
||||
#[cfg(all(unix, feature = "gtk"))]
|
||||
#[cfg(feature = "gtk")]
|
||||
None => Frontend::Gtk,
|
||||
#[cfg(any(not(feature = "gtk"), not(unix)))]
|
||||
#[cfg(not(feature = "gtk"))]
|
||||
None => Frontend::Cli,
|
||||
Some(s) => match s.as_str() {
|
||||
"cli" => Frontend::Cli,
|
||||
|
||||
142
src/consumer.rs
142
src/consumer.rs
@@ -1,9 +1,6 @@
|
||||
use async_trait::async_trait;
|
||||
use std::future;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
use std::env;
|
||||
|
||||
use crate::{
|
||||
backend::consumer,
|
||||
client::{ClientEvent, ClientHandle},
|
||||
@@ -11,15 +8,6 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[derive(Debug)]
|
||||
enum Backend {
|
||||
Wlroots,
|
||||
X11,
|
||||
RemoteDesktopPortal,
|
||||
Libei,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait EventConsumer: Send {
|
||||
async fn consume(&mut self, event: Event, client_handle: ClientHandle);
|
||||
@@ -33,90 +21,58 @@ pub trait EventConsumer: Send {
|
||||
async fn destroy(&mut self);
|
||||
}
|
||||
|
||||
pub async fn create() -> Result<Box<dyn EventConsumer>> {
|
||||
pub async fn create() -> Box<dyn EventConsumer> {
|
||||
#[cfg(windows)]
|
||||
return Ok(Box::new(consumer::windows::WindowsConsumer::new()));
|
||||
match consumer::windows::WindowsConsumer::new() {
|
||||
Ok(c) => return Box::new(c),
|
||||
Err(e) => log::warn!("windows event consumer unavailable: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
return Ok(Box::new(consumer::macos::MacOSConsumer::new()?));
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
let backend = match env::var("XDG_SESSION_TYPE") {
|
||||
Ok(session_type) => match session_type.as_str() {
|
||||
"x11" => {
|
||||
log::info!("XDG_SESSION_TYPE = x11 -> using x11 event consumer");
|
||||
Backend::X11
|
||||
}
|
||||
"wayland" => {
|
||||
log::info!("XDG_SESSION_TYPE = wayland -> using wayland event consumer");
|
||||
match env::var("XDG_CURRENT_DESKTOP") {
|
||||
Ok(current_desktop) => match current_desktop.as_str() {
|
||||
"GNOME" => {
|
||||
log::info!("XDG_CURRENT_DESKTOP = GNOME -> using libei backend");
|
||||
Backend::Libei
|
||||
}
|
||||
"KDE" => {
|
||||
log::info!(
|
||||
"XDG_CURRENT_DESKTOP = KDE -> using xdg_desktop_portal backend"
|
||||
);
|
||||
Backend::RemoteDesktopPortal
|
||||
}
|
||||
"sway" => {
|
||||
log::info!("XDG_CURRENT_DESKTOP = sway -> using wlroots backend");
|
||||
Backend::Wlroots
|
||||
}
|
||||
"Hyprland" => {
|
||||
log::info!("XDG_CURRENT_DESKTOP = Hyprland -> using wlroots backend");
|
||||
Backend::Wlroots
|
||||
}
|
||||
_ => {
|
||||
log::warn!(
|
||||
"unknown XDG_CURRENT_DESKTOP -> defaulting to wlroots backend"
|
||||
);
|
||||
Backend::Wlroots
|
||||
}
|
||||
},
|
||||
// default to wlroots backend for now
|
||||
_ => {
|
||||
log::warn!("unknown XDG_CURRENT_DESKTOP -> defaulting to wlroots backend");
|
||||
Backend::Wlroots
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!("unknown XDG_SESSION_TYPE"),
|
||||
},
|
||||
Err(_) => {
|
||||
panic!("could not detect session type: XDG_SESSION_TYPE environment variable not set!")
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
match backend {
|
||||
Backend::Libei => {
|
||||
#[cfg(not(feature = "libei"))]
|
||||
panic!("feature libei not enabled");
|
||||
#[cfg(feature = "libei")]
|
||||
Ok(Box::new(consumer::libei::LibeiConsumer::new().await?))
|
||||
}
|
||||
Backend::RemoteDesktopPortal => {
|
||||
#[cfg(not(feature = "xdg_desktop_portal"))]
|
||||
panic!("feature xdg_desktop_portal not enabled");
|
||||
#[cfg(feature = "xdg_desktop_portal")]
|
||||
Ok(Box::new(
|
||||
consumer::xdg_desktop_portal::DesktopPortalConsumer::new().await?,
|
||||
))
|
||||
}
|
||||
Backend::Wlroots => {
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
panic!("feature wayland not enabled");
|
||||
#[cfg(feature = "wayland")]
|
||||
Ok(Box::new(consumer::wlroots::WlrootsConsumer::new()?))
|
||||
}
|
||||
Backend::X11 => {
|
||||
#[cfg(not(feature = "x11"))]
|
||||
panic!("feature x11 not enabled");
|
||||
#[cfg(feature = "x11")]
|
||||
Ok(Box::new(consumer::x11::X11Consumer::new()))
|
||||
match consumer::macos::MacOSConsumer::new() {
|
||||
Ok(c) => {
|
||||
log::info!("using macos event consumer");
|
||||
return Box::new(c);
|
||||
}
|
||||
Err(e) => log::error!("macos consumer not available: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
match consumer::wlroots::WlrootsConsumer::new() {
|
||||
Ok(c) => {
|
||||
log::info!("using wlroots event consumer");
|
||||
return Box::new(c);
|
||||
}
|
||||
Err(e) => log::info!("wayland backend not available: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
match consumer::libei::LibeiConsumer::new().await {
|
||||
Ok(c) => {
|
||||
log::info!("using libei event consumer");
|
||||
return Box::new(c);
|
||||
}
|
||||
Err(e) => log::info!("libei not available: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
match consumer::xdg_desktop_portal::DesktopPortalConsumer::new().await {
|
||||
Ok(c) => {
|
||||
log::info!("using xdg-remote-desktop-portal event consumer");
|
||||
return Box::new(c);
|
||||
}
|
||||
Err(e) => log::info!("remote desktop portal not available: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
match consumer::x11::X11Consumer::new() {
|
||||
Ok(c) => {
|
||||
log::info!("using x11 event consumer");
|
||||
return Box::new(c);
|
||||
}
|
||||
Err(e) => log::info!("x11 consumer not available: {e}"),
|
||||
}
|
||||
|
||||
log::error!("falling back to dummy event consumer");
|
||||
Box::new(consumer::dummy::DummyConsumer::new())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{error::Error, net::IpAddr};
|
||||
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
|
||||
pub(crate) struct DnsResolver {
|
||||
pub struct DnsResolver {
|
||||
resolver: TokioAsyncResolver,
|
||||
}
|
||||
impl DnsResolver {
|
||||
|
||||
227
src/event.rs
227
src/event.rs
@@ -1,14 +1,17 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
// FIXME
|
||||
pub(crate) const BTN_LEFT: u32 = 0x110;
|
||||
pub(crate) const BTN_RIGHT: u32 = 0x111;
|
||||
pub(crate) const BTN_MIDDLE: u32 = 0x112;
|
||||
pub const BTN_LEFT: u32 = 0x110;
|
||||
pub const BTN_RIGHT: u32 = 0x111;
|
||||
pub const BTN_MIDDLE: u32 = 0x112;
|
||||
pub const BTN_BACK: u32 = 0x113;
|
||||
pub const BTN_FORWARD: u32 = 0x114;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum PointerEvent {
|
||||
Motion {
|
||||
time: u32,
|
||||
@@ -28,7 +31,7 @@ pub enum PointerEvent {
|
||||
Frame {},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum KeyboardEvent {
|
||||
Key {
|
||||
time: u32,
|
||||
@@ -43,13 +46,29 @@ pub enum KeyboardEvent {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub enum Event {
|
||||
/// pointer event (motion / button / axis)
|
||||
Pointer(PointerEvent),
|
||||
/// keyboard events (key / modifiers)
|
||||
Keyboard(KeyboardEvent),
|
||||
Release(),
|
||||
/// enter event: request to enter a client.
|
||||
/// The client must release the pointer if it is grabbed
|
||||
/// and reply with a leave event, as soon as its ready to
|
||||
/// receive events
|
||||
Enter(),
|
||||
/// leave event: this client is now ready to receive events and will
|
||||
/// not send any events after until it sends an enter event
|
||||
Leave(),
|
||||
/// ping a client, to see if it is still alive. A client that does
|
||||
/// not respond with a pong event will be assumed to be offline.
|
||||
Ping(),
|
||||
/// response to a ping event: this event signals that a client
|
||||
/// is still alive but must otherwise be ignored
|
||||
Pong(),
|
||||
/// explicit disconnect request. The client will no longer
|
||||
/// send events until the next Enter event. All of its keys should be released.
|
||||
Disconnect(),
|
||||
}
|
||||
|
||||
impl Display for PointerEvent {
|
||||
@@ -101,9 +120,11 @@ impl Display for Event {
|
||||
match self {
|
||||
Event::Pointer(p) => write!(f, "{}", p),
|
||||
Event::Keyboard(k) => write!(f, "{}", k),
|
||||
Event::Release() => write!(f, "release"),
|
||||
Event::Enter() => write!(f, "enter"),
|
||||
Event::Leave() => write!(f, "leave"),
|
||||
Event::Ping() => write!(f, "ping"),
|
||||
Event::Pong() => write!(f, "pong"),
|
||||
Event::Disconnect() => write!(f, "disconnect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,11 +132,13 @@ impl Display for Event {
|
||||
impl Event {
|
||||
fn event_type(&self) -> EventType {
|
||||
match self {
|
||||
Self::Pointer(_) => EventType::POINTER,
|
||||
Self::Keyboard(_) => EventType::KEYBOARD,
|
||||
Self::Release() => EventType::RELEASE,
|
||||
Self::Ping() => EventType::PING,
|
||||
Self::Pong() => EventType::PONG,
|
||||
Self::Pointer(_) => EventType::Pointer,
|
||||
Self::Keyboard(_) => EventType::Keyboard,
|
||||
Self::Enter() => EventType::Enter,
|
||||
Self::Leave() => EventType::Leave,
|
||||
Self::Ping() => EventType::Ping,
|
||||
Self::Pong() => EventType::Pong,
|
||||
Self::Disconnect() => EventType::Disconnect,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,10 +146,10 @@ impl Event {
|
||||
impl PointerEvent {
|
||||
fn event_type(&self) -> PointerEventType {
|
||||
match self {
|
||||
Self::Motion { .. } => PointerEventType::MOTION,
|
||||
Self::Button { .. } => PointerEventType::BUTTON,
|
||||
Self::Axis { .. } => PointerEventType::AXIS,
|
||||
Self::Frame { .. } => PointerEventType::FRAME,
|
||||
Self::Motion { .. } => PointerEventType::Motion,
|
||||
Self::Button { .. } => PointerEventType::Button,
|
||||
Self::Axis { .. } => PointerEventType::Axis,
|
||||
Self::Frame { .. } => PointerEventType::Frame,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,40 +157,42 @@ impl PointerEvent {
|
||||
impl KeyboardEvent {
|
||||
fn event_type(&self) -> KeyboardEventType {
|
||||
match self {
|
||||
KeyboardEvent::Key { .. } => KeyboardEventType::KEY,
|
||||
KeyboardEvent::Modifiers { .. } => KeyboardEventType::MODIFIERS,
|
||||
KeyboardEvent::Key { .. } => KeyboardEventType::Key,
|
||||
KeyboardEvent::Modifiers { .. } => KeyboardEventType::Modifiers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PointerEventType {
|
||||
MOTION,
|
||||
BUTTON,
|
||||
AXIS,
|
||||
FRAME,
|
||||
Motion,
|
||||
Button,
|
||||
Axis,
|
||||
Frame,
|
||||
}
|
||||
enum KeyboardEventType {
|
||||
KEY,
|
||||
MODIFIERS,
|
||||
Key,
|
||||
Modifiers,
|
||||
}
|
||||
enum EventType {
|
||||
POINTER,
|
||||
KEYBOARD,
|
||||
RELEASE,
|
||||
PING,
|
||||
PONG,
|
||||
Pointer,
|
||||
Keyboard,
|
||||
Enter,
|
||||
Leave,
|
||||
Ping,
|
||||
Pong,
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PointerEventType {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
match value {
|
||||
x if x == Self::MOTION as u8 => Ok(Self::MOTION),
|
||||
x if x == Self::BUTTON as u8 => Ok(Self::BUTTON),
|
||||
x if x == Self::AXIS as u8 => Ok(Self::AXIS),
|
||||
x if x == Self::FRAME as u8 => Ok(Self::FRAME),
|
||||
_ => Err(Box::new(ProtocolError {
|
||||
x if x == Self::Motion as u8 => Ok(Self::Motion),
|
||||
x if x == Self::Button as u8 => Ok(Self::Button),
|
||||
x if x == Self::Axis as u8 => Ok(Self::Axis),
|
||||
x if x == Self::Frame as u8 => Ok(Self::Frame),
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid pointer event type {}", value),
|
||||
})),
|
||||
}
|
||||
@@ -175,30 +200,32 @@ impl TryFrom<u8> for PointerEventType {
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for KeyboardEventType {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
match value {
|
||||
x if x == Self::KEY as u8 => Ok(Self::KEY),
|
||||
x if x == Self::MODIFIERS as u8 => Ok(Self::MODIFIERS),
|
||||
_ => Err(Box::new(ProtocolError {
|
||||
x if x == Self::Key as u8 => Ok(Self::Key),
|
||||
x if x == Self::Modifiers as u8 => Ok(Self::Modifiers),
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid keyboard event type {}", value),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for &Event {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let event_id = vec![self.event_type() as u8];
|
||||
let event_data = match self {
|
||||
impl From<&Event> for Vec<u8> {
|
||||
fn from(event: &Event) -> Self {
|
||||
let event_id = vec![event.event_type() as u8];
|
||||
let event_data = match event {
|
||||
Event::Pointer(p) => p.into(),
|
||||
Event::Keyboard(k) => k.into(),
|
||||
Event::Release() => vec![],
|
||||
Event::Enter() => vec![],
|
||||
Event::Leave() => vec![],
|
||||
Event::Ping() => vec![],
|
||||
Event::Pong() => vec![],
|
||||
Event::Disconnect() => vec![],
|
||||
};
|
||||
vec![event_id, event_data].concat()
|
||||
[event_id, event_data].concat()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,27 +242,29 @@ impl fmt::Display for ProtocolError {
|
||||
impl Error for ProtocolError {}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Event {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: Vec<u8>) -> Result<Self> {
|
||||
let event_id = u8::from_be_bytes(value[..1].try_into()?);
|
||||
match event_id {
|
||||
i if i == (EventType::POINTER as u8) => Ok(Event::Pointer(value.try_into()?)),
|
||||
i if i == (EventType::KEYBOARD as u8) => Ok(Event::Keyboard(value.try_into()?)),
|
||||
i if i == (EventType::RELEASE as u8) => Ok(Event::Release()),
|
||||
i if i == (EventType::PING as u8) => Ok(Event::Ping()),
|
||||
i if i == (EventType::PONG as u8) => Ok(Event::Pong()),
|
||||
_ => Err(Box::new(ProtocolError {
|
||||
i if i == (EventType::Pointer as u8) => Ok(Event::Pointer(value.try_into()?)),
|
||||
i if i == (EventType::Keyboard as u8) => Ok(Event::Keyboard(value.try_into()?)),
|
||||
i if i == (EventType::Enter as u8) => Ok(Event::Enter()),
|
||||
i if i == (EventType::Leave as u8) => Ok(Event::Leave()),
|
||||
i if i == (EventType::Ping as u8) => Ok(Event::Ping()),
|
||||
i if i == (EventType::Pong as u8) => Ok(Event::Pong()),
|
||||
i if i == (EventType::Disconnect as u8) => Ok(Event::Disconnect()),
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid event_id {}", event_id),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for &PointerEvent {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let id = vec![self.event_type() as u8];
|
||||
let data = match self {
|
||||
impl From<&PointerEvent> for Vec<u8> {
|
||||
fn from(event: &PointerEvent) -> Self {
|
||||
let id = vec![event.event_type() as u8];
|
||||
let data = match event {
|
||||
PointerEvent::Motion {
|
||||
time,
|
||||
relative_x,
|
||||
@@ -244,7 +273,7 @@ impl Into<Vec<u8>> for &PointerEvent {
|
||||
let time = time.to_be_bytes();
|
||||
let relative_x = relative_x.to_be_bytes();
|
||||
let relative_y = relative_y.to_be_bytes();
|
||||
vec![&time[..], &relative_x[..], &relative_y[..]].concat()
|
||||
[&time[..], &relative_x[..], &relative_y[..]].concat()
|
||||
}
|
||||
PointerEvent::Button {
|
||||
time,
|
||||
@@ -254,26 +283,26 @@ impl Into<Vec<u8>> for &PointerEvent {
|
||||
let time = time.to_be_bytes();
|
||||
let button = button.to_be_bytes();
|
||||
let state = state.to_be_bytes();
|
||||
vec![&time[..], &button[..], &state[..]].concat()
|
||||
[&time[..], &button[..], &state[..]].concat()
|
||||
}
|
||||
PointerEvent::Axis { time, axis, value } => {
|
||||
let time = time.to_be_bytes();
|
||||
let axis = axis.to_be_bytes();
|
||||
let value = value.to_be_bytes();
|
||||
vec![&time[..], &axis[..], &value[..]].concat()
|
||||
[&time[..], &axis[..], &value[..]].concat()
|
||||
}
|
||||
PointerEvent::Frame {} => {
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
vec![id, data].concat()
|
||||
[id, data].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
fn try_from(data: Vec<u8>) -> Result<Self> {
|
||||
match data.get(1) {
|
||||
Some(id) => {
|
||||
let event_type = match id.to_owned().try_into() {
|
||||
@@ -281,11 +310,11 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
match event_type {
|
||||
PointerEventType::MOTION => {
|
||||
PointerEventType::Motion => {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
@@ -293,7 +322,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let relative_x = match data.get(6..14) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
@@ -301,7 +330,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let relative_y = match data.get(14..22) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
@@ -312,11 +341,11 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
relative_y,
|
||||
})
|
||||
}
|
||||
PointerEventType::BUTTON => {
|
||||
PointerEventType::Button => {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
@@ -324,7 +353,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let button = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
@@ -332,7 +361,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let state = match data.get(10..14) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
@@ -343,11 +372,11 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
state,
|
||||
})
|
||||
}
|
||||
PointerEventType::AXIS => {
|
||||
PointerEventType::Axis => {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
@@ -355,7 +384,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let axis = match data.get(6) {
|
||||
Some(d) => *d,
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 1 Byte at index 6".into(),
|
||||
}));
|
||||
}
|
||||
@@ -363,32 +392,32 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
let value = match data.get(7..15) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 7".into(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
Ok(Self::Axis { time, axis, value })
|
||||
}
|
||||
PointerEventType::FRAME => Ok(Self::Frame {}),
|
||||
PointerEventType::Frame => Ok(Self::Frame {}),
|
||||
}
|
||||
}
|
||||
None => Err(Box::new(ProtocolError {
|
||||
None => Err(anyhow!(ProtocolError {
|
||||
msg: "Expected an element at index 0".into(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for &KeyboardEvent {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let id = vec![self.event_type() as u8];
|
||||
let data = match self {
|
||||
impl From<&KeyboardEvent> for Vec<u8> {
|
||||
fn from(event: &KeyboardEvent) -> Self {
|
||||
let id = vec![event.event_type() as u8];
|
||||
let data = match event {
|
||||
KeyboardEvent::Key { time, key, state } => {
|
||||
let time = time.to_be_bytes();
|
||||
let key = key.to_be_bytes();
|
||||
let state = state.to_be_bytes();
|
||||
vec![&time[..], &key[..], &state[..]].concat()
|
||||
[&time[..], &key[..], &state[..]].concat()
|
||||
}
|
||||
KeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
@@ -400,7 +429,7 @@ impl Into<Vec<u8>> for &KeyboardEvent {
|
||||
let mods_latched = mods_latched.to_be_bytes();
|
||||
let mods_locked = mods_locked.to_be_bytes();
|
||||
let group = group.to_be_bytes();
|
||||
vec![
|
||||
[
|
||||
&mods_depressed[..],
|
||||
&mods_latched[..],
|
||||
&mods_locked[..],
|
||||
@@ -409,14 +438,14 @@ impl Into<Vec<u8>> for &KeyboardEvent {
|
||||
.concat()
|
||||
}
|
||||
};
|
||||
vec![id, data].concat()
|
||||
[id, data].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
type Error = Box<dyn Error>;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
fn try_from(data: Vec<u8>) -> Result<Self> {
|
||||
match data.get(1) {
|
||||
Some(id) => {
|
||||
let event_type = match id.to_owned().try_into() {
|
||||
@@ -424,11 +453,11 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
match event_type {
|
||||
KeyboardEventType::KEY => {
|
||||
KeyboardEventType::Key => {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
@@ -436,7 +465,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let key = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
@@ -444,18 +473,18 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let state = match data.get(10) {
|
||||
Some(d) => *d,
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 1 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(KeyboardEvent::Key { time, key, state })
|
||||
}
|
||||
KeyboardEventType::MODIFIERS => {
|
||||
KeyboardEventType::Modifiers => {
|
||||
let mods_depressed = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
@@ -463,7 +492,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let mods_latched = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
@@ -471,7 +500,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let mods_locked = match data.get(10..14) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
@@ -479,7 +508,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
let group = match data.get(14..18) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(Box::new(ProtocolError {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 18".into(),
|
||||
}))
|
||||
}
|
||||
@@ -493,7 +522,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Err(Box::new(ProtocolError {
|
||||
None => Err(anyhow!(ProtocolError {
|
||||
msg: "Expected an element at index 0".into(),
|
||||
})),
|
||||
}
|
||||
|
||||
@@ -31,16 +31,16 @@ use crate::{
|
||||
pub mod cli;
|
||||
|
||||
/// gtk frontend
|
||||
#[cfg(all(unix, feature = "gtk"))]
|
||||
#[cfg(feature = "gtk")]
|
||||
pub mod gtk;
|
||||
|
||||
pub fn run_frontend(config: &Config) -> Result<()> {
|
||||
match config.frontend {
|
||||
#[cfg(all(unix, feature = "gtk"))]
|
||||
#[cfg(feature = "gtk")]
|
||||
Frontend::Gtk => {
|
||||
gtk::run();
|
||||
}
|
||||
#[cfg(any(not(feature = "gtk"), not(unix)))]
|
||||
#[cfg(not(feature = "gtk"))]
|
||||
Frontend::Gtk => panic!("gtk frontend requested but feature not enabled!"),
|
||||
Frontend::Cli => {
|
||||
cli::run()?;
|
||||
@@ -108,6 +108,7 @@ pub enum FrontendNotify {
|
||||
NotifyClientDelete(ClientHandle),
|
||||
/// new port, reason of failure (if failed)
|
||||
NotifyPortChange(u16, Option<String>),
|
||||
/// Client State, active
|
||||
Enumerate(Vec<(Client, bool)>),
|
||||
NotifyError(String),
|
||||
}
|
||||
@@ -201,8 +202,6 @@ impl FrontendListener {
|
||||
|
||||
#[cfg(unix)]
|
||||
pub async fn accept(&mut self) -> Result<ReadHalf<UnixStream>> {
|
||||
log::trace!("frontend.accept()");
|
||||
|
||||
let stream = self.listener.accept().await?.0;
|
||||
let (rx, tx) = tokio::io::split(stream);
|
||||
self.tx_streams.push(tx);
|
||||
@@ -229,11 +228,11 @@ impl FrontendListener {
|
||||
// TODO do simultaneously
|
||||
for tx in self.tx_streams.iter_mut() {
|
||||
// write len + payload
|
||||
if let Err(_) = tx.write(&len).await {
|
||||
if tx.write(&len).await.is_err() {
|
||||
keep.push(false);
|
||||
continue;
|
||||
}
|
||||
if let Err(_) = tx.write(payload).await {
|
||||
if tx.write(payload).await.is_err() {
|
||||
keep.push(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,17 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
#[cfg(windows)]
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
use std::{
|
||||
io::{ErrorKind, Read, Write},
|
||||
str::SplitWhitespace,
|
||||
thread,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::net::TcpStream;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use crate::{client::Position, config::DEFAULT_PORT};
|
||||
|
||||
use super::{FrontendEvent, FrontendNotify};
|
||||
|
||||
pub fn run() -> Result<()> {
|
||||
#[cfg(unix)]
|
||||
let socket_path = super::FrontendListener::socket_path()?;
|
||||
|
||||
#[cfg(unix)]
|
||||
let Ok(mut tx) = UnixStream::connect(&socket_path) else {
|
||||
return Err(anyhow!("Could not connect to lan-mouse-socket"));
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let Ok(mut tx) = TcpStream::connect("127.0.0.1:5252".parse::<SocketAddrV4>().unwrap()) else {
|
||||
let Ok(mut tx) = super::wait_for_service() else {
|
||||
return Err(anyhow!("Could not connect to lan-mouse-socket"));
|
||||
};
|
||||
|
||||
@@ -40,7 +25,7 @@ pub fn run() -> Result<()> {
|
||||
loop {
|
||||
let mut buf = String::new();
|
||||
match std::io::stdin().read_line(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(0) => return,
|
||||
Ok(len) => {
|
||||
if let Some(events) = parse_cmd(buf, len) {
|
||||
for event in events.iter() {
|
||||
@@ -63,14 +48,16 @@ pub fn run() -> Result<()> {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("error reading from stdin: {e}");
|
||||
break;
|
||||
if e.kind() != ErrorKind::UnexpectedEof {
|
||||
log::error!("error reading from stdin: {e}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
let writer = thread::Builder::new()
|
||||
let _ = thread::Builder::new()
|
||||
.name("cli-frontend-notify".to_string())
|
||||
.spawn(move || {
|
||||
loop {
|
||||
@@ -139,7 +126,7 @@ pub fn run() -> Result<()> {
|
||||
}
|
||||
})?;
|
||||
match reader.join() {
|
||||
Ok(_) => (),
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let msg = match (e.downcast_ref::<&str>(), e.downcast_ref::<String>()) {
|
||||
(Some(&s), _) => s,
|
||||
@@ -149,17 +136,6 @@ pub fn run() -> Result<()> {
|
||||
log::error!("reader thread paniced: {msg}");
|
||||
}
|
||||
}
|
||||
match writer.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!("writer thread paniced: {msg}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use adw::Application;
|
||||
use gtk::{
|
||||
gdk::Display,
|
||||
gio::{SimpleAction, SimpleActionGroup},
|
||||
glib::{clone, MainContext, Priority},
|
||||
glib::clone,
|
||||
prelude::*,
|
||||
subclass::prelude::ObjectSubclassIsExt,
|
||||
CssProvider, IconTheme,
|
||||
@@ -27,7 +27,17 @@ use super::FrontendNotify;
|
||||
|
||||
pub fn run() -> glib::ExitCode {
|
||||
log::debug!("running gtk frontend");
|
||||
#[cfg(windows)]
|
||||
let ret = std::thread::Builder::new()
|
||||
.stack_size(8 * 1024 * 1024) // https://gitlab.gnome.org/GNOME/gtk/-/commit/52dbb3f372b2c3ea339e879689c1de535ba2c2c3 -> caused crash on windows
|
||||
.name("gtk".into())
|
||||
.spawn(gtk_main)
|
||||
.unwrap()
|
||||
.join()
|
||||
.unwrap();
|
||||
#[cfg(not(windows))]
|
||||
let ret = gtk_main();
|
||||
|
||||
log::debug!("frontend exited");
|
||||
ret
|
||||
}
|
||||
@@ -81,7 +91,7 @@ fn build_ui(app: &Application) {
|
||||
};
|
||||
log::debug!("connected to lan-mouse-socket");
|
||||
|
||||
let (sender, receiver) = MainContext::channel::<FrontendNotify>(Priority::default());
|
||||
let (sender, receiver) = async_channel::bounded(10);
|
||||
|
||||
gio::spawn_blocking(move || {
|
||||
match loop {
|
||||
@@ -105,7 +115,7 @@ fn build_ui(app: &Application) {
|
||||
// parse json
|
||||
let json = str::from_utf8(&buf).unwrap();
|
||||
match serde_json::from_str(json) {
|
||||
Ok(notify) => sender.send(notify).unwrap(),
|
||||
Ok(notify) => sender.send_blocking(notify).unwrap(),
|
||||
Err(e) => log::error!("{e}"),
|
||||
}
|
||||
} {
|
||||
@@ -116,8 +126,9 @@ fn build_ui(app: &Application) {
|
||||
|
||||
let window = Window::new(app);
|
||||
window.imp().stream.borrow_mut().replace(tx);
|
||||
receiver.attach(None, clone!(@weak window => @default-return glib::ControlFlow::Break,
|
||||
move |notify| {
|
||||
glib::spawn_future_local(clone!(@weak window => async move {
|
||||
loop {
|
||||
let notify = receiver.recv().await.unwrap();
|
||||
match notify {
|
||||
FrontendNotify::NotifyClientCreate(client, hostname, port, position) => {
|
||||
window.new_client(client, hostname, port, position, false);
|
||||
@@ -158,9 +169,8 @@ fn build_ui(app: &Application) {
|
||||
window.imp().set_port(port);
|
||||
}
|
||||
}
|
||||
glib::ControlFlow::Continue
|
||||
}
|
||||
));
|
||||
}));
|
||||
|
||||
let action_request_client_update =
|
||||
SimpleAction::new("request-client-update", Some(&u32::static_variant_type()));
|
||||
@@ -175,7 +185,7 @@ fn build_ui(app: &Application) {
|
||||
let index = param.unwrap()
|
||||
.get::<u32>()
|
||||
.unwrap();
|
||||
let Some(client) = window.clients().item(index as u32) else {
|
||||
let Some(client) = window.clients().item(index) else {
|
||||
return;
|
||||
};
|
||||
let client = client.downcast_ref::<ClientObject>().unwrap();
|
||||
|
||||
@@ -63,7 +63,7 @@ impl ClientRow {
|
||||
let port_binding = client_object
|
||||
.bind_property("port", &self.imp().port.get(), "text")
|
||||
.transform_from(|_, v: String| {
|
||||
if v == "" {
|
||||
if v.is_empty() {
|
||||
Some(DEFAULT_PORT as u32)
|
||||
} else {
|
||||
Some(v.parse::<u16>().unwrap_or(DEFAULT_PORT) as u32)
|
||||
|
||||
@@ -85,16 +85,13 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn client_idx(&self, handle: ClientHandle) -> Option<usize> {
|
||||
self.clients()
|
||||
.iter::<ClientObject>()
|
||||
.position(|c| {
|
||||
if let Ok(c) = c {
|
||||
c.handle() == handle
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|p| p as usize)
|
||||
self.clients().iter::<ClientObject>().position(|c| {
|
||||
if let Ok(c) = c {
|
||||
c.handle() == handle
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_client(&self, handle: ClientHandle) {
|
||||
@@ -117,7 +114,7 @@ impl Window {
|
||||
|
||||
pub fn request_port_change(&self) {
|
||||
let port = self.imp().port_entry.get().text().to_string();
|
||||
if let Ok(port) = u16::from_str_radix(port.as_str(), 10) {
|
||||
if let Ok(port) = port.as_str().parse::<u16>() {
|
||||
self.request(FrontendEvent::ChangePort(port));
|
||||
} else {
|
||||
self.request(FrontendEvent::ChangePort(DEFAULT_PORT));
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
os::unix::net::UnixStream,
|
||||
};
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::net::TcpStream;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use adw::subclass::prelude::*;
|
||||
use adw::{
|
||||
@@ -29,7 +31,10 @@ pub struct Window {
|
||||
#[template_child]
|
||||
pub toast_overlay: TemplateChild<ToastOverlay>,
|
||||
pub clients: RefCell<Option<gio::ListStore>>,
|
||||
#[cfg(unix)]
|
||||
pub stream: RefCell<Option<UnixStream>>,
|
||||
#[cfg(windows)]
|
||||
pub stream: RefCell<Option<TcpStream>>,
|
||||
pub port: Cell<u16>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::client::Position;
|
||||
|
||||
pub fn ask_confirmation(default: bool) -> Result<bool, io::Error> {
|
||||
eprint!("{}", if default { " [Y,n] " } else { " [y,N] " });
|
||||
io::stderr().flush()?;
|
||||
let answer = loop {
|
||||
let mut buffer = String::new();
|
||||
io::stdin().read_line(&mut buffer)?;
|
||||
let answer = buffer.to_lowercase();
|
||||
let answer = answer.trim();
|
||||
match answer {
|
||||
"" => break default,
|
||||
"y" => break true,
|
||||
"n" => break false,
|
||||
_ => {
|
||||
eprint!("Enter y for Yes or n for No: ");
|
||||
io::stderr().flush()?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
pub fn ask_position() -> Result<Position, io::Error> {
|
||||
eprint!("Enter position - top (t) | bottom (b) | left(l) | right(r): ");
|
||||
io::stderr().flush()?;
|
||||
let pos = loop {
|
||||
let mut buffer = String::new();
|
||||
io::stdin().read_line(&mut buffer)?;
|
||||
let answer = buffer.to_lowercase();
|
||||
let answer = answer.trim();
|
||||
match answer {
|
||||
"t" | "top" => break Position::Top,
|
||||
"b" | "bottom" => break Position::Bottom,
|
||||
"l" | "left" => break Position::Right,
|
||||
"r" | "right" => break Position::Left,
|
||||
_ => {
|
||||
eprint!("Invalid position: {answer} - enter top (t) | bottom (b) | left(l) | right(r): ");
|
||||
io::stderr().flush()?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
};
|
||||
Ok(pos)
|
||||
}
|
||||
@@ -9,4 +9,4 @@ pub mod producer;
|
||||
|
||||
pub mod backend;
|
||||
pub mod frontend;
|
||||
pub mod ioutils;
|
||||
pub mod scancode;
|
||||
|
||||
46
src/main.rs
46
src/main.rs
@@ -2,15 +2,9 @@ use anyhow::Result;
|
||||
use std::process::{self, Child, Command};
|
||||
|
||||
use env_logger::Env;
|
||||
use lan_mouse::{
|
||||
config::Config,
|
||||
consumer,
|
||||
frontend::{self, FrontendListener},
|
||||
producer,
|
||||
server::Server,
|
||||
};
|
||||
use lan_mouse::{config::Config, frontend, server::Server};
|
||||
|
||||
use tokio::{join, task::LocalSet};
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
pub fn main() {
|
||||
// init logging
|
||||
@@ -42,8 +36,19 @@ pub fn run() -> Result<()> {
|
||||
} else {
|
||||
// otherwise start the service as a child process and
|
||||
// run a frontend
|
||||
start_service()?;
|
||||
let mut service = start_service()?;
|
||||
frontend::run_frontend(&config)?;
|
||||
log::info!("terminating service");
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// on unix we give the service a chance to terminate gracefully
|
||||
let pid = service.id() as libc::pid_t;
|
||||
unsafe {
|
||||
libc::kill(pid, libc::SIGINT);
|
||||
}
|
||||
service.wait()?;
|
||||
}
|
||||
service.kill()?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
@@ -58,27 +63,12 @@ fn run_service(config: &Config) -> Result<()> {
|
||||
|
||||
// run async event loop
|
||||
runtime.block_on(LocalSet::new().run_until(async {
|
||||
// create frontend communication adapter
|
||||
let frontend_adapter = match FrontendListener::new().await {
|
||||
Some(Err(e)) => return Err(e),
|
||||
Some(Ok(f)) => f,
|
||||
None => {
|
||||
// none means some other instance is already running
|
||||
log::info!("service already running, exiting");
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// create event producer and consumer
|
||||
let (producer, consumer) = join!(producer::create(), consumer::create(),);
|
||||
let (producer, consumer) = (producer?, consumer?);
|
||||
|
||||
// create server
|
||||
let mut event_server = Server::new(config, frontend_adapter, consumer, producer).await?;
|
||||
// run main loop
|
||||
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
|
||||
|
||||
// run event loop
|
||||
event_server.run().await?;
|
||||
let server = Server::new(config);
|
||||
server.run().await?;
|
||||
|
||||
log::debug!("service exiting");
|
||||
anyhow::Ok(())
|
||||
}))?;
|
||||
|
||||
110
src/producer.rs
110
src/producer.rs
@@ -1,4 +1,3 @@
|
||||
use anyhow::Result;
|
||||
use std::io;
|
||||
|
||||
use futures_core::Stream;
|
||||
@@ -9,83 +8,54 @@ use crate::{
|
||||
event::Event,
|
||||
};
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
use std::env;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
enum Backend {
|
||||
LayerShell,
|
||||
Libei,
|
||||
X11,
|
||||
}
|
||||
|
||||
pub async fn create() -> Result<Box<dyn EventProducer>> {
|
||||
pub async fn create() -> Box<dyn EventProducer> {
|
||||
#[cfg(target_os = "macos")]
|
||||
return Ok(Box::new(producer::macos::MacOSProducer::new()));
|
||||
match producer::macos::MacOSProducer::new() {
|
||||
Ok(p) => return Box::new(p),
|
||||
Err(e) => log::info!("macos event producer not available: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
return Ok(Box::new(producer::windows::WindowsProducer::new()));
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
let backend = match env::var("XDG_SESSION_TYPE") {
|
||||
Ok(session_type) => match session_type.as_str() {
|
||||
"x11" => {
|
||||
log::info!("XDG_SESSION_TYPE = x11 -> using X11 event producer");
|
||||
Backend::X11
|
||||
}
|
||||
"wayland" => {
|
||||
log::info!("XDG_SESSION_TYPE = wayland -> using wayland event producer");
|
||||
match env::var("XDG_CURRENT_DESKTOP") {
|
||||
Ok(desktop) => match desktop.as_str() {
|
||||
"GNOME" => {
|
||||
log::info!("XDG_CURRENT_DESKTOP = GNOME -> using libei backend");
|
||||
Backend::Libei
|
||||
}
|
||||
d => {
|
||||
log::info!("XDG_CURRENT_DESKTOP = {d} -> using layer_shell backend");
|
||||
Backend::LayerShell
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
log::warn!("XDG_CURRENT_DESKTOP not set! Assuming layer_shell support -> using layer_shell backend");
|
||||
Backend::LayerShell
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!("unknown XDG_SESSION_TYPE"),
|
||||
},
|
||||
Err(_) => {
|
||||
panic!("could not detect session type: XDG_SESSION_TYPE environment variable not set!")
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
match backend {
|
||||
Backend::X11 => {
|
||||
#[cfg(not(feature = "x11"))]
|
||||
panic!("feature x11 not enabled");
|
||||
#[cfg(feature = "x11")]
|
||||
Ok(Box::new(producer::x11::X11Producer::new()))
|
||||
}
|
||||
Backend::LayerShell => {
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
panic!("feature wayland not enabled");
|
||||
#[cfg(feature = "wayland")]
|
||||
Ok(Box::new(producer::wayland::WaylandEventProducer::new()?))
|
||||
}
|
||||
Backend::Libei => {
|
||||
#[cfg(not(feature = "libei"))]
|
||||
panic!("feature libei not enabled");
|
||||
#[cfg(feature = "libei")]
|
||||
Ok(Box::new(producer::libei::LibeiProducer::new()?))
|
||||
}
|
||||
match producer::windows::WindowsProducer::new() {
|
||||
Ok(p) => return Box::new(p),
|
||||
Err(e) => log::info!("windows event producer not available: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
match producer::libei::LibeiProducer::new() {
|
||||
Ok(p) => {
|
||||
log::info!("using libei event producer");
|
||||
return Box::new(p);
|
||||
}
|
||||
Err(e) => log::info!("libei event producer not available: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
match producer::wayland::WaylandEventProducer::new() {
|
||||
Ok(p) => {
|
||||
log::info!("using layer-shell event producer");
|
||||
return Box::new(p);
|
||||
}
|
||||
Err(e) => log::info!("layer_shell event producer not available: {e}"),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
match producer::x11::X11Producer::new() {
|
||||
Ok(p) => {
|
||||
log::info!("using x11 event producer");
|
||||
return Box::new(p);
|
||||
}
|
||||
Err(e) => log::info!("x11 event producer not available: {e}"),
|
||||
}
|
||||
|
||||
log::error!("falling back to dummy event producer");
|
||||
Box::new(producer::dummy::DummyProducer::new())
|
||||
}
|
||||
|
||||
pub trait EventProducer: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin {
|
||||
/// notify event producer of configuration changes
|
||||
fn notify(&mut self, event: ClientEvent);
|
||||
fn notify(&mut self, event: ClientEvent) -> io::Result<()>;
|
||||
|
||||
/// release mouse
|
||||
fn release(&mut self);
|
||||
fn release(&mut self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
698
src/scancode.rs
Normal file
698
src/scancode.rs
Normal file
@@ -0,0 +1,698 @@
|
||||
/*
|
||||
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
|
||||
*/
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Windows {
|
||||
Shutdown = 0xE05E,
|
||||
SystemSleep = 0xE05F,
|
||||
SystemWakeUp = 0xE063,
|
||||
ErrorRollOver = 0x00FF,
|
||||
KeyA = 0x001E,
|
||||
KeyB = 0x0030,
|
||||
KeyC = 0x002E,
|
||||
KeyD = 0x0020,
|
||||
KeyE = 0x0012,
|
||||
KeyF = 0x0021,
|
||||
KeyG = 0x0022,
|
||||
KeyH = 0x0023,
|
||||
KeyI = 0x0017,
|
||||
KeyJ = 0x0024,
|
||||
KeyK = 0x0025,
|
||||
KeyL = 0x0026,
|
||||
KeyM = 0x0032,
|
||||
KeyN = 0x0031,
|
||||
KeyO = 0x0018,
|
||||
KeyP = 0x0019,
|
||||
KeyQ = 0x0010,
|
||||
KeyR = 0x0013,
|
||||
KeyS = 0x001F,
|
||||
KeyT = 0x0014,
|
||||
KeyU = 0x0016,
|
||||
KeyV = 0x002F,
|
||||
KeyW = 0x0011,
|
||||
KeyX = 0x002D,
|
||||
KeyY = 0x0015,
|
||||
KeyZ = 0x002C,
|
||||
Key1 = 0x0002,
|
||||
Key2 = 0x0003,
|
||||
Key3 = 0x0004,
|
||||
Key4 = 0x0005,
|
||||
Key5 = 0x0006,
|
||||
Key6 = 0x0007,
|
||||
Key7 = 0x0008,
|
||||
Key8 = 0x0009,
|
||||
Key9 = 0x000A,
|
||||
Key0 = 0x000B,
|
||||
KeyEnter = 0x001C,
|
||||
KeyEsc = 0x0001,
|
||||
KeyDelete = 0x000E,
|
||||
KeyTab = 0x000F,
|
||||
KeySpace = 0x0039,
|
||||
KeyMinus = 0x000C,
|
||||
KeyEqual = 0x000D,
|
||||
KeyLeftBrace = 0x001A,
|
||||
KeyRightBrace = 0x001B,
|
||||
KeyBackslash = 0x002B,
|
||||
KeySemiColon = 0x0027,
|
||||
KeyApostrophe = 0x0028,
|
||||
KeyGrave = 0x0029,
|
||||
KeyComma = 0x0033,
|
||||
KeyDot = 0x0034,
|
||||
KeySlash = 0x0035,
|
||||
KeyCapsLock = 0x003A,
|
||||
KeyF1 = 0x003B,
|
||||
KeyF2 = 0x003C,
|
||||
KeyF3 = 0x003D,
|
||||
KeyF4 = 0x003E,
|
||||
KeyF5 = 0x003F,
|
||||
KeyF6 = 0x0040,
|
||||
KeyF7 = 0x0041,
|
||||
KeyF8 = 0x0042,
|
||||
KeyF9 = 0x0043,
|
||||
KeyF10 = 0x0044,
|
||||
KeyF11 = 0x0057,
|
||||
KeyF12 = 0x0058,
|
||||
KeyPrintScreen = 0xE037,
|
||||
KeyScrollLock = 0x0046,
|
||||
KeyPause = 0xE11D45,
|
||||
KeyInsert = 0xE052,
|
||||
KeyHome = 0xE047,
|
||||
KeyPageUp = 0xE049,
|
||||
KeyDeleteForward = 0xE053,
|
||||
KeyEnd = 0xE04F,
|
||||
KeyPageDown = 0xE051,
|
||||
KeyRight = 0xE04D,
|
||||
KeyLeft = 0xE04B,
|
||||
KeyDown = 0xE050,
|
||||
KeyUp = 0xE048,
|
||||
KeypadNumLock = 0x0045,
|
||||
KeypadSlash = 0xE035,
|
||||
KeypadStar = 0x0037,
|
||||
KeypadDash = 0x004A,
|
||||
KeypadPlus = 0x004E,
|
||||
KeypadEnter = 0xE01C,
|
||||
Keypad1End = 0x004F,
|
||||
Keypad2DownArrow = 0x0050,
|
||||
Keypad3PageDn = 0x0051,
|
||||
Keypad4LeftArrow = 0x004B,
|
||||
Keypad5 = 0x004C,
|
||||
Keypad6RightArrow = 0x004D,
|
||||
Keypad7Home = 0x0047,
|
||||
Keypad8UpArrow = 0x0048,
|
||||
Keypad9PageUp = 0x0049,
|
||||
Keypad0Insert = 0x0052,
|
||||
KeypadDot = 0x0053,
|
||||
KeyNonUSSlashBar = 0x0056,
|
||||
KeyApplication = 0xE05D,
|
||||
KeypadEquals = 0x0059,
|
||||
KeyF13 = 0x0064,
|
||||
KeyF14 = 0x0065,
|
||||
KeyF15 = 0x0066,
|
||||
KeyF16 = 0x0067,
|
||||
KeyF17 = 0x0068,
|
||||
KeyF18 = 0x0069,
|
||||
KeyF19 = 0x006A,
|
||||
KeyF20 = 0x006B,
|
||||
KeyF21 = 0x006C,
|
||||
KeyF22 = 0x006D,
|
||||
KeyF23 = 0x006E,
|
||||
KeyF24 = 0x0076,
|
||||
KeypadComma = 0x007E,
|
||||
KeyInternational1 = 0x0073,
|
||||
KeyInternational2 = 0x0070,
|
||||
KeyInternational3 = 0x007D,
|
||||
#[allow(dead_code)]
|
||||
KeyInternational4 = 0x0079, // FIXME unused
|
||||
#[allow(dead_code)]
|
||||
KeyInternational5 = 0x007B, // FIXME unused
|
||||
// KeyInternational6 = 0x005C,
|
||||
KeyLANG1 = 0x0072,
|
||||
KeyLANG2 = 0x0071,
|
||||
KeyLANG3 = 0x0078,
|
||||
KeyLANG4 = 0x0077,
|
||||
// KeyLANG5 = 0x0076,
|
||||
KeyLeftCtrl = 0x001D,
|
||||
KeyLeftShift = 0x002A,
|
||||
KeyLeftAlt = 0x0038,
|
||||
KeyLeftGUI = 0xE05B,
|
||||
KeyRightCtrl = 0xE01D,
|
||||
KeyRightShift = 0x0036,
|
||||
KeyRightAlt = 0xE038,
|
||||
KeyRightGUI = 0xE05C,
|
||||
KeyScanNextTrack = 0xE019,
|
||||
KeyScanPreviousTrack = 0xE010,
|
||||
KeyStop = 0xE024,
|
||||
KeyPlayPause = 0xE022,
|
||||
KeyMute = 0xE020,
|
||||
KeyVolumeUp = 0xE030,
|
||||
KeyVolumeDown = 0xE02E,
|
||||
#[allow(dead_code)]
|
||||
ALConsumerControlConfiguration = 0xE06D, // TODO Unused
|
||||
ALEmailReader = 0xE06C,
|
||||
ALCalculator = 0xE021,
|
||||
ALLocalMachineBrowser = 0xE06B,
|
||||
ACSearch = 0xE065,
|
||||
ACHome = 0xE032,
|
||||
ACBack = 0xE06A,
|
||||
ACForward = 0xE069,
|
||||
ACStop = 0xE068,
|
||||
ACRefresh = 0xE067,
|
||||
ACBookmarks = 0xE066,
|
||||
}
|
||||
|
||||
/*
|
||||
* https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
|
||||
*/
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Linux {
|
||||
KeyReserved = 0,
|
||||
KeyEsc = 1,
|
||||
Key1 = 2,
|
||||
Key2 = 3,
|
||||
Key3 = 4,
|
||||
Key4 = 5,
|
||||
Key5 = 6,
|
||||
Key6 = 7,
|
||||
Key7 = 8,
|
||||
Key8 = 9,
|
||||
Key9 = 10,
|
||||
Key0 = 11,
|
||||
KeyMinus = 12,
|
||||
KeyEqual = 13,
|
||||
KeyBackspace = 14,
|
||||
KeyTab = 15,
|
||||
KeyQ = 16,
|
||||
KeyW = 17,
|
||||
KeyE = 18,
|
||||
KeyR = 19,
|
||||
KeyT = 20,
|
||||
KeyY = 21,
|
||||
KeyU = 22,
|
||||
KeyI = 23,
|
||||
KeyO = 24,
|
||||
KeyP = 25,
|
||||
KeyLeftbrace = 26,
|
||||
KeyRightbrace = 27,
|
||||
KeyEnter = 28,
|
||||
KeyLeftCtrl = 29,
|
||||
KeyA = 30,
|
||||
KeyS = 31,
|
||||
KeyD = 32,
|
||||
KeyF = 33,
|
||||
KeyG = 34,
|
||||
KeyH = 35,
|
||||
KeyJ = 36,
|
||||
KeyK = 37,
|
||||
KeyL = 38,
|
||||
KeySemicolon = 39,
|
||||
KeyApostrophe = 40,
|
||||
KeyGrave = 41,
|
||||
KeyLeftshift = 42,
|
||||
KeyBackslash = 43,
|
||||
KeyZ = 44,
|
||||
KeyX = 45,
|
||||
KeyC = 46,
|
||||
KeyV = 47,
|
||||
KeyB = 48,
|
||||
KeyN = 49,
|
||||
KeyM = 50,
|
||||
KeyComma = 51,
|
||||
KeyDot = 52,
|
||||
KeySlash = 53,
|
||||
KeyRightShift = 54,
|
||||
KeyKpAsterisk = 55,
|
||||
KeyLeftalt = 56,
|
||||
KeySpace = 57,
|
||||
KeyCapsLock = 58,
|
||||
KeyF1 = 59,
|
||||
KeyF2 = 60,
|
||||
KeyF3 = 61,
|
||||
KeyF4 = 62,
|
||||
KeyF5 = 63,
|
||||
KeyF6 = 64,
|
||||
KeyF7 = 65,
|
||||
KeyF8 = 66,
|
||||
KeyF9 = 67,
|
||||
KeyF10 = 68,
|
||||
KeyNumlock = 69,
|
||||
KeyScrollLock = 70,
|
||||
KeyKp7 = 71,
|
||||
KeyKp8 = 72,
|
||||
KeyKp9 = 73,
|
||||
KeyKpMinus = 74,
|
||||
KeyKp4 = 75,
|
||||
KeyKp5 = 76,
|
||||
KeyKp6 = 77,
|
||||
KeyKpplus = 78,
|
||||
KeyKp1 = 79,
|
||||
KeyKp2 = 80,
|
||||
KeyKp3 = 81,
|
||||
KeyKp0 = 82,
|
||||
KeyKpDot = 83,
|
||||
Invalid = 84,
|
||||
KeyZenkakuhankaku = 85,
|
||||
Key102nd = 86,
|
||||
KeyF11 = 87,
|
||||
KeyF12 = 88,
|
||||
KeyRo = 89,
|
||||
KeyKatakana = 90,
|
||||
KeyHiragana = 91,
|
||||
KeyHenkan = 92,
|
||||
KeyKatakanahiragana = 93,
|
||||
KeyMuhenkan = 94,
|
||||
KeyKpJpComma = 95,
|
||||
KeyKpEnter = 96,
|
||||
KeyRightCtrl = 97,
|
||||
KeyKpslash = 98,
|
||||
KeySysrq = 99,
|
||||
KeyRightalt = 100,
|
||||
KeyLinefeed = 101,
|
||||
KeyHome = 102,
|
||||
KeyUp = 103,
|
||||
KeyPageup = 104,
|
||||
KeyLeft = 105,
|
||||
KeyRight = 106,
|
||||
KeyEnd = 107,
|
||||
KeyDown = 108,
|
||||
KeyPagedown = 109,
|
||||
KeyInsert = 110,
|
||||
KeyDelete = 111,
|
||||
KeyMacro = 112,
|
||||
KeyMute = 113,
|
||||
KeyVolumeDown = 114,
|
||||
KeyVolumeUp = 115,
|
||||
KeyPower = 116, /* SC System Power Down */
|
||||
KeyKpequal = 117,
|
||||
KeyKpplusminus = 118,
|
||||
KeyPause = 119,
|
||||
KeyScale = 120, /* AL Compiz Scale (Expose) */
|
||||
KeyKpcomma = 121,
|
||||
KeyHangeul = 122,
|
||||
// KEY_HANGUEL = KeyHangeul,
|
||||
KeyHanja = 123,
|
||||
KeyYen = 124,
|
||||
KeyLeftmeta = 125,
|
||||
KeyRightmeta = 126,
|
||||
KeyCompose = 127,
|
||||
KeyStop = 128, /* AC Stop */
|
||||
KeyAgain = 129,
|
||||
KeyProps = 130, /* AC Properties */
|
||||
KeyUndo = 131, /* AC Undo */
|
||||
KeyFront = 132,
|
||||
KeyCopy = 133, /* AC Copy */
|
||||
KeyOpen = 134, /* AC Open */
|
||||
KeyPaste = 135, /* AC Paste */
|
||||
KeyFind = 136, /* AC Search */
|
||||
KeyCut = 137, /* AC Cut */
|
||||
KeyHelp = 138, /* AL Integrated Help Center */
|
||||
KeyMenu = 139, /* Menu (show menu) */
|
||||
KeyCalc = 140, /* AL Calculator */
|
||||
KeySetup = 141,
|
||||
KeySleep = 142, /* SC System Sleep */
|
||||
KeyWakeup = 143, /* System Wake Up */
|
||||
KeyFile = 144, /* AL Local Machine Browser */
|
||||
KeySendfile = 145,
|
||||
KeyDeletefile = 146,
|
||||
KeyXfer = 147,
|
||||
KeyProg1 = 148,
|
||||
KeyProg2 = 149,
|
||||
KeyWww = 150, /* AL Internet Browser */
|
||||
KeyMsdos = 151,
|
||||
KeyCoffee = 152, /* AL Terminal Lock/Screensaver */
|
||||
// KEY_SCREENLOCK = KeyCoffee,
|
||||
KeyRotateDisplay = 153, /* Display orientation for e.g. tablets */
|
||||
// KEY_DIRECTION = KeyRotateDisplay,
|
||||
KeyCyclewindows = 154,
|
||||
KeyMail = 155,
|
||||
KeyBookmarks = 156, /* AC Bookmarks */
|
||||
KeyComputer = 157,
|
||||
KeyBack = 158, /* AC Back */
|
||||
KeyForward = 159, /* AC Forward */
|
||||
KeyClosecd = 160,
|
||||
KeyEjectcd = 161,
|
||||
KeyEjectclosecd = 162,
|
||||
KeyNextsong = 163,
|
||||
KeyPlaypause = 164,
|
||||
KeyPrevioussong = 165,
|
||||
KeyStopcd = 166,
|
||||
KeyRecord = 167,
|
||||
KeyRewind = 168,
|
||||
KeyPhone = 169, /* Media Select Telephone */
|
||||
KeyIso = 170,
|
||||
KeyConfig = 171, /* AL Consumer Control Configuration */
|
||||
KeyHomepage = 172, /* AC Home */
|
||||
KeyRefresh = 173, /* AC Refresh */
|
||||
KeyExit = 174, /* AC Exit */
|
||||
KeyMove = 175,
|
||||
KeyEdit = 176,
|
||||
KeyScrollup = 177,
|
||||
KeyScrolldown = 178,
|
||||
KeyKpleftparen = 179,
|
||||
KeyKprightparen = 180,
|
||||
KeyNew = 181, /* AC New */
|
||||
KeyRedo = 182, /* AC Redo/Repeat */
|
||||
KeyF13 = 183,
|
||||
KeyF14 = 184,
|
||||
KeyF15 = 185,
|
||||
KeyF16 = 186,
|
||||
KeyF17 = 187,
|
||||
KeyF18 = 188,
|
||||
KeyF19 = 189,
|
||||
KeyF20 = 190,
|
||||
KeyF21 = 191,
|
||||
KeyF22 = 192,
|
||||
KeyF23 = 193,
|
||||
KeyF24 = 194,
|
||||
Invalid1 = 195,
|
||||
Invalid2 = 196,
|
||||
Invalid3 = 197,
|
||||
Invalid4 = 198,
|
||||
Invalid5 = 199,
|
||||
KeyPlaycd = 200,
|
||||
KeyPausecd = 201,
|
||||
KeyProg3 = 202,
|
||||
KeyProg4 = 203,
|
||||
KeyAllApplications = 204, /* AC Desktop Show All Applications */
|
||||
// KEY_DASHBOARD = KeyAllApplications,
|
||||
KeySuspend = 205,
|
||||
KeyClose = 206, /* AC Close */
|
||||
KeyPlay = 207,
|
||||
KeyFastforward = 208,
|
||||
KeyBassboost = 209,
|
||||
KeyPrint = 210, /* AC Print */
|
||||
KeyHp = 211,
|
||||
KeyCamera = 212,
|
||||
KeySound = 213,
|
||||
KeyQuestion = 214,
|
||||
KeyEmail = 215,
|
||||
KeyChat = 216,
|
||||
KeySearch = 217,
|
||||
KeyConnect = 218,
|
||||
KeyFinance = 219, /* AL Checkbook/Finance */
|
||||
KeySport = 220,
|
||||
KeyShop = 221,
|
||||
KeyAlterase = 222,
|
||||
KeyCancel = 223, /* AC Cancel */
|
||||
KeyBrightnessdown = 224,
|
||||
KeyBrightnessup = 225,
|
||||
KeyMedia = 226,
|
||||
KeySwitchvideomode = 227, /* Cycle between available video, outputs (Monitor/LCD/TV-out/etc) */
|
||||
KeyKbdillumtoggle = 228,
|
||||
KeyKbdillumdown = 229,
|
||||
KeyKbdillumup = 230,
|
||||
KeySend = 231, /* AC Send */
|
||||
KeyReply = 232, /* AC Reply */
|
||||
KeyForwardmail = 233, /* AC Forward Msg */
|
||||
KeySave = 234, /* AC Save */
|
||||
KeyDocuments = 235,
|
||||
KeyBattery = 236,
|
||||
KeyBluetooth = 237,
|
||||
KeyWlan = 238,
|
||||
KeyUwb = 239,
|
||||
KeyUnknown = 240,
|
||||
KeyVideoNext = 241, /* drive next video source */
|
||||
KeyVideoPrev = 242, /* drive previous video source */
|
||||
KeyBrightnessCycle = 243, /* brightness up, after max is min */
|
||||
KeyBrightnessAuto = 244, /* Set Auto Brightness: manual, brightness control is off, rely on ambient */
|
||||
// KEY_BRIGHTNESS_ZERO=KeyBrightnessAuto,
|
||||
KeyDisplayOff = 245, /* display device to off state */
|
||||
KeyWwan = 246, /* Wireless WAN (LTE, UMTS, GSM, etc.) */
|
||||
// KEY_WIMAX = KeyWwan,
|
||||
KeyRfkill = 247, /* Key that controls all radios */
|
||||
KeyMicmute = 248, /* Mute / unmute the microphone */
|
||||
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 = ();
|
||||
|
||||
fn try_from(value: Linux) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Linux::KeyReserved => Err(()),
|
||||
Linux::KeyEsc => Ok(Self::KeyEsc),
|
||||
Linux::Key1 => Ok(Self::Key1),
|
||||
Linux::Key2 => Ok(Self::Key2),
|
||||
Linux::Key3 => Ok(Self::Key3),
|
||||
Linux::Key4 => Ok(Self::Key4),
|
||||
Linux::Key5 => Ok(Self::Key5),
|
||||
Linux::Key6 => Ok(Self::Key6),
|
||||
Linux::Key7 => Ok(Self::Key7),
|
||||
Linux::Key8 => Ok(Self::Key8),
|
||||
Linux::Key9 => Ok(Self::Key9),
|
||||
Linux::Key0 => Ok(Self::Key0),
|
||||
Linux::KeyMinus => Ok(Self::KeyMinus),
|
||||
Linux::KeyEqual => Ok(Self::KeyEqual),
|
||||
Linux::KeyBackspace => Ok(Self::KeyDelete),
|
||||
Linux::KeyTab => Ok(Self::KeyTab),
|
||||
Linux::KeyQ => Ok(Self::KeyQ),
|
||||
Linux::KeyW => Ok(Self::KeyW),
|
||||
Linux::KeyE => Ok(Self::KeyE),
|
||||
Linux::KeyR => Ok(Self::KeyR),
|
||||
Linux::KeyT => Ok(Self::KeyT),
|
||||
Linux::KeyY => Ok(Self::KeyY),
|
||||
Linux::KeyU => Ok(Self::KeyU),
|
||||
Linux::KeyI => Ok(Self::KeyI),
|
||||
Linux::KeyO => Ok(Self::KeyO),
|
||||
Linux::KeyP => Ok(Self::KeyP),
|
||||
Linux::KeyLeftbrace => Ok(Self::KeyLeftBrace),
|
||||
Linux::KeyRightbrace => Ok(Self::KeyRightBrace),
|
||||
Linux::KeyEnter => Ok(Self::KeyEnter),
|
||||
Linux::KeyLeftCtrl => Ok(Self::KeyLeftCtrl),
|
||||
Linux::KeyA => Ok(Self::KeyA),
|
||||
Linux::KeyS => Ok(Self::KeyS),
|
||||
Linux::KeyD => Ok(Self::KeyD),
|
||||
Linux::KeyF => Ok(Self::KeyF),
|
||||
Linux::KeyG => Ok(Self::KeyG),
|
||||
Linux::KeyH => Ok(Self::KeyH),
|
||||
Linux::KeyJ => Ok(Self::KeyJ),
|
||||
Linux::KeyK => Ok(Self::KeyK),
|
||||
Linux::KeyL => Ok(Self::KeyL),
|
||||
Linux::KeySemicolon => Ok(Self::KeySemiColon),
|
||||
Linux::KeyApostrophe => Ok(Self::KeyApostrophe),
|
||||
Linux::KeyGrave => Ok(Self::KeyGrave),
|
||||
Linux::KeyLeftshift => Ok(Self::KeyLeftShift),
|
||||
Linux::KeyBackslash => Ok(Self::KeyBackslash),
|
||||
Linux::KeyZ => Ok(Self::KeyZ),
|
||||
Linux::KeyX => Ok(Self::KeyX),
|
||||
Linux::KeyC => Ok(Self::KeyC),
|
||||
Linux::KeyV => Ok(Self::KeyV),
|
||||
Linux::KeyB => Ok(Self::KeyB),
|
||||
Linux::KeyN => Ok(Self::KeyN),
|
||||
Linux::KeyM => Ok(Self::KeyM),
|
||||
Linux::KeyComma => Ok(Self::KeyComma),
|
||||
Linux::KeyDot => Ok(Self::KeyDot),
|
||||
Linux::KeySlash => Ok(Self::KeySlash),
|
||||
Linux::KeyRightShift => Ok(Self::KeyRightShift),
|
||||
Linux::KeyKpAsterisk => Ok(Self::KeypadStar),
|
||||
Linux::KeyLeftalt => Ok(Self::KeyLeftAlt),
|
||||
Linux::KeySpace => Ok(Self::KeySpace),
|
||||
Linux::KeyCapsLock => Ok(Self::KeyCapsLock),
|
||||
Linux::KeyF1 => Ok(Self::KeyF1),
|
||||
Linux::KeyF2 => Ok(Self::KeyF2),
|
||||
Linux::KeyF3 => Ok(Self::KeyF3),
|
||||
Linux::KeyF4 => Ok(Self::KeyF4),
|
||||
Linux::KeyF5 => Ok(Self::KeyF5),
|
||||
Linux::KeyF6 => Ok(Self::KeyF6),
|
||||
Linux::KeyF7 => Ok(Self::KeyF7),
|
||||
Linux::KeyF8 => Ok(Self::KeyF8),
|
||||
Linux::KeyF9 => Ok(Self::KeyF9),
|
||||
Linux::KeyF10 => Ok(Self::KeyF10),
|
||||
Linux::KeyNumlock => Ok(Self::KeypadNumLock),
|
||||
Linux::KeyScrollLock => Ok(Self::KeyScrollLock),
|
||||
Linux::KeyKp7 => Ok(Self::Keypad7Home),
|
||||
Linux::KeyKp8 => Ok(Self::Keypad8UpArrow),
|
||||
Linux::KeyKp9 => Ok(Self::Keypad9PageUp),
|
||||
Linux::KeyKpMinus => Ok(Self::KeypadDash),
|
||||
Linux::KeyKp4 => Ok(Self::Keypad4LeftArrow),
|
||||
Linux::KeyKp5 => Ok(Self::Keypad5),
|
||||
Linux::KeyKp6 => Ok(Self::Keypad6RightArrow),
|
||||
Linux::KeyKpplus => Ok(Self::KeypadPlus),
|
||||
Linux::KeyKp1 => Ok(Self::Keypad1End),
|
||||
Linux::KeyKp2 => Ok(Self::Keypad2DownArrow),
|
||||
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::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::KeyKpJpComma => Ok(Self::KeypadComma),
|
||||
Linux::KeyKpEnter => Ok(Self::KeypadEnter),
|
||||
Linux::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
||||
Linux::KeyKpslash => Ok(Self::KeypadSlash),
|
||||
Linux::KeySysrq => Ok(Self::KeyPrintScreen), // TODO Windows does not have Sysrq, right?
|
||||
Linux::KeyRightalt => Ok(Self::KeyRightAlt),
|
||||
Linux::KeyLinefeed => Ok(Self::KeyEnter), // TODO unsure
|
||||
Linux::KeyHome => Ok(Self::KeyHome),
|
||||
Linux::KeyUp => Ok(Self::KeyUp),
|
||||
Linux::KeyPageup => Ok(Self::KeyPageUp),
|
||||
Linux::KeyLeft => Ok(Self::KeyLeft),
|
||||
Linux::KeyRight => Ok(Self::KeyRight),
|
||||
Linux::KeyEnd => Ok(Self::KeyEnd),
|
||||
Linux::KeyDown => Ok(Self::KeyDown),
|
||||
Linux::KeyPagedown => Ok(Self::KeyPageDown),
|
||||
Linux::KeyInsert => Ok(Self::KeyInsert),
|
||||
Linux::KeyDelete => Ok(Self::KeyDeleteForward),
|
||||
Linux::KeyMacro => Err(()), // TODO
|
||||
Linux::KeyMute => Ok(Self::KeyMute),
|
||||
Linux::KeyVolumeDown => Ok(Self::KeyVolumeDown),
|
||||
Linux::KeyVolumeUp => Ok(Self::KeyVolumeUp),
|
||||
Linux::KeyPower => Ok(Self::Shutdown),
|
||||
Linux::KeyKpequal => Ok(Self::KeypadEquals),
|
||||
Linux::KeyKpplusminus => Ok(Self::KeypadPlus),
|
||||
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::KeyLeftmeta => Ok(Self::KeyLeftGUI),
|
||||
Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
|
||||
Linux::KeyCompose => Ok(Self::KeyApplication),
|
||||
Linux::KeyStop => Ok(Self::ACStop),
|
||||
Linux::KeyAgain => Err(()),
|
||||
Linux::KeyProps => Err(()),
|
||||
Linux::KeyUndo => Err(()),
|
||||
Linux::KeyFront => Err(()),
|
||||
Linux::KeyCopy => Err(()),
|
||||
Linux::KeyOpen => Err(()),
|
||||
Linux::KeyPaste => Err(()),
|
||||
Linux::KeyFind => Ok(Self::ACSearch),
|
||||
Linux::KeyCut => Err(()),
|
||||
Linux::KeyHelp => Ok(Self::KeyF1), // AL Integrated Help Center?
|
||||
Linux::KeyMenu => Ok(Self::KeyApplication),
|
||||
Linux::KeyCalc => Ok(Self::ALCalculator),
|
||||
Linux::KeySetup => Err(()),
|
||||
Linux::KeySleep => Ok(Self::SystemSleep),
|
||||
Linux::KeyWakeup => Ok(Self::SystemWakeUp),
|
||||
Linux::KeyFile => Ok(Self::ALLocalMachineBrowser),
|
||||
Linux::KeySendfile => Err(()),
|
||||
Linux::KeyDeletefile => Err(()),
|
||||
Linux::KeyXfer => Err(()),
|
||||
Linux::KeyProg1 => Err(()),
|
||||
Linux::KeyProg2 => Err(()),
|
||||
Linux::KeyWww => Ok(Self::ACSearch), // TODO unsure
|
||||
Linux::KeyMsdos => Err(()),
|
||||
Linux::KeyCoffee => Err(()),
|
||||
Linux::KeyRotateDisplay => Err(()),
|
||||
Linux::KeyCyclewindows => Err(()),
|
||||
Linux::KeyMail => Ok(Self::ALEmailReader),
|
||||
Linux::KeyBookmarks => Ok(Self::ACBookmarks),
|
||||
Linux::KeyComputer => Ok(Self::ACHome),
|
||||
Linux::KeyBack => Ok(Self::ACBack),
|
||||
Linux::KeyForward => Ok(Self::ACForward),
|
||||
Linux::KeyClosecd => Err(()),
|
||||
Linux::KeyEjectcd => Err(()),
|
||||
Linux::KeyEjectclosecd => Err(()),
|
||||
Linux::KeyNextsong => Ok(Self::KeyScanNextTrack),
|
||||
Linux::KeyPlaypause => Ok(Self::KeyPlayPause),
|
||||
Linux::KeyPrevioussong => Ok(Self::KeyScanPreviousTrack),
|
||||
Linux::KeyStopcd => Ok(Self::KeyStop),
|
||||
Linux::KeyRecord => Err(()),
|
||||
Linux::KeyRewind => Err(()),
|
||||
Linux::KeyPhone => Err(()),
|
||||
Linux::KeyIso => Err(()),
|
||||
Linux::KeyConfig => Err(()),
|
||||
Linux::KeyHomepage => Ok(Self::ACHome),
|
||||
Linux::KeyRefresh => Ok(Self::ACRefresh),
|
||||
Linux::KeyExit => Err(()),
|
||||
Linux::KeyMove => Err(()),
|
||||
Linux::KeyEdit => Err(()),
|
||||
Linux::KeyScrollup => Err(()),
|
||||
Linux::KeyScrolldown => Err(()),
|
||||
Linux::KeyKpleftparen => Err(()),
|
||||
Linux::KeyKprightparen => Err(()),
|
||||
Linux::KeyNew => Err(()),
|
||||
Linux::KeyRedo => Err(()),
|
||||
Linux::KeyF13 => Ok(Self::KeyF13),
|
||||
Linux::KeyF14 => Ok(Self::KeyF14),
|
||||
Linux::KeyF15 => Ok(Self::KeyF15),
|
||||
Linux::KeyF16 => Ok(Self::KeyF16),
|
||||
Linux::KeyF17 => Ok(Self::KeyF17),
|
||||
Linux::KeyF18 => Ok(Self::KeyF18),
|
||||
Linux::KeyF19 => Ok(Self::KeyF19),
|
||||
Linux::KeyF20 => Ok(Self::KeyF20),
|
||||
Linux::KeyF21 => Ok(Self::KeyF21),
|
||||
Linux::KeyF22 => Ok(Self::KeyF22),
|
||||
Linux::KeyF23 => Ok(Self::KeyF23),
|
||||
Linux::KeyF24 => Ok(Self::KeyF24),
|
||||
Linux::KeyPlaycd => Err(()),
|
||||
Linux::KeyPausecd => Err(()),
|
||||
Linux::KeyProg3 => Err(()),
|
||||
Linux::KeyProg4 => Err(()),
|
||||
Linux::KeyAllApplications => Err(()),
|
||||
Linux::KeySuspend => Err(()),
|
||||
Linux::KeyClose => Err(()),
|
||||
Linux::KeyPlay => Err(()),
|
||||
Linux::KeyFastforward => Err(()),
|
||||
Linux::KeyBassboost => Err(()),
|
||||
Linux::KeyPrint => Err(()),
|
||||
Linux::KeyHp => Err(()),
|
||||
Linux::KeyCamera => Err(()),
|
||||
Linux::KeySound => Err(()),
|
||||
Linux::KeyQuestion => Err(()),
|
||||
Linux::KeyEmail => Err(()),
|
||||
Linux::KeyChat => Err(()),
|
||||
Linux::KeySearch => Err(()),
|
||||
Linux::KeyConnect => Err(()),
|
||||
Linux::KeyFinance => Err(()),
|
||||
Linux::KeySport => Err(()),
|
||||
Linux::KeyShop => Err(()),
|
||||
Linux::KeyAlterase => Err(()),
|
||||
Linux::KeyCancel => Err(()),
|
||||
Linux::KeyBrightnessdown => Err(()),
|
||||
Linux::KeyBrightnessup => Err(()),
|
||||
Linux::KeyMedia => Err(()),
|
||||
Linux::KeySwitchvideomode => Err(()),
|
||||
Linux::KeyKbdillumtoggle => Err(()),
|
||||
Linux::KeyKbdillumdown => Err(()),
|
||||
Linux::KeyKbdillumup => Err(()),
|
||||
Linux::KeySend => Err(()),
|
||||
Linux::KeyReply => Err(()),
|
||||
Linux::KeyForwardmail => Err(()),
|
||||
Linux::KeySave => Err(()),
|
||||
Linux::KeyDocuments => Err(()),
|
||||
Linux::KeyBattery => Err(()),
|
||||
Linux::KeyBluetooth => Err(()),
|
||||
Linux::KeyWlan => Err(()),
|
||||
Linux::KeyUwb => Err(()),
|
||||
Linux::KeyUnknown => Err(()),
|
||||
Linux::KeyVideoNext => Err(()),
|
||||
Linux::KeyVideoPrev => Err(()),
|
||||
Linux::KeyBrightnessCycle => Err(()),
|
||||
Linux::KeyBrightnessAuto => Err(()),
|
||||
Linux::KeyDisplayOff => Err(()),
|
||||
Linux::KeyWwan => Err(()),
|
||||
Linux::KeyRfkill => Err(()),
|
||||
Linux::KeyMicmute => Err(()),
|
||||
Linux::KeyCount => Err(()),
|
||||
Linux::Invalid => Err(()),
|
||||
Linux::Invalid1 => Err(()),
|
||||
Linux::Invalid2 => Err(()),
|
||||
Linux::Invalid3 => Err(()),
|
||||
Linux::Invalid4 => Err(()),
|
||||
Linux::Invalid5 => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
1220
src/server.rs
1220
src/server.rs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user