mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-28 07:30:54 +03:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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:
|
windows-release-build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
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
|
- uses: actions/checkout@v3
|
||||||
- name: Release Build
|
- name: Release Build
|
||||||
run: cargo build --release
|
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
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-windows
|
name: lan-mouse-windows
|
||||||
path: target/release/lan-mouse.exe
|
path: lan-mouse-windows.zip
|
||||||
|
|
||||||
macos-release-build:
|
macos-release-build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
@@ -46,12 +84,14 @@ jobs:
|
|||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: brew install gtk4 libadwaita
|
run: brew install gtk4 libadwaita
|
||||||
- name: Release Build
|
- name: Release Build
|
||||||
run: cargo build --release
|
run: |
|
||||||
|
cargo build --release
|
||||||
|
cp target/release/lan-mouse lan-mouse-macos-intel
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-macos
|
name: lan-mouse-macos
|
||||||
path: target/release/lan-mouse
|
path: lan-mouse-macos-intel
|
||||||
|
|
||||||
pre-release:
|
pre-release:
|
||||||
name: "Pre Release"
|
name: "Pre Release"
|
||||||
@@ -69,5 +109,5 @@ jobs:
|
|||||||
title: "Development Build"
|
title: "Development Build"
|
||||||
files: |
|
files: |
|
||||||
lan-mouse-linux/lan-mouse
|
lan-mouse-linux/lan-mouse
|
||||||
lan-mouse-windows/lan-mouse.exe
|
lan-mouse-macos/lan-mouse-macos-intel
|
||||||
lan-mouse-macos/lan-mouse
|
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
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
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
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -37,15 +41,55 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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
|
- name: Build
|
||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
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
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-windows
|
name: lan-mouse-windows
|
||||||
path: target/debug/lan-mouse.exe
|
path: |
|
||||||
|
target/debug/lan-mouse.exe
|
||||||
|
target/debug/*.dll
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
@@ -57,6 +101,10 @@ jobs:
|
|||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
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
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
50
.github/workflows/tagged-release.yml
vendored
50
.github/workflows/tagged-release.yml
vendored
@@ -26,14 +26,52 @@ jobs:
|
|||||||
windows-release-build:
|
windows-release-build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
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
|
- uses: actions/checkout@v3
|
||||||
- name: Release Build
|
- name: Release Build
|
||||||
run: cargo build --release
|
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
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-windows
|
name: lan-mouse-windows
|
||||||
path: target/release/lan-mouse.exe
|
path: lan-mouse-windows.zip
|
||||||
|
|
||||||
macos-release-build:
|
macos-release-build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
@@ -42,12 +80,14 @@ jobs:
|
|||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: brew install gtk4 libadwaita
|
run: brew install gtk4 libadwaita
|
||||||
- name: Release Build
|
- name: Release Build
|
||||||
run: cargo build --release
|
run: |
|
||||||
|
cargo build --release
|
||||||
|
cp target/release/lan-mouse lan-mouse-macos-intel
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-macos
|
name: lan-mouse-macos
|
||||||
path: target/release/lan-mouse
|
path: lan-mouse-macos-intel
|
||||||
|
|
||||||
tagged-release:
|
tagged-release:
|
||||||
name: "Tagged Release"
|
name: "Tagged Release"
|
||||||
@@ -63,5 +103,5 @@ jobs:
|
|||||||
prerelease: false
|
prerelease: false
|
||||||
files: |
|
files: |
|
||||||
lan-mouse-linux/lan-mouse
|
lan-mouse-linux/lan-mouse
|
||||||
lan-mouse-windows/lan-mouse.exe
|
lan-mouse-macos/lan-mouse-macos-intel
|
||||||
lan-mouse-macos/lan-mouse
|
lan-mouse-windows/lan-mouse-windows.zip
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
.gdbinit
|
.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]
|
[package]
|
||||||
name = "lan-mouse"
|
name = "lan-mouse"
|
||||||
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
|
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
repository = "https://github.com/ferdinandschober/lan-mouse"
|
repository = "https://github.com/ferdinandschober/lan-mouse"
|
||||||
@@ -16,46 +16,46 @@ lto = "fat"
|
|||||||
tempfile = "3.8"
|
tempfile = "3.8"
|
||||||
trust-dns-resolver = "0.23"
|
trust-dns-resolver = "0.23"
|
||||||
memmap = "0.7"
|
memmap = "0.7"
|
||||||
toml = "0.7"
|
toml = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
libc = "0.2.148"
|
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
tokio = {version = "1.32.0", features = ["io-util", "macros", "net", "rt", "sync", "signal"] }
|
tokio = {version = "1.32.0", features = ["io-util", "macros", "net", "rt", "sync", "signal"] }
|
||||||
async-trait = "0.1.73"
|
async-trait = "0.1.73"
|
||||||
futures-core = "0.3.28"
|
futures-core = "0.3.28"
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
clap = { version="4.4.11", features = ["derive"] }
|
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]
|
[target.'cfg(all(unix, not(target_os="macos")))'.dependencies]
|
||||||
wayland-client = { version="0.30.2", optional = true }
|
wayland-client = { version="0.31.1", optional = true }
|
||||||
wayland-protocols = { version="0.30.0", features=["client", "staging", "unstable"], optional = true }
|
wayland-protocols = { version="0.31.0", features=["client", "staging", "unstable"], optional = true }
|
||||||
wayland-protocols-wlr = { version="0.1.0", features=["client"], optional = true }
|
wayland-protocols-wlr = { version="0.2.0", features=["client"], optional = true }
|
||||||
wayland-protocols-misc = { version="0.1.0", features=["client"], optional = true }
|
wayland-protocols-misc = { version="0.2.0", features=["client"], optional = true }
|
||||||
wayland-protocols-plasma = { version="0.1.0", features=["client"], optional = true }
|
|
||||||
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
|
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
|
||||||
ashpd = { version = "0.6.2", default-features = false, features = ["tokio"], 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 }
|
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]
|
[target.'cfg(target_os="macos")'.dependencies]
|
||||||
core-graphics = { version = "0.23", features = ["highsierra"] }
|
core-graphics = { version = "0.23", features = ["highsierra"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3.9", features = ["winuser"] }
|
winapi = { version = "0.3.9", features = ["winuser"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.build-dependencies]
|
[build-dependencies]
|
||||||
glib-build-tools = "0.18.0"
|
glib-build-tools = "0.18.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wayland", "x11", "xdg_desktop_portal", "libei", "gtk"]
|
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"]
|
x11 = ["dep:x11"]
|
||||||
xdg_desktop_portal = ["dep:ashpd"]
|
xdg_desktop_portal = ["dep:ashpd"]
|
||||||
libei = ["dep:reis", "dep:ashpd"]
|
libei = ["dep:reis", "dep:ashpd"]
|
||||||
gtk = ["dep:gtk", "dep:adw"]
|
gtk = ["dep:gtk", "dep:adw", "dep:async-channel"]
|
||||||
|
|||||||
125
README.md
125
README.md
@@ -1,13 +1,17 @@
|
|||||||
# Lan Mouse
|
# Lan Mouse
|
||||||
- _Now with a gtk frontend_
|
|
||||||
|
|
||||||
Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices.
|
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.
|
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](https://symless.com/synergy), [Share Mouse](https://www.sharemouse.com/de/).
|
||||||
@@ -28,13 +32,79 @@ input capture (to send events *to* other clients) on different operating systems
|
|||||||
| Wayland (wlroots) | :heavy_check_mark: | :heavy_check_mark: |
|
| Wayland (wlroots) | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Wayland (KDE) | :heavy_check_mark: | :heavy_check_mark: |
|
| Wayland (KDE) | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Wayland (Gnome) | :heavy_check_mark: | WIP |
|
| Wayland (Gnome) | :heavy_check_mark: | WIP |
|
||||||
| X11 | (WIP) | WIP |
|
| X11 | :heavy_check_mark: | WIP |
|
||||||
| Windows | ( :heavy_check_mark: ) | WIP |
|
| Windows | :heavy_check_mark: | WIP |
|
||||||
| MacOS | ( :heavy_check_mark: ) | WIP |
|
| MacOS | ( :heavy_check_mark: ) | WIP |
|
||||||
|
|
||||||
Keycode translation is not yet implemented so on MacOS only mouse emulation works as of right now.
|
Keycode translation is not yet implemented so on MacOS only mouse emulation works as of right now.
|
||||||
|
|
||||||
## Build and Run
|
## 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:
|
Build in release mode:
|
||||||
```sh
|
```sh
|
||||||
cargo build --release
|
cargo build --release
|
||||||
@@ -47,22 +117,23 @@ cargo run --release
|
|||||||
|
|
||||||
### Conditional Compilation
|
### 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
|
Depending on the toolchain used, support for other platforms is omitted
|
||||||
automatically (it does not make sense to build a Windows `.exe` with
|
automatically (it does not make sense to build a Windows `.exe` with
|
||||||
support for x11 and wayland backends).
|
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.
|
a Linux system.
|
||||||
|
|
||||||
This is possible through
|
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
|
E.g. if only wayland support is needed, the following command produces
|
||||||
an executable with just support for wayland:
|
an executable with just support for wayland:
|
||||||
```sh
|
```sh
|
||||||
cargo build --no-default-features --features wayland
|
cargo build --no-default-features --features wayland
|
||||||
```
|
```
|
||||||
|
For a detailed list of available features, checkout the [Cargo.toml](./Cargo.toml)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
### Gtk Frontend
|
### Gtk Frontend
|
||||||
@@ -135,22 +206,19 @@ port = 4242
|
|||||||
Where `left` can be either `left`, `right`, `top` or `bottom`.
|
Where `left` can be either `left`, `right`, `top` or `bottom`.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
- [x] Capture the actual mouse events on the server side via a wayland client and send them to the client
|
- [x] Graphical frontend (gtk + libadwaita)
|
||||||
- [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] respect xdg-config-home for config file location.
|
- [x] respect xdg-config-home for config file location.
|
||||||
|
- [x] IP Address switching
|
||||||
|
- [x] Liveness tracking Automatically ungrab mouse when client unreachable
|
||||||
|
- [ ] Liveness tracking: Automatically release keys, when server offline
|
||||||
|
- [ ] X11 Input Capture
|
||||||
|
- [ ] Windows Input Capture
|
||||||
|
- [ ] MacOS Input Capture
|
||||||
|
- [ ] MaxOS KeyCode Translation
|
||||||
|
- [ ] Latency measurement and visualization
|
||||||
|
- [ ] Bandwidth usage measurement and visualization
|
||||||
|
- [ ] Clipboard support
|
||||||
|
- [ ] *Encryption*
|
||||||
|
|
||||||
## Protocol
|
## Protocol
|
||||||
Currently *all* mouse and keyboard events are sent via **UDP** for performance reasons.
|
Currently *all* mouse and keyboard events are sent via **UDP** for performance reasons.
|
||||||
@@ -222,10 +290,9 @@ it is however not exposed to third party applications.
|
|||||||
The recommended way to emulate input on KDE is the
|
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).
|
[freedesktop remote-desktop-portal](https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.RemoteDesktop).
|
||||||
|
|
||||||
#### Gnome (TODO)
|
#### Gnome
|
||||||
Gnome uses [libei](https://gitlab.freedesktop.org/libinput/libei) for input emulation,
|
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 Input on wayland.
|
which has the goal to become the general approach for emulating and capturing Input on Wayland.
|
||||||
|
|
||||||
|
|
||||||
### Input capture
|
### Input capture
|
||||||
|
|
||||||
|
|||||||
1
build.rs
1
build.rs
@@ -1,6 +1,5 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
// composite_templates
|
// composite_templates
|
||||||
#[cfg(unix)]
|
|
||||||
glib_build_tools::compile_resources(
|
glib_build_tools::compile_resources(
|
||||||
&["resources"],
|
&["resources"],
|
||||||
"resources/resources.gresource.xml",
|
"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.0
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
<property name="title" translatable="yes">Lan Mouse</property>
|
<property name="title" translatable="yes">Lan Mouse</property>
|
||||||
<property name="show-menubar">True</property>
|
<property name="show-menubar">True</property>
|
||||||
<property name="content">
|
<property name="content">
|
||||||
<object class="AdwToolbarView">
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
<child type="top">
|
<child type="top">
|
||||||
<object class="AdwHeaderBar">
|
<object class="AdwHeaderBar">
|
||||||
<child type ="end">
|
<child type ="end">
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<property name="content">
|
<child>
|
||||||
<object class="AdwToastOverlay" id="toast_overlay">
|
<object class="AdwToastOverlay" id="toast_overlay">
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwStatusPage">
|
<object class="AdwStatusPage">
|
||||||
@@ -138,7 +139,7 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -15,3 +15,6 @@ pub mod libei;
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub mod 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)?;
|
let context = ei::Context::new(stream)?;
|
||||||
context.flush()?;
|
context.flush()?;
|
||||||
let events = EiEventStream::new(context.clone())?;
|
let events = EiEventStream::new(context.clone())?;
|
||||||
return Ok(Self {
|
Ok(Self {
|
||||||
handshake: false,
|
handshake: false,
|
||||||
context,
|
context,
|
||||||
events,
|
events,
|
||||||
@@ -96,7 +96,7 @@ impl LibeiConsumer {
|
|||||||
capability_mask: 0,
|
capability_mask: 0,
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
serial: 0,
|
serial: 0,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ impl EventConsumer for LibeiConsumer {
|
|||||||
};
|
};
|
||||||
let event = match event {
|
let event = match event {
|
||||||
PendingRequestResult::Request(result) => result,
|
PendingRequestResult::Request(result) => result,
|
||||||
PendingRequestResult::ProtocolError(e) => {
|
PendingRequestResult::ParseError(e) => {
|
||||||
return Err(anyhow!("libei protocol violation: {e}"))
|
return Err(anyhow!("libei protocol violation: {e}"))
|
||||||
}
|
}
|
||||||
PendingRequestResult::InvalidObject(e) => return Err(anyhow!("invalid object {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 crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use core_graphics::display::CGPoint;
|
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint};
|
||||||
use core_graphics::event::{
|
use core_graphics::event::{
|
||||||
CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, ScrollEventUnit,
|
CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, EventField, ScrollEventUnit,
|
||||||
};
|
};
|
||||||
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
@@ -78,6 +78,16 @@ impl EventConsumer for MacOSConsumer {
|
|||||||
relative_x,
|
relative_x,
|
||||||
relative_y,
|
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() {
|
let mut mouse_location = match self.get_mouse_location() {
|
||||||
Some(l) => l,
|
Some(l) => l,
|
||||||
None => {
|
None => {
|
||||||
@@ -85,8 +95,9 @@ impl EventConsumer for MacOSConsumer {
|
|||||||
return;
|
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;
|
let mut event_type = CGEventType::MouseMoved;
|
||||||
if self.button_state.left {
|
if self.button_state.left {
|
||||||
@@ -108,6 +119,14 @@ impl EventConsumer for MacOSConsumer {
|
|||||||
return;
|
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);
|
event.post(CGEventTapLocation::HID);
|
||||||
}
|
}
|
||||||
PointerEvent::Button {
|
PointerEvent::Button {
|
||||||
@@ -140,7 +159,7 @@ impl EventConsumer for MacOSConsumer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
// store button state
|
// 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 location = self.get_mouse_location().unwrap();
|
||||||
let event = match CGEvent::new_mouse_event(
|
let event = match CGEvent::new_mouse_event(
|
||||||
@@ -209,9 +228,7 @@ impl EventConsumer for MacOSConsumer {
|
|||||||
}
|
}
|
||||||
KeyboardEvent::Modifiers { .. } => {}
|
KeyboardEvent::Modifiers { .. } => {}
|
||||||
},
|
},
|
||||||
Event::Release() => {}
|
_ => (),
|
||||||
Event::Ping() => {}
|
|
||||||
Event::Pong() => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
consumer::EventConsumer,
|
consumer::EventConsumer,
|
||||||
event::{KeyboardEvent, PointerEvent},
|
event::{KeyboardEvent, PointerEvent},
|
||||||
|
scancode,
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use winapi::um::winuser::{SendInput, KEYEVENTF_EXTENDEDKEY};
|
||||||
use winapi::{
|
use winapi::{
|
||||||
self,
|
self,
|
||||||
um::winuser::{
|
um::winuser::{
|
||||||
@@ -21,8 +24,8 @@ use crate::{
|
|||||||
pub struct WindowsConsumer {}
|
pub struct WindowsConsumer {}
|
||||||
|
|
||||||
impl WindowsConsumer {
|
impl WindowsConsumer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Result<Self> {
|
||||||
Self {}
|
Ok(Self {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +79,8 @@ fn send_mouse_input(mi: MOUSEINPUT) {
|
|||||||
u: std::mem::transmute(mi),
|
u: std::mem::transmute(mi),
|
||||||
};
|
};
|
||||||
|
|
||||||
winapi::um::winuser::SendInput(
|
SendInput(
|
||||||
1 as u32,
|
1_u32,
|
||||||
&mut input as LPINPUT,
|
&mut input as LPINPUT,
|
||||||
std::mem::size_of::<INPUT>() as i32,
|
std::mem::size_of::<INPUT>() as i32,
|
||||||
);
|
);
|
||||||
@@ -141,10 +144,17 @@ fn scroll(axis: u8, value: f64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn key_event(key: u32, state: u8) {
|
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 {
|
let ki = KEYBDINPUT {
|
||||||
wVk: 0,
|
wVk: 0,
|
||||||
wScan: key as u16,
|
wScan: scancode,
|
||||||
dwFlags: KEYEVENTF_SCANCODE
|
dwFlags: KEYEVENTF_SCANCODE
|
||||||
|
| if extended { KEYEVENTF_EXTENDEDKEY } else { 0 }
|
||||||
| match state {
|
| match state {
|
||||||
0 => KEYEVENTF_KEYUP,
|
0 => KEYEVENTF_KEYUP,
|
||||||
1 => 0u32,
|
1 => 0u32,
|
||||||
@@ -163,6 +173,26 @@ fn send_keyboard_input(ki: KEYBDINPUT) {
|
|||||||
u: std::mem::zeroed(),
|
u: std::mem::zeroed(),
|
||||||
};
|
};
|
||||||
*input.u.ki_mut() = ki;
|
*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 async_trait::async_trait;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::fd::OwnedFd;
|
use std::os::fd::{AsFd, OwnedFd};
|
||||||
use std::os::unix::prelude::AsRawFd;
|
|
||||||
use wayland_client::backend::WaylandError;
|
use wayland_client::backend::WaylandError;
|
||||||
use wayland_client::WEnum;
|
use wayland_client::WEnum;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use wayland_client::globals::BindError;
|
|
||||||
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
|
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
|
||||||
use wayland_client::protocol::wl_pointer::{Axis, ButtonState};
|
use wayland_client::protocol::wl_pointer::{Axis, ButtonState};
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
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,
|
zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1 as Vk,
|
||||||
};
|
};
|
||||||
|
|
||||||
use wayland_protocols_plasma::fake_input::client::org_kde_kwin_fake_input::OrgKdeKwinFakeInput;
|
|
||||||
|
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
delegate_noop,
|
delegate_noop,
|
||||||
globals::{registry_queue_init, GlobalListContents},
|
globals::{registry_queue_init, GlobalListContents},
|
||||||
@@ -34,17 +30,13 @@ use wayland_client::{
|
|||||||
|
|
||||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||||
|
|
||||||
enum VirtualInputManager {
|
|
||||||
Wlroots { vpm: VpManager, vkm: VkManager },
|
|
||||||
Kde { fake_input: OrgKdeKwinFakeInput },
|
|
||||||
}
|
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
keymap: Option<(u32, OwnedFd, u32)>,
|
keymap: Option<(u32, OwnedFd, u32)>,
|
||||||
input_for_client: HashMap<ClientHandle, VirtualInput>,
|
input_for_client: HashMap<ClientHandle, VirtualInput>,
|
||||||
seat: wl_seat::WlSeat,
|
seat: wl_seat::WlSeat,
|
||||||
virtual_input_manager: VirtualInputManager,
|
|
||||||
qh: QueueHandle<Self>,
|
qh: QueueHandle<Self>,
|
||||||
|
vpm: VpManager,
|
||||||
|
vkm: VkManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
// App State, implements Dispatch event handlers
|
// App State, implements Dispatch event handlers
|
||||||
@@ -56,8 +48,8 @@ pub(crate) struct WlrootsConsumer {
|
|||||||
|
|
||||||
impl WlrootsConsumer {
|
impl WlrootsConsumer {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let conn = Connection::connect_to_env().unwrap();
|
let conn = Connection::connect_to_env()?;
|
||||||
let (globals, queue) = registry_queue_init::<State>(&conn).unwrap();
|
let (globals, queue) = registry_queue_init::<State>(&conn)?;
|
||||||
let qh = queue.handle();
|
let qh = queue.handle();
|
||||||
|
|
||||||
let seat: wl_seat::WlSeat = match globals.bind(&qh, 7..=8, ()) {
|
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")),
|
Err(_) => return Err(anyhow!("wl_seat >= v7 not supported")),
|
||||||
};
|
};
|
||||||
|
|
||||||
let vpm: Result<VpManager, BindError> = globals.bind(&qh, 1..=1, ());
|
let vpm: VpManager = globals.bind(&qh, 1..=1, ())?;
|
||||||
let vkm: Result<VkManager, BindError> = globals.bind(&qh, 1..=1, ());
|
let vkm: VkManager = 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 input_for_client: HashMap<ClientHandle, VirtualInput> = HashMap::new();
|
let input_for_client: HashMap<ClientHandle, VirtualInput> = HashMap::new();
|
||||||
|
|
||||||
@@ -98,7 +68,8 @@ impl WlrootsConsumer {
|
|||||||
keymap: None,
|
keymap: None,
|
||||||
input_for_client,
|
input_for_client,
|
||||||
seat,
|
seat,
|
||||||
virtual_input_manager,
|
vpm,
|
||||||
|
vkm,
|
||||||
qh,
|
qh,
|
||||||
},
|
},
|
||||||
queue,
|
queue,
|
||||||
@@ -118,29 +89,19 @@ impl WlrootsConsumer {
|
|||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
fn add_client(&mut self, client: ClientHandle) {
|
fn add_client(&mut self, client: ClientHandle) {
|
||||||
// create virtual input devices
|
let pointer: Vp = self.vpm.create_virtual_pointer(None, &self.qh, ());
|
||||||
match &self.virtual_input_manager {
|
let keyboard: Vk = self.vkm.create_virtual_keyboard(&self.seat, &self.qh, ());
|
||||||
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, ());
|
|
||||||
|
|
||||||
// TODO: use server side keymap
|
// TODO: use server side keymap
|
||||||
if let Some((format, fd, size)) = self.keymap.as_ref() {
|
if let Some((format, fd, size)) = self.keymap.as_ref() {
|
||||||
keyboard.keymap(*format, fd.as_raw_fd(), *size);
|
keyboard.keymap(*format, fd.as_fd(), *size);
|
||||||
} else {
|
} else {
|
||||||
panic!("no keymap");
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {}
|
async fn destroy(&mut self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VirtualInput {
|
struct VirtualInput {
|
||||||
Wlroots { pointer: Vp, keyboard: Vk },
|
pointer: Vp,
|
||||||
Kde { fake_input: OrgKdeKwinFakeInput },
|
keyboard: Vk,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualInput {
|
impl VirtualInput {
|
||||||
@@ -208,94 +169,37 @@ impl VirtualInput {
|
|||||||
time,
|
time,
|
||||||
relative_x,
|
relative_x,
|
||||||
relative_y,
|
relative_y,
|
||||||
} => match self {
|
} => self.pointer.motion(time, relative_x, relative_y),
|
||||||
VirtualInput::Wlroots {
|
|
||||||
pointer,
|
|
||||||
keyboard: _,
|
|
||||||
} => {
|
|
||||||
pointer.motion(time, relative_x, relative_y);
|
|
||||||
}
|
|
||||||
VirtualInput::Kde { fake_input } => {
|
|
||||||
fake_input.pointer_motion(relative_y, relative_y);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
PointerEvent::Button {
|
PointerEvent::Button {
|
||||||
time,
|
time,
|
||||||
button,
|
button,
|
||||||
state,
|
state,
|
||||||
} => {
|
} => {
|
||||||
let state: ButtonState = state.try_into()?;
|
let state: ButtonState = state.try_into()?;
|
||||||
match self {
|
self.pointer.button(time, button, state);
|
||||||
VirtualInput::Wlroots {
|
|
||||||
pointer,
|
|
||||||
keyboard: _,
|
|
||||||
} => {
|
|
||||||
pointer.button(time, button, state);
|
|
||||||
}
|
|
||||||
VirtualInput::Kde { fake_input } => {
|
|
||||||
fake_input.button(button, state as u32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
PointerEvent::Axis { time, axis, value } => {
|
PointerEvent::Axis { time, axis, value } => {
|
||||||
let axis: Axis = (axis as u32).try_into()?;
|
let axis: Axis = (axis as u32).try_into()?;
|
||||||
match self {
|
self.pointer.axis(time, axis, value);
|
||||||
VirtualInput::Wlroots {
|
self.pointer.frame();
|
||||||
pointer,
|
|
||||||
keyboard: _,
|
|
||||||
} => {
|
|
||||||
pointer.axis(time, axis, value);
|
|
||||||
pointer.frame();
|
|
||||||
}
|
|
||||||
VirtualInput::Kde { fake_input } => {
|
|
||||||
fake_input.axis(axis as u32, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
PointerEvent::Frame {} => match self {
|
PointerEvent::Frame {} => self.pointer.frame(),
|
||||||
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();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
self.pointer.frame();
|
||||||
}
|
}
|
||||||
Event::Keyboard(e) => match e {
|
Event::Keyboard(e) => match e {
|
||||||
KeyboardEvent::Key { time, key, state } => match self {
|
KeyboardEvent::Key { time, key, state } => {
|
||||||
VirtualInput::Wlroots {
|
self.keyboard.key(time, key, state as u32);
|
||||||
pointer: _,
|
}
|
||||||
keyboard,
|
|
||||||
} => {
|
|
||||||
keyboard.key(time, key, state as u32);
|
|
||||||
}
|
|
||||||
VirtualInput::Kde { fake_input } => {
|
|
||||||
fake_input.keyboard_key(key, state as u32);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
KeyboardEvent::Modifiers {
|
KeyboardEvent::Modifiers {
|
||||||
mods_depressed,
|
mods_depressed,
|
||||||
mods_latched,
|
mods_latched,
|
||||||
mods_locked,
|
mods_locked,
|
||||||
group,
|
group,
|
||||||
} => match self {
|
} => {
|
||||||
VirtualInput::Wlroots {
|
self.keyboard
|
||||||
pointer: _,
|
.modifiers(mods_depressed, mods_latched, mods_locked, group);
|
||||||
keyboard,
|
}
|
||||||
} => {
|
|
||||||
keyboard.modifiers(mods_depressed, mods_latched, mods_locked, group);
|
|
||||||
}
|
|
||||||
VirtualInput::Kde { fake_input: _ } => {}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -307,7 +211,6 @@ delegate_noop!(State: Vp);
|
|||||||
delegate_noop!(State: Vk);
|
delegate_noop!(State: Vk);
|
||||||
delegate_noop!(State: VpManager);
|
delegate_noop!(State: VpManager);
|
||||||
delegate_noop!(State: VkManager);
|
delegate_noop!(State: VkManager);
|
||||||
delegate_noop!(State: OrgKdeKwinFakeInput);
|
|
||||||
|
|
||||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
|
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
|
||||||
fn event(
|
fn event(
|
||||||
@@ -330,11 +233,8 @@ impl Dispatch<WlKeyboard, ()> for State {
|
|||||||
_: &Connection,
|
_: &Connection,
|
||||||
_: &QueueHandle<Self>,
|
_: &QueueHandle<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
if let wl_keyboard::Event::Keymap { format, fd, size } = event {
|
||||||
wl_keyboard::Event::Keymap { format, fd, size } => {
|
state.keymap = Some((u32::from(format), fd, size));
|
||||||
state.keymap = Some((u32::from(format), fd, size));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::ptr;
|
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 {
|
pub struct X11Consumer {
|
||||||
display: *mut xlib::Display,
|
display: *mut xlib::Display,
|
||||||
@@ -11,21 +21,80 @@ pub struct X11Consumer {
|
|||||||
unsafe impl Send for X11Consumer {}
|
unsafe impl Send for X11Consumer {}
|
||||||
|
|
||||||
impl X11Consumer {
|
impl X11Consumer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Result<Self> {
|
||||||
let display = unsafe {
|
let display = unsafe {
|
||||||
match xlib::XOpenDisplay(ptr::null()) {
|
match xlib::XOpenDisplay(ptr::null()) {
|
||||||
d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => None,
|
d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => {
|
||||||
display => Some(display),
|
Err(anyhow!("could not open display"))
|
||||||
|
}
|
||||||
|
display => Ok(display),
|
||||||
}
|
}
|
||||||
};
|
}?;
|
||||||
let display = display.expect("could not open display");
|
Ok(Self { display })
|
||||||
Self { display }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn relative_motion(&self, dx: i32, dy: i32) {
|
fn relative_motion(&self, dx: i32, dy: i32) {
|
||||||
unsafe {
|
unsafe {
|
||||||
xtest::XTestFakeRelativeMotionEvent(self.display, dx, dy, 0, 0);
|
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) {
|
async fn consume(&mut self, event: Event, _: ClientHandle) {
|
||||||
match event {
|
match event {
|
||||||
Event::Pointer(pointer_event) => match pointer_event {
|
Event::Pointer(pointer_event) => match pointer_event {
|
||||||
crate::event::PointerEvent::Motion {
|
PointerEvent::Motion {
|
||||||
time: _,
|
time: _,
|
||||||
relative_x,
|
relative_x,
|
||||||
relative_y,
|
relative_y,
|
||||||
} => {
|
} => {
|
||||||
self.relative_motion(relative_x as i32, relative_y as i32);
|
self.relative_motion(relative_x as i32, relative_y as i32);
|
||||||
}
|
}
|
||||||
crate::event::PointerEvent::Button { .. } => {}
|
PointerEvent::Button {
|
||||||
crate::event::PointerEvent::Axis { .. } => {}
|
time: _,
|
||||||
crate::event::PointerEvent::Frame {} => {}
|
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) {
|
async fn notify(&mut self, _: crate::client::ClientEvent) {
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ use ashpd::{
|
|||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
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> {
|
pub struct DesktopPortalConsumer<'a> {
|
||||||
proxy: RemoteDesktop<'a>,
|
proxy: RemoteDesktop<'a>,
|
||||||
@@ -17,8 +24,11 @@ pub struct DesktopPortalConsumer<'a> {
|
|||||||
|
|
||||||
impl<'a> DesktopPortalConsumer<'a> {
|
impl<'a> DesktopPortalConsumer<'a> {
|
||||||
pub async fn new() -> Result<DesktopPortalConsumer<'a>> {
|
pub async fn new() -> Result<DesktopPortalConsumer<'a>> {
|
||||||
|
log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ...");
|
||||||
let proxy = RemoteDesktop::new().await?;
|
let proxy = RemoteDesktop::new().await?;
|
||||||
|
log::debug!("creating session ...");
|
||||||
let session = proxy.create_session().await?;
|
let session = proxy.create_session().await?;
|
||||||
|
log::debug!("selecting devices ...");
|
||||||
proxy
|
proxy
|
||||||
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
|
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -27,6 +37,7 @@ impl<'a> DesktopPortalConsumer<'a> {
|
|||||||
.start(&session, &WindowIdentifier::default())
|
.start(&session, &WindowIdentifier::default())
|
||||||
.await?
|
.await?
|
||||||
.response()?;
|
.response()?;
|
||||||
|
log::debug!("started session");
|
||||||
|
|
||||||
Ok(Self { proxy, session })
|
Ok(Self { proxy, session })
|
||||||
}
|
}
|
||||||
@@ -36,9 +47,9 @@ impl<'a> DesktopPortalConsumer<'a> {
|
|||||||
impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
||||||
async fn consume(&mut self, event: crate::event::Event, _client: crate::client::ClientHandle) {
|
async fn consume(&mut self, event: crate::event::Event, _client: crate::client::ClientHandle) {
|
||||||
match event {
|
match event {
|
||||||
crate::event::Event::Pointer(p) => {
|
Pointer(p) => {
|
||||||
match p {
|
match p {
|
||||||
crate::event::PointerEvent::Motion {
|
PointerEvent::Motion {
|
||||||
time: _,
|
time: _,
|
||||||
relative_x,
|
relative_x,
|
||||||
relative_y,
|
relative_y,
|
||||||
@@ -51,7 +62,7 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
|||||||
log::warn!("{e}");
|
log::warn!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::event::PointerEvent::Button {
|
PointerEvent::Button {
|
||||||
time: _,
|
time: _,
|
||||||
button,
|
button,
|
||||||
state,
|
state,
|
||||||
@@ -68,7 +79,7 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
|||||||
log::warn!("{e}");
|
log::warn!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::event::PointerEvent::Axis {
|
PointerEvent::Axis {
|
||||||
time: _,
|
time: _,
|
||||||
axis,
|
axis,
|
||||||
value,
|
value,
|
||||||
@@ -86,12 +97,12 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
|||||||
log::warn!("{e}");
|
log::warn!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::event::PointerEvent::Frame {} => {}
|
PointerEvent::Frame {} => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::event::Event::Keyboard(k) => {
|
Keyboard(k) => {
|
||||||
match k {
|
match k {
|
||||||
crate::event::KeyboardEvent::Key {
|
KeyboardEvent::Key {
|
||||||
time: _,
|
time: _,
|
||||||
key,
|
key,
|
||||||
state,
|
state,
|
||||||
@@ -108,7 +119,7 @@ impl<'a> EventConsumer for DesktopPortalConsumer<'a> {
|
|||||||
log::warn!("{e}");
|
log::warn!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::event::KeyboardEvent::Modifiers { .. } => {
|
KeyboardEvent::Modifiers { .. } => {
|
||||||
// ignore
|
// 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) {
|
async fn destroy(&mut self) {
|
||||||
log::debug!("closing remote desktop session");
|
log::debug!("closing remote desktop session");
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
pub mod libei;
|
pub mod libei;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub mod macos;
|
pub mod macos;
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
pub mod wayland;
|
pub mod wayland;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod windows;
|
pub mod windows;
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
pub mod x11;
|
pub mod x11;
|
||||||
|
|
||||||
|
/// fallback event producer
|
||||||
|
pub mod dummy;
|
||||||
|
|||||||
38
src/backend/producer/dummy.rs
Normal file
38
src/backend/producer/dummy.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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, _: ClientEvent) {}
|
||||||
|
|
||||||
|
fn release(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,4 +1,4 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use std::{io, task::Poll};
|
use std::{io, task::Poll};
|
||||||
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
@@ -9,7 +9,7 @@ pub struct LibeiProducer {}
|
|||||||
|
|
||||||
impl LibeiProducer {
|
impl LibeiProducer {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
Ok(Self {})
|
Err(anyhow!("not implemented"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::client::{ClientEvent, ClientHandle};
|
use crate::client::{ClientEvent, ClientHandle};
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use crate::producer::EventProducer;
|
use crate::producer::EventProducer;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{io, pin::Pin};
|
use std::{io, pin::Pin};
|
||||||
@@ -8,8 +9,8 @@ use std::{io, pin::Pin};
|
|||||||
pub struct MacOSProducer;
|
pub struct MacOSProducer;
|
||||||
|
|
||||||
impl MacOSProducer {
|
impl MacOSProducer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Result<Self> {
|
||||||
Self {}
|
Err(anyhow!("not yet implemented"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::{
|
|||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
env,
|
env,
|
||||||
io::{self, ErrorKind},
|
io::{self, ErrorKind},
|
||||||
os::fd::{OwnedFd, RawFd},
|
os::fd::{AsFd, OwnedFd, RawFd},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{ready, Context, Poll},
|
task::{ready, Context, Poll},
|
||||||
};
|
};
|
||||||
@@ -145,7 +145,7 @@ impl Window {
|
|||||||
draw(&mut file, (width, height));
|
draw(&mut file, (width, height));
|
||||||
let pool = g
|
let pool = g
|
||||||
.shm
|
.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(
|
let buffer = pool.create_buffer(
|
||||||
0,
|
0,
|
||||||
width as i32,
|
width as i32,
|
||||||
@@ -159,7 +159,7 @@ impl Window {
|
|||||||
|
|
||||||
let layer_surface = g.layer_shell.get_layer_surface(
|
let layer_surface = g.layer_shell.get_layer_surface(
|
||||||
&surface,
|
&surface,
|
||||||
Some(&output),
|
Some(output),
|
||||||
Layer::Overlay,
|
Layer::Overlay,
|
||||||
"LAN Mouse Sharing".into(),
|
"LAN Mouse Sharing".into(),
|
||||||
qh,
|
qh,
|
||||||
@@ -221,13 +221,7 @@ fn get_output_configuration(state: &State, pos: Position) -> Vec<(WlOutput, Outp
|
|||||||
// as an opposite edge of a different output
|
// as an opposite edge of a different output
|
||||||
let outputs: Vec<WlOutput> = edges
|
let outputs: Vec<WlOutput> = edges
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, edge)| {
|
.filter(|(_, edge)| !opposite_edges.iter().map(|(_, e)| *e).any(|e| &e == edge))
|
||||||
opposite_edges
|
|
||||||
.iter()
|
|
||||||
.map(|(_, e)| *e)
|
|
||||||
.find(|e| e == edge)
|
|
||||||
.is_none()
|
|
||||||
})
|
|
||||||
.map(|(o, _)| o.clone())
|
.map(|(o, _)| o.clone())
|
||||||
.collect();
|
.collect();
|
||||||
state
|
state
|
||||||
@@ -244,7 +238,7 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
|
|||||||
for _ in 0..width {
|
for _ in 0..width {
|
||||||
if env::var("LM_DEBUG_LAYER_SHELL").ok().is_some() {
|
if env::var("LM_DEBUG_LAYER_SHELL").ok().is_some() {
|
||||||
// AARRGGBB
|
// AARRGGBB
|
||||||
buf.write_all(&0xFF11d116u32.to_ne_bytes()).unwrap();
|
buf.write_all(&0xff11d116u32.to_ne_bytes()).unwrap();
|
||||||
} else {
|
} else {
|
||||||
// AARRGGBB
|
// AARRGGBB
|
||||||
buf.write_all(&0x00000000u32.to_ne_bytes()).unwrap();
|
buf.write_all(&0x00000000u32.to_ne_bytes()).unwrap();
|
||||||
@@ -330,7 +324,7 @@ impl WaylandEventProducer {
|
|||||||
queue.flush()?;
|
queue.flush()?;
|
||||||
|
|
||||||
// prepare reading wayland events
|
// 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();
|
let wayland_fd = read_guard.connection_fd().try_clone_to_owned().unwrap();
|
||||||
std::mem::drop(read_guard);
|
std::mem::drop(read_guard);
|
||||||
|
|
||||||
@@ -372,7 +366,15 @@ impl WaylandEventProducer {
|
|||||||
log::debug!("{:#?}", i.1);
|
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);
|
state.read_guard = Some(read_guard);
|
||||||
|
|
||||||
let inner = AsyncFd::new(Inner { queue, state })?;
|
let inner = AsyncFd::new(Inner { queue, state })?;
|
||||||
@@ -468,7 +470,7 @@ impl State {
|
|||||||
let outputs = get_output_configuration(self, pos);
|
let outputs = get_output_configuration(self, pos);
|
||||||
|
|
||||||
outputs.iter().for_each(|(o, i)| {
|
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);
|
let window = Rc::new(window);
|
||||||
self.client_for_window.push((window, client));
|
self.client_for_window.push((window, client));
|
||||||
});
|
});
|
||||||
@@ -490,16 +492,20 @@ impl Inner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_read(&mut self) {
|
fn prepare_read(&mut self) -> io::Result<()> {
|
||||||
match self.queue.prepare_read() {
|
loop {
|
||||||
Ok(r) => self.state.read_guard = Some(r),
|
match self.queue.prepare_read() {
|
||||||
Err(WaylandError::Io(e)) => {
|
None => match self.queue.dispatch_pending(&mut self.state) {
|
||||||
log::error!("error preparing read from wayland socket: {e}")
|
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) {
|
fn dispatch_events(&mut self) {
|
||||||
@@ -545,19 +551,15 @@ impl EventProducer for WaylandEventProducer {
|
|||||||
}
|
}
|
||||||
ClientEvent::Destroy(handle) => {
|
ClientEvent::Destroy(handle) => {
|
||||||
let inner = self.0.get_mut();
|
let inner = self.0.get_mut();
|
||||||
loop {
|
// remove all windows corresponding to this client
|
||||||
// remove all windows corresponding to this client
|
while let Some(i) = inner
|
||||||
if let Some(i) = inner
|
.state
|
||||||
.state
|
.client_for_window
|
||||||
.client_for_window
|
.iter()
|
||||||
.iter()
|
.position(|(_, c)| *c == handle)
|
||||||
.position(|(_, c)| *c == handle)
|
{
|
||||||
{
|
inner.state.client_for_window.remove(i);
|
||||||
inner.state.client_for_window.remove(i);
|
inner.state.focused = None;
|
||||||
inner.state.focused = None;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,7 +578,6 @@ impl Stream for WaylandEventProducer {
|
|||||||
type Item = io::Result<(ClientHandle, Event)>;
|
type Item = io::Result<(ClientHandle, Event)>;
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
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() {
|
if let Some(event) = self.0.get_mut().state.pending_events.pop_front() {
|
||||||
return Poll::Ready(Some(Ok(event)));
|
return Poll::Ready(Some(Ok(event)));
|
||||||
}
|
}
|
||||||
@@ -590,7 +591,10 @@ impl Stream for WaylandEventProducer {
|
|||||||
// read events
|
// read events
|
||||||
while inner.read() {
|
while inner.read() {
|
||||||
// prepare next read
|
// prepare next read
|
||||||
inner.prepare_read();
|
match inner.prepare_read() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispatch the events
|
// dispatch the events
|
||||||
@@ -600,7 +604,10 @@ impl Stream for WaylandEventProducer {
|
|||||||
inner.flush_events();
|
inner.flush_events();
|
||||||
|
|
||||||
// prepare for the next read
|
// 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
|
// clear read readiness for tokio read guard
|
||||||
@@ -663,7 +670,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
|||||||
.find(|(w, _c)| w.surface == surface)
|
.find(|(w, _c)| w.surface == surface)
|
||||||
{
|
{
|
||||||
app.focused = Some((window.clone(), *client));
|
app.focused = Some((window.clone(), *client));
|
||||||
app.grab(&surface, pointer, serial.clone(), qh);
|
app.grab(&surface, pointer, serial, qh);
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -673,7 +680,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|(w, _c)| w.surface == surface)
|
.find(|(w, _c)| w.surface == surface)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
app.pending_events.push_back((*client, Event::Release()));
|
app.pending_events.push_back((*client, Event::Enter()));
|
||||||
}
|
}
|
||||||
wl_pointer::Event::Leave { .. } => {
|
wl_pointer::Event::Leave { .. } => {
|
||||||
app.ungrab();
|
app.ungrab();
|
||||||
@@ -764,10 +771,6 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
|
|||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if mods_depressed == 77 {
|
|
||||||
// ctrl shift super alt
|
|
||||||
app.ungrab();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
wl_keyboard::Event::Keymap {
|
wl_keyboard::Event::Keymap {
|
||||||
format: _,
|
format: _,
|
||||||
@@ -834,7 +837,7 @@ impl Dispatch<ZwlrLayerSurfaceV1, ()> for State {
|
|||||||
// client corresponding to the layer_surface
|
// client corresponding to the layer_surface
|
||||||
let surface = &window.surface;
|
let surface = &window.surface;
|
||||||
let buffer = &window.buffer;
|
let buffer = &window.buffer;
|
||||||
surface.attach(Some(&buffer), 0, 0);
|
surface.attach(Some(buffer), 0, 0);
|
||||||
layer_surface.ack_configure(serial);
|
layer_surface.ack_configure(serial);
|
||||||
surface.commit();
|
surface.commit();
|
||||||
}
|
}
|
||||||
@@ -869,16 +872,15 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
|||||||
name,
|
name,
|
||||||
interface,
|
interface,
|
||||||
version: _,
|
version: _,
|
||||||
} => match interface.as_str() {
|
} => {
|
||||||
"wl_output" => {
|
if interface.as_str() == "wl_output" {
|
||||||
log::debug!("wl_output global");
|
log::debug!("wl_output global");
|
||||||
state
|
state
|
||||||
.g
|
.g
|
||||||
.outputs
|
.outputs
|
||||||
.push(registry.bind::<wl_output::WlOutput, _, _>(name, 4, qh, ()))
|
.push(registry.bind::<wl_output::WlOutput, _, _>(name, 4, qh, ()))
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
},
|
|
||||||
wl_registry::Event::GlobalRemove { .. } => {}
|
wl_registry::Event::GlobalRemove { .. } => {}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
use core::task::{Context, Poll};
|
use core::task::{Context, Poll};
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use std::io::Result;
|
use std::{io, pin::Pin};
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{ClientEvent, ClientHandle},
|
client::{ClientEvent, ClientHandle},
|
||||||
@@ -18,13 +18,13 @@ impl EventProducer for WindowsProducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsProducer {
|
impl WindowsProducer {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Result<Self> {
|
||||||
Self {}
|
Err(anyhow!("not implemented"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for WindowsProducer {
|
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>> {
|
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::task::Poll;
|
use std::task::Poll;
|
||||||
|
|
||||||
@@ -11,8 +12,8 @@ use crate::client::{ClientEvent, ClientHandle};
|
|||||||
pub struct X11Producer {}
|
pub struct X11Producer {}
|
||||||
|
|
||||||
impl X11Producer {
|
impl X11Producer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Result<Self> {
|
||||||
Self {}
|
Err(anyhow!("not implemented"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ impl Display for Position {
|
|||||||
pub struct Client {
|
pub struct Client {
|
||||||
/// hostname of this client
|
/// hostname of this client
|
||||||
pub hostname: Option<String>,
|
pub hostname: Option<String>,
|
||||||
|
/// fix ips, determined by the user
|
||||||
|
pub fix_ips: Vec<IpAddr>,
|
||||||
/// unique handle to refer to the client.
|
/// unique handle to refer to the client.
|
||||||
/// This way any event consumer / producer backend does not
|
/// This way any event consumer / producer backend does not
|
||||||
/// need to know anything about a client other than its handle.
|
/// need to know anything about a client other than its handle.
|
||||||
@@ -69,6 +71,7 @@ pub struct Client {
|
|||||||
pub pos: Position,
|
pub pos: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ClientEvent {
|
pub enum ClientEvent {
|
||||||
Create(ClientHandle, Position),
|
Create(ClientHandle, Position),
|
||||||
Destroy(ClientHandle),
|
Destroy(ClientHandle),
|
||||||
@@ -89,6 +92,12 @@ pub struct ClientManager {
|
|||||||
clients: Vec<Option<ClientState>>, // HashMap likely not beneficial
|
clients: Vec<Option<ClientState>>, // HashMap likely not beneficial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ClientManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ClientManager {
|
impl ClientManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { clients: vec![] }
|
Self { clients: vec![] }
|
||||||
@@ -98,7 +107,7 @@ impl ClientManager {
|
|||||||
pub fn add_client(
|
pub fn add_client(
|
||||||
&mut self,
|
&mut self,
|
||||||
hostname: Option<String>,
|
hostname: Option<String>,
|
||||||
addrs: HashSet<IpAddr>,
|
ips: HashSet<IpAddr>,
|
||||||
port: u16,
|
port: u16,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> ClientHandle {
|
) -> ClientHandle {
|
||||||
@@ -108,12 +117,16 @@ impl ClientManager {
|
|||||||
// we dont know, which IP is initially active
|
// we dont know, which IP is initially active
|
||||||
let active_addr = None;
|
let active_addr = None;
|
||||||
|
|
||||||
|
// store fix ip addresses
|
||||||
|
let fix_ips = ips.iter().cloned().collect();
|
||||||
|
|
||||||
// map ip addresses to socket addresses
|
// 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
|
// store the client
|
||||||
let client = Client {
|
let client = Client {
|
||||||
hostname,
|
hostname,
|
||||||
|
fix_ips,
|
||||||
handle,
|
handle,
|
||||||
active_addr,
|
active_addr,
|
||||||
addrs,
|
addrs,
|
||||||
@@ -174,20 +187,20 @@ impl ClientManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// returns an immutable reference to the client state corresponding to `client`
|
// 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()
|
self.clients.get(client as usize)?.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns a mutable reference to the client state corresponding to `client`
|
/// 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()
|
self.clients.get_mut(client as usize)?.as_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enumerate(&self) -> Vec<(Client, bool)> {
|
pub fn get_client_states(&self) -> impl Iterator<Item = &ClientState> {
|
||||||
self.clients
|
self.clients.iter().filter_map(|x| x.as_ref())
|
||||||
.iter()
|
}
|
||||||
.filter_map(|s| s.as_ref())
|
|
||||||
.map(|s| (s.client.clone(), s.active))
|
pub fn get_client_states_mut(&mut self) -> impl Iterator<Item = &mut ClientState> {
|
||||||
.collect()
|
self.clients.iter_mut().filter_map(|x| x.as_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ impl Config {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let frontend = match frontend {
|
let frontend = match frontend {
|
||||||
#[cfg(all(unix, feature = "gtk"))]
|
#[cfg(feature = "gtk")]
|
||||||
None => Frontend::Gtk,
|
None => Frontend::Gtk,
|
||||||
#[cfg(any(not(feature = "gtk"), not(unix)))]
|
#[cfg(not(feature = "gtk"))]
|
||||||
None => Frontend::Cli,
|
None => Frontend::Cli,
|
||||||
Some(s) => match s.as_str() {
|
Some(s) => match s.as_str() {
|
||||||
"cli" => Frontend::Cli,
|
"cli" => Frontend::Cli,
|
||||||
|
|||||||
142
src/consumer.rs
142
src/consumer.rs
@@ -1,9 +1,6 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::future;
|
use std::future;
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::consumer,
|
backend::consumer,
|
||||||
client::{ClientEvent, ClientHandle},
|
client::{ClientEvent, ClientHandle},
|
||||||
@@ -11,15 +8,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Backend {
|
|
||||||
Wlroots,
|
|
||||||
X11,
|
|
||||||
RemoteDesktopPortal,
|
|
||||||
Libei,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait EventConsumer: Send {
|
pub trait EventConsumer: Send {
|
||||||
async fn consume(&mut self, event: Event, client_handle: ClientHandle);
|
async fn consume(&mut self, event: Event, client_handle: ClientHandle);
|
||||||
@@ -33,90 +21,58 @@ pub trait EventConsumer: Send {
|
|||||||
async fn destroy(&mut self);
|
async fn destroy(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create() -> Result<Box<dyn EventConsumer>> {
|
pub async fn create() -> Box<dyn EventConsumer> {
|
||||||
#[cfg(windows)]
|
#[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")]
|
#[cfg(target_os = "macos")]
|
||||||
return Ok(Box::new(consumer::macos::MacOSConsumer::new()?));
|
match consumer::macos::MacOSConsumer::new() {
|
||||||
|
Ok(c) => {
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
log::info!("using macos event consumer");
|
||||||
let backend = match env::var("XDG_SESSION_TYPE") {
|
return Box::new(c);
|
||||||
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()))
|
|
||||||
}
|
}
|
||||||
|
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;
|
use trust_dns_resolver::TokioAsyncResolver;
|
||||||
|
|
||||||
pub(crate) struct DnsResolver {
|
pub struct DnsResolver {
|
||||||
resolver: TokioAsyncResolver,
|
resolver: TokioAsyncResolver,
|
||||||
}
|
}
|
||||||
impl DnsResolver {
|
impl DnsResolver {
|
||||||
|
|||||||
150
src/event.rs
150
src/event.rs
@@ -4,9 +4,11 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
pub(crate) const BTN_LEFT: u32 = 0x110;
|
pub const BTN_LEFT: u32 = 0x110;
|
||||||
pub(crate) const BTN_RIGHT: u32 = 0x111;
|
pub const BTN_RIGHT: u32 = 0x111;
|
||||||
pub(crate) const BTN_MIDDLE: u32 = 0x112;
|
pub const BTN_MIDDLE: u32 = 0x112;
|
||||||
|
pub const BTN_BACK: u32 = 0x113;
|
||||||
|
pub const BTN_FORWARD: u32 = 0x114;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum PointerEvent {
|
pub enum PointerEvent {
|
||||||
@@ -45,10 +47,23 @@ pub enum KeyboardEvent {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
/// pointer event (motion / button / axis)
|
||||||
Pointer(PointerEvent),
|
Pointer(PointerEvent),
|
||||||
|
/// keyboard events (key / modifiers)
|
||||||
Keyboard(KeyboardEvent),
|
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(),
|
Ping(),
|
||||||
|
/// response to a ping event: this event signals that a client
|
||||||
|
/// is still alive but must otherwise be ignored
|
||||||
Pong(),
|
Pong(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +116,8 @@ impl Display for Event {
|
|||||||
match self {
|
match self {
|
||||||
Event::Pointer(p) => write!(f, "{}", p),
|
Event::Pointer(p) => write!(f, "{}", p),
|
||||||
Event::Keyboard(k) => write!(f, "{}", k),
|
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::Ping() => write!(f, "ping"),
|
||||||
Event::Pong() => write!(f, "pong"),
|
Event::Pong() => write!(f, "pong"),
|
||||||
}
|
}
|
||||||
@@ -111,11 +127,12 @@ impl Display for Event {
|
|||||||
impl Event {
|
impl Event {
|
||||||
fn event_type(&self) -> EventType {
|
fn event_type(&self) -> EventType {
|
||||||
match self {
|
match self {
|
||||||
Self::Pointer(_) => EventType::POINTER,
|
Self::Pointer(_) => EventType::Pointer,
|
||||||
Self::Keyboard(_) => EventType::KEYBOARD,
|
Self::Keyboard(_) => EventType::Keyboard,
|
||||||
Self::Release() => EventType::RELEASE,
|
Self::Enter() => EventType::Enter,
|
||||||
Self::Ping() => EventType::PING,
|
Self::Leave() => EventType::Leave,
|
||||||
Self::Pong() => EventType::PONG,
|
Self::Ping() => EventType::Ping,
|
||||||
|
Self::Pong() => EventType::Pong,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,10 +140,10 @@ impl Event {
|
|||||||
impl PointerEvent {
|
impl PointerEvent {
|
||||||
fn event_type(&self) -> PointerEventType {
|
fn event_type(&self) -> PointerEventType {
|
||||||
match self {
|
match self {
|
||||||
Self::Motion { .. } => PointerEventType::MOTION,
|
Self::Motion { .. } => PointerEventType::Motion,
|
||||||
Self::Button { .. } => PointerEventType::BUTTON,
|
Self::Button { .. } => PointerEventType::Button,
|
||||||
Self::Axis { .. } => PointerEventType::AXIS,
|
Self::Axis { .. } => PointerEventType::Axis,
|
||||||
Self::Frame { .. } => PointerEventType::FRAME,
|
Self::Frame { .. } => PointerEventType::Frame,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,28 +151,29 @@ impl PointerEvent {
|
|||||||
impl KeyboardEvent {
|
impl KeyboardEvent {
|
||||||
fn event_type(&self) -> KeyboardEventType {
|
fn event_type(&self) -> KeyboardEventType {
|
||||||
match self {
|
match self {
|
||||||
KeyboardEvent::Key { .. } => KeyboardEventType::KEY,
|
KeyboardEvent::Key { .. } => KeyboardEventType::Key,
|
||||||
KeyboardEvent::Modifiers { .. } => KeyboardEventType::MODIFIERS,
|
KeyboardEvent::Modifiers { .. } => KeyboardEventType::Modifiers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PointerEventType {
|
enum PointerEventType {
|
||||||
MOTION,
|
Motion,
|
||||||
BUTTON,
|
Button,
|
||||||
AXIS,
|
Axis,
|
||||||
FRAME,
|
Frame,
|
||||||
}
|
}
|
||||||
enum KeyboardEventType {
|
enum KeyboardEventType {
|
||||||
KEY,
|
Key,
|
||||||
MODIFIERS,
|
Modifiers,
|
||||||
}
|
}
|
||||||
enum EventType {
|
enum EventType {
|
||||||
POINTER,
|
Pointer,
|
||||||
KEYBOARD,
|
Keyboard,
|
||||||
RELEASE,
|
Enter,
|
||||||
PING,
|
Leave,
|
||||||
PONG,
|
Ping,
|
||||||
|
Pong,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for PointerEventType {
|
impl TryFrom<u8> for PointerEventType {
|
||||||
@@ -163,10 +181,10 @@ impl TryFrom<u8> for PointerEventType {
|
|||||||
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
x if x == Self::MOTION as u8 => Ok(Self::MOTION),
|
x if x == Self::Motion as u8 => Ok(Self::Motion),
|
||||||
x if x == Self::BUTTON as u8 => Ok(Self::BUTTON),
|
x if x == Self::Button as u8 => Ok(Self::Button),
|
||||||
x if x == Self::AXIS as u8 => Ok(Self::AXIS),
|
x if x == Self::Axis as u8 => Ok(Self::Axis),
|
||||||
x if x == Self::FRAME as u8 => Ok(Self::FRAME),
|
x if x == Self::Frame as u8 => Ok(Self::Frame),
|
||||||
_ => Err(Box::new(ProtocolError {
|
_ => Err(Box::new(ProtocolError {
|
||||||
msg: format!("invalid pointer event type {}", value),
|
msg: format!("invalid pointer event type {}", value),
|
||||||
})),
|
})),
|
||||||
@@ -179,8 +197,8 @@ impl TryFrom<u8> for KeyboardEventType {
|
|||||||
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
x if x == Self::KEY as u8 => Ok(Self::KEY),
|
x if x == Self::Key as u8 => Ok(Self::Key),
|
||||||
x if x == Self::MODIFIERS as u8 => Ok(Self::MODIFIERS),
|
x if x == Self::Modifiers as u8 => Ok(Self::Modifiers),
|
||||||
_ => Err(Box::new(ProtocolError {
|
_ => Err(Box::new(ProtocolError {
|
||||||
msg: format!("invalid keyboard event type {}", value),
|
msg: format!("invalid keyboard event type {}", value),
|
||||||
})),
|
})),
|
||||||
@@ -188,17 +206,18 @@ impl TryFrom<u8> for KeyboardEventType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Vec<u8>> for &Event {
|
impl From<&Event> for Vec<u8> {
|
||||||
fn into(self) -> Vec<u8> {
|
fn from(event: &Event) -> Self {
|
||||||
let event_id = vec![self.event_type() as u8];
|
let event_id = vec![event.event_type() as u8];
|
||||||
let event_data = match self {
|
let event_data = match event {
|
||||||
Event::Pointer(p) => p.into(),
|
Event::Pointer(p) => p.into(),
|
||||||
Event::Keyboard(k) => k.into(),
|
Event::Keyboard(k) => k.into(),
|
||||||
Event::Release() => vec![],
|
Event::Enter() => vec![],
|
||||||
|
Event::Leave() => vec![],
|
||||||
Event::Ping() => vec![],
|
Event::Ping() => vec![],
|
||||||
Event::Pong() => vec![],
|
Event::Pong() => vec![],
|
||||||
};
|
};
|
||||||
vec![event_id, event_data].concat()
|
[event_id, event_data].concat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,11 +239,12 @@ impl TryFrom<Vec<u8>> for Event {
|
|||||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
let event_id = u8::from_be_bytes(value[..1].try_into()?);
|
let event_id = u8::from_be_bytes(value[..1].try_into()?);
|
||||||
match event_id {
|
match event_id {
|
||||||
i if i == (EventType::POINTER as u8) => Ok(Event::Pointer(value.try_into()?)),
|
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::Keyboard as u8) => Ok(Event::Keyboard(value.try_into()?)),
|
||||||
i if i == (EventType::RELEASE as u8) => Ok(Event::Release()),
|
i if i == (EventType::Enter as u8) => Ok(Event::Enter()),
|
||||||
i if i == (EventType::PING as u8) => Ok(Event::Ping()),
|
i if i == (EventType::Leave as u8) => Ok(Event::Leave()),
|
||||||
i if i == (EventType::PONG as u8) => Ok(Event::Pong()),
|
i if i == (EventType::Ping as u8) => Ok(Event::Ping()),
|
||||||
|
i if i == (EventType::Pong as u8) => Ok(Event::Pong()),
|
||||||
_ => Err(Box::new(ProtocolError {
|
_ => Err(Box::new(ProtocolError {
|
||||||
msg: format!("invalid event_id {}", event_id),
|
msg: format!("invalid event_id {}", event_id),
|
||||||
})),
|
})),
|
||||||
@@ -232,10 +252,10 @@ impl TryFrom<Vec<u8>> for Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Vec<u8>> for &PointerEvent {
|
impl From<&PointerEvent> for Vec<u8> {
|
||||||
fn into(self) -> Vec<u8> {
|
fn from(event: &PointerEvent) -> Self {
|
||||||
let id = vec![self.event_type() as u8];
|
let id = vec![event.event_type() as u8];
|
||||||
let data = match self {
|
let data = match event {
|
||||||
PointerEvent::Motion {
|
PointerEvent::Motion {
|
||||||
time,
|
time,
|
||||||
relative_x,
|
relative_x,
|
||||||
@@ -244,7 +264,7 @@ impl Into<Vec<u8>> for &PointerEvent {
|
|||||||
let time = time.to_be_bytes();
|
let time = time.to_be_bytes();
|
||||||
let relative_x = relative_x.to_be_bytes();
|
let relative_x = relative_x.to_be_bytes();
|
||||||
let relative_y = relative_y.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 {
|
PointerEvent::Button {
|
||||||
time,
|
time,
|
||||||
@@ -254,19 +274,19 @@ impl Into<Vec<u8>> for &PointerEvent {
|
|||||||
let time = time.to_be_bytes();
|
let time = time.to_be_bytes();
|
||||||
let button = button.to_be_bytes();
|
let button = button.to_be_bytes();
|
||||||
let state = state.to_be_bytes();
|
let state = state.to_be_bytes();
|
||||||
vec![&time[..], &button[..], &state[..]].concat()
|
[&time[..], &button[..], &state[..]].concat()
|
||||||
}
|
}
|
||||||
PointerEvent::Axis { time, axis, value } => {
|
PointerEvent::Axis { time, axis, value } => {
|
||||||
let time = time.to_be_bytes();
|
let time = time.to_be_bytes();
|
||||||
let axis = axis.to_be_bytes();
|
let axis = axis.to_be_bytes();
|
||||||
let value = value.to_be_bytes();
|
let value = value.to_be_bytes();
|
||||||
vec![&time[..], &axis[..], &value[..]].concat()
|
[&time[..], &axis[..], &value[..]].concat()
|
||||||
}
|
}
|
||||||
PointerEvent::Frame {} => {
|
PointerEvent::Frame {} => {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
vec![id, data].concat()
|
[id, data].concat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +301,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
|||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
match event_type {
|
match event_type {
|
||||||
PointerEventType::MOTION => {
|
PointerEventType::Motion => {
|
||||||
let time = match data.get(2..6) {
|
let time = match data.get(2..6) {
|
||||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||||
None => {
|
None => {
|
||||||
@@ -312,7 +332,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
|||||||
relative_y,
|
relative_y,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
PointerEventType::BUTTON => {
|
PointerEventType::Button => {
|
||||||
let time = match data.get(2..6) {
|
let time = match data.get(2..6) {
|
||||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||||
None => {
|
None => {
|
||||||
@@ -343,7 +363,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
|||||||
state,
|
state,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
PointerEventType::AXIS => {
|
PointerEventType::Axis => {
|
||||||
let time = match data.get(2..6) {
|
let time = match data.get(2..6) {
|
||||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||||
None => {
|
None => {
|
||||||
@@ -370,7 +390,7 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
|||||||
};
|
};
|
||||||
Ok(Self::Axis { time, axis, value })
|
Ok(Self::Axis { time, axis, value })
|
||||||
}
|
}
|
||||||
PointerEventType::FRAME => Ok(Self::Frame {}),
|
PointerEventType::Frame => Ok(Self::Frame {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => Err(Box::new(ProtocolError {
|
None => Err(Box::new(ProtocolError {
|
||||||
@@ -380,15 +400,15 @@ impl TryFrom<Vec<u8>> for PointerEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Vec<u8>> for &KeyboardEvent {
|
impl From<&KeyboardEvent> for Vec<u8> {
|
||||||
fn into(self) -> Vec<u8> {
|
fn from(event: &KeyboardEvent) -> Self {
|
||||||
let id = vec![self.event_type() as u8];
|
let id = vec![event.event_type() as u8];
|
||||||
let data = match self {
|
let data = match event {
|
||||||
KeyboardEvent::Key { time, key, state } => {
|
KeyboardEvent::Key { time, key, state } => {
|
||||||
let time = time.to_be_bytes();
|
let time = time.to_be_bytes();
|
||||||
let key = key.to_be_bytes();
|
let key = key.to_be_bytes();
|
||||||
let state = state.to_be_bytes();
|
let state = state.to_be_bytes();
|
||||||
vec![&time[..], &key[..], &state[..]].concat()
|
[&time[..], &key[..], &state[..]].concat()
|
||||||
}
|
}
|
||||||
KeyboardEvent::Modifiers {
|
KeyboardEvent::Modifiers {
|
||||||
mods_depressed,
|
mods_depressed,
|
||||||
@@ -400,7 +420,7 @@ impl Into<Vec<u8>> for &KeyboardEvent {
|
|||||||
let mods_latched = mods_latched.to_be_bytes();
|
let mods_latched = mods_latched.to_be_bytes();
|
||||||
let mods_locked = mods_locked.to_be_bytes();
|
let mods_locked = mods_locked.to_be_bytes();
|
||||||
let group = group.to_be_bytes();
|
let group = group.to_be_bytes();
|
||||||
vec![
|
[
|
||||||
&mods_depressed[..],
|
&mods_depressed[..],
|
||||||
&mods_latched[..],
|
&mods_latched[..],
|
||||||
&mods_locked[..],
|
&mods_locked[..],
|
||||||
@@ -409,7 +429,7 @@ impl Into<Vec<u8>> for &KeyboardEvent {
|
|||||||
.concat()
|
.concat()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
vec![id, data].concat()
|
[id, data].concat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +444,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
|||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
match event_type {
|
match event_type {
|
||||||
KeyboardEventType::KEY => {
|
KeyboardEventType::Key => {
|
||||||
let time = match data.get(2..6) {
|
let time = match data.get(2..6) {
|
||||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||||
None => {
|
None => {
|
||||||
@@ -451,7 +471,7 @@ impl TryFrom<Vec<u8>> for KeyboardEvent {
|
|||||||
};
|
};
|
||||||
Ok(KeyboardEvent::Key { time, key, state })
|
Ok(KeyboardEvent::Key { time, key, state })
|
||||||
}
|
}
|
||||||
KeyboardEventType::MODIFIERS => {
|
KeyboardEventType::Modifiers => {
|
||||||
let mods_depressed = match data.get(2..6) {
|
let mods_depressed = match data.get(2..6) {
|
||||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ use crate::{
|
|||||||
pub mod cli;
|
pub mod cli;
|
||||||
|
|
||||||
/// gtk frontend
|
/// gtk frontend
|
||||||
#[cfg(all(unix, feature = "gtk"))]
|
#[cfg(feature = "gtk")]
|
||||||
pub mod gtk;
|
pub mod gtk;
|
||||||
|
|
||||||
pub fn run_frontend(config: &Config) -> Result<()> {
|
pub fn run_frontend(config: &Config) -> Result<()> {
|
||||||
match config.frontend {
|
match config.frontend {
|
||||||
#[cfg(all(unix, feature = "gtk"))]
|
#[cfg(feature = "gtk")]
|
||||||
Frontend::Gtk => {
|
Frontend::Gtk => {
|
||||||
gtk::run();
|
gtk::run();
|
||||||
}
|
}
|
||||||
#[cfg(any(not(feature = "gtk"), not(unix)))]
|
#[cfg(not(feature = "gtk"))]
|
||||||
Frontend::Gtk => panic!("gtk frontend requested but feature not enabled!"),
|
Frontend::Gtk => panic!("gtk frontend requested but feature not enabled!"),
|
||||||
Frontend::Cli => {
|
Frontend::Cli => {
|
||||||
cli::run()?;
|
cli::run()?;
|
||||||
@@ -201,8 +201,6 @@ impl FrontendListener {
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub async fn accept(&mut self) -> Result<ReadHalf<UnixStream>> {
|
pub async fn accept(&mut self) -> Result<ReadHalf<UnixStream>> {
|
||||||
log::trace!("frontend.accept()");
|
|
||||||
|
|
||||||
let stream = self.listener.accept().await?.0;
|
let stream = self.listener.accept().await?.0;
|
||||||
let (rx, tx) = tokio::io::split(stream);
|
let (rx, tx) = tokio::io::split(stream);
|
||||||
self.tx_streams.push(tx);
|
self.tx_streams.push(tx);
|
||||||
@@ -229,11 +227,11 @@ impl FrontendListener {
|
|||||||
// TODO do simultaneously
|
// TODO do simultaneously
|
||||||
for tx in self.tx_streams.iter_mut() {
|
for tx in self.tx_streams.iter_mut() {
|
||||||
// write len + payload
|
// write len + payload
|
||||||
if let Err(_) = tx.write(&len).await {
|
if tx.write(&len).await.is_err() {
|
||||||
keep.push(false);
|
keep.push(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Err(_) = tx.write(payload).await {
|
if tx.write(payload).await.is_err() {
|
||||||
keep.push(false);
|
keep.push(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,17 @@
|
|||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
#[cfg(windows)]
|
|
||||||
use std::net::SocketAddrV4;
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{ErrorKind, Read, Write},
|
io::{ErrorKind, Read, Write},
|
||||||
str::SplitWhitespace,
|
str::SplitWhitespace,
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
use std::net::TcpStream;
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::net::UnixStream;
|
|
||||||
|
|
||||||
use crate::{client::Position, config::DEFAULT_PORT};
|
use crate::{client::Position, config::DEFAULT_PORT};
|
||||||
|
|
||||||
use super::{FrontendEvent, FrontendNotify};
|
use super::{FrontendEvent, FrontendNotify};
|
||||||
|
|
||||||
pub fn run() -> Result<()> {
|
pub fn run() -> Result<()> {
|
||||||
#[cfg(unix)]
|
let Ok(mut tx) = super::wait_for_service() else {
|
||||||
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 {
|
|
||||||
return Err(anyhow!("Could not connect to lan-mouse-socket"));
|
return Err(anyhow!("Could not connect to lan-mouse-socket"));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,7 +25,7 @@ pub fn run() -> Result<()> {
|
|||||||
loop {
|
loop {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
match std::io::stdin().read_line(&mut buf) {
|
match std::io::stdin().read_line(&mut buf) {
|
||||||
Ok(0) => break,
|
Ok(0) => return,
|
||||||
Ok(len) => {
|
Ok(len) => {
|
||||||
if let Some(events) = parse_cmd(buf, len) {
|
if let Some(events) = parse_cmd(buf, len) {
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
@@ -63,14 +48,16 @@ pub fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("error reading from stdin: {e}");
|
if e.kind() != ErrorKind::UnexpectedEof {
|
||||||
break;
|
log::error!("error reading from stdin: {e}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let writer = thread::Builder::new()
|
let _ = thread::Builder::new()
|
||||||
.name("cli-frontend-notify".to_string())
|
.name("cli-frontend-notify".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
@@ -139,7 +126,7 @@ pub fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
match reader.join() {
|
match reader.join() {
|
||||||
Ok(_) => (),
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = match (e.downcast_ref::<&str>(), e.downcast_ref::<String>()) {
|
let msg = match (e.downcast_ref::<&str>(), e.downcast_ref::<String>()) {
|
||||||
(Some(&s), _) => s,
|
(Some(&s), _) => s,
|
||||||
@@ -149,17 +136,6 @@ pub fn run() -> Result<()> {
|
|||||||
log::error!("reader thread paniced: {msg}");
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use adw::Application;
|
|||||||
use gtk::{
|
use gtk::{
|
||||||
gdk::Display,
|
gdk::Display,
|
||||||
gio::{SimpleAction, SimpleActionGroup},
|
gio::{SimpleAction, SimpleActionGroup},
|
||||||
glib::{clone, MainContext, Priority},
|
glib::clone,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
subclass::prelude::ObjectSubclassIsExt,
|
subclass::prelude::ObjectSubclassIsExt,
|
||||||
CssProvider, IconTheme,
|
CssProvider, IconTheme,
|
||||||
@@ -27,7 +27,17 @@ use super::FrontendNotify;
|
|||||||
|
|
||||||
pub fn run() -> glib::ExitCode {
|
pub fn run() -> glib::ExitCode {
|
||||||
log::debug!("running gtk frontend");
|
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();
|
let ret = gtk_main();
|
||||||
|
|
||||||
log::debug!("frontend exited");
|
log::debug!("frontend exited");
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
@@ -81,7 +91,7 @@ fn build_ui(app: &Application) {
|
|||||||
};
|
};
|
||||||
log::debug!("connected to lan-mouse-socket");
|
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 || {
|
gio::spawn_blocking(move || {
|
||||||
match loop {
|
match loop {
|
||||||
@@ -105,7 +115,7 @@ fn build_ui(app: &Application) {
|
|||||||
// parse json
|
// parse json
|
||||||
let json = str::from_utf8(&buf).unwrap();
|
let json = str::from_utf8(&buf).unwrap();
|
||||||
match serde_json::from_str(json) {
|
match serde_json::from_str(json) {
|
||||||
Ok(notify) => sender.send(notify).unwrap(),
|
Ok(notify) => sender.send_blocking(notify).unwrap(),
|
||||||
Err(e) => log::error!("{e}"),
|
Err(e) => log::error!("{e}"),
|
||||||
}
|
}
|
||||||
} {
|
} {
|
||||||
@@ -116,8 +126,9 @@ fn build_ui(app: &Application) {
|
|||||||
|
|
||||||
let window = Window::new(app);
|
let window = Window::new(app);
|
||||||
window.imp().stream.borrow_mut().replace(tx);
|
window.imp().stream.borrow_mut().replace(tx);
|
||||||
receiver.attach(None, clone!(@weak window => @default-return glib::ControlFlow::Break,
|
glib::spawn_future_local(clone!(@weak window => async move {
|
||||||
move |notify| {
|
loop {
|
||||||
|
let notify = receiver.recv().await.unwrap();
|
||||||
match notify {
|
match notify {
|
||||||
FrontendNotify::NotifyClientCreate(client, hostname, port, position) => {
|
FrontendNotify::NotifyClientCreate(client, hostname, port, position) => {
|
||||||
window.new_client(client, hostname, port, position, false);
|
window.new_client(client, hostname, port, position, false);
|
||||||
@@ -158,9 +169,8 @@ fn build_ui(app: &Application) {
|
|||||||
window.imp().set_port(port);
|
window.imp().set_port(port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glib::ControlFlow::Continue
|
|
||||||
}
|
}
|
||||||
));
|
}));
|
||||||
|
|
||||||
let action_request_client_update =
|
let action_request_client_update =
|
||||||
SimpleAction::new("request-client-update", Some(&u32::static_variant_type()));
|
SimpleAction::new("request-client-update", Some(&u32::static_variant_type()));
|
||||||
@@ -175,7 +185,7 @@ fn build_ui(app: &Application) {
|
|||||||
let index = param.unwrap()
|
let index = param.unwrap()
|
||||||
.get::<u32>()
|
.get::<u32>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let Some(client) = window.clients().item(index as u32) else {
|
let Some(client) = window.clients().item(index) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let client = client.downcast_ref::<ClientObject>().unwrap();
|
let client = client.downcast_ref::<ClientObject>().unwrap();
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl ClientRow {
|
|||||||
let port_binding = client_object
|
let port_binding = client_object
|
||||||
.bind_property("port", &self.imp().port.get(), "text")
|
.bind_property("port", &self.imp().port.get(), "text")
|
||||||
.transform_from(|_, v: String| {
|
.transform_from(|_, v: String| {
|
||||||
if v == "" {
|
if v.is_empty() {
|
||||||
Some(DEFAULT_PORT as u32)
|
Some(DEFAULT_PORT as u32)
|
||||||
} else {
|
} else {
|
||||||
Some(v.parse::<u16>().unwrap_or(DEFAULT_PORT) as u32)
|
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> {
|
pub fn client_idx(&self, handle: ClientHandle) -> Option<usize> {
|
||||||
self.clients()
|
self.clients().iter::<ClientObject>().position(|c| {
|
||||||
.iter::<ClientObject>()
|
if let Ok(c) = c {
|
||||||
.position(|c| {
|
c.handle() == handle
|
||||||
if let Ok(c) = c {
|
} else {
|
||||||
c.handle() == handle
|
false
|
||||||
} else {
|
}
|
||||||
false
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|p| p as usize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_client(&self, handle: ClientHandle) {
|
pub fn delete_client(&self, handle: ClientHandle) {
|
||||||
@@ -117,7 +114,7 @@ impl Window {
|
|||||||
|
|
||||||
pub fn request_port_change(&self) {
|
pub fn request_port_change(&self) {
|
||||||
let port = self.imp().port_entry.get().text().to_string();
|
let port = self.imp().port_entry.get().text().to_string();
|
||||||
if let Ok(port) = u16::from_str_radix(port.as_str(), 10) {
|
if let Ok(port) = port.as_str().parse::<u16>() {
|
||||||
self.request(FrontendEvent::ChangePort(port));
|
self.request(FrontendEvent::ChangePort(port));
|
||||||
} else {
|
} else {
|
||||||
self.request(FrontendEvent::ChangePort(DEFAULT_PORT));
|
self.request(FrontendEvent::ChangePort(DEFAULT_PORT));
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::{
|
use std::cell::{Cell, RefCell};
|
||||||
cell::{Cell, RefCell},
|
|
||||||
os::unix::net::UnixStream,
|
#[cfg(windows)]
|
||||||
};
|
use std::net::TcpStream;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use adw::{
|
use adw::{
|
||||||
@@ -29,7 +31,10 @@ pub struct Window {
|
|||||||
#[template_child]
|
#[template_child]
|
||||||
pub toast_overlay: TemplateChild<ToastOverlay>,
|
pub toast_overlay: TemplateChild<ToastOverlay>,
|
||||||
pub clients: RefCell<Option<gio::ListStore>>,
|
pub clients: RefCell<Option<gio::ListStore>>,
|
||||||
|
#[cfg(unix)]
|
||||||
pub stream: RefCell<Option<UnixStream>>,
|
pub stream: RefCell<Option<UnixStream>>,
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub stream: RefCell<Option<TcpStream>>,
|
||||||
pub port: Cell<u16>,
|
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 backend;
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
pub mod ioutils;
|
pub mod scancode;
|
||||||
|
|||||||
44
src/main.rs
44
src/main.rs
@@ -2,15 +2,9 @@ use anyhow::Result;
|
|||||||
use std::process::{self, Child, Command};
|
use std::process::{self, Child, Command};
|
||||||
|
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
use lan_mouse::{
|
use lan_mouse::{config::Config, frontend, server::Server};
|
||||||
config::Config,
|
|
||||||
consumer,
|
|
||||||
frontend::{self, FrontendListener},
|
|
||||||
producer,
|
|
||||||
server::Server,
|
|
||||||
};
|
|
||||||
|
|
||||||
use tokio::{join, task::LocalSet};
|
use tokio::task::LocalSet;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
// init logging
|
// init logging
|
||||||
@@ -42,8 +36,19 @@ pub fn run() -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
// otherwise start the service as a child process and
|
// otherwise start the service as a child process and
|
||||||
// run a frontend
|
// run a frontend
|
||||||
start_service()?;
|
let mut service = start_service()?;
|
||||||
frontend::run_frontend(&config)?;
|
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(())
|
anyhow::Ok(())
|
||||||
@@ -58,27 +63,10 @@ fn run_service(config: &Config) -> Result<()> {
|
|||||||
|
|
||||||
// run async event loop
|
// run async event loop
|
||||||
runtime.block_on(LocalSet::new().run_until(async {
|
runtime.block_on(LocalSet::new().run_until(async {
|
||||||
// create frontend communication adapter
|
// run main loop
|
||||||
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?;
|
|
||||||
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
|
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
|
||||||
|
Server::run(config).await?;
|
||||||
|
|
||||||
// run event loop
|
|
||||||
event_server.run().await?;
|
|
||||||
log::debug!("service exiting");
|
log::debug!("service exiting");
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
}))?;
|
}))?;
|
||||||
|
|||||||
106
src/producer.rs
106
src/producer.rs
@@ -1,4 +1,3 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
@@ -9,77 +8,48 @@ use crate::{
|
|||||||
event::Event,
|
event::Event,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
pub async fn create() -> Box<dyn EventProducer> {
|
||||||
use std::env;
|
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
|
||||||
enum Backend {
|
|
||||||
LayerShell,
|
|
||||||
Libei,
|
|
||||||
X11,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create() -> Result<Box<dyn EventProducer>> {
|
|
||||||
#[cfg(target_os = "macos")]
|
#[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)]
|
#[cfg(windows)]
|
||||||
return Ok(Box::new(producer::windows::WindowsProducer::new()));
|
match producer::windows::WindowsProducer::new() {
|
||||||
|
Ok(p) => return Box::new(p),
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
Err(e) => log::info!("windows event producer not available: {e}"),
|
||||||
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()?))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 {
|
pub trait EventProducer: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin {
|
||||||
|
|||||||
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(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1065
src/server.rs
1065
src/server.rs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user