Skip to content

Commit e086227

Browse files
coreydowningsamvvclaude
committed
Add raw evdev input emulation backend
Adds an evdev/uinput-based input emulation backend for Linux that creates a virtual mouse/keyboard, bypassing Wayland restrictions. Useful as a fallback on systems without a working remote desktop portal (e.g. COSMIC). Rebased onto the build.rs cfg-alias mechanism (evdev / evdev_emulation) introduced after the original PR. The backend is preferred in the auto-fallback order when /dev/uinput is accessible and falls back cleanly to the other backends otherwise. Documents the udev-rule permissions setup in the README. Original work by Sam Vervaeck (feschber#297). Co-authored-by: Sam Vervaeck <samvv@pm.me> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 392af44 commit e086227

10 files changed

Lines changed: 880 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 73 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ default = [
8181
"libei_emulation",
8282
"rdp_emulation",
8383
"x11_emulation",
84+
"evdev_emulation",
8485
]
8586
gtk = ["dep:lan-mouse-gtk"]
8687
layer_shell_capture = ["input-capture/layer_shell"]
@@ -90,6 +91,7 @@ libei_emulation = ["input-event/libei", "input-emulation/libei"]
9091
wlroots_emulation = ["input-emulation/wlroots"]
9192
x11_emulation = ["input-emulation/x11"]
9293
rdp_emulation = ["input-emulation/remote_desktop_portal"]
94+
evdev_emulation = ["input-emulation/evdev"]
9395

9496
[package.metadata.bundle]
9597
name = "Lan Mouse"

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,27 @@ Most current desktop environments and operating systems are fully supported, thi
5555
5656
For more detailed information about os support see [Detailed OS Support](#detailed-os-support)
5757

58+
### evdev emulation backend (Linux)
59+
60+
On Linux, Lan Mouse can emulate input through a virtual `evdev`/`uinput` device.
61+
This backend works independently of the compositor and is useful as a fallback on
62+
systems without a working remote desktop portal (for example COSMIC).
63+
64+
It requires access to `/dev/uinput`. Rather than running as root, grant your user
65+
access with a dedicated group and a udev rule:
66+
67+
```sh
68+
sudo groupadd --system lan-mouse
69+
sudo usermod -aG lan-mouse <username>
70+
echo 'KERNEL=="uinput", GROUP="lan-mouse"' | sudo tee /lib/udev/rules.d/05-lan-mouse.rules
71+
```
72+
73+
Log out and back in (or reboot) for the group change to take effect. When
74+
`/dev/uinput` is accessible, the `evdev` backend is preferred automatically; if it
75+
is not accessible, Lan Mouse falls back to the other emulation backends. It can
76+
also be selected explicitly with `--emulation-backend evdev` or via
77+
`emulation_backend = "evdev"` in the config.
78+
5879
### Android & IOS
5980

6081
A proof of concept for an Android / IOS Application by [rohitsangwan01](https://github.com/rohitsangwan01) can be found [here](https://github.com/rohitsangwan01/lan-mouse-mobile).

build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ fn main() {
1717
let x11_emulation = cfg!(feature = "x11_emulation");
1818
let wlroots_emulation = cfg!(feature = "wlroots_emulation");
1919
let rdp_emulation = cfg!(feature = "rdp_emulation");
20+
let evdev_emulation = cfg!(feature = "evdev_emulation");
21+
let linux = cfg!(target_os = "linux");
2022

2123
let layer_shell_capture = unix && !macos && layer_shell_capture;
2224
let libei_capture = unix && !macos && libei_capture;
@@ -26,6 +28,7 @@ fn main() {
2628
let rdp_emulation = unix && !macos && rdp_emulation;
2729
let wlroots_emulation = unix && !macos && wlroots_emulation;
2830
let x11_emulation = unix && !macos && x11_emulation;
31+
let evdev_emulation = linux && evdev_emulation;
2932

3033
println!("cargo::rustc-check-cfg=cfg(layer_shell_capture)");
3134
println!("cargo::rustc-check-cfg=cfg(libei_capture)");
@@ -35,6 +38,7 @@ fn main() {
3538
println!("cargo::rustc-check-cfg=cfg(rdp_emulation)");
3639
println!("cargo::rustc-check-cfg=cfg(wlroots_emulation)");
3740
println!("cargo::rustc-check-cfg=cfg(x11_emulation)");
41+
println!("cargo::rustc-check-cfg=cfg(evdev_emulation)");
3842

3943
if layer_shell_capture {
4044
println!("cargo::rustc-cfg=layer_shell_capture");
@@ -58,4 +62,7 @@ fn main() {
5862
if x11_emulation {
5963
println!("cargo::rustc-cfg=x11_emulation");
6064
}
65+
if evdev_emulation {
66+
println!("cargo::rustc-cfg=evdev_emulation");
67+
}
6168
}

input-emulation/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ ashpd = { version = "0.13.9", default-features = false, features = [
4646
"tokio",
4747
], optional = true }
4848
reis = { version = "0.7.0", features = ["tokio"], optional = true }
49+
evdev = { version = "0.13.1", features = [
50+
"stream-trait",
51+
"tokio",
52+
], optional = true }
4953

5054
[target.'cfg(target_os="macos")'.dependencies]
5155
bitflags = "2.6.0"
@@ -66,13 +70,14 @@ windows = { version = "0.61.2", features = [
6670
] }
6771

6872
[features]
69-
default = ["wlroots", "x11", "remote_desktop_portal", "libei"]
73+
default = ["wlroots", "x11", "remote_desktop_portal", "libei", "evdev"]
7074
wlroots = [
7175
"dep:wayland-client",
7276
"dep:wayland-protocols",
7377
"dep:wayland-protocols-wlr",
7478
"dep:wayland-protocols-misc",
7579
]
80+
evdev = ["dep:evdev"]
7681
x11 = ["dep:x11"]
7782
remote_desktop_portal = ["dep:ashpd"]
7883
libei = ["dep:reis", "dep:ashpd"]

input-emulation/build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,27 @@ fn main() {
55
let macos = cfg!(target_os = "macos");
66
let wlroots = cfg!(feature = "wlroots");
77
let rdp = cfg!(feature = "remote_desktop_portal");
8+
let evdev = cfg!(feature = "evdev");
9+
let linux = cfg!(target_os = "linux");
810

911
let libei = unix && !macos && libei;
1012
let wlroots = unix && !macos && wlroots;
1113
let x11 = unix && !macos && x11;
1214
let rdp = unix && !macos && rdp;
15+
let evdev = linux && evdev;
1316

1417
println!("cargo::rustc-check-cfg=cfg(wlroots)");
1518
println!("cargo::rustc-check-cfg=cfg(libei)");
1619
println!("cargo::rustc-check-cfg=cfg(x11)");
1720
println!("cargo::rustc-check-cfg=cfg(rdp)");
21+
println!("cargo::rustc-check-cfg=cfg(evdev)");
1822

1923
if libei {
2024
println!("cargo::rustc-cfg=libei");
2125
}
26+
if evdev {
27+
println!("cargo::rustc-cfg=evdev");
28+
}
2229
if x11 {
2330
println!("cargo::rustc-cfg=x11");
2431
}

input-emulation/src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub enum EmulationCreationError {
4343
#[cfg(libei)]
4444
#[error("libei backend: `{0}`")]
4545
Libei(#[from] LibeiEmulationCreationError),
46+
#[cfg(evdev)]
47+
#[error("evdev backend: `{0}`")]
48+
Evdev(#[from] EvdevEmulationCreationError),
4649
#[cfg(rdp)]
4750
#[error("xdg-desktop-portal: `{0}`")]
4851
Xdp(#[from] XdpEmulationCreationError),
@@ -127,6 +130,13 @@ pub enum LibeiEmulationCreationError {
127130
Reis(#[from] reis::Error),
128131
}
129132

133+
#[cfg(evdev)]
134+
#[derive(Debug, Error)]
135+
pub enum EvdevEmulationCreationError {
136+
#[error(transparent)]
137+
Io(#[from] std::io::Error),
138+
}
139+
130140
#[cfg(rdp)]
131141
#[derive(Debug, Error)]
132142
pub enum XdpEmulationCreationError {

0 commit comments

Comments
 (0)