From 0b68da9dfa23a1155113dce07dc70fbc9e69f6dc Mon Sep 17 00:00:00 2001 From: Lauri Niskanen Date: Fri, 24 Jan 2014 18:12:43 +0200 Subject: [PATCH] Support rumble events --- bin/rumbletest.py | 39 ++++++++++++++++++++++++++++ evdev/uinput.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++- evdev/uinput.py | 18 +++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100755 bin/rumbletest.py diff --git a/bin/rumbletest.py b/bin/rumbletest.py new file mode 100755 index 0000000..73b5c03 --- /dev/null +++ b/bin/rumbletest.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Example of a device listening to rumble events. + +Test using any program that sends rumble events to the uinput device (e.g. fftest) +''' + +import time + +from evdev import UInput, UInputError, ecodes + +joystick = UInput() + +had_event = True + +while True: + event = joystick.read() + + if event == None: + if had_event: + had_event = False + print('Waiting for events', end='', flush=True) + else: + print('.', end='', flush=True) + else: + had_event = True + print() + print() + + if event == ecodes.FF_STATUS_PLAYING: + print("Rumble playing!") + elif event == ecodes.FF_STATUS_STOPPED: + print("Rumble stopped!") + else: + print("Received an unknown event!") + + time.sleep(0.2) diff --git a/evdev/uinput.c b/evdev/uinput.c index c885ed7..0458d23 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -33,7 +33,7 @@ uinput_open(PyObject *self, PyObject *args) int ret = PyArg_ParseTuple(args, "s", &devnode); if (!ret) return NULL; - int fd = open(devnode, O_WRONLY | O_NONBLOCK); + int fd = open(devnode, O_RDWR | O_NONBLOCK); if (fd < 0) { PyErr_SetString(PyExc_IOError, "could not open uinput device in write mode"); return NULL; @@ -76,6 +76,14 @@ uinput_create(PyObject *self, PyObject *args) { uidev.absflat[abscode] = PyLong_AsLong(PyList_GetItem(item, 4)); } + uidev.ff_effects_max = 1; + + if (ioctl(fd, UI_SET_EVBIT, EV_FF) < 0) + goto on_err; + + if (ioctl(fd, UI_SET_FFBIT, FF_RUMBLE) < 0) + goto on_err; + if (write(fd, &uidev, sizeof(uidev)) != sizeof(uidev)) goto on_err; @@ -142,6 +150,58 @@ uinput_write(PyObject *self, PyObject *args) } +static PyObject * +uinput_read(PyObject *self, PyObject *args) +{ + int fd; + + int ret = PyArg_ParseTuple(args, "i", &fd); + if (!ret) return NULL; + + size_t len; + struct input_event event; + + struct uinput_ff_upload upload; + struct uinput_ff_erase erase; + + len = read(fd, &event, sizeof(event)); + if (len == -1) { + if (errno == EAGAIN) { + // No events available + Py_RETURN_NONE; + } else { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + } else if (len != sizeof(event)) { + return NULL; + } + + switch (event.type) { + case EV_UINPUT: + switch (event.code) { + case UI_FF_UPLOAD: + upload.request_id = event.value; + if (ioctl(fd, UI_BEGIN_FF_UPLOAD, &upload) < 0) return NULL; + if (ioctl(fd, UI_END_FF_UPLOAD, &upload) < 0) return NULL; + return Py_BuildValue("i", UI_FF_UPLOAD); + + case UI_FF_ERASE: + erase.request_id = event.value; + if (ioctl(fd, UI_BEGIN_FF_ERASE, &erase) < 0) return NULL; + if (ioctl(fd, UI_END_FF_ERASE, &erase) < 0) return NULL; + return Py_BuildValue("i", UI_FF_ERASE); + + default: break; + } + + default: break; + } + + Py_RETURN_NONE; +} + + static PyObject * uinput_enable_event(PyObject *self, PyObject *args) { @@ -197,6 +257,9 @@ static PyMethodDef MethodTable[] = { { "write", uinput_write, METH_VARARGS, "Write event to uinput device."}, + { "read", uinput_read, METH_VARARGS, + "Read event from uinput device."}, + { "enable", uinput_enable_event, METH_VARARGS, "Enable a type of event."}, diff --git a/evdev/uinput.py b/evdev/uinput.py index 30c3a06..8450e08 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -159,6 +159,24 @@ def write(self, etype, code, value): _uinput.write(self.fd, etype, code, value) + def read(self): + ''' + Read a queued event from the uinput device. Returns None if no events + are available. + ''' + event = _uinput.read(self.fd) + + # Return values from uinput.h + UI_FF_UPLOAD = 1 # start rumble + UI_FF_ERASE = 2 # stop rumble + + if event == UI_FF_UPLOAD: + return ecodes.FF_STATUS_PLAYING + elif event == UI_FF_ERASE: + return ecodes.FF_STATUS_STOPPED + + # No supported events available + def syn(self): ''' Inject a ``SYN_REPORT`` event into the input subsystem. Events