summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen M. Webb <stephen.webb@canonical.com>2011-11-30 22:26:27 -0500
committerStephen M. Webb <stephen.webb@canonical.com>2011-11-30 22:26:27 -0500
commit4f3e3372c61fbdab0e32c6763f827209871abfc7 (patch)
tree3a826589fbd7b2b88e32416baf2fa604c3664a23
parentf1605cf4a4c0b6cc742a00cbbd6999ff9c8bbd16 (diff)
parent1906581fd01266403c167770398b07267a25e79e (diff)
Added Python bindings and unit tests (LP: #731678).
-rw-r--r--.bzrignore1
-rw-r--r--Makefile.am6
-rw-r--r--configure.ac2
-rw-r--r--data/3m.prop2
-rw-r--r--data/bcm5974.prop2
-rw-r--r--data/ntrig-dell-xt2.event146
-rw-r--r--data/ntrig-dell-xt2.prop31
-rw-r--r--data/ntrig-lenovo-T410s.event0
-rw-r--r--data/ntrig-lenovo-T410s.prop24
-rw-r--r--data/synaptics.prop32
-rw-r--r--data/wetab.prop2
-rw-r--r--python/Makefile.am37
-rw-r--r--python/evemu-test-runner.in4
-rw-r--r--python/evemu/__init__.py271
-rw-r--r--python/evemu/base.py43
-rw-r--r--python/evemu/const.py235
-rw-r--r--python/evemu/exception.py22
-rw-r--r--python/evemu/testing/__init__.py0
-rw-r--r--python/evemu/testing/mocker.py5
-rw-r--r--python/evemu/testing/result.py48
-rw-r--r--python/evemu/testing/runner.py63
-rw-r--r--python/evemu/testing/testcase.py70
-rw-r--r--python/evemu/tests/__init__.py0
-rw-r--r--python/evemu/tests/test_base.py25
-rw-r--r--python/evemu/tests/test_device.py223
-rw-r--r--python/setup.py0
-rw-r--r--src/evemu.c2
27 files changed, 1291 insertions, 5 deletions
diff --git a/.bzrignore b/.bzrignore
index f3556d2..be7c30f 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -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);