Source code for evdev.eventio_async
import asyncio
import select
import sys
from . import eventio
from .events import InputEvent
# needed for compatibility
from .eventio import EvdevError
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing import Any as Self
class ReadIterator:
def __init__(self, device: "EventIO"):
self.current_batch = iter(())
self.device = device
# Standard iterator protocol.
def __iter__(self) -> Self:
return self
def __next__(self) -> InputEvent:
try:
# Read from the previous batch of events.
return next(self.current_batch)
except StopIteration:
r, w, x = select.select([self.device.fd], [], [])
self.current_batch = self.device.read()
return next(self.current_batch)
def __aiter__(self) -> Self:
return self
def __anext__(self) -> "asyncio.Future[InputEvent]":
future = asyncio.get_running_loop().create_future()
try:
# Read from the previous batch of events.
future.set_result(next(self.current_batch))
except StopIteration:
def next_batch_ready(batch):
try:
self.current_batch = batch.result()
future.set_result(next(self.current_batch))
except Exception as e:
future.set_exception(e)
self.device.async_read().add_done_callback(next_batch_ready)
return future
[docs]
class EventIO(eventio.EventIO):
# The event loop a reader was last registered on, or None if no async read
# has been awaited yet. Set in _do_when_readable, used by close().
_loop: "asyncio.AbstractEventLoop | None" = None
def _do_when_readable(self, callback) -> None:
# Remember the loop the reader is registered on so that close() can
# remove it later, even when called without a running event loop.
loop = asyncio.get_running_loop()
self._loop = loop
def ready():
loop.remove_reader(self.fileno())
callback()
loop.add_reader(self.fileno(), ready)
def _set_result(self, future, cb) -> None:
try:
future.set_result(cb())
except Exception as error:
future.set_exception(error)
[docs]
def async_read_one(self) -> "asyncio.Future[InputEvent]":
"""
Asyncio coroutine to read and return a single input event as
an instance of :class:`InputEvent <evdev.events.InputEvent>`.
"""
future = asyncio.get_running_loop().create_future()
self._do_when_readable(lambda: self._set_result(future, self.read_one))
return future
[docs]
def async_read(self) -> "asyncio.Future[InputEvent]":
"""
Asyncio coroutine to read multiple input events from device. Return
a generator object that yields :class:`InputEvent <evdev.events.InputEvent>`
instances.
"""
future = asyncio.get_running_loop().create_future()
self._do_when_readable(lambda: self._set_result(future, self.read))
return future
[docs]
def async_read_loop(self) -> ReadIterator:
"""
Return an iterator that yields input events. This iterator is
compatible with the ``async for`` syntax.
"""
return ReadIterator(self)
[docs]
def close(self) -> None:
# A reader is only registered once an async read has been awaited, in
# which case _do_when_readable recorded the loop it was added to.
loop = self._loop
if loop is None or loop.is_closed():
# No reader was ever registered, or its loop is already gone, so
# there is nothing to remove the reader from.
return
loop.remove_reader(self.fileno())