mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-20 03:25:43 +00:00
add event type to mapping key
This commit is contained in:
parent
5fe404dd2d
commit
edf71e82e0
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user