add event type to mapping key

This commit is contained in:
sezanzeb 2020-12-02 19:33:31 +01:00 committed by sezanzeb
parent 5fe404dd2d
commit edf71e82e0
14 changed files with 180 additions and 116 deletions

View File

@ -41,8 +41,13 @@ INITIAL_CONFIG = {
'keystroke_sleep_ms': 10
},
'gamepad': {
'joystick': {
'non_linearity': 4,
'pointer_speed': 80
'pointer_speed': 80,
},
'triggers': {
'button_threshold': 0.5
},
}
}
@ -52,10 +57,15 @@ class _Config:
self._config = {}
self.load_config()
def _resolve(self, path, func):
def _resolve(self, path, func, config=None):
"""Call func for the given config value."""
chunks = path.split('.')
if config is None:
child = self._config
else:
child = config
while True:
chunk = chunks.pop(0)
parent = child
@ -97,19 +107,27 @@ class _Config:
self._resolve(path, do)
def get(self, path, default=None):
"""Get a config value.
def get(self, path, log_unknown=True):
"""Get a config value. If not set, return the default
Parameters
----------
path : string
For example 'macros.keystroke_sleep_ms'
default : any
If the configured value is not available or None, return this
instead
log_unknown : bool
If True, write an error.
"""
resolved = self._resolve(path, lambda parent, child, chunk: child)
return resolved if resolved is not None else default
def do(parent, child, chunk):
return child
resolved = self._resolve(path, do)
if resolved is None:
resolved = self._resolve(path, do, INITIAL_CONFIG)
if resolved is None and log_unknown:
logger.error('Unknown config key "%s"', path)
return resolved
def set_autoload_preset(self, device, preset):
"""Set a preset to be automatically applied on start.
@ -118,7 +136,7 @@ class _Config:
----------
device : string
preset : string or None
if None, don't autoload something for this device
if None, don't autoload something for this device.
"""
if preset is not None:
self.set(f'autoload.{device}', preset)
@ -131,7 +149,7 @@ class _Config:
def is_autoloaded(self, device, preset):
"""Should this preset be loaded automatically?"""
return self.get(f'autoload.{device}') == preset
return self.get(f'autoload.{device}', '') == preset
def load_config(self):
"""Load the config from the file system."""
@ -139,7 +157,9 @@ class _Config:
if not os.path.exists(CONFIG_PATH):
# treated like an empty config
logger.debug('Config file "%s" doesn\'t exist', CONFIG_PATH)
logger.debug('Config "%s" doesn\'t exist yet', CONFIG_PATH)
self.clear_config()
self.save_config()
return
with open(CONFIG_PATH, 'r') as file:

View File

@ -57,8 +57,8 @@ async def ev_abs_mapper(abs_state, input_device, keymapper_device):
max_value = input_device.absinfo(EV_ABS).max
max_speed = ((max_value ** 2) * 2) ** 0.5
pointer_speed = config.get('gamepad.pointer_speed', 80)
non_linearity = config.get('gamepad.non_linearity', 4)
pointer_speed = config.get('gamepad.joystick.pointer_speed')
non_linearity = config.get('gamepad.joystick.non_linearity')
while True:
start = time.time()

View File

@ -119,6 +119,13 @@ class KeycodeInjector:
self._process = multiprocessing.Process(target=self._start_injecting)
self._process.start()
def map_ev_to_abs(self, capabilities):
"""Check if joystick movements can and should be mapped."""
# mapping buttons only works without ABS events in the capabilities,
# possibly due to some intentional constraints in the os. So always
# do this without the option to configure, if it is possible.
return evdev.ecodes.ABS_X in capabilities.get(EV_ABS, [])
def _prepare_device(self, path):
"""Try to grab the device, return if not needed/possible.
@ -135,12 +142,13 @@ class KeycodeInjector:
needed = False
if capabilities.get(EV_KEY) is not None:
for keycode, _ in self.mapping:
if keycode - KEYCODE_OFFSET in capabilities[EV_KEY]:
for (ev_type, keycode), _ in self.mapping:
# TEST ev_type
if keycode - KEYCODE_OFFSET in capabilities.get(ev_type, []):
needed = True
break
map_ev_abs = evdev.ecodes.ABS_X in capabilities.get(EV_ABS, [])
map_ev_abs = self.map_ev_to_abs(capabilities)
if map_ev_abs:
needed = True
@ -194,10 +202,10 @@ class KeycodeInjector:
if len(self.mapping) > 0 and capabilities.get(ecodes.EV_KEY) is None:
capabilities[ecodes.EV_KEY] = []
for _, character in self.mapping:
for (ev_type, _), character in self.mapping:
keycode = system_mapping.get(character)
if keycode is not None:
capabilities[ecodes.EV_KEY].append(keycode - KEYCODE_OFFSET)
capabilities[ev_type].append(keycode - KEYCODE_OFFSET)
if map_ev_abs:
del capabilities[ecodes.EV_ABS]
@ -320,7 +328,7 @@ class KeycodeInjector:
# Parse all macros beforehand
logger.debug('Parsing macros')
macros = {}
for keycode, output in self.mapping:
for (ev_type, keycode), output in self.mapping:
keycode -= KEYCODE_OFFSET
if '(' in output and ')' in output and len(output) >= 4:

View File

@ -114,7 +114,7 @@ class _Macro:
def add_keycode_pause(self):
"""To add a pause between keystrokes."""
sleeptime = config.get('macros.keystroke_sleep_ms', 10) / 1000
sleeptime = config.get('macros.keystroke_sleep_ms') / 1000
async def sleep():
await asyncio.sleep(sleeptime)

View File

@ -83,6 +83,7 @@ class _GetDevices(threading.Thread):
# https://www.kernel.org/doc/html/latest/input/event-codes.html
capabilities = device.capabilities().keys()
if EV_KEY not in capabilities and EV_ABS not in capabilities:
# or gamepads, because they can be mapped like a keyboard
continue
usb = device.phys.split('/')[0]

View File

@ -27,6 +27,8 @@ gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0')
from gi.repository import Gtk, GLib
from evdev.ecodes import EV_KEY
from keymapper.state import custom_mapping
from keymapper.logger import logger
@ -76,7 +78,7 @@ class Row(Gtk.ListBoxRow):
return
# keycode is already set by some other row
if custom_mapping.get_character(new_keycode) is not None:
if custom_mapping.get_character(EV_KEY, new_keycode) is not None:
msg = f'Keycode {new_keycode} is already mapped'
logger.info(msg)
self.window.get('status_bar').push(CTX_KEYCODE, msg)
@ -98,7 +100,7 @@ class Row(Gtk.ListBoxRow):
return
# else, the keycode has changed, the character is set, all good
custom_mapping.change(new_keycode, character, previous_keycode)
custom_mapping.change(EV_KEY, new_keycode, character, previous_keycode)
def highlight(self):
"""Mark this row as changed."""
@ -116,7 +118,7 @@ class Row(Gtk.ListBoxRow):
self.highlight()
if keycode is not None:
custom_mapping.change(
custom_mapping.change(EV_KEY,
previous_keycode=None,
new_keycode=keycode,
character=character
@ -178,7 +180,7 @@ class Row(Gtk.ListBoxRow):
"""Destroy the row and remove it from the config."""
keycode = self.get_keycode()
if keycode is not None:
custom_mapping.clear(keycode)
custom_mapping.clear(EV_KEY, keycode)
self.character_input.set_text('')
self.keycode_input.set_label('')
self.delete_callback(self)

View File

@ -374,7 +374,7 @@ class Window:
custom_mapping.load(self.selected_device, self.selected_preset)
key_list = self.get('key_list')
for keycode, output in custom_mapping:
for (_, keycode), output in custom_mapping:
single_key_mapping = Row(
window=self,
delete_callback=self.on_row_removed,

View File

@ -25,6 +25,7 @@
import os
import json
import copy
import evdev
from keymapper.logger import logger
from keymapper.paths import get_config_path, touch
@ -50,16 +51,18 @@ class Mapping:
def __len__(self):
return len(self._mapping)
def change(self, new_keycode, character, previous_keycode=None):
def change(self, ev_type, new_keycode, character, previous_keycode=None):
"""Replace the mapping of a keycode with a different one.
Return True on success.
Parameters
----------
ev_type : int
one of evdev.events. The original event
new_keycode : int
The source keycode, what the mouse would report without any
modification.
modification. xkb keycode.
character : string or string[]
A single character known to xkb, Examples: KP_1, Shift_L, a, B.
Can also be an array, which is used for reading the xkbmap output
@ -67,7 +70,7 @@ class Mapping:
previous_keycode : int or None
If None, will not remove any previous mapping. If you recently
used 10 for new_keycode and want to overwrite that with 11,
provide 5 here.
provide 5 here. xkb keycode.
"""
try:
new_keycode = int(new_keycode)
@ -78,25 +81,28 @@ class Mapping:
return False
if new_keycode and character:
self._mapping[new_keycode] = character
self._mapping[(ev_type, new_keycode)] = character
if new_keycode != previous_keycode:
# clear previous mapping of that code, because the line
# representing that one will now represent a different one.
self.clear(previous_keycode)
self.clear(ev_type, previous_keycode)
self.changed = True
return True
return False
def clear(self, keycode):
def clear(self, ev_type, keycode):
"""Remove a keycode from the mapping.
Parameters
----------
keycode : int
the xkb keycode
ev_type : int
one of evdev.events
"""
if self._mapping.get(keycode) is not None:
del self._mapping[keycode]
if self._mapping.get((ev_type, keycode)) is not None:
del self._mapping[(ev_type, keycode)]
self.changed = True
def empty(self):
@ -119,13 +125,23 @@ class Mapping:
logger.error('Invalid preset config at "%s"', path)
return
for keycode, character in preset_dict['mapping'].items():
for key, character in preset_dict['mapping'].items():
if ',' not in key:
logger.error('Found invalid key: "%s"', key)
continue
ev_type, keycode = key.split(',')
try:
keycode = int(keycode)
except ValueError:
logger.error('Found non-int keycode: %s', keycode)
logger.error('Found non-int keycode: "%s"', keycode)
continue
self._mapping[keycode] = character
try:
ev_type = int(ev_type)
except ValueError:
logger.error('Found non-int ev_type: "%s"', ev_type)
continue
self._mapping[(ev_type, keycode)] = character
# add any metadata of the mapping
for key in preset_dict:
@ -152,18 +168,27 @@ class Mapping:
with open(path, 'w') as file:
# make sure to keep the option to add metadata if ever needed,
# so put the mapping into a special key
preset_dict = {'mapping': self._mapping}
json_ready_mapping = {}
# tuple keys are not possible in json
for key, value in self._mapping.items():
new_key = f'{key[0]},{key[1]}'
json_ready_mapping[new_key] = value
preset_dict = {'mapping': json_ready_mapping}
preset_dict.update(self.config)
json.dump(preset_dict, file, indent=4)
file.write('\n')
self.changed = False
def get_character(self, keycode):
def get_character(self, ev_type, keycode):
"""Read the character that is mapped to this keycode.
Parameters
----------
keycode : int
ev_type : int
one of evdev.events. codes may be the same for various
event types.
"""
return self._mapping.get(keycode)
return self._mapping.get((ev_type, keycode))

View File

@ -30,9 +30,15 @@ class TestConfig(unittest.TestCase):
self.assertEqual(len(config.iterate_autoload_presets()), 0)
config.save_config()
def test_get_default(self):
config._config = {}
self.assertEqual(config.get('gamepad.joystick.non_linearity'), 4)
config.set('gamepad.joystick.non_linearity', 3)
self.assertEqual(config.get('gamepad.joystick.non_linearity'), 3)
def test_basic(self):
self.assertEqual(config.get('a'), None)
self.assertEqual(config.get('a', 'foo'), 'foo')
config.set('a', 1)
self.assertEqual(config.get('a'), 1)

View File

@ -25,6 +25,7 @@ import unittest
import time
import evdev
from evdev.ecodes import EV_KEY
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
@ -84,8 +85,8 @@ class TestDaemon(unittest.TestCase):
keycode_from_2 = 12
keycode_to_2 = 100
custom_mapping.change(keycode_from_1, 'a')
custom_mapping.change(keycode_from_2, 'b')
custom_mapping.change(EV_KEY, keycode_from_1, 'a')
custom_mapping.change(EV_KEY, keycode_from_2, 'b')
clear_system_mapping()
system_mapping['a'] = keycode_to_1
system_mapping['b'] = keycode_to_2

View File

@ -76,7 +76,7 @@ class TestInjector(unittest.TestCase):
}
mapping = Mapping()
mapping.change(
mapping.change(EV_KEY,
new_keycode=80,
character='a'
)
@ -99,7 +99,7 @@ class TestInjector(unittest.TestCase):
def test_grab(self):
# path is from the fixtures
custom_mapping.change(10, 'a')
custom_mapping.change(EV_KEY, 10, 'a')
self.injector = KeycodeInjector('device 1', custom_mapping)
path = '/dev/input/event10'
@ -124,7 +124,7 @@ class TestInjector(unittest.TestCase):
def test_skip_unused_device(self):
# skips a device because its capabilities are not used in the mapping
custom_mapping.change(10, 'a')
custom_mapping.change(EV_KEY, 10, 'a')
self.injector = KeycodeInjector('device 1', custom_mapping)
path = '/dev/input/event11'
device, map_ev_abs = self.injector._prepare_device(path)
@ -166,11 +166,9 @@ class TestInjector(unittest.TestCase):
def test_abs_to_rel(self):
# maps gamepad joystick events to mouse events
# TODO enable this somewhere so that map_ev_abs returns true
# in the .json file of the mapping.
config.set('gamepad.non_linearity', 1)
config.set('gamepad.joystick.non_linearity', 1)
pointer_speed = 80
config.set('gamepad.pointer_speed', pointer_speed)
config.set('gamepad.joystick.pointer_speed', pointer_speed)
# same for ABS, 0 for x, 1 for y
rel_x = evdev.ecodes.REL_X
@ -286,11 +284,11 @@ class TestInjector(unittest.TestCase):
self.assertIn(('b', 0), history)
def test_injector(self):
custom_mapping.change(8, 'k(KEY_Q).k(w)')
custom_mapping.change(9, 'a')
custom_mapping.change(EV_KEY, 8, 'k(KEY_Q).k(w)')
custom_mapping.change(EV_KEY, 9, 'a')
# one mapping that is unknown in the system_mapping on purpose
input_b = 10
custom_mapping.change(input_b, 'b')
custom_mapping.change(EV_KEY, input_b, 'b')
clear_system_mapping()
code_a = 100

View File

@ -24,6 +24,7 @@ import time
import os
import unittest
import evdev
from evdev.events import EV_KEY
import json
from unittest.mock import patch
from importlib.util import spec_from_loader, module_from_spec
@ -156,9 +157,9 @@ class TestIntegration(unittest.TestCase):
def test_select_device(self):
# creates a new empty preset when no preset exists for the device
self.window.on_select_device(FakeDropdown('device 1'))
custom_mapping.change(50, 'q')
custom_mapping.change(51, 'u')
custom_mapping.change(52, 'x')
custom_mapping.change(EV_KEY, 50, 'q')
custom_mapping.change(EV_KEY, 51, 'u')
custom_mapping.change(EV_KEY, 52, 'x')
self.assertEqual(len(custom_mapping), 3)
self.window.on_select_device(FakeDropdown('device 2'))
self.assertEqual(len(custom_mapping), 0)
@ -202,7 +203,7 @@ class TestIntegration(unittest.TestCase):
gtk_iteration()
self.assertEqual(len(self.window.get('key_list').get_children()), 2)
self.assertEqual(custom_mapping.get_character(30), 'Shift_L')
self.assertEqual(custom_mapping.get_character(EV_KEY, 30), 'Shift_L')
self.assertEqual(row.get_character(), 'Shift_L')
self.assertEqual(row.get_keycode(), 30)
@ -270,8 +271,8 @@ class TestIntegration(unittest.TestCase):
time.sleep(0.1)
self.assertEqual(len(self.get_rows()), num_rows_target)
self.assertEqual(custom_mapping.get_character(10), 'a')
self.assertEqual(custom_mapping.get_character(11), 'k(b).k(c)')
self.assertEqual(custom_mapping.get_character(EV_KEY, 10), 'a')
self.assertEqual(custom_mapping.get_character(EV_KEY, 11), 'k(b).k(c)')
self.assertTrue(custom_mapping.changed)
self.window.on_save_preset_clicked(None)
@ -293,13 +294,13 @@ class TestIntegration(unittest.TestCase):
row.get_style_context().list_classes()
)
self.assertEqual(custom_mapping.get_character(10), 'c')
self.assertEqual(custom_mapping.get_character(11), 'k(b).k(c)')
self.assertEqual(custom_mapping.get_character(EV_KEY, 10), 'c')
self.assertEqual(custom_mapping.get_character(EV_KEY, 11), 'k(b).k(c)')
self.assertTrue(custom_mapping.changed)
# try to add a duplicate keycode, it should be ignored
self.change_empty_row(11, 'd', success=False)
self.assertEqual(custom_mapping.get_character(11), 'k(b).k(c)')
self.assertEqual(custom_mapping.get_character(EV_KEY, 11), 'k(b).k(c)')
# and the number of rows shouldn't change
self.assertEqual(len(self.get_rows()), num_rows_target)
@ -316,11 +317,11 @@ class TestIntegration(unittest.TestCase):
gtk_iteration()
self.assertEqual(len(self.get_rows()), 3)
self.assertEqual(custom_mapping.get_character(11), 'b')
self.assertEqual(custom_mapping.get_character(EV_KEY, 11), 'b')
def remove(row, code, char, num_rows_after):
if code is not None and char is not None:
self.assertEqual(custom_mapping.get_character(code), char)
self.assertEqual(custom_mapping.get_character(EV_KEY, code), char)
self.assertEqual(row.get_character(), char)
self.assertEqual(row.get_keycode(), code)
row.on_delete_button_clicked()
@ -328,7 +329,7 @@ class TestIntegration(unittest.TestCase):
gtk_iteration()
self.assertIsNone(row.get_keycode())
self.assertIsNone(row.get_character())
self.assertIsNone(custom_mapping.get_character(code))
self.assertIsNone(custom_mapping.get_character(EV_KEY, code))
self.assertEqual(len(self.get_rows()), num_rows_after)
remove(row_1, 10, 'a', 2)
@ -339,17 +340,17 @@ class TestIntegration(unittest.TestCase):
remove(row_3, None, 'c', 1)
def test_rename_and_save(self):
custom_mapping.change(14, 'a', None)
custom_mapping.change(EV_KEY, 14, 'a', None)
self.assertEqual(self.window.selected_preset, 'new preset')
self.window.on_save_preset_clicked(None)
self.assertEqual(custom_mapping.get_character(14), 'a')
self.assertEqual(custom_mapping.get_character(EV_KEY, 14), 'a')
custom_mapping.change(14, 'b', None)
custom_mapping.change(EV_KEY, 14, 'b', None)
self.window.get('preset_name_input').set_text('asdf')
self.window.on_save_preset_clicked(None)
self.assertEqual(self.window.selected_preset, 'asdf')
self.assertTrue(os.path.exists(f'{CONFIG}/device 1/asdf.json'))
self.assertEqual(custom_mapping.get_character(14), 'b')
self.assertEqual(custom_mapping.get_character(EV_KEY, 14), 'b')
def test_select_device_and_preset(self):
# created on start because the first device is selected and some empty
@ -379,7 +380,7 @@ class TestIntegration(unittest.TestCase):
gtk_iteration()
self.assertEqual(self.window.selected_preset, 'new preset')
self.assertFalse(os.path.exists(f'{CONFIG}/device 1/abc 123.json'))
custom_mapping.change(10, '1', None)
custom_mapping.change(EV_KEY, 10, '1', None)
self.window.on_save_preset_clicked(None)
gtk_iteration()
self.assertEqual(self.window.selected_preset, 'abc 123')

View File

@ -57,7 +57,7 @@ class TestMacros(unittest.TestCase):
repeats = 20
macro = f'r({repeats}, k(k))'
self.loop.run_until_complete(parse(macro, self.handler).run())
keystroke_sleep = config.get('macros.keystroke_sleep_ms', 10)
keystroke_sleep = config.get('macros.keystroke_sleep_ms')
sleep_time = 2 * repeats * keystroke_sleep / 1000
self.assertGreater(time.time() - start, sleep_time * 0.9)
self.assertLess(time.time() - start, sleep_time * 1.1)
@ -68,7 +68,7 @@ class TestMacros(unittest.TestCase):
macro = 'r(3, k(m).w(100))'
self.loop.run_until_complete(parse(macro, self.handler).run())
keystroke_time = 6 * config.get('macros.keystroke_sleep_ms', 10)
keystroke_time = 6 * config.get('macros.keystroke_sleep_ms')
total_time = keystroke_time + 300
total_time /= 1000
@ -97,7 +97,7 @@ class TestMacros(unittest.TestCase):
self.loop.run_until_complete(parse(macro, self.handler).run())
num_pauses = 8 + 6 + 4
keystroke_time = num_pauses * config.get('macros.keystroke_sleep_ms', 10)
keystroke_time = num_pauses * config.get('macros.keystroke_sleep_ms')
wait_time = 220
total_time = (keystroke_time + wait_time) / 1000

View File

@ -20,6 +20,7 @@
import unittest
from evdev.events import EV_KEY
from keymapper.mapping import Mapping
from keymapper.state import populate_system_mapping
@ -31,6 +32,7 @@ class TestMapping(unittest.TestCase):
self.assertFalse(self.mapping.changed)
def test_populate_system_mapping(self):
# not actually a mapping object, just a dict
mapping = populate_system_mapping()
self.assertGreater(len(mapping), 100)
# xkb keycode 10 is typically mapped to '1'
@ -41,20 +43,20 @@ class TestMapping(unittest.TestCase):
def test_clone(self):
mapping1 = Mapping()
mapping1.change(1, 'a')
mapping1.change(EV_KEY, 1, 'a')
mapping2 = mapping1.clone()
mapping1.change(2, 'b')
mapping1.change(EV_KEY, 2, 'b')
self.assertEqual(mapping1.get_character(1), 'a')
self.assertEqual(mapping1.get_character(2), 'b')
self.assertEqual(mapping1.get_character(EV_KEY, 1), 'a')
self.assertEqual(mapping1.get_character(EV_KEY, 2), 'b')
self.assertEqual(mapping2.get_character(1), 'a')
self.assertIsNone(mapping2.get_character(2))
self.assertEqual(mapping2.get_character(EV_KEY, 1), 'a')
self.assertIsNone(mapping2.get_character(EV_KEY, 2))
def test_save_load(self):
self.mapping.change(10, '1')
self.mapping.change(11, '2')
self.mapping.change(12, '3')
self.mapping.change(EV_KEY, 10, '1')
self.mapping.change(EV_KEY, 11, '2')
self.mapping.change(EV_KEY, 12, '3')
self.mapping.config['foo'] = 'bar'
self.mapping.save('device 1', 'test')
@ -63,77 +65,77 @@ class TestMapping(unittest.TestCase):
loaded.load('device 1', 'test')
self.assertEqual(len(loaded), 3)
self.assertEqual(loaded.get_character(10), '1')
self.assertEqual(loaded.get_character(11), '2')
self.assertEqual(loaded.get_character(12), '3')
self.assertEqual(loaded.get_character(EV_KEY, 10), '1')
self.assertEqual(loaded.get_character(EV_KEY, 11), '2')
self.assertEqual(loaded.get_character(EV_KEY, 12), '3')
self.assertEqual(loaded.config['foo'], 'bar')
def test_change(self):
# 1 is not assigned yet, ignore it
self.mapping.change(2, 'a', 1)
self.mapping.change(EV_KEY, 2, 'a', 1)
self.assertTrue(self.mapping.changed)
self.assertIsNone(self.mapping.get_character(1))
self.assertEqual(self.mapping.get_character(2), 'a')
self.assertIsNone(self.mapping.get_character(EV_KEY, 1))
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(3, 'b', 2)
self.assertIsNone(self.mapping.get_character(2))
self.assertEqual(self.mapping.get_character(3), 'b')
self.mapping.change(EV_KEY, 3, 'b', 2)
self.assertIsNone(self.mapping.get_character(EV_KEY, 2))
self.assertEqual(self.mapping.get_character(EV_KEY, 3), 'b')
self.assertEqual(len(self.mapping), 1)
# add 4
self.mapping.change(4, 'c', None)
self.assertEqual(self.mapping.get_character(3), 'b')
self.assertEqual(self.mapping.get_character(4), 'c')
self.mapping.change(EV_KEY, 4, 'c', None)
self.assertEqual(self.mapping.get_character(EV_KEY, 3), 'b')
self.assertEqual(self.mapping.get_character(EV_KEY, 4), 'c')
self.assertEqual(len(self.mapping), 2)
# change the mapping of 4 to d
self.mapping.change(4, 'd', None)
self.assertEqual(self.mapping.get_character(4), 'd')
self.mapping.change(EV_KEY, 4, 'd', None)
self.assertEqual(self.mapping.get_character(EV_KEY, 4), 'd')
self.assertEqual(len(self.mapping), 2)
# this also works in the same way
self.mapping.change(4, 'e', 4)
self.assertEqual(self.mapping.get_character(4), 'e')
self.mapping.change(EV_KEY, 4, 'e', 4)
self.assertEqual(self.mapping.get_character(EV_KEY, 4), 'e')
self.assertEqual(len(self.mapping), 2)
# and this
self.mapping.change('4', 'f', '4')
self.assertEqual(self.mapping.get_character(4), 'f')
self.mapping.change(EV_KEY, '4', 'f', '4')
self.assertEqual(self.mapping.get_character(EV_KEY, 4), 'f')
self.assertEqual(len(self.mapping), 2)
# non-int keycodes are ignored
self.mapping.change('b', 'c', 'a')
self.mapping.change(EV_KEY, 'b', 'c', 'a')
self.assertEqual(len(self.mapping), 2)
def test_clear(self):
# does nothing
self.mapping.clear(40)
self.mapping.clear(EV_KEY, 40)
self.assertFalse(self.mapping.changed)
self.assertEqual(len(self.mapping), 0)
self.mapping._mapping[40] = 'b'
self.mapping._mapping[(EV_KEY, 40)] = 'b'
self.assertEqual(len(self.mapping), 1)
self.mapping.clear(40)
self.mapping.clear(EV_KEY, 40)
self.assertEqual(len(self.mapping), 0)
self.assertTrue(self.mapping.changed)
self.mapping.change(10, 'KP_1', None)
self.mapping.change(EV_KEY, 10, 'KP_1', None)
self.assertTrue(self.mapping.changed)
self.mapping.change(20, 'KP_2', None)
self.mapping.change(30, 'KP_3', None)
self.mapping.change(EV_KEY, 20, 'KP_2', None)
self.mapping.change(EV_KEY, 30, 'KP_3', None)
self.assertEqual(len(self.mapping), 3)
self.mapping.clear(20)
self.mapping.clear(EV_KEY, 20)
self.assertEqual(len(self.mapping), 2)
self.assertEqual(self.mapping.get_character(10), 'KP_1')
self.assertIsNone(self.mapping.get_character(20))
self.assertEqual(self.mapping.get_character(30), 'KP_3')
self.assertEqual(self.mapping.get_character(EV_KEY, 10), 'KP_1')
self.assertIsNone(self.mapping.get_character(EV_KEY, 20))
self.assertEqual(self.mapping.get_character(EV_KEY, 30), 'KP_3')
def test_empty(self):
self.mapping.change(10, '1')
self.mapping.change(11, '2')
self.mapping.change(12, '3')
self.mapping.change(EV_KEY, 10, '1')
self.mapping.change(EV_KEY, 11, '2')
self.mapping.change(EV_KEY, 12, '3')
self.assertEqual(len(self.mapping), 3)
self.mapping.empty()
self.assertEqual(len(self.mapping), 0)