revised is_gamepad

xkb
sezanzeb 3 years ago committed by sezanzeb
parent 60970caa0a
commit 462809462a

@ -28,7 +28,8 @@ import time
import asyncio
import evdev
from evdev.ecodes import EV_KEY, EV_ABS, KEY_CAMERA, EV_REL
from evdev.ecodes import EV_KEY, EV_ABS, KEY_CAMERA, EV_REL, ABS_PRESSURE, \
BTN_STYLUS, BTN_A
from keymapper.logger import logger
@ -60,33 +61,32 @@ def is_gamepad(device):
----------
device : InputDevice
"""
# if false positives appear, prefer requiring more gamepad specific
# capabilities over searching for capabilities that gamepads usually
# don't have.
capabilities = device.capabilities(absinfo=False)
# some tests that should easily match most devices of those
# non-gamepad types:
if EV_REL in capabilities:
# A mouse
return False
if BTN_STYLUS in capabilities.get(EV_KEY, []):
# a graphics tablet
return False
abs_capabilities = capabilities.get(EV_ABS)
if abs_capabilities is not None:
if evdev.ecodes.ABS_MT_TRACKING_ID in abs_capabilities:
# check for some random mousepad capability
return False
# graphics tablet tests.
# They use ABS_X and ABS_Y for moving the cursor
keys = capabilities.get(EV_KEY, [])
if [key for key in keys if key in TABLET_KEYS]:
return False
if evdev.ecodes.ABS_X in abs_capabilities:
# can be a joystick or a mousepad (already handled), so it's
# a joystick
return True
# buttons
if BTN_A not in capabilities.get(EV_KEY, []):
return False
if evdev.ecodes.ABS_Y in abs_capabilities:
return True
# joystick
abs_capabilities = capabilities.get(EV_ABS, [])
if evdev.ecodes.ABS_X not in abs_capabilities:
return False
if evdev.ecodes.ABS_Y not in abs_capabilities:
return False
return False
return True
class _GetDevices(threading.Thread):

@ -0,0 +1,176 @@
# Capabilities
A list of example capabilities for reference.
- [Gamepads](#Gamepads)
- [Graphics Tablets](#Graphics-tablets)
- [Touchpads](#Touchpads)
Feel free to extend this list with more devices that are not keyboards
and not mice.
```bash
sudo python3
```
```py
import evdev
evdev.InputDevice('/dev/input/event12').capabilities(verbose=True)
```
## Gamepads
#### Microsoft X-Box 360 pad
```py
{
('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_DROPPED', 3), ('?', 21)],
('EV_KEY', 1): [
(['BTN_A', 'BTN_GAMEPAD', 'BTN_SOUTH'], 304),
(['BTN_B', 'BTN_EAST'], 305), (['BTN_NORTH', 'BTN_X'], 307),
(['BTN_WEST', 'BTN_Y'], 308), ('BTN_TL', 310), ('BTN_TR', 311),
('BTN_SELECT', 314), ('BTN_START', 315), ('BTN_MODE', 316),
('BTN_THUMBL', 317), ('BTN_THUMBR', 318)
],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=1476, min=-32768, max=32767, fuzz=16, flat=128, resolution=0)),
(('ABS_Y', 1), AbsInfo(value=366, min=-32768, max=32767, fuzz=16, flat=128, resolution=0)),
(('ABS_Z', 2), AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)),
(('ABS_RX', 3), AbsInfo(value=-2950, min=-32768, max=32767, fuzz=16, flat=128, resolution=0)),
(('ABS_RY', 4), AbsInfo(value=1973, min=-32768, max=32767, fuzz=16, flat=128, resolution=0)),
(('ABS_RZ', 5), AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)),
(('ABS_HAT0X', 16), AbsInfo(value=0, min=-1, max=1, fuzz=0, flat=0, resolution=0)),
(('ABS_HAT0Y', 17), AbsInfo(value=0, min=-1, max=1, fuzz=0, flat=0, resolution=0))
],
('EV_FF', 21): [
(['FF_EFFECT_MIN', 'FF_RUMBLE'], 80), ('FF_PERIODIC', 81),
(['FF_SQUARE', 'FF_WAVEFORM_MIN'], 88), ('FF_TRIANGLE', 89),
('FF_SINE', 90), (['FF_GAIN', 'FF_MAX_EFFECTS'], 96)
]
}
```
## Graphics tablets
#### Wacom Intuos 5 M
Pen
```py
{
('EV_SYN', 0): [
('SYN_REPORT', 0), ('SYN_CONFIG', 1),
('SYN_MT_REPORT', 2), ('SYN_DROPPED', 3), ('?', 4)
],
('EV_KEY', 1): [
(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274),
('BTN_SIDE', 275), ('BTN_EXTRA', 276), (['BTN_DIGI', 'BTN_TOOL_PEN'], 320),
('BTN_TOOL_RUBBER', 321), ('BTN_TOOL_BRUSH', 322), ('BTN_TOOL_PENCIL', 323),
('BTN_TOOL_AIRBRUSH', 324), ('BTN_TOOL_MOUSE', 326), ('BTN_TOOL_LENS', 327),
('BTN_TOUCH', 330), ('BTN_STYLUS', 331), ('BTN_STYLUS2', 332)
],
('EV_REL', 2): [('REL_WHEEL', 8)],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=0, min=0, max=44704, fuzz=4, flat=0, resolution=200)),
(('ABS_Y', 1), AbsInfo(value=0, min=0, max=27940, fuzz=4, flat=0, resolution=200)),
(('ABS_Z', 2), AbsInfo(value=0, min=-900, max=899, fuzz=0, flat=0, resolution=287)),
(('ABS_RZ', 5), AbsInfo(value=0, min=-900, max=899, fuzz=0, flat=0, resolution=287)),
(('ABS_THROTTLE', 6), AbsInfo(value=0, min=-1023, max=1023, fuzz=0, flat=0, resolution=0)),
(('ABS_WHEEL', 8), AbsInfo(value=0, min=0, max=1023, fuzz=0, flat=0, resolution=0)),
(('ABS_PRESSURE', 24), AbsInfo(value=0, min=0, max=2047, fuzz=0, flat=0, resolution=0)),
(('ABS_DISTANCE', 25), AbsInfo(value=0, min=0, max=63, fuzz=1, flat=0, resolution=0)),
(('ABS_TILT_X', 26), AbsInfo(value=0, min=-64, max=63, fuzz=1, flat=0, resolution=57)),
(('ABS_TILT_Y', 27), AbsInfo(value=0, min=-64, max=63, fuzz=1, flat=0, resolution=57)),
(('ABS_MISC', 40), AbsInfo(value=0, min=0, max=0, fuzz=0, flat=0, resolution=0))
],
('EV_MSC', 4): [('MSC_SERIAL', 0)]
}
```
Pad
```py
{
('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_DROPPED', 3)],
('EV_KEY', 1): [
(['BTN_0', 'BTN_MISC'], 256), ('BTN_1', 257), ('BTN_2', 258),
('BTN_3', 259), ('BTN_4', 260), ('BTN_5', 261), ('BTN_6', 262),
('BTN_7', 263), ('BTN_8', 264), ('BTN_STYLUS', 331)],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=0, min=0, max=1, fuzz=0, flat=0, resolution=0)),
(('ABS_Y', 1), AbsInfo(value=0, min=0, max=1, fuzz=0, flat=0, resolution=0)),
(('ABS_WHEEL', 8), AbsInfo(value=0, min=0, max=71, fuzz=0, flat=0, resolution=0)),
(('ABS_MISC', 40), AbsInfo(value=0, min=0, max=0, fuzz=0, flat=0, resolution=0))
]
}
```
#### 10 inch PenTablet
```py
{
('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_DROPPED', 3), ('?', 4)],
('EV_KEY', 1): [(['BTN_DIGI', 'BTN_TOOL_PEN'], 320), ('BTN_TOUCH', 330), ('BTN_STYLUS', 331)],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=41927, min=0, max=50794, fuzz=0, flat=0, resolution=200)),
(('ABS_Y', 1), AbsInfo(value=11518, min=0, max=30474, fuzz=0, flat=0, resolution=200)),
(('ABS_PRESSURE', 24), AbsInfo(value=0, min=0, max=8191, fuzz=0, flat=0, resolution=0)),
(('ABS_TILT_X', 26), AbsInfo(value=0, min=-127, max=127, fuzz=0, flat=0, resolution=0)),
(('ABS_TILT_Y', 27), AbsInfo(value=0, min=-127, max=127, fuzz=0, flat=0, resolution=0))
],
('EV_MSC', 4): [('MSC_SCAN', 4)]
}
```
10 inch PenTablet Mouse
```py
{
('EV_SYN', 0): [
('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_MT_REPORT', 2),
('SYN_DROPPED', 3), ('?', 4)
],
('EV_KEY', 1): [
(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_RIGHT', 273),
('BTN_MIDDLE', 274), ('BTN_SIDE', 275), ('BTN_EXTRA', 276),
('BTN_TOUCH', 330)
],
('EV_REL', 2): [
('REL_X', 0), ('REL_Y', 1), ('REL_HWHEEL', 6), ('REL_WHEEL', 8),
('REL_WHEEL_HI_RES', 11), ('REL_HWHEEL_HI_RES', 12)
],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=0, min=0, max=32767, fuzz=0, flat=0, resolution=0)),
(('ABS_Y', 1), AbsInfo(value=0, min=0, max=32767, fuzz=0, flat=0, resolution=0)),
(('ABS_PRESSURE', 24), AbsInfo(value=0, min=0, max=2047, fuzz=0, flat=0, resolution=0))
],
('EV_MSC', 4): [('MSC_SCAN', 4)]
}
```
## Touchpads
#### ThinkPad E590 SynPS/2 Synaptics TouchPad
```py
{
('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_DROPPED', 3)],
('EV_KEY', 1): [
(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_TOOL_FINGER', 325),
('BTN_TOOL_QUINTTAP', 328), ('BTN_TOUCH', 330),
('BTN_TOOL_DOUBLETAP', 333), ('BTN_TOOL_TRIPLETAP', 334),
('BTN_TOOL_QUADTAP', 335)
],
('EV_ABS', 3): [
(('ABS_X', 0), AbsInfo(value=3111, min=1266, max=5678, fuzz=0, flat=0, resolution=0)),
(('ABS_Y', 1), AbsInfo(value=2120, min=1162, max=4694, fuzz=0, flat=0, resolution=0)),
(('ABS_PRESSURE', 24), AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)),
(('ABS_TOOL_WIDTH', 28), AbsInfo(value=0, min=0, max=15, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_SLOT', 47), AbsInfo(value=0, min=0, max=1, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_POSITION_X', 53), AbsInfo(value=0, min=1266, max=5678, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_POSITION_Y', 54), AbsInfo(value=0, min=1162, max=4694, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_TRACKING_ID', 57), AbsInfo(value=0, min=0, max=65535, fuzz=0, flat=0, resolution=0)),
(('ABS_MT_PRESSURE', 58), AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0))
]
}
```

@ -126,12 +126,15 @@ class TestGetDevices(unittest.TestCase):
self.assertNotIn('camera', get_devices())
self.assertIn('gamepad', get_devices())
def test_gamepad_without_ev_key(self):
def test_device_with_only_ev_abs(self):
def list_devices():
return ['/foo/bar', '/dev/input/event30']
# could be anything, a lot of devices have ABS_X capabilities,
# so it is not treated as gamepad joystick and since it also
# doesn't have key capabilities, there is nothing to map.
fixtures['/foo/bar'] = {
'name': 'gamepad2', 'phys': 'abcd2',
'name': 'qux', 'phys': 'abcd2',
'info': evdev.DeviceInfo(1, 2, 3, 4),
'capabilities': {
evdev.ecodes.EV_ABS: [
@ -144,7 +147,7 @@ class TestGetDevices(unittest.TestCase):
refresh_devices()
self.assertIn('gamepad', get_devices())
self.assertIn('gamepad2', get_devices())
self.assertNotIn('qux', get_devices())
def test_is_gamepad(self):
# properly detects if the device is a gamepad
@ -160,33 +163,37 @@ class TestGetDevices(unittest.TestCase):
assert not absinfo
return self.c
"""positive tests"""
self.assertTrue(is_gamepad(FakeDevice({
EV_ABS: [evdev.ecodes.ABS_X]
})))
self.assertTrue(is_gamepad(FakeDevice({
EV_ABS: [evdev.ecodes.ABS_X, evdev.ecodes.ABS_RY],
EV_KEY: [evdev.ecodes.KEY_A]
})))
self.assertFalse(is_gamepad(FakeDevice({
EV_ABS: [evdev.ecodes.ABS_X, evdev.ecodes.ABS_MT_TRACKING_ID]
EV_ABS: [evdev.ecodes.ABS_X, evdev.ecodes.ABS_Y],
EV_KEY: [evdev.ecodes.BTN_A]
})))
"""negative tests"""
self.assertFalse(is_gamepad(FakeDevice({
EV_ABS: [evdev.ecodes.ABS_HAT2X]
EV_ABS: [evdev.ecodes.ABS_X, evdev.ecodes.ABS_Y],
EV_KEY: [evdev.ecodes.BTN_A],
EV_REL: [evdev.ecodes.REL_X]
})))
self.assertFalse(is_gamepad(FakeDevice({
EV_KEY: [0]
EV_ABS: [evdev.ecodes.ABS_X, evdev.ecodes.ABS_Y],
EV_KEY: [evdev.ecodes.KEY_1]
})))
self.assertFalse(is_gamepad(FakeDevice({
EV_ABS: [evdev.ecodes.ABS_Y],
EV_KEY: [evdev.ecodes.BTN_TOOL_PEN]
EV_ABS: [evdev.ecodes.ABS_X],
EV_KEY: [evdev.ecodes.BTN_A]
})))
self.assertFalse(is_gamepad(FakeDevice({
EV_ABS: [evdev.ecodes.ABS_X],
EV_KEY: [evdev.ecodes.BTN_STYLUS]
EV_KEY: [evdev.ecodes.BTN_A]
})))
self.assertFalse(is_gamepad(FakeDevice({
EV_ABS: [evdev.ecodes.ABS_X],
EV_REL: [evdev.ecodes.REL_X]
EV_ABS: [evdev.ecodes.ABS_X]
})))

@ -199,30 +199,28 @@ class TestInjector(unittest.TestCase):
def test_adds_ev_key(self):
# for some reason, having any EV_KEY capability is needed to
# be able to control the mouse. it probably wants the mouse click.
custom_mapping.change(Key(EV_KEY, BTN_A, 1), 'a')
self.injector = Injector('gamepad 2', custom_mapping)
"""gamepad without any existing key capability"""
"""ABS device without any key capability"""
path = self.new_gamepad
gamepad_template = copy.deepcopy(fixtures['/dev/input/event30'])
fixtures[path] = {
'name': 'gamepad 2', 'phys': 'abcd', 'info': '1234',
'name': 'qux 2', 'phys': 'abcd', 'info': '1234',
'capabilities': gamepad_template['capabilities']
}
del fixtures[path]['capabilities'][EV_KEY]
device = self.injector._grab_device(path)
gamepad = is_gamepad(device)
self.assertNotIn(EV_KEY, device.capabilities())
capabilities = self.injector._modify_capabilities(device, gamepad)
self.assertIn(EV_KEY, capabilities)
self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
# no reason to grab, BTN_A capability is missing in the device
self.assertIsNone(device)
"""gamepad with a btn_mouse capability existing"""
"""ABS device with a btn_mouse capability"""
path = self.new_gamepad
gamepad_template = copy.deepcopy(fixtures['/dev/input/event30'])
fixtures[path] = {
'name': 'gamepad 3', 'phys': 'abcd', 'info': '1234',
'name': 'qux 3', 'phys': 'abcd', 'info': '1234',
'capabilities': gamepad_template['capabilities']
}
fixtures[path]['capabilities'][EV_KEY].append(BTN_LEFT)
@ -234,7 +232,7 @@ class TestInjector(unittest.TestCase):
self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
self.assertIn(evdev.ecodes.KEY_A, capabilities[EV_KEY])
"""gamepad with existing key capabilities, but not btn_mouse"""
"""a gamepad"""
path = '/dev/input/event30'
device = self.injector._grab_device(path)

Loading…
Cancel
Save