Skip to content

Add readonly parameter to InputDevice and writable parameter to list_devices/is_device#251

Open
xz-dev wants to merge 1 commit intogvalkov:mainfrom
xz-dev:feat/inputdevice-readonly
Open

Add readonly parameter to InputDevice and writable parameter to list_devices/is_device#251
xz-dev wants to merge 1 commit intogvalkov:mainfrom
xz-dev:feat/inputdevice-readonly

Conversation

@xz-dev
Copy link
Copy Markdown

@xz-dev xz-dev commented Apr 1, 2026

Summary

  • Add readonly parameter to InputDevice.__init__() (readonly=False by default, no behavior change)
  • Add writable parameter to list_devices() and is_device() (writable=True by default, no behavior change)

Motivation

Currently InputDevice.__init__() always attempts O_RDWR first and falls back to O_RDONLY on failure. This has two practical issues:

  1. Permission-restricted environments: When a process only has read access to /dev/input/eventX (common without udev rules or root), the O_RDWR attempt always fails before falling back. The readonly=True parameter lets callers skip the unnecessary failed syscall.

  2. API correctness: Callers that only need to read device state (e.g. querying leds(), capabilities()) have no way to express "I only need read access" — the constructor always attempts write access regardless of intent.

The writable parameter on list_devices()/is_device() complements this by allowing enumeration of devices that are readable but not writable.

The existing need_write decorator on EventIO.write() already guards against writes on read-only fds, so no additional protection is needed.

Note on LED pulsing issue

A real-world report described touchpad LED pulsing when polling InputDevice.leds() at 1–2 Hz. After investigating the Linux kernel source (drivers/input/evdev.c, drivers/input/input.c), the root cause is not O_RDWR vs O_RDONLY — the kernel's evdev_open() does not distinguish between open modes at all. Both go through the same path: evdev_open()input_open_device()dev->open(dev).

The actual cause is the repeated open/close cycle itself. Each time the fd is closed, dev->users drops to 0; the next open triggers the hardware driver's open() callback again (e.g. hidinput_open()hid_hw_open() → USB/I2C transport layer reinitialization), which on certain hardware (Tuxedo Stellaris 15 Gen3) causes the EC firmware to re-assert LED state.

The correct fix for the LED pulsing issue is to keep the InputDevice fd open across queries rather than creating/destroying it each time. The EVIOCGLED ioctl used by leds() is a pure kernel memory read (bitmap_copy) and does not touch hardware.

Changes

File Change Default Breaking
src/evdev/device.py Add readonly=False to __init__ Unchanged No
src/evdev/util.py Add writable=True to list_devices/is_device Unchanged No

Usage

# Read-only device open — no O_RDWR attempted
device = InputDevice('/dev/input/event0', readonly=True)
print(device.leds())  # works
device.set_led(LED_NUML, 1)  # raises EvdevError (existing need_write guard)

# List readable-only devices
paths = list_devices(writable=False)

Relates to #35

xz-dev added a commit to xz-dev/numlockw that referenced this pull request Apr 1, 2026
Use evdev readonly mode for the status command to prevent O_RDWR open
from triggering LED re-assertion on certain hardware (e.g. Tuxedo
Stellaris touchpad LED).

Temporarily depends on xz-dev/python-evdev@feat/inputdevice-readonly
pending upstream merge (gvalkov/python-evdev#251).

See: https://www.reddit.com/r/tuxedocomputers/comments/1rtj3c9/
@xz-dev xz-dev force-pushed the feat/inputdevice-readonly branch from 0a0b410 to 986ba0f Compare April 1, 2026 13:51
xz-dev added a commit to xz-dev/numlockw that referenced this pull request Apr 1, 2026
Use evdev readonly mode for the status command to prevent O_RDWR open
from triggering LED re-assertion on certain hardware (e.g. Tuxedo
Stellaris touchpad LED).

Temporarily depends on xz-dev/python-evdev@feat/inputdevice-readonly
pending upstream merge (gvalkov/python-evdev#251).

See: https://www.reddit.com/r/tuxedocomputers/comments/1rtj3c9/
xz-dev added a commit to xz-dev/numlockw that referenced this pull request Apr 1, 2026
…ware

Keep evdev device fds permanently open so the kernel never re-triggers
driver open callbacks (input_open_device) on subsequent opens. This
eliminates LED pulsing caused by repeated open/close cycles on hardware
like Tuxedo Stellaris 15 Gen3.

See: https://www.reddit.com/r/tuxedocomputers/comments/1rtj3c9/
See: gvalkov/python-evdev#251
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant