2

On a linux machine (Debian wheezy) I am trying to write an event-based server that does the following:

  1. Grab exclusive input to the input device (a special keyboard) to prevent the keystroke get into the usual event chain.

  2. Register for events in the twisted reactor

  3. Register callback at the deferred returned from waiting for events. This callback would then send an HTTP request after a special key sequence is received.

This is the sample code from the pyevdev package. It works that I get notified and receive the keystrokes accordingly.

By looking at the source code of the read_loop() command it is also using the select statement similar to twisted.

My question

How can I integrate this code into python Twisted? One idea would be to look at the underlying character device /dev/input/event0 and read from it in a non-blocking way. If if would be a regular file, I would use something along the lines of inotify but in this case I do not know.

sample code from the evdev package

from evdev import InputDevice, categorize, ecodes,  list_devices

devices = [InputDevice(fn) for fn in list_devices()]
for dev in devices:
   print(dev.fn, dev.name, dev.phys)

dev = InputDevice('/dev/input/event0')

# get exclusive access to input device
dev.grab()

for event in dev.read_loop():
    if event.type == ecodes.EV_KEY:
            print categorize(event)
GorillaPatch
  • 5,007
  • 1
  • 39
  • 56

1 Answers1

1

evdev.device.InputDevice has a fileno() method , which means that you can hook it up to a Twisted IReactorFDSet; pretty much all reactors available on Linux where evdev is relevant implement this interface. Since an event device is an object with a file descriptor that you can mostly just read from, you need an IReadDescriptor to wrap it.

An implementation of roughly the same logic as your example, but using the reactor to process the events, might look like so:

from zope.interface import implementer
from twisted.internet.interfaces import IReadDescriptor
from twisted.logger import Logger

log = Logger()

@implementer(IReadDescriptor)
class InputDescriptor(object):
    def __init__(self, reactor, inputDevice, eventReceiver):
        self._reactor = reactor
        self._dev = inputDevice
        self._receiver = eventReceiver

    def fileno(self):
        return self._dev.fileno()

    def logPrefix(self):
        return "Input Device: " + repr(self._dev)

    def doRead(self):
        evt = self._dev.read_one()
        try:
            self._receiver.eventReceived(evt)
        except:
            log.failure("while dispatching HID event")

    def connectionLost(self, reason):
        self.stop()
        self._receiver.connectionLost(reason)

    def start(self):
        self._dev.grab()
        self._reactor.addReader(self)

    def stop(self):
        self._reactor.removeReader(self)
        self._dev.ungrab()

from evdev import InputDevice, categorize, ecodes, list_devices

devices = [InputDevice(fn) for fn in list_devices()]
for dev in devices:
   print(dev.fn, dev.name, dev.phys)

dev = InputDevice('/dev/input/event0')

class KeyReceiver(object):
    def eventReceived(self, event):
        if event.type == ecodes.EV_KEY:
            print(categorize(event))

    def connectionLost(self, reason):
        print("Event device lost!!", reason)

from twisted.internet import reactor
InputDescriptor(reactor, dev, KeyReceiver()).start()
reactor.run()

Please note that this code is totally untested, so it may not work quite right at first, but it should at least give you an idea of what is required.

Glyph
  • 31,152
  • 11
  • 87
  • 129
  • 1
    Thank you so much for your help. I can now process the events in the twisted loop. To be honest I like Twisted a lot, but it is hard for me to find anything in the docs. I am already ready "Twisted Network Essentials", however I am often stuck. I think that e.g. Django has easier to search documentation. But thanks for such a mature and great framework anyway. – GorillaPatch Feb 14 '15 at 09:55
  • I've modified to code to read all events instead of the first one – sherpya Aug 29 '19 at 16:54
  • @sherpya The original code was intentionally conservative. If you call `.read()`, you potentially trigger EWOULDBLOCK exceptions which you then need to handle outside the loop. If you call `read_one()` and that leaves data in the buffer, then the FD will remain readable and the loop will dispatch to us again. – Glyph Sep 15 '19 at 05:09
  • 1
    @Glyph, `read_one()` is not iterable I think you would remove the for-loop if you want to keep `read_one()`. I've a qrcode reader and using only `read_one()` it loses events on a rpi0w, works fine on desktop, so in `doRead()` you should really read all available events. I think it's better to use `read()` and maybe add a try/except for `BlockingIOError` – sherpya Sep 16 '19 at 10:38
  • 1
    btw there is no `BlockingIOError` in the library, `device_read_many()` raises `IOError` if native `read()` returns < 0 – sherpya Sep 16 '19 at 10:42