mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-04 12:00:16 +00:00
#65 device icons
This commit is contained in:
parent
16c766257d
commit
0abbbf45e1
@ -104,7 +104,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="device_selection">
|
||||
<object class="GtkComboBox" id="device_selection">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<signal name="changed" handler="on_select_device" swapped="no"/>
|
||||
@ -213,8 +213,7 @@ To give your keys back their original mapping.</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Presets need to be saved before they can be applied.
|
||||
Don't hold down any keys while the injection starts.</property>
|
||||
<property name="tooltip-text" translatable="yes">Don't hold down any keys while the injection starts.</property>
|
||||
<property name="image">check-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="always-show-image">True</property>
|
||||
@ -255,6 +254,7 @@ Don't hold down any keys while the injection starts.</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_create_preset_clicked" swapped="no"/>
|
||||
<accelerator key="n" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
@ -28,7 +28,8 @@ import time
|
||||
import asyncio
|
||||
|
||||
import evdev
|
||||
from evdev.ecodes import EV_KEY, EV_ABS, KEY_CAMERA, EV_REL, BTN_STYLUS, BTN_A
|
||||
from evdev.ecodes import EV_KEY, EV_ABS, KEY_CAMERA, EV_REL, BTN_STYLUS, \
|
||||
BTN_A, ABS_MT_POSITION_X, REL_X, KEY_A, BTN_LEFT
|
||||
|
||||
from keymapper.logger import logger
|
||||
|
||||
@ -44,6 +45,21 @@ TABLET_KEYS = [
|
||||
]
|
||||
|
||||
|
||||
GAMEPAD = 'gamepad'
|
||||
KEYBOARD = 'keyboard'
|
||||
MOUSE = 'mouse'
|
||||
TOUCHPAD = 'touchpad'
|
||||
GRAPHICS_TABLET = 'graphics-tablet'
|
||||
CAMERA = 'camera'
|
||||
UNKNOWN = 'unknown'
|
||||
|
||||
|
||||
# sort types that most devices would fall in easily to the right
|
||||
PRIORITIES = [
|
||||
GRAPHICS_TABLET, TOUCHPAD, MOUSE, GAMEPAD, KEYBOARD, CAMERA, UNKNOWN
|
||||
]
|
||||
|
||||
|
||||
if not hasattr(evdev.InputDevice, 'path'):
|
||||
# for evdev < 1.0.0 patch the path property
|
||||
@property
|
||||
@ -53,32 +69,15 @@ if not hasattr(evdev.InputDevice, 'path'):
|
||||
evdev.InputDevice.path = path
|
||||
|
||||
|
||||
def is_gamepad(device):
|
||||
"""Check if joystick movements are available for mapping.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
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
|
||||
def _is_gamepad(capabilities):
|
||||
"""Check if joystick movements are available for mapping."""
|
||||
if len(capabilities.get(EV_REL, [])) > 0:
|
||||
return False
|
||||
|
||||
# buttons
|
||||
if BTN_A not in capabilities.get(EV_KEY, []):
|
||||
return False
|
||||
|
||||
# joystick
|
||||
# joysticks
|
||||
abs_capabilities = capabilities.get(EV_ABS, [])
|
||||
if evdev.ecodes.ABS_X not in abs_capabilities:
|
||||
return False
|
||||
@ -88,6 +87,78 @@ def is_gamepad(device):
|
||||
return True
|
||||
|
||||
|
||||
def _is_mouse(capabilities):
|
||||
"""Check if the capabilities represent those of a mouse."""
|
||||
if not REL_X in capabilities.get(EV_REL, []):
|
||||
return False
|
||||
|
||||
if not BTN_LEFT in capabilities.get(EV_KEY, []):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _is_graphics_tablet(capabilities):
|
||||
"""Check if the capabilities represent those of a graphics tablet."""
|
||||
if BTN_STYLUS in capabilities.get(EV_KEY, []):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _is_touchpad(capabilities):
|
||||
"""Check if the capabilities represent those of a touchpad."""
|
||||
if ABS_MT_POSITION_X in capabilities.get(EV_ABS, []):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _is_keyboard(capabilities):
|
||||
"""Check if the capabilities represent those of a keyboard."""
|
||||
if KEY_A in capabilities.get(EV_KEY, []):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _is_camera(capabilities):
|
||||
"""Check if the capabilities represent those of a camera."""
|
||||
key_capa = capabilities.get(EV_KEY)
|
||||
return key_capa and len(key_capa) == 1 and key_capa[0] == KEY_CAMERA
|
||||
|
||||
|
||||
def classify(device):
|
||||
"""Figure out what kind of device this is.
|
||||
|
||||
Use this instead of functions like _is_keyboard to avoid getting false
|
||||
positives.
|
||||
"""
|
||||
# TODO test
|
||||
capabilities = device.capabilities(absinfo=False)
|
||||
|
||||
if _is_graphics_tablet(capabilities):
|
||||
# check this before is_gamepad to avoid classifying abs_x
|
||||
# as joysticks when they are actually stylus positions
|
||||
return GRAPHICS_TABLET
|
||||
|
||||
if _is_touchpad(capabilities):
|
||||
return TOUCHPAD
|
||||
|
||||
if _is_mouse(capabilities):
|
||||
return MOUSE
|
||||
|
||||
if _is_gamepad(capabilities):
|
||||
return GAMEPAD
|
||||
|
||||
if _is_camera(capabilities):
|
||||
return CAMERA
|
||||
|
||||
if _is_keyboard(capabilities):
|
||||
# very low in the chain to avoid classifying most devices
|
||||
# as keyboard, because there are many with ev_key capabilities
|
||||
return KEYBOARD
|
||||
|
||||
return UNKNOWN
|
||||
|
||||
|
||||
class _GetDevices(threading.Thread):
|
||||
"""Process to get the devices that can be worked with.
|
||||
|
||||
@ -123,21 +194,20 @@ class _GetDevices(threading.Thread):
|
||||
if device.name == 'Power Button':
|
||||
continue
|
||||
|
||||
gamepad = is_gamepad(device)
|
||||
device_type = classify(device)
|
||||
|
||||
if device_type == CAMERA:
|
||||
continue
|
||||
|
||||
# https://www.kernel.org/doc/html/latest/input/event-codes.html
|
||||
capabilities = device.capabilities(absinfo=False)
|
||||
|
||||
key_capa = capabilities.get(EV_KEY)
|
||||
|
||||
if key_capa is None and not gamepad:
|
||||
if key_capa is None and device_type != GAMEPAD:
|
||||
# skip devices that don't provide buttons that can be mapped
|
||||
continue
|
||||
|
||||
if key_capa and len(key_capa) == 1 and key_capa[0] == KEY_CAMERA:
|
||||
# skip cameras
|
||||
continue
|
||||
|
||||
name = device.name
|
||||
path = device.path
|
||||
|
||||
@ -152,23 +222,28 @@ class _GetDevices(threading.Thread):
|
||||
grouped[info] = []
|
||||
|
||||
logger.spam(
|
||||
'Found "%s", "%s", "%s" %s',
|
||||
info, path, name, '(gamepad)' if gamepad else ''
|
||||
'Found "%s", "%s", "%s", type: %s',
|
||||
info, path, name, device_type
|
||||
)
|
||||
|
||||
grouped[info].append((name, path, gamepad))
|
||||
grouped[info].append((name, path, device_type))
|
||||
|
||||
# now write down all the paths of that group
|
||||
result = {}
|
||||
for group in grouped.values():
|
||||
names = [entry[0] for entry in group]
|
||||
devs = [entry[1] for entry in group]
|
||||
gamepad = True in [entry[2] for entry in group]
|
||||
|
||||
# find the most specific type from all devices per group.
|
||||
# e.g. a device with mouse and keyboard subdevices is a mouse.
|
||||
types = sorted([entry[2] for entry in group], key=PRIORITIES.index)
|
||||
device_type = types[0]
|
||||
|
||||
shortest_name = sorted(names, key=len)[0]
|
||||
result[shortest_name] = {
|
||||
'paths': devs,
|
||||
'devices': names,
|
||||
'gamepad': gamepad
|
||||
'type': device_type
|
||||
}
|
||||
|
||||
self.pipe.send(result)
|
||||
|
@ -131,7 +131,7 @@ class Reader:
|
||||
if event is None:
|
||||
continue
|
||||
|
||||
gamepad = get_devices()[self.device_name]['gamepad']
|
||||
gamepad = get_devices()[self.device_name]['type'] == 'gamepad'
|
||||
if not utils.should_map_as_btn(event, custom_mapping, gamepad):
|
||||
continue
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
from gi.repository import Gtk, Gdk, GLib
|
||||
|
||||
@ -34,7 +35,8 @@ from keymapper.presets import get_presets, find_newest_preset, \
|
||||
delete_preset, rename_preset, get_available_preset_name
|
||||
from keymapper.logger import logger, COMMIT_HASH, version, evdev_version, \
|
||||
is_debug
|
||||
from keymapper.getdevices import get_devices
|
||||
from keymapper.getdevices import get_devices, GAMEPAD, KEYBOARD, UNKNOWN, \
|
||||
GRAPHICS_TABLET, TOUCHPAD, MOUSE
|
||||
from keymapper.gui.row import Row, to_string
|
||||
from keymapper.gui.reader import reader
|
||||
from keymapper.gui.helper import is_helper_running
|
||||
@ -132,6 +134,20 @@ class Window:
|
||||
builder.connect_signals(self)
|
||||
self.builder = builder
|
||||
|
||||
# set up the device selection
|
||||
# https://python-gtk-3-tutorial.readthedocs.io/en/latest/treeview.html#the-view
|
||||
combobox = self.get('device_selection')
|
||||
self.device_store = Gtk.ListStore(str, str)
|
||||
combobox.set_model(self.device_store)
|
||||
renderer_icon = Gtk.CellRendererPixbuf()
|
||||
renderer_text = Gtk.CellRendererText()
|
||||
renderer_text.set_padding(5, 0)
|
||||
combobox.set_id_column(1)
|
||||
combobox.pack_start(renderer_icon, False)
|
||||
combobox.pack_start(renderer_text, False)
|
||||
combobox.add_attribute(renderer_icon, 'icon-name', 0)
|
||||
combobox.add_attribute(renderer_text, 'text', 1)
|
||||
|
||||
self.confirm_delete = builder.get_object('confirm-delete')
|
||||
self.about = builder.get_object('about-dialog')
|
||||
self.about.connect('delete-event', on_close_about)
|
||||
@ -184,7 +200,11 @@ class Window:
|
||||
cmd = f'pkexec key-mapper-control --command helper {debug}'
|
||||
|
||||
logger.debug('Running `%s`', cmd)
|
||||
os.system(cmd)
|
||||
exit_code = os.system(cmd)
|
||||
|
||||
if exit_code != 0:
|
||||
logger.error('Failed to pkexec the helper, code %d', exit_code)
|
||||
sys.exit()
|
||||
|
||||
def show_confirm_delete(self):
|
||||
"""Blocks until the user decided about an action."""
|
||||
@ -222,7 +242,7 @@ class Window:
|
||||
def initialize_gamepad_config(self):
|
||||
"""Set slider and dropdown values when a gamepad is selected."""
|
||||
devices = get_devices()
|
||||
if devices[self.selected_device]['gamepad']:
|
||||
if devices[self.selected_device]['type'] == 'gamepad':
|
||||
self.get('gamepad_separator').show()
|
||||
self.get('gamepad_config').show()
|
||||
else:
|
||||
@ -311,9 +331,21 @@ class Window:
|
||||
device_selection = self.get('device_selection')
|
||||
|
||||
with HandlerDisabled(device_selection, self.on_select_device):
|
||||
device_selection.remove_all()
|
||||
self.device_store.clear()
|
||||
|
||||
for device in devices:
|
||||
device_selection.append(device, device)
|
||||
icons = {
|
||||
GAMEPAD: 'input-gaming',
|
||||
MOUSE: 'input-mouse',
|
||||
KEYBOARD: 'input-keyboard',
|
||||
GRAPHICS_TABLET: 'input-tablet',
|
||||
TOUCHPAD: 'input-touchpad',
|
||||
UNKNOWN: None,
|
||||
}
|
||||
self.device_store.append([
|
||||
icons[devices[device]['type']],
|
||||
device
|
||||
])
|
||||
|
||||
self.select_newest_preset()
|
||||
|
||||
@ -588,7 +620,7 @@ class Window:
|
||||
# preset. Prevent another unsaved-changes dialog to pop up
|
||||
custom_mapping.changed = False
|
||||
|
||||
device = dropdown.get_active_text()
|
||||
device = dropdown.get_active_id()
|
||||
|
||||
if device is None:
|
||||
return
|
||||
|
@ -30,7 +30,7 @@ import evdev
|
||||
from evdev.ecodes import EV_KEY, EV_REL
|
||||
|
||||
from keymapper.logger import logger
|
||||
from keymapper.getdevices import get_devices, is_gamepad
|
||||
from keymapper.getdevices import get_devices, classify, GAMEPAD
|
||||
from keymapper import utils
|
||||
from keymapper.mapping import DISABLE_CODE
|
||||
from keymapper.injection.keycode_mapper import KeycodeMapper
|
||||
@ -159,7 +159,7 @@ class Injector(multiprocessing.Process):
|
||||
needed = True
|
||||
break
|
||||
|
||||
gamepad = is_gamepad(device)
|
||||
gamepad = classify(device) == GAMEPAD
|
||||
|
||||
if gamepad and self.context.maps_joystick():
|
||||
logger.debug('Grabbing "%s" because of maps_joystick', path)
|
||||
@ -341,7 +341,7 @@ class Injector(multiprocessing.Process):
|
||||
self.context.uinput = evdev.UInput(
|
||||
name=self.get_udef_name(self.device, 'mapped'),
|
||||
phys=DEV_NAME,
|
||||
events=self._construct_capabilities(group['gamepad'])
|
||||
events=self._construct_capabilities(group['type'] == 'gamepad')
|
||||
)
|
||||
|
||||
# Watch over each one of the potentially multiple devices per hardware
|
||||
@ -355,7 +355,7 @@ class Injector(multiprocessing.Process):
|
||||
# certain capabilities can have side effects apparently. with an
|
||||
# EV_ABS capability, EV_REL won't move the mouse pointer anymore.
|
||||
# so don't merge all InputDevices into one UInput device.
|
||||
gamepad = is_gamepad(source)
|
||||
gamepad = classify(source) == GAMEPAD
|
||||
forward_to = evdev.UInput(
|
||||
name=self.get_udef_name(source.name, 'forwarded'),
|
||||
phys=DEV_NAME,
|
||||
@ -423,7 +423,7 @@ class Injector(multiprocessing.Process):
|
||||
source.path, source.fd
|
||||
)
|
||||
|
||||
gamepad = is_gamepad(source)
|
||||
gamepad = classify(source) == GAMEPAD
|
||||
|
||||
keycode_handler = KeycodeMapper(self.context, source, forward_to)
|
||||
|
||||
|
@ -32,6 +32,7 @@ import subprocess
|
||||
import multiprocessing
|
||||
import asyncio
|
||||
import psutil
|
||||
from pickle import UnpicklingError
|
||||
|
||||
import evdev
|
||||
import gi
|
||||
@ -121,20 +122,29 @@ def read_write_history_pipe():
|
||||
phys_1 = 'usb-0000:03:00.0-1/input2'
|
||||
info_1 = evdev.device.DeviceInfo(1, 1, 1, 1)
|
||||
|
||||
keyboard_keys = sorted(evdev.ecodes.keys.keys())[:255]
|
||||
|
||||
fixtures = {
|
||||
# device 1
|
||||
'/dev/input/event11': {
|
||||
'capabilities': {evdev.ecodes.EV_KEY: [], evdev.ecodes.EV_REL: [
|
||||
evdev.ecodes.REL_WHEEL,
|
||||
evdev.ecodes.REL_HWHEEL
|
||||
]},
|
||||
'capabilities': {
|
||||
evdev.ecodes.EV_KEY: [
|
||||
evdev.ecodes.BTN_LEFT
|
||||
],
|
||||
evdev.ecodes.EV_REL: [
|
||||
evdev.ecodes.REL_X,
|
||||
evdev.ecodes.REL_Y,
|
||||
evdev.ecodes.REL_WHEEL,
|
||||
evdev.ecodes.REL_HWHEEL
|
||||
]
|
||||
},
|
||||
'phys': f'{phys_1}/input2',
|
||||
'info': info_1,
|
||||
'name': 'device 1 foo',
|
||||
'group': 'device 1'
|
||||
},
|
||||
'/dev/input/event10': {
|
||||
'capabilities': {evdev.ecodes.EV_KEY: list(evdev.ecodes.keys.keys())},
|
||||
'capabilities': {evdev.ecodes.EV_KEY: keyboard_keys},
|
||||
'phys': f'{phys_1}/input3',
|
||||
'info': info_1,
|
||||
'name': 'device 1',
|
||||
@ -157,7 +167,7 @@ fixtures = {
|
||||
|
||||
# device 2
|
||||
'/dev/input/event20': {
|
||||
'capabilities': {evdev.ecodes.EV_KEY: list(evdev.ecodes.keys.keys())},
|
||||
'capabilities': {evdev.ecodes.EV_KEY: keyboard_keys},
|
||||
'phys': 'usb-0000:03:00.0-2/input1',
|
||||
'info': evdev.device.DeviceInfo(2, 1, 2, 1),
|
||||
'name': 'device 2'
|
||||
@ -193,7 +203,7 @@ fixtures = {
|
||||
# key-mapper devices are not displayed in the ui, some instance
|
||||
# of key-mapper started injecting apparently.
|
||||
'/dev/input/event40': {
|
||||
'capabilities': {evdev.ecodes.EV_KEY: list(evdev.ecodes.keys.keys())},
|
||||
'capabilities': {evdev.ecodes.EV_KEY: keyboard_keys},
|
||||
'phys': 'key-mapper/input1',
|
||||
'info': evdev.device.DeviceInfo(5, 1, 5, 1),
|
||||
'name': 'key-mapper device 2'
|
||||
@ -350,7 +360,13 @@ class InputDevice:
|
||||
return None
|
||||
|
||||
time.sleep(EVENT_READ_TIMEOUT)
|
||||
event = pending_events[self.group][1].recv()
|
||||
try:
|
||||
event = pending_events[self.group][1].recv()
|
||||
except UnpicklingError as error:
|
||||
# failed in tests sometimes
|
||||
print(error)
|
||||
return None
|
||||
|
||||
self.log(event, 'read_one')
|
||||
return event
|
||||
|
||||
|
@ -24,8 +24,9 @@ from unittest import mock
|
||||
|
||||
import evdev
|
||||
|
||||
from keymapper.getdevices import _GetDevices, get_devices, is_gamepad, \
|
||||
refresh_devices
|
||||
from keymapper.getdevices import _GetDevices, get_devices, classify, \
|
||||
refresh_devices, GAMEPAD, MOUSE, UNKNOWN, GRAPHICS_TABLET, TOUCHPAD, \
|
||||
KEYBOARD
|
||||
|
||||
from tests.test import cleanup, fixtures
|
||||
|
||||
@ -56,22 +57,22 @@ class TestGetDevices(unittest.TestCase):
|
||||
'device 1',
|
||||
'device 1'
|
||||
],
|
||||
'gamepad': False
|
||||
'type': MOUSE
|
||||
},
|
||||
'device 2': {
|
||||
'paths': ['/dev/input/event20'],
|
||||
'devices': ['device 2'],
|
||||
'gamepad': False
|
||||
'type': KEYBOARD
|
||||
},
|
||||
'gamepad': {
|
||||
'paths': ['/dev/input/event30'],
|
||||
'devices': ['gamepad'],
|
||||
'gamepad': True
|
||||
'type': GAMEPAD
|
||||
},
|
||||
'key-mapper device 2': {
|
||||
'paths': ['/dev/input/event40'],
|
||||
'devices': ['key-mapper device 2'],
|
||||
'gamepad': False
|
||||
'type': KEYBOARD
|
||||
},
|
||||
})
|
||||
self.assertDictEqual(pipe.devices, get_devices(include_keymapper=True))
|
||||
@ -89,17 +90,17 @@ class TestGetDevices(unittest.TestCase):
|
||||
'device 1',
|
||||
'device 1'
|
||||
],
|
||||
'gamepad': False
|
||||
'type': MOUSE
|
||||
},
|
||||
'device 2': {
|
||||
'paths': ['/dev/input/event20'],
|
||||
'devices': ['device 2'],
|
||||
'gamepad': False
|
||||
'type': KEYBOARD
|
||||
},
|
||||
'gamepad': {
|
||||
'paths': ['/dev/input/event30'],
|
||||
'devices': ['gamepad'],
|
||||
'gamepad': True
|
||||
'type': GAMEPAD
|
||||
},
|
||||
})
|
||||
|
||||
@ -144,7 +145,7 @@ class TestGetDevices(unittest.TestCase):
|
||||
self.assertIn('gamepad', get_devices())
|
||||
self.assertNotIn('qux', get_devices())
|
||||
|
||||
def test_is_gamepad(self):
|
||||
def test_classify(self):
|
||||
# properly detects if the device is a gamepad
|
||||
EV_ABS = evdev.ecodes.EV_ABS
|
||||
EV_KEY = evdev.ecodes.EV_KEY
|
||||
@ -158,38 +159,58 @@ class TestGetDevices(unittest.TestCase):
|
||||
assert not absinfo
|
||||
return self.c
|
||||
|
||||
"""positive tests"""
|
||||
"""gamepads"""
|
||||
|
||||
self.assertTrue(is_gamepad(FakeDevice({
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_ABS: [evdev.ecodes.ABS_X, evdev.ecodes.ABS_Y],
|
||||
EV_KEY: [evdev.ecodes.BTN_A]
|
||||
})))
|
||||
})), GAMEPAD)
|
||||
|
||||
"""negative tests"""
|
||||
"""mice"""
|
||||
|
||||
self.assertFalse(is_gamepad(FakeDevice({
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_REL: [evdev.ecodes.REL_X, evdev.ecodes.REL_Y],
|
||||
EV_KEY: [evdev.ecodes.BTN_LEFT]
|
||||
})), MOUSE)
|
||||
|
||||
"""keyboard"""
|
||||
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_KEY: [evdev.ecodes.KEY_A]
|
||||
})), KEYBOARD)
|
||||
|
||||
"""touchpads"""
|
||||
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_KEY: [evdev.ecodes.KEY_A],
|
||||
EV_ABS: [evdev.ecodes.ABS_MT_POSITION_X]
|
||||
})), TOUCHPAD)
|
||||
|
||||
"""weird combos"""
|
||||
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_ABS: [evdev.ecodes.ABS_X, evdev.ecodes.ABS_Y],
|
||||
EV_KEY: [evdev.ecodes.BTN_A],
|
||||
EV_REL: [evdev.ecodes.REL_X]
|
||||
})))
|
||||
})), UNKNOWN)
|
||||
|
||||
self.assertFalse(is_gamepad(FakeDevice({
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_ABS: [evdev.ecodes.ABS_X, evdev.ecodes.ABS_Y],
|
||||
EV_KEY: [evdev.ecodes.KEY_1]
|
||||
})))
|
||||
})), UNKNOWN)
|
||||
|
||||
self.assertFalse(is_gamepad(FakeDevice({
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_ABS: [evdev.ecodes.ABS_X],
|
||||
EV_KEY: [evdev.ecodes.BTN_A]
|
||||
})))
|
||||
})), UNKNOWN)
|
||||
|
||||
self.assertFalse(is_gamepad(FakeDevice({
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_KEY: [evdev.ecodes.BTN_A]
|
||||
})))
|
||||
})), UNKNOWN)
|
||||
|
||||
self.assertFalse(is_gamepad(FakeDevice({
|
||||
self.assertEqual(classify(FakeDevice({
|
||||
EV_ABS: [evdev.ecodes.ABS_X]
|
||||
})))
|
||||
})), UNKNOWN)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -39,11 +39,12 @@ from keymapper.config import config, NONE, MOUSE, WHEEL, BUTTONS
|
||||
from keymapper.key import Key
|
||||
from keymapper.injection.macros import parse
|
||||
from keymapper.injection.context import Context
|
||||
from keymapper.getdevices import get_devices, is_gamepad
|
||||
from keymapper.getdevices import get_devices, classify, GAMEPAD
|
||||
|
||||
from tests.test import new_event, push_events, fixtures, \
|
||||
EVENT_READ_TIMEOUT, uinput_write_history_pipe, \
|
||||
MAX_ABS, quick_cleanup, read_write_history_pipe, InputDevice, uinputs
|
||||
MAX_ABS, quick_cleanup, read_write_history_pipe, InputDevice, uinputs, \
|
||||
keyboard_keys
|
||||
|
||||
|
||||
class TestInjector(unittest.TestCase):
|
||||
@ -84,7 +85,7 @@ class TestInjector(unittest.TestCase):
|
||||
# _grab_device
|
||||
self.injector.context = Context(custom_mapping)
|
||||
device = self.injector._grab_device(path)
|
||||
gamepad = is_gamepad(device)
|
||||
gamepad = classify(device) == GAMEPAD
|
||||
self.assertFalse(gamepad)
|
||||
self.assertEqual(self.failed, 2)
|
||||
# success on the third try
|
||||
@ -134,7 +135,7 @@ class TestInjector(unittest.TestCase):
|
||||
|
||||
path = '/dev/input/event30'
|
||||
device = self.injector._grab_device(path)
|
||||
gamepad = is_gamepad(device)
|
||||
gamepad = classify(device) == GAMEPAD
|
||||
self.assertIsNotNone(device)
|
||||
self.assertTrue(gamepad)
|
||||
|
||||
@ -165,7 +166,7 @@ class TestInjector(unittest.TestCase):
|
||||
custom_mapping.change(Key(EV_KEY, BTN_A, 1), 'a')
|
||||
device = self.injector._grab_device(path)
|
||||
self.assertIsNotNone(device)
|
||||
gamepad = is_gamepad(device)
|
||||
gamepad = classify(device) == GAMEPAD
|
||||
self.assertTrue(gamepad)
|
||||
capabilities = self.injector._construct_capabilities(gamepad)
|
||||
self.assertNotIn(EV_ABS, capabilities)
|
||||
@ -183,7 +184,7 @@ class TestInjector(unittest.TestCase):
|
||||
# the right joystick maps as mouse, so it is grabbed
|
||||
# even with an empty mapping
|
||||
self.assertIsNotNone(device)
|
||||
gamepad = is_gamepad(device)
|
||||
gamepad = classify(device) == GAMEPAD
|
||||
self.assertTrue(gamepad)
|
||||
capabilities = self.injector._construct_capabilities(gamepad)
|
||||
self.assertNotIn(EV_ABS, capabilities)
|
||||
@ -191,7 +192,7 @@ class TestInjector(unittest.TestCase):
|
||||
|
||||
custom_mapping.change(Key(EV_KEY, BTN_A, 1), 'a')
|
||||
device = self.injector._grab_device(path)
|
||||
gamepad = is_gamepad(device)
|
||||
gamepad = classify(device) == GAMEPAD
|
||||
self.assertIsNotNone(device)
|
||||
self.assertTrue(gamepad)
|
||||
capabilities = self.injector._construct_capabilities(gamepad)
|
||||
@ -230,7 +231,7 @@ class TestInjector(unittest.TestCase):
|
||||
fixtures[path]['capabilities'][EV_KEY].append(BTN_LEFT)
|
||||
fixtures[path]['capabilities'][EV_KEY].append(KEY_A)
|
||||
device = self.injector._grab_device(path)
|
||||
gamepad = is_gamepad(device)
|
||||
gamepad = classify(device) == GAMEPAD
|
||||
capabilities = self.injector._construct_capabilities(gamepad)
|
||||
self.assertIn(EV_KEY, capabilities)
|
||||
self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
|
||||
@ -240,7 +241,7 @@ class TestInjector(unittest.TestCase):
|
||||
|
||||
path = '/dev/input/event30'
|
||||
device = self.injector._grab_device(path)
|
||||
gamepad = is_gamepad(device)
|
||||
gamepad = classify(device) == GAMEPAD
|
||||
self.assertIn(EV_KEY, device.capabilities())
|
||||
self.assertNotIn(evdev.ecodes.BTN_MOUSE, device.capabilities()[EV_KEY])
|
||||
capabilities = self.injector._construct_capabilities(gamepad)
|
||||
@ -259,17 +260,14 @@ class TestInjector(unittest.TestCase):
|
||||
self.assertEqual(self.failed, 0)
|
||||
|
||||
def test_skip_unknown_device(self):
|
||||
custom_mapping.change(Key(EV_KEY, 10, 1), 'a')
|
||||
|
||||
# skips a device because its capabilities are not used in the mapping
|
||||
self.injector = Injector('device 1', custom_mapping)
|
||||
self.injector.context = Context(custom_mapping)
|
||||
path = '/dev/input/event11'
|
||||
device = self.injector._grab_device(path)
|
||||
|
||||
# make sure the test uses a fixture without interesting capabilities
|
||||
capabilities = evdev.InputDevice(path).capabilities()
|
||||
self.assertEqual(len(capabilities.get(EV_KEY, [])), 0)
|
||||
self.assertEqual(len(capabilities.get(EV_ABS, [])), 0)
|
||||
|
||||
# skips the device alltogether, so no grab attempts fail
|
||||
self.assertEqual(self.failed, 0)
|
||||
self.assertIsNone(device)
|
||||
@ -516,8 +514,8 @@ class TestInjector(unittest.TestCase):
|
||||
self.assertIn(EV_REL, forwarded_foo.capabilities())
|
||||
self.assertIn(EV_KEY, forwarded.capabilities())
|
||||
self.assertEqual(
|
||||
len(forwarded.capabilities()[EV_KEY]),
|
||||
len(evdev.ecodes.keys)
|
||||
sorted(forwarded.capabilities()[EV_KEY]),
|
||||
keyboard_keys
|
||||
)
|
||||
|
||||
def test_injector(self):
|
||||
|
@ -147,8 +147,9 @@ class TestGetDevicesFromHelper(unittest.TestCase):
|
||||
# the gui an empty dict, because it doesn't know any devices
|
||||
# without the help of the privileged helper
|
||||
set_devices({})
|
||||
else:
|
||||
cls.original_os_system(cmd)
|
||||
return 0
|
||||
|
||||
return cls.original_os_system(cmd)
|
||||
|
||||
os.system = os_system
|
||||
|
||||
@ -1342,7 +1343,7 @@ class TestIntegration(unittest.TestCase):
|
||||
self.assertEqual(self.window.selected_preset, 'preset 1')
|
||||
|
||||
# add a device that doesn't exist to the dropdown
|
||||
device_selection.insert(0, 'foo', 'foo')
|
||||
self.window.device_store.insert(0, [None, 'foo'])
|
||||
|
||||
# now the newest preset should be selected and the non-existing
|
||||
# device removed
|
||||
|
Loading…
Reference in New Issue
Block a user