mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-08 07:10:36 +00:00
Properly forwarding unmapped gamepad events
This commit is contained in:
parent
c33d8e8255
commit
db047390d4
@ -99,7 +99,6 @@ class EventProducer:
|
|||||||
except OverflowError:
|
except OverflowError:
|
||||||
# screwed up the calculation of mouse movements
|
# screwed up the calculation of mouse movements
|
||||||
logger.error('OverflowError (%s, %s, %s)', ev_type, keycode, value)
|
logger.error('OverflowError (%s, %s, %s)', ev_type, keycode, value)
|
||||||
pass
|
|
||||||
|
|
||||||
def debounce(self, debounce_id, func, args, ticks):
|
def debounce(self, debounce_id, func, args, ticks):
|
||||||
"""Debounce a function call.
|
"""Debounce a function call.
|
||||||
|
@ -33,7 +33,7 @@ from evdev.ecodes import EV_KEY, EV_REL
|
|||||||
|
|
||||||
from keymapper.logger import logger
|
from keymapper.logger import logger
|
||||||
from keymapper.getdevices import get_devices, is_gamepad
|
from keymapper.getdevices import get_devices, is_gamepad
|
||||||
from keymapper.dev.keycode_mapper import handle_keycode
|
from keymapper.dev.keycode_mapper import KeycodeMapper
|
||||||
from keymapper.dev import utils
|
from keymapper.dev import utils
|
||||||
from keymapper.dev.event_producer import EventProducer
|
from keymapper.dev.event_producer import EventProducer
|
||||||
from keymapper.dev.macros import parse, is_this_a_macro
|
from keymapper.dev.macros import parse, is_this_a_macro
|
||||||
@ -515,6 +515,11 @@ class Injector:
|
|||||||
source.path, source.fd
|
source.path, source.fd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
keycode_handler = KeycodeMapper(
|
||||||
|
source, self.mapping, uinput,
|
||||||
|
self._key_to_code, macros
|
||||||
|
)
|
||||||
|
|
||||||
async for event in source.async_read_loop():
|
async for event in source.async_read_loop():
|
||||||
if self._event_producer.is_handled(event):
|
if self._event_producer.is_handled(event):
|
||||||
# the event_producer will take care of it
|
# the event_producer will take care of it
|
||||||
@ -522,14 +527,11 @@ class Injector:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# for mapped stuff
|
# for mapped stuff
|
||||||
if utils.should_map_event_as_btn(source, event, self.mapping):
|
if utils.should_map_event_as_btn(event, self.mapping):
|
||||||
will_report_key_up = utils.will_report_key_up(event)
|
will_report_key_up = utils.will_report_key_up(event)
|
||||||
|
|
||||||
handle_keycode(
|
keycode_handler.handle_keycode(
|
||||||
self._key_to_code,
|
|
||||||
macros,
|
|
||||||
event,
|
event,
|
||||||
uinput,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not will_report_key_up:
|
if not will_report_key_up:
|
||||||
@ -538,11 +540,9 @@ class Injector:
|
|||||||
release = evdev.InputEvent(0, 0, event.type, event.code, 0)
|
release = evdev.InputEvent(0, 0, event.type, event.code, 0)
|
||||||
self._event_producer.debounce(
|
self._event_producer.debounce(
|
||||||
debounce_id=(event.type, event.code, event.value),
|
debounce_id=(event.type, event.code, event.value),
|
||||||
func=handle_keycode,
|
func=keycode_handler.handle_keycode,
|
||||||
args=(
|
args=(
|
||||||
self._key_to_code, macros,
|
|
||||||
release,
|
release,
|
||||||
uinput,
|
|
||||||
False
|
False
|
||||||
),
|
),
|
||||||
ticks=3,
|
ticks=3,
|
||||||
@ -551,7 +551,6 @@ class Injector:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# forward the rest
|
# forward the rest
|
||||||
# TODO triggers should retain their original value if not mapped
|
|
||||||
uinput.write(event.type, event.code, event.value)
|
uinput.write(event.type, event.code, event.value)
|
||||||
# this already includes SYN events, so need to syn here again
|
# this already includes SYN events, so need to syn here again
|
||||||
|
|
||||||
|
@ -29,43 +29,46 @@ from evdev.ecodes import EV_KEY, EV_ABS
|
|||||||
|
|
||||||
from keymapper.logger import logger
|
from keymapper.logger import logger
|
||||||
from keymapper.mapping import DISABLE_CODE
|
from keymapper.mapping import DISABLE_CODE
|
||||||
|
from keymapper.dev import utils
|
||||||
|
|
||||||
|
|
||||||
# maps mouse buttons to macro instances that have been executed. They may
|
# this state is shared by all KeycodeMappers of this process
|
||||||
# still be running or already be done. Just like unreleased, this is a
|
|
||||||
# mapping of (type, code). The value is not included in the key, because
|
# maps mouse buttons to macro instances that have been executed.
|
||||||
# a key release event with a value of 0 needs to be able to find the
|
# They may still be running or already be done. Just like unreleased,
|
||||||
# running macro. The downside is that a d-pad cannot execute two macros at
|
# this is a mapping of (type, code). The value is not included in the
|
||||||
# once, one for each direction. Only sequentially.W
|
# key, because a key release event with a value of 0 needs to be able
|
||||||
|
# to find the running macro. The downside is that a d-pad cannot
|
||||||
|
# execute two macros at once, one for each direction.
|
||||||
|
# Only sequentially.
|
||||||
active_macros = {}
|
active_macros = {}
|
||||||
|
|
||||||
# mapping of future release event (type, code) to (output, input event),
|
# mapping of future release event (type, code) to an Unreleased object,
|
||||||
# with output being a tuple of (type, code) as well. All key-up events
|
# All key-up events have a value of 0, so it is not added to
|
||||||
# have a value of 0, so it is not added to the tuple.
|
# the tuple. This is needed in order to release the correct event
|
||||||
# This is needed in order to release the correct event mapped on a
|
# mapped on a D-Pad. Each direction on one D-Pad axis reports the
|
||||||
# D-Pad. Each direction on one D-Pad axis reports the same type and
|
# same type and code, but different values. There cannot be both at
|
||||||
# code, but different values. There cannot be both at the same time,
|
# the same time, as pressing one side of a D-Pad forces the other
|
||||||
# as pressing one side of a D-Pad forces the other side to go up.
|
# side to go up. If both sides of a D-Pad are mapped to different
|
||||||
# If both sides of a D-Pad are mapped to different event-codes, this data
|
# event-codes, this data structure helps to figure out which of those
|
||||||
# structure helps to figure out which of those two to release on an event
|
# two to release on an event of value 0. Same goes for the Wheel.
|
||||||
# of value 0. Same goes for the Wheel.
|
# The input event is remembered to make sure no duplicate down-events
|
||||||
# The input event is remembered to make sure no duplicate down-events are
|
# are written. Since wheels report a lot of "down" events that don't
|
||||||
# written. Since wheels report a lot of "down" events that don't serve
|
# serve any purpose when mapped to a key, those duplicate down events
|
||||||
# any purpose when mapped to a key, those duplicate down events should be
|
# should be removed. If the same type and code arrives but with a
|
||||||
# removed. If the same type and code arrives but with a different value
|
# different value (direction), there must be a way to check if the
|
||||||
# (direction), there must be a way to check if the event is actually a
|
# event is actually a duplicate and not a different event.
|
||||||
# duplicate and not a different event.
|
|
||||||
unreleased = {}
|
unreleased = {}
|
||||||
|
|
||||||
|
|
||||||
def is_key_down(event):
|
def is_key_down(value):
|
||||||
"""Is this event a key press."""
|
"""Is this event value a key press."""
|
||||||
return event.value != 0
|
return value != 0
|
||||||
|
|
||||||
|
|
||||||
def is_key_up(event):
|
def is_key_up(value):
|
||||||
"""Is this event a key release."""
|
"""Is this event value a key release."""
|
||||||
return event.value == 0
|
return value == 0
|
||||||
|
|
||||||
|
|
||||||
def write(uinput, key):
|
def write(uinput, key):
|
||||||
@ -84,6 +87,8 @@ def subsets(combination):
|
|||||||
If combination is only one element long it returns an empty list,
|
If combination is only one element long it returns an empty list,
|
||||||
because it's not a combination and there is no reason to iterate.
|
because it's not a combination and there is no reason to iterate.
|
||||||
|
|
||||||
|
Includes the complete input as well.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
combination : tuple
|
combination : tuple
|
||||||
@ -121,27 +126,41 @@ class Unreleased:
|
|||||||
self.target_type_code = target_type_code
|
self.target_type_code = target_type_code
|
||||||
self.input_event_tuple = input_event_tuple
|
self.input_event_tuple = input_event_tuple
|
||||||
self.key = key
|
self.key = key
|
||||||
|
|
||||||
|
if (
|
||||||
|
not isinstance(input_event_tuple[0], int) or
|
||||||
|
len(input_event_tuple) != 3
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
'Expected input_event_tuple to be a 3-tuple of ints, but '
|
||||||
|
f'got {input_event_tuple}'
|
||||||
|
)
|
||||||
|
|
||||||
unreleased[input_event_tuple[:2]] = self
|
unreleased[input_event_tuple[:2]] = self
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return (
|
||||||
f'target{self.target_type_code} '
|
'Unreleased('
|
||||||
f'input{self.input_event_tuple} '
|
f'target{self.target_type_code},'
|
||||||
f'key{self.key}'
|
f'input{self.input_event_tuple},'
|
||||||
|
f'key{"(None)" if self.key is None else self.key}'
|
||||||
|
')'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
def find_by_event(event):
|
|
||||||
|
def find_by_event(key):
|
||||||
"""Find an unreleased entry by an event.
|
"""Find an unreleased entry by an event.
|
||||||
|
|
||||||
If such an entry exists, it was created by an event that is exactly like
|
If such an entry exists, it was created by an event that is exactly
|
||||||
the input parameter (except for the timestamp).
|
like the input parameter (except for the timestamp).
|
||||||
|
|
||||||
That doesn't mean it triggered something, only that it was seen before.
|
That doesn't mean it triggered something, only that it was seen before.
|
||||||
"""
|
"""
|
||||||
unreleased_entry = unreleased.get((event.type, event.code))
|
unreleased_entry = unreleased.get(key[:2])
|
||||||
event_tuple = (event.type, event.code, event.value)
|
if unreleased_entry and unreleased_entry.input_event_tuple == key:
|
||||||
if unreleased_entry and unreleased_entry.input_event_tuple == event_tuple:
|
|
||||||
return unreleased_entry
|
return unreleased_entry
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@ -150,9 +169,9 @@ def find_by_event(event):
|
|||||||
def find_by_key(key):
|
def find_by_key(key):
|
||||||
"""Find an unreleased entry by a combination of keys.
|
"""Find an unreleased entry by a combination of keys.
|
||||||
|
|
||||||
If such an entry exist, it was created when a combination of keys (which
|
If such an entry exist, it was created when a combination of keys
|
||||||
matches the parameter) (can also be of len 1 = single key) ended
|
(which matches the parameter) (can also be of len 1 = single key)
|
||||||
up triggering something.
|
ended up triggering something.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@ -165,7 +184,62 @@ def find_by_key(key):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_key(event, key_to_code, macros):
|
def print_unreleased():
|
||||||
|
"""For debugging purposes."""
|
||||||
|
logger.debug('unreleased:')
|
||||||
|
logger.debug('\n'.join([
|
||||||
|
f' {key}: {str(value)}' for key, value in unreleased.items()
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
|
class KeycodeMapper:
|
||||||
|
def __init__(self, source, mapping, uinput, key_to_code, macros):
|
||||||
|
"""Create a keycode mapper for one virtual device.
|
||||||
|
|
||||||
|
There may be multiple KeycodeMappers for one hardware device. They
|
||||||
|
share some state (unreleased and active_macros) with each other.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
source : InputDevice
|
||||||
|
where events used in handle_keycode come from
|
||||||
|
mapping : Mapping
|
||||||
|
the mapping that is the source of key_to_code and macros,
|
||||||
|
only used to query config values.
|
||||||
|
uinput : UInput:
|
||||||
|
where to inject events to
|
||||||
|
key_to_code : dict
|
||||||
|
mapping of ((type, code, value),) to linux-keycode
|
||||||
|
or multiple of those like ((...), (...), ...) for combinations
|
||||||
|
combinations need to be present in every possible valid ordering.
|
||||||
|
e.g. shift + alt + a and alt + shift + a.
|
||||||
|
This is needed to query keycodes more efficiently without having
|
||||||
|
to search mapping each time.
|
||||||
|
macros : dict
|
||||||
|
mapping of ((type, code, value),) to _Macro objects.
|
||||||
|
Combinations work similar as in key_to_code
|
||||||
|
"""
|
||||||
|
self.source = source
|
||||||
|
self.max_abs = utils.get_max_abs(source)
|
||||||
|
self.mapping = mapping
|
||||||
|
self.uinput = uinput
|
||||||
|
|
||||||
|
# some type checking, prevents me from forgetting what that stuff
|
||||||
|
# is supposed to be when writing tests.
|
||||||
|
# TODO create that stuff (including macros) from mapping here instead
|
||||||
|
# of the injector
|
||||||
|
for key in key_to_code:
|
||||||
|
for sub_key in key:
|
||||||
|
if abs(sub_key[2]) > 1:
|
||||||
|
raise ValueError(
|
||||||
|
f'Expected values to be one of -1, 0 or 1, '
|
||||||
|
f'but got {key}'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.key_to_code = key_to_code
|
||||||
|
self.macros = macros
|
||||||
|
|
||||||
|
def _get_key(self, key):
|
||||||
"""If the event triggers stuff, get the key for that.
|
"""If the event triggers stuff, get the key for that.
|
||||||
|
|
||||||
This key can be used to index `key_to_code` and `macros` and it might
|
This key can be used to index `key_to_code` and `macros` and it might
|
||||||
@ -175,27 +249,36 @@ def _get_key(event, key_to_code, macros):
|
|||||||
|
|
||||||
The return format is always a tuple of 3-tuples, each 3-tuple being
|
The return format is always a tuple of 3-tuples, each 3-tuple being
|
||||||
type, code, value (int, int, int)
|
type, code, value (int, int, int)
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
key : int, int, int
|
||||||
|
3-tuple of type, code, value
|
||||||
|
Value should be one of -1, 0 or 1
|
||||||
"""
|
"""
|
||||||
|
unreleased_entry = find_by_event(key)
|
||||||
|
|
||||||
# The key used to index the mappings `key_to_code` and `macros`.
|
# The key used to index the mappings `key_to_code` and `macros`.
|
||||||
# If the key triggers a combination, the returned key will be that one
|
# If the key triggers a combination, the returned key will be that one
|
||||||
# instead
|
# instead
|
||||||
key = ((event.type, event.code, event.value),)
|
value = key[2]
|
||||||
|
key = (key,)
|
||||||
|
|
||||||
unreleased_entry = find_by_event(event)
|
|
||||||
if unreleased_entry is not None and unreleased_entry.key is not None:
|
if unreleased_entry is not None and unreleased_entry.key is not None:
|
||||||
# seen before. If this key triggered a combination,
|
# seen before. If this key triggered a combination,
|
||||||
# use the combination that was triggered by this as key.
|
# use the combination that was triggered by this as key.
|
||||||
return unreleased_entry.key
|
return unreleased_entry.key
|
||||||
|
|
||||||
if is_key_down(event):
|
if is_key_down(value):
|
||||||
# get the key/combination that the key-down would trigger
|
# get the key/combination that the key-down would trigger
|
||||||
|
|
||||||
# the triggering key-down has to be the last element in combination,
|
# the triggering key-down has to be the last element in
|
||||||
# all others can have any arbitrary order. By checking all unreleased
|
# combination, all others can have any arbitrary order. By
|
||||||
# keys, a + b + c takes priority over b + c, if both mappings exist.
|
# checking all unreleased keys, a + b + c takes priority over
|
||||||
# WARNING! the combination-down triggers, but a single key-up releases.
|
# b + c, if both mappings exist.
|
||||||
# Do not check if key in macros and such, if it is an up event. It's
|
# WARNING! the combination-down triggers, but a single key-up
|
||||||
# going to be False.
|
# releases. Do not check if key in macros and such, if it is an
|
||||||
|
# up event. It's going to be False.
|
||||||
combination = tuple([
|
combination = tuple([
|
||||||
value.input_event_tuple for value
|
value.input_event_tuple for value
|
||||||
in unreleased.values()
|
in unreleased.values()
|
||||||
@ -204,35 +287,27 @@ def _get_key(event, key_to_code, macros):
|
|||||||
combination += key
|
combination += key
|
||||||
|
|
||||||
# find any triggered combination. macros and key_to_code contain
|
# find any triggered combination. macros and key_to_code contain
|
||||||
# every possible equivalent permutation of possible macros. The last
|
# every possible equivalent permutation of possible macros. The
|
||||||
# key in the combination needs to remain the newest key though.
|
# last key in the combination needs to remain the newest key
|
||||||
|
# though.
|
||||||
for subset in subsets(combination):
|
for subset in subsets(combination):
|
||||||
if subset[-1] != key[0]:
|
if subset[-1] != key[0]:
|
||||||
# only combinations that are completed and triggered by the
|
# only combinations that are completed and triggered by
|
||||||
# newest input are of interest
|
# the newest input are of interest
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if subset in macros or subset in key_to_code:
|
if subset in self.macros or subset in self.key_to_code:
|
||||||
key = subset
|
key = subset
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# no subset found, just use the key. all indices are tuples of
|
# no subset found, just use the key. all indices are tuples of
|
||||||
# tuples, both for combinations and single keys.
|
# tuples, both for combinations and single keys.
|
||||||
if event.value == 1 and len(combination) > 1:
|
if value == 1 and len(combination) > 1:
|
||||||
logger.key_spam(combination, 'unknown combination')
|
logger.key_spam(combination, 'unknown combination')
|
||||||
|
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
def handle_keycode(self, event, forward=True):
|
||||||
def print_unreleased():
|
|
||||||
"""For debugging purposes."""
|
|
||||||
logger.debug('unreleased:')
|
|
||||||
logger.debug('\n'.join([
|
|
||||||
f' {key}: {str(value)}' for key, value in unreleased.items()
|
|
||||||
]))
|
|
||||||
|
|
||||||
|
|
||||||
def handle_keycode(key_to_code, macros, event, uinput, forward=True):
|
|
||||||
"""Write mapped keycodes, forward unmapped ones and manage macros.
|
"""Write mapped keycodes, forward unmapped ones and manage macros.
|
||||||
|
|
||||||
As long as the provided event is mapped it will handle it, it won't
|
As long as the provided event is mapped it will handle it, it won't
|
||||||
@ -241,17 +316,10 @@ def handle_keycode(key_to_code, macros, event, uinput, forward=True):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
key_to_code : dict
|
|
||||||
mapping of ((type, code, value),) to linux-keycode
|
|
||||||
or multiple of those like ((...), (...), ...) for combinations
|
|
||||||
combinations need to be present in every possible valid ordering.
|
|
||||||
e.g. shift + alt + a and alt + shift + a
|
|
||||||
macros : dict
|
|
||||||
mapping of ((type, code, value),) to _Macro objects.
|
|
||||||
Combinations work similar as in key_to_code
|
|
||||||
event : evdev.InputEvent
|
event : evdev.InputEvent
|
||||||
forward : bool
|
forward : bool
|
||||||
if False, will not forward the event if it didn't trigger any mapping
|
if False, will not forward the event if it didn't trigger any
|
||||||
|
mapping
|
||||||
"""
|
"""
|
||||||
if event.type == EV_KEY and event.value == 2:
|
if event.type == EV_KEY and event.value == 2:
|
||||||
# button-hold event. Linux creates them on its own for the
|
# button-hold event. Linux creates them on its own for the
|
||||||
@ -259,18 +327,26 @@ def handle_keycode(key_to_code, macros, event, uinput, forward=True):
|
|||||||
# no need to forward or map them.
|
# no need to forward or map them.
|
||||||
return
|
return
|
||||||
|
|
||||||
# the tuple of the actual input event. Used to forward the event if it is
|
# normalize event numbers to one of -1, 0, +1. Otherwise
|
||||||
# not mapped, and to index unreleased and active_macros. stays constant
|
# mapping trigger values that are between 1 and 255 is not
|
||||||
|
# possible, because they might skip the 1 when pressed fast
|
||||||
|
# enough.
|
||||||
|
original_tuple = (event.type, event.code, event.value)
|
||||||
|
event.value = utils.normalize_value(event, self.max_abs)
|
||||||
|
|
||||||
|
# the tuple of the actual input event. Used to forward the event if
|
||||||
|
# it is not mapped, and to index unreleased and active_macros. stays
|
||||||
|
# constant
|
||||||
event_tuple = (event.type, event.code, event.value)
|
event_tuple = (event.type, event.code, event.value)
|
||||||
type_code = (event.type, event.code)
|
type_code = (event.type, event.code)
|
||||||
active_macro = active_macros.get(type_code)
|
active_macro = active_macros.get(type_code)
|
||||||
|
|
||||||
key = _get_key(event, key_to_code, macros)
|
key = self._get_key(event_tuple)
|
||||||
is_mapped = key in macros or key in key_to_code
|
is_mapped = key in self.macros or key in self.key_to_code
|
||||||
|
|
||||||
"""Releasing keys and macros"""
|
"""Releasing keys and macros"""
|
||||||
|
|
||||||
if is_key_up(event):
|
if is_key_up(event.value):
|
||||||
if active_macro is not None and active_macro.is_holding():
|
if active_macro is not None and active_macro.is_holding():
|
||||||
# Tell the macro for that keycode that the key is released and
|
# Tell the macro for that keycode that the key is released and
|
||||||
# let it decide what to do with that information.
|
# let it decide what to do with that information.
|
||||||
@ -279,7 +355,9 @@ def handle_keycode(key_to_code, macros, event, uinput, forward=True):
|
|||||||
|
|
||||||
if type_code in unreleased:
|
if type_code in unreleased:
|
||||||
# figure out what this release event was for
|
# figure out what this release event was for
|
||||||
target_type, target_code = unreleased[type_code].target_type_code
|
target_type, target_code = (
|
||||||
|
unreleased[type_code].target_type_code
|
||||||
|
)
|
||||||
del unreleased[type_code]
|
del unreleased[type_code]
|
||||||
|
|
||||||
if target_code == DISABLE_CODE:
|
if target_code == DISABLE_CODE:
|
||||||
@ -289,16 +367,16 @@ def handle_keycode(key_to_code, macros, event, uinput, forward=True):
|
|||||||
elif type_code != (target_type, target_code):
|
elif type_code != (target_type, target_code):
|
||||||
# release what the input is mapped to
|
# release what the input is mapped to
|
||||||
logger.key_spam(key, 'releasing %s', target_code)
|
logger.key_spam(key, 'releasing %s', target_code)
|
||||||
write(uinput, (target_type, target_code, 0))
|
write(self.uinput, (target_type, target_code, 0))
|
||||||
elif forward:
|
elif forward:
|
||||||
# forward the release event
|
# forward the release event
|
||||||
logger.key_spam(key, 'forwarding release')
|
logger.key_spam((original_tuple,), 'forwarding release')
|
||||||
write(uinput, (target_type, target_code, 0))
|
write(self.uinput, original_tuple)
|
||||||
else:
|
else:
|
||||||
logger.key_spam(key, 'not forwarding release')
|
logger.key_spam(key, 'not forwarding release')
|
||||||
elif event.type != EV_ABS:
|
elif event.type != EV_ABS:
|
||||||
# ABS events might be spammed like crazy every time the position
|
# ABS events might be spammed like crazy every time the
|
||||||
# slightly changes
|
# position slightly changes
|
||||||
logger.key_spam(key, 'unexpected key up')
|
logger.key_spam(key, 'unexpected key up')
|
||||||
|
|
||||||
# everything that can be released is released now
|
# everything that can be released is released now
|
||||||
@ -306,35 +384,36 @@ def handle_keycode(key_to_code, macros, event, uinput, forward=True):
|
|||||||
|
|
||||||
"""Filtering duplicate key downs"""
|
"""Filtering duplicate key downs"""
|
||||||
|
|
||||||
if is_mapped and is_key_down(event):
|
if is_mapped and is_key_down(event.value):
|
||||||
# unmapped keys should not be filtered here, they should just
|
# unmapped keys should not be filtered here, they should just
|
||||||
# be forwarded to populate unreleased and then be written.
|
# be forwarded to populate unreleased and then be written.
|
||||||
|
|
||||||
if find_by_key(key) is not None:
|
if find_by_key(key) is not None:
|
||||||
# this key/combination triggered stuff before.
|
# this key/combination triggered stuff before.
|
||||||
# duplicate key-down. skip this event. Avoid writing millions of
|
# duplicate key-down. skip this event. Avoid writing millions
|
||||||
# key-down events when a continuous value is reported, for example
|
# of key-down events when a continuous value is reported, for
|
||||||
# for gamepad triggers or mouse-wheel-side buttons
|
# example for gamepad triggers or mouse-wheel-side buttons
|
||||||
logger.key_spam(key, 'duplicate key down')
|
logger.key_spam(key, 'duplicate key down')
|
||||||
return
|
return
|
||||||
|
|
||||||
# it would start a macro usually
|
# it would start a macro usually
|
||||||
if key in macros and active_macro is not None and active_macro.running:
|
if key in self.macros and active_macro and active_macro.running:
|
||||||
# for key-down events and running macros, don't do anything.
|
# for key-down events and running macros, don't do anything.
|
||||||
# This avoids spawning a second macro while the first one is not
|
# This avoids spawning a second macro while the first one is
|
||||||
# finished, especially since gamepad-triggers report a ton of
|
# not finished, especially since gamepad-triggers report a ton
|
||||||
# events with a positive value.
|
# of events with a positive value.
|
||||||
logger.key_spam(key, 'macro already running')
|
logger.key_spam(key, 'macro already running')
|
||||||
return
|
return
|
||||||
|
|
||||||
"""starting new macros or injecting new keys"""
|
"""starting new macros or injecting new keys"""
|
||||||
|
|
||||||
if is_key_down(event):
|
if is_key_down(event.value):
|
||||||
# also enter this for unmapped keys, as they might end up triggering
|
# also enter this for unmapped keys, as they might end up
|
||||||
# a combination, so they should be remembered in unreleased
|
# triggering a combination, so they should be remembered in
|
||||||
|
# unreleased
|
||||||
|
|
||||||
if key in macros:
|
if key in self.macros:
|
||||||
macro = macros[key]
|
macro = self.macros[key]
|
||||||
active_macros[type_code] = macro
|
active_macros[type_code] = macro
|
||||||
Unreleased((None, None), event_tuple, key)
|
Unreleased((None, None), event_tuple, key)
|
||||||
macro.press_key()
|
macro.press_key()
|
||||||
@ -342,8 +421,8 @@ def handle_keycode(key_to_code, macros, event, uinput, forward=True):
|
|||||||
asyncio.ensure_future(macro.run())
|
asyncio.ensure_future(macro.run())
|
||||||
return
|
return
|
||||||
|
|
||||||
if key in key_to_code:
|
if key in self.key_to_code:
|
||||||
target_code = key_to_code[key]
|
target_code = self.key_to_code[key]
|
||||||
# remember the key that triggered this
|
# remember the key that triggered this
|
||||||
# (this combination or this single key)
|
# (this combination or this single key)
|
||||||
Unreleased((EV_KEY, target_code), event_tuple, key)
|
Unreleased((EV_KEY, target_code), event_tuple, key)
|
||||||
@ -353,17 +432,17 @@ def handle_keycode(key_to_code, macros, event, uinput, forward=True):
|
|||||||
return
|
return
|
||||||
|
|
||||||
logger.key_spam(key, 'maps to %s', target_code)
|
logger.key_spam(key, 'maps to %s', target_code)
|
||||||
write(uinput, (EV_KEY, target_code, 1))
|
write(self.uinput, (EV_KEY, target_code, 1))
|
||||||
return
|
return
|
||||||
|
|
||||||
if forward:
|
if forward:
|
||||||
logger.key_spam(key, 'forwarding')
|
logger.key_spam((original_tuple,), 'forwarding')
|
||||||
write(uinput, event_tuple)
|
write(self.uinput, original_tuple)
|
||||||
else:
|
else:
|
||||||
logger.key_spam(key, 'not forwarding')
|
logger.key_spam((event_tuple,), 'not forwarding')
|
||||||
|
|
||||||
# unhandled events may still be important for triggering combinations
|
# unhandled events may still be important for triggering
|
||||||
# later, so remember them as well.
|
# combinations later, so remember them as well.
|
||||||
Unreleased((event_tuple[:2]), event_tuple, None)
|
Unreleased((event_tuple[:2]), event_tuple, None)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -174,9 +174,12 @@ class _KeycodeReader:
|
|||||||
# which breaks the current workflow.
|
# which breaks the current workflow.
|
||||||
return
|
return
|
||||||
|
|
||||||
if not utils.should_map_event_as_btn(device, event, custom_mapping):
|
if not utils.should_map_event_as_btn(event, custom_mapping):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
max_abs = utils.get_max_abs(device)
|
||||||
|
event.value = utils.normalize_value(event, max_abs)
|
||||||
|
|
||||||
self._pipe[1].send(event)
|
self._pipe[1].send(event)
|
||||||
|
|
||||||
def _read_worker(self):
|
def _read_worker(self):
|
||||||
@ -267,7 +270,6 @@ class _KeycodeReader:
|
|||||||
# have to trigger anything, manage any macros and only
|
# have to trigger anything, manage any macros and only
|
||||||
# reports key-down events. This function is called periodically
|
# reports key-down events. This function is called periodically
|
||||||
# by the window.
|
# by the window.
|
||||||
|
|
||||||
if self._pipe is None:
|
if self._pipe is None:
|
||||||
self.fail_counter += 1
|
self.fail_counter += 1
|
||||||
if self.fail_counter % 10 == 0: # spam less
|
if self.fail_counter % 10 == 0: # spam less
|
||||||
@ -293,10 +295,7 @@ class _KeycodeReader:
|
|||||||
self._release(type_code)
|
self._release(type_code)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
key_down_received = True
|
|
||||||
|
|
||||||
if self._unreleased.get(type_code) == event_tuple:
|
if self._unreleased.get(type_code) == event_tuple:
|
||||||
if event.type != EV_ABS: # spams a lot
|
|
||||||
logger.key_spam(event_tuple, 'duplicate key down')
|
logger.key_spam(event_tuple, 'duplicate key down')
|
||||||
self._debounce_start(event_tuple)
|
self._debounce_start(event_tuple)
|
||||||
continue
|
continue
|
||||||
@ -318,7 +317,10 @@ class _KeycodeReader:
|
|||||||
previous_event.value
|
previous_event.value
|
||||||
)
|
)
|
||||||
if prev_tuple[:2] in self._unreleased:
|
if prev_tuple[:2] in self._unreleased:
|
||||||
logger.key_spam(prev_tuple, 'ignoring previous event')
|
logger.key_spam(
|
||||||
|
event_tuple,
|
||||||
|
'ignoring previous event %s', prev_tuple
|
||||||
|
)
|
||||||
self._release(prev_tuple[:2])
|
self._release(prev_tuple[:2])
|
||||||
|
|
||||||
# to keep track of combinations.
|
# to keep track of combinations.
|
||||||
@ -326,6 +328,7 @@ class _KeycodeReader:
|
|||||||
# event for a D-Pad axis might be any direction, hence this maps
|
# event for a D-Pad axis might be any direction, hence this maps
|
||||||
# from release to input in order to remember it. Since all release
|
# from release to input in order to remember it. Since all release
|
||||||
# events have value 0, the value is not used in the key.
|
# events have value 0, the value is not used in the key.
|
||||||
|
key_down_received = True
|
||||||
logger.key_spam(event_tuple, 'down')
|
logger.key_spam(event_tuple, 'down')
|
||||||
self._unreleased[type_code] = event_tuple
|
self._unreleased[type_code] = event_tuple
|
||||||
self._debounce_start(event_tuple)
|
self._debounce_start(event_tuple)
|
||||||
|
@ -41,12 +41,13 @@ JOYSTICK = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# a third of a quarter circle
|
# a third of a quarter circle, so that each quarter is divided in 3 areas:
|
||||||
|
# up, left and up-left. That makes up/down/left/right larger than the
|
||||||
|
# overlapping sections though, maybe it should be 8 equal areas though, idk
|
||||||
JOYSTICK_BUTTON_THRESHOLD = math.sin((math.pi / 2) / 3 * 1)
|
JOYSTICK_BUTTON_THRESHOLD = math.sin((math.pi / 2) / 3 * 1)
|
||||||
|
|
||||||
|
|
||||||
def sign(value):
|
def sign(value):
|
||||||
"""Get the sign of the value, or 0 if 0."""
|
|
||||||
if value > 0:
|
if value > 0:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@ -56,6 +57,23 @@ def sign(value):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_value(event, max_abs):
|
||||||
|
"""Fit the event value to one of 0, 1 or -1."""
|
||||||
|
if event.type == EV_ABS and event.code in JOYSTICK:
|
||||||
|
if max_abs is None:
|
||||||
|
logger.error(
|
||||||
|
'Got %s, but max_abs is %s',
|
||||||
|
(event.type, event.code, event.value), max_abs
|
||||||
|
)
|
||||||
|
return event.value
|
||||||
|
|
||||||
|
threshold = max_abs * JOYSTICK_BUTTON_THRESHOLD
|
||||||
|
triggered = abs(event.value) > threshold
|
||||||
|
return sign(event.value) if triggered else 0
|
||||||
|
|
||||||
|
return sign(event.value)
|
||||||
|
|
||||||
|
|
||||||
def is_wheel(event):
|
def is_wheel(event):
|
||||||
"""Check if this is a wheel event."""
|
"""Check if this is a wheel event."""
|
||||||
return event.type == EV_REL and event.code in [REL_WHEEL, REL_HWHEEL]
|
return event.type == EV_REL and event.code in [REL_WHEEL, REL_HWHEEL]
|
||||||
@ -66,7 +84,7 @@ def will_report_key_up(event):
|
|||||||
return not is_wheel(event)
|
return not is_wheel(event)
|
||||||
|
|
||||||
|
|
||||||
def should_map_event_as_btn(device, event, mapping):
|
def should_map_event_as_btn(event, mapping):
|
||||||
"""Does this event describe a button.
|
"""Does this event describe a button.
|
||||||
|
|
||||||
If it does, this function will make sure its value is one of [-1, 0, 1],
|
If it does, this function will make sure its value is one of [-1, 0, 1],
|
||||||
@ -93,30 +111,12 @@ def should_map_event_as_btn(device, event, mapping):
|
|||||||
l_purpose = mapping.get('gamepad.joystick.left_purpose')
|
l_purpose = mapping.get('gamepad.joystick.left_purpose')
|
||||||
r_purpose = mapping.get('gamepad.joystick.right_purpose')
|
r_purpose = mapping.get('gamepad.joystick.right_purpose')
|
||||||
|
|
||||||
max_abs = get_max_abs(device)
|
|
||||||
|
|
||||||
if max_abs is None:
|
|
||||||
logger.error(
|
|
||||||
'Got %s, but max_abs is %s',
|
|
||||||
(event.type, event.code, event.value), max_abs
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
threshold = max_abs * JOYSTICK_BUTTON_THRESHOLD
|
|
||||||
triggered = abs(event.value) > threshold
|
|
||||||
|
|
||||||
if event.code in [ABS_X, ABS_Y] and l_purpose == BUTTONS:
|
if event.code in [ABS_X, ABS_Y] and l_purpose == BUTTONS:
|
||||||
event.value = sign(event.value) if triggered else 0
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if event.code in [ABS_RX, ABS_RY] and r_purpose == BUTTONS:
|
if event.code in [ABS_RX, ABS_RY] and r_purpose == BUTTONS:
|
||||||
event.value = sign(event.value) if triggered else 0
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# normalize event numbers to one of -1, 0, +1. Otherwise mapping
|
|
||||||
# trigger values that are between 1 and 255 is not possible,
|
|
||||||
# because they might skip the 1 when pressed fast enough.
|
|
||||||
event.value = sign(event.value)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -100,6 +100,10 @@ class Key:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'Key{str(self.keys)}'
|
return f'Key{str(self.keys)}'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
# used in the AssertionError output of tests
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
if len(self.keys) == 1:
|
if len(self.keys) == 1:
|
||||||
return hash(self.keys[0])
|
return hash(self.keys[0])
|
||||||
|
@ -32,6 +32,8 @@ SPAM = 5
|
|||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
|
previous_key_spam = None
|
||||||
|
|
||||||
|
|
||||||
def spam(self, message, *args, **kwargs):
|
def spam(self, message, *args, **kwargs):
|
||||||
"""Log a more-verbose message than debug."""
|
"""Log a more-verbose message than debug."""
|
||||||
@ -52,6 +54,9 @@ def key_spam(self, key, msg, *args):
|
|||||||
"""
|
"""
|
||||||
if not self.isEnabledFor(SPAM):
|
if not self.isEnabledFor(SPAM):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
global previous_key_spam
|
||||||
|
|
||||||
msg = msg % args
|
msg = msg % args
|
||||||
str_key = str(key)
|
str_key = str(key)
|
||||||
str_key = str_key.replace(',)', ')')
|
str_key = str_key.replace(',)', ')')
|
||||||
@ -59,6 +64,13 @@ def key_spam(self, key, msg, *args):
|
|||||||
if len(spacing) == 1:
|
if len(spacing) == 1:
|
||||||
spacing = ''
|
spacing = ''
|
||||||
msg = f'{str_key}{spacing} {msg}'
|
msg = f'{str_key}{spacing} {msg}'
|
||||||
|
|
||||||
|
if msg == previous_key_spam:
|
||||||
|
# avoid some super spam from EV_ABS events
|
||||||
|
return
|
||||||
|
|
||||||
|
previous_key_spam = msg
|
||||||
|
|
||||||
self._log(SPAM, msg, args=None)
|
self._log(SPAM, msg, args=None)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<text x="22.0" y="14">pylint</text>
|
<text x="22.0" y="14">pylint</text>
|
||||||
</g>
|
</g>
|
||||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||||
<text x="63.0" y="15" fill="#010101" fill-opacity=".3">9.73</text>
|
<text x="63.0" y="15" fill="#010101" fill-opacity=".3">9.72</text>
|
||||||
<text x="62.0" y="14">9.73</text>
|
<text x="62.0" y="14">9.72</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@ -28,7 +28,6 @@ from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, KEY_A, \
|
|||||||
from keymapper.config import config, BUTTONS
|
from keymapper.config import config, BUTTONS
|
||||||
from keymapper.mapping import Mapping
|
from keymapper.mapping import Mapping
|
||||||
from keymapper.dev import utils
|
from keymapper.dev import utils
|
||||||
from keymapper.key import Key
|
|
||||||
|
|
||||||
from tests.test import new_event, InputDevice, MAX_ABS
|
from tests.test import new_event, InputDevice, MAX_ABS
|
||||||
|
|
||||||
@ -51,12 +50,11 @@ class TestDevUtils(unittest.TestCase):
|
|||||||
self.assertFalse(utils.is_wheel(new_event(EV_ABS, ABS_HAT0X, -1)))
|
self.assertFalse(utils.is_wheel(new_event(EV_ABS, ABS_HAT0X, -1)))
|
||||||
|
|
||||||
def test_should_map_event_as_btn(self):
|
def test_should_map_event_as_btn(self):
|
||||||
device = InputDevice('/dev/input/event30')
|
|
||||||
mapping = Mapping()
|
mapping = Mapping()
|
||||||
|
|
||||||
# the function name is so horribly long
|
# the function name is so horribly long
|
||||||
def do(event):
|
def do(event):
|
||||||
return utils.should_map_event_as_btn(device, event, mapping)
|
return utils.should_map_event_as_btn(event, mapping)
|
||||||
|
|
||||||
"""D-Pad"""
|
"""D-Pad"""
|
||||||
|
|
||||||
@ -86,29 +84,31 @@ class TestDevUtils(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertFalse(do(new_event(EV_ABS, ecodes.ABS_RX, 1234)))
|
self.assertFalse(do(new_event(EV_ABS, ecodes.ABS_RX, 1234)))
|
||||||
self.assertFalse(do(new_event(EV_ABS, ecodes.ABS_Y, -1)))
|
self.assertFalse(do(new_event(EV_ABS, ecodes.ABS_Y, -1)))
|
||||||
|
self.assertFalse(do(new_event(EV_ABS, ecodes.ABS_RY, -1)))
|
||||||
|
|
||||||
mapping.set('gamepad.joystick.left_purpose', BUTTONS)
|
mapping.set('gamepad.joystick.right_purpose', BUTTONS)
|
||||||
|
config.set('gamepad.joystick.left_purpose', BUTTONS)
|
||||||
|
|
||||||
|
self.assertTrue(do(new_event(EV_ABS, ecodes.ABS_Y, -1)))
|
||||||
|
self.assertTrue(do(new_event(EV_ABS, ecodes.ABS_RY, -1)))
|
||||||
|
|
||||||
|
def test_normalize_value(self):
|
||||||
|
def do(event):
|
||||||
|
return utils.normalize_value(event, MAX_ABS)
|
||||||
|
|
||||||
# the event.value should be modified for the left joystick
|
|
||||||
# to one of 0, -1 or 1
|
|
||||||
event = new_event(EV_ABS, ecodes.ABS_RX, MAX_ABS)
|
event = new_event(EV_ABS, ecodes.ABS_RX, MAX_ABS)
|
||||||
self.assertFalse(do(event))
|
self.assertEqual(do(event), 1)
|
||||||
self.assertEqual(event.value, MAX_ABS)
|
|
||||||
event = new_event(EV_ABS, ecodes.ABS_Y, -MAX_ABS)
|
event = new_event(EV_ABS, ecodes.ABS_Y, -MAX_ABS)
|
||||||
self.assertTrue(do(event))
|
self.assertEqual(do(event), -1)
|
||||||
self.assertEqual(event.value, -1)
|
|
||||||
event = new_event(EV_ABS, ecodes.ABS_X, -MAX_ABS // 4)
|
event = new_event(EV_ABS, ecodes.ABS_X, -MAX_ABS // 4)
|
||||||
self.assertTrue(do(event))
|
self.assertEqual(do(event), 0)
|
||||||
self.assertEqual(event.value, 0)
|
|
||||||
|
|
||||||
config.set('gamepad.joystick.right_purpose', BUTTONS)
|
|
||||||
|
|
||||||
event = new_event(EV_ABS, ecodes.ABS_RX, MAX_ABS)
|
event = new_event(EV_ABS, ecodes.ABS_RX, MAX_ABS)
|
||||||
self.assertTrue(do(event))
|
self.assertEqual(do(event), 1)
|
||||||
self.assertEqual(event.value, 1)
|
|
||||||
event = new_event(EV_ABS, ecodes.ABS_Y, MAX_ABS)
|
event = new_event(EV_ABS, ecodes.ABS_Y, MAX_ABS)
|
||||||
self.assertTrue(do(event))
|
self.assertEqual(do(event), 1)
|
||||||
self.assertEqual(event.value, 1)
|
|
||||||
event = new_event(EV_ABS, ecodes.ABS_X, MAX_ABS // 4)
|
event = new_event(EV_ABS, ecodes.ABS_X, MAX_ABS // 4)
|
||||||
self.assertTrue(do(event))
|
self.assertEqual(do(event), 0)
|
||||||
self.assertEqual(event.value, 0)
|
|
||||||
|
# if none, it just forwards the value
|
||||||
|
event = new_event(EV_ABS, ecodes.ABS_RX, MAX_ABS)
|
||||||
|
self.assertEqual(utils.normalize_value(event, None), MAX_ABS)
|
||||||
|
@ -25,7 +25,8 @@ import copy
|
|||||||
|
|
||||||
import evdev
|
import evdev
|
||||||
from evdev.ecodes import EV_REL, EV_KEY, EV_ABS, ABS_HAT0X, BTN_LEFT, KEY_A, \
|
from evdev.ecodes import EV_REL, EV_KEY, EV_ABS, ABS_HAT0X, BTN_LEFT, KEY_A, \
|
||||||
REL_X, REL_Y, REL_WHEEL, REL_HWHEEL, BTN_A, ABS_X, ABS_Y
|
REL_X, REL_Y, REL_WHEEL, REL_HWHEEL, BTN_A, ABS_X, ABS_Y, BTN_NORTH,\
|
||||||
|
ABS_Z, ABS_RZ
|
||||||
|
|
||||||
from keymapper.dev.injector import is_numlock_on, set_numlock, \
|
from keymapper.dev.injector import is_numlock_on, set_numlock, \
|
||||||
ensure_numlock, Injector, is_in_capabilities, \
|
ensure_numlock, Injector, is_in_capabilities, \
|
||||||
@ -396,6 +397,33 @@ class TestInjector(unittest.TestCase):
|
|||||||
self.assertEqual(history.count((EV_KEY, 77, 1)), 2)
|
self.assertEqual(history.count((EV_KEY, 77, 1)), 2)
|
||||||
self.assertEqual(history.count((EV_KEY, 77, 0)), 2)
|
self.assertEqual(history.count((EV_KEY, 77, 0)), 2)
|
||||||
|
|
||||||
|
def test_gamepad_trigger(self):
|
||||||
|
# map one of the triggers to BTN_NORTH, while the other one
|
||||||
|
# should be forwarded unchanged
|
||||||
|
value = MAX_ABS // 2
|
||||||
|
pending_events['gamepad'] = [
|
||||||
|
new_event(EV_ABS, ABS_Z, value),
|
||||||
|
new_event(EV_ABS, ABS_RZ, value),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ABS_Z -> 77
|
||||||
|
# ABS_RZ is not mapped
|
||||||
|
custom_mapping.change(Key((EV_ABS, ABS_Z, 1)), 'b')
|
||||||
|
system_mapping._set('b', 77)
|
||||||
|
self.injector = Injector('gamepad', custom_mapping)
|
||||||
|
self.injector.start_injecting()
|
||||||
|
|
||||||
|
# wait for the injector to start sending, at most 1s
|
||||||
|
uinput_write_history_pipe[0].poll(1)
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# convert the write history to some easier to manage list
|
||||||
|
history = read_write_history_pipe()
|
||||||
|
|
||||||
|
print(history)
|
||||||
|
self.assertEqual(history.count((EV_KEY, 77, 1)), 1)
|
||||||
|
self.assertEqual(history.count((EV_ABS, ABS_RZ, value)), 1)
|
||||||
|
|
||||||
def test_gamepad_to_mouse_event_producer(self):
|
def test_gamepad_to_mouse_event_producer(self):
|
||||||
custom_mapping.set('gamepad.joystick.left_purpose', MOUSE)
|
custom_mapping.set('gamepad.joystick.left_purpose', MOUSE)
|
||||||
custom_mapping.set('gamepad.joystick.right_purpose', NONE)
|
custom_mapping.set('gamepad.joystick.right_purpose', NONE)
|
||||||
|
@ -34,6 +34,7 @@ class TestKey(unittest.TestCase):
|
|||||||
self.assertEqual(len(key_1), 2)
|
self.assertEqual(len(key_1), 2)
|
||||||
self.assertEqual(key_1[0], (1, 3, 1))
|
self.assertEqual(key_1[0], (1, 3, 1))
|
||||||
self.assertEqual(key_1[1], (1, 5, 1))
|
self.assertEqual(key_1[1], (1, 5, 1))
|
||||||
|
self.assertEqual(hash(key_1), hash(((1, 3, 1), (1, 5, 1))))
|
||||||
|
|
||||||
key_2 = Key((1, 3, 1))
|
key_2 = Key((1, 3, 1))
|
||||||
self.assertEqual(str(key_2), 'Key((1, 3, 1),)')
|
self.assertEqual(str(key_2), 'Key((1, 3, 1),)')
|
||||||
|
@ -24,17 +24,17 @@ import asyncio
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from evdev.ecodes import EV_KEY, EV_ABS, KEY_A, BTN_TL, \
|
from evdev.ecodes import EV_KEY, EV_ABS, KEY_A, BTN_TL, \
|
||||||
ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y
|
ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_Y
|
||||||
|
|
||||||
from keymapper.dev.keycode_mapper import active_macros, handle_keycode,\
|
from keymapper.dev.keycode_mapper import active_macros, KeycodeMapper, \
|
||||||
unreleased, subsets
|
unreleased, subsets
|
||||||
from keymapper.state import system_mapping
|
from keymapper.state import system_mapping
|
||||||
from keymapper.dev.macros import parse
|
from keymapper.dev.macros import parse
|
||||||
from keymapper.config import config
|
from keymapper.config import config, BUTTONS
|
||||||
from keymapper.mapping import Mapping, DISABLE_CODE
|
from keymapper.mapping import Mapping, DISABLE_CODE
|
||||||
|
|
||||||
from tests.test import new_event, UInput, uinput_write_history, \
|
from tests.test import new_event, UInput, uinput_write_history, \
|
||||||
cleanup
|
cleanup, InputDevice, MAX_ABS
|
||||||
|
|
||||||
|
|
||||||
def wait(func, timeout=1.0):
|
def wait(func, timeout=1.0):
|
||||||
@ -74,6 +74,7 @@ def calculate_event_number(holdtime, before, after):
|
|||||||
class TestKeycodeMapper(unittest.TestCase):
|
class TestKeycodeMapper(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mapping = Mapping()
|
self.mapping = Mapping()
|
||||||
|
self.source = InputDevice('/dev/input/event11')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# make sure all macros are stopped by tests
|
# make sure all macros are stopped by tests
|
||||||
@ -117,9 +118,15 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
_key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
# a bunch of d-pad key down events at once
|
# a bunch of d-pad key down events at once
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_1), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_1))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_4), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_4))
|
||||||
self.assertEqual(len(unreleased), 2)
|
self.assertEqual(len(unreleased), 2)
|
||||||
|
|
||||||
self.assertEqual(unreleased.get(ev_1[:2]).target_type_code, (EV_KEY, _key_to_code[(ev_1,)]))
|
self.assertEqual(unreleased.get(ev_1[:2]).target_type_code, (EV_KEY, _key_to_code[(ev_1,)]))
|
||||||
@ -131,13 +138,13 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertEqual(unreleased.get(ev_4[:2]).key, (ev_4,))
|
self.assertEqual(unreleased.get(ev_4[:2]).key, (ev_4,))
|
||||||
|
|
||||||
# release all of them
|
# release all of them
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_3), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_3))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_6), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_6))
|
||||||
self.assertEqual(len(unreleased), 0)
|
self.assertEqual(len(unreleased), 0)
|
||||||
|
|
||||||
# repeat with other values
|
# repeat with other values
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_2), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_2))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_5), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_5))
|
||||||
self.assertEqual(len(unreleased), 2)
|
self.assertEqual(len(unreleased), 2)
|
||||||
self.assertEqual(unreleased.get(ev_2[:2]).target_type_code, (EV_KEY, _key_to_code[(ev_2,)]))
|
self.assertEqual(unreleased.get(ev_2[:2]).target_type_code, (EV_KEY, _key_to_code[(ev_2,)]))
|
||||||
self.assertEqual(unreleased.get(ev_2[:2]).input_event_tuple, ev_2)
|
self.assertEqual(unreleased.get(ev_2[:2]).input_event_tuple, ev_2)
|
||||||
@ -145,8 +152,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertEqual(unreleased.get(ev_5[:2]).input_event_tuple, ev_5)
|
self.assertEqual(unreleased.get(ev_5[:2]).input_event_tuple, ev_5)
|
||||||
|
|
||||||
# release all of them again
|
# release all of them again
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_3), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_3))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_6), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_6))
|
||||||
self.assertEqual(len(unreleased), 0)
|
self.assertEqual(len(unreleased), 0)
|
||||||
|
|
||||||
self.assertEqual(len(uinput_write_history), 8)
|
self.assertEqual(len(uinput_write_history), 8)
|
||||||
@ -168,16 +175,55 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
up = (EV_KEY, 91, 0)
|
up = (EV_KEY, 91, 0)
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
|
|
||||||
handle_keycode({}, {}, new_event(*down), uinput, False)
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
{}, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(*down), False)
|
||||||
self.assertEqual(unreleased[(EV_KEY, 91)].input_event_tuple, down)
|
self.assertEqual(unreleased[(EV_KEY, 91)].input_event_tuple, down)
|
||||||
self.assertEqual(unreleased[(EV_KEY, 91)].target_type_code, down[:2])
|
self.assertEqual(unreleased[(EV_KEY, 91)].target_type_code, down[:2])
|
||||||
self.assertEqual(len(unreleased), 1)
|
self.assertEqual(len(unreleased), 1)
|
||||||
self.assertEqual(uinput.write_count, 0)
|
self.assertEqual(uinput.write_count, 0)
|
||||||
|
|
||||||
handle_keycode({}, {}, new_event(*up), uinput, False)
|
keycode_mapper.handle_keycode(new_event(*up), False)
|
||||||
self.assertEqual(len(unreleased), 0)
|
self.assertEqual(len(unreleased), 0)
|
||||||
self.assertEqual(uinput.write_count, 0)
|
self.assertEqual(uinput.write_count, 0)
|
||||||
|
|
||||||
|
def test_release_joystick_button(self):
|
||||||
|
# with the left joystick mapped as button, it will release the mapped
|
||||||
|
# key when it goes back to close to its resting position
|
||||||
|
ev_1 = (3, 0, MAX_ABS // 10) # release
|
||||||
|
ev_3 = (3, 0, -MAX_ABS) # press
|
||||||
|
|
||||||
|
uinput = UInput()
|
||||||
|
|
||||||
|
_key_to_code = {
|
||||||
|
((3, 0, -1),): 73
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mapping.set('gamepad.joystick.left_purpose', BUTTONS)
|
||||||
|
|
||||||
|
# something with gamepad capabilities
|
||||||
|
source = InputDevice('/dev/input/event30')
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
source, self.mapping, uinput,
|
||||||
|
_key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(*ev_3))
|
||||||
|
keycode_mapper.handle_keycode(new_event(*ev_1))
|
||||||
|
|
||||||
|
# array of 3-tuples
|
||||||
|
history = [a.t for a in uinput_write_history]
|
||||||
|
|
||||||
|
self.assertIn((EV_KEY, 73, 1), history)
|
||||||
|
self.assertEqual(history.count((EV_KEY, 73, 1)), 1)
|
||||||
|
|
||||||
|
self.assertIn((EV_KEY, 73, 0), history)
|
||||||
|
self.assertEqual(history.count((EV_KEY, 73, 0)), 1)
|
||||||
|
|
||||||
def test_dont_filter_unmapped(self):
|
def test_dont_filter_unmapped(self):
|
||||||
# if an event is not used at all, it should be written into
|
# if an event is not used at all, it should be written into
|
||||||
# unmapped but not furthermore modified. For example wheel events
|
# unmapped but not furthermore modified. For example wheel events
|
||||||
@ -188,15 +234,20 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
up = (EV_KEY, 91, 0)
|
up = (EV_KEY, 91, 0)
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
{}, {}
|
||||||
|
)
|
||||||
|
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
handle_keycode({}, {}, new_event(*down), uinput)
|
keycode_mapper.handle_keycode(new_event(*down))
|
||||||
|
|
||||||
self.assertEqual(unreleased[(EV_KEY, 91)].input_event_tuple, down)
|
self.assertEqual(unreleased[(EV_KEY, 91)].input_event_tuple, down)
|
||||||
self.assertEqual(unreleased[(EV_KEY, 91)].target_type_code, down[:2])
|
self.assertEqual(unreleased[(EV_KEY, 91)].target_type_code, down[:2])
|
||||||
self.assertEqual(len(unreleased), 1)
|
self.assertEqual(len(unreleased), 1)
|
||||||
self.assertEqual(uinput.write_count, 10)
|
self.assertEqual(uinput.write_count, 10)
|
||||||
|
|
||||||
handle_keycode({}, {}, new_event(*up), uinput)
|
keycode_mapper.handle_keycode(new_event(*up))
|
||||||
self.assertEqual(len(unreleased), 0)
|
self.assertEqual(len(unreleased), 0)
|
||||||
self.assertEqual(uinput.write_count, 11)
|
self.assertEqual(uinput.write_count, 11)
|
||||||
|
|
||||||
@ -215,9 +266,14 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
(down_1, down_2): 71
|
(down_1, down_2): 71
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_keycode(key_to_code, {}, new_event(*down_1), uinput)
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(*down_1))
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
handle_keycode(key_to_code, {}, new_event(*down_2), uinput)
|
keycode_mapper.handle_keycode(new_event(*down_2))
|
||||||
|
|
||||||
# all duplicate down events should have been ignored
|
# all duplicate down events should have been ignored
|
||||||
self.assertEqual(len(unreleased), 2)
|
self.assertEqual(len(unreleased), 2)
|
||||||
@ -225,8 +281,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertEqual(uinput_write_history[0].t, down_1)
|
self.assertEqual(uinput_write_history[0].t, down_1)
|
||||||
self.assertEqual(uinput_write_history[1].t, (EV_KEY, output, 1))
|
self.assertEqual(uinput_write_history[1].t, (EV_KEY, output, 1))
|
||||||
|
|
||||||
handle_keycode({}, {}, new_event(*up_1), uinput)
|
keycode_mapper.handle_keycode(new_event(*up_1))
|
||||||
handle_keycode({}, {}, new_event(*up_2), uinput)
|
keycode_mapper.handle_keycode(new_event(*up_2))
|
||||||
self.assertEqual(len(unreleased), 0)
|
self.assertEqual(len(unreleased), 0)
|
||||||
self.assertEqual(uinput.write_count, 4)
|
self.assertEqual(uinput.write_count, 4)
|
||||||
self.assertEqual(uinput_write_history[2].t, up_1)
|
self.assertEqual(uinput_write_history[2].t, up_1)
|
||||||
@ -245,9 +301,15 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
_key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
# a bunch of d-pad key down events at once
|
# a bunch of d-pad key down events at once
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_1), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_1))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_2), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_2))
|
||||||
# (what_will_be_released, what_caused_the_key_down)
|
# (what_will_be_released, what_caused_the_key_down)
|
||||||
self.assertEqual(unreleased.get(ev_1[:2]).target_type_code, (EV_ABS, ABS_HAT0X))
|
self.assertEqual(unreleased.get(ev_1[:2]).target_type_code, (EV_ABS, ABS_HAT0X))
|
||||||
self.assertEqual(unreleased.get(ev_1[:2]).input_event_tuple, ev_1)
|
self.assertEqual(unreleased.get(ev_1[:2]).input_event_tuple, ev_1)
|
||||||
@ -261,8 +323,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 51, 1))
|
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 51, 1))
|
||||||
|
|
||||||
# release all of them
|
# release all of them
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_3), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_3))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_4), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_4))
|
||||||
self.assertEqual(len(unreleased), 0)
|
self.assertEqual(len(unreleased), 0)
|
||||||
|
|
||||||
self.assertEqual(len(uinput_write_history), 4)
|
self.assertEqual(len(uinput_write_history), 4)
|
||||||
@ -276,9 +338,15 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
handle_keycode(_key_to_code, {}, new_event(EV_KEY, 1, 1), uinput)
|
|
||||||
handle_keycode(_key_to_code, {}, new_event(EV_KEY, 3, 1), uinput)
|
keycode_mapper = KeycodeMapper(
|
||||||
handle_keycode(_key_to_code, {}, new_event(EV_KEY, 2, 1), uinput)
|
self.source, self.mapping, uinput,
|
||||||
|
_key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
|
||||||
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 3, 1))
|
||||||
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 1))
|
||||||
|
|
||||||
self.assertEqual(len(uinput_write_history), 3)
|
self.assertEqual(len(uinput_write_history), 3)
|
||||||
self.assertEqual(uinput_write_history[0].t, (EV_KEY, 101, 1))
|
self.assertEqual(uinput_write_history[0].t, (EV_KEY, 101, 1))
|
||||||
@ -292,8 +360,14 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination[0]), uinput)
|
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination[1]), uinput)
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
_key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(*combination[0]))
|
||||||
|
keycode_mapper.handle_keycode(new_event(*combination[1]))
|
||||||
|
|
||||||
self.assertEqual(len(uinput_write_history), 2)
|
self.assertEqual(len(uinput_write_history), 2)
|
||||||
# the first event is written and then the triggered combination
|
# the first event is written and then the triggered combination
|
||||||
@ -301,8 +375,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 101, 1))
|
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 101, 1))
|
||||||
|
|
||||||
# release them
|
# release them
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination[0][:2], 0), uinput)
|
keycode_mapper.handle_keycode(new_event(*combination[0][:2], 0))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination[1][:2], 0), uinput)
|
keycode_mapper.handle_keycode(new_event(*combination[1][:2], 0))
|
||||||
# the first key writes its release event. The second key is hidden
|
# the first key writes its release event. The second key is hidden
|
||||||
# behind the executed combination. The result of the combination is
|
# behind the executed combination. The result of the combination is
|
||||||
# also released, because it acts like a key.
|
# also released, because it acts like a key.
|
||||||
@ -312,8 +386,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
# press them in the wrong order (the wrong key at the end, the order
|
# press them in the wrong order (the wrong key at the end, the order
|
||||||
# of all other keys won't matter). no combination should be triggered
|
# of all other keys won't matter). no combination should be triggered
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination[1]), uinput)
|
keycode_mapper.handle_keycode(new_event(*combination[1]))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination[0]), uinput)
|
keycode_mapper.handle_keycode(new_event(*combination[0]))
|
||||||
self.assertEqual(len(uinput_write_history), 6)
|
self.assertEqual(len(uinput_write_history), 6)
|
||||||
self.assertEqual(uinput_write_history[4].t, (EV_KEY, 2, 1))
|
self.assertEqual(uinput_write_history[4].t, (EV_KEY, 2, 1))
|
||||||
self.assertEqual(uinput_write_history[5].t, (EV_KEY, 1, 1))
|
self.assertEqual(uinput_write_history[5].t, (EV_KEY, 1, 1))
|
||||||
@ -321,7 +395,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
def test_combination_keycode_2(self):
|
def test_combination_keycode_2(self):
|
||||||
combination_1 = (
|
combination_1 = (
|
||||||
(EV_KEY, 1, 1),
|
(EV_KEY, 1, 1),
|
||||||
(EV_KEY, 2, 1),
|
(EV_ABS, ABS_Y, -MAX_ABS),
|
||||||
(EV_KEY, 3, 1),
|
(EV_KEY, 3, 1),
|
||||||
(EV_KEY, 4, 1)
|
(EV_KEY, 4, 1)
|
||||||
)
|
)
|
||||||
@ -337,30 +411,44 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
up_5 = (EV_KEY, 5, 0)
|
up_5 = (EV_KEY, 5, 0)
|
||||||
up_4 = (EV_KEY, 4, 0)
|
up_4 = (EV_KEY, 4, 0)
|
||||||
|
|
||||||
|
def sign_value(key):
|
||||||
|
return key[0], key[1], key[2] / abs(key[2])
|
||||||
|
|
||||||
_key_to_code = {
|
_key_to_code = {
|
||||||
combination_1: 101,
|
# key_to_code is supposed to only contain normalized values
|
||||||
|
tuple([sign_value(a) for a in combination_1]): 101,
|
||||||
combination_2: 102,
|
combination_2: 102,
|
||||||
(down_5,): 103
|
(down_5,): 103
|
||||||
}
|
}
|
||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
# 10 and 11: more key-down events than needed
|
|
||||||
handle_keycode(_key_to_code, {}, new_event(EV_KEY, 10, 1), uinput)
|
source = InputDevice('/dev/input/event30')
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination_1[0]), uinput)
|
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination_1[1]), uinput)
|
keycode_mapper = KeycodeMapper(
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination_1[2]), uinput)
|
source, self.mapping, uinput,
|
||||||
handle_keycode(_key_to_code, {}, new_event(EV_KEY, 11, 1), uinput)
|
_key_to_code, {}
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combination_1[3]), uinput)
|
)
|
||||||
|
|
||||||
|
# 10 and 11: insert some more arbitrary key-down events,
|
||||||
|
# they should not break the combinations
|
||||||
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 10, 1))
|
||||||
|
keycode_mapper.handle_keycode(new_event(*combination_1[0]))
|
||||||
|
keycode_mapper.handle_keycode(new_event(*combination_1[1]))
|
||||||
|
keycode_mapper.handle_keycode(new_event(*combination_1[2]))
|
||||||
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 11, 1))
|
||||||
|
keycode_mapper.handle_keycode(new_event(*combination_1[3]))
|
||||||
|
|
||||||
self.assertEqual(len(uinput_write_history), 6)
|
self.assertEqual(len(uinput_write_history), 6)
|
||||||
# the first event is written and then the triggered combination
|
# the first events are written and then the triggered combination,
|
||||||
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 1, 1))
|
# while the triggering event is the only one that is omitted
|
||||||
self.assertEqual(uinput_write_history[2].t, (EV_KEY, 2, 1))
|
self.assertEqual(uinput_write_history[1].t, combination_1[0])
|
||||||
self.assertEqual(uinput_write_history[3].t, (EV_KEY, 3, 1))
|
self.assertEqual(uinput_write_history[2].t, combination_1[1])
|
||||||
|
self.assertEqual(uinput_write_history[3].t, combination_1[2])
|
||||||
self.assertEqual(uinput_write_history[5].t, (EV_KEY, 101, 1))
|
self.assertEqual(uinput_write_history[5].t, (EV_KEY, 101, 1))
|
||||||
|
|
||||||
# while the combination is down, another unrelated key can be used
|
# while the combination is down, another unrelated key can be used
|
||||||
handle_keycode(_key_to_code, {}, new_event(*down_5), uinput)
|
keycode_mapper.handle_keycode(new_event(*down_5))
|
||||||
# the keycode_mapper searches for subsets of the current held-down
|
# the keycode_mapper searches for subsets of the current held-down
|
||||||
# keys to activate combinations, down_5 should not trigger them
|
# keys to activate combinations, down_5 should not trigger them
|
||||||
# again.
|
# again.
|
||||||
@ -369,8 +457,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
# release the combination by releasing the last key, and release
|
# release the combination by releasing the last key, and release
|
||||||
# the unrelated key
|
# the unrelated key
|
||||||
handle_keycode(_key_to_code, {}, new_event(*up_4), uinput)
|
keycode_mapper.handle_keycode(new_event(*up_4))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*up_5), uinput)
|
keycode_mapper.handle_keycode(new_event(*up_5))
|
||||||
self.assertEqual(len(uinput_write_history), 9)
|
self.assertEqual(len(uinput_write_history), 9)
|
||||||
|
|
||||||
self.assertEqual(uinput_write_history[7].t, (EV_KEY, 101, 0))
|
self.assertEqual(uinput_write_history[7].t, (EV_KEY, 101, 0))
|
||||||
@ -393,8 +481,13 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
macro_mapping[((EV_KEY, 1, 1),)].set_handler(lambda *args: history.append(args))
|
macro_mapping[((EV_KEY, 1, 1),)].set_handler(lambda *args: history.append(args))
|
||||||
macro_mapping[((EV_KEY, 2, 1),)].set_handler(lambda *args: history.append(args))
|
macro_mapping[((EV_KEY, 2, 1),)].set_handler(lambda *args: history.append(args))
|
||||||
|
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 1), None)
|
keycode_mapper = KeycodeMapper(
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 2, 1), None)
|
self.source, self.mapping, None,
|
||||||
|
{}, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
|
||||||
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 1))
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
@ -413,8 +506,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
# releasing stuff
|
# releasing stuff
|
||||||
self.assertIn((EV_KEY, 1), unreleased)
|
self.assertIn((EV_KEY, 1), unreleased)
|
||||||
self.assertIn((EV_KEY, 2), unreleased)
|
self.assertIn((EV_KEY, 2), unreleased)
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0))
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 2, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 0))
|
||||||
self.assertNotIn((EV_KEY, 1), unreleased)
|
self.assertNotIn((EV_KEY, 1), unreleased)
|
||||||
self.assertNotIn((EV_KEY, 2), unreleased)
|
self.assertNotIn((EV_KEY, 2), unreleased)
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
@ -440,9 +533,14 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
macro_mapping[((EV_KEY, 1, 1),)].set_handler(handler)
|
macro_mapping[((EV_KEY, 1, 1),)].set_handler(handler)
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, None,
|
||||||
|
{}, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
"""start macro"""
|
"""start macro"""
|
||||||
|
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 1), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
@ -456,7 +554,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
"""stop macro"""
|
"""stop macro"""
|
||||||
|
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0))
|
||||||
|
|
||||||
loop.run_until_complete(asyncio.sleep(keystroke_sleep * 10 / 1000))
|
loop.run_until_complete(asyncio.sleep(keystroke_sleep * 10 / 1000))
|
||||||
|
|
||||||
@ -510,9 +608,14 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
macro_mapping[((EV_KEY, 2, 1),)].set_handler(handler)
|
macro_mapping[((EV_KEY, 2, 1),)].set_handler(handler)
|
||||||
macro_mapping[((EV_KEY, 3, 1),)].set_handler(handler)
|
macro_mapping[((EV_KEY, 3, 1),)].set_handler(handler)
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, None,
|
||||||
|
{}, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
"""start macro 2"""
|
"""start macro 2"""
|
||||||
|
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 2, 1), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 1))
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
@ -522,8 +625,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
# spam garbage events
|
# spam garbage events
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 1), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 3, 1), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 3, 1))
|
||||||
loop.run_until_complete(asyncio.sleep(0.05))
|
loop.run_until_complete(asyncio.sleep(0.05))
|
||||||
self.assertTrue(active_macros[(EV_KEY, 1)].is_holding())
|
self.assertTrue(active_macros[(EV_KEY, 1)].is_holding())
|
||||||
self.assertTrue(active_macros[(EV_KEY, 1)].running)
|
self.assertTrue(active_macros[(EV_KEY, 1)].running)
|
||||||
@ -541,7 +644,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertNotIn((code_d, 0), history)
|
self.assertNotIn((code_d, 0), history)
|
||||||
|
|
||||||
# stop macro 2
|
# stop macro 2
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 2, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 0))
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
|
|
||||||
# it stopped and didn't restart, so the count stays at 1
|
# it stopped and didn't restart, so the count stays at 1
|
||||||
@ -562,7 +665,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
history = []
|
history = []
|
||||||
|
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 2, 1), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 1))
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
self.assertEqual(history.count((code_c, 1)), 1)
|
self.assertEqual(history.count((code_c, 1)), 1)
|
||||||
self.assertEqual(history.count((code_c, 0)), 1)
|
self.assertEqual(history.count((code_c, 0)), 1)
|
||||||
@ -570,8 +673,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
# spam garbage events again, this time key-up events on all other
|
# spam garbage events again, this time key-up events on all other
|
||||||
# macros
|
# macros
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0))
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 3, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 3, 0))
|
||||||
loop.run_until_complete(asyncio.sleep(0.05))
|
loop.run_until_complete(asyncio.sleep(0.05))
|
||||||
self.assertFalse(active_macros[(EV_KEY, 1)].is_holding())
|
self.assertFalse(active_macros[(EV_KEY, 1)].is_holding())
|
||||||
self.assertFalse(active_macros[(EV_KEY, 1)].running)
|
self.assertFalse(active_macros[(EV_KEY, 1)].running)
|
||||||
@ -581,7 +684,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertFalse(active_macros[(EV_KEY, 3)].running)
|
self.assertFalse(active_macros[(EV_KEY, 3)].running)
|
||||||
|
|
||||||
# stop macro 2
|
# stop macro 2
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 2, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 0))
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
# was started only once
|
# was started only once
|
||||||
self.assertEqual(history.count((code_c, 1)), 1)
|
self.assertEqual(history.count((code_c, 1)), 1)
|
||||||
@ -591,8 +694,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertEqual(history.count((code_d, 0)), 1)
|
self.assertEqual(history.count((code_d, 0)), 1)
|
||||||
|
|
||||||
# stop all macros
|
# stop all macros
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0))
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 3, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 3, 0))
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
|
|
||||||
# it's stopped and won't write stuff anymore
|
# it's stopped and won't write stuff anymore
|
||||||
@ -629,14 +732,19 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
macro_mapping[((EV_KEY, 1, 1),)].set_handler(handler)
|
macro_mapping[((EV_KEY, 1, 1),)].set_handler(handler)
|
||||||
|
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 1), None)
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, None,
|
||||||
|
{}, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
self.assertTrue(active_macros[(EV_KEY, 1)].is_holding())
|
self.assertTrue(active_macros[(EV_KEY, 1)].is_holding())
|
||||||
self.assertTrue(active_macros[(EV_KEY, 1)].running)
|
self.assertTrue(active_macros[(EV_KEY, 1)].running)
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 1), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
|
||||||
loop.run_until_complete(asyncio.sleep(0.05))
|
loop.run_until_complete(asyncio.sleep(0.05))
|
||||||
|
|
||||||
# duplicate key down events don't do anything
|
# duplicate key down events don't do anything
|
||||||
@ -646,7 +754,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertEqual(history.count((code_c, 0)), 0)
|
self.assertEqual(history.count((code_c, 0)), 0)
|
||||||
|
|
||||||
# stop
|
# stop
|
||||||
handle_keycode({}, macro_mapping, new_event(EV_KEY, 1, 0), None)
|
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0))
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
self.assertEqual(history.count((code_a, 1)), 1)
|
self.assertEqual(history.count((code_a, 1)), 1)
|
||||||
self.assertEqual(history.count((code_a, 0)), 1)
|
self.assertEqual(history.count((code_a, 0)), 1)
|
||||||
@ -706,19 +814,29 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
macros_uinput = UInput()
|
macros_uinput = UInput()
|
||||||
keys_uinput = UInput()
|
keys_uinput = UInput()
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, macros_uinput,
|
||||||
|
{}, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
# key up won't do anything
|
# key up won't do anything
|
||||||
handle_keycode({}, macro_mapping, new_event(*up_0), macros_uinput)
|
keycode_mapper.handle_keycode(new_event(*up_0))
|
||||||
handle_keycode({}, macro_mapping, new_event(*up_1), macros_uinput)
|
keycode_mapper.handle_keycode(new_event(*up_1))
|
||||||
handle_keycode({}, macro_mapping, new_event(*up_2), macros_uinput)
|
keycode_mapper.handle_keycode(new_event(*up_2))
|
||||||
loop.run_until_complete(asyncio.sleep(0.1))
|
loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
self.assertEqual(len(active_macros), 0)
|
self.assertEqual(len(active_macros), 0)
|
||||||
|
|
||||||
"""start macros"""
|
"""start macros"""
|
||||||
|
|
||||||
handle_keycode({}, macro_mapping, new_event(*down_0), keys_uinput)
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, keys_uinput,
|
||||||
|
{}, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(*down_0))
|
||||||
self.assertEqual(keys_uinput.write_count, 1)
|
self.assertEqual(keys_uinput.write_count, 1)
|
||||||
handle_keycode({}, macro_mapping, new_event(*down_1), keys_uinput)
|
keycode_mapper.handle_keycode(new_event(*down_1))
|
||||||
handle_keycode({}, macro_mapping, new_event(*down_2), keys_uinput)
|
keycode_mapper.handle_keycode(new_event(*down_2))
|
||||||
self.assertEqual(keys_uinput.write_count, 1)
|
self.assertEqual(keys_uinput.write_count, 1)
|
||||||
|
|
||||||
# let the mainloop run for some time so that the macro does its stuff
|
# let the mainloop run for some time so that the macro does its stuff
|
||||||
@ -738,9 +856,14 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
"""stop macros"""
|
"""stop macros"""
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, None,
|
||||||
|
{}, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
# releasing the last key of a combination releases the whole macro
|
# releasing the last key of a combination releases the whole macro
|
||||||
handle_keycode({}, macro_mapping, new_event(*up_1), None)
|
keycode_mapper.handle_keycode(new_event(*up_1))
|
||||||
handle_keycode({}, macro_mapping, new_event(*up_2), None)
|
keycode_mapper.handle_keycode(new_event(*up_2))
|
||||||
|
|
||||||
self.assertIn(down_0[:2], unreleased)
|
self.assertIn(down_0[:2], unreleased)
|
||||||
self.assertNotIn(down_1[:2], unreleased)
|
self.assertNotIn(down_1[:2], unreleased)
|
||||||
@ -812,11 +935,16 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
macro_mapping[(right,)].set_handler(handler)
|
macro_mapping[(right,)].set_handler(handler)
|
||||||
macro_mapping[(left,)].set_handler(handler)
|
macro_mapping[(left,)].set_handler(handler)
|
||||||
|
|
||||||
handle_keycode({}, macro_mapping, new_event(*right), None)
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, None,
|
||||||
|
{}, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(*right))
|
||||||
self.assertIn((EV_ABS, ABS_HAT0X), unreleased)
|
self.assertIn((EV_ABS, ABS_HAT0X), unreleased)
|
||||||
handle_keycode({}, macro_mapping, new_event(*release), None)
|
keycode_mapper.handle_keycode(new_event(*release))
|
||||||
self.assertNotIn((EV_ABS, ABS_HAT0X), unreleased)
|
self.assertNotIn((EV_ABS, ABS_HAT0X), unreleased)
|
||||||
handle_keycode({}, macro_mapping, new_event(*left), None)
|
keycode_mapper.handle_keycode(new_event(*left))
|
||||||
self.assertIn((EV_ABS, ABS_HAT0X), unreleased)
|
self.assertIn((EV_ABS, ABS_HAT0X), unreleased)
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
@ -840,13 +968,18 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
_key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
"""positive"""
|
"""positive"""
|
||||||
|
|
||||||
for _ in range(1, 20):
|
for _ in range(1, 20):
|
||||||
handle_keycode(_key_to_code, {}, new_event(*trigger, 1), uinput)
|
keycode_mapper.handle_keycode(new_event(*trigger, 1))
|
||||||
self.assertIn(trigger, unreleased)
|
self.assertIn(trigger, unreleased)
|
||||||
|
|
||||||
handle_keycode(_key_to_code, {}, new_event(*trigger, 0), uinput)
|
keycode_mapper.handle_keycode(new_event(*trigger, 0))
|
||||||
self.assertNotIn(trigger, unreleased)
|
self.assertNotIn(trigger, unreleased)
|
||||||
|
|
||||||
self.assertEqual(len(uinput_write_history), 2)
|
self.assertEqual(len(uinput_write_history), 2)
|
||||||
@ -854,10 +987,10 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
"""negative"""
|
"""negative"""
|
||||||
|
|
||||||
for _ in range(1, 20):
|
for _ in range(1, 20):
|
||||||
handle_keycode(_key_to_code, {}, new_event(*trigger, -1), uinput)
|
keycode_mapper.handle_keycode(new_event(*trigger, -1))
|
||||||
self.assertIn(trigger, unreleased)
|
self.assertIn(trigger, unreleased)
|
||||||
|
|
||||||
handle_keycode(_key_to_code, {}, new_event(*trigger, 0), uinput)
|
keycode_mapper.handle_keycode(new_event(*trigger, 0))
|
||||||
self.assertNotIn(trigger, unreleased)
|
self.assertNotIn(trigger, unreleased)
|
||||||
|
|
||||||
self.assertEqual(len(uinput_write_history), 4)
|
self.assertEqual(len(uinput_write_history), 4)
|
||||||
@ -880,13 +1013,19 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_1), uinput)
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
_key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(*ev_1))
|
||||||
|
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_2), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_2))
|
||||||
|
|
||||||
self.assertIn(key, unreleased)
|
self.assertIn(key, unreleased)
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_3), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_3))
|
||||||
self.assertNotIn(key, unreleased)
|
self.assertNotIn(key, unreleased)
|
||||||
|
|
||||||
self.assertEqual(len(uinput_write_history), 2)
|
self.assertEqual(len(uinput_write_history), 2)
|
||||||
@ -915,16 +1054,21 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
_key_to_code, {}
|
||||||
|
)
|
||||||
|
|
||||||
"""single keys"""
|
"""single keys"""
|
||||||
|
|
||||||
# down
|
# down
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_1), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_1))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_3), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_3))
|
||||||
self.assertIn(ev_1[:2], unreleased)
|
self.assertIn(ev_1[:2], unreleased)
|
||||||
self.assertIn(ev_3[:2], unreleased)
|
self.assertIn(ev_3[:2], unreleased)
|
||||||
# up
|
# up
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_2), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_2))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*ev_4), uinput)
|
keycode_mapper.handle_keycode(new_event(*ev_4))
|
||||||
self.assertNotIn(ev_1[:2], unreleased)
|
self.assertNotIn(ev_1[:2], unreleased)
|
||||||
self.assertNotIn(ev_3[:2], unreleased)
|
self.assertNotIn(ev_3[:2], unreleased)
|
||||||
|
|
||||||
@ -935,8 +1079,8 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
"""a combination that ends in a disabled key"""
|
"""a combination that ends in a disabled key"""
|
||||||
|
|
||||||
# ev_5 should be forwarded and the combination triggered
|
# ev_5 should be forwarded and the combination triggered
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combi_1[0]), uinput)
|
keycode_mapper.handle_keycode(new_event(*combi_1[0]))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combi_1[1]), uinput)
|
keycode_mapper.handle_keycode(new_event(*combi_1[1]))
|
||||||
self.assertEqual(len(uinput_write_history), 4)
|
self.assertEqual(len(uinput_write_history), 4)
|
||||||
self.assertEqual(uinput_write_history[2].t, (EV_KEY, KEY_A, 1))
|
self.assertEqual(uinput_write_history[2].t, (EV_KEY, KEY_A, 1))
|
||||||
self.assertEqual(uinput_write_history[3].t, (EV_KEY, 62, 1))
|
self.assertEqual(uinput_write_history[3].t, (EV_KEY, 62, 1))
|
||||||
@ -950,14 +1094,14 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
# release the last key of the combi first, it should
|
# release the last key of the combi first, it should
|
||||||
# release what the combination maps to
|
# release what the combination maps to
|
||||||
event = new_event(combi_1[1][0], combi_1[1][1], 0)
|
event = new_event(combi_1[1][0], combi_1[1][1], 0)
|
||||||
handle_keycode(_key_to_code, {}, event, uinput)
|
keycode_mapper.handle_keycode(event)
|
||||||
self.assertEqual(len(uinput_write_history), 5)
|
self.assertEqual(len(uinput_write_history), 5)
|
||||||
self.assertEqual(uinput_write_history[-1].t, (EV_KEY, 62, 0))
|
self.assertEqual(uinput_write_history[-1].t, (EV_KEY, 62, 0))
|
||||||
self.assertIn(combi_1[0][:2], unreleased)
|
self.assertIn(combi_1[0][:2], unreleased)
|
||||||
self.assertNotIn(combi_1[1][:2], unreleased)
|
self.assertNotIn(combi_1[1][:2], unreleased)
|
||||||
|
|
||||||
event = new_event(combi_1[0][0], combi_1[0][1], 0)
|
event = new_event(combi_1[0][0], combi_1[0][1], 0)
|
||||||
handle_keycode(_key_to_code, {}, event, uinput)
|
keycode_mapper.handle_keycode(event)
|
||||||
self.assertEqual(len(uinput_write_history), 6)
|
self.assertEqual(len(uinput_write_history), 6)
|
||||||
self.assertEqual(uinput_write_history[-1].t, (EV_KEY, KEY_A, 0))
|
self.assertEqual(uinput_write_history[-1].t, (EV_KEY, KEY_A, 0))
|
||||||
self.assertNotIn(combi_1[0][:2], unreleased)
|
self.assertNotIn(combi_1[0][:2], unreleased)
|
||||||
@ -966,22 +1110,22 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
"""a combination that starts with a disabled key"""
|
"""a combination that starts with a disabled key"""
|
||||||
|
|
||||||
# only the combination should get triggered
|
# only the combination should get triggered
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combi_2[0]), uinput)
|
keycode_mapper.handle_keycode(new_event(*combi_2[0]))
|
||||||
handle_keycode(_key_to_code, {}, new_event(*combi_2[1]), uinput)
|
keycode_mapper.handle_keycode(new_event(*combi_2[1]))
|
||||||
self.assertEqual(len(uinput_write_history), 7)
|
self.assertEqual(len(uinput_write_history), 7)
|
||||||
self.assertEqual(uinput_write_history[-1].t, (EV_KEY, 63, 1))
|
self.assertEqual(uinput_write_history[-1].t, (EV_KEY, 63, 1))
|
||||||
|
|
||||||
# release the last key of the combi first, it should
|
# release the last key of the combi first, it should
|
||||||
# release what the combination maps to
|
# release what the combination maps to
|
||||||
event = new_event(combi_2[1][0], combi_2[1][1], 0)
|
event = new_event(combi_2[1][0], combi_2[1][1], 0)
|
||||||
handle_keycode(_key_to_code, {}, event, uinput)
|
keycode_mapper.handle_keycode(event)
|
||||||
self.assertEqual(len(uinput_write_history), 8)
|
self.assertEqual(len(uinput_write_history), 8)
|
||||||
self.assertEqual(uinput_write_history[-1].t, (EV_KEY, 63, 0))
|
self.assertEqual(uinput_write_history[-1].t, (EV_KEY, 63, 0))
|
||||||
|
|
||||||
# the first key of combi_2 is disabled, so it won't write another
|
# the first key of combi_2 is disabled, so it won't write another
|
||||||
# key-up event
|
# key-up event
|
||||||
event = new_event(combi_2[0][0], combi_2[0][1], 0)
|
event = new_event(combi_2[0][0], combi_2[0][1], 0)
|
||||||
handle_keycode(_key_to_code, {}, event, uinput)
|
keycode_mapper.handle_keycode(event)
|
||||||
self.assertEqual(len(uinput_write_history), 8)
|
self.assertEqual(len(uinput_write_history), 8)
|
||||||
|
|
||||||
def test_combination_keycode_macro_mix(self):
|
def test_combination_keycode_macro_mix(self):
|
||||||
@ -1007,8 +1151,13 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping, uinput,
|
||||||
|
_key_to_code, macro_mapping
|
||||||
|
)
|
||||||
|
|
||||||
# macro starts
|
# macro starts
|
||||||
handle_keycode(_key_to_code, macro_mapping, new_event(*down_1), uinput)
|
keycode_mapper.handle_keycode(new_event(*down_1))
|
||||||
loop.run_until_complete(asyncio.sleep(0.05))
|
loop.run_until_complete(asyncio.sleep(0.05))
|
||||||
self.assertEqual(len(uinput_write_history), 0)
|
self.assertEqual(len(uinput_write_history), 0)
|
||||||
self.assertGreater(len(macro_history), 1)
|
self.assertGreater(len(macro_history), 1)
|
||||||
@ -1016,7 +1165,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertIn((92, 1), macro_history)
|
self.assertIn((92, 1), macro_history)
|
||||||
|
|
||||||
# combination triggered
|
# combination triggered
|
||||||
handle_keycode(_key_to_code, macro_mapping, new_event(*down_2), uinput)
|
keycode_mapper.handle_keycode(new_event(*down_2))
|
||||||
self.assertIn(down_1[:2], unreleased)
|
self.assertIn(down_1[:2], unreleased)
|
||||||
self.assertIn(down_2[:2], unreleased)
|
self.assertIn(down_2[:2], unreleased)
|
||||||
self.assertEqual(uinput_write_history[0].t, (EV_KEY, 91, 1))
|
self.assertEqual(uinput_write_history[0].t, (EV_KEY, 91, 1))
|
||||||
@ -1028,7 +1177,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
self.assertGreater(len_b, len_a)
|
self.assertGreater(len_b, len_a)
|
||||||
|
|
||||||
# release
|
# release
|
||||||
handle_keycode(_key_to_code, macro_mapping, new_event(*up_1), uinput)
|
keycode_mapper.handle_keycode(new_event(*up_1))
|
||||||
self.assertNotIn(down_1[:2], unreleased)
|
self.assertNotIn(down_1[:2], unreleased)
|
||||||
self.assertIn(down_2[:2], unreleased)
|
self.assertIn(down_2[:2], unreleased)
|
||||||
loop.run_until_complete(asyncio.sleep(0.05))
|
loop.run_until_complete(asyncio.sleep(0.05))
|
||||||
@ -1038,7 +1187,7 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
# not running anymore
|
# not running anymore
|
||||||
self.assertEqual(len_c, len_d)
|
self.assertEqual(len_c, len_d)
|
||||||
|
|
||||||
handle_keycode(_key_to_code, macro_mapping, new_event(*up_2), uinput)
|
keycode_mapper.handle_keycode(new_event(*up_2))
|
||||||
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 91, 0))
|
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 91, 0))
|
||||||
self.assertEqual(len(uinput_write_history), 2)
|
self.assertEqual(len(uinput_write_history), 2)
|
||||||
self.assertNotIn(down_1[:2], unreleased)
|
self.assertNotIn(down_1[:2], unreleased)
|
||||||
@ -1069,23 +1218,28 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
|
|
||||||
uinput = UInput()
|
uinput = UInput()
|
||||||
|
|
||||||
handle_keycode(k2c, {}, new_event(*btn_down), uinput)
|
keycode_mapper = KeycodeMapper(
|
||||||
|
self.source, self.mapping,
|
||||||
|
uinput, k2c, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
keycode_mapper.handle_keycode(new_event(*btn_down))
|
||||||
# "forwarding"
|
# "forwarding"
|
||||||
self.assertEqual(uinput_write_history[0].t, btn_down)
|
self.assertEqual(uinput_write_history[0].t, btn_down)
|
||||||
|
|
||||||
handle_keycode(k2c, {}, new_event(*scroll), uinput)
|
keycode_mapper.handle_keycode(new_event(*scroll))
|
||||||
# "maps to 30"
|
# "maps to 30"
|
||||||
self.assertEqual(uinput_write_history[1].t, (1, 30, 1))
|
self.assertEqual(uinput_write_history[1].t, (1, 30, 1))
|
||||||
|
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
# keep scrolling
|
# keep scrolling
|
||||||
# "duplicate key down"
|
# "duplicate key down"
|
||||||
handle_keycode(k2c, {}, new_event(*scroll), uinput)
|
keycode_mapper.handle_keycode(new_event(*scroll))
|
||||||
|
|
||||||
# nothing new since all of them were duplicate key downs
|
# nothing new since all of them were duplicate key downs
|
||||||
self.assertEqual(len(uinput_write_history), 2)
|
self.assertEqual(len(uinput_write_history), 2)
|
||||||
|
|
||||||
handle_keycode(k2c, {}, new_event(*btn_up), uinput)
|
keycode_mapper.handle_keycode(new_event(*btn_up))
|
||||||
# "forwarding release"
|
# "forwarding release"
|
||||||
self.assertEqual(uinput_write_history[2].t, btn_up)
|
self.assertEqual(uinput_write_history[2].t, btn_up)
|
||||||
|
|
||||||
@ -1093,14 +1247,14 @@ class TestKeycodeMapper(unittest.TestCase):
|
|||||||
# it should be ignored as duplicate key-down
|
# it should be ignored as duplicate key-down
|
||||||
self.assertEqual(len(uinput_write_history), 3)
|
self.assertEqual(len(uinput_write_history), 3)
|
||||||
# "forwarding" (should be "duplicate key down")
|
# "forwarding" (should be "duplicate key down")
|
||||||
handle_keycode(k2c, {}, new_event(*scroll), uinput)
|
keycode_mapper.handle_keycode(new_event(*scroll))
|
||||||
self.assertEqual(len(uinput_write_history), 3)
|
self.assertEqual(len(uinput_write_history), 3)
|
||||||
|
|
||||||
# the failure to release the mapped key
|
# the failure to release the mapped key
|
||||||
# forward=False is what the debouncer uses, because a
|
# forward=False is what the debouncer uses, because a
|
||||||
# "scroll release" doesn't actually exist so it is not actually
|
# "scroll release" doesn't actually exist so it is not actually
|
||||||
# written if it doesn't release any mapping
|
# written if it doesn't release any mapping
|
||||||
handle_keycode(k2c, {}, new_event(*scroll_up), uinput, forward=False)
|
keycode_mapper.handle_keycode(new_event(*scroll_up), forward=False)
|
||||||
|
|
||||||
# 30 should be released
|
# 30 should be released
|
||||||
self.assertEqual(uinput_write_history[3].t, (1, 30, 0))
|
self.assertEqual(uinput_write_history[3].t, (1, 30, 0))
|
||||||
|
@ -25,7 +25,7 @@ import multiprocessing
|
|||||||
|
|
||||||
from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, ABS_HAT0Y, KEY_COMMA, \
|
from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, ABS_HAT0Y, KEY_COMMA, \
|
||||||
BTN_LEFT, BTN_TOOL_DOUBLETAP, ABS_Z, ABS_Y, ABS_MISC, KEY_A, \
|
BTN_LEFT, BTN_TOOL_DOUBLETAP, ABS_Z, ABS_Y, ABS_MISC, KEY_A, \
|
||||||
EV_REL, REL_WHEEL, REL_X
|
EV_REL, REL_WHEEL, REL_X, ABS_X, ABS_RZ
|
||||||
|
|
||||||
from keymapper.dev.reader import keycode_reader, will_report_up, \
|
from keymapper.dev.reader import keycode_reader, will_report_up, \
|
||||||
event_unix_time
|
event_unix_time
|
||||||
@ -253,7 +253,10 @@ class TestReader(unittest.TestCase):
|
|||||||
# if their purpose is "buttons"
|
# if their purpose is "buttons"
|
||||||
custom_mapping.set('gamepad.joystick.left_purpose', BUTTONS)
|
custom_mapping.set('gamepad.joystick.left_purpose', BUTTONS)
|
||||||
pending_events['gamepad'] = [
|
pending_events['gamepad'] = [
|
||||||
new_event(EV_ABS, ABS_Y, MAX_ABS)
|
new_event(EV_ABS, ABS_Y, MAX_ABS),
|
||||||
|
# the value of that one is interpreted as release, because
|
||||||
|
# it is too small
|
||||||
|
new_event(EV_ABS, ABS_X, MAX_ABS // 10)
|
||||||
]
|
]
|
||||||
keycode_reader.start_reading('gamepad')
|
keycode_reader.start_reading('gamepad')
|
||||||
wait(keycode_reader._pipe[0].poll, 0.5)
|
wait(keycode_reader._pipe[0].poll, 0.5)
|
||||||
@ -271,6 +274,36 @@ class TestReader(unittest.TestCase):
|
|||||||
self.assertEqual(keycode_reader.read(), None)
|
self.assertEqual(keycode_reader.read(), None)
|
||||||
self.assertEqual(len(keycode_reader._unreleased), 0)
|
self.assertEqual(len(keycode_reader._unreleased), 0)
|
||||||
|
|
||||||
|
def test_combine_triggers(self):
|
||||||
|
pipe = multiprocessing.Pipe()
|
||||||
|
keycode_reader._pipe = pipe
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
def next_timestamp():
|
||||||
|
nonlocal i
|
||||||
|
i += 1
|
||||||
|
return 100 * i
|
||||||
|
|
||||||
|
# based on an observed bug
|
||||||
|
pipe[1].send(new_event(3, 1, 0, next_timestamp()))
|
||||||
|
pipe[1].send(new_event(3, 0, 0, next_timestamp()))
|
||||||
|
pipe[1].send(new_event(3, 2, 1, next_timestamp()))
|
||||||
|
self.assertEqual(keycode_reader.read(), (EV_ABS, ABS_Z, 1))
|
||||||
|
pipe[1].send(new_event(3, 0, 0, next_timestamp()))
|
||||||
|
pipe[1].send(new_event(3, 5, 1, next_timestamp()))
|
||||||
|
self.assertEqual(keycode_reader.read(), ((EV_ABS, ABS_Z, 1), (EV_ABS, ABS_RZ, 1)))
|
||||||
|
pipe[1].send(new_event(3, 5, 0, next_timestamp()))
|
||||||
|
pipe[1].send(new_event(3, 0, 0, next_timestamp()))
|
||||||
|
pipe[1].send(new_event(3, 1, 0, next_timestamp()))
|
||||||
|
self.assertEqual(keycode_reader.read(), None)
|
||||||
|
pipe[1].send(new_event(3, 2, 1, next_timestamp()))
|
||||||
|
pipe[1].send(new_event(3, 1, 0, next_timestamp()))
|
||||||
|
pipe[1].send(new_event(3, 0, 0, next_timestamp()))
|
||||||
|
# due to not properly handling the duplicate down event it cleared
|
||||||
|
# the combination and returned it. Instead it should report None
|
||||||
|
# and by doing that keep the previous combination.
|
||||||
|
self.assertEqual(keycode_reader.read(), None)
|
||||||
|
|
||||||
def test_ignore_btn_left(self):
|
def test_ignore_btn_left(self):
|
||||||
# click events are ignored because overwriting them would render the
|
# click events are ignored because overwriting them would render the
|
||||||
# mouse useless, but a mouse is needed to stop the injection
|
# mouse useless, but a mouse is needed to stop the injection
|
||||||
@ -456,6 +489,8 @@ class TestReader(unittest.TestCase):
|
|||||||
def test_prioritizing_3_normalize(self):
|
def test_prioritizing_3_normalize(self):
|
||||||
# take the sign of -1234, just like in test_prioritizing_2_normalize
|
# take the sign of -1234, just like in test_prioritizing_2_normalize
|
||||||
pending_events['device 1'] = [
|
pending_events['device 1'] = [
|
||||||
|
# HAT0X usually reports only -1, 0 and 1, but that shouldn't
|
||||||
|
# matter. Everything is normalized.
|
||||||
new_event(EV_ABS, ABS_HAT0X, -1234, 1234.0000),
|
new_event(EV_ABS, ABS_HAT0X, -1234, 1234.0000),
|
||||||
new_event(EV_ABS, ABS_HAT0Y, 0, 1234.0030) # ignored
|
new_event(EV_ABS, ABS_HAT0Y, 0, 1234.0030) # ignored
|
||||||
# this time don't release anything as well, but it's not
|
# this time don't release anything as well, but it's not
|
||||||
|
Loading…
Reference in New Issue
Block a user