diff options
-rw-r--r-- | .bzrignore | 1 | ||||
-rw-r--r-- | Makefile.am | 6 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | data/3m.prop | 2 | ||||
-rw-r--r-- | data/bcm5974.prop | 2 | ||||
-rw-r--r-- | data/ntrig-dell-xt2.event | 146 | ||||
-rw-r--r-- | data/ntrig-dell-xt2.prop | 31 | ||||
-rw-r--r-- | data/ntrig-lenovo-T410s.event | 0 | ||||
-rw-r--r-- | data/ntrig-lenovo-T410s.prop | 24 | ||||
-rw-r--r-- | data/synaptics.prop | 32 | ||||
-rw-r--r-- | data/wetab.prop | 2 | ||||
-rw-r--r-- | python/Makefile.am | 37 | ||||
-rw-r--r-- | python/evemu-test-runner.in | 4 | ||||
-rw-r--r-- | python/evemu/__init__.py | 271 | ||||
-rw-r--r-- | python/evemu/base.py | 43 | ||||
-rw-r--r-- | python/evemu/const.py | 235 | ||||
-rw-r--r-- | python/evemu/exception.py | 22 | ||||
-rw-r--r-- | python/evemu/testing/__init__.py | 0 | ||||
-rw-r--r-- | python/evemu/testing/mocker.py | 5 | ||||
-rw-r--r-- | python/evemu/testing/result.py | 48 | ||||
-rw-r--r-- | python/evemu/testing/runner.py | 63 | ||||
-rw-r--r-- | python/evemu/testing/testcase.py | 70 | ||||
-rw-r--r-- | python/evemu/tests/__init__.py | 0 | ||||
-rw-r--r-- | python/evemu/tests/test_base.py | 25 | ||||
-rw-r--r-- | python/evemu/tests/test_device.py | 223 | ||||
-rw-r--r-- | python/setup.py | 0 | ||||
-rw-r--r-- | src/evemu.c | 2 |
27 files changed, 1291 insertions, 5 deletions
@@ -11,6 +11,7 @@ ChangeLog config-aux config.* configure +evemu-test-runner libtool stamp-* tools/evemu-describe diff --git a/Makefile.am b/Makefile.am index a97bdb1..f117981 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = src tools +SUBDIRS = src tools python pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = utouch-evemu.pc @@ -9,6 +9,8 @@ INCLUDES = $(top_srcdir)/include/ INSTALL: $(INSTALL_CMD) +EXTRA_DIST = data + ChangeLog: @if test -d ".bzr"; then \ cmd=bzr; \ @@ -20,3 +22,5 @@ ChangeLog: $${cmd} log > ChangeLog; dist-hook: ChangeLog INSTALL + +DISTCLEANFILES = ChangeLog diff --git a/configure.ac b/configure.ac index 0ccef4d..20589c9 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,7 @@ AC_PROG_LIBTOOL # Checks for programs. AC_PROG_CC AC_PROG_INSTALL +AM_PATH_PYTHON([2.6]) # man page generation AC_ARG_VAR([XMLTO], [Path to xmlto command]) @@ -39,6 +40,7 @@ AC_SUBST(AM_CFLAGS, AC_CONFIG_FILES([Makefile src/Makefile + python/Makefile tools/Makefile utouch-evemu.pc]) AC_OUTPUT diff --git a/data/3m.prop b/data/3m.prop index f6625b5..a22dc2c 100644 --- a/data/3m.prop +++ b/data/3m.prop @@ -1,4 +1,4 @@ -N: 3M-3M-MicroTouch-USB-controller +N: 3M-3M-MicroTouch-USB-controller Virtual Device I: 0003 0596 0502 0110 B: 00 0b 00 00 00 00 00 00 00 B: 01 00 00 00 00 00 00 00 00 diff --git a/data/bcm5974.prop b/data/bcm5974.prop index 11d87e0..0a21aee 100644 --- a/data/bcm5974.prop +++ b/data/bcm5974.prop @@ -1,4 +1,4 @@ -N: bcm5974 +N: bcm5974 Virtual Device I: 0003 05ac 0223 0000 B: 00 0b 00 00 00 00 00 00 00 B: 01 00 00 00 00 00 00 00 00 diff --git a/data/ntrig-dell-xt2.event b/data/ntrig-dell-xt2.event new file mode 100644 index 0000000..8f5e84d --- /dev/null +++ b/data/ntrig-dell-xt2.event @@ -0,0 +1,146 @@ +E: 1299660667.063211 0003 0035 7411 +E: 1299660667.063229 0003 0036 4677 +E: 1299660667.063232 0003 0034 1 +E: 1299660667.063236 0003 0030 462 +E: 1299660667.063238 0003 0031 360 +E: 1299660667.063242 0000 0002 0 +E: 1299660667.063260 0003 0035 7361 +E: 1299660667.063263 0003 0036 3291 +E: 1299660667.063266 0003 0034 1 +E: 1299660667.063269 0003 0030 462 +E: 1299660667.063271 0003 0031 360 +E: 1299660667.063275 0000 0002 0 +E: 1299660667.063280 0003 0035 5912 +E: 1299660667.063283 0003 0036 1483 +E: 1299660667.063286 0003 0034 0 +E: 1299660667.063289 0003 0030 540 +E: 1299660667.063292 0003 0031 462 +E: 1299660667.063295 0000 0002 0 +E: 1299660667.063299 0001 014a 1 +E: 1299660667.063302 0003 0000 7411 +E: 1299660667.063306 0003 0001 4677 +E: 1299660667.063311 0000 0000 0 +E: 1299660667.081029 0003 0035 7380 +E: 1299660667.081039 0003 0036 4674 +E: 1299660667.081043 0003 0034 1 +E: 1299660667.081045 0003 0030 462 +E: 1299660667.081048 0003 0031 360 +E: 1299660667.081051 0000 0002 0 +E: 1299660667.081064 0003 0035 7401 +E: 1299660667.081067 0003 0036 3263 +E: 1299660667.081070 0003 0034 0 +E: 1299660667.081073 0003 0030 360 +E: 1299660667.081076 0003 0031 308 +E: 1299660667.081079 0000 0002 0 +E: 1299660667.081082 0003 0035 5887 +E: 1299660667.081085 0003 0036 1484 +E: 1299660667.081088 0003 0034 0 +E: 1299660667.081091 0003 0030 540 +E: 1299660667.081094 0003 0031 308 +E: 1299660667.081097 0000 0002 0 +E: 1299660667.081106 0000 0000 0 +E: 1299660667.097214 0003 0035 7379 +E: 1299660667.097237 0003 0036 4678 +E: 1299660667.097240 0003 0034 0 +E: 1299660667.097243 0003 0030 360 +E: 1299660667.097246 0003 0031 308 +E: 1299660667.097249 0000 0002 0 +E: 1299660667.097267 0003 0035 7371 +E: 1299660667.097270 0003 0036 3262 +E: 1299660667.097273 0003 0034 1 +E: 1299660667.097276 0003 0030 462 +E: 1299660667.097279 0003 0031 360 +E: 1299660667.097282 0000 0002 0 +E: 1299660667.097286 0003 0035 5901 +E: 1299660667.097290 0003 0036 1488 +E: 1299660667.097293 0003 0034 0 +E: 1299660667.097296 0003 0030 540 +E: 1299660667.097299 0003 0031 462 +E: 1299660667.097302 0000 0002 0 +E: 1299660667.097312 0000 0000 0 +E: 1299660667.113209 0003 0035 7382 +E: 1299660667.113228 0003 0036 4680 +E: 1299660667.113231 0003 0034 0 +E: 1299660667.113234 0003 0030 360 +E: 1299660667.113237 0003 0031 308 +E: 1299660667.113240 0000 0002 0 +E: 1299660667.113256 0003 0035 7399 +E: 1299660667.113259 0003 0036 3253 +E: 1299660667.113262 0003 0034 0 +E: 1299660667.113265 0003 0030 360 +E: 1299660667.113268 0003 0031 308 +E: 1299660667.113271 0000 0002 0 +E: 1299660667.113275 0003 0035 5886 +E: 1299660667.113277 0003 0036 1489 +E: 1299660667.113280 0003 0034 1 +E: 1299660667.113283 0003 0030 462 +E: 1299660667.113286 0003 0031 360 +E: 1299660667.113289 0000 0002 0 +E: 1299660667.113293 0003 0035 6837 +E: 1299660667.113296 0003 0036 2669 +E: 1299660667.113298 0003 0034 1 +E: 1299660667.113301 0003 0030 462 +E: 1299660667.113304 0003 0031 360 +E: 1299660667.113307 0000 0002 0 +E: 1299660667.113316 0000 0000 0 +E: 1299660667.129014 0003 0035 7375 +E: 1299660667.129022 0003 0036 4685 +E: 1299660667.129025 0003 0034 0 +E: 1299660667.129028 0003 0030 360 +E: 1299660667.129031 0003 0031 308 +E: 1299660667.129034 0000 0002 0 +E: 1299660667.129043 0003 0035 7396 +E: 1299660667.129046 0003 0036 3254 +E: 1299660667.129049 0003 0034 0 +E: 1299660667.129052 0003 0030 360 +E: 1299660667.129055 0003 0031 308 +E: 1299660667.129058 0000 0002 0 +E: 1299660667.129062 0003 0035 5892 +E: 1299660667.129064 0003 0036 1503 +E: 1299660667.129067 0003 0034 0 +E: 1299660667.129070 0003 0030 540 +E: 1299660667.129073 0003 0031 462 +E: 1299660667.129076 0000 0002 0 +E: 1299660667.129080 0003 0035 6829 +E: 1299660667.129083 0003 0036 2671 +E: 1299660667.129086 0003 0034 1 +E: 1299660667.129089 0003 0030 462 +E: 1299660667.129091 0003 0031 360 +E: 1299660667.129094 0000 0002 0 +E: 1299660667.129103 0000 0000 0 +E: 1299660667.145205 0003 0035 7378 +E: 1299660667.145221 0003 0036 4687 +E: 1299660667.145224 0003 0034 0 +E: 1299660667.145232 0003 0030 360 +E: 1299660667.145235 0003 0031 308 +E: 1299660667.145238 0000 0002 0 +E: 1299660667.145254 0003 0035 7403 +E: 1299660667.145257 0003 0036 3252 +E: 1299660667.145260 0003 0034 0 +E: 1299660667.145263 0003 0030 360 +E: 1299660667.145266 0003 0031 308 +E: 1299660667.145269 0000 0002 0 +E: 1299660667.145272 0003 0035 5894 +E: 1299660667.145275 0003 0036 1508 +E: 1299660667.145278 0003 0034 0 +E: 1299660667.145281 0003 0030 540 +E: 1299660667.145284 0003 0031 462 +E: 1299660667.145287 0000 0002 0 +E: 1299660667.145290 0003 0035 6853 +E: 1299660667.145293 0003 0036 2668 +E: 1299660667.145296 0003 0034 0 +E: 1299660667.145299 0003 0030 360 +E: 1299660667.145302 0003 0031 154 +E: 1299660667.145305 0000 0002 0 +E: 1299660667.145314 0000 0000 0 +E: 1299660667.169030 0003 0035 5897 +E: 1299660667.169040 0003 0036 1513 +E: 1299660667.169044 0003 0034 0 +E: 1299660667.169047 0003 0030 540 +E: 1299660667.169049 0003 0031 308 +E: 1299660667.169053 0000 0002 0 +E: 1299660667.169066 0003 0000 5897 +E: 1299660667.169069 0003 0001 1513 +E: 1299660667.169074 0000 0000 0 +E: 1299660667.181005 0001 014a 0 +E: 1299660667.181013 0000 0000 0 diff --git a/data/ntrig-dell-xt2.prop b/data/ntrig-dell-xt2.prop new file mode 100644 index 0000000..b12b048 --- /dev/null +++ b/data/ntrig-dell-xt2.prop @@ -0,0 +1,31 @@ +N: N-Trig-MultiTouch-Virtual-Device +I: 0003 1b96 0001 0110 +P: 00 00 00 00 00 00 00 00 +B: 00 0b 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 04 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 02 00 00 00 00 00 00 00 00 +B: 03 03 00 00 00 00 00 73 00 +B: 04 00 00 00 00 00 00 00 00 +B: 05 00 00 00 00 00 00 00 00 +B: 11 00 00 00 00 00 00 00 00 +B: 12 00 00 00 00 00 00 00 00 +B: 15 00 00 00 00 00 00 00 00 +B: 15 00 00 00 00 00 00 00 00 +A: 00 0 9600 75 0 +A: 01 0 7200 78 0 +A: 30 0 9600 200 0 +A: 31 0 7200 150 0 +A: 34 0 1 0 0 +A: 35 0 9600 75 0 +A: 36 0 7200 78 0 diff --git a/data/ntrig-lenovo-T410s.event b/data/ntrig-lenovo-T410s.event new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/data/ntrig-lenovo-T410s.event diff --git a/data/ntrig-lenovo-T410s.prop b/data/ntrig-lenovo-T410s.prop new file mode 100644 index 0000000..5cafbb1 --- /dev/null +++ b/data/ntrig-lenovo-T410s.prop @@ -0,0 +1,24 @@ +N: N-Trig-MultiTouch Virtual Device +I: 0003 1b96 0001 0110 +P: 00 00 00 00 00 00 00 00 +B: 00 01 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 02 00 00 00 00 00 00 00 00 +B: 03 00 00 00 00 00 00 00 00 +B: 04 00 00 00 00 00 00 00 00 +B: 05 00 00 00 00 00 00 00 00 +B: 11 00 00 00 00 00 00 00 00 +B: 12 00 00 00 00 00 00 00 00 +B: 15 00 00 00 00 00 00 00 00 +B: 15 00 00 00 00 00 00 00 00 diff --git a/data/synaptics.prop b/data/synaptics.prop new file mode 100644 index 0000000..05e27de --- /dev/null +++ b/data/synaptics.prop @@ -0,0 +1,32 @@ +N: SynPS/2 Synaptics TouchPad +I: 0011 0002 0007 01b1 +P: 09 00 00 00 00 00 00 00 +B: 00 0b 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 03 00 00 00 00 00 +B: 01 20 64 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 02 00 00 00 00 00 00 00 00 +B: 03 03 00 00 11 00 80 60 02 +B: 04 00 00 00 00 00 00 00 00 +B: 05 00 00 00 00 00 00 00 00 +B: 11 00 00 00 00 00 00 00 00 +B: 12 00 00 00 00 00 00 00 00 +B: 15 00 00 00 00 00 00 00 00 +B: 15 00 00 00 00 00 00 00 00 +A: 00 1472 5888 0 0 +A: 01 1408 4820 0 0 +A: 18 0 255 0 0 +A: 1c 0 15 0 0 +A: 2f 0 1 0 0 +A: 35 1472 5888 0 0 +A: 36 1408 4820 0 0 +A: 39 0 65535 0 0 diff --git a/data/wetab.prop b/data/wetab.prop index ae4557b..d49d997 100644 --- a/data/wetab.prop +++ b/data/wetab.prop @@ -1,4 +1,4 @@ -N: eGalax-Inc.-USB-TouchController +N: eGalax-Inc.-USB-TouchController Virtual Device I: 0003 0eef 72a1 0210 B: 00 0b 00 00 00 00 00 00 00 B: 01 00 00 00 00 00 00 00 00 diff --git a/python/Makefile.am b/python/Makefile.am new file mode 100644 index 0000000..5a17793 --- /dev/null +++ b/python/Makefile.am @@ -0,0 +1,37 @@ +# +# @file python/Makefile.am +# @brief automake recipe for the uTouch evemu Python bindings +# +# Copyright 2011 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranties of +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. + +nobase_python_PYTHON = \ + $(wildcard evemu/*.py) + +check_SCRIPTS = evemu-test-runner + +TESTS = $(check_SCRIPTS) + +evemu-test-runner: evemu-test-runner.in Makefile + $(AM_V_GEN)$(SED) \ + -e 's,[@]builddir[@],$(builddir),g' \ + -e 's,[@]top_builddir[@],$(top_builddir),g' \ + -e 's,[@]srcdir[@],$(srcdir),g' \ + -e 's,[@]python[@],$(PYTHON),g' \ + $< >$@ + chmod +x $@ + +BUILT_SOURCES = evemu-test-runner +EXTRA_DIST = evemu-test-runner.in $(wildcard evemu/test*) +CLEANFILES = $(BUILT_SOURCES) diff --git a/python/evemu-test-runner.in b/python/evemu-test-runner.in new file mode 100644 index 0000000..2d5d28c --- /dev/null +++ b/python/evemu-test-runner.in @@ -0,0 +1,4 @@ +#!/bin/sh + +PYTHONPATH=@builddir@:@srcdir@ LD_LIBRARY_PATH=@top_builddir@/src/.libs @python@ -m evemu.testing.runner + diff --git a/python/evemu/__init__.py b/python/evemu/__init__.py new file mode 100644 index 0000000..5bde51f --- /dev/null +++ b/python/evemu/__init__.py @@ -0,0 +1,271 @@ +""" +The evemu module provides the Python interface to the kernel-level input device +raw events. +""" + +# Copyright 2011 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranties of +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. + +from ctypes.util import find_library + +import ctypes +import evemu.base +import evemu.const +import glob +import os +import re +import stat + +__all__ = ["Device"] + + +class Device(object): + """ + Encapsulates a raw kernel input event device, either an existing one as + reported by the kernel or a pseudodevice as created through a .prop file. + """ + + def __init__(self, f): + """ + Initializas an evemu Device. + + args: + f -- a file object or filename string for either an existing input + device node (/dev/input/eventNN) or an evemu prop file that can be used + to create a pseudo-device node. + """ + + if type(f).__name__ == 'str': + self._file = open(f, 'r+b') + elif type(f).__name__ == 'file': + self._file = f + else: + raise TypeError("expected file or file name") + + self._is_propfile = True + if stat.S_ISCHR(os.fstat(self._file.fileno()).st_mode): + self._is_propfile = False + elif self._file.read(3) == 'N: ': + self._file.seek(0) + else: + raise TypeError("file must be a device special or prop file") + + self._evemu = evemu.base.EvEmuBase(find_library(evemu.const.LIB)) + self._uinput = None + + libevemu_new = self._evemu.get_lib().evemu_new + libevemu_new.restype = ctypes.c_void_p + self._evemu_device = libevemu_new("") + + if self._is_propfile: + fs = self._evemu._call0(self._evemu.get_c_lib().fdopen, + self._file.fileno(), + 'r') + self._evemu._call(self._evemu.get_lib().evemu_read, + self._evemu_device, + fs) + self._uinput = os.open(evemu.const.UINPUT_NODE, os.O_WRONLY) + self._file = self._create_devnode() + else: + self._evemu._call(self._evemu.get_lib().evemu_extract, + self._evemu_device, + self._file.fileno()) + + def __del__(self): + if self._is_propfile: + self._file.close() + self._evemu._call(self._evemu.get_lib().evemu_destroy, + self._uinput) + + def _create_devnode(self): + self._evemu._call(self._evemu.get_lib().evemu_create, + self._evemu_device, + self._uinput) + return open(self._find_newest_devnode(self.name), 'r+') + + def _find_newest_devnode(self, target_name): + newest_node = (None, float(0)) + for sysname in glob.glob("/sys/class/input/event*/device/name"): + with open(sysname) as f: + name = f.read().rstrip() + if name == target_name: + ev = re.search("(event\d+)", sysname) + if ev: + devname = os.path.join("/dev/input", ev.group(1)) + ctime = os.stat(devname).st_ctime + if ctime > newest_node[1]: + newest_node = (devname, ctime) + return newest_node[0] + + def describe(self, prop_file): + """ + Gathers information about the input device and prints it + to prop_file. This information can be parsed later when constructing + a Device to create a virtual input device with the same properties. + + Scripts that use this method need to be run as root. + """ + if type(prop_file).__name__ != 'file': + raise TypeError("expected file") + + fs = self._evemu._call0(self._evemu.get_c_lib().fdopen, + prop_file.fileno(), + "w") + self._evemu._call(self._evemu.get_lib().evemu_write, + self._evemu_device, + fs) + self._evemu.get_c_lib().fflush(fs) + + def play(self, events_file): + """ + Replays an event sequence, as provided by the events_file, + through the input device. The event sequence must be in + the form created by the record method. + + Scripts that use this method need to be run as root. + """ + if type(events_file).__name__ != 'file': + raise TypeError("expected file") + + fs = self._evemu._call0(self._evemu.get_c_lib().fdopen, + events_file.fileno(), + "r") + self._evemu._call(self._evemu.get_lib().evemu_play, + fs, + self._file.fileno()) + + def record(self, events_file, timeout=10000): + """ + Captures events from the input device and prints them to the + events_file. The events can be parsed by the play method, + allowing a virtual input device to emit the exact same event + sequence. + + Scripts that use this method need to be run as root. + """ + if type(events_file).__name__ != 'file': + raise TypeError("expected file") + + fs = self._evemu._call0(self._evemu.get_c_lib().fdopen, + events_file.fileno(), + "w") + self._evemu._call(self._evemu.get_lib().evemu_record, + fs, + self._file.fileno(), + timeout) + self._evemu.get_c_lib().fflush(fs) + + @property + def version(self): + """ + Gets the version of the evemu library used to create the Device. + """ + return self._evemu._call(self._evemu.get_lib().evemu_get_version, + self._evemu_device) + + @property + def devnode(self): + """ + Gets the name of the /dev node of the input device. + """ + return self._file.name + + @property + def name(self): + """ + Gets the name of the input device (as reported by the device). + """ + func = self._evemu.get_lib().evemu_get_name + func.restype = ctypes.c_char_p + return self._evemu._call(func, self._evemu_device) + + @property + def id_bustype(self): + """ + Identifies the kernel device bustype. + """ + return self._evemu._call(self._evemu.get_lib().evemu_get_id_bustype, + self._evemu_device) + + @property + def id_vendor(self): + """ + Identifies the kernel device vendor. + """ + return self._evemu._call(self._evemu.get_lib().evemu_get_id_vendor, + self._evemu_device) + + @property + def id_product(self): + """ + Identifies the kernel device product. + """ + return self._evemu._call(self._evemu.get_lib().evemu_get_id_product, + self._evemu_device) + + @property + def id_version(self): + """ + Identifies the kernel device version. + """ + return self._evemu._call(self._evemu.get_lib().evemu_get_id_version, + self._evemu_device) + + def get_abs_minimum(self, event_code): + return self._evemu._call(self._evemu.get_lib().evemu_get_abs_minimum, + self._evemu_device, + int(event_code)) + + def get_abs_maximum(self, event_code): + return self._evemu._call(self._evemu.get_lib().evemu_get_abs_maximum, + self._evemu_device, + event_code) + + def get_abs_fuzz(self, event_code): + return self._evemu._call(self._evemu.get_lib().evemu_get_abs_fuzz, + self._evemu_device, + event_code) + + def get_abs_flat(self, event_code): + return self._evemu._call(self._evemu.get_lib().evemu_get_abs_flat, + self._evemu_device, + event_code) + + def get_abs_resolution(self, event_code): + return self._evemu._call(self._evemu.get_lib().evemu_get_abs_resolution, + self._evemu_device, + event_code) + + def has_prop(self, event_code): + return self._evemu._call(self._evemu.get_lib().evemu_has_prop, + self._evemu_device, + event_code) + + def has_event(self, event_type, event_code): + """ + This method's 'even_type' parameter is expected to mostly take the + value for EV_ABS (i.e., 0x03), but may on occasion EV_KEY (i.e., 0x01). + If the former, then the even_code parameter will take the same values + as the methods above (ABS_*). However, if the latter, then the legal + values will be BTN_*. + + The reason for including the button data, is that buttons are sometimes + used to simulate gestures for a higher number of touches than are + possible with just 2-touch hardware. + """ + return self._evemu._call(self._evemu.get_lib().evemu_has_event, + self._evemu_device, + event_type, + event_code) + diff --git a/python/evemu/base.py b/python/evemu/base.py new file mode 100644 index 0000000..84e0306 --- /dev/null +++ b/python/evemu/base.py @@ -0,0 +1,43 @@ +import ctypes +from ctypes.util import find_library +import os + +from evemu import const +from evemu import exception + + +class EvEmuBase(object): + """ + A base wrapper class for the evemu functions, accessed via ctypes. + """ + def __init__(self, library=""): + if not library: + library = const.LIB + self._lib = ctypes.CDLL(library, use_errno=True) + self._libc = ctypes.CDLL(find_library("c"), use_errno=True) + + def _call0(self, api_call, *parameters): + result = api_call(*parameters) + if result == 0 and self.get_c_errno() != 0: + raise exception.ExecutionError, "%s: %s" % ( + api_call.__name__, self.get_c_error()) + return result + + def _call(self, api_call, *parameters): + result = api_call(*parameters) + if result < 0 and self.get_c_errno() != 0: + raise exception.ExecutionError, "%s: %s" % ( + api_call.__name__, self.get_c_error()) + return result + + def get_c_errno(self): + return ctypes.get_errno() + + def get_c_error(self): + return os.strerror(ctypes.get_errno()) + + def get_c_lib(self): + return self._libc + + def get_lib(self): + return self._lib diff --git a/python/evemu/const.py b/python/evemu/const.py new file mode 100644 index 0000000..f70d022 --- /dev/null +++ b/python/evemu/const.py @@ -0,0 +1,235 @@ +LIB = "utouch-evemu" +DEFAULT_LIB = "/usr/lib/libutouch-evemu.so" +LOCAL_LIB = "../src/.libs/libutouch-evemu.so" +UINPUT_NODE = "/dev/uinput" +MAX_EVENT_NODE = 32 +UINPUT_MAX_NAME_SIZE = 80 # defined in linux/uinput.h +DEVICE_PATH_TEMPLATE = "/dev/input/event%d" +DEVICE_NAME_PATH_TEMPLATE = "/sys/class/input/event%d/device/name" +# The following should be examined every release of evemu +API = [ + "evemu_new", + "evemu_delete", + "evemu_extract", + "evemu_write", + "evemu_read", + "evemu_write_event", + "evemu_record", + "evemu_read_event", + "evemu_play", + "evemu_create", + "evemu_destroy", + # Device settrs + "evemu_set_name", + # Device gettrs + "evemu_get_version", + "evemu_get_name", + "evemu_get_id_bustype", + "evemu_get_id_vendor", + "evemu_get_id_product", + "evemu_get_id_version", + "evemu_get_abs_minimum", + "evemu_get_abs_maximum", + "evemu_get_abs_fuzz", + "evemu_get_abs_flat", + "evemu_get_abs_resolution", + # Device hasers + "evemu_has_prop", + "evemu_has_event", + ] + +event_types = { + "EV_SYN": 0x00, + "EV_KEY": 0x01, + "EV_REL": 0x02, + "EV_ABS": 0x03, + "EV_MSC": 0x04, + "EV_SW": 0x05, + "EV_LED": 0x11, + "EV_SND": 0x12, + "EV_REP": 0x14, + "EV_FF": 0x15, + "EV_PWR": 0x16, + "EV_FF_STATUS": 0x17, + "EV_MAX": 0x1f, + } +event_types["EV_CNT"] = event_types["EV_MAX"] + 1, + +event_names = { + "EV_SYN": "Sync", + "EV_KEY": "Keys or Buttons", + "EV_REL": "Relative Axes", + "EV_ABS": "Absolute Axes", + "EV_MSC": "Miscellaneous", + "EV_SW": "Switches", + "EV_LED": "Leds", + "EV_SND": "Sound", + "EV_REP": "Repeat", + "EV_FF": "Force Feedback", + "EV_PWR": "Power Management", + "EV_FF_STATUS": "Force Feedback Status", +} + +absolute_axes = { + "ABS_X": 0x00, + "ABS_Y": 0x01, + "ABS_Z": 0x02, + "ABS_RX": 0x03, + "ABS_RY": 0x04, + "ABS_RZ": 0x05, + "ABS_THROTTLE": 0x06, + "ABS_RUDDER": 0x07, + "ABS_WHEEL": 0x08, + "ABS_GAS": 0x09, + "ABS_BRAKE": 0x0a, + "ABS_HAT0X": 0x10, + "ABS_HAT0Y": 0x11, + "ABS_HAT1X": 0x12, + "ABS_HAT1Y": 0x13, + "ABS_HAT2X": 0x14, + "ABS_HAT2Y": 0x15, + "ABS_HAT3X": 0x16, + "ABS_HAT3Y": 0x17, + "ABS_PRESSURE": 0x18, + "ABS_DISTANCE": 0x19, + "ABS_TILT_X": 0x1a, + "ABS_TILT_Y": 0x1b, + "ABS_TOOL_WIDTH": 0x1c, + "ABS_VOLUME": 0x20, + "ABS_MISC": 0x28, + "ABS_MT_SLOT": 0x2f, # MT slot being modified + "ABS_MT_TOUCH_MAJOR": 0x30, # Major axis of touching ellipse + "ABS_MT_TOUCH_MINOR": 0x31, # Minor axis (omit if circular) + "ABS_MT_WIDTH_MAJOR": 0x32, # Major axis of approaching ellipse + "ABS_MT_WIDTH_MINOR": 0x33, # Minor axis (omit if circular) + "ABS_MT_ORIENTATION": 0x34, # Ellipse orientation + "ABS_MT_POSITION_X": 0x35, # Center X ellipse position + "ABS_MT_POSITION_Y": 0x36, # Center Y ellipse position + "ABS_MT_TOOL_TYPE": 0x37, # Type of touching device + "ABS_MT_BLOB_ID": 0x38, # Group a set of packets as a blob + "ABS_MT_TRACKING_ID": 0x39, # Unique ID of initiated contact + "ABS_MT_PRESSURE": 0x3a, # Pressure on contact area + "ABS_MT_DISTANCE": 0x3b, # Contact hover distance + "ABS_MAX": 0x3f, + } +# XXX ABS_CNT doesn't always give the same value from test data; disabling it +# for now. +#absolute_axes["ABS_CNT"] = absolute_axes["ABS_MAX"] + 1 + +buttons = { + "BTN_MISC": 0x100, + "BTN_0": 0x100, + "BTN_1": 0x101, + "BTN_2": 0x102, + "BTN_3": 0x103, + "BTN_4": 0x104, + "BTN_5": 0x105, + "BTN_6": 0x106, + "BTN_7": 0x107, + "BTN_8": 0x108, + "BTN_9": 0x109, + + "BTN_MOUSE": 0x110, + "BTN_LEFT": 0x110, + "BTN_RIGHT": 0x111, + "BTN_MIDDLE": 0x112, + "BTN_SIDE": 0x113, + "BTN_EXTRA": 0x114, + "BTN_FORWARD": 0x115, + "BTN_BACK": 0x116, + "BTN_TASK": 0x117, + + "BTN_JOYSTICK": 0x120, + "BTN_TRIGGER": 0x120, + "BTN_THUMB": 0x121, + "BTN_THUMB2": 0x122, + "BTN_TOP": 0x123, + "BTN_TOP2": 0x124, + "BTN_PINKIE": 0x125, + "BTN_BASE": 0x126, + "BTN_BASE2": 0x127, + "BTN_BASE3": 0x128, + "BTN_BASE4": 0x129, + "BTN_BASE5": 0x12a, + "BTN_BASE6": 0x12b, + "BTN_DEAD": 0x12f, + + "BTN_GAMEPAD": 0x130, + "BTN_A": 0x130, + "BTN_B": 0x131, + "BTN_C": 0x132, + "BTN_X": 0x133, + "BTN_Y": 0x134, + "BTN_Z": 0x135, + "BTN_TL": 0x136, + "BTN_TR": 0x137, + "BTN_TL2": 0x138, + "BTN_TR2": 0x139, + "BTN_SELECT": 0x13a, + "BTN_START": 0x13b, + "BTN_MODE": 0x13c, + "BTN_THUMBL": 0x13d, + "BTN_THUMBR": 0x13e, + + "BTN_DIGI": 0x140, + "BTN_TOOL_PEN": 0x140, + "BTN_TOOL_RUBBER": 0x141, + "BTN_TOOL_BRUSH": 0x142, + "BTN_TOOL_PENCIL": 0x143, + "BTN_TOOL_AIRBRUSH": 0x144, + "BTN_TOOL_FINGER": 0x145, + "BTN_TOOL_MOUSE": 0x146, + "BTN_TOOL_LENS": 0x147, + "BTN_TOUCH": 0x14a, + "BTN_STYLUS": 0x14b, + "BTN_STYLUS2": 0x14c, + "BTN_TOOL_DOUBLETAP": 0x14d, + "BTN_TOOL_TRIPLETAP": 0x14e, + "BTN_TOOL_QUADTAP": 0x14f, # Four fingers on trackpad + + "BTN_WHEEL": 0x150, + "BTN_GEAR_DOWN": 0x150, + "BTN_GEAR_UP": 0x151, + + "BTN_TRIGGER_HAPPY": 0x2c0, + "BTN_TRIGGER_HAPPY1": 0x2c0, + "BTN_TRIGGER_HAPPY2": 0x2c1, + "BTN_TRIGGER_HAPPY3": 0x2c2, + "BTN_TRIGGER_HAPPY4": 0x2c3, + "BTN_TRIGGER_HAPPY5": 0x2c4, + "BTN_TRIGGER_HAPPY6": 0x2c5, + "BTN_TRIGGER_HAPPY7": 0x2c6, + "BTN_TRIGGER_HAPPY8": 0x2c7, + "BTN_TRIGGER_HAPPY9": 0x2c8, + "BTN_TRIGGER_HAPPY10": 0x2c9, + "BTN_TRIGGER_HAPPY11": 0x2ca, + "BTN_TRIGGER_HAPPY12": 0x2cb, + "BTN_TRIGGER_HAPPY13": 0x2cc, + "BTN_TRIGGER_HAPPY14": 0x2cd, + "BTN_TRIGGER_HAPPY15": 0x2ce, + "BTN_TRIGGER_HAPPY16": 0x2cf, + "BTN_TRIGGER_HAPPY17": 0x2d0, + "BTN_TRIGGER_HAPPY18": 0x2d1, + "BTN_TRIGGER_HAPPY19": 0x2d2, + "BTN_TRIGGER_HAPPY20": 0x2d3, + "BTN_TRIGGER_HAPPY21": 0x2d4, + "BTN_TRIGGER_HAPPY22": 0x2d5, + "BTN_TRIGGER_HAPPY23": 0x2d6, + "BTN_TRIGGER_HAPPY24": 0x2d7, + "BTN_TRIGGER_HAPPY25": 0x2d8, + "BTN_TRIGGER_HAPPY26": 0x2d9, + "BTN_TRIGGER_HAPPY27": 0x2da, + "BTN_TRIGGER_HAPPY28": 0x2db, + "BTN_TRIGGER_HAPPY29": 0x2dc, + "BTN_TRIGGER_HAPPY30": 0x2dd, + "BTN_TRIGGER_HAPPY31": 0x2de, + "BTN_TRIGGER_HAPPY32": 0x2df, + "BTN_TRIGGER_HAPPY33": 0x2e0, + "BTN_TRIGGER_HAPPY34": 0x2e1, + "BTN_TRIGGER_HAPPY35": 0x2e2, + "BTN_TRIGGER_HAPPY36": 0x2e3, + "BTN_TRIGGER_HAPPY37": 0x2e4, + "BTN_TRIGGER_HAPPY38": 0x2e5, + "BTN_TRIGGER_HAPPY39": 0x2e6, + "BTN_TRIGGER_HAPPY40": 0x2e7, + } diff --git a/python/evemu/exception.py b/python/evemu/exception.py new file mode 100644 index 0000000..20782f2 --- /dev/null +++ b/python/evemu/exception.py @@ -0,0 +1,22 @@ +class EvEmuError(Exception): + pass + + +class WrapperError(EvEmuError): + pass + + +class ExecutionError(EvEmuError): + pass + + +class TestError(EvEmuError): + pass + + +class NullFileHandleError(EvEmuError): + pass + + +class SkipTest(Exception): + pass diff --git a/python/evemu/testing/__init__.py b/python/evemu/testing/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/python/evemu/testing/__init__.py diff --git a/python/evemu/testing/mocker.py b/python/evemu/testing/mocker.py new file mode 100644 index 0000000..5cecbd2 --- /dev/null +++ b/python/evemu/testing/mocker.py @@ -0,0 +1,5 @@ +""" +This module is for use by unit tests in order to mock the uinput device. This +alleviates the need for root access, and thus makes the unit tests something +that can be run by packaging software. +""" diff --git a/python/evemu/testing/result.py b/python/evemu/testing/result.py new file mode 100644 index 0000000..d824b36 --- /dev/null +++ b/python/evemu/testing/result.py @@ -0,0 +1,48 @@ +import unittest +try: + # Python 2.7 + from unittest import TextTestResult +except ImportError: + # Python 2.4, 2.5, 2.6 + from unittest import _TextTestResult as TextTestResult + + +def get_test_directory(): + from evemu import tests + return tests.__path__[0] + + +def get_test_module(): + return get_test_directory().replace("/", ".") + + +class CustomTestResult(TextTestResult): + + def __init__(self, *args, **kwds): + super(CustomTestResult, self).__init__(*args, **kwds) + self.current_module = "" + self.last_module = "" + self.current_class = "" + self.last_class = "" + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + if not self.showAll: + return + self.last_module = self.current_module + self.last_class = self.current_class + method = test._testMethodName + module_and_class = test.id().rsplit(method)[0][:-1] + this_module = ".".join(module_and_class.split(".")[:-1]) + self.current_module = this_module + this_class = module_and_class.split(".")[-1] + self.current_class = this_class + if self.last_module != self.current_module: + heading = "\n%s.%s" % (get_test_module(), this_module) + self.stream.writeln(heading) + if self.last_class != self.current_class: + self.stream.writeln(" %s" % this_class) + self.stream.write(" %s " % method.ljust(50, ".")) + self.stream.write(" ") + self.stream.flush() + diff --git a/python/evemu/testing/runner.py b/python/evemu/testing/runner.py new file mode 100644 index 0000000..d7bf814 --- /dev/null +++ b/python/evemu/testing/runner.py @@ -0,0 +1,63 @@ +import os +import unittest + +from evemu.testing import result + + +def get_test_directory(): + from evemu import tests + return tests.__path__[0] + + +class CustomTestRunner(unittest.TextTestRunner): + """ + This is only needed for Python 2.6 and lower. + """ + def _makeResult(self): + return result.CustomTestResult( + self.stream, self.descriptions, self.verbosity) + + +def get_suite(loader, top_level_directory): + if hasattr(loader, "discover"): + # Python 2.7 + suite = loader.discover(top_level_directory) + else: + # Python 2.4, 2.5, 2.6 + names = [] + def _path_to_module(path): + # generate dotted names for file paths + path = path.replace(".py", "") + return path.replace("/", ".") + + # walk the directory + for dirpath, dirnames, filenames in os.walk(top_level_directory): + modules = [ + _path_to_module(os.path.join(dirpath, x)) for x in filenames + if x.startswith("test_") and x.endswith(".py")] + if not modules: + continue + names.extend(modules) + suite = loader.loadTestsFromNames(names) + return suite + + +def get_runner(): + try: + # Python 2.7 + runner = unittest.TextTestRunner( + verbosity=2, resultclass=result.CustomTestResult) + except TypeError: + # Python 2.4, 2.5, 2.6 + runner = CustomTestRunner(verbosity=2) + return runner + + +def run_tests(): + loader = unittest.TestLoader() + suite = get_suite(loader, get_test_directory()) + get_runner().run(suite) + + +if __name__ == "__main__": + run_tests() diff --git a/python/evemu/testing/testcase.py b/python/evemu/testing/testcase.py new file mode 100644 index 0000000..35b7a0b --- /dev/null +++ b/python/evemu/testing/testcase.py @@ -0,0 +1,70 @@ +from ctypes.util import find_library +import os +import unittest + +from evemu import const +from evemu import exception + + +def get_top_directory(): + import evemu + return evemu.__path__[0] + + +def skip(message): + try: + return unittest.skip(message) + except AttributeError: + def _skip(message): + def decorator(test_item): + def skip_wrapper(*args, **kwds): + raise exception.SkipTest(message) + return skip_wrapper + return decorator + return _skip(message) + + +class Non26BaseTestCase(unittest.TestCase): + """ + This is to provide methods that aren't in 2.6 and below, but are in 2.7 and + above. + """ + def __init__(self, *args, **kwds): + super(Non26BaseTestCase, self).__init__(*args, **kwds) + if not hasattr(unittest.TestCase, "assertIn"): + self.assertIn = self._assertIn26 + + def _assertIn26(self, member, container, msg=None): + """Just like self.assertTrue(a in b), but with a nicer default message.""" + if member not in container: + standardMsg = '%s not found in %s' % (repr(member), + repr(container)) + self.fail(msg or standardMsg) + + +class BaseTestCase(unittest.TestCase): + + def setUp(self): + super(BaseTestCase, self).setUp() + library = find_library(const.LIB) + if not library: + if os.path.exists(const.DEFAULT_LIB): + library = const.DEFAULT_LIB + else: + library = const.LOCAL_LIB + self.library = library + basedir = get_top_directory() + self.data_dir = os.path.join(basedir, "..", "..", "data") + self.device = None + + def tearDown(self): + if self.device: + self.device.destroy() + super(BaseTestCase, self).tearDown() + + def get_device_file(self): + return os.path.join(self.data_dir, "ntrig-dell-xt2.prop") + + def get_events_file(self): + return os.path.join(self.data_dir, "ntrig-dell-xt2.event") + diff --git a/python/evemu/tests/__init__.py b/python/evemu/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/python/evemu/tests/__init__.py diff --git a/python/evemu/tests/test_base.py b/python/evemu/tests/test_base.py new file mode 100644 index 0000000..0b72abd --- /dev/null +++ b/python/evemu/tests/test_base.py @@ -0,0 +1,25 @@ +import unittest + +from evemu import const +from evemu.base import EvEmuBase +from evemu.testing import testcase + + +class EvEmuBaseTestCase(testcase.BaseTestCase): + + def test_so_library_found(self): + wrapper = EvEmuBase(self.library) + # Make sure that the library loads + self.assertNotEqual( + wrapper._lib._name.find("libutouch-evemu"), -1) + + def test_c_symbols_found(self): + # Make sure that the expected functions are present + wrapper = EvEmuBase(self.library) + for function_name in const.API: + function = getattr(wrapper._lib, function_name) + self.assertTrue(function is not None) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/evemu/tests/test_device.py b/python/evemu/tests/test_device.py new file mode 100644 index 0000000..a22858f --- /dev/null +++ b/python/evemu/tests/test_device.py @@ -0,0 +1,223 @@ + +from evemu.testing import testcase +from multiprocessing import Process, Queue, Event + +import evemu +import os +import re +import tempfile +import unittest + + +def record(recording_started, device_node, q): + """ + Runs the recorder in a separate process because the evemu API is a + blocking API. + """ + device = evemu.Device(device_node) + with tempfile.TemporaryFile() as event_file: + recording_started.set() + device.record(event_file, 1000) + event_file.flush() + event_file.seek(0) + outdata = event_file.readlines() + q.put(outdata) + + +class DeviceActionTestCase(testcase.BaseTestCase): + """ + Verifies the high-level Device functions (create, describe, play, record). + """ + + def test_construct_from_dev_node_name(self): + """ + Verifies a Device can be constructed from an existing input device node + name. + """ + d = evemu.Device("/dev/input/event10") + + def test_construct_from_dev_node_file(self): + """ + Verifies a Device can be constructed from an existing input device node + file object. + """ + d = evemu.Device(open("/dev/input/event10")) + + def test_construct_from_prop_file_name(self): + """ + Verifies a device can be constructed from an evemu prop file name. + """ + d = evemu.Device(self.get_device_file()) + + def test_construct_from_prop_file_file(self): + """ + Verifies a device can be constructed from an evemu prop file file + object. + """ + d = evemu.Device(open(self.get_device_file())) + + def test_describe(self): + """ + Verifies that a device description can be correctly extracted from a + Device. + """ + # Get original description + with open(self.get_device_file()) as f: + data = f.readlines() + + # Create a pseudo device with that description + d = evemu.Device(self.get_device_file()) + + # get the description to a temporary file + with tempfile.TemporaryFile() as t: + d.describe(t) + + # read in the temporary file and compare to the original + t.flush() + t.seek(0) + newdata = t.readlines() + self.assertEquals(data, newdata) + + def test_play_and_record(self): + """ + Verifies that a Device and play back prerecorded events. + """ + device = evemu.Device(self.get_device_file()) + devnode = device.devnode + events_file = self.get_events_file() + with open(events_file) as e: + indata = e.readlines() + + recording_started = Event() + q = Queue() + record_process = Process(target=record, + args=(recording_started, devnode, q)) + record_process.start() + recording_started.wait(100) + device.play(open(events_file)) + + outdata = q.get() + record_process.join() + + self.assertEquals(len(indata), len(outdata)) + fuzz = re.compile("E: \d+\.\d+ (.*)") + for i in range(len(indata)): + lhs = fuzz.match(indata[i]) + self.assertTrue(lhs) + rhs = fuzz.match(outdata[i]) + self.assertTrue(rhs) + self.assertEquals(lhs.group(1), rhs.group(1)) + + +class DevicePropertiesTestCase(testcase.BaseTestCase): + """ + Verifies the workings of the various device property accessors. + """ + + def setUp(self): + super(DevicePropertiesTestCase, self).setUp() + self._device = evemu.Device(self.get_device_file()) + + def tearDown(self): + del self._device + super(DevicePropertiesTestCase, self).tearDown() + + def test_version(self): + self.assertEqual(self._device.version, 0) + + def test_name(self): + self.assertEqual(self._device.name, "N-Trig-MultiTouch-Virtual-Device") + + def test_id_bustype(self): + self.assertEqual(self._device.id_bustype, 3) + + def test_id_vendor(self): + self.assertEqual(hex(self._device.id_vendor), "0x1b96") + + def test_id_product(self): + self.assertEqual(self._device.id_product, 1) + + def test_id_version(self): + self.assertEqual(self._device.id_version, 272) + + def test_get_abs_minimum(self): + expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + results = [self._device.get_abs_minimum(x) + for x in evemu.const.absolute_axes.values()] + self.assertEqual(results, expected) + + def test_get_abs_maximum(self): + expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9600, 7200, 0, 0, 0, 0, + 7200, 1, 7200, 0, 9600, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9600, + 0, 0, + ] + # Skipping the entry for ABS_CNT; some times it's 0, sometimes a very + # large negative number. + results = [self._device.get_abs_maximum(x) + for x in evemu.const.absolute_axes.values()] + self.assertEqual(results[:-1], expected[:-1]) + + def test_get_abs_fuzz(self): + expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 78, 0, 0, 0, 0, 150, + 0, 78, 0, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 0, 0, + ] + results = [self._device.get_abs_fuzz(x) + for x in evemu.const.absolute_axes.values()] + self.assertEqual(results, expected) + + def test_get_abs_flat(self): + expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + results = [self._device.get_abs_flat(x) + for x in evemu.const.absolute_axes.values()] + self.assertEqual(results, expected) + + def test_get_abs_resolution(self): + expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + results = [self._device.get_abs_resolution(x) + for x in evemu.const.absolute_axes.values()] + self.assertEqual(results, expected) + + def test_has_prop(self): + expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + results = [self._device.has_prop(x) + for x in evemu.const.absolute_axes.values()] + self.assertEqual(results, expected) + + def test_has_event_ev_abs(self): + expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, + 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + ] + results = [self._device.has_event(evemu.const.event_types["EV_ABS"], x) + for x in evemu.const.absolute_axes.values()] + self.assertEqual(results, expected) + + def test_has_event_ev_key(self): + expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + results = [self._device.has_event(evemu.const.event_types["EV_KEY"], x) + for x in evemu.const.buttons.values()] + self.assertEqual(results, expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/python/setup.py diff --git a/src/evemu.c b/src/evemu.c index 6c84f9d..ebcab2c 100644 --- a/src/evemu.c +++ b/src/evemu.c @@ -337,7 +337,7 @@ int evemu_read(struct evemu_device *dev, FILE *fp) memset(dev, 0, sizeof(*dev)); - ret = fscanf(fp, "N: %ms\n", &devname); + ret = fscanf(fp, "N: %m[^\n]\n", &devname); if (ret <= 0) { if (devname != NULL) free(devname); |