#!/usr/bin/env python3
"""keyboard-lock: grab all keyboard devices for N seconds to prevent accidental input.
Usage: keyboard-lock <15|30|60>
Ctrl+U to cancel early.
Requires read/write access to /dev/input/* — either be in the 'input' group
or run with sudo.
"""
import sys
import time
import select
import evdev
from evdev import InputDevice, ecodes
def find_keyboards():
keyboards = []
for path in evdev.list_devices():
try:
dev = InputDevice(path)
caps = dev.capabilities()
keys = caps.get(ecodes.EV_KEY, [])
# Must have alphabetic keys and space — filters out mice, joysticks, etc.
if ecodes.KEY_A in keys and ecodes.KEY_SPACE in keys:
keyboards.append(dev)
except Exception:
pass
return keyboards
def lock_keyboard(duration: int):
keyboards = find_keyboards()
if not keyboards:
sys.exit(
"Error: no keyboard devices found.\n"
"Ensure you are in the 'input' group or run with sudo."
)
grabbed = []
for kb in keyboards:
try:
kb.grab()
grabbed.append(kb)
except PermissionError:
for g in grabbed:
try:
g.ungrab()
except Exception:
pass
sys.exit(
f"Permission denied for {kb.path}.\n"
f"Run: sudo usermod -aG input $USER then log out and back in,\n"
f"or prefix the command with sudo."
)
def release_all():
for kb in grabbed:
try:
kb.ungrab()
except Exception:
pass
names = ", ".join(kb.name for kb in grabbed)
print(f"Grabbed: {names}")
print(f"[LOCKED] {duration}s | Ctrl+U to unlock early\n")
ctrl_keys_held = set()
deadline = time.monotonic() + duration
bar_width = 30
try:
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
break
filled = int(bar_width * (1 - remaining / duration))
bar = "\u2588" * filled + "\u2591" * (bar_width - filled)
print(f"\r [{bar}] {remaining:5.1f}s ", end="", flush=True)
readable, _, _ = select.select(grabbed, [], [], 0.1)
for dev in readable:
try:
for event in dev.read():
if event.type != ecodes.EV_KEY:
continue
is_ctrl = event.code in (
ecodes.KEY_LEFTCTRL,
ecodes.KEY_RIGHTCTRL,
)
if is_ctrl:
if event.value != 0: # pressed or repeat
ctrl_keys_held.add(event.code)
else: # released
ctrl_keys_held.discard(event.code)
elif (
event.code == ecodes.KEY_U
and event.value == 1 # key-down only
and ctrl_keys_held
):
release_all()
print("\n\n[UNLOCKED] Cancelled early.")
return
except Exception:
pass
except KeyboardInterrupt:
pass # Ctrl+C from the terminal won't reach here while grabbed, but just in case
release_all()
print("\n\n[UNLOCKED] Done — keyboard restored.")
def main():
valid = {"15", "30", "60"}
if len(sys.argv) != 2 or sys.argv[1] not in valid:
print(f"Usage: keyboard-lock <15|30|60>")
print(f" Locks the keyboard for the given number of seconds.")
print(f" Press Ctrl+U to cancel early.")
sys.exit(1)
lock_keyboard(int(sys.argv[1]))
if __name__ == "__main__":
main()
Usage
Save to .local/bin/keyboard-lock
Make executable
chmod +x /home/<user>/.local/bin/keyboard-lock
Join the input group:
sudo usermod -aG input $USER
Log out and in, or activate the group in current shell with newgrp input
Finally use with desired timer:
keyboard-lock 15 # 15 seconds
keyboard-lock 30 # 30 seconds
keyboard-lock 60 # 60 seconds
# Can be cancelled early with Ctrl+U