mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-12 01:10:38 +00:00
pressed keys represented by class
This commit is contained in:
parent
d09f02d573
commit
b382a04be5
@ -98,30 +98,6 @@ def ensure_numlock(func):
|
||||
return wrapped
|
||||
|
||||
|
||||
def store_permutations(target, combination, value):
|
||||
"""Store permutations for key combinations.
|
||||
|
||||
Store every permutation of combination, while the last
|
||||
element needs to remain the last one. It is the finishing
|
||||
key. E.g. a + b is something different than b + a, but
|
||||
a + b + c is the same as b + a + c
|
||||
|
||||
a, b and c are tuples of (type, code, value)
|
||||
|
||||
If combination is not a tuple of 3-tuples, it just uses it as key without
|
||||
permutating anything.
|
||||
"""
|
||||
if not isinstance(combination, tuple):
|
||||
logger.error('Expected a tuple, but got "%s"', combination)
|
||||
return
|
||||
|
||||
if isinstance(combination[0], tuple):
|
||||
for permutation in itertools.permutations(combination[:-1]):
|
||||
target[(*permutation, combination[-1])] = value
|
||||
else:
|
||||
target[combination] = value
|
||||
|
||||
|
||||
def is_in_capabilities(key, capabilities):
|
||||
"""Are this key or all of its sub keys in the capabilities?"""
|
||||
if isinstance(key[0], tuple):
|
||||
@ -153,6 +129,7 @@ class KeycodeInjector:
|
||||
----------
|
||||
device : string
|
||||
the name of the device as available in get_device
|
||||
mapping : Mapping
|
||||
"""
|
||||
self.device = device
|
||||
self.mapping = mapping
|
||||
@ -168,7 +145,13 @@ class KeycodeInjector:
|
||||
self.abs_state = [0, 0, 0, 0]
|
||||
|
||||
def _map_keys_to_codes(self):
|
||||
"""To quickly get target keycodes during operation."""
|
||||
"""To quickly get target keycodes during operation.
|
||||
|
||||
Returns a mapping of one or more 3-tuples to ints.
|
||||
Examples:
|
||||
((1, 2, 1),): 3
|
||||
((1, 5, 1), (1, 4, 1)): 4
|
||||
"""
|
||||
key_to_code = {}
|
||||
for key, output in self.mapping:
|
||||
if is_this_a_macro(output):
|
||||
@ -179,7 +162,8 @@ class KeycodeInjector:
|
||||
logger.error('Don\'t know what %s is', output)
|
||||
continue
|
||||
|
||||
store_permutations(key_to_code, key, target_code)
|
||||
for permutation in key.get_permutations():
|
||||
key_to_code[permutation.keys] = target_code
|
||||
|
||||
return key_to_code
|
||||
|
||||
@ -368,7 +352,8 @@ class KeycodeInjector:
|
||||
if macro is None:
|
||||
continue
|
||||
|
||||
store_permutations(macros, key, macro)
|
||||
for permutation in key.get_permutations():
|
||||
macros[permutation.keys] = macro
|
||||
|
||||
if len(macros) == 0:
|
||||
logger.debug('No macros configured')
|
||||
|
@ -31,6 +31,7 @@ from evdev.events import EV_KEY, EV_ABS
|
||||
|
||||
from keymapper.logger import logger
|
||||
from keymapper.util import sign
|
||||
from keymapper.key import Key
|
||||
from keymapper.getdevices import get_devices, refresh_devices
|
||||
from keymapper.dev.keycode_mapper import should_map_event_as_btn
|
||||
|
||||
@ -60,7 +61,7 @@ class _KeycodeReader:
|
||||
Does not serve any purpose for the injection service.
|
||||
|
||||
When a button was pressed, the newest keycode can be obtained from this
|
||||
object. GTK has get_keycode for keyboard keys, but KeycodeReader also
|
||||
object. GTK has get_key for keyboard keys, but KeycodeReader also
|
||||
has knowledge of buttons like the middle-mouse button.
|
||||
"""
|
||||
def __init__(self):
|
||||
@ -197,7 +198,7 @@ class _KeycodeReader:
|
||||
return len(self._unreleased) > 0
|
||||
|
||||
def read(self):
|
||||
"""Get the newest tuple of event type, keycode or None.
|
||||
"""Get the newest key as Key object
|
||||
|
||||
If the timing of two recent events is very close, prioritize
|
||||
key events over abs events.
|
||||
@ -253,12 +254,8 @@ class _KeycodeReader:
|
||||
|
||||
self.newest_event = newest_event
|
||||
|
||||
if len(self._unreleased) > 1:
|
||||
# a combination
|
||||
return tuple(self._unreleased.values())
|
||||
elif len(self._unreleased) == 1:
|
||||
# a single key
|
||||
return list(self._unreleased.values())[0]
|
||||
if len(self._unreleased) > 0:
|
||||
return Key(*self._unreleased.values())
|
||||
else:
|
||||
# nothing
|
||||
return None
|
||||
|
@ -27,6 +27,7 @@ from gi.repository import Gtk, GLib, Gdk
|
||||
|
||||
from keymapper.state import custom_mapping, system_mapping
|
||||
from keymapper.logger import logger
|
||||
from keymapper.key import Key
|
||||
|
||||
|
||||
CTX_KEYCODE = 2
|
||||
@ -39,38 +40,45 @@ for name in system_mapping.list_names():
|
||||
|
||||
def to_string(key):
|
||||
"""A nice to show description of the pressed key."""
|
||||
if isinstance(key[0], tuple):
|
||||
if isinstance(key, Key):
|
||||
return ' + '.join([to_string(sub_key) for sub_key in key])
|
||||
elif isinstance(key[0], tuple):
|
||||
raise Exception('deprecated stuff')
|
||||
|
||||
ev_type, code, value = key
|
||||
|
||||
try:
|
||||
key_name = evdev.ecodes.bytype[ev_type][code]
|
||||
if isinstance(key_name, list):
|
||||
key_name = key_name[0]
|
||||
|
||||
if ev_type != evdev.ecodes.EV_KEY:
|
||||
direction = {
|
||||
(evdev.ecodes.ABS_HAT0X, -1): 'L',
|
||||
(evdev.ecodes.ABS_HAT0X, 1): 'R',
|
||||
(evdev.ecodes.ABS_HAT0Y, -1): 'U',
|
||||
(evdev.ecodes.ABS_HAT0Y, 1): 'D',
|
||||
(evdev.ecodes.ABS_HAT1X, -1): 'L',
|
||||
(evdev.ecodes.ABS_HAT1X, 1): 'R',
|
||||
(evdev.ecodes.ABS_HAT1Y, -1): 'U',
|
||||
(evdev.ecodes.ABS_HAT1Y, 1): 'D',
|
||||
(evdev.ecodes.ABS_HAT2X, -1): 'L',
|
||||
(evdev.ecodes.ABS_HAT2X, 1): 'R',
|
||||
(evdev.ecodes.ABS_HAT2Y, -1): 'U',
|
||||
(evdev.ecodes.ABS_HAT2Y, 1): 'D',
|
||||
}.get((code, value))
|
||||
if direction is not None:
|
||||
key_name += f' {direction}'
|
||||
|
||||
return key_name.replace('KEY_', '')
|
||||
except KeyError:
|
||||
if ev_type not in evdev.ecodes.bytype:
|
||||
logger.error('Unknown key type for %s', key)
|
||||
return 'unknown'
|
||||
|
||||
if code not in evdev.ecodes.bytype[ev_type]:
|
||||
logger.error('Unknown key code for', key)
|
||||
return 'unknown'
|
||||
|
||||
key_name = evdev.ecodes.bytype[ev_type][code]
|
||||
if isinstance(key_name, list):
|
||||
key_name = key_name[0]
|
||||
|
||||
if ev_type != evdev.ecodes.EV_KEY:
|
||||
direction = {
|
||||
(evdev.ecodes.ABS_HAT0X, -1): 'L',
|
||||
(evdev.ecodes.ABS_HAT0X, 1): 'R',
|
||||
(evdev.ecodes.ABS_HAT0Y, -1): 'U',
|
||||
(evdev.ecodes.ABS_HAT0Y, 1): 'D',
|
||||
(evdev.ecodes.ABS_HAT1X, -1): 'L',
|
||||
(evdev.ecodes.ABS_HAT1X, 1): 'R',
|
||||
(evdev.ecodes.ABS_HAT1Y, -1): 'U',
|
||||
(evdev.ecodes.ABS_HAT1Y, 1): 'D',
|
||||
(evdev.ecodes.ABS_HAT2X, -1): 'L',
|
||||
(evdev.ecodes.ABS_HAT2X, 1): 'R',
|
||||
(evdev.ecodes.ABS_HAT2Y, -1): 'U',
|
||||
(evdev.ecodes.ABS_HAT2Y, 1): 'D',
|
||||
}.get((code, value))
|
||||
if direction is not None:
|
||||
key_name += f' {direction}'
|
||||
|
||||
return key_name.replace('KEY_', '')
|
||||
|
||||
|
||||
IDLE = 0
|
||||
HOLDING = 1
|
||||
@ -85,9 +93,11 @@ class Row(Gtk.ListBoxRow):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
key : int, int, int
|
||||
event, code, value
|
||||
key : Key
|
||||
"""
|
||||
if key is not None and not isinstance(key, Key):
|
||||
raise TypeError('Expected key to be a Key object')
|
||||
|
||||
super().__init__()
|
||||
self.device = window.selected_device
|
||||
self.window = window
|
||||
@ -111,8 +121,8 @@ class Row(Gtk.ListBoxRow):
|
||||
window = self.window.window
|
||||
GLib.idle_add(lambda: window.set_focus(self.character_input))
|
||||
|
||||
def get_keycode(self):
|
||||
"""Get a tuple of type, code and value from the left column.
|
||||
def get_key(self):
|
||||
"""Get the Key object from the left column.
|
||||
|
||||
Or None if no code is mapped on this row.
|
||||
"""
|
||||
@ -123,11 +133,19 @@ class Row(Gtk.ListBoxRow):
|
||||
character = self.character_input.get_text()
|
||||
return character if character else None
|
||||
|
||||
def set_new_keycode(self, new_key):
|
||||
"""Check if a keycode has been pressed and if so, display it."""
|
||||
def set_new_key(self, new_key):
|
||||
"""Check if a keycode has been pressed and if so, display it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_key : Key
|
||||
"""
|
||||
if new_key is not None and not isinstance(new_key, Key):
|
||||
raise TypeError('Expected new_key to be a Key object')
|
||||
|
||||
# the newest_keycode is populated since the ui regularly polls it
|
||||
# in order to display it in the status bar.
|
||||
previous_key = self.get_keycode()
|
||||
previous_key = self.get_key()
|
||||
|
||||
# no input
|
||||
if new_key is None:
|
||||
@ -182,7 +200,7 @@ class Row(Gtk.ListBoxRow):
|
||||
|
||||
def on_character_input_change(self, _):
|
||||
"""When the output character for that keycode is typed in."""
|
||||
key = self.get_keycode()
|
||||
key = self.get_key()
|
||||
character = self.get_character()
|
||||
|
||||
if character is None:
|
||||
@ -204,7 +222,7 @@ class Row(Gtk.ListBoxRow):
|
||||
|
||||
def show_click_here(self):
|
||||
"""Show 'click here' on the keycode input button."""
|
||||
if self.get_keycode() is not None:
|
||||
if self.get_key() is not None:
|
||||
return
|
||||
|
||||
self.set_keycode_input_label('click here')
|
||||
@ -212,7 +230,7 @@ class Row(Gtk.ListBoxRow):
|
||||
|
||||
def show_press_key(self):
|
||||
"""Show 'press key' on the keycode input button."""
|
||||
if self.get_keycode() is not None:
|
||||
if self.get_key() is not None:
|
||||
return
|
||||
|
||||
self.set_keycode_input_label('press key')
|
||||
@ -311,7 +329,7 @@ class Row(Gtk.ListBoxRow):
|
||||
|
||||
def on_delete_button_clicked(self, *args):
|
||||
"""Destroy the row and remove it from the config."""
|
||||
key = self.get_keycode()
|
||||
key = self.get_key()
|
||||
if key is not None:
|
||||
custom_mapping.clear(key)
|
||||
|
||||
|
@ -364,7 +364,7 @@ class Window:
|
||||
# inform the currently selected row about the new keycode
|
||||
row, focused = self.get_focused_row()
|
||||
if isinstance(focused, Gtk.ToggleButton):
|
||||
row.set_new_keycode(key)
|
||||
row.set_new_key(key)
|
||||
|
||||
return True
|
||||
|
||||
|
128
keymapper/key.py
Normal file
128
keymapper/key.py
Normal file
@ -0,0 +1,128 @@
|
||||
#!/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/>.
|
||||
|
||||
|
||||
"""A button or a key combination."""
|
||||
|
||||
|
||||
import itertools
|
||||
|
||||
from keymapper.util import sign
|
||||
|
||||
|
||||
def verify(key):
|
||||
"""Check if the key is an int 3-tuple of type, code, value"""
|
||||
if not isinstance(key, tuple) or len(key) != 3:
|
||||
raise ValueError(f'Expected key to be a 3-tuple, but got {key}')
|
||||
if sum([not isinstance(value, int) for value in key]) != 0:
|
||||
raise ValueError(f'Can only use numbers, but got {key}')
|
||||
|
||||
|
||||
class Key:
|
||||
"""Represents one or more pressed down keys.
|
||||
|
||||
Can be used in hashmaps/dicts as key
|
||||
"""
|
||||
def __init__(self, *keys):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
Takes an arbitrary number of tuples as arguments. Each one should
|
||||
be in the format of
|
||||
0: type, one of evdev.events, taken from the original source
|
||||
event. Everything will be mapped to EV_KEY.
|
||||
1: The source keycode, what the mouse would report without any
|
||||
modification.
|
||||
2. The value. 1 (down), 0 (up) or any
|
||||
other value that the device reports. Gamepads use a continuous
|
||||
space of values for joysticks and triggers.
|
||||
|
||||
or Key objects, which will flatten all of them into one combination
|
||||
"""
|
||||
if len(keys) == 0:
|
||||
raise ValueError('At least one key is required')
|
||||
|
||||
if isinstance(keys[0], int):
|
||||
# type, code, value was provided instead of a tuple
|
||||
keys = (keys,)
|
||||
|
||||
# multiple objects of Key get flattened into one tuple
|
||||
flattened = ()
|
||||
for key in keys:
|
||||
if isinstance(key, Key):
|
||||
flattened += key.keys
|
||||
else:
|
||||
flattened += (key,)
|
||||
keys = flattened
|
||||
|
||||
for key in keys:
|
||||
verify(key)
|
||||
|
||||
self.keys = tuple(keys)
|
||||
self.release = (*self.keys[-1][:2], 0)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.keys)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.keys[item]
|
||||
|
||||
def __len__(self):
|
||||
"""Get the number of pressed down kes."""
|
||||
return len(self.keys)
|
||||
|
||||
def __str__(self):
|
||||
return f'Key{str(self.keys)}'
|
||||
|
||||
def __hash__(self):
|
||||
if len(self.keys) == 1:
|
||||
return hash(self.keys[0])
|
||||
|
||||
return hash(self.keys)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, tuple):
|
||||
if isinstance(other[0], tuple):
|
||||
# a combination ((1, 5, 1), (1, 3, 1))
|
||||
return self.keys == other
|
||||
|
||||
# otherwise, self needs to represent a single key as well
|
||||
return len(self.keys) == 1 and self.keys[0] == other
|
||||
|
||||
if not isinstance(other, Key):
|
||||
return False
|
||||
|
||||
# compare two instances of Key
|
||||
return self.keys == other.keys
|
||||
|
||||
def get_permutations(self):
|
||||
"""Get a list of Key objects representing all possible permutations.
|
||||
|
||||
combining a + b + c should have the same result as b + a + c.
|
||||
Only the last key remains the same in the returned result.
|
||||
"""
|
||||
if len(self.keys) <= 2:
|
||||
return [self]
|
||||
|
||||
permutations = []
|
||||
for permutation in itertools.permutations(self.keys[:-1]):
|
||||
permutations.append(Key(*permutation, self.keys[-1]))
|
||||
|
||||
return permutations
|
@ -30,28 +30,13 @@ import copy
|
||||
from keymapper.logger import logger
|
||||
from keymapper.paths import touch
|
||||
from keymapper.config import ConfigBase, config
|
||||
|
||||
|
||||
def verify_key(key):
|
||||
"""Check if the key describes a tuple or tuples of (type, code, value).
|
||||
|
||||
For combinations it could be e.g. ((1, 2, 1), (1, 3, 1)).
|
||||
"""
|
||||
if not isinstance(key, tuple):
|
||||
raise ValueError(f'Expected keys to be a 3-tuple, but got {key}')
|
||||
|
||||
if isinstance(key[0], tuple):
|
||||
for sub_key in key:
|
||||
verify_key(sub_key)
|
||||
else:
|
||||
if len(key) != 3:
|
||||
raise ValueError(f'Expected key to be a 3-tuple, but got {key}')
|
||||
if sum([not isinstance(value, int) for value in key]) != 0:
|
||||
raise ValueError(f'Can only use numbers, but got {key}')
|
||||
from keymapper.key import Key
|
||||
|
||||
|
||||
def split_key(key):
|
||||
"""Take a key like "1,2,3" and return a 3-tuple of ints."""
|
||||
key = key.strip()
|
||||
|
||||
if ',' not in key:
|
||||
logger.error('Found invalid key: "%s"', key)
|
||||
return None
|
||||
@ -79,12 +64,12 @@ def split_key(key):
|
||||
class Mapping(ConfigBase):
|
||||
"""Contains and manages mappings and config of a single preset."""
|
||||
def __init__(self):
|
||||
self._mapping = {}
|
||||
self._mapping = {} # a mapping of Key objects to strings
|
||||
self.changed = False
|
||||
super().__init__(fallback=config)
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over tuples of unique keycodes and their character."""
|
||||
"""Iterate over Key objects and their character."""
|
||||
return iter(self._mapping.items())
|
||||
|
||||
def __len__(self):
|
||||
@ -105,33 +90,23 @@ class Mapping(ConfigBase):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_key : int, int, int
|
||||
the new key. (type, code, value). key as in hashmap-key
|
||||
|
||||
0: type, one of evdev.events, taken from the original source
|
||||
event. Everything will be mapped to EV_KEY.
|
||||
1: The source keycode, what the mouse would report without any
|
||||
modification.
|
||||
2. The value. 1 (down), 2 (up) or any
|
||||
other value that the device reports. Gamepads use a continuous
|
||||
space of values for joysticks and triggers.
|
||||
new_key : Key
|
||||
character : string
|
||||
A single character known to xkb or linux.
|
||||
Examples: KP_1, Shift_L, a, B, BTN_LEFT.
|
||||
previous_key : int, int, int
|
||||
the previous key, same format as new_key
|
||||
previous_key : Key or None
|
||||
the previous key
|
||||
|
||||
If not set, will not remove any previous mapping. If you recently
|
||||
used (1, 10, 1) for new_key and want to overwrite that with
|
||||
(1, 11, 1), provide (1, 5, 1) here.
|
||||
"""
|
||||
if not isinstance(new_key, Key):
|
||||
raise TypeError(f'Expected {new_key} to be a Key object')
|
||||
|
||||
if character is None:
|
||||
raise ValueError('Expected `character` not to be None')
|
||||
|
||||
verify_key(new_key)
|
||||
if previous_key:
|
||||
verify_key(previous_key)
|
||||
|
||||
logger.debug(
|
||||
'%s will map to %s, replacing %s',
|
||||
new_key, character, previous_key
|
||||
@ -153,31 +128,16 @@ class Mapping(ConfigBase):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
key : int, int, int
|
||||
keycode : int
|
||||
ev_type : int
|
||||
one of evdev.events. codes may be the same for various
|
||||
event types.
|
||||
value : int
|
||||
event value. Usually you want 1 (down)
|
||||
key : Key
|
||||
"""
|
||||
verify_key(key)
|
||||
if not isinstance(key, Key):
|
||||
raise TypeError('Expected key to be a Key object')
|
||||
|
||||
if isinstance(key[0], tuple):
|
||||
for permutation in itertools.permutations(key[:-1]):
|
||||
permutation += (key[-1],)
|
||||
if permutation in self._mapping:
|
||||
logger.debug('%s will be cleared', permutation)
|
||||
del self._mapping[permutation]
|
||||
return
|
||||
|
||||
if self._mapping.get(key) is not None:
|
||||
logger.debug('%s will be cleared', key)
|
||||
del self._mapping[key]
|
||||
self.changed = True
|
||||
return
|
||||
|
||||
logger.error('Unknown key %s', key)
|
||||
for permutation in key.get_permutations():
|
||||
if permutation in self._mapping:
|
||||
logger.debug('%s will be cleared', permutation)
|
||||
del self._mapping[permutation]
|
||||
self.changed = True
|
||||
|
||||
def empty(self):
|
||||
"""Remove all mappings."""
|
||||
@ -208,15 +168,17 @@ class Mapping(ConfigBase):
|
||||
return
|
||||
|
||||
for key, character in preset_dict['mapping'].items():
|
||||
if '+' in key:
|
||||
chunks = key.split('+')
|
||||
key = tuple([split_key(chunk) for chunk in chunks])
|
||||
if None in key:
|
||||
continue
|
||||
else:
|
||||
key = split_key(key)
|
||||
if key is None:
|
||||
continue
|
||||
try:
|
||||
key = Key(*[
|
||||
split_key(chunk) for chunk in key.split('+')
|
||||
if chunk.strip() != ''
|
||||
])
|
||||
except ValueError as error:
|
||||
logger.error(str(error))
|
||||
continue
|
||||
|
||||
if None in key:
|
||||
continue
|
||||
|
||||
logger.spam('%s maps to %s', key, character)
|
||||
self._mapping[key] = character
|
||||
@ -254,17 +216,13 @@ class Mapping(ConfigBase):
|
||||
json_ready_mapping = {}
|
||||
# tuple keys are not possible in json, encode them as string
|
||||
for key, value in self._mapping.items():
|
||||
if isinstance(key[0], tuple):
|
||||
# combinations to "1,2,1+1,3,1"
|
||||
new_key = '+'.join([
|
||||
','.join([
|
||||
str(value)
|
||||
for value in sub_key
|
||||
])
|
||||
for sub_key in key
|
||||
new_key = '+'.join([
|
||||
','.join([
|
||||
str(value)
|
||||
for value in sub_key
|
||||
])
|
||||
else:
|
||||
new_key = ','.join([str(value) for value in key])
|
||||
for sub_key in key
|
||||
])
|
||||
json_ready_mapping[new_key] = value
|
||||
|
||||
preset_dict['mapping'] = json_ready_mapping
|
||||
@ -278,23 +236,12 @@ class Mapping(ConfigBase):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
key : int, int, int
|
||||
keycode : int
|
||||
ev_type : int
|
||||
one of evdev.events. codes may be the same for various
|
||||
event types.
|
||||
value : int
|
||||
event value. Usually you want 1 (down)
|
||||
|
||||
Or a tuple of multiple of those. Checks any possible permutation
|
||||
with the last key being always at the end, to work well with
|
||||
combinations.
|
||||
key : Key
|
||||
"""
|
||||
if isinstance(key[0], tuple):
|
||||
for permutation in itertools.permutations(key[:-1]):
|
||||
permutation += (key[-1],)
|
||||
existing = self._mapping.get(permutation)
|
||||
if existing is not None:
|
||||
return existing
|
||||
if not isinstance(key, Key):
|
||||
raise TypeError('Expected key to be a Key object')
|
||||
|
||||
return self._mapping.get(key)
|
||||
for permutation in key.get_permutations():
|
||||
existing = self._mapping.get(permutation)
|
||||
if existing is not None:
|
||||
return existing
|
||||
|
@ -34,6 +34,7 @@ from keymapper.state import custom_mapping, system_mapping
|
||||
from keymapper.config import config
|
||||
from keymapper.getdevices import get_devices
|
||||
from keymapper.paths import get_preset_path
|
||||
from keymapper.key import Key
|
||||
from keymapper.daemon import Daemon, get_dbus_interface, BUS_NAME
|
||||
|
||||
from tests.test import cleanup, uinput_write_history_pipe, InputEvent, \
|
||||
@ -125,8 +126,8 @@ class TestDaemon(unittest.TestCase):
|
||||
|
||||
device = 'device 2'
|
||||
|
||||
custom_mapping.change((*ev_1, 1), 'a')
|
||||
custom_mapping.change((*ev_2, -1), 'b')
|
||||
custom_mapping.change(Key(*ev_1, 1), 'a')
|
||||
custom_mapping.change(Key(*ev_2, -1), 'b')
|
||||
|
||||
system_mapping.clear()
|
||||
system_mapping._set('a', keycode_to_1)
|
||||
@ -186,7 +187,7 @@ class TestDaemon(unittest.TestCase):
|
||||
device = '9876 name'
|
||||
# this test only makes sense if this device is unknown yet
|
||||
self.assertIsNone(get_devices().get(device))
|
||||
custom_mapping.change((*ev, 1), 'a')
|
||||
custom_mapping.change(Key(*ev, 1), 'a')
|
||||
system_mapping.clear()
|
||||
system_mapping._set('a', keycode_to)
|
||||
preset = 'foo'
|
||||
@ -231,7 +232,7 @@ class TestDaemon(unittest.TestCase):
|
||||
|
||||
path = get_preset_path(device, preset)
|
||||
|
||||
custom_mapping.change(event, to_name)
|
||||
custom_mapping.change(Key(event), to_name)
|
||||
custom_mapping.save(path)
|
||||
|
||||
system_mapping.clear()
|
||||
|
@ -27,10 +27,11 @@ import evdev
|
||||
from evdev.ecodes import EV_REL, EV_KEY, EV_ABS, ABS_HAT0X, BTN_A
|
||||
|
||||
from keymapper.dev.injector import is_numlock_on, set_numlock, \
|
||||
ensure_numlock, KeycodeInjector, store_permutations, is_in_capabilities
|
||||
ensure_numlock, KeycodeInjector, is_in_capabilities
|
||||
from keymapper.state import custom_mapping, system_mapping
|
||||
from keymapper.mapping import Mapping
|
||||
from keymapper.config import config
|
||||
from keymapper.key import Key
|
||||
from keymapper.dev.macros import parse
|
||||
|
||||
from tests.test import InputEvent, pending_events, fixtures, \
|
||||
@ -76,16 +77,16 @@ class TestInjector(unittest.TestCase):
|
||||
}
|
||||
|
||||
mapping = Mapping()
|
||||
mapping.change((EV_KEY, 80, 1), 'a')
|
||||
mapping.change(Key(EV_KEY, 80, 1), 'a')
|
||||
|
||||
macro_code = 'r(2, m(sHiFt_l, r(2, k(1).k(2))))'
|
||||
macro = parse(macro_code, mapping)
|
||||
|
||||
mapping.change((EV_KEY, 60, 111), macro_code)
|
||||
mapping.change(Key(EV_KEY, 60, 111), macro_code)
|
||||
|
||||
# going to be ignored, because EV_REL cannot be mapped, that's
|
||||
# mouse movements.
|
||||
mapping.change((EV_REL, 1234, 3), 'b')
|
||||
mapping.change(Key(EV_REL, 1234, 3), 'b')
|
||||
|
||||
a = system_mapping.get('a')
|
||||
shift_l = system_mapping.get('ShIfT_L')
|
||||
@ -114,7 +115,7 @@ class TestInjector(unittest.TestCase):
|
||||
|
||||
def test_grab(self):
|
||||
# path is from the fixtures
|
||||
custom_mapping.change((EV_KEY, 10, 1), 'a')
|
||||
custom_mapping.change(Key(EV_KEY, 10, 1), 'a')
|
||||
|
||||
self.injector = KeycodeInjector('device 1', custom_mapping)
|
||||
path = '/dev/input/event10'
|
||||
@ -128,7 +129,7 @@ class TestInjector(unittest.TestCase):
|
||||
|
||||
def test_fail_grab(self):
|
||||
self.make_it_fail = 10
|
||||
custom_mapping.change((EV_KEY, 10, 1), 'a')
|
||||
custom_mapping.change(Key(EV_KEY, 10, 1), 'a')
|
||||
|
||||
self.injector = KeycodeInjector('device 1', custom_mapping)
|
||||
path = '/dev/input/event10'
|
||||
@ -145,7 +146,7 @@ class TestInjector(unittest.TestCase):
|
||||
|
||||
def test_prepare_device_1(self):
|
||||
# according to the fixtures, /dev/input/event30 can do ABS_HAT0X
|
||||
custom_mapping.change((EV_ABS, ABS_HAT0X, 1), 'a')
|
||||
custom_mapping.change(Key(EV_ABS, ABS_HAT0X, 1), 'a')
|
||||
self.injector = KeycodeInjector('foobar', custom_mapping)
|
||||
|
||||
_prepare_device = self.injector._prepare_device
|
||||
@ -153,7 +154,7 @@ class TestInjector(unittest.TestCase):
|
||||
self.assertIsNotNone(_prepare_device('/dev/input/event30')[0])
|
||||
|
||||
def test_prepare_device_non_existing(self):
|
||||
custom_mapping.change((EV_ABS, ABS_HAT0X, 1), 'a')
|
||||
custom_mapping.change(Key(EV_ABS, ABS_HAT0X, 1), 'a')
|
||||
self.injector = KeycodeInjector('foobar', custom_mapping)
|
||||
|
||||
_prepare_device = self.injector._prepare_device
|
||||
@ -224,7 +225,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((EV_KEY, 10, 1), 'a')
|
||||
custom_mapping.change(Key(EV_KEY, 10, 1), 'a')
|
||||
self.injector = KeycodeInjector('device 1', custom_mapping)
|
||||
path = '/dev/input/event11'
|
||||
device, abs_to_rel = self.injector._prepare_device(path)
|
||||
@ -332,12 +333,12 @@ class TestInjector(unittest.TestCase):
|
||||
|
||||
numlock_before = is_numlock_on()
|
||||
|
||||
combination = ((EV_KEY, 8, 1), (EV_KEY, 9, 1))
|
||||
combination = Key((EV_KEY, 8, 1), (EV_KEY, 9, 1))
|
||||
custom_mapping.change(combination, 'k(KEY_Q).k(w)')
|
||||
custom_mapping.change((EV_ABS, ABS_HAT0X, -1), 'a')
|
||||
custom_mapping.change(Key(EV_ABS, ABS_HAT0X, -1), 'a')
|
||||
# one mapping that is unknown in the system_mapping on purpose
|
||||
input_b = 10
|
||||
custom_mapping.change((EV_KEY, input_b, 1), 'b')
|
||||
custom_mapping.change(Key(EV_KEY, input_b, 1), 'b')
|
||||
|
||||
# stuff the custom_mapping outputs (except for the unknown b)
|
||||
system_mapping.clear()
|
||||
@ -429,42 +430,13 @@ class TestInjector(unittest.TestCase):
|
||||
numlock_after = is_numlock_on()
|
||||
self.assertEqual(numlock_before, numlock_after)
|
||||
|
||||
def test_store_permutations(self):
|
||||
target = {}
|
||||
|
||||
store_permutations(target, ((1,), (2,), (3,), (4,)), 1234)
|
||||
self.assertEqual(len(target), 6)
|
||||
self.assertEqual(target[((1,), (2,), (3,), (4,))], 1234)
|
||||
self.assertEqual(target[((1,), (3,), (2,), (4,))], 1234)
|
||||
self.assertEqual(target[((2,), (1,), (3,), (4,))], 1234)
|
||||
self.assertEqual(target[((2,), (3,), (1,), (4,))], 1234)
|
||||
self.assertEqual(target[((3,), (1,), (2,), (4,))], 1234)
|
||||
self.assertEqual(target[((3,), (2,), (1,), (4,))], 1234)
|
||||
|
||||
store_permutations(target, ((1,), (2,)), 5678)
|
||||
self.assertEqual(len(target), 7)
|
||||
self.assertEqual(target[((1,), (2,))], 5678)
|
||||
|
||||
store_permutations(target, ((1,),), 3456)
|
||||
self.assertEqual(len(target), 8)
|
||||
self.assertEqual(target[((1,),)], 3456)
|
||||
|
||||
store_permutations(target, (1,), 7890)
|
||||
self.assertEqual(len(target), 9)
|
||||
self.assertEqual(target[(1,)], 7890)
|
||||
|
||||
# only accepts tuples, because key-mapper always uses tuples
|
||||
# for this stuff
|
||||
store_permutations(target, 1, 1357)
|
||||
self.assertEqual(len(target), 9)
|
||||
|
||||
def test_store_permutations_for_macros(self):
|
||||
mapping = Mapping()
|
||||
ev_1 = (EV_KEY, 41, 1)
|
||||
ev_2 = (EV_KEY, 42, 1)
|
||||
ev_3 = (EV_KEY, 43, 1)
|
||||
# a combination
|
||||
mapping.change((ev_1, ev_2, ev_3), 'k(a)')
|
||||
mapping.change(Key(ev_1, ev_2, ev_3), 'k(a)')
|
||||
self.injector = KeycodeInjector('device 1', mapping)
|
||||
|
||||
history = []
|
||||
@ -496,17 +468,17 @@ class TestInjector(unittest.TestCase):
|
||||
ev_2 = (EV_KEY, 42, 1)
|
||||
ev_3 = (EV_KEY, 43, 1)
|
||||
ev_4 = (EV_KEY, 44, 1)
|
||||
mapping.change(ev_1, 'a')
|
||||
mapping.change(Key(ev_1), 'a')
|
||||
# a combination
|
||||
mapping.change((ev_2, ev_3, ev_4), 'b')
|
||||
self.assertEqual(mapping.get_character((ev_2, ev_3, ev_4)), 'b')
|
||||
mapping.change(Key(ev_2, ev_3, ev_4), 'b')
|
||||
self.assertEqual(mapping.get_character(Key(ev_2, ev_3, ev_4)), 'b')
|
||||
|
||||
system_mapping.clear()
|
||||
system_mapping._set('a', 51)
|
||||
system_mapping._set('b', 52)
|
||||
|
||||
injector = KeycodeInjector('device 1', mapping)
|
||||
self.assertEqual(injector._key_to_code.get(ev_1), 51)
|
||||
self.assertEqual(injector._key_to_code.get((ev_1,)), 51)
|
||||
# permutations to make matching combinations easier
|
||||
self.assertEqual(injector._key_to_code.get((ev_2, ev_3, ev_4)), 52)
|
||||
self.assertEqual(injector._key_to_code.get((ev_3, ev_2, ev_4)), 52)
|
||||
|
@ -42,6 +42,7 @@ from keymapper.config import config, WHEEL, MOUSE
|
||||
from keymapper.dev.reader import keycode_reader
|
||||
from keymapper.gtk.row import to_string, HOLDING, IDLE
|
||||
from keymapper.dev import permissions
|
||||
from keymapper.key import Key
|
||||
|
||||
from tests.test import tmp, pending_events, InputEvent, \
|
||||
uinput_write_history_pipe, cleanup
|
||||
@ -206,9 +207,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((EV_KEY, 50, 1), 'q')
|
||||
custom_mapping.change((EV_KEY, 51, 1), 'u')
|
||||
custom_mapping.change((EV_KEY, 52, 1), 'x')
|
||||
custom_mapping.change(Key(EV_KEY, 50, 1), 'q')
|
||||
custom_mapping.change(Key(EV_KEY, 51, 1), 'u')
|
||||
custom_mapping.change(Key(EV_KEY, 52, 1), 'x')
|
||||
self.assertEqual(len(custom_mapping), 3)
|
||||
self.window.on_select_device(FakeDropdown('device 2'))
|
||||
self.assertEqual(len(custom_mapping), 0)
|
||||
@ -227,14 +228,14 @@ class TestIntegration(unittest.TestCase):
|
||||
|
||||
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, evdev.ecodes.KEY_9, 1)), '9')
|
||||
self.assertEqual(to_string((EV_KEY, evdev.ecodes.KEY_SEMICOLON, 1)), 'SEMICOLON')
|
||||
self.assertEqual(to_string((EV_ABS, evdev.ecodes.ABS_HAT0X, -1)), 'ABS_HAT0X L')
|
||||
self.assertEqual(to_string((EV_ABS, evdev.ecodes.ABS_HAT0X, 1)), 'ABS_HAT0X R')
|
||||
self.assertEqual(to_string((EV_KEY, evdev.ecodes.BTN_A, 1)), 'BTN_A')
|
||||
self.assertEqual(to_string(Key(EV_KEY, evdev.ecodes.KEY_9, 1)), '9')
|
||||
self.assertEqual(to_string(Key(EV_KEY, evdev.ecodes.KEY_SEMICOLON, 1)), 'SEMICOLON')
|
||||
self.assertEqual(to_string(Key(EV_ABS, evdev.ecodes.ABS_HAT0X, -1)), 'ABS_HAT0X L')
|
||||
self.assertEqual(to_string(Key(EV_ABS, evdev.ecodes.ABS_HAT0X, 1)), 'ABS_HAT0X R')
|
||||
self.assertEqual(to_string(Key(EV_KEY, evdev.ecodes.BTN_A, 1)), 'BTN_A')
|
||||
|
||||
# combinations
|
||||
self.assertEqual(to_string((
|
||||
self.assertEqual(to_string(Key(
|
||||
(EV_KEY, evdev.ecodes.BTN_A, 1),
|
||||
(EV_KEY, evdev.ecodes.BTN_B, 1),
|
||||
(EV_KEY, evdev.ecodes.BTN_C, 1)
|
||||
@ -246,21 +247,21 @@ class TestIntegration(unittest.TestCase):
|
||||
|
||||
row = rows[0]
|
||||
|
||||
row.set_new_keycode(None)
|
||||
self.assertIsNone(row.get_keycode())
|
||||
row.set_new_key(None)
|
||||
self.assertIsNone(row.get_key())
|
||||
self.assertEqual(len(custom_mapping), 0)
|
||||
self.assertEqual(row.keycode_input.get_label(), 'click here')
|
||||
|
||||
row.set_new_keycode((EV_KEY, 30, 1))
|
||||
row.set_new_key(Key(EV_KEY, 30, 1))
|
||||
self.assertEqual(len(custom_mapping), 0)
|
||||
self.assertEqual(row.get_keycode(), (EV_KEY, 30, 1))
|
||||
self.assertEqual(row.get_key(), (EV_KEY, 30, 1))
|
||||
# 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, 1))
|
||||
row.set_new_key(Key(EV_KEY, 30, 1))
|
||||
self.assertEqual(len(custom_mapping), 0)
|
||||
self.assertEqual(row.get_keycode(), (EV_KEY, 30, 1))
|
||||
self.assertEqual(row.get_key(), (EV_KEY, 30, 1))
|
||||
|
||||
time.sleep(0.1)
|
||||
gtk_iteration()
|
||||
@ -273,9 +274,9 @@ class TestIntegration(unittest.TestCase):
|
||||
gtk_iteration()
|
||||
self.assertEqual(len(self.window.get('key_list').get_children()), 2)
|
||||
|
||||
self.assertEqual(custom_mapping.get_character((EV_KEY, 30, 1)), 'Shift_L')
|
||||
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 30, 1)), 'Shift_L')
|
||||
self.assertEqual(row.get_character(), 'Shift_L')
|
||||
self.assertEqual(row.get_keycode(), (EV_KEY, 30, 1))
|
||||
self.assertEqual(row.get_key(), (EV_KEY, 30, 1))
|
||||
|
||||
def change_empty_row(self, key, char, code_first=True, expect_success=True):
|
||||
"""Modify the one empty row that always exists.
|
||||
@ -284,9 +285,7 @@ class TestIntegration(unittest.TestCase):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
key : int, int, int
|
||||
type, code, value,
|
||||
or a tuple of multiple of those
|
||||
key : Key or None
|
||||
code_first : boolean
|
||||
If True, the code is entered and then the character.
|
||||
If False, the character is entered first.
|
||||
@ -304,7 +303,7 @@ class TestIntegration(unittest.TestCase):
|
||||
# find the empty row
|
||||
rows = self.get_rows()
|
||||
row = rows[-1]
|
||||
self.assertIsNone(row.get_keycode())
|
||||
self.assertIsNone(row.get_key())
|
||||
self.assertEqual(row.character_input.get_text(), '')
|
||||
self.assertNotIn('changed', row.get_style_context().list_classes())
|
||||
self.assertEqual(row.state, IDLE)
|
||||
@ -322,7 +321,7 @@ class TestIntegration(unittest.TestCase):
|
||||
|
||||
self.window.window.set_focus(row.keycode_input)
|
||||
gtk_iteration()
|
||||
self.assertIsNone(row.get_keycode())
|
||||
self.assertIsNone(row.get_key())
|
||||
self.assertEqual(row.keycode_input.get_label(), 'press key')
|
||||
|
||||
if key:
|
||||
@ -330,11 +329,8 @@ class TestIntegration(unittest.TestCase):
|
||||
# but by sending an event. Events should be consumed 30 times
|
||||
# per second, so sleep a bit more than 0.033ms each time
|
||||
# press down all the keys of a combination
|
||||
if isinstance(key[0], tuple):
|
||||
for sub_key in key:
|
||||
keycode_reader._pipe[1].send(InputEvent(*sub_key))
|
||||
else:
|
||||
keycode_reader._pipe[1].send(InputEvent(*key))
|
||||
for sub_key in key:
|
||||
keycode_reader._pipe[1].send(InputEvent(*sub_key))
|
||||
|
||||
# make the window consume the keycode
|
||||
time.sleep(0.05)
|
||||
@ -346,11 +342,8 @@ class TestIntegration(unittest.TestCase):
|
||||
self.assertTrue(row.keycode_input.is_focus())
|
||||
|
||||
# release all the keys
|
||||
if isinstance(key[0], tuple):
|
||||
for sub_key in key:
|
||||
keycode_reader._pipe[1].send(InputEvent(*sub_key[:2], 0))
|
||||
else:
|
||||
keycode_reader._pipe[1].send(InputEvent(*key[:2], 0))
|
||||
for sub_key in key:
|
||||
keycode_reader._pipe[1].send(InputEvent(*sub_key[:2], 0))
|
||||
|
||||
# make the window consume the keycode
|
||||
time.sleep(0.05)
|
||||
@ -361,14 +354,14 @@ class TestIntegration(unittest.TestCase):
|
||||
self.assertEqual(row.state, IDLE)
|
||||
|
||||
if expect_success:
|
||||
self.assertEqual(row.get_keycode(), key)
|
||||
self.assertEqual(row.get_key(), key)
|
||||
css_classes = row.get_style_context().list_classes()
|
||||
self.assertIn('changed', css_classes)
|
||||
self.assertEqual(row.keycode_input.get_label(), to_string(key))
|
||||
self.assertFalse(row.keycode_input.is_focus())
|
||||
|
||||
if not expect_success:
|
||||
self.assertIsNone(row.get_keycode())
|
||||
self.assertIsNone(row.get_key())
|
||||
self.assertIsNone(row.get_character())
|
||||
css_classes = row.get_style_context().list_classes()
|
||||
self.assertNotIn('changed', css_classes)
|
||||
@ -388,8 +381,8 @@ class TestIntegration(unittest.TestCase):
|
||||
# how many rows there should be in the end
|
||||
num_rows_target = 3
|
||||
|
||||
ev_1 = (EV_KEY, 10, 1)
|
||||
ev_2 = (EV_ABS, evdev.ecodes.ABS_HAT0X, -1)
|
||||
ev_1 = Key(EV_KEY, 10, 1)
|
||||
ev_2 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, -1)
|
||||
|
||||
"""edit"""
|
||||
|
||||
@ -440,10 +433,10 @@ class TestIntegration(unittest.TestCase):
|
||||
|
||||
def test_hat0x(self):
|
||||
# it should be possible to add all of them
|
||||
ev_1 = (EV_ABS, evdev.ecodes.ABS_HAT0X, -1)
|
||||
ev_2 = (EV_ABS, evdev.ecodes.ABS_HAT0X, 1)
|
||||
ev_3 = (EV_ABS, evdev.ecodes.ABS_HAT0Y, -1)
|
||||
ev_4 = (EV_ABS, evdev.ecodes.ABS_HAT0Y, 1)
|
||||
ev_1 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, -1)
|
||||
ev_2 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, 1)
|
||||
ev_3 = Key(EV_ABS, evdev.ecodes.ABS_HAT0Y, -1)
|
||||
ev_4 = Key(EV_ABS, evdev.ecodes.ABS_HAT0Y, 1)
|
||||
|
||||
self.change_empty_row(ev_1, 'a')
|
||||
self.change_empty_row(ev_2, 'b')
|
||||
@ -471,20 +464,20 @@ class TestIntegration(unittest.TestCase):
|
||||
|
||||
def test_combination(self):
|
||||
# it should be possible to write a key combination
|
||||
ev_1 = (EV_KEY, evdev.ecodes.KEY_A, 1)
|
||||
ev_2 = (EV_ABS, evdev.ecodes.ABS_HAT0X, 1)
|
||||
ev_3 = (EV_KEY, evdev.ecodes.KEY_C, 1)
|
||||
ev_4 = (EV_ABS, evdev.ecodes.ABS_HAT0X, -1)
|
||||
combination_1 = (ev_1, ev_2, ev_3)
|
||||
combination_2 = (ev_2, ev_1, ev_3)
|
||||
ev_1 = Key(EV_KEY, evdev.ecodes.KEY_A, 1)
|
||||
ev_2 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, 1)
|
||||
ev_3 = Key(EV_KEY, evdev.ecodes.KEY_C, 1)
|
||||
ev_4 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, -1)
|
||||
combination_1 = Key(ev_1, ev_2, ev_3)
|
||||
combination_2 = Key(ev_2, ev_1, ev_3)
|
||||
|
||||
# same as 1, but different D-Pad direction
|
||||
combination_3 = (ev_1, ev_4, ev_3)
|
||||
combination_4 = (ev_4, ev_1, ev_3)
|
||||
combination_3 = Key(ev_1, ev_4, ev_3)
|
||||
combination_4 = Key(ev_4, ev_1, ev_3)
|
||||
|
||||
# same as 1, but the last key is different
|
||||
combination_5 = (ev_1, ev_3, ev_2)
|
||||
combination_6 = (ev_3, ev_1, ev_2)
|
||||
combination_5 = Key(ev_1, ev_3, ev_2)
|
||||
combination_6 = Key(ev_3, ev_1, ev_2)
|
||||
|
||||
self.change_empty_row(combination_1, 'a')
|
||||
self.assertEqual(custom_mapping.get_character(combination_1), 'a')
|
||||
@ -535,8 +528,8 @@ class TestIntegration(unittest.TestCase):
|
||||
"""Comprehensive test for rows 2."""
|
||||
# sleeps are added to be able to visually follow and debug the test.
|
||||
# add two rows by modifiying the one empty row that exists
|
||||
row_1 = self.change_empty_row((EV_KEY, 10, 1), 'a')
|
||||
row_2 = self.change_empty_row((EV_KEY, 11, 1), 'b')
|
||||
row_1 = self.change_empty_row(Key(EV_KEY, 10, 1), 'a')
|
||||
row_2 = self.change_empty_row(Key(EV_KEY, 11, 1), 'b')
|
||||
row_3 = self.change_empty_row(None, 'c')
|
||||
|
||||
# no empty row added because one is unfinished
|
||||
@ -544,7 +537,7 @@ class TestIntegration(unittest.TestCase):
|
||||
gtk_iteration()
|
||||
self.assertEqual(len(self.get_rows()), 3)
|
||||
|
||||
self.assertEqual(custom_mapping.get_character((EV_KEY, 11, 1)), 'b')
|
||||
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 11, 1)), 'b')
|
||||
|
||||
def remove(row, code, char, num_rows_after):
|
||||
"""Remove a row by clicking the delete button.
|
||||
@ -560,13 +553,13 @@ class TestIntegration(unittest.TestCase):
|
||||
after deleting, how many rows are expected to still be there
|
||||
"""
|
||||
if code is not None and char is not None:
|
||||
self.assertEqual(custom_mapping.get_character((EV_KEY, code, 1)), char)
|
||||
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, code, 1)), char)
|
||||
|
||||
self.assertEqual(row.get_character(), char)
|
||||
if code is None:
|
||||
self.assertIsNone(row.get_keycode())
|
||||
self.assertIsNone(row.get_key())
|
||||
else:
|
||||
self.assertEqual(row.get_keycode(), (EV_KEY, code, 1))
|
||||
self.assertEqual(row.get_key(), Key(EV_KEY, code, 1))
|
||||
|
||||
row.on_delete_button_clicked()
|
||||
time.sleep(0.2)
|
||||
@ -575,9 +568,10 @@ class TestIntegration(unittest.TestCase):
|
||||
# if a reference to the row is held somewhere and it is
|
||||
# accidentally used again, make sure to not provide any outdated
|
||||
# information that is supposed to be deleted
|
||||
self.assertIsNone(row.get_keycode())
|
||||
self.assertIsNone(row.get_key())
|
||||
self.assertIsNone(row.get_character())
|
||||
self.assertIsNone(custom_mapping.get_character((EV_KEY, code, 1)))
|
||||
if code is not None:
|
||||
self.assertIsNone(custom_mapping.get_character(Key(EV_KEY, code, 1)))
|
||||
self.assertEqual(len(self.get_rows()), num_rows_after)
|
||||
|
||||
remove(row_1, 10, 'a', 2)
|
||||
@ -588,17 +582,17 @@ class TestIntegration(unittest.TestCase):
|
||||
remove(row_3, None, 'c', 1)
|
||||
|
||||
def test_rename_and_save(self):
|
||||
custom_mapping.change((EV_KEY, 14, 1), 'a', None)
|
||||
custom_mapping.change(Key(EV_KEY, 14, 1), 'a', None)
|
||||
self.assertEqual(self.window.selected_preset, 'new preset')
|
||||
self.window.on_save_preset_clicked(None)
|
||||
self.assertEqual(custom_mapping.get_character((EV_KEY, 14, 1)), 'a')
|
||||
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 14, 1)), 'a')
|
||||
|
||||
custom_mapping.change((EV_KEY, 14, 1), 'b', None)
|
||||
custom_mapping.change(Key(EV_KEY, 14, 1), '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_PATH}/presets/device 1/asdf.json'))
|
||||
self.assertEqual(custom_mapping.get_character((EV_KEY, 14, 1)), 'b')
|
||||
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 14, 1)), 'b')
|
||||
|
||||
error_icon = self.window.get('error_status_icon')
|
||||
status = self.window.get('status_bar')
|
||||
@ -610,20 +604,20 @@ class TestIntegration(unittest.TestCase):
|
||||
status = self.window.get('status_bar')
|
||||
error_icon = self.window.get('error_status_icon')
|
||||
|
||||
custom_mapping.change((EV_KEY, 9, 1), 'k(1))', None)
|
||||
custom_mapping.change(Key(EV_KEY, 9, 1), 'k(1))', None)
|
||||
self.window.on_save_preset_clicked(None)
|
||||
tooltip = status.get_tooltip_text().lower()
|
||||
self.assertIn('brackets', tooltip)
|
||||
self.assertTrue(error_icon.get_visible())
|
||||
|
||||
custom_mapping.change((EV_KEY, 9, 1), 'k(1)', None)
|
||||
custom_mapping.change(Key(EV_KEY, 9, 1), 'k(1)', None)
|
||||
self.window.on_save_preset_clicked(None)
|
||||
tooltip = status.get_tooltip_text().lower()
|
||||
self.assertNotIn('brackets', tooltip)
|
||||
self.assertIn('saved', tooltip)
|
||||
self.assertFalse(error_icon.get_visible())
|
||||
|
||||
self.assertEqual(custom_mapping.get_character((EV_KEY, 9, 1)), 'k(1)')
|
||||
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 9, 1)), 'k(1)')
|
||||
|
||||
def test_select_device_and_preset(self):
|
||||
# created on start because the first device is selected and some empty
|
||||
@ -653,7 +647,7 @@ class TestIntegration(unittest.TestCase):
|
||||
gtk_iteration()
|
||||
self.assertEqual(self.window.selected_preset, 'new preset')
|
||||
self.assertFalse(os.path.exists(f'{CONFIG_PATH}/presets/device 1/abc 123.json'))
|
||||
custom_mapping.change((EV_KEY, 10, 1), '1', None)
|
||||
custom_mapping.change(Key(EV_KEY, 10, 1), '1', None)
|
||||
self.window.on_save_preset_clicked(None)
|
||||
gtk_iteration()
|
||||
self.assertEqual(self.window.selected_preset, 'abc 123')
|
||||
@ -706,7 +700,7 @@ class TestIntegration(unittest.TestCase):
|
||||
keycode_from = 9
|
||||
keycode_to = 200
|
||||
|
||||
self.change_empty_row((EV_KEY, keycode_from, 1), 'a')
|
||||
self.change_empty_row(Key(EV_KEY, keycode_from, 1), 'a')
|
||||
system_mapping.clear()
|
||||
system_mapping._set('a', keycode_to)
|
||||
|
||||
@ -746,7 +740,7 @@ class TestIntegration(unittest.TestCase):
|
||||
keycode_from = 16
|
||||
keycode_to = 90
|
||||
|
||||
self.change_empty_row((EV_KEY, keycode_from, 1), 't')
|
||||
self.change_empty_row(Key(EV_KEY, keycode_from, 1), 't')
|
||||
system_mapping.clear()
|
||||
system_mapping._set('t', keycode_to)
|
||||
|
||||
|
@ -24,10 +24,11 @@ import unittest
|
||||
import json
|
||||
from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, KEY_A
|
||||
|
||||
from keymapper.mapping import Mapping, verify_key
|
||||
from keymapper.mapping import Mapping
|
||||
from keymapper.state import SystemMapping, XMODMAP_FILENAME
|
||||
from keymapper.config import config
|
||||
from keymapper.paths import get_preset_path
|
||||
from keymapper.key import Key
|
||||
|
||||
from tests.test import tmp, cleanup
|
||||
|
||||
@ -133,12 +134,12 @@ class TestMapping(unittest.TestCase):
|
||||
|
||||
# setting mapping.whatever does not overwrite the mapping
|
||||
# after saving. It should be ignored.
|
||||
self.mapping.change((EV_KEY, 81, 1), 'a')
|
||||
self.mapping.change(Key(EV_KEY, 81, 1), 'a')
|
||||
self.mapping.set('mapping.a', 2)
|
||||
self.mapping.save(get_preset_path('foo', 'bar'))
|
||||
self.assertFalse(self.mapping.changed)
|
||||
self.mapping.load(get_preset_path('foo', 'bar'))
|
||||
self.assertEqual(self.mapping.get_character((EV_KEY, 81, 1)), 'a')
|
||||
self.assertEqual(self.mapping.get_character(Key(EV_KEY, 81, 1)), 'a')
|
||||
self.assertIsNone(self.mapping.get('mapping.a'))
|
||||
self.assertFalse(self.mapping.changed)
|
||||
|
||||
@ -156,8 +157,8 @@ class TestMapping(unittest.TestCase):
|
||||
self.assertEqual(self.mapping.get('d.e.f'), 3)
|
||||
|
||||
def test_clone(self):
|
||||
ev_1 = (EV_KEY, 1, 1)
|
||||
ev_2 = (EV_KEY, 2, 0)
|
||||
ev_1 = Key(EV_KEY, 1, 1)
|
||||
ev_2 = Key(EV_KEY, 2, 0)
|
||||
|
||||
mapping1 = Mapping()
|
||||
mapping1.change(ev_1, 'a')
|
||||
@ -170,17 +171,17 @@ class TestMapping(unittest.TestCase):
|
||||
self.assertEqual(mapping2.get_character(ev_1), 'a')
|
||||
self.assertIsNone(mapping2.get_character(ev_2))
|
||||
|
||||
self.assertIsNone(mapping2.get_character((EV_KEY, 2, 3)))
|
||||
self.assertIsNone(mapping2.get_character((EV_KEY, 1, 3)))
|
||||
self.assertIsNone(mapping2.get_character(Key(EV_KEY, 2, 3)))
|
||||
self.assertIsNone(mapping2.get_character(Key(EV_KEY, 1, 3)))
|
||||
|
||||
def test_save_load(self):
|
||||
one = (EV_KEY, 10, 1)
|
||||
two = (EV_KEY, 11, 1)
|
||||
three = (EV_KEY, 12, 1)
|
||||
one = Key(EV_KEY, 10, 1)
|
||||
two = Key(EV_KEY, 11, 1)
|
||||
three = Key(EV_KEY, 12, 1)
|
||||
|
||||
self.mapping.change(one, '1')
|
||||
self.mapping.change(two, '2')
|
||||
self.mapping.change((two, three), '3')
|
||||
self.mapping.change(Key(two, three), '3')
|
||||
self.mapping._config['foo'] = 'bar'
|
||||
self.mapping.save(get_preset_path('device 1', 'test'))
|
||||
|
||||
@ -194,7 +195,7 @@ class TestMapping(unittest.TestCase):
|
||||
self.assertEqual(len(loaded), 3)
|
||||
self.assertEqual(loaded.get_character(one), '1')
|
||||
self.assertEqual(loaded.get_character(two), '2')
|
||||
self.assertEqual(loaded.get_character((two, three)), '3')
|
||||
self.assertEqual(loaded.get_character(Key(two, three)), '3')
|
||||
self.assertEqual(loaded._config['foo'], 'bar')
|
||||
|
||||
def test_save_load_2(self):
|
||||
@ -209,10 +210,8 @@ class TestMapping(unittest.TestCase):
|
||||
f'{EV_ABS},{ABS_HAT0X},-1': 'b',
|
||||
f'{EV_ABS},1,1+{EV_ABS},2,-1+{EV_ABS},3,1': 'c',
|
||||
# ignored because broken
|
||||
f'3,1,1+': 'd',
|
||||
f'3,1,1,2': 'e',
|
||||
f'3': 'e',
|
||||
f'+3,1,2': 'f',
|
||||
f',,+3,1,2': 'g',
|
||||
f'': 'h',
|
||||
}
|
||||
@ -221,19 +220,21 @@ class TestMapping(unittest.TestCase):
|
||||
loaded = Mapping()
|
||||
loaded.load(get_preset_path('device 1', 'test'))
|
||||
self.assertEqual(len(loaded), 3)
|
||||
self.assertEqual(loaded.get_character((EV_KEY, 3, 1)), 'a')
|
||||
self.assertEqual(loaded.get_character((EV_ABS, ABS_HAT0X, -1)), 'b')
|
||||
self.assertEqual(loaded.get_character(
|
||||
((EV_ABS, 1, 1), (EV_ABS, 2, -1), (EV_ABS, 3, 1))
|
||||
self.assertEqual(loaded.get_character(Key(EV_KEY, 3, 1)), 'a')
|
||||
self.assertEqual(loaded.get_character(Key(EV_ABS, ABS_HAT0X, -1)), 'b')
|
||||
self.assertEqual(loaded.get_character(Key(
|
||||
(EV_ABS, 1, 1),
|
||||
(EV_ABS, 2, -1),
|
||||
Key(EV_ABS, 3, 1))
|
||||
), 'c')
|
||||
|
||||
def test_change(self):
|
||||
# the reader would not report values like 111 or 222, only 1 or -1.
|
||||
# the mapping just does what it is told, so it accepts them.
|
||||
ev_1 = (EV_KEY, 1, 111)
|
||||
ev_2 = (EV_KEY, 1, 222)
|
||||
ev_3 = (EV_KEY, 2, 111)
|
||||
ev_4 = (EV_ABS, 1, 111)
|
||||
ev_1 = Key(EV_KEY, 1, 111)
|
||||
ev_2 = Key(EV_KEY, 1, 222)
|
||||
ev_3 = Key(EV_KEY, 2, 111)
|
||||
ev_4 = Key(EV_ABS, 1, 111)
|
||||
|
||||
# 1 is not assigned yet, ignore it
|
||||
self.mapping.change(ev_1, 'a', ev_2)
|
||||
@ -265,13 +266,13 @@ class TestMapping(unittest.TestCase):
|
||||
self.assertEqual(len(self.mapping), 2)
|
||||
|
||||
def test_combinations(self):
|
||||
ev_1 = (EV_KEY, 1, 111)
|
||||
ev_2 = (EV_KEY, 1, 222)
|
||||
ev_3 = (EV_KEY, 2, 111)
|
||||
ev_4 = (EV_ABS, 1, 111)
|
||||
combi_1 = (ev_1, ev_2, ev_3)
|
||||
combi_2 = (ev_2, ev_1, ev_3)
|
||||
combi_3 = (ev_1, ev_2, ev_4)
|
||||
ev_1 = Key(EV_KEY, 1, 111)
|
||||
ev_2 = Key(EV_KEY, 1, 222)
|
||||
ev_3 = Key(EV_KEY, 2, 111)
|
||||
ev_4 = Key(EV_ABS, 1, 111)
|
||||
combi_1 = Key(ev_1, ev_2, ev_3)
|
||||
combi_2 = Key(ev_2, ev_1, ev_3)
|
||||
combi_3 = Key(ev_1, ev_2, ev_4)
|
||||
|
||||
self.mapping.change(combi_1, 'a')
|
||||
self.assertEqual(self.mapping.get_character(combi_1), 'a')
|
||||
@ -293,50 +294,55 @@ class TestMapping(unittest.TestCase):
|
||||
|
||||
def test_clear(self):
|
||||
# does nothing
|
||||
self.mapping.clear((EV_KEY, 40, 1))
|
||||
ev_1 = Key(EV_KEY, 40, 1)
|
||||
ev_2 = Key(EV_KEY, 30, 1)
|
||||
ev_3 = Key(EV_KEY, 20, 1)
|
||||
ev_4 = Key(EV_KEY, 10, 1)
|
||||
|
||||
self.mapping.clear(ev_1)
|
||||
self.assertFalse(self.mapping.changed)
|
||||
self.assertEqual(len(self.mapping), 0)
|
||||
|
||||
self.mapping._mapping[(EV_KEY, 40, 1)] = 'b'
|
||||
self.mapping._mapping[ev_1] = 'b'
|
||||
self.assertEqual(len(self.mapping), 1)
|
||||
self.mapping.clear((EV_KEY, 40, 1))
|
||||
self.mapping.clear(ev_1)
|
||||
self.assertEqual(len(self.mapping), 0)
|
||||
self.assertTrue(self.mapping.changed)
|
||||
|
||||
self.mapping.change((EV_KEY, 10, 1), 'KP_1', None)
|
||||
self.mapping.change(ev_4, 'KP_1', None)
|
||||
self.assertTrue(self.mapping.changed)
|
||||
self.mapping.change((EV_KEY, 20, 1), 'KP_2', None)
|
||||
self.mapping.change((EV_KEY, 30, 1), 'KP_3', None)
|
||||
self.mapping.change(ev_3, 'KP_2', None)
|
||||
self.mapping.change(ev_2, 'KP_3', None)
|
||||
self.assertEqual(len(self.mapping), 3)
|
||||
self.mapping.clear((EV_KEY, 20, 1))
|
||||
self.mapping.clear(ev_3)
|
||||
self.assertEqual(len(self.mapping), 2)
|
||||
self.assertEqual(self.mapping.get_character((EV_KEY, 10, 1)), 'KP_1')
|
||||
self.assertIsNone(self.mapping.get_character((EV_KEY, 20, 1)))
|
||||
self.assertEqual(self.mapping.get_character((EV_KEY, 30, 1)), 'KP_3')
|
||||
self.assertEqual(self.mapping.get_character(ev_4), 'KP_1')
|
||||
self.assertIsNone(self.mapping.get_character(ev_3))
|
||||
self.assertEqual(self.mapping.get_character(ev_2), 'KP_3')
|
||||
|
||||
def test_empty(self):
|
||||
self.mapping.change((EV_KEY, 10, 1), '1')
|
||||
self.mapping.change((EV_KEY, 11, 1), '2')
|
||||
self.mapping.change((EV_KEY, 12, 1), '3')
|
||||
self.mapping.change(Key(EV_KEY, 10, 1), '1')
|
||||
self.mapping.change(Key(EV_KEY, 11, 1), '2')
|
||||
self.mapping.change(Key(EV_KEY, 12, 1), '3')
|
||||
self.assertEqual(len(self.mapping), 3)
|
||||
self.mapping.empty()
|
||||
self.assertEqual(len(self.mapping), 0)
|
||||
|
||||
def test_verify_key(self):
|
||||
self.assertRaises(ValueError, lambda: verify_key(1))
|
||||
self.assertRaises(ValueError, lambda: verify_key(None))
|
||||
self.assertRaises(ValueError, lambda: verify_key([1]))
|
||||
self.assertRaises(ValueError, lambda: verify_key((1,)))
|
||||
self.assertRaises(ValueError, lambda: verify_key((1, 2)))
|
||||
self.assertRaises(ValueError, lambda: verify_key(('1', '2', '3')))
|
||||
self.assertRaises(ValueError, lambda: verify_key('1'))
|
||||
self.assertRaises(ValueError, lambda: verify_key('(1,2,3)'))
|
||||
self.assertRaises(ValueError, lambda: verify_key(((1, 2, 3), (1, 2, '3'))))
|
||||
self.assertRaises(ValueError, lambda: verify_key(((1, 2, 3), (1, 2, 3), None)))
|
||||
self.assertRaises(ValueError, lambda: Key(1))
|
||||
self.assertRaises(ValueError, lambda: Key(None))
|
||||
self.assertRaises(ValueError, lambda: Key([1]))
|
||||
self.assertRaises(ValueError, lambda: Key((1,)))
|
||||
self.assertRaises(ValueError, lambda: Key((1, 2)))
|
||||
self.assertRaises(ValueError, lambda: Key(('1', '2', '3')))
|
||||
self.assertRaises(ValueError, lambda: Key('1'))
|
||||
self.assertRaises(ValueError, lambda: Key('(1,2,3)'))
|
||||
self.assertRaises(ValueError, lambda: Key((1, 2, 3), (1, 2, '3')))
|
||||
self.assertRaises(ValueError, lambda: Key((1, 2, 3), (1, 2, 3), None))
|
||||
|
||||
# those don't raise errors
|
||||
verify_key(((1, 2, 3), (1, 2, 3)))
|
||||
verify_key((1, 2, 3))
|
||||
Key((1, 2, 3), (1, 2, 3))
|
||||
Key((1, 2, 3))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Loading…
Reference in New Issue
Block a user