mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-04 12:00:16 +00:00
some more tests
This commit is contained in:
parent
e54a08f3be
commit
d75082bdf7
@ -32,8 +32,8 @@ Documentation:
|
||||
|
||||
##### Names
|
||||
|
||||
For a list of supported keystrokes and their names, check the output of
|
||||
`xmodmap -pke`
|
||||
For a list of supported keystrokes and their names for the middle column,
|
||||
check the output of `xmodmap -pke`
|
||||
|
||||
- Alphanumeric `a` to `z` and `0` to `9`
|
||||
- Modifiers `Alt_L` `Control_L` `Control_R` `Shift_L` `Shift_R`
|
||||
@ -42,6 +42,7 @@ If you can't find what you need, consult
|
||||
[linux/input-event-codes.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h)
|
||||
|
||||
- Mouse buttons `BTN_LEFT` `BTN_RIGHT` `BTN_MIDDLE` `BTN_SIDE`
|
||||
- Multimedia keys `KEY_NEXTSONG`, `KEY_PLAYPAUSE`, ...
|
||||
- Macro special keys `KEY_MACRO1` `KEY_MACRO2` ...
|
||||
|
||||
##### Gamepads
|
||||
|
@ -143,7 +143,6 @@ class KeycodeInjector:
|
||||
|
||||
needed = False
|
||||
for (ev_type, keycode), _ in self.mapping:
|
||||
# TODO test ev_type
|
||||
if keycode in capabilities.get(ev_type, []):
|
||||
needed = True
|
||||
break
|
||||
@ -161,13 +160,13 @@ class KeycodeInjector:
|
||||
|
||||
attempts = 0
|
||||
while True:
|
||||
device = evdev.InputDevice(path)
|
||||
try:
|
||||
# grab to avoid e.g. the disabled keycode of 10 to confuse
|
||||
# X, especially when one of the buttons of your mouse also
|
||||
# uses 10. This also avoids having to load an empty xkb
|
||||
# symbols file to prevent writing any unwanted keys.
|
||||
device.grab()
|
||||
logger.debug('Grab %s', path)
|
||||
break
|
||||
except IOError:
|
||||
attempts += 1
|
||||
@ -224,7 +223,6 @@ class KeycodeInjector:
|
||||
capabilities[ecodes.EV_KEY] = []
|
||||
# for reasons I don't know, it is also required to have
|
||||
# any keyboard button in capabilities.
|
||||
# TODO test that this is always present when abs_to_rel
|
||||
capabilities[ecodes.EV_KEY].append(ecodes.KEY_0)
|
||||
|
||||
# just like what python-evdev does in from_device
|
||||
|
@ -43,7 +43,6 @@ def should_map_event_as_btn(type, code):
|
||||
code : int
|
||||
linux keycode
|
||||
"""
|
||||
# TODO test
|
||||
if type == evdev.events.EV_KEY:
|
||||
return True
|
||||
|
||||
|
@ -108,8 +108,6 @@ class _KeycodeReader:
|
||||
logger.debug('Pipe closed, reader stops.')
|
||||
sys.exit(0)
|
||||
|
||||
# TODO write a test to map event `type 3 (EV_ABS), code 16
|
||||
# (ABS_HAT0X), value 0` to a button
|
||||
if should_map_event_as_btn(event.type, event.code):
|
||||
logger.spam(
|
||||
'got code:%s value:%s type:%s',
|
||||
|
@ -38,7 +38,6 @@ CTX_KEYCODE = 2
|
||||
|
||||
def to_string(ev_type, code):
|
||||
"""A nice to show description of the pressed key."""
|
||||
# TODO test
|
||||
try:
|
||||
name = evdev.ecodes.bytype[ev_type][code]
|
||||
if isinstance(name, list):
|
||||
|
@ -86,7 +86,7 @@ class Mapping:
|
||||
prev_keycode = int(prev_keycode)
|
||||
if prev_type is not None:
|
||||
prev_type = int(prev_type)
|
||||
except ValueError:
|
||||
except (TypeError, ValueError):
|
||||
logger.error('Can only use numbers in the tuples')
|
||||
return False
|
||||
|
||||
@ -100,9 +100,6 @@ class Mapping:
|
||||
if code_changed and prev_keycode is not None:
|
||||
# clear previous mapping of that code, because the line
|
||||
# representing that one will now represent a different one.
|
||||
# TODO test that None won't reach the clear function.
|
||||
# TODO test that overwriting an entry with a different type
|
||||
# works
|
||||
self.clear(prev_type, prev_keycode)
|
||||
self.changed = True
|
||||
return True
|
||||
|
@ -25,6 +25,7 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import copy
|
||||
import unittest
|
||||
import subprocess
|
||||
import multiprocessing
|
||||
@ -99,7 +100,16 @@ fixtures = {
|
||||
},
|
||||
|
||||
'/dev/input/event30': {
|
||||
'capabilities': {evdev.ecodes.EV_SYN: [], evdev.ecodes.EV_ABS: [0, 1]},
|
||||
# this device is expected to not have EV_KEY capabilities in tests
|
||||
# yet. Only when the injector is running EV_KEY stuff is added
|
||||
'capabilities': {
|
||||
evdev.ecodes.EV_SYN: [],
|
||||
evdev.ecodes.EV_ABS: [
|
||||
evdev.ecodes.ABS_X,
|
||||
evdev.ecodes.ABS_Y,
|
||||
evdev.ecodes.ABS_HAT0X
|
||||
]
|
||||
},
|
||||
'phys': 'usb-0000:03:00.0-3/input1',
|
||||
'name': 'gamepad'
|
||||
},
|
||||
@ -252,7 +262,7 @@ def patch_evdev():
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
def capabilities(self, absinfo=True):
|
||||
return fixtures[self.path]['capabilities']
|
||||
return copy.deepcopy(fixtures[self.path]['capabilities'])
|
||||
|
||||
class UInput:
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -288,6 +298,7 @@ def clear_write_history():
|
||||
while uinput_write_history_pipe[0].poll():
|
||||
uinput_write_history_pipe[0].recv()
|
||||
|
||||
|
||||
# quickly fake some stuff before any other file gets a chance to import
|
||||
# the original versions
|
||||
patch_paths()
|
||||
|
@ -24,7 +24,7 @@ import unittest
|
||||
import time
|
||||
|
||||
import evdev
|
||||
from evdev.ecodes import EV_REL, EV_KEY, EV_ABS
|
||||
from evdev.ecodes import EV_REL, EV_KEY, EV_ABS, ABS_HAT0X
|
||||
|
||||
from keymapper.dev.injector import is_numlock_on, toggle_numlock,\
|
||||
ensure_numlock, KeycodeInjector
|
||||
@ -65,6 +65,7 @@ class TestInjector(unittest.TestCase):
|
||||
for key in keys:
|
||||
del pending_events[key]
|
||||
clear_write_history()
|
||||
custom_mapping.empty()
|
||||
|
||||
def test_modify_capabilities(self):
|
||||
class FakeDevice:
|
||||
@ -108,6 +109,15 @@ class TestInjector(unittest.TestCase):
|
||||
# success on the third try
|
||||
device.name = fixtures[path]['name']
|
||||
|
||||
def test_prepare_device_1(self):
|
||||
# according to the fixtures, /dev/input/event30 can do ABS_HAT0X
|
||||
custom_mapping.change((EV_ABS, ABS_HAT0X), 'a')
|
||||
self.injector = KeycodeInjector('foobar', custom_mapping)
|
||||
|
||||
_prepare_device = self.injector._prepare_device
|
||||
self.assertIsNone(_prepare_device('/dev/input/event10')[0])
|
||||
self.assertIsNotNone(_prepare_device('/dev/input/event30')[0])
|
||||
|
||||
def test_gamepad_capabilities(self):
|
||||
self.injector = KeycodeInjector('gamepad', custom_mapping)
|
||||
|
||||
@ -119,6 +129,11 @@ class TestInjector(unittest.TestCase):
|
||||
self.assertNotIn(evdev.ecodes.EV_ABS, capabilities)
|
||||
self.assertIn(evdev.ecodes.EV_REL, capabilities)
|
||||
|
||||
# for some reason, having any EV_KEY capability is needed to
|
||||
# be able to control the mouse
|
||||
self.assertIn(evdev.ecodes.EV_KEY, capabilities)
|
||||
self.assertEqual(len(capabilities[evdev.ecodes.EV_KEY]), 1)
|
||||
|
||||
def test_skip_unused_device(self):
|
||||
# skips a device because its capabilities are not used in the mapping
|
||||
custom_mapping.change((EV_KEY, 10), 'a')
|
||||
@ -282,7 +297,7 @@ class TestInjector(unittest.TestCase):
|
||||
|
||||
def test_injector(self):
|
||||
custom_mapping.change((EV_KEY, 8), 'k(KEY_Q).k(w)')
|
||||
custom_mapping.change((EV_KEY, 9), 'a')
|
||||
custom_mapping.change((EV_ABS, ABS_HAT0X), 'a')
|
||||
# one mapping that is unknown in the system_mapping on purpose
|
||||
input_b = 10
|
||||
custom_mapping.change((EV_KEY, input_b), 'b')
|
||||
@ -301,9 +316,9 @@ class TestInjector(unittest.TestCase):
|
||||
# should execute a macro
|
||||
Event(EV_KEY, 8, 1),
|
||||
Event(EV_KEY, 8, 0),
|
||||
# normal keystroke
|
||||
Event(EV_KEY, 9, 1),
|
||||
Event(EV_KEY, 9, 0),
|
||||
# normal keystrokes
|
||||
Event(EV_ABS, ABS_HAT0X, 1),
|
||||
Event(EV_ABS, ABS_HAT0X, 0),
|
||||
# just pass those over without modifying
|
||||
Event(EV_KEY, 10, 1),
|
||||
Event(EV_KEY, 10, 0),
|
||||
|
@ -40,6 +40,7 @@ from keymapper.state import custom_mapping, system_mapping, \
|
||||
from keymapper.paths import CONFIG, get_config_path
|
||||
from keymapper.config import config
|
||||
from keymapper.dev.reader import keycode_reader
|
||||
from keymapper.gtk.row import to_string
|
||||
|
||||
from tests.test import tmp, pending_events, Event, uinput_write_history_pipe, \
|
||||
clear_write_history
|
||||
@ -176,6 +177,11 @@ class TestIntegration(unittest.TestCase):
|
||||
self.assertIsNotNone(self.window)
|
||||
self.assertTrue(self.window.window.get_visible())
|
||||
|
||||
def test_row_keycode_to_string(self):
|
||||
# not an integration test, but I have all the row tests here already
|
||||
self.assertEqual(to_string(EV_KEY, 10), '9')
|
||||
self.assertEqual(to_string(EV_KEY, 39), 'SEMICOLON')
|
||||
|
||||
def test_row_simple(self):
|
||||
rows = self.window.get('key_list').get_children()
|
||||
self.assertEqual(len(rows), 1)
|
||||
@ -185,9 +191,15 @@ class TestIntegration(unittest.TestCase):
|
||||
row.set_new_keycode(None, None)
|
||||
self.assertIsNone(row.get_keycode())
|
||||
self.assertEqual(len(custom_mapping), 0)
|
||||
self.assertEqual(row.keycode_input.get_label(), None)
|
||||
|
||||
row.set_new_keycode(EV_KEY, 30)
|
||||
self.assertEqual(len(custom_mapping), 0)
|
||||
self.assertEqual(row.get_keycode(), (EV_KEY, 30))
|
||||
# this is KEY_A in linux/input-event-codes.h,
|
||||
# but KEY_ is removed from the text
|
||||
self.assertEqual(row.keycode_input.get_label(), 'A')
|
||||
|
||||
row.set_new_keycode(EV_KEY, 30)
|
||||
self.assertEqual(len(custom_mapping), 0)
|
||||
self.assertEqual(row.get_keycode(), (EV_KEY, 30))
|
||||
@ -208,8 +220,20 @@ class TestIntegration(unittest.TestCase):
|
||||
self.assertEqual(row.get_keycode(), (EV_KEY, 30))
|
||||
|
||||
def change_empty_row(self, code, char, code_first=True, success=True):
|
||||
"""Modify the one empty row that always exists."""
|
||||
# this is not a test, it's a utility function for other tests.
|
||||
"""Modify the one empty row that always exists.
|
||||
|
||||
Utility function for other tests.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code_first : boolean
|
||||
If True, the code is entered and then the character.
|
||||
If False, the character is entered first.
|
||||
success : boolean
|
||||
If this change on the empty row is going to result in a change
|
||||
in the mapping eventually. False if this change is going to
|
||||
cause a duplicate.
|
||||
"""
|
||||
# wait for the window to create a new empty row if needed
|
||||
time.sleep(0.1)
|
||||
gtk_iteration()
|
||||
|
38
tests/testcases/test_keycode_mapper.py
Normal file
38
tests/testcases/test_keycode_mapper.py
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2020 sezanzeb <proxima@hip70890b.de>
|
||||
#
|
||||
# This file is part of key-mapper.
|
||||
#
|
||||
# key-mapper is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# key-mapper is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY 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 key-mapper. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, KEY_A, ABS_X, EV_REL, REL_X
|
||||
|
||||
from keymapper.dev.keycode_mapper import should_map_event_as_btn
|
||||
|
||||
|
||||
class TestKeycodeMapper(unittest.TestCase):
|
||||
def test_should_map_event_as_btn(self):
|
||||
self.assertTrue(should_map_event_as_btn(EV_ABS, ABS_HAT0X))
|
||||
self.assertTrue(should_map_event_as_btn(EV_KEY, KEY_A))
|
||||
self.assertFalse(should_map_event_as_btn(EV_ABS, ABS_X))
|
||||
self.assertFalse(should_map_event_as_btn(EV_REL, REL_X))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -20,7 +20,7 @@
|
||||
|
||||
|
||||
import unittest
|
||||
from evdev.events import EV_KEY
|
||||
from evdev.events import EV_KEY, EV_ABS
|
||||
|
||||
from keymapper.mapping import Mapping
|
||||
from keymapper.state import populate_system_mapping
|
||||
@ -81,15 +81,15 @@ class TestMapping(unittest.TestCase):
|
||||
self.assertEqual(self.mapping.get_character(EV_KEY, 2), 'a')
|
||||
self.assertEqual(len(self.mapping), 1)
|
||||
|
||||
# change 2 to 3 and change a to b
|
||||
self.mapping.change((EV_KEY, 3), 'b', (EV_KEY, 2))
|
||||
# change KEY 2 to ABS 16 and change a to b
|
||||
self.mapping.change((EV_ABS, 16), 'b', (EV_KEY, 2))
|
||||
self.assertIsNone(self.mapping.get_character(EV_KEY, 2))
|
||||
self.assertEqual(self.mapping.get_character(EV_KEY, 3), 'b')
|
||||
self.assertEqual(self.mapping.get_character(EV_ABS, 16), 'b')
|
||||
self.assertEqual(len(self.mapping), 1)
|
||||
|
||||
# add 4
|
||||
self.mapping.change((EV_KEY, 4), 'c', (None, None))
|
||||
self.assertEqual(self.mapping.get_character(EV_KEY, 3), 'b')
|
||||
self.assertEqual(self.mapping.get_character(EV_ABS, 16), 'b')
|
||||
self.assertEqual(self.mapping.get_character(EV_KEY, 4), 'c')
|
||||
self.assertEqual(len(self.mapping), 2)
|
||||
|
||||
@ -109,10 +109,23 @@ class TestMapping(unittest.TestCase):
|
||||
self.assertEqual(len(self.mapping), 2)
|
||||
|
||||
# non-int keycodes are ignored
|
||||
# TODO what about non-int types?
|
||||
self.mapping.change((EV_KEY, 'b'), 'c', (EV_KEY, 'a'))
|
||||
self.mapping.change((EV_KEY, 'b'), 'c')
|
||||
self.mapping.change(('foo', 1), 'c', ('foo', 2))
|
||||
self.mapping.change(('foo', 1), 'c')
|
||||
self.assertEqual(len(self.mapping), 2)
|
||||
|
||||
def test_change_2(self):
|
||||
self.mapping.change((EV_KEY, 2), 'a')
|
||||
|
||||
self.mapping.change((None, 2), 'b', (EV_KEY, 2))
|
||||
self.assertEqual(self.mapping.get_character(EV_KEY, 2), 'a')
|
||||
|
||||
self.mapping.change((EV_KEY, None), 'c', (EV_KEY, 2))
|
||||
self.assertEqual(self.mapping.get_character(EV_KEY, 2), 'a')
|
||||
|
||||
self.assertEqual(len(self.mapping), 1)
|
||||
|
||||
def test_clear(self):
|
||||
# does nothing
|
||||
self.mapping.clear(EV_KEY, 40)
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
import unittest
|
||||
|
||||
from evdev.events import EV_KEY
|
||||
from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X
|
||||
import time
|
||||
|
||||
from keymapper.dev.reader import keycode_reader
|
||||
@ -34,6 +34,17 @@ CODE_2 = 101
|
||||
CODE_3 = 102
|
||||
|
||||
|
||||
def wait(func, timeout=1.0):
|
||||
"""Wait for func to return True."""
|
||||
iterations = 0
|
||||
sleepytime = 0.1
|
||||
while not func():
|
||||
time.sleep(sleepytime)
|
||||
iterations += 1
|
||||
if iterations * sleepytime > timeout:
|
||||
break
|
||||
|
||||
|
||||
class TestReader(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# verify that tearDown properly cleared the reader
|
||||
@ -45,10 +56,10 @@ class TestReader(unittest.TestCase):
|
||||
for key in keys:
|
||||
del pending_events[key]
|
||||
|
||||
def test_reading(self):
|
||||
def test_reading_1(self):
|
||||
pending_events['device 1'] = [
|
||||
Event(EV_KEY, CODE_1, 1),
|
||||
Event(EV_KEY, CODE_2, 1),
|
||||
Event(EV_ABS, ABS_HAT0X, 1),
|
||||
Event(EV_KEY, CODE_3, 1)
|
||||
]
|
||||
keycode_reader.start_reading('device 1')
|
||||
@ -56,10 +67,18 @@ class TestReader(unittest.TestCase):
|
||||
# sending anything arbitrary does not stop the pipe
|
||||
keycode_reader._pipe[0].send((EV_KEY, 1234))
|
||||
|
||||
time.sleep(EVENT_READ_TIMEOUT * 5)
|
||||
wait(keycode_reader._pipe[0].poll, 0.5)
|
||||
|
||||
self.assertEqual(keycode_reader.read(), (EV_KEY, CODE_3))
|
||||
self.assertEqual(keycode_reader.read(), (None, None))
|
||||
|
||||
def test_reading_2(self):
|
||||
pending_events['device 1'] = [Event(EV_ABS, ABS_HAT0X, 1)]
|
||||
keycode_reader.start_reading('device 1')
|
||||
wait(keycode_reader._pipe[0].poll, 0.5)
|
||||
self.assertEqual(keycode_reader.read(), (EV_ABS, ABS_HAT0X))
|
||||
self.assertEqual(keycode_reader.read(), (None, None))
|
||||
|
||||
def test_wrong_device(self):
|
||||
pending_events['device 1'] = [
|
||||
Event(EV_KEY, CODE_1, 1),
|
||||
|
Loading…
Reference in New Issue
Block a user