use monio::{Event, EventType, Key, grab};
use std::sync::atomic::{AtomicU32, Ordering};
use std::thread;
use std::time::Duration;
static BLOCKED_COUNT: AtomicU32 = AtomicU32::new(0);
fn get_display_server() -> &'static str {
if std::env::var("WAYLAND_DISPLAY").is_ok() {
"Wayland"
} else if std::env::var("DISPLAY").is_ok() {
"X11"
} else {
"unknown"
}
}
fn check_linux_permissions() {
#[cfg(target_os = "linux")]
{
use std::process::Command;
let is_wayland = std::env::var("WAYLAND_DISPLAY").is_ok();
if is_wayland {
eprintln!("\n⚠️ WAYLAND DETECTED - IMPORTANT LIMITATION:");
eprintln!(" Wayland compositors use libinput which may not recognize");
eprintln!(" re-injected events from virtual devices. Grab mode may");
eprintln!(" block all input instead of passing through allowed events.");
eprintln!(" This is a fundamental limitation of evdev+ uinput on Wayland.");
eprintln!();
}
let output = Command::new("id")
.arg("-Gn")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok());
let has_input_group = output.as_ref().map_or(false, |g| g.contains("input"));
let current_groups = Command::new("groups")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok());
let currently_has_input = current_groups
.as_ref()
.map_or(false, |g| g.contains("input"));
if has_input_group && !currently_has_input {
eprintln!("⚠️ You are in the 'input' group, but the change hasn't taken effect yet.");
eprintln!(" Please log out and log back in, or run: newgrp input");
eprintln!();
} else if !has_input_group {
eprintln!("⚠️ WARNING: You are not in the 'input' group.");
eprintln!(" The grab feature requires access to /dev/input devices.");
eprintln!(" Run: sudo usermod -aG input $USER");
eprintln!(" Then log out and back in.\n");
}
let input_accessible = std::fs::read_dir("/dev/input").is_ok();
if !input_accessible {
eprintln!("⚠️ /dev/input is not accessible.");
if !has_input_group {
eprintln!(" This should be fixed after adding yourself to the 'input' group.");
} else {
eprintln!(" You may need to create a udev rule:");
eprintln!(
" echo 'SUBSYSTEM==\"input\", GROUP=\"input\", MODE=\"660\"' | sudo tee /etc/udev/rules.d/99-input.rules"
);
}
eprintln!();
}
use std::os::unix::fs::MetadataExt;
let uinput_metadata = std::fs::metadata("/dev/uinput").ok();
let (uinput_uid, uinput_mode) = uinput_metadata
.as_ref()
.map_or((0, 0), |m| (m.uid(), m.mode() & 0o777));
let uinput_needs_rule = uinput_uid == 0 && uinput_mode == 0o600;
if uinput_needs_rule {
eprintln!("⚠️ CRITICAL: /dev/uinput is root-only (mode 0600).");
eprintln!(" Grab mode requires uinput access to re-inject events.");
eprintln!(" Create this udev rule (copy & paste the entire block):");
eprintln!();
eprintln!(
" echo 'KERNEL==\"uinput\", GROUP=\"input\", MODE=\"0660\"' | sudo tee /etc/udev/rules.d/99-uinput.rules"
);
eprintln!(" sudo udevadm control --reload-rules");
eprintln!(" sudo udevadm trigger --subsystem-match=input");
eprintln!();
eprintln!(
" Then log out and back in (or run: sudo chmod 660 /dev/uinput for immediate test)"
);
eprintln!();
}
if has_input_group && !currently_has_input {
eprintln!("💡 Quick fix: Try running this example with 'newgrp input':");
eprintln!(
" newgrp input -c 'cargo run --example grab --features evdev --no-default-features'"
);
eprintln!();
}
}
}
fn main() {
println!("monio grab example");
println!("===================\n");
println!("Display server: {}\n", get_display_server());
check_linux_permissions();
thread::spawn(move || {
thread::sleep(Duration::from_secs(10));
println!("\n[10 second timeout reached - exiting]");
std::process::exit(0);
});
println!("Auto-exit in 10 seconds (safety timeout)\n");
println!("This example blocks the following:");
println!(" - Q key (completely blocked)");
println!(" - W key (completely blocked)");
println!(" - E key (completely blocked)");
println!("\nTry typing q, w, or e - they won't work in other apps!");
println!("Press Ctrl+C to exit.\n");
if let Err(e) = grab(|event: &Event| {
match event.event_type {
EventType::KeyPressed => {
if let Some(kb) = &event.keyboard {
match kb.key {
Key::KeyQ => {
let count = BLOCKED_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
println!("BLOCKED Q key! (total blocked: {})", count);
return None; }
Key::KeyW => {
let count = BLOCKED_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
println!("BLOCKED W key! (total blocked: {})", count);
return None; }
Key::KeyE => {
let count = BLOCKED_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
println!("BLOCKED E key! (total blocked: {})", count);
return None; }
Key::Escape => {
println!("Escape pressed - passing through");
}
_ => {
}
}
}
}
EventType::MousePressed => {
if let Some(mouse) = &event.mouse {
println!(
"Mouse {:?} pressed at ({:.0}, {:.0}) - passing through",
mouse.button, mouse.x, mouse.y
);
}
}
_ => {}
}
Some(event.clone())
}) {
eprintln!("Error: {}", e);
}
}