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

  1. Run the daemon with sufficient privileges: sudo, or add the user to the 'input' group (usermod -aG input $USER) and re-login.
  2. Check /proc/bus/input/devices for a device with 'mouse' handlers and confirm the corresponding /dev/input/eventX node exists and is readable.
  3. Look at the preceding eprintln lines "evdev: cannot grab {path:?}" to see the per-device errno and fix the root cause (permission, busy, gone).
  4. 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

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)