Skip to content

Commit a4cf8b5

Browse files
committed
Functionality to set device properties eg. INPUT_PROP_DIRECT or INPUT_PROP_POINTER
1 parent 69a5030 commit a4cf8b5

File tree

7 files changed

+249
-3
lines changed

7 files changed

+249
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ tags
1313
TAGS
1414
evdev/*.so
1515
evdev/ecodes.c
16+
evdev/iprops.c
1617
docs/_build
1718
.#*
1819
__pycache__

evdev/device.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ def capabilities(self, verbose=False, absinfo=True):
223223
else:
224224
return self._capabilities(absinfo)
225225

226+
def props(self):
227+
props = _input.ioctl_EVIOCGPROP(self.fd)
228+
229+
return props
230+
226231
def leds(self, verbose=False):
227232
'''
228233
Return currently set LED keys.

evdev/geniprops.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# -*- coding: utf-8; -*-
2+
3+
'''
4+
Generate a Python extension module with the constants defined in linux/input.h.
5+
'''
6+
7+
from __future__ import print_function
8+
import os, sys, re
9+
10+
11+
#-----------------------------------------------------------------------------
12+
# The default header file locations to try.
13+
headers = [
14+
'/usr/include/linux/input.h',
15+
'/usr/include/linux/input-event-codes.h',
16+
]
17+
18+
if sys.argv[1:]:
19+
headers = sys.argv[1:]
20+
21+
22+
#-----------------------------------------------------------------------------
23+
macro_regex = r'#define +((?:INPUT_PROP)_\w+).*'
24+
macro_regex = re.compile(macro_regex)
25+
26+
uname = list(os.uname()); del uname[1]
27+
uname = ' '.join(uname)
28+
29+
30+
#-----------------------------------------------------------------------------
31+
template = r'''
32+
#include <Python.h>
33+
#ifdef __FreeBSD__
34+
#include <dev/evdev/input.h>
35+
#else
36+
#include <linux/input.h>
37+
#include <linux/uinput.h>
38+
#endif
39+
40+
/* Automatically generated by evdev.geniprops */
41+
/* Generated on %s */
42+
43+
#define MODULE_NAME "iprops"
44+
#define MODULE_HELP "linux/input.h macros"
45+
46+
static PyMethodDef MethodTable[] = {
47+
{ NULL, NULL, 0, NULL}
48+
};
49+
50+
#if PY_MAJOR_VERSION >= 3
51+
static struct PyModuleDef moduledef = {
52+
PyModuleDef_HEAD_INIT,
53+
MODULE_NAME,
54+
MODULE_HELP,
55+
-1, /* m_size */
56+
MethodTable, /* m_methods */
57+
NULL, /* m_reload */
58+
NULL, /* m_traverse */
59+
NULL, /* m_clear */
60+
NULL, /* m_free */
61+
};
62+
#endif
63+
64+
static PyObject *
65+
moduleinit(void)
66+
{
67+
68+
#if PY_MAJOR_VERSION >= 3
69+
PyObject* m = PyModule_Create(&moduledef);
70+
#else
71+
PyObject* m = Py_InitModule3(MODULE_NAME, MethodTable, MODULE_HELP);
72+
#endif
73+
74+
if (m == NULL) return NULL;
75+
76+
%s
77+
78+
return m;
79+
}
80+
81+
#if PY_MAJOR_VERSION >= 3
82+
PyMODINIT_FUNC
83+
PyInit__iprops(void)
84+
{
85+
return moduleinit();
86+
}
87+
#else
88+
PyMODINIT_FUNC
89+
init_iprops(void)
90+
{
91+
moduleinit();
92+
}
93+
#endif
94+
'''
95+
96+
def parse_header(header):
97+
for line in open(header):
98+
macro = macro_regex.search(line)
99+
if macro:
100+
yield ' PyModule_AddIntMacro(m, %s);' % macro.group(1)
101+
102+
all_macros = []
103+
for header in headers:
104+
try:
105+
fh = open(header)
106+
except (IOError, OSError):
107+
continue
108+
all_macros += parse_header(header)
109+
110+
if not all_macros:
111+
print('no input macros found in: %s' % ' '.join(headers), file=sys.stderr)
112+
sys.exit(1)
113+
114+
115+
macros = os.linesep.join(all_macros)
116+
print(template % (uname, macros))

evdev/input.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,33 @@ erase_effect(PyObject *self, PyObject *args)
454454
return Py_None;
455455
}
456456

457+
static PyObject *
458+
ioctl_EVIOCGPROP(PyObject *self, PyObject *args)
459+
{
460+
int fd, ret;
461+
462+
ret = PyArg_ParseTuple(args, "i", &fd);
463+
if (!ret) return NULL;
464+
465+
char bytes[(INPUT_PROP_MAX+7)/8];
466+
memset(bytes, 0, sizeof bytes);
467+
468+
ret = ioctl(fd, EVIOCGPROP(sizeof(bytes)), &bytes);
469+
470+
if (ret == -1)
471+
return NULL;
472+
473+
PyObject* res = PyList_New(0);
474+
for (int i=0; i<INPUT_PROP_MAX; i++) {
475+
if (test_bit(bytes, i)) {
476+
PyList_Append(res, Py_BuildValue("i", i));
477+
}
478+
}
479+
480+
return res;
481+
}
482+
483+
457484

458485
static PyMethodDef MethodTable[] = {
459486
{ "ioctl_devinfo", ioctl_devinfo, METH_VARARGS, "fetch input device info" },
@@ -468,6 +495,7 @@ static PyMethodDef MethodTable[] = {
468495
{ "device_read_many", device_read_many, METH_VARARGS, "read all available input events from a device" },
469496
{ "upload_effect", upload_effect, METH_VARARGS, "" },
470497
{ "erase_effect", erase_effect, METH_VARARGS, "" },
498+
{ "ioctl_EVIOCGPROP", ioctl_EVIOCGPROP, METH_VARARGS, "get device properties"},
471499

472500
{ NULL, NULL, 0, NULL}
473501
};

evdev/uinput.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@ uinput_set_phys(PyObject *self, PyObject *args)
7777
return NULL;
7878
}
7979

80+
static PyObject *
81+
uinput_set_prop(PyObject *self, PyObject *args)
82+
{
83+
int fd;
84+
uint16_t prop;
85+
86+
int ret = PyArg_ParseTuple(args, "ih", &fd, &prop);
87+
if (!ret) return NULL;
88+
89+
if (ioctl(fd, UI_SET_PROPBIT, prop) < 0)
90+
goto on_err;
91+
92+
Py_RETURN_NONE;
93+
94+
on_err:
95+
_uinput_close(fd);
96+
PyErr_SetFromErrno(PyExc_IOError);
97+
return NULL;
98+
}
99+
80100

81101
// Different kernel versions have different device setup methods. You can read
82102
// more about it here:
@@ -339,6 +359,9 @@ static PyMethodDef MethodTable[] = {
339359
{ "set_phys", uinput_set_phys, METH_VARARGS,
340360
"Set physical path"},
341361

362+
{ "set_prop", uinput_set_prop, METH_VARARGS,
363+
"Set device property"},
364+
342365
{ NULL, NULL, 0, NULL}
343366
};
344367

evdev/uinput.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __init__(self,
7777
events=None,
7878
name='py-evdev-uinput',
7979
vendor=0x1, product=0x1, version=0x1, bustype=0x3,
80-
devnode='/dev/uinput', phys='py-evdev-uinput'):
80+
devnode='/dev/uinput', phys='py-evdev-uinput', props=[]):
8181
'''
8282
Arguments
8383
---------
@@ -132,6 +132,10 @@ def __init__(self,
132132
# Set phys name
133133
_uinput.set_phys(self.fd, phys)
134134

135+
# Set properties
136+
for prop in props:
137+
_uinput.set_prop(self.fd, prop)
138+
135139
for etype, code in prepared_events:
136140
_uinput.enable(self.fd, etype, code)
137141

setup.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
input_c = Extension('evdev._input', sources=['evdev/input.c'], extra_compile_args=cflags)
4141
uinput_c = Extension('evdev._uinput', sources=['evdev/uinput.c'], extra_compile_args=cflags)
4242
ecodes_c = Extension('evdev._ecodes', sources=['evdev/ecodes.c'], extra_compile_args=cflags)
43+
iprops_c = Extension('evdev.iprops', sources=['evdev/iprops.c'], extra_compile_args=cflags)
4344

4445
#-----------------------------------------------------------------------------
4546
kw = {
@@ -57,7 +58,7 @@
5758
'classifiers': classifiers,
5859

5960
'packages': ['evdev'],
60-
'ext_modules': [input_c, uinput_c, ecodes_c],
61+
'ext_modules': [input_c, uinput_c, ecodes_c, iprops_c],
6162
'include_package_data': False,
6263
'zip_safe': True,
6364
'cmdclass': {},
@@ -106,6 +107,48 @@ def create_ecodes(headers=None):
106107
check_call(cmd, cwd="%s/evdev" % here, shell=True)
107108

108109

110+
#-----------------------------------------------------------------------------
111+
def create_iprops(headers=None):
112+
if not headers:
113+
headers = [
114+
'/usr/include/linux/input.h',
115+
'/usr/include/linux/input-event-codes.h',
116+
'/usr/include/linux/uinput.h',
117+
]
118+
119+
headers = [header for header in headers if os.path.isfile(header)]
120+
if not headers:
121+
msg = '''\
122+
The 'linux/input.h' and 'linux/input-event-codes.h' include files
123+
are missing. You will have to install the kernel header files in
124+
order to continue:
125+
126+
yum install kernel-headers-$(uname -r)
127+
apt-get install linux-headers-$(uname -r)
128+
emerge sys-kernel/linux-headers
129+
pacman -S kernel-headers
130+
131+
In case they are installed in a non-standard location, you may use
132+
the '--evdev-headers' option to specify one or more colon-separated
133+
paths. For example:
134+
135+
python setup.py \\
136+
build \\
137+
build_iprops --evdev-headers path/input.h:path/input-event-codes.h \\
138+
build_ext --include-dirs path/ \\
139+
install
140+
'''
141+
142+
sys.stderr.write(textwrap.dedent(msg))
143+
sys.exit(1)
144+
145+
from subprocess import check_call
146+
147+
print('writing iprops.c (using %s)' % ' '.join(headers))
148+
cmd = '%s geniprops.py %s > iprops.c' % (sys.executable, ' '.join(headers))
149+
check_call(cmd, cwd="%s/evdev" % here, shell=True)
150+
151+
109152
#-----------------------------------------------------------------------------
110153
class build_ecodes(Command):
111154
description = 'generate ecodes.c'
@@ -125,6 +168,24 @@ def run(self):
125168
create_ecodes(self.evdev_headers)
126169

127170

171+
class build_iprops(Command):
172+
description = 'generate iprops.c'
173+
174+
user_options = [
175+
('evdev-headers=', None, 'colon-separated paths to input subsystem headers'),
176+
]
177+
178+
def initialize_options(self):
179+
self.evdev_headers = None
180+
181+
def finalize_options(self):
182+
if self.evdev_headers:
183+
self.evdev_headers = self.evdev_headers.split(':')
184+
185+
def run(self):
186+
create_iprops(self.evdev_headers)
187+
188+
128189
class build_ext(_build_ext.build_ext):
129190
def has_ecodes(self):
130191
ecodes_path = os.path.join(here, 'evdev/ecodes.c')
@@ -133,17 +194,25 @@ def has_ecodes(self):
133194
print('ecodes.c already exists ... skipping build_ecodes')
134195
return not res
135196

197+
def has_iprops(self):
198+
iprops_path = os.path.join(here, 'evdev/iprops.c')
199+
res = os.path.exists(iprops_path)
200+
if res:
201+
print('iprops.c already exists ... skipping build_iprops')
202+
return not res
203+
136204
def run(self):
137205
for cmd_name in self.get_sub_commands():
138206
self.run_command(cmd_name)
139207
_build_ext.build_ext.run(self)
140208

141-
sub_commands = [('build_ecodes', has_ecodes)] + _build_ext.build_ext.sub_commands
209+
sub_commands = [('build_ecodes', has_ecodes), ('build_iprops', has_iprops)] + _build_ext.build_ext.sub_commands
142210

143211

144212
#-----------------------------------------------------------------------------
145213
kw['cmdclass']['build_ext'] = build_ext
146214
kw['cmdclass']['build_ecodes'] = build_ecodes
215+
kw['cmdclass']['build_iprops'] = build_iprops
147216

148217

149218
#-----------------------------------------------------------------------------

0 commit comments

Comments
 (0)