Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/evdev/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,26 @@ class InputDevice(EventIO, Generic[_AnyStr]):

__slots__ = ("path", "fd", "info", "name", "phys", "uniq", "_rawcapabilities", "version", "ff_effects_count")

def __init__(self, dev: Union[_AnyStr, "os.PathLike[_AnyStr]"]):
def __init__(self, dev: Union[_AnyStr, "os.PathLike[_AnyStr]"], readonly: bool = False):
"""
Arguments
---------
dev : str|bytes|PathLike
Path to input device
readonly : bool
If True, the device is opened in read-only mode (``O_RDONLY``)
without attempting ``O_RDWR`` first. This avoids triggering
firmware side-effects (such as LED state re-assertion) on
certain hardware. Default is False.
"""

#: Path to input device.
self.path: _AnyStr = dev if not hasattr(dev, "__fspath__") else dev.__fspath__()

# Certain operations are possible only when the device is opened in read-write mode.
try:
if readonly:
raise OSError
fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK)
except OSError:
fd = os.open(dev, os.O_RDONLY | os.O_NONBLOCK)
Expand Down
31 changes: 25 additions & 6 deletions src/evdev/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,33 @@
from .events import InputEvent, event_factory, KeyEvent, RelEvent, AbsEvent, SynEvent


def list_devices(input_device_dir: Union[str, bytes, os.PathLike] = "/dev/input") -> List[str]:
"""List readable character devices in ``input_device_dir``."""
def list_devices(input_device_dir: Union[str, bytes, os.PathLike] = "/dev/input", writable: bool = True) -> List[str]:
"""List readable (and optionally writable) character devices in ``input_device_dir``.

Arguments
---------
input_device_dir : str|bytes|PathLike
Path to the input device directory. Default is ``/dev/input``.
writable : bool
If True (default), only list devices that are both readable and
writable. If False, list all readable devices.
"""

fns = glob.glob("{}/event*".format(input_device_dir))
return list(filter(is_device, fns))
return list(filter(lambda fn: is_device(fn, writable=writable), fns))


def is_device(fn: Union[str, bytes, os.PathLike], writable: bool = True) -> bool:
"""Check if ``fn`` is a readable (and optionally writable) character device.

def is_device(fn: Union[str, bytes, os.PathLike]) -> bool:
"""Check if ``fn`` is a readable and writable character device."""
Arguments
---------
fn : str|bytes|PathLike
Path to the device file.
writable : bool
If True (default), also check for write access. If False, only
check for read access.
"""

if not os.path.exists(fn):
return False
Expand All @@ -26,7 +44,8 @@ def is_device(fn: Union[str, bytes, os.PathLike]) -> bool:
if not stat.S_ISCHR(m):
return False

if not os.access(fn, os.R_OK | os.W_OK):
access = os.R_OK | os.W_OK if writable else os.R_OK
if not os.access(fn, access):
return False

return True
Expand Down