From d22ef950eeb47c2ca72ce31dd2082646c8a9bd46 Mon Sep 17 00:00:00 2001 From: Lauri Niskanen Date: Fri, 24 Jan 2014 18:12:43 +0200 Subject: [PATCH 1/9] 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 From bf582c2e2fda178c929d8c1a361900075b05ec2a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 15 Apr 2014 00:44:21 +0200 Subject: [PATCH 2/9] list_devices() must also check for write access --- evdev/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evdev/util.py b/evdev/util.py index baad38c..ccc6b58 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -18,7 +18,7 @@ def list_devices(input_device_dir='/dev/input'): def is_device(fn): - '''Check if ``fn`` exists, is readable and if it is a character device.''' + '''Check if ``fn`` is a readable and writable character device.''' if not os.path.exists(fn): return False @@ -27,7 +27,7 @@ def is_device(fn): if not stat.S_ISCHR(m): return False - if not os.access(fn, os.R_OK): + if not os.access(fn, os.R_OK | os.W_OK): return False return True From 16bfa790f01c416f8306afa4fb2fb0a609cf9855 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 15 Apr 2014 00:47:11 +0200 Subject: [PATCH 3/9] correct vendor/product order for str(DeviceInfo) --- evdev/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index 789a6a1..a83b4ee 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -68,7 +68,7 @@ def __str__(self): class DeviceInfo(_DeviceInfo): def __str__(self): - msg = 'bus: {:04x}, product {:04x}, vendor {:04x}, version {:04x}' + msg = 'bus: {:04x}, vendor {:04x}, product {:04x}, version {:04x}' return msg.format(*self) From f455e2d61052bee4d8d08e8dc57ab0c3358736f9 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 3 May 2014 17:34:48 +0200 Subject: [PATCH 4/9] mention uinput-mapper --- doc/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/index.rst b/doc/index.rst index 1c53e02..87a15dc 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -30,6 +30,7 @@ Similar Projects ================ * `python-uinput`_ +* `uinput-mapper`_ * `ruby-evdev`_ * `evdev`_ (ctypes) @@ -69,3 +70,4 @@ Indices and Tables .. _python-uinput: https://github.com/tuomasjjrasanen/python-uinput .. _ruby-evdev: http://technofetish.net/repos/buffaloplay/ruby_evdev/doc/ .. _evdev: http://svn.navi.cx/misc/trunk/python/evdev/ +.. _uinput-mapper: https://github.com/MerlijnWajer/uinput-mapper From 3450482f25e177ed11f2045d6552463532a1b515 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 3 May 2014 17:38:32 +0200 Subject: [PATCH 5/9] include certain constants from uinput.h --- evdev/ecodes.py | 5 +++-- evdev/genecodes.py | 41 +++++++++++++++++++++++++++-------------- setup.py | 15 +++++++++------ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/evdev/ecodes.py b/evdev/ecodes.py index c94b0c1..ce27301 100644 --- a/evdev/ecodes.py +++ b/evdev/ecodes.py @@ -1,12 +1,13 @@ # encoding: utf-8 ''' -This modules exposes the integer constants defined in ``linux/input.h``. +This modules exposes integer constants defined in ``linux/input.h`` +and ``linux/uinput.h``. Exposed constants:: KEY, ABS, REL, SW, MSC, LED, BTN, REP, SND, ID, EV, - BUS, SYN, FF, FF_STATUS + BUS, SYN, FF, FF_STATUS, UI, UINPUT This module also provides numerous reverse and forward mappings that are best illustrated by a few examples:: diff --git a/evdev/genecodes.py b/evdev/genecodes.py index 8572920..4fa2789 100755 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -3,7 +3,7 @@ ''' Generate a Python extension module that exports macros from -/usr/include/linux/input.h +/usr/include/linux/input.h and /usr/include/linux/uinput.h ''' import os, sys, re @@ -12,12 +12,13 @@ template = r''' #include #include +#include /* Automatically generated by evdev.genecodes */ -/* Generated on %s */ +/* Generated on %(uname)s */ #define MODULE_NAME "_ecodes" -#define MODULE_HELP "linux/input.h macros" +#define MODULE_HELP "linux/input.h and linux/uinput.h macros" static PyMethodDef MethodTable[] = { { NULL, NULL, 0, NULL} @@ -49,7 +50,11 @@ if (m == NULL) return NULL; -%s + /* input.h constants */ +%(input)s + + /* uinput.h constants */ +%(uinput)s return m; } @@ -69,16 +74,21 @@ #endif ''' -header = '/usr/include/linux/input.h' if len(sys.argv) == 1 else sys.argv[1] -regex = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF)_\w+)' -regex = re.compile(regex) +inputh = '/usr/include/linux/input.h' if len(sys.argv) == 1 else sys.argv[1] +uinputh = '/usr/include/linux/uinput.h' if len(sys.argv) < 3 else sys.argv[2] + +inputh_re = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF)_\w+)' +inputh_re = re.compile(inputh_re) +uinputh_re = r'#define +((?:EV|UI_FF)_\w+)' +uinputh_re = re.compile(uinputh_re) -if not os.path.exists(header): - print('no such file: %s' % header) - sys.exit(1) +for fn in inputh, uinputh: + if not os.path.exists(fn): + print('no such file: %s' % inputh) + sys.exit(1) -def getmacros(): - for line in open(header): +def getmacros(fn, regex): + for line in open(fn): macro = regex.search(line) if macro: yield ' PyModule_AddIntMacro(m, %s);' % macro.group(1) @@ -86,5 +96,8 @@ def getmacros(): uname = list(os.uname()); del uname[1] uname = ' '.join(uname) -macros = os.linesep.join(getmacros()) -print(template % (uname, macros)) +input_macros = os.linesep.join(getmacros(inputh, inputh_re)) +uinput_macros = os.linesep.join(getmacros(uinputh, uinputh_re)) + +ctx = {'uname': uname, 'input': input_macros, 'uinput': uinput_macros} +print(template % ctx) diff --git a/setup.py b/setup.py index 2448671..ff0f6e6 100755 --- a/setup.py +++ b/setup.py @@ -59,12 +59,14 @@ def create_ecodes(): # :todo: expose as a command option - header = '/usr/include/linux/input.h' + input_header = '/usr/include/linux/input.h' + uinput_header = '/usr/include/linux/uinput.h' - if not os.path.isfile(header): + if not all(map(os.path.isfile, (input_header, uinput_header))): msg = '''\ - The linux/input.h header file is missing. You will have to - install the headers for your kernel in order to continue: + The linux/input.h and linux/uinput.h header files are + missing. You will have to install the headers for your kernel + in order to continue: yum install kernel-headers-$(uname -r) apt-get intall linux-headers-$(uname -r) @@ -75,8 +77,9 @@ def create_ecodes(): from subprocess import check_call - print('writing ecodes.c (using %s)' % header) - cmd = '%s genecodes.py %s > ecodes.c' % (sys.executable, header) + headers = (input_header, uinput_header) + print('writing ecodes.c (using %s and %s)' % headers) + cmd = '%s genecodes.py %s > ecodes.c' % (sys.executable, ' '.join(headers)) check_call(cmd, cwd="%s/evdev" % here, shell=True) From b86629ec90eeb85939bd75afb68d39528a1f7604 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 3 May 2014 17:40:09 +0200 Subject: [PATCH 6/9] new indentation preferences --- setup.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/setup.py b/setup.py index ff0f6e6..3af9d5c 100755 --- a/setup.py +++ b/setup.py @@ -33,27 +33,26 @@ ecodes_c = Extension('evdev._ecodes', sources=['evdev/ecodes.c'], ) # extra_compile_args=['-O0']) kw = { - 'name' : 'evdev', - 'version' : '0.4.3', - - 'description' : 'Bindings for the linux input handling subsystem', - 'long_description' : open(pjoin(here, 'README.rst')).read(), - - 'author' : 'Georgi Valkov', - 'author_email' : 'georgi.t.valkov@gmail.com', - 'license' : 'Revised BSD License', - - 'keywords' : 'evdev input uinput', - 'classifiers' : classifiers, - 'url' : 'https://github.com/gvalkov/python-evdev', - - 'packages' : ['evdev'], - 'ext_modules' : [input_c, uinput_c, ecodes_c], - 'tests_require' : ['pytest'], - - 'include_package_data' : False, - 'zip_safe' : True, - 'cmdclass' : {}, + 'name': 'evdev', + 'version': '0.4.3', + 'description': 'Bindings for the linux input-handling subsystem', + 'long_description': open(pjoin(here, 'README.rst')).read(), + + 'author': 'Georgi Valkov', + 'author_email': 'georgi.t.valkov@gmail.com', + 'license': 'Revised BSD License', + + 'classifiers': classifiers, + 'keywords': 'evdev input uinput forcefeedback', + 'url': 'https://github.com/gvalkov/python-evdev', + + 'packages': ['evdev'], + 'ext_modules': [input_c, uinput_c, ecodes_c], + 'tests_require': ['pytest'], + + 'zip_safe': True, + 'cmdclass': {}, + 'include_package_data': False, } From 3ebe5499e60deafba35fac838f8dd9085a1da994 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 3 May 2014 17:40:48 +0200 Subject: [PATCH 7/9] add the 'mode' argument to list_devices() and is_valid() --- evdev/util.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/evdev/util.py b/evdev/util.py index ccc6b58..e7618ed 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -3,21 +3,28 @@ import os import stat import glob +import functools from evdev import ecodes from evdev.events import event_factory -def list_devices(input_device_dir='/dev/input'): +def list_devices(input_device_dir='/dev/input', mode='rw'): '''List readable, character devices.''' - fns = glob.glob('{}/event*'.format(input_device_dir)) - fns = list(filter(is_device, fns)) + if isinstance(mode, str): + modemap = {'r': os.R_OK, 'w': os.W_OK} + mode = [modemap[i] for i in mode] + mode = functools.reduce(lambda x,y: x|y, mode) - return fns + files = glob.glob('%s/event*' % input_device_dir) + pred = functools.partial(is_device, mode=mode) + files = list(filter(pred, files)) + return files -def is_device(fn): + +def is_device(fn, mode=os.R_OK | os.W_OK): '''Check if ``fn`` is a readable and writable character device.''' if not os.path.exists(fn): @@ -27,7 +34,7 @@ def is_device(fn): if not stat.S_ISCHR(m): return False - if not os.access(fn, os.R_OK | os.W_OK): + if not os.access(fn, mode): return False return True From fb8553a265520ec3862d623f3d05b87c7e69c219 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 15 Apr 2014 00:51:43 +0200 Subject: [PATCH 8/9] wip --- bin/fftest-uinput-test.py | 49 ++++++++++++++++ bin/rumbletest.py | 39 ------------- doc/conf.py | 2 +- evdev/uinput.c | 115 ++++++++++++-------------------------- evdev/uinput.py | 72 ++++++++++++++++-------- 5 files changed, 135 insertions(+), 142 deletions(-) create mode 100755 bin/fftest-uinput-test.py delete mode 100755 bin/rumbletest.py diff --git a/bin/fftest-uinput-test.py b/bin/fftest-uinput-test.py new file mode 100755 index 0000000..9a2f37a --- /dev/null +++ b/bin/fftest-uinput-test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +evdev example - uinput force feedback event monitor +''' + +from __future__ import print_function +import time, sys + +from evdev import UInput, UInputError, ecodes + + +cap = { + ecodes.EV_FF: [ + ecodes.FF_CONSTANT, + ecodes.FF_PERIODIC, + ecodes.FF_RAMP, + ecodes.FF_SPRING, + ecodes.FF_FRICTION, + ecodes.FF_DAMPER, + # ecodes.FF_RUMBLE, + ecodes.FF_INERTIA, + ] +} + +ui = UInput(cap, ff_effects_max=10) + +try: + print('uinput device created - run "fftest %s" to test' % ui.device.fn) +except AttributeError: + print('error: could not find corresponding /dev/input/eventXX device - check permissions') + sys.exit(1) + +print('waiting for events:') +ui.read_loop() +# while True: +# status, event = ui.read() + +# if status is not None: +# print(event) +# # if status == ecodes.FF_STATUS_PLAYING: +# # print("rumble playing") +# # elif status == ecodes.FF_STATUS_STOPPED: +# # print("rumble stopped") +# # else: +# # print("received an unknown event!") + +# time.sleep(0.2) diff --git a/bin/rumbletest.py b/bin/rumbletest.py deleted file mode 100755 index 73b5c03..0000000 --- a/bin/rumbletest.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/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/doc/conf.py b/doc/conf.py index ee8ec57..93dc16c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -47,7 +47,7 @@ # General information about the project. project = u'python-evdev' -copyright = u'2012-2013, Georgi Valkov' +copyright = u'2012-2014, Georgi Valkov' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/evdev/uinput.c b/evdev/uinput.c index 0458d23..2e1e270 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -12,8 +12,7 @@ #include -int _uinput_close(int fd) -{ +int _uinput_close(int fd) { if (ioctl(fd, UI_DEV_DESTROY) < 0) { int oerrno = errno; close(fd); @@ -47,15 +46,18 @@ static PyObject * uinput_create(PyObject *self, PyObject *args) { int fd, len, i, abscode; __u16 vendor, product, version, bustype; + __u32 ff_effects_max; PyObject *absinfo = NULL, *item = NULL; struct uinput_user_dev uidev; const char* name; - int ret = PyArg_ParseTuple(args, "ishhhhO", &fd, &name, &vendor, - &product, &version, &bustype, &absinfo); - if (!ret) return NULL; + int ret = PyArg_ParseTuple(args, "ishhhhOi", &fd, &name, &vendor, + &product, &version, &bustype, &absinfo, + &ff_effects_max); + if (!ret) + return NULL; memset(&uidev, 0, sizeof(uidev)); strncpy(uidev.name, name, UINPUT_MAX_NAME_SIZE); @@ -64,6 +66,9 @@ uinput_create(PyObject *self, PyObject *args) { uidev.id.version = version; uidev.id.bustype = bustype; + // force-feedback-capable driver + uidev.ff_effects_max = ff_effects_max; + len = PyList_Size(absinfo); for (i=0; i (ABS_X, 0, 255, 0, 0) @@ -76,25 +81,9 @@ 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; - /* if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) */ - /* goto on_err; */ - /* int i; */ - /* for (i=0; i` instance #: for the fake input device. ``None`` if the device cannot be @@ -95,10 +111,10 @@ def __exit__(self, type, value, tb): self.close() def __repr__(self): - # :todo: - v = (repr(getattr(self, i)) for i in - ('name', 'bustype', 'vendor', 'product', 'version')) - return '{}({})'.format(self.__class__.__name__, ', '.join(v)) + attr = 'name', 'bustype', 'vendor', 'product', 'version' + vals = (repr(getattr(self, i)) for i in attr) + vals = ', '.join(vals) + return '%s(%s)' % (self.__class__.__name__, vals) def __str__(self): msg = ('name "{}", bus "{}", vendor "{:04x}", product "{:04x}", version "{:04x}"\n' @@ -159,23 +175,33 @@ def write(self, etype, code, value): _uinput.write(self.fd, etype, code, value) - def read(self): + def read_loop(self): ''' - Read a queued event from the uinput device. Returns None if no events - are available. + 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 + while True: + r, w, x = select.select([self.fd], [], []) + if r: + event = _input.device_read(self.fd) + _, _, etype, code, value = event + + if etype == ecodes.EV_FF: + print('callback:', event) + + if etype == ecodes.EV_UINPUT: + if code == ecodes.UI_FF_UPLOAD: + data = _uinput.upload_effect(self.fd, value) + print(data) + effect = ff.Effect.from_buffer(data) + if len(self.ff_effects) > ecodes.FF_EFFECT_MAX: + self.ff_effects.pop() + self.ff_effects.append(effect) + elif code == ecodes.UI_FF_ERASE: + pass + else: + print('unknown') - # No supported events available def syn(self): ''' From a231180ac4c2eedfb5766c7aa956426debc43afb Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 3 May 2014 19:01:58 +0200 Subject: [PATCH 9/9] [wip] --- bin/fftest-uinput-test.py | 26 ++++++---------- bin/fftest.py | 19 ++++++++++++ evdev/ff.py | 5 ++- evdev/input.c | 27 +---------------- evdev/uinput.c | 29 +++++++++++++++++- evdev/uinput.py | 64 +++++++++++++++++++++++++-------------- evdev/util.h | 23 ++++++++++++++ 7 files changed, 126 insertions(+), 67 deletions(-) create mode 100755 bin/fftest.py create mode 100644 evdev/util.h diff --git a/bin/fftest-uinput-test.py b/bin/fftest-uinput-test.py index 9a2f37a..62edf24 100755 --- a/bin/fftest-uinput-test.py +++ b/bin/fftest-uinput-test.py @@ -6,7 +6,7 @@ ''' from __future__ import print_function -import time, sys +import time, sys, select from evdev import UInput, UInputError, ecodes @@ -24,7 +24,11 @@ ] } -ui = UInput(cap, ff_effects_max=10) +def ffcb(event): + import ipdb ; ipdb.set_trace() + print(event) + +ui = UInput(cap, ff_effects_max=10, ff_callback=ffcb) try: print('uinput device created - run "fftest %s" to test' % ui.device.fn) @@ -33,17 +37,7 @@ sys.exit(1) print('waiting for events:') -ui.read_loop() -# while True: -# status, event = ui.read() - -# if status is not None: -# print(event) -# # if status == ecodes.FF_STATUS_PLAYING: -# # print("rumble playing") -# # elif status == ecodes.FF_STATUS_STOPPED: -# # print("rumble stopped") -# # else: -# # print("received an unknown event!") - -# time.sleep(0.2) +while True: + r, w, x = select.select([ui.fd], [], []) + if r: + ui.read() diff --git a/bin/fftest.py b/bin/fftest.py new file mode 100755 index 0000000..2c1144a --- /dev/null +++ b/bin/fftest.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# encoding: utf-8 + +''' +evdev example - tests the force feedback driver +''' + +from sys import argv, exit +from select import select +from getopt import gnu_getopt + +from evdev import ecodes, ff, InputDevice + + +usage = 'usage: fftest [options] ' + +device = InputDevice(argv[1]) + +print('Axes query:') diff --git a/evdev/ff.py b/evdev/ff.py index e4598d7..187448d 100644 --- a/evdev/ff.py +++ b/evdev/ff.py @@ -8,6 +8,7 @@ _u16 = ctypes.c_uint16 _u32 = ctypes.c_uint32 _s16 = ctypes.c_int16 +_s32 = ctypes.c_int32 class Replay(ctypes.Structure): ''' @@ -152,7 +153,9 @@ class EffectType(ctypes.Union): ('ff_constant_effect', Constant), ('ff_ramp_effect', Ramp), ('ff_periodic_effect', Periodic), - ('ff_condition_effect', Condition * 2), # one for each axis + # ('ff_condition_effect', Condition * 2), # one for each axis + ('ff_condition_effect_1', Condition), + ('ff_condition_effect_2', Condition), ('ff_rumble_effect', Rumble), ] diff --git a/evdev/input.c b/evdev/input.c index 30c5012..fed03c9 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -19,6 +19,7 @@ #include #include +#include "util.h" #define MAX_NAME_SIZE 256 @@ -354,32 +355,6 @@ ioctl_EVIOCGEFFECTS(PyObject *self, PyObject *args) return Py_BuildValue("i", res); } -void print_ff_effect(struct ff_effect* effect) { - fprintf(stderr, - "ff_effect:\n" - " type: %d \n" - " id: %d \n" - " direction: %d\n" - " trigger: (%d, %d)\n" - " replay: (%d, %d)\n", - effect->type, effect->id, effect->direction, - effect->trigger.button, effect->trigger.interval, - effect->replay.length, effect->replay.delay - ); - - - switch (effect->type) { - case FF_CONSTANT: - fprintf(stderr, " constant: (%d, (%d, %d, %d, %d))\n", effect->u.constant.level, - effect->u.constant.envelope.attack_length, - effect->u.constant.envelope.attack_level, - effect->u.constant.envelope.fade_length, - effect->u.constant.envelope.fade_level); - break; - } -} - - static PyObject * upload_effect(PyObject *self, PyObject *args) { diff --git a/evdev/uinput.c b/evdev/uinput.c index 2e1e270..0aa5f56 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -11,6 +11,7 @@ #include #include +#include "util.h" int _uinput_close(int fd) { if (ioctl(fd, UI_DEV_DESTROY) < 0) { @@ -155,7 +156,32 @@ uinput_upload_effect(PyObject *self, PyObject *args) if (ioctl(fd, UI_END_FF_UPLOAD, &upload) < 0) return NULL; - return Py_BuildValue("s#", &upload.effect, sizeof(struct ff_effect)); + // print_ff_effect(&upload.effect); + + return Py_BuildValue("s#", &upload, sizeof(struct uinput_ff_upload)); // y# for bytes in 3.x +} + + +static PyObject * +uinput_erase_effect(PyObject *self, PyObject *args) +{ + int fd; + __u32 request_id; + int ret = PyArg_ParseTuple(args, "ii", &fd, &request_id); + if (!ret) return NULL; + + struct uinput_ff_erase erase; + + erase.request_id = request_id; + if (ioctl(fd, UI_BEGIN_FF_ERASE, &erase) < 0) + return NULL; + + if (ioctl(fd, UI_END_FF_ERASE, &erase) < 0) + return NULL; + + // print_ff_effect(&erase.effect); + + return Py_BuildValue("s#", &erase, sizeof(struct uinput_ff_erase)); // y# for bytes in 3.x } @@ -218,6 +244,7 @@ static PyMethodDef MethodTable[] = { { "close", uinput_close, METH_VARARGS, "Destroy uinput device."}, { "write", uinput_write, METH_VARARGS, "Write event to uinput device."}, { "upload_effect", uinput_upload_effect, METH_VARARGS, ""}, + { "erase_effect", uinput_erase_effect, METH_VARARGS, ""}, { "enable", uinput_enable_event, METH_VARARGS, "Enable a type of event."}, { NULL, NULL, 0, NULL} diff --git a/evdev/uinput.py b/evdev/uinput.py index 2a68085..dac2ff1 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -3,15 +3,31 @@ import os import stat import time +import ctypes import select from evdev import _uinput, _input, ff -from evdev import ecodes, util, device +from evdev import ecodes, util, device, InputEvent class UInputError(Exception): pass +class uinput_ff_upload(ctypes.Structure): + _fields_ = [ + ('request_id', ff._u32), + ('retval', ff._s32), + ('effect', ff.Effect), + ('old', ff.Effect), + ] + +class uinput_ff_erase(ctypes.Structure): + _fields_ = [ + ('request_id', ff._u32), + ('retval', ff._s32), + ('effect_id', ff._u32) + ] + class UInput(object): ''' @@ -175,32 +191,34 @@ def write(self, etype, code, value): _uinput.write(self.fd, etype, code, value) - def read_loop(self): + def read(self): ''' Read a queued event from the uinput device. Returns ``None`` if no events are available. ''' - while True: - r, w, x = select.select([self.fd], [], []) - if r: - event = _input.device_read(self.fd) - _, _, etype, code, value = event - - if etype == ecodes.EV_FF: - print('callback:', event) - - if etype == ecodes.EV_UINPUT: - if code == ecodes.UI_FF_UPLOAD: - data = _uinput.upload_effect(self.fd, value) - print(data) - effect = ff.Effect.from_buffer(data) - if len(self.ff_effects) > ecodes.FF_EFFECT_MAX: - self.ff_effects.pop() - self.ff_effects.append(effect) - elif code == ecodes.UI_FF_ERASE: - pass - else: - print('unknown') + event = _input.device_read(self.fd) + _, _, etype, code, value = event + + if etype == ecodes.EV_FF: + if self.ff_callback: + self.ff_callback(InputEvent(*event)) + + if etype == ecodes.EV_UINPUT: + if code == ecodes.UI_FF_UPLOAD: + data = _uinput.upload_effect(self.fd, value) + up_effect = uinput_ff_upload.from_buffer_copy(buffer(data)) + + if len(self.ff_effects) > ecodes.FF_EFFECT_MAX: + self.ff_effects.pop() + self.ff_effects.append(up_effect.effect) + + elif code == ecodes.UI_FF_ERASE: + data = _uinput.erase_effect(self.fd, value) + er_effect = uinput_ff_erase.from_buffer_copy(buffer(data)) + + for n, effect in enumerate(self.ff_effects): + if er_effect.effect_id == effect.id: + del self.ff_effects[n] def syn(self): diff --git a/evdev/util.h b/evdev/util.h new file mode 100644 index 0000000..10d9e48 --- /dev/null +++ b/evdev/util.h @@ -0,0 +1,23 @@ +void print_ff_effect(struct ff_effect* effect) { + fprintf(stderr, + "ff_effect:\n" + " type: %d \n" + " id: %d \n" + " direction: %d\n" + " trigger: (%d, %d)\n" + " replay: (%d, %d)\n", + effect->type, effect->id, effect->direction, + effect->trigger.button, effect->trigger.interval, + effect->replay.length, effect->replay.delay + ); + + switch (effect->type) { + case FF_CONSTANT: + fprintf(stderr, " constant: (%d, (%d, %d, %d, %d))\n", effect->u.constant.level, + effect->u.constant.envelope.attack_length, + effect->u.constant.envelope.attack_level, + effect->u.constant.envelope.fade_length, + effect->u.constant.envelope.fade_level); + break; + } +}