mgth/LittleBigMouse · error · std::io::Error
NotFound
NotFound
Error message
no grabbable mouse
What it means
The evdev hook arm routine scans candidate /dev/input device paths, tries to grab each one with EVIOCGRAB, and collects the successfully grabbed mice. If the final set is empty — either no mouse-like devices were found or every grab attempt failed — it returns io::ErrorKind::NotFound with "no grabbable mouse". This signals that the daemon cannot intercept any pointer input at the kernel level.
Solutions
- Run the daemon with sufficient privileges: sudo, or add the user to the 'input' group (usermod -aG input $USER) and re-login.
- Check /proc/bus/input/devices for a device with 'mouse' handlers and confirm the corresponding /dev/input/eventX node exists and is readable.
- Look at the preceding eprintln lines "evdev: cannot grab {path:?}" to see the per-device errno and fix the root cause (permission, busy, gone).
- If running under Wayland/sandbox, use a session with libinput access or run with elevated permissions; under containers, bind-mount /dev/input.
Example fix
// before
Err(e) => eprintln!("[LittleBigMouse.Hook] evdev: cannot grab {path:?}: {e}"),
// after (diagnose)
// run: sudo ./lbm-hook OR usermod -aG input $USER && re-login
// then re-run arm(); device grab succeeds and devices is non-empty Defensive patterns
Strategy: try-catch
Validate before calling
// before calling arm()
if !std::path::Path::new("/dev/input").read_dir().map(|d| d.count() > 0).unwrap_or(false) {
eprintln!("no /dev/input devices visible; are you in the 'input' group?");
} Try / catch
match arm() {
Ok(devices) => { /* proceed */ }
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
eprintln!("no grabbable mouse: check 'input' group membership and /dev/input access");
// fall back to non-hook mode or exit cleanly
}
Err(e) => return Err(e),
} Prevention
- Add the service user to the 'input' group and re-login before deploying.
- Smoke-test device access with `head -c1 /dev/input/event0` in CI/setup scripts.
- Log per-path grab errors (as the source already does) to distinguish permission vs missing-device causes.
When it happens
Trigger: Calling arm() when (a) no /dev/input/event* device matches the mouse filter (devices.is_empty() before grabbing), or (b) every EVIOCGRAB call returned Err (logged per-path as "cannot grab {path:?}") — typically EPERM/EACCES from lacking permissions, or the device vanished between enumeration and grab.
Common situations: Running the hook without root or without membership in the 'input' group; running inside a container/VM without /dev/input passthrough; Wayland session where the compositor holds the devices; udev rules denying EVIOCGRAB; device hot-unplugged mid-scan.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of mgth/LittleBigMouse@7a42f01d47 (2026-09-16).
Data as JSON: /api/errors/5d7ac8641eb7721b.
Report an issue: GitHub.
Appendix: source
Thrown at rust/crates/lbm-hook/src/hook/linux/evdev/router.rs:211
dev.set_nonblocking(true)?;
match dev.grab() {
Ok(()) => {
let id = dev.input_id();
let name = dev.name().unwrap_or("?").to_string();
let settings = accel_cfg.for_device(id.vendor(), id.product(), &name);
eprintln!("[LittleBigMouse.Hook] evdev: grabbed {name} ({path:?}, accel {:?} speed {})",
settings.profile, settings.speed);
devices.push((
path,
dev,
PointerAccel::new(settings.profile, settings.speed),
));
}
Err(e) => eprintln!("[LittleBigMouse.Hook] evdev: cannot grab {path:?}: {e}"),
}
}
if devices.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no grabbable mouse",
));
}
eprintln!(
"[LittleBigMouse.Hook] evdev: observing {} keyboard(s) for ctrl-override",
keyboards.len()
);
// Take over from where the cursor really is: ask the compositor (KWin
// scripting, logical coordinates — the zones' space), else where the
// previous arm left it. Only a first arm on a non-KDE compositor falls
// back to a neutral point (centre of the first main zone).
let (start, origin) = match probed {
Some(p) => (p, "compositor"),
None => match resume_at {
Some(p) => (p, "previous position"),
None => (View on GitHub (pinned to 7a42f01d47)