mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-25 14:10:55 +03:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66bce9083e | ||
|
|
102b64f2b4 | ||
|
|
4b499742ad | ||
|
|
a86d74b52c | ||
|
|
c25a15e2d8 | ||
|
|
8b82325bdb | ||
|
|
5415205c83 | ||
|
|
1666fb8b7b | ||
|
|
9afe7da0dd | ||
|
|
f7c59e40c9 | ||
|
|
5be5b0ad7c | ||
|
|
8ed4520172 | ||
|
|
9e56c546cd | ||
|
|
daf8818a9f | ||
|
|
6eaa199503 | ||
|
|
8ff991aefe | ||
|
|
abf95afb9f | ||
|
|
9a75a7622e | ||
|
|
0196cfe56c | ||
|
|
097468f708 | ||
|
|
3470abc03a | ||
|
|
a7397ad4f4 | ||
|
|
9889b49f10 |
24
.github/workflows/cachix.yml
vendored
Normal file
24
.github/workflows/cachix.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Binary Cache
|
||||||
|
|
||||||
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
jobs:
|
||||||
|
nix:
|
||||||
|
name: "Build"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
with:
|
||||||
|
logger: pretty
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
- uses: cachix/cachix-action@v14
|
||||||
|
with:
|
||||||
|
name: lan-mouse
|
||||||
|
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||||
|
|
||||||
|
- name: Build lan-mouse
|
||||||
|
run: nix build --print-build-logs
|
||||||
14
.github/workflows/pre-release.yml
vendored
14
.github/workflows/pre-release.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
linux-release-build:
|
linux-release-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
- name: Release Build
|
- name: Release Build
|
||||||
run: cargo build --release
|
run: cargo build --release
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-linux
|
name: lan-mouse-linux
|
||||||
path: target/release/lan-mouse
|
path: target/release/lan-mouse
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
gvsbuild build --msys-dir=C:\msys64 gtk4 libadwaita librsvg
|
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\usr\notbin" "C:\Program Files\Git\usr\bin"
|
||||||
Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin"
|
Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin"
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Release Build
|
- name: Release Build
|
||||||
run: cargo build --release
|
run: cargo build --release
|
||||||
- name: Create Archive
|
- name: Create Archive
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
Copy-Item -Path "target\release\lan-mouse.exe" -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
|
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@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-windows
|
name: lan-mouse-windows
|
||||||
path: lan-mouse-windows.zip
|
path: lan-mouse-windows.zip
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
macos-release-build:
|
macos-release-build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: brew install gtk4 libadwaita
|
run: brew install gtk4 libadwaita
|
||||||
- name: Release Build
|
- name: Release Build
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
cargo build --release
|
cargo build --release
|
||||||
cp target/release/lan-mouse lan-mouse-macos-intel
|
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@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-macos
|
name: lan-mouse-macos
|
||||||
path: lan-mouse-macos-intel
|
path: lan-mouse-macos-intel
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
steps:
|
steps:
|
||||||
- name: Download build artifacts
|
- name: Download build artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: "marvinpinto/action-automatic-releases@latest"
|
uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
with:
|
with:
|
||||||
|
|||||||
12
.github/workflows/rust.yml
vendored
12
.github/workflows/rust.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --all-features --all-targets -- --deny warnings
|
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@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse
|
name: lan-mouse
|
||||||
path: target/debug/lan-mouse
|
path: target/debug/lan-mouse
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
- name: Copy Gtk Dlls
|
- name: Copy Gtk Dlls
|
||||||
run: Get-Childitem -Path "C:\\gtk-build\\gtk\\x64\\release\\bin\\*.dll" -File -Recurse | Copy-Item -Destination "target\\debug"
|
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@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-windows
|
name: lan-mouse-windows
|
||||||
path: |
|
path: |
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
build-macos:
|
build-macos:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: brew install gtk4 libadwaita
|
run: brew install gtk4 libadwaita
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -106,7 +106,7 @@ jobs:
|
|||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --all-features --all-targets -- --deny warnings
|
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@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-macos
|
name: lan-mouse-macos
|
||||||
path: target/debug/lan-mouse
|
path: target/debug/lan-mouse
|
||||||
|
|||||||
14
.github/workflows/tagged-release.yml
vendored
14
.github/workflows/tagged-release.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
linux-release-build:
|
linux-release-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: Release Build
|
- name: Release Build
|
||||||
run: cargo build --release
|
run: cargo build --release
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-linux
|
name: lan-mouse-linux
|
||||||
path: target/release/lan-mouse
|
path: target/release/lan-mouse
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
gvsbuild build --msys-dir=C:\msys64 gtk4 libadwaita librsvg
|
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\usr\notbin" "C:\Program Files\Git\usr\bin"
|
||||||
Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin"
|
Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin"
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Release Build
|
- name: Release Build
|
||||||
run: cargo build --release
|
run: cargo build --release
|
||||||
- name: Create Archive
|
- name: Create Archive
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
Copy-Item -Path "target\release\lan-mouse.exe" -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
|
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@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-windows
|
name: lan-mouse-windows
|
||||||
path: lan-mouse-windows.zip
|
path: lan-mouse-windows.zip
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
macos-release-build:
|
macos-release-build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: brew install gtk4 libadwaita
|
run: brew install gtk4 libadwaita
|
||||||
- name: Release Build
|
- name: Release Build
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
cargo build --release
|
cargo build --release
|
||||||
cp target/release/lan-mouse lan-mouse-macos-intel
|
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@v4
|
||||||
with:
|
with:
|
||||||
name: lan-mouse-macos
|
name: lan-mouse-macos
|
||||||
path: lan-mouse-macos-intel
|
path: lan-mouse-macos-intel
|
||||||
@@ -95,7 +95,7 @@ jobs:
|
|||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
steps:
|
steps:
|
||||||
- name: Download build artifacts
|
- name: Download build artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
- name: "Create Release"
|
- name: "Create Release"
|
||||||
uses: "marvinpinto/action-automatic-releases@latest"
|
uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
with:
|
with:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
|||||||
.gdbinit
|
.gdbinit
|
||||||
.idea/
|
.idea/
|
||||||
.vs/
|
.vs/
|
||||||
|
.vscode/
|
||||||
|
.direnv/
|
||||||
|
result
|
||||||
917
Cargo.lock
generated
917
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
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.6.0"
|
version = "0.7.2"
|
||||||
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"
|
||||||
@@ -20,17 +20,18 @@ 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.11.3"
|
||||||
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 }
|
gtk = { package = "gtk4", version = "0.8.1", features = ["v4_2"], optional = true }
|
||||||
adw = { package = "libadwaita", version = "0.5.2", features = ["v1_1"], optional = true }
|
adw = { package = "libadwaita", version = "0.6.0", features = ["v1_1"], optional = true }
|
||||||
async-channel = { version = "2.1.1", optional = true }
|
async-channel = { version = "2.1.1", optional = true }
|
||||||
keycode = "0.4.0"
|
keycode = "0.4.0"
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2.148"
|
libc = "0.2.148"
|
||||||
@@ -41,8 +42,8 @@ wayland-protocols = { version="0.31.0", features=["client", "staging", "unstable
|
|||||||
wayland-protocols-wlr = { version="0.2.0", features=["client"], optional = true }
|
wayland-protocols-wlr = { version="0.2.0", features=["client"], optional = true }
|
||||||
wayland-protocols-misc = { version="0.2.0", features=["client"], optional = true }
|
wayland-protocols-misc = { version="0.2.0", features=["client"], optional = true }
|
||||||
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
|
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.8", default-features = false, features = ["tokio"], optional = true }
|
||||||
reis = { git = "https://github.com/ids1024/reis", features = [ "tokio" ], optional = true }
|
reis = { version = "0.2", features = [ "tokio" ], 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"] }
|
||||||
@@ -51,7 +52,7 @@ core-graphics = { version = "0.23", features = ["highsierra"] }
|
|||||||
winapi = { version = "0.3.9", features = ["winuser"] }
|
winapi = { version = "0.3.9", features = ["winuser"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
glib-build-tools = "0.18.0"
|
glib-build-tools = "0.19.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wayland", "x11", "xdg_desktop_portal", "libei", "gtk"]
|
default = ["wayland", "x11", "xdg_desktop_portal", "libei", "gtk"]
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -43,12 +43,17 @@ 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: | :heavy_check_mark: |
|
||||||
| X11 | :heavy_check_mark: | 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 |
|
||||||
|
|
||||||
> [!Important]
|
> [!Important]
|
||||||
|
> Gnome -> Sway only partially works (modifier events are not handled correctly)
|
||||||
|
|
||||||
|
> [!Important]
|
||||||
|
> **Wayfire**
|
||||||
|
>
|
||||||
> If you are using [Wayfire](https://github.com/WayfireWM/wayfire), make sure to use a recent version (must be newer than October 23rd) and **add `shortcuts-inhibit` to the list of plugins in your wayfire config!**
|
> If you are using [Wayfire](https://github.com/WayfireWM/wayfire), make sure to use a recent version (must be newer than October 23rd) and **add `shortcuts-inhibit` to the list of plugins in your wayfire config!**
|
||||||
> Otherwise input capture will not work.
|
> Otherwise input capture will not work.
|
||||||
|
|
||||||
@@ -69,6 +74,10 @@ paru -S lan-mouse-git
|
|||||||
paru -S lan-mouse-bin
|
paru -S lan-mouse-bin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Nix
|
||||||
|
- nixpkgs: [search.nixos.org](https://search.nixos.org/packages?channel=unstable&show=lan-mouse&from=0&size=50&sort=relevance&type=packages&query=lan-mouse)
|
||||||
|
- flake: [README.md](./nix/README.md)
|
||||||
|
|
||||||
|
|
||||||
### Building from Source
|
### Building from Source
|
||||||
|
|
||||||
@@ -96,7 +105,7 @@ gtk-update-icon-cache /usr/local/share/icons/hicolor/
|
|||||||
|
|
||||||
# install desktop entry
|
# install desktop entry
|
||||||
sudo mkdir -p /usr/local/share/applications
|
sudo mkdir -p /usr/local/share/applications
|
||||||
sudo cp de.feschber.LanMouse.dekstop /usr/local/share/applications
|
sudo cp de.feschber.LanMouse.desktop /usr/local/share/applications
|
||||||
|
|
||||||
# when using firewalld: install firewall rule
|
# when using firewalld: install firewall rule
|
||||||
sudo cp firewall/lan-mouse.xml /etc/firewalld/services
|
sudo cp firewall/lan-mouse.xml /etc/firewalld/services
|
||||||
@@ -161,7 +170,7 @@ Build gtk from source
|
|||||||
# install chocolatey
|
# install chocolatey
|
||||||
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
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)
|
# install python 3.11 (Version is important, as 3.12 does not work currently) -> Has been fixed recently
|
||||||
choco install python --version=3.11.0
|
choco install python --version=3.11.0
|
||||||
|
|
||||||
# install git
|
# install git
|
||||||
@@ -180,6 +189,10 @@ choco install visualstudio2022-workload-vctools
|
|||||||
# install gvsbuild with python
|
# install gvsbuild with python
|
||||||
python -m pip install --user pipx
|
python -m pip install --user pipx
|
||||||
python -m pipx ensurepath
|
python -m pipx ensurepath
|
||||||
|
```
|
||||||
|
|
||||||
|
- Relaunch your powershell instance so the changes in the environment are reflected.
|
||||||
|
```sh
|
||||||
pipx install gvsbuild
|
pipx install gvsbuild
|
||||||
|
|
||||||
# build gtk + libadwaita
|
# build gtk + libadwaita
|
||||||
@@ -243,10 +256,17 @@ To automatically load clients on startup, the file `$XDG_CONFIG_HOME/lan-mouse/c
|
|||||||
To create this file you can copy the following example config:
|
To create this file you can copy the following example config:
|
||||||
|
|
||||||
### Example config
|
### Example config
|
||||||
|
> [!TIP]
|
||||||
|
> key symbols in the release bind are named according
|
||||||
|
> to their names in [src/scancode.rs#L172](src/scancode.rs#L172).
|
||||||
|
> This is bound to change
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# example configuration
|
# example configuration
|
||||||
|
|
||||||
|
# configure release bind
|
||||||
|
release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ]
|
||||||
|
|
||||||
# optional port (defaults to 4242)
|
# optional port (defaults to 4242)
|
||||||
port = 4242
|
port = 4242
|
||||||
# # optional frontend -> defaults to gtk if available
|
# # optional frontend -> defaults to gtk if available
|
||||||
@@ -281,11 +301,11 @@ Where `left` can be either `left`, `right`, `top` or `bottom`.
|
|||||||
- [x] IP Address switching
|
- [x] IP Address switching
|
||||||
- [x] Liveness tracking Automatically ungrab mouse when client unreachable
|
- [x] Liveness tracking Automatically ungrab mouse when client unreachable
|
||||||
- [x] Liveness tracking: Automatically release keys, when server offline
|
- [x] Liveness tracking: Automatically release keys, when server offline
|
||||||
- [ ] Libei Input Capture
|
- [x] MacOS KeyCode Translation
|
||||||
|
- [x] Libei Input Capture
|
||||||
- [ ] X11 Input Capture
|
- [ ] X11 Input Capture
|
||||||
- [ ] Windows Input Capture
|
- [ ] Windows Input Capture
|
||||||
- [ ] MacOS Input Capture
|
- [ ] MacOS Input Capture
|
||||||
- [ ] MacOS KeyCode Translation
|
|
||||||
- [ ] Latency measurement and visualization
|
- [ ] Latency measurement and visualization
|
||||||
- [ ] Bandwidth usage measurement and visualization
|
- [ ] Bandwidth usage measurement and visualization
|
||||||
- [ ] Clipboard support
|
- [ ] Clipboard support
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# example configuration
|
# example configuration
|
||||||
|
|
||||||
|
# release bind
|
||||||
|
release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ]
|
||||||
|
|
||||||
# optional port (defaults to 4242)
|
# optional port (defaults to 4242)
|
||||||
port = 4242
|
port = 4242
|
||||||
# optional frontend -> defaults to gtk if available
|
# optional frontend -> defaults to gtk if available
|
||||||
|
|||||||
82
flake.lock
generated
Normal file
82
flake.lock
generated
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705309234,
|
||||||
|
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710806803,
|
||||||
|
"narHash": "sha256-qrxvLS888pNJFwJdK+hf1wpRCSQcqA6W5+Ox202NDa0=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b06025f1533a1e07b6db3e75151caa155d1c7eb3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710987136,
|
||||||
|
"narHash": "sha256-Q8GRdlAIKZ8tJUXrbcRO1pA33AdoPfTUirsSnmGQnOU=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "97596b54ac34ad8184ca1eef44b1ec2e5c2b5f9e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
58
flake.nix
Normal file
58
flake.nix
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
description = "Nix Flake for lan-mouse";
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
rust-overlay = {
|
||||||
|
url = "github:oxalica/rust-overlay";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
outputs = {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
rust-overlay,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit (nixpkgs) lib;
|
||||||
|
genSystems = lib.genAttrs [
|
||||||
|
"x86_64-linux"
|
||||||
|
];
|
||||||
|
pkgsFor = system:
|
||||||
|
import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
|
||||||
|
overlays = [
|
||||||
|
rust-overlay.overlays.default
|
||||||
|
];
|
||||||
|
};
|
||||||
|
mkRustToolchain = pkgs:
|
||||||
|
pkgs.rust-bin.stable.latest.default.override {
|
||||||
|
extensions = ["rust-src"];
|
||||||
|
};
|
||||||
|
pkgs = genSystems (system: import nixpkgs {inherit system;});
|
||||||
|
in {
|
||||||
|
packages = genSystems (system: rec {
|
||||||
|
default = pkgs.${system}.callPackage ./nix {};
|
||||||
|
lan-mouse = default;
|
||||||
|
});
|
||||||
|
homeManagerModules.default = import ./nix/hm-module.nix self;
|
||||||
|
devShells = genSystems (system: let
|
||||||
|
pkgs = pkgsFor system;
|
||||||
|
rust = mkRustToolchain pkgs;
|
||||||
|
in {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
rust
|
||||||
|
rust-analyzer-unwrapped
|
||||||
|
pkg-config
|
||||||
|
xorg.libX11
|
||||||
|
gtk4
|
||||||
|
libadwaita
|
||||||
|
xorg.libXtst
|
||||||
|
];
|
||||||
|
|
||||||
|
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
40
nix/README.md
Normal file
40
nix/README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Nix Flake Usage
|
||||||
|
|
||||||
|
## run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run github:feschber/lan-mouse
|
||||||
|
|
||||||
|
# with params
|
||||||
|
nix run github:feschber/lan-mouse -- --help
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## home-manager module
|
||||||
|
|
||||||
|
add input
|
||||||
|
|
||||||
|
```nix
|
||||||
|
inputs = {
|
||||||
|
lan-mouse.url = "github:feschber/lan-mouse";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
enable lan-mouse
|
||||||
|
|
||||||
|
``` nix
|
||||||
|
{
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
# add the home manager module
|
||||||
|
imports = [inputs.lan-mouse.homeManagerModules.default];
|
||||||
|
|
||||||
|
programs.lan-mouse = {
|
||||||
|
enable = true;
|
||||||
|
# systemd = false;
|
||||||
|
# package = inputs.lan-mouse.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
42
nix/default.nix
Normal file
42
nix/default.nix
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
rustPlatform,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
}:
|
||||||
|
rustPlatform.buildRustPackage {
|
||||||
|
pname = "lan-mouse";
|
||||||
|
version = "0.7.0";
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
cmake
|
||||||
|
buildPackages.gtk4
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
xorg.libX11
|
||||||
|
gtk4
|
||||||
|
libadwaita
|
||||||
|
xorg.libXtst
|
||||||
|
];
|
||||||
|
|
||||||
|
src = builtins.path {
|
||||||
|
name = "lan-mouse";
|
||||||
|
path = lib.cleanSource ../.;
|
||||||
|
};
|
||||||
|
|
||||||
|
cargoLock.lockFile = ../Cargo.lock;
|
||||||
|
|
||||||
|
# Set Environment Variables
|
||||||
|
RUST_BACKTRACE = "full";
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Lan Mouse is a mouse and keyboard sharing software";
|
||||||
|
longDescription = ''
|
||||||
|
Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple pcs with a single set of mouse and keyboard. This is also known as a Software KVM switch.
|
||||||
|
The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details).
|
||||||
|
'';
|
||||||
|
mainProgram = "lan-mouse";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
50
nix/hm-module.nix
Normal file
50
nix/hm-module.nix
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
self: {
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with lib; let
|
||||||
|
cfg = config.programs.lan-mouse;
|
||||||
|
defaultPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||||
|
in {
|
||||||
|
options.programs.lan-mouse = with types; {
|
||||||
|
enable = mkEnableOption "Whether or not to enable lan-mouse.";
|
||||||
|
package = mkOption {
|
||||||
|
type = with types; nullOr package;
|
||||||
|
default = defaultPackage;
|
||||||
|
defaultText = literalExpression "inputs.lan-mouse.packages.${pkgs.stdenv.hostPlatform.system}.default";
|
||||||
|
description = ''
|
||||||
|
The lan-mouse package to use.
|
||||||
|
|
||||||
|
By default, this option will use the `packages.default` as exposed by this flake.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
systemd = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = pkgs.stdenv.isLinux;
|
||||||
|
description = "Whether to enable to systemd service for lan-mouse.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.user.services.lan-mouse = lib.mkIf cfg.systemd {
|
||||||
|
Unit = {
|
||||||
|
Description = "Systemd service for Lan Mouse";
|
||||||
|
Requires = ["graphical-session.target"];
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${cfg.package}/bin/lan-mouse --daemon";
|
||||||
|
};
|
||||||
|
Install.WantedBy = [
|
||||||
|
(lib.mkIf config.wayland.windowManager.hyprland.systemd.enable "hyprland-session.target")
|
||||||
|
(lib.mkIf config.wayland.windowManager.sway.systemd.enable "sway-session.target")
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
home.packages = [
|
||||||
|
cfg.package
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
1
shell.nix
Normal file
1
shell.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
(builtins.getFlake ("git+file://" + toString ./.)).devShells.${builtins.currentSystem}.default
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io,
|
os::{fd::OwnedFd, unix::net::UnixStream},
|
||||||
os::{
|
|
||||||
fd::{FromRawFd, RawFd},
|
|
||||||
unix::net::UnixStream,
|
|
||||||
},
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use ashpd::desktop::remote_desktop::{DeviceType, RemoteDesktop};
|
use ashpd::{
|
||||||
|
desktop::{
|
||||||
|
remote_desktop::{DeviceType, RemoteDesktop},
|
||||||
|
ResponseError,
|
||||||
|
},
|
||||||
|
WindowIdentifier,
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
@@ -43,22 +45,34 @@ pub struct LibeiConsumer {
|
|||||||
serial: u32,
|
serial: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_ei_fd() -> Result<RawFd, ashpd::Error> {
|
async fn get_ei_fd() -> Result<OwnedFd, ashpd::Error> {
|
||||||
let proxy = RemoteDesktop::new().await?;
|
let proxy = RemoteDesktop::new().await?;
|
||||||
let session = proxy.create_session().await?;
|
|
||||||
|
|
||||||
// I HATE EVERYTHING, THIS TOOK 8 HOURS OF DEBUGGING
|
// retry when user presses the cancel button
|
||||||
proxy
|
let (session, _) = loop {
|
||||||
.select_devices(
|
log::debug!("creating session ...");
|
||||||
&session,
|
let session = proxy.create_session().await?;
|
||||||
DeviceType::Pointer | DeviceType::Keyboard | DeviceType::Touchscreen,
|
|
||||||
)
|
log::debug!("selecting devices ...");
|
||||||
.await?;
|
proxy
|
||||||
|
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
log::info!("requesting permission for input emulation");
|
||||||
|
match proxy
|
||||||
|
.start(&session, &WindowIdentifier::default())
|
||||||
|
.await?
|
||||||
|
.response()
|
||||||
|
{
|
||||||
|
Ok(d) => break (session, d),
|
||||||
|
Err(ashpd::Error::Response(ResponseError::Cancelled)) => {
|
||||||
|
log::warn!("request cancelled!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
e => e?,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
proxy
|
|
||||||
.start(&session, &ashpd::WindowIdentifier::default())
|
|
||||||
.await?
|
|
||||||
.response()?;
|
|
||||||
proxy.connect_to_eis(&session).await
|
proxy.connect_to_eis(&session).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,15 +80,7 @@ impl LibeiConsumer {
|
|||||||
pub async fn new() -> Result<Self> {
|
pub async fn new() -> Result<Self> {
|
||||||
// fd is owned by the message, so we need to dup it
|
// fd is owned by the message, so we need to dup it
|
||||||
let eifd = get_ei_fd().await?;
|
let eifd = get_ei_fd().await?;
|
||||||
let eifd = unsafe {
|
let stream = UnixStream::from(eifd);
|
||||||
let ret = libc::dup(eifd);
|
|
||||||
if ret < 0 {
|
|
||||||
Err(io::Error::last_os_error())
|
|
||||||
} else {
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
let stream = unsafe { UnixStream::from_raw_fd(eifd) };
|
|
||||||
// let stream = UnixStream::connect("/run/user/1000/eis-0")?;
|
// let stream = UnixStream::connect("/run/user/1000/eis-0")?;
|
||||||
stream.set_nonblocking(true)?;
|
stream.set_nonblocking(true)?;
|
||||||
let context = ei::Context::new(stream)?;
|
let context = ei::Context::new(stream)?;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
|||||||
use ashpd::{
|
use ashpd::{
|
||||||
desktop::{
|
desktop::{
|
||||||
remote_desktop::{Axis, DeviceType, KeyState, RemoteDesktop},
|
remote_desktop::{Axis, DeviceType, KeyState, RemoteDesktop},
|
||||||
Session,
|
ResponseError, Session,
|
||||||
},
|
},
|
||||||
WindowIdentifier,
|
WindowIdentifier,
|
||||||
};
|
};
|
||||||
@@ -26,17 +26,32 @@ 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 ...");
|
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?;
|
|
||||||
log::debug!("selecting devices ...");
|
|
||||||
proxy
|
|
||||||
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let _ = proxy
|
// retry when user presses the cancel button
|
||||||
.start(&session, &WindowIdentifier::default())
|
let (session, _) = loop {
|
||||||
.await?
|
log::debug!("creating session ...");
|
||||||
.response()?;
|
let session = proxy.create_session().await?;
|
||||||
|
|
||||||
|
log::debug!("selecting devices ...");
|
||||||
|
proxy
|
||||||
|
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
log::info!("requesting permission for input emulation");
|
||||||
|
match proxy
|
||||||
|
.start(&session, &WindowIdentifier::default())
|
||||||
|
.await?
|
||||||
|
.response()
|
||||||
|
{
|
||||||
|
Ok(d) => break (session, d),
|
||||||
|
Err(ashpd::Error::Response(ResponseError::Cancelled)) => {
|
||||||
|
log::warn!("request cancelled!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
e => e?,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
log::debug!("started session");
|
log::debug!("started session");
|
||||||
|
|
||||||
Ok(Self { proxy, session })
|
Ok(Self { proxy, session })
|
||||||
|
|||||||
@@ -1,39 +1,555 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use std::{io, task::Poll};
|
use ashpd::{
|
||||||
|
desktop::{
|
||||||
|
input_capture::{Activated, Barrier, BarrierID, Capabilities, InputCapture, Region, Zones},
|
||||||
|
ResponseError, Session,
|
||||||
|
},
|
||||||
|
enumflags2::BitFlags,
|
||||||
|
};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use reis::{
|
||||||
|
ei::{self, keyboard::KeyState},
|
||||||
|
eis::button::ButtonState,
|
||||||
|
event::{DeviceCapability, EiEvent},
|
||||||
|
tokio::{EiConvertEventStream, EiEventStream},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
cell::Cell,
|
||||||
|
collections::HashMap,
|
||||||
|
io,
|
||||||
|
os::unix::net::UnixStream,
|
||||||
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
|
task::{ready, Context, Poll},
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
sync::mpsc::{Receiver, Sender},
|
||||||
|
task::JoinHandle,
|
||||||
|
};
|
||||||
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{ClientEvent, ClientHandle},
|
client::{ClientEvent, ClientHandle, Position},
|
||||||
event::Event,
|
event::{Event, KeyboardEvent, PointerEvent},
|
||||||
producer::EventProducer,
|
producer::EventProducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct LibeiProducer {}
|
#[derive(Debug)]
|
||||||
|
enum ProducerEvent {
|
||||||
|
Release,
|
||||||
|
ClientEvent(ClientEvent),
|
||||||
|
}
|
||||||
|
|
||||||
impl LibeiProducer {
|
#[allow(dead_code)]
|
||||||
pub fn new() -> Result<Self> {
|
pub struct LibeiProducer<'a> {
|
||||||
Err(anyhow!("not implemented"))
|
input_capture: Pin<Box<InputCapture<'a>>>,
|
||||||
|
libei_task: JoinHandle<Result<()>>,
|
||||||
|
event_rx: tokio::sync::mpsc::Receiver<(u32, Event)>,
|
||||||
|
notify_tx: tokio::sync::mpsc::Sender<ProducerEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static INTERFACES: Lazy<HashMap<&'static str, u32>> = Lazy::new(|| {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert("ei_connection", 1);
|
||||||
|
m.insert("ei_callback", 1);
|
||||||
|
m.insert("ei_pingpong", 1);
|
||||||
|
m.insert("ei_seat", 1);
|
||||||
|
m.insert("ei_device", 2);
|
||||||
|
m.insert("ei_pointer", 1);
|
||||||
|
m.insert("ei_pointer_absolute", 1);
|
||||||
|
m.insert("ei_scroll", 1);
|
||||||
|
m.insert("ei_button", 1);
|
||||||
|
m.insert("ei_keyboard", 1);
|
||||||
|
m.insert("ei_touchscreen", 1);
|
||||||
|
m
|
||||||
|
});
|
||||||
|
|
||||||
|
fn pos_to_barrier(r: &Region, pos: Position) -> (i32, i32, i32, i32) {
|
||||||
|
let (x, y) = (r.x_offset(), r.y_offset());
|
||||||
|
let (width, height) = (r.width() as i32, r.height() as i32);
|
||||||
|
match pos {
|
||||||
|
Position::Left => (x, y, x, y + height - 1), // start pos, end pos, inclusive
|
||||||
|
Position::Right => (x + width, y, x + width, y + height - 1),
|
||||||
|
Position::Top => (x, y, x + width - 1, y),
|
||||||
|
Position::Bottom => (x, y + height, x + width - 1, y + height),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventProducer for LibeiProducer {
|
fn select_barriers(
|
||||||
fn notify(&mut self, _event: ClientEvent) -> io::Result<()> {
|
zones: &Zones,
|
||||||
|
clients: &Vec<(ClientHandle, Position)>,
|
||||||
|
next_barrier_id: &mut u32,
|
||||||
|
) -> (Vec<Barrier>, HashMap<BarrierID, ClientHandle>) {
|
||||||
|
let mut client_for_barrier = HashMap::new();
|
||||||
|
let mut barriers: Vec<Barrier> = vec![];
|
||||||
|
|
||||||
|
for (handle, pos) in clients {
|
||||||
|
let mut client_barriers = zones
|
||||||
|
.regions()
|
||||||
|
.iter()
|
||||||
|
.map(|r| {
|
||||||
|
let id = *next_barrier_id;
|
||||||
|
*next_barrier_id = id + 1;
|
||||||
|
let position = pos_to_barrier(r, *pos);
|
||||||
|
client_for_barrier.insert(id, *handle);
|
||||||
|
Barrier::new(id, position)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
barriers.append(&mut client_barriers);
|
||||||
|
}
|
||||||
|
(barriers, client_for_barrier)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_barriers(
|
||||||
|
input_capture: &InputCapture<'_>,
|
||||||
|
session: &Session<'_>,
|
||||||
|
active_clients: &Vec<(ClientHandle, Position)>,
|
||||||
|
next_barrier_id: &mut u32,
|
||||||
|
) -> Result<HashMap<BarrierID, ClientHandle>> {
|
||||||
|
let zones = input_capture.zones(session).await?.response()?;
|
||||||
|
log::debug!("zones: {zones:?}");
|
||||||
|
|
||||||
|
let (barriers, id_map) = select_barriers(&zones, active_clients, next_barrier_id);
|
||||||
|
log::debug!("barriers: {barriers:?}");
|
||||||
|
log::debug!("client for barrier id: {id_map:?}");
|
||||||
|
|
||||||
|
let response = input_capture
|
||||||
|
.set_pointer_barriers(session, &barriers, zones.zone_set())
|
||||||
|
.await?;
|
||||||
|
let response = response.response()?;
|
||||||
|
log::info!("{response:?}");
|
||||||
|
Ok(id_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Drop for LibeiProducer<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.libei_task.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_session<'a>(
|
||||||
|
input_capture: &'a InputCapture<'a>,
|
||||||
|
) -> Result<(Session<'a>, BitFlags<Capabilities>)> {
|
||||||
|
log::info!("creating input capture session");
|
||||||
|
let (session, capabilities) = loop {
|
||||||
|
match input_capture
|
||||||
|
.create_session(
|
||||||
|
&ashpd::WindowIdentifier::default(),
|
||||||
|
Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(s) => break s,
|
||||||
|
Err(ashpd::Error::Response(ResponseError::Cancelled)) => continue,
|
||||||
|
o => o?,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
log::debug!("capabilities: {capabilities:?}");
|
||||||
|
Ok((session, capabilities))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect_to_eis(
|
||||||
|
input_capture: &InputCapture<'_>,
|
||||||
|
session: &Session<'_>,
|
||||||
|
) -> Result<(ei::Context, EiConvertEventStream)> {
|
||||||
|
log::info!("connect_to_eis");
|
||||||
|
let fd = input_capture.connect_to_eis(session).await?;
|
||||||
|
|
||||||
|
// create unix stream from fd
|
||||||
|
let stream = UnixStream::from(fd);
|
||||||
|
stream.set_nonblocking(true)?;
|
||||||
|
|
||||||
|
// create ei context
|
||||||
|
let context = ei::Context::new(stream)?;
|
||||||
|
let mut event_stream = EiEventStream::new(context.clone())?;
|
||||||
|
let response = match reis::tokio::ei_handshake(
|
||||||
|
&mut event_stream,
|
||||||
|
"de.feschber.LanMouse",
|
||||||
|
ei::handshake::ContextType::Receiver,
|
||||||
|
&INTERFACES,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => return Err(anyhow!("ei handshake failed: {e:?}")),
|
||||||
|
};
|
||||||
|
let event_stream = EiConvertEventStream::new(event_stream, response.serial);
|
||||||
|
|
||||||
|
Ok((context, event_stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn libei_event_handler(
|
||||||
|
mut ei_event_stream: EiConvertEventStream,
|
||||||
|
context: ei::Context,
|
||||||
|
event_tx: Sender<(u32, Event)>,
|
||||||
|
current_client: Rc<Cell<Option<ClientHandle>>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
loop {
|
||||||
|
let ei_event = match ei_event_stream.next().await {
|
||||||
|
Some(Ok(event)) => event,
|
||||||
|
Some(Err(e)) => return Err(anyhow!("libei connection closed: {e:?}")),
|
||||||
|
None => return Err(anyhow!("libei connection closed")),
|
||||||
|
};
|
||||||
|
log::trace!("from ei: {ei_event:?}");
|
||||||
|
let client = current_client.get();
|
||||||
|
handle_ei_event(ei_event, client, &context, &event_tx).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_active_client(
|
||||||
|
notify_rx: &mut Receiver<ProducerEvent>,
|
||||||
|
active_clients: &mut Vec<(ClientHandle, Position)>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// wait for a client update
|
||||||
|
while let Some(producer_event) = notify_rx.recv().await {
|
||||||
|
if let ProducerEvent::ClientEvent(c) = producer_event {
|
||||||
|
handle_producer_event(ProducerEvent::ClientEvent(c), active_clients)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> LibeiProducer<'a> {
|
||||||
|
pub async fn new() -> Result<Self> {
|
||||||
|
let input_capture = Box::pin(InputCapture::new().await?);
|
||||||
|
let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture<'static>;
|
||||||
|
let mut first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?);
|
||||||
|
|
||||||
|
let (event_tx, event_rx) = tokio::sync::mpsc::channel(32);
|
||||||
|
let (notify_tx, mut notify_rx) = tokio::sync::mpsc::channel(32);
|
||||||
|
let libei_task = tokio::task::spawn_local(async move {
|
||||||
|
/* safety: libei_task does not outlive Self */
|
||||||
|
let input_capture = unsafe { &*input_capture_ptr };
|
||||||
|
|
||||||
|
let mut active_clients: Vec<(ClientHandle, Position)> = vec![];
|
||||||
|
let mut next_barrier_id = 1u32;
|
||||||
|
|
||||||
|
/* there is a bug in xdg-remote-desktop-portal-gnome / mutter that
|
||||||
|
* prevents receiving further events after a session has been disabled once.
|
||||||
|
* Therefore the session needs to recreated when the barriers are updated */
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// otherwise it asks to capture input even with no active clients
|
||||||
|
if active_clients.is_empty() {
|
||||||
|
wait_for_active_client(&mut notify_rx, &mut active_clients).await?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_client = Rc::new(Cell::new(None));
|
||||||
|
|
||||||
|
// create session
|
||||||
|
let (session, _) = match first_session.take() {
|
||||||
|
Some(s) => s,
|
||||||
|
_ => create_session(input_capture).await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// connect to eis server
|
||||||
|
let (context, ei_event_stream) = connect_to_eis(input_capture, &session).await?;
|
||||||
|
|
||||||
|
// async event task
|
||||||
|
let mut ei_task: JoinHandle<Result<(), anyhow::Error>> =
|
||||||
|
tokio::task::spawn_local(libei_event_handler(
|
||||||
|
ei_event_stream,
|
||||||
|
context,
|
||||||
|
event_tx.clone(),
|
||||||
|
current_client.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut activated = input_capture.receive_activated().await?;
|
||||||
|
let mut zones_changed = input_capture.receive_zones_changed().await?;
|
||||||
|
|
||||||
|
// set barriers
|
||||||
|
let client_for_barrier_id = update_barriers(
|
||||||
|
input_capture,
|
||||||
|
&session,
|
||||||
|
&active_clients,
|
||||||
|
&mut next_barrier_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
log::info!("enabling session");
|
||||||
|
input_capture.enable(&session).await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
activated = activated.next() => {
|
||||||
|
let activated = activated.ok_or(anyhow!("error receiving activation token"))?;
|
||||||
|
log::debug!("activated: {activated:?}");
|
||||||
|
|
||||||
|
let client = *client_for_barrier_id
|
||||||
|
.get(&activated.barrier_id())
|
||||||
|
.expect("invalid barrier id");
|
||||||
|
current_client.replace(Some(client));
|
||||||
|
|
||||||
|
event_tx.send((client, Event::Enter())).await?;
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
producer_event = notify_rx.recv() => {
|
||||||
|
let producer_event = producer_event.expect("channel closed");
|
||||||
|
if handle_producer_event(producer_event, &mut active_clients)? {
|
||||||
|
break; /* clients updated */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zones_changed = zones_changed.next() => {
|
||||||
|
log::debug!("zones changed: {zones_changed:?}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res = &mut ei_task => {
|
||||||
|
if let Err(e) = res.expect("ei task paniced") {
|
||||||
|
log::warn!("libei task exited: {e}");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release_capture(
|
||||||
|
input_capture,
|
||||||
|
&session,
|
||||||
|
activated,
|
||||||
|
client,
|
||||||
|
&active_clients,
|
||||||
|
).await?;
|
||||||
|
}
|
||||||
|
producer_event = notify_rx.recv() => {
|
||||||
|
let producer_event = producer_event.expect("channel closed");
|
||||||
|
if handle_producer_event(producer_event, &mut active_clients)? {
|
||||||
|
/* clients updated */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
res = &mut ei_task => {
|
||||||
|
if let Err(e) = res.expect("ei task paniced") {
|
||||||
|
log::warn!("libei task exited: {e}");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ei_task.abort();
|
||||||
|
input_capture.disable(&session).await?;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let producer = Self {
|
||||||
|
input_capture,
|
||||||
|
event_rx,
|
||||||
|
libei_task,
|
||||||
|
notify_tx,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(producer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn release_capture(
|
||||||
|
input_capture: &InputCapture<'_>,
|
||||||
|
session: &Session<'_>,
|
||||||
|
activated: Activated,
|
||||||
|
current_client: ClientHandle,
|
||||||
|
active_clients: &[(ClientHandle, Position)],
|
||||||
|
) -> Result<()> {
|
||||||
|
log::debug!("releasing input capture {}", activated.activation_id());
|
||||||
|
let (x, y) = activated.cursor_position();
|
||||||
|
let pos = active_clients
|
||||||
|
.iter()
|
||||||
|
.filter(|(c, _)| *c == current_client)
|
||||||
|
.map(|(_, p)| p)
|
||||||
|
.next()
|
||||||
|
.unwrap(); // FIXME
|
||||||
|
let (dx, dy) = match pos {
|
||||||
|
// offset cursor position to not enter again immediately
|
||||||
|
Position::Left => (1., 0.),
|
||||||
|
Position::Right => (-1., 0.),
|
||||||
|
Position::Top => (0., 1.),
|
||||||
|
Position::Bottom => (0., -1.),
|
||||||
|
};
|
||||||
|
// release 1px to the right of the entered zone
|
||||||
|
let cursor_position = (x as f64 + dx, y as f64 + dy);
|
||||||
|
input_capture
|
||||||
|
.release(session, activated.activation_id(), cursor_position)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_producer_event(
|
||||||
|
producer_event: ProducerEvent,
|
||||||
|
active_clients: &mut Vec<(ClientHandle, Position)>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
log::debug!("handling event: {producer_event:?}");
|
||||||
|
let updated = match producer_event {
|
||||||
|
ProducerEvent::Release => false,
|
||||||
|
ProducerEvent::ClientEvent(ClientEvent::Create(c, p)) => {
|
||||||
|
active_clients.push((c, p));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
ProducerEvent::ClientEvent(ClientEvent::Destroy(c)) => {
|
||||||
|
active_clients.retain(|(h, _)| *h != c);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_ei_event(
|
||||||
|
ei_event: EiEvent,
|
||||||
|
current_client: Option<ClientHandle>,
|
||||||
|
context: &ei::Context,
|
||||||
|
event_tx: &Sender<(u32, Event)>,
|
||||||
|
) {
|
||||||
|
match ei_event {
|
||||||
|
EiEvent::SeatAdded(s) => {
|
||||||
|
s.seat.bind_capabilities(&[
|
||||||
|
DeviceCapability::Pointer,
|
||||||
|
DeviceCapability::PointerAbsolute,
|
||||||
|
DeviceCapability::Keyboard,
|
||||||
|
DeviceCapability::Touch,
|
||||||
|
DeviceCapability::Scroll,
|
||||||
|
DeviceCapability::Button,
|
||||||
|
]);
|
||||||
|
context.flush().unwrap();
|
||||||
|
}
|
||||||
|
EiEvent::SeatRemoved(_) => {}
|
||||||
|
EiEvent::DeviceAdded(_) => {}
|
||||||
|
EiEvent::DeviceRemoved(_) => {}
|
||||||
|
EiEvent::DevicePaused(_) => {}
|
||||||
|
EiEvent::DeviceResumed(_) => {}
|
||||||
|
EiEvent::KeyboardModifiers(mods) => {
|
||||||
|
let modifier_event = KeyboardEvent::Modifiers {
|
||||||
|
mods_depressed: mods.depressed,
|
||||||
|
mods_latched: mods.latched,
|
||||||
|
mods_locked: mods.locked,
|
||||||
|
group: mods.group,
|
||||||
|
};
|
||||||
|
if let Some(current_client) = current_client {
|
||||||
|
event_tx
|
||||||
|
.send((current_client, Event::Keyboard(modifier_event)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EiEvent::Frame(_) => {}
|
||||||
|
EiEvent::DeviceStartEmulating(_) => {
|
||||||
|
log::debug!("START EMULATING =============>");
|
||||||
|
}
|
||||||
|
EiEvent::DeviceStopEmulating(_) => {
|
||||||
|
log::debug!("==================> STOP EMULATING");
|
||||||
|
}
|
||||||
|
EiEvent::PointerMotion(motion) => {
|
||||||
|
let motion_event = PointerEvent::Motion {
|
||||||
|
time: motion.time as u32,
|
||||||
|
relative_x: motion.dx as f64,
|
||||||
|
relative_y: motion.dy as f64,
|
||||||
|
};
|
||||||
|
if let Some(current_client) = current_client {
|
||||||
|
event_tx
|
||||||
|
.send((current_client, Event::Pointer(motion_event)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EiEvent::PointerMotionAbsolute(_) => {}
|
||||||
|
EiEvent::Button(button) => {
|
||||||
|
let button_event = PointerEvent::Button {
|
||||||
|
time: button.time as u32,
|
||||||
|
button: button.button,
|
||||||
|
state: match button.state {
|
||||||
|
ButtonState::Released => 0,
|
||||||
|
ButtonState::Press => 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if let Some(current_client) = current_client {
|
||||||
|
event_tx
|
||||||
|
.send((current_client, Event::Pointer(button_event)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EiEvent::ScrollDelta(_) => {}
|
||||||
|
EiEvent::ScrollStop(_) => {}
|
||||||
|
EiEvent::ScrollCancel(_) => {}
|
||||||
|
EiEvent::ScrollDiscrete(scroll) => {
|
||||||
|
if scroll.discrete_dy != 0 {
|
||||||
|
let event = PointerEvent::Axis {
|
||||||
|
time: 0,
|
||||||
|
axis: 0,
|
||||||
|
value: scroll.discrete_dy as f64,
|
||||||
|
};
|
||||||
|
if let Some(current_client) = current_client {
|
||||||
|
event_tx
|
||||||
|
.send((current_client, Event::Pointer(event)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scroll.discrete_dx != 0 {
|
||||||
|
let event = PointerEvent::Axis {
|
||||||
|
time: 0,
|
||||||
|
axis: 1,
|
||||||
|
value: scroll.discrete_dx as f64,
|
||||||
|
};
|
||||||
|
if let Some(current_client) = current_client {
|
||||||
|
event_tx
|
||||||
|
.send((current_client, Event::Pointer(event)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EiEvent::KeyboardKey(key) => {
|
||||||
|
let key_event = KeyboardEvent::Key {
|
||||||
|
key: key.key,
|
||||||
|
state: match key.state {
|
||||||
|
KeyState::Press => 1,
|
||||||
|
KeyState::Released => 0,
|
||||||
|
},
|
||||||
|
time: key.time as u32,
|
||||||
|
};
|
||||||
|
if let Some(current_client) = current_client {
|
||||||
|
event_tx
|
||||||
|
.send((current_client, Event::Keyboard(key_event)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EiEvent::TouchDown(_) => {}
|
||||||
|
EiEvent::TouchUp(_) => {}
|
||||||
|
EiEvent::TouchMotion(_) => {}
|
||||||
|
EiEvent::Disconnected(d) => {
|
||||||
|
log::error!("disconnect: {d:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EventProducer for LibeiProducer<'a> {
|
||||||
|
fn notify(&mut self, event: ClientEvent) -> io::Result<()> {
|
||||||
|
let notify_tx = self.notify_tx.clone();
|
||||||
|
tokio::task::spawn_local(async move {
|
||||||
|
log::debug!("notifying {event:?}");
|
||||||
|
let _ = notify_tx.send(ProducerEvent::ClientEvent(event)).await;
|
||||||
|
log::debug!("done !");
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release(&mut self) -> io::Result<()> {
|
fn release(&mut self) -> io::Result<()> {
|
||||||
|
let notify_tx = self.notify_tx.clone();
|
||||||
|
tokio::task::spawn_local(async move {
|
||||||
|
log::debug!("notifying Release");
|
||||||
|
let _ = notify_tx.send(ProducerEvent::Release).await;
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for LibeiProducer {
|
impl<'a> Stream for LibeiProducer<'a> {
|
||||||
type Item = io::Result<(ClientHandle, Event)>;
|
type Item = io::Result<(ClientHandle, Event)>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
self: std::pin::Pin<&mut Self>,
|
match ready!(self.event_rx.poll_recv(cx)) {
|
||||||
_cx: &mut std::task::Context<'_>,
|
None => Poll::Ready(None),
|
||||||
) -> std::task::Poll<Option<Self::Item>> {
|
Some(e) => Poll::Ready(Some(Ok(e))),
|
||||||
Poll::Pending
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,9 +54,12 @@ use wayland_client::{
|
|||||||
delegate_noop,
|
delegate_noop,
|
||||||
globals::{registry_queue_init, GlobalListContents},
|
globals::{registry_queue_init, GlobalListContents},
|
||||||
protocol::{
|
protocol::{
|
||||||
wl_buffer, wl_compositor, wl_keyboard,
|
wl_buffer, wl_compositor,
|
||||||
|
wl_keyboard::{self, WlKeyboard},
|
||||||
wl_output::{self, WlOutput},
|
wl_output::{self, WlOutput},
|
||||||
wl_pointer, wl_region, wl_registry, wl_seat, wl_shm, wl_shm_pool, wl_surface,
|
wl_pointer::{self, WlPointer},
|
||||||
|
wl_region, wl_registry, wl_seat, wl_shm, wl_shm_pool,
|
||||||
|
wl_surface::WlSurface,
|
||||||
},
|
},
|
||||||
Connection, Dispatch, DispatchError, EventQueue, QueueHandle, WEnum,
|
Connection, Dispatch, DispatchError, EventQueue, QueueHandle, WEnum,
|
||||||
};
|
};
|
||||||
@@ -73,7 +76,7 @@ struct Globals {
|
|||||||
seat: wl_seat::WlSeat,
|
seat: wl_seat::WlSeat,
|
||||||
shm: wl_shm::WlShm,
|
shm: wl_shm::WlShm,
|
||||||
layer_shell: ZwlrLayerShellV1,
|
layer_shell: ZwlrLayerShellV1,
|
||||||
outputs: Vec<wl_output::WlOutput>,
|
outputs: Vec<WlOutput>,
|
||||||
xdg_output_manager: ZxdgOutputManagerV1,
|
xdg_output_manager: ZxdgOutputManagerV1,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +98,8 @@ impl OutputInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
|
pointer: Option<WlPointer>,
|
||||||
|
keyboard: Option<WlKeyboard>,
|
||||||
pointer_lock: Option<ZwpLockedPointerV1>,
|
pointer_lock: Option<ZwpLockedPointerV1>,
|
||||||
rel_pointer: Option<ZwpRelativePointerV1>,
|
rel_pointer: Option<ZwpRelativePointerV1>,
|
||||||
shortcut_inhibitor: Option<ZwpKeyboardShortcutsInhibitorV1>,
|
shortcut_inhibitor: Option<ZwpKeyboardShortcutsInhibitorV1>,
|
||||||
@@ -123,7 +128,7 @@ pub struct WaylandEventProducer(AsyncFd<Inner>);
|
|||||||
|
|
||||||
struct Window {
|
struct Window {
|
||||||
buffer: wl_buffer::WlBuffer,
|
buffer: wl_buffer::WlBuffer,
|
||||||
surface: wl_surface::WlSurface,
|
surface: WlSurface,
|
||||||
layer_surface: ZwlrLayerSurfaceV1,
|
layer_surface: ZwlrLayerSurfaceV1,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
}
|
}
|
||||||
@@ -136,6 +141,7 @@ impl Window {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
size: (i32, i32),
|
size: (i32, i32),
|
||||||
) -> Window {
|
) -> Window {
|
||||||
|
log::debug!("creating window output: {output:?}, size: {size:?}");
|
||||||
let g = &state.g;
|
let g = &state.g;
|
||||||
|
|
||||||
let (width, height) = match pos {
|
let (width, height) = match pos {
|
||||||
@@ -217,6 +223,7 @@ fn get_edges(outputs: &[(WlOutput, OutputInfo)], pos: Position) -> Vec<(WlOutput
|
|||||||
fn get_output_configuration(state: &State, pos: Position) -> Vec<(WlOutput, OutputInfo)> {
|
fn get_output_configuration(state: &State, pos: Position) -> Vec<(WlOutput, OutputInfo)> {
|
||||||
// get all output edges corresponding to the position
|
// get all output edges corresponding to the position
|
||||||
let edges = get_edges(&state.output_info, pos);
|
let edges = get_edges(&state.output_info, pos);
|
||||||
|
log::debug!("edges: {edges:?}");
|
||||||
let opposite_edges = get_edges(&state.output_info, pos.opposite());
|
let opposite_edges = get_edges(&state.output_info, pos.opposite());
|
||||||
|
|
||||||
// remove those edges that are at the same position
|
// remove those edges that are at the same position
|
||||||
@@ -331,6 +338,8 @@ impl WaylandEventProducer {
|
|||||||
std::mem::drop(read_guard);
|
std::mem::drop(read_guard);
|
||||||
|
|
||||||
let mut state = State {
|
let mut state = State {
|
||||||
|
pointer: None,
|
||||||
|
keyboard: None,
|
||||||
g,
|
g,
|
||||||
pointer_lock: None,
|
pointer_lock: None,
|
||||||
rel_pointer: None,
|
rel_pointer: None,
|
||||||
@@ -406,8 +415,8 @@ impl WaylandEventProducer {
|
|||||||
impl State {
|
impl State {
|
||||||
fn grab(
|
fn grab(
|
||||||
&mut self,
|
&mut self,
|
||||||
surface: &wl_surface::WlSurface,
|
surface: &WlSurface,
|
||||||
pointer: &wl_pointer::WlPointer,
|
pointer: &WlPointer,
|
||||||
serial: u32,
|
serial: u32,
|
||||||
qh: &QueueHandle<State>,
|
qh: &QueueHandle<State>,
|
||||||
) {
|
) {
|
||||||
@@ -489,6 +498,7 @@ impl State {
|
|||||||
fn add_client(&mut self, client: ClientHandle, pos: Position) {
|
fn add_client(&mut self, client: ClientHandle, pos: Position) {
|
||||||
let outputs = get_output_configuration(self, pos);
|
let outputs = get_output_configuration(self, pos);
|
||||||
|
|
||||||
|
log::debug!("outputs: {outputs:?}");
|
||||||
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);
|
||||||
@@ -654,7 +664,7 @@ impl Stream for WaylandEventProducer {
|
|||||||
|
|
||||||
impl Dispatch<wl_seat::WlSeat, ()> for State {
|
impl Dispatch<wl_seat::WlSeat, ()> for State {
|
||||||
fn event(
|
fn event(
|
||||||
_: &mut Self,
|
state: &mut Self,
|
||||||
seat: &wl_seat::WlSeat,
|
seat: &wl_seat::WlSeat,
|
||||||
event: <wl_seat::WlSeat as wayland_client::Proxy>::Event,
|
event: <wl_seat::WlSeat as wayland_client::Proxy>::Event,
|
||||||
_: &(),
|
_: &(),
|
||||||
@@ -665,21 +675,21 @@ impl Dispatch<wl_seat::WlSeat, ()> for State {
|
|||||||
capabilities: WEnum::Value(capabilities),
|
capabilities: WEnum::Value(capabilities),
|
||||||
} = event
|
} = event
|
||||||
{
|
{
|
||||||
if capabilities.contains(wl_seat::Capability::Pointer) {
|
if capabilities.contains(wl_seat::Capability::Pointer) && state.pointer.is_none() {
|
||||||
seat.get_pointer(qh, ());
|
state.pointer.replace(seat.get_pointer(qh, ()));
|
||||||
}
|
}
|
||||||
if capabilities.contains(wl_seat::Capability::Keyboard) {
|
if capabilities.contains(wl_seat::Capability::Keyboard) && state.keyboard.is_none() {
|
||||||
seat.get_keyboard(qh, ());
|
seat.get_keyboard(qh, ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
impl Dispatch<WlPointer, ()> for State {
|
||||||
fn event(
|
fn event(
|
||||||
app: &mut Self,
|
app: &mut Self,
|
||||||
pointer: &wl_pointer::WlPointer,
|
pointer: &WlPointer,
|
||||||
event: <wl_pointer::WlPointer as wayland_client::Proxy>::Event,
|
event: <WlPointer as wayland_client::Proxy>::Event,
|
||||||
_: &(),
|
_: &(),
|
||||||
_: &Connection,
|
_: &Connection,
|
||||||
qh: &QueueHandle<Self>,
|
qh: &QueueHandle<Self>,
|
||||||
@@ -761,10 +771,10 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
|
impl Dispatch<WlKeyboard, ()> for State {
|
||||||
fn event(
|
fn event(
|
||||||
app: &mut Self,
|
app: &mut Self,
|
||||||
_: &wl_keyboard::WlKeyboard,
|
_: &WlKeyboard,
|
||||||
event: wl_keyboard::Event,
|
event: wl_keyboard::Event,
|
||||||
_: &(),
|
_: &(),
|
||||||
_: &Connection,
|
_: &Connection,
|
||||||
@@ -917,7 +927,7 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
|||||||
state
|
state
|
||||||
.g
|
.g
|
||||||
.outputs
|
.outputs
|
||||||
.push(registry.bind::<wl_output::WlOutput, _, _>(name, 4, qh, ()))
|
.push(registry.bind::<WlOutput, _, _>(name, 4, qh, ()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wl_registry::Event::GlobalRemove { .. } => {}
|
wl_registry::Event::GlobalRemove { .. } => {}
|
||||||
@@ -962,11 +972,11 @@ impl Dispatch<ZxdgOutputV1, WlOutput> for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dispatch<wl_output::WlOutput, ()> for State {
|
impl Dispatch<WlOutput, ()> for State {
|
||||||
fn event(
|
fn event(
|
||||||
state: &mut Self,
|
state: &mut Self,
|
||||||
_proxy: &wl_output::WlOutput,
|
_proxy: &WlOutput,
|
||||||
event: <wl_output::WlOutput as wayland_client::Proxy>::Event,
|
event: <WlOutput as wayland_client::Proxy>::Event,
|
||||||
_data: &(),
|
_data: &(),
|
||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qhandle: &QueueHandle<Self>,
|
_qhandle: &QueueHandle<Self>,
|
||||||
@@ -990,6 +1000,6 @@ delegate_noop!(State: ZwpPointerConstraintsV1);
|
|||||||
delegate_noop!(State: ignore ZxdgOutputManagerV1);
|
delegate_noop!(State: ignore ZxdgOutputManagerV1);
|
||||||
delegate_noop!(State: ignore wl_shm::WlShm);
|
delegate_noop!(State: ignore wl_shm::WlShm);
|
||||||
delegate_noop!(State: ignore wl_buffer::WlBuffer);
|
delegate_noop!(State: ignore wl_buffer::WlBuffer);
|
||||||
delegate_noop!(State: ignore wl_surface::WlSurface);
|
delegate_noop!(State: ignore WlSurface);
|
||||||
delegate_noop!(State: ignore ZwpKeyboardShortcutsInhibitorV1);
|
delegate_noop!(State: ignore ZwpKeyboardShortcutsInhibitorV1);
|
||||||
delegate_noop!(State: ignore ZwpLockedPointerV1);
|
delegate_noop!(State: ignore ZwpLockedPointerV1);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use std::{error::Error, fs};
|
|||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use crate::client::Position;
|
use crate::client::Position;
|
||||||
|
use crate::scancode;
|
||||||
|
use crate::scancode::Linux::{KeyLeftAlt, KeyLeftCtrl, KeyLeftMeta, KeyLeftShift};
|
||||||
|
|
||||||
pub const DEFAULT_PORT: u16 = 4242;
|
pub const DEFAULT_PORT: u16 = 4242;
|
||||||
|
|
||||||
@@ -15,6 +17,7 @@ pub const DEFAULT_PORT: u16 = 4242;
|
|||||||
pub struct ConfigToml {
|
pub struct ConfigToml {
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
pub frontend: Option<String>,
|
pub frontend: Option<String>,
|
||||||
|
pub release_bind: Option<Vec<scancode::Linux>>,
|
||||||
pub left: Option<TomlClient>,
|
pub left: Option<TomlClient>,
|
||||||
pub right: Option<TomlClient>,
|
pub right: Option<TomlClient>,
|
||||||
pub top: Option<TomlClient>,
|
pub top: Option<TomlClient>,
|
||||||
@@ -70,6 +73,7 @@ pub struct Config {
|
|||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub clients: Vec<(TomlClient, Position)>,
|
pub clients: Vec<(TomlClient, Position)>,
|
||||||
pub daemon: bool,
|
pub daemon: bool,
|
||||||
|
pub release_bind: Vec<scancode::Linux>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ConfigClient {
|
pub struct ConfigClient {
|
||||||
@@ -80,6 +84,9 @@ pub struct ConfigClient {
|
|||||||
pub active: bool,
|
pub active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] =
|
||||||
|
[KeyLeftCtrl, KeyLeftShift, KeyLeftMeta, KeyLeftAlt];
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let args = CliArgs::parse();
|
let args = CliArgs::parse();
|
||||||
@@ -138,6 +145,12 @@ impl Config {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::debug!("{config_toml:?}");
|
||||||
|
let release_bind = config_toml
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|c| c.release_bind.clone())
|
||||||
|
.unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned()));
|
||||||
|
|
||||||
let mut clients: Vec<(TomlClient, Position)> = vec![];
|
let mut clients: Vec<(TomlClient, Position)> = vec![];
|
||||||
|
|
||||||
if let Some(config_toml) = config_toml {
|
if let Some(config_toml) = config_toml {
|
||||||
@@ -162,6 +175,7 @@ impl Config {
|
|||||||
frontend,
|
frontend,
|
||||||
clients,
|
clients,
|
||||||
port,
|
port,
|
||||||
|
release_bind,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use adw::subclass::prelude::*;
|
|||||||
use adw::{prelude::*, ActionRow, ComboRow};
|
use adw::{prelude::*, ActionRow, ComboRow};
|
||||||
use glib::{subclass::InitializingObject, Binding};
|
use glib::{subclass::InitializingObject, Binding};
|
||||||
use gtk::glib::clone;
|
use gtk::glib::clone;
|
||||||
use gtk::glib::once_cell::sync::Lazy;
|
|
||||||
use gtk::glib::subclass::Signal;
|
use gtk::glib::subclass::Signal;
|
||||||
use gtk::{glib, Button, CompositeTemplate, Switch};
|
use gtk::{glib, Button, CompositeTemplate, Switch};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
#[derive(CompositeTemplate, Default)]
|
#[derive(CompositeTemplate, Default)]
|
||||||
#[template(resource = "/de/feschber/LanMouse/client_row.ui")]
|
#[template(resource = "/de/feschber/LanMouse/client_row.ui")]
|
||||||
@@ -55,15 +55,15 @@ impl ObjectImpl for ClientRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signals() -> &'static [glib::subclass::Signal] {
|
fn signals() -> &'static [glib::subclass::Signal] {
|
||||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
|
||||||
|
SIGNALS.get_or_init(|| {
|
||||||
vec![
|
vec![
|
||||||
Signal::builder("request-update")
|
Signal::builder("request-update")
|
||||||
.param_types([bool::static_type()])
|
.param_types([bool::static_type()])
|
||||||
.build(),
|
.build(),
|
||||||
Signal::builder("request-delete").build(),
|
Signal::builder("request-delete").build(),
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
SIGNALS.as_ref()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub fn run() -> Result<()> {
|
|||||||
// parse config file + cli args
|
// parse config file + cli args
|
||||||
let config = Config::new()?;
|
let config = Config::new()?;
|
||||||
log::debug!("{config:?}");
|
log::debug!("{config:?}");
|
||||||
|
log::info!("release bind: {:?}", config.release_bind);
|
||||||
|
|
||||||
if config.daemon {
|
if config.daemon {
|
||||||
// if daemon is specified we run the service
|
// if daemon is specified we run the service
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub async fn create() -> Box<dyn EventProducer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
match producer::libei::LibeiProducer::new() {
|
match producer::libei::LibeiProducer::new().await {
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
log::info!("using libei event producer");
|
log::info!("using libei event producer");
|
||||||
return Box::new(p);
|
return Box::new(p);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
|
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
|
||||||
*/
|
*/
|
||||||
@@ -165,7 +167,7 @@ pub enum Windows {
|
|||||||
* https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
|
* https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
|
||||||
*/
|
*/
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum Linux {
|
pub enum Linux {
|
||||||
KeyReserved = 0,
|
KeyReserved = 0,
|
||||||
@@ -210,7 +212,7 @@ pub enum Linux {
|
|||||||
KeySemicolon = 39,
|
KeySemicolon = 39,
|
||||||
KeyApostrophe = 40,
|
KeyApostrophe = 40,
|
||||||
KeyGrave = 41,
|
KeyGrave = 41,
|
||||||
KeyLeftshift = 42,
|
KeyLeftShift = 42,
|
||||||
KeyBackslash = 43,
|
KeyBackslash = 43,
|
||||||
KeyZ = 44,
|
KeyZ = 44,
|
||||||
KeyX = 45,
|
KeyX = 45,
|
||||||
@@ -224,7 +226,7 @@ pub enum Linux {
|
|||||||
KeySlash = 53,
|
KeySlash = 53,
|
||||||
KeyRightShift = 54,
|
KeyRightShift = 54,
|
||||||
KeyKpAsterisk = 55,
|
KeyKpAsterisk = 55,
|
||||||
KeyLeftalt = 56,
|
KeyLeftAlt = 56,
|
||||||
KeySpace = 57,
|
KeySpace = 57,
|
||||||
KeyCapsLock = 58,
|
KeyCapsLock = 58,
|
||||||
KeyF1 = 59,
|
KeyF1 = 59,
|
||||||
@@ -294,7 +296,7 @@ pub enum Linux {
|
|||||||
// KEY_HANGUEL = KeyHangeul,
|
// KEY_HANGUEL = KeyHangeul,
|
||||||
KeyHanja = 123,
|
KeyHanja = 123,
|
||||||
KeyYen = 124,
|
KeyYen = 124,
|
||||||
KeyLeftmeta = 125,
|
KeyLeftMeta = 125,
|
||||||
KeyRightmeta = 126,
|
KeyRightmeta = 126,
|
||||||
KeyCompose = 127,
|
KeyCompose = 127,
|
||||||
KeyStop = 128, /* AC Stop */
|
KeyStop = 128, /* AC Stop */
|
||||||
@@ -485,7 +487,7 @@ impl TryFrom<Linux> for Windows {
|
|||||||
Linux::KeySemicolon => Ok(Self::KeySemiColon),
|
Linux::KeySemicolon => Ok(Self::KeySemiColon),
|
||||||
Linux::KeyApostrophe => Ok(Self::KeyApostrophe),
|
Linux::KeyApostrophe => Ok(Self::KeyApostrophe),
|
||||||
Linux::KeyGrave => Ok(Self::KeyGrave),
|
Linux::KeyGrave => Ok(Self::KeyGrave),
|
||||||
Linux::KeyLeftshift => Ok(Self::KeyLeftShift),
|
Linux::KeyLeftShift => Ok(Self::KeyLeftShift),
|
||||||
Linux::KeyBackslash => Ok(Self::KeyBackslash),
|
Linux::KeyBackslash => Ok(Self::KeyBackslash),
|
||||||
Linux::KeyZ => Ok(Self::KeyZ),
|
Linux::KeyZ => Ok(Self::KeyZ),
|
||||||
Linux::KeyX => Ok(Self::KeyX),
|
Linux::KeyX => Ok(Self::KeyX),
|
||||||
@@ -499,7 +501,7 @@ impl TryFrom<Linux> for Windows {
|
|||||||
Linux::KeySlash => Ok(Self::KeySlash),
|
Linux::KeySlash => Ok(Self::KeySlash),
|
||||||
Linux::KeyRightShift => Ok(Self::KeyRightShift),
|
Linux::KeyRightShift => Ok(Self::KeyRightShift),
|
||||||
Linux::KeyKpAsterisk => Ok(Self::KeypadStar),
|
Linux::KeyKpAsterisk => Ok(Self::KeypadStar),
|
||||||
Linux::KeyLeftalt => Ok(Self::KeyLeftAlt),
|
Linux::KeyLeftAlt => Ok(Self::KeyLeftAlt),
|
||||||
Linux::KeySpace => Ok(Self::KeySpace),
|
Linux::KeySpace => Ok(Self::KeySpace),
|
||||||
Linux::KeyCapsLock => Ok(Self::KeyCapsLock),
|
Linux::KeyCapsLock => Ok(Self::KeyCapsLock),
|
||||||
Linux::KeyF1 => Ok(Self::KeyF1),
|
Linux::KeyF1 => Ok(Self::KeyF1),
|
||||||
@@ -567,7 +569,7 @@ impl TryFrom<Linux> for Windows {
|
|||||||
Linux::KeyHangeul => Ok(Self::KeyInternational1), // TODO unsure
|
Linux::KeyHangeul => Ok(Self::KeyInternational1), // TODO unsure
|
||||||
Linux::KeyHanja => Ok(Self::KeyInternational2), // TODO unsure
|
Linux::KeyHanja => Ok(Self::KeyInternational2), // TODO unsure
|
||||||
Linux::KeyYen => Ok(Self::KeyInternational3), // TODO unsure
|
Linux::KeyYen => Ok(Self::KeyInternational3), // TODO unsure
|
||||||
Linux::KeyLeftmeta => Ok(Self::KeyLeftGUI),
|
Linux::KeyLeftMeta => Ok(Self::KeyLeftGUI),
|
||||||
Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
|
Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
|
||||||
Linux::KeyCompose => Ok(Self::KeyApplication),
|
Linux::KeyCompose => Ok(Self::KeyApplication),
|
||||||
Linux::KeyStop => Ok(Self::ACStop),
|
Linux::KeyStop => Ok(Self::ACStop),
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub struct Server {
|
|||||||
client_manager: Rc<RefCell<ClientManager>>,
|
client_manager: Rc<RefCell<ClientManager>>,
|
||||||
port: Rc<Cell<u16>>,
|
port: Rc<Cell<u16>>,
|
||||||
state: Rc<Cell<State>>,
|
state: Rc<Cell<State>>,
|
||||||
|
release_bind: Vec<crate::scancode::Linux>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
@@ -57,11 +58,13 @@ impl Server {
|
|||||||
config_client.active,
|
config_client.active,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
let release_bind = config.release_bind.clone();
|
||||||
Self {
|
Self {
|
||||||
active_client,
|
active_client,
|
||||||
client_manager,
|
client_manager,
|
||||||
port,
|
port,
|
||||||
state,
|
state,
|
||||||
|
release_bind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +88,13 @@ impl Server {
|
|||||||
network_task::new(self.clone(), frontend_notify_tx).await?;
|
network_task::new(self.clone(), frontend_notify_tx).await?;
|
||||||
|
|
||||||
// event producer
|
// event producer
|
||||||
let (mut producer_task, producer_channel) =
|
let (mut producer_task, producer_channel) = producer_task::new(
|
||||||
producer_task::new(producer, self.clone(), sender_tx.clone(), timer_tx.clone());
|
producer,
|
||||||
|
self.clone(),
|
||||||
|
sender_tx.clone(),
|
||||||
|
timer_tx.clone(),
|
||||||
|
self.release_bind.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
// event consumer
|
// event consumer
|
||||||
let (mut consumer_task, consumer_channel) = consumer_task::new(
|
let (mut consumer_task, consumer_channel) = consumer_task::new(
|
||||||
@@ -142,6 +150,7 @@ impl Server {
|
|||||||
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log::info!("running service");
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = signal::ctrl_c() => {
|
_ = signal::ctrl_c() => {
|
||||||
|
|||||||
@@ -240,22 +240,24 @@ pub async fn remove_client(
|
|||||||
frontend: &mut FrontendListener,
|
frontend: &mut FrontendListener,
|
||||||
client: ClientHandle,
|
client: ClientHandle,
|
||||||
) -> Option<ClientHandle> {
|
) -> Option<ClientHandle> {
|
||||||
let _ = producer_notify_tx
|
let Some((client, active)) = server
|
||||||
.send(ProducerEvent::ClientEvent(ClientEvent::Destroy(client)))
|
|
||||||
.await;
|
|
||||||
let _ = consumer_notify_tx
|
|
||||||
.send(ConsumerEvent::ClientEvent(ClientEvent::Destroy(client)))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let Some(client) = server
|
|
||||||
.client_manager
|
.client_manager
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.remove_client(client)
|
.remove_client(client)
|
||||||
.map(|s| s.client.handle)
|
.map(|s| (s.client.handle, s.active))
|
||||||
else {
|
else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if active {
|
||||||
|
let _ = producer_notify_tx
|
||||||
|
.send(ProducerEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||||
|
.await;
|
||||||
|
let _ = consumer_notify_tx
|
||||||
|
.send(ConsumerEvent::ClientEvent(ClientEvent::Destroy(client)))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
let notify = FrontendNotify::NotifyClientDelete(client);
|
let notify = FrontendNotify::NotifyClientDelete(client);
|
||||||
log::debug!("{notify:?}");
|
log::debug!("{notify:?}");
|
||||||
if let Err(e) = frontend.notify_all(notify).await {
|
if let Err(e) = frontend.notify_all(notify).await {
|
||||||
@@ -272,6 +274,7 @@ async fn update_client(
|
|||||||
client_update: (ClientHandle, Option<String>, u16, Position),
|
client_update: (ClientHandle, Option<String>, u16, Position),
|
||||||
) {
|
) {
|
||||||
let (handle, hostname, port, pos) = client_update;
|
let (handle, hostname, port, pos) = client_update;
|
||||||
|
let mut changed = false;
|
||||||
let (hostname, handle, active) = {
|
let (hostname, handle, active) = {
|
||||||
// retrieve state
|
// retrieve state
|
||||||
let mut client_manager = server.client_manager.borrow_mut();
|
let mut client_manager = server.client_manager.borrow_mut();
|
||||||
@@ -280,12 +283,16 @@ async fn update_client(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// update pos
|
// update pos
|
||||||
state.client.pos = pos;
|
if state.client.pos != pos {
|
||||||
|
state.client.pos = pos;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
// update port
|
// update port
|
||||||
if state.client.port != port {
|
if state.client.port != port {
|
||||||
state.client.port = port;
|
state.client.port = port;
|
||||||
state.active_addr = state.active_addr.map(|a| SocketAddr::new(a.ip(), port));
|
state.active_addr = state.active_addr.map(|a| SocketAddr::new(a.ip(), port));
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update hostname
|
// update hostname
|
||||||
@@ -293,6 +300,7 @@ async fn update_client(
|
|||||||
state.client.ips = HashSet::new();
|
state.client.ips = HashSet::new();
|
||||||
state.active_addr = None;
|
state.active_addr = None;
|
||||||
state.client.hostname = hostname;
|
state.client.hostname = hostname;
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("client updated: {:?}", state);
|
log::debug!("client updated: {:?}", state);
|
||||||
@@ -303,13 +311,14 @@ async fn update_client(
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// resolve dns
|
|
||||||
if let Some(hostname) = hostname {
|
|
||||||
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update state in event consumer & producer
|
// update state in event consumer & producer
|
||||||
if active {
|
if changed && active {
|
||||||
|
// resolve dns
|
||||||
|
if let Some(hostname) = hostname {
|
||||||
|
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update state
|
||||||
let _ = producer_notify_tx
|
let _ = producer_notify_tx
|
||||||
.send(ProducerEvent::ClientEvent(ClientEvent::Destroy(handle)))
|
.send(ProducerEvent::ClientEvent(ClientEvent::Destroy(handle)))
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@@ -86,9 +86,9 @@ pub fn new(
|
|||||||
|
|
||||||
// give clients time to resond
|
// give clients time to resond
|
||||||
if receiving {
|
if receiving {
|
||||||
log::debug!("waiting {MAX_RESPONSE_TIME:?} for response from client with pressed keys ...");
|
log::trace!("waiting {MAX_RESPONSE_TIME:?} for response from client with pressed keys ...");
|
||||||
} else {
|
} else {
|
||||||
log::debug!(
|
log::trace!(
|
||||||
"state: {:?} => waiting {MAX_RESPONSE_TIME:?} for client to respond ...",
|
"state: {:?} => waiting {MAX_RESPONSE_TIME:?} for client to respond ...",
|
||||||
server.state.get()
|
server.state.get()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use std::net::SocketAddr;
|
use std::{collections::HashSet, net::SocketAddr};
|
||||||
|
|
||||||
use tokio::{sync::mpsc::Sender, task::JoinHandle};
|
use tokio::{sync::mpsc::Sender, task::JoinHandle};
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ use crate::{
|
|||||||
client::{ClientEvent, ClientHandle},
|
client::{ClientEvent, ClientHandle},
|
||||||
event::{Event, KeyboardEvent},
|
event::{Event, KeyboardEvent},
|
||||||
producer::EventProducer,
|
producer::EventProducer,
|
||||||
|
scancode,
|
||||||
server::State,
|
server::State,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,14 +29,19 @@ pub fn new(
|
|||||||
server: Server,
|
server: Server,
|
||||||
sender_tx: Sender<(Event, SocketAddr)>,
|
sender_tx: Sender<(Event, SocketAddr)>,
|
||||||
timer_tx: Sender<()>,
|
timer_tx: Sender<()>,
|
||||||
|
release_bind: Vec<scancode::Linux>,
|
||||||
) -> (JoinHandle<Result<()>>, Sender<ProducerEvent>) {
|
) -> (JoinHandle<Result<()>>, Sender<ProducerEvent>) {
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||||
let task = tokio::task::spawn_local(async move {
|
let task = tokio::task::spawn_local(async move {
|
||||||
|
let mut pressed_keys = HashSet::new();
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
event = producer.next() => {
|
event = producer.next() => {
|
||||||
let event = event.ok_or(anyhow!("event producer closed"))??;
|
match event {
|
||||||
handle_producer_event(&server, &mut producer, &sender_tx, &timer_tx, event).await?;
|
Some(Ok(event)) => handle_producer_event(&server, &mut producer, &sender_tx, &timer_tx, event, &mut pressed_keys, &release_bind).await?,
|
||||||
|
Some(Err(e)) => return Err(anyhow!("event producer: {e:?}")),
|
||||||
|
None => return Err(anyhow!("event producer closed")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
e = rx.recv() => {
|
e = rx.recv() => {
|
||||||
log::debug!("producer notify rx: {e:?}");
|
log::debug!("producer notify rx: {e:?}");
|
||||||
@@ -59,7 +65,15 @@ pub fn new(
|
|||||||
(task, tx)
|
(task, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
const RELEASE_MODIFIERDS: u32 = 77; // ctrl+shift+super+alt
|
fn update_pressed_keys(pressed_keys: &mut HashSet<scancode::Linux>, key: u32, state: u8) {
|
||||||
|
if let Ok(scancode) = scancode::Linux::try_from(key) {
|
||||||
|
log::debug!("key: {key}, state: {state}, scancode: {scancode:?}");
|
||||||
|
match state {
|
||||||
|
1 => pressed_keys.insert(scancode),
|
||||||
|
_ => pressed_keys.remove(&scancode),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_producer_event(
|
async fn handle_producer_event(
|
||||||
server: &Server,
|
server: &Server,
|
||||||
@@ -67,12 +81,18 @@ async fn handle_producer_event(
|
|||||||
sender_tx: &Sender<(Event, SocketAddr)>,
|
sender_tx: &Sender<(Event, SocketAddr)>,
|
||||||
timer_tx: &Sender<()>,
|
timer_tx: &Sender<()>,
|
||||||
event: (ClientHandle, Event),
|
event: (ClientHandle, Event),
|
||||||
|
pressed_keys: &mut HashSet<scancode::Linux>,
|
||||||
|
release_bind: &[scancode::Linux],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (c, mut e) = event;
|
let (c, mut e) = event;
|
||||||
log::trace!("({c}) {e:?}");
|
log::trace!("({c}) {e:?}");
|
||||||
|
|
||||||
if let Event::Keyboard(KeyboardEvent::Modifiers { mods_depressed, .. }) = e {
|
if let Event::Keyboard(KeyboardEvent::Key { key, state, .. }) = e {
|
||||||
if mods_depressed == RELEASE_MODIFIERDS {
|
update_pressed_keys(pressed_keys, key, state);
|
||||||
|
log::debug!("{pressed_keys:?}");
|
||||||
|
if release_bind.iter().all(|k| pressed_keys.contains(k)) {
|
||||||
|
pressed_keys.clear();
|
||||||
|
log::info!("releasing pointer");
|
||||||
producer.release()?;
|
producer.release()?;
|
||||||
server.state.replace(State::Receiving);
|
server.state.replace(State::Receiving);
|
||||||
log::trace!("STATE ===> Receiving");
|
log::trace!("STATE ===> Receiving");
|
||||||
|
|||||||
Reference in New Issue
Block a user