From 0abbbf45e1c301cd04dad4fb20d43cfd7f2df09d Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Sat, 27 Mar 2021 13:21:35 +0100 Subject: [PATCH] #65 device icons --- data/key-mapper.glade | 6 +- keymapper/getdevices.py | 141 +++++++++++++++++++++------- keymapper/gui/reader.py | 2 +- keymapper/gui/window.py | 44 +++++++-- keymapper/injection/injector.py | 10 +- tests/test.py | 32 +++++-- tests/testcases/test_getdevices.py | 69 +++++++++----- tests/testcases/test_injector.py | 30 +++--- tests/testcases/test_integration.py | 7 +- 9 files changed, 242 insertions(+), 99 deletions(-) diff --git a/data/key-mapper.glade b/data/key-mapper.glade index 132348c3..466dbecc 100644 --- a/data/key-mapper.glade +++ b/data/key-mapper.glade @@ -104,7 +104,7 @@ - + True False @@ -213,8 +213,7 @@ To give your keys back their original mapping. True True True - Presets need to be saved before they can be applied. -Don't hold down any keys while the injection starts. + Don't hold down any keys while the injection starts. check-icon none True @@ -255,6 +254,7 @@ Don't hold down any keys while the injection starts. none True + True diff --git a/keymapper/getdevices.py b/keymapper/getdevices.py index ebfa0572..e467f1cc 100644 --- a/keymapper/getdevices.py +++ b/keymapper/getdevices.py @@ -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) diff --git a/keymapper/gui/reader.py b/keymapper/gui/reader.py index 56e06084..b9110471 100644 --- a/keymapper/gui/reader.py +++ b/keymapper/gui/reader.py @@ -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 diff --git a/keymapper/gui/window.py b/keymapper/gui/window.py index 323187fa..4afc6478 100755 --- a/keymapper/gui/window.py +++ b/keymapper/gui/window.py @@ -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 diff --git a/keymapper/injection/injector.py b/keymapper/injection/injector.py index 287ba61b..086b215e 100644 --- a/keymapper/injection/injector.py +++ b/keymapper/injection/injector.py @@ -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) diff --git a/tests/test.py b/tests/test.py index 27446621..b0126837 100644 --- a/tests/test.py +++ b/tests/test.py @@ -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 diff --git a/tests/testcases/test_getdevices.py b/tests/testcases/test_getdevices.py index 17fe6c67..37f78e16 100644 --- a/tests/testcases/test_getdevices.py +++ b/tests/testcases/test_getdevices.py @@ -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__": diff --git a/tests/testcases/test_injector.py b/tests/testcases/test_injector.py index bd09918b..6324e1b3 100644 --- a/tests/testcases/test_injector.py +++ b/tests/testcases/test_injector.py @@ -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): diff --git a/tests/testcases/test_integration.py b/tests/testcases/test_integration.py index 2a4c62a8..fad9b19f 100644 --- a/tests/testcases/test_integration.py +++ b/tests/testcases/test_integration.py @@ -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