moved injection-wide infos to a dedicated context class

pull/45/head
sezanzeb 4 years ago
parent a6b69be088
commit e81328d110

@ -0,0 +1,153 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# key-mapper - GUI for device specific keyboard mappings
# Copyright (C) 2021 sezanzeb <proxima@hip70890b.de>
#
# This file is part of key-mapper.
#
# key-mapper is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# key-mapper is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with key-mapper. If not, see <https://www.gnu.org/licenses/>.
"""Stores injection-process wide information."""
from keymapper.logger import logger
from keymapper.injection.macros import parse, is_this_a_macro
from keymapper.state import system_mapping
from keymapper.config import NONE, MOUSE, WHEEL
class Context:
"""
Stores injection-process wide information.
In some ways this is a wrapper for the mapping that derives some
information that is specifically important to the injection.
One Context exists for each injection process, which is shared
with all coroutines and used objects.
Benefits of the context:
- less redundant passing around of parameters
- more clear which parameters are shared between coroutines and which
ones are unique to a certain method
- easier to add new process wide information without having to adjust
all function calls in unittests
- makes the injection class shorter and more specific to a certain task,
which is actually spinning up the injection.
Members
-------
mapping : Mapping
the mapping that is the source of key_to_code and macros,
only used to query config values.
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
"""
def __init__(self, mapping):
self.mapping = mapping
# avoid searching through the mapping at runtime,
# might be a bit expensive
self.key_to_code = self._map_keys_to_codes()
self.macros = self._parse_macros()
self.left_purpose = None
self.right_purpose = None
self.update_purposes()
def update_purposes(self):
"""Read joystick purposes from the configuration."""
self.left_purpose = self.mapping.get('gamepad.joystick.left_purpose')
self.right_purpose = self.mapping.get('gamepad.joystick.right_purpose')
def _parse_macros(self):
"""To quickly get the target macro during operation."""
logger.debug('Parsing macros')
macros = {}
for key, output in self.mapping:
if is_this_a_macro(output):
macro = parse(output, self.mapping)
if macro is None:
continue
for permutation in key.get_permutations():
macros[permutation.keys] = macro
if len(macros) == 0:
logger.debug('No macros configured')
return macros
def _map_keys_to_codes(self):
"""To quickly get target keycodes during operation.
Returns a mapping of one or more 3-tuples to ints.
Examples:
((1, 2, 1),): 3
((1, 5, 1), (1, 4, 1)): 4
"""
key_to_code = {}
for key, output in self.mapping:
if is_this_a_macro(output):
continue
target_code = system_mapping.get(output)
if target_code is None:
logger.error('Don\'t know what %s is', output)
continue
for permutation in key.get_permutations():
if permutation.keys[-1][-1] not in [-1, 1]:
logger.error(
'Expected values to be -1 or 1 at this point: %s',
permutation.keys
)
key_to_code[permutation.keys] = target_code
return key_to_code
def is_mapped(self, key):
"""Check if this key is used for macros or mappings.
Parameters
----------
key : ((int, int, int),)
One or more 3-tuples of type, code, value
"""
return key in self.macros or key in self.key_to_code
def forwards_joystick(self):
"""If at least one of the joysticks remains a regular joystick."""
return NONE in (self.left_purpose, self.right_purpose)
def maps_joystick(self):
"""If at least one of the joysticks will serve a special purpose."""
return (self.left_purpose, self.right_purpose) != (NONE, NONE)
def joystick_as_mouse(self):
"""If at least one joystick maps to an EV_REL capability."""
purposes = (self.left_purpose, self.right_purpose)
return MOUSE in purposes or WHEEL in purposes
def writes_keys(self):
"""Check if anything is being mapped to keys."""
return len(self.macros) == 0 and len(self.key_to_code) == 0

@ -51,15 +51,10 @@ class EventProducer:
This class does not handle injecting macro stuff over time, that is done
by the keycode_mapper.
"""
def __init__(self, mapping):
"""Construct the event producer without it doing anything yet.
def __init__(self, context):
"""Construct the event producer without it doing anything yet."""
self.context = context
Parameters
----------
mapping : Mapping
the mapping object that configures the current injection
"""
self.mapping = mapping
self.mouse_uinput = None
self.max_abs = None
# events only take ints, so a movement of 0.3 needs to add
@ -69,15 +64,6 @@ class EventProducer:
self.abs_state = {ABS_X: 0, ABS_Y: 0, ABS_RX: 0, ABS_RY: 0}
self.debounces = {}
self.left_purpose = None
self.right_purpose = None
self.update_purposes()
def update_purposes(self):
"""Figure out how the joysticks should be used"""
self.left_purpose = self.mapping.get('gamepad.joystick.left_purpose')
self.right_purpose = self.mapping.get('gamepad.joystick.right_purpose')
def notify(self, event):
"""Tell the EventProducer about the newest ABS event.
@ -150,7 +136,7 @@ class EventProducer:
self.max_abs = max_abs
logger.debug('Max abs of "%s": %s', device.name, max_abs)
def get_abs_values(self, left_purpose, right_purpose):
def get_abs_values(self):
"""Get the raw values for wheel and mouse movement.
If two joysticks have the same purpose, the one that reports higher
@ -158,26 +144,26 @@ class EventProducer:
"""
mouse_x, mouse_y, wheel_x, wheel_y = 0, 0, 0, 0
if left_purpose == MOUSE:
if self.context.left_purpose == MOUSE:
mouse_x = abs_max(mouse_x, self.abs_state[ABS_X])
mouse_y = abs_max(mouse_y, self.abs_state[ABS_Y])
if left_purpose == WHEEL:
if self.context.left_purpose == WHEEL:
wheel_x = abs_max(wheel_x, self.abs_state[ABS_X])
wheel_y = abs_max(wheel_y, self.abs_state[ABS_Y])
if right_purpose == MOUSE:
if self.context.right_purpose == MOUSE:
mouse_x = abs_max(mouse_x, self.abs_state[ABS_RX])
mouse_y = abs_max(mouse_y, self.abs_state[ABS_RY])
if right_purpose == WHEEL:
if self.context.right_purpose == WHEEL:
wheel_x = abs_max(wheel_x, self.abs_state[ABS_RX])
wheel_y = abs_max(wheel_y, self.abs_state[ABS_RY])
return mouse_x, mouse_y, wheel_x, wheel_y
def is_handled(self, event):
"""Check if the event is something ev_abs will take care of."""
"""Check if the event is something this will take care of."""
if event.type != EV_ABS or event.code not in utils.JOYSTICK:
return False
@ -185,11 +171,13 @@ class EventProducer:
return False
purposes = [MOUSE, WHEEL]
left_purpose = self.context.left_purpose
right_purpose = self.context.right_purpose
if event.code in (ABS_X, ABS_Y) and self.left_purpose in purposes:
if event.code in (ABS_X, ABS_Y) and left_purpose in purposes:
return True
if event.code in (ABS_RX, ABS_RY) and self.right_purpose in purposes:
if event.code in (ABS_RX, ABS_RY) and right_purpose in purposes:
return True
return False
@ -201,19 +189,17 @@ class EventProducer:
its position, this will keep injecting the mouse movement events.
"""
max_abs = self.max_abs
mapping = self.mapping
mapping = self.context.mapping
pointer_speed = mapping.get('gamepad.joystick.pointer_speed')
non_linearity = mapping.get('gamepad.joystick.non_linearity')
x_scroll_speed = mapping.get('gamepad.joystick.x_scroll_speed')
y_scroll_speed = mapping.get('gamepad.joystick.y_scroll_speed')
left_purpose = self.left_purpose
right_purpose = self.right_purpose
if max_abs is not None:
logger.info(
'Left joystick as %s, right joystick as %s',
left_purpose,
right_purpose
self.context.left_purpose,
self.context.right_purpose
)
start = time.time()
@ -243,7 +229,7 @@ class EventProducer:
max_speed = ((max_abs ** 2) * 2) ** 0.5
abs_values = self.get_abs_values(left_purpose, right_purpose)
abs_values = self.get_abs_values()
if len([val for val in abs_values if val > max_abs]) > 0:
logger.error(

@ -36,10 +36,8 @@ from keymapper.getdevices import get_devices, is_gamepad
from keymapper.injection.keycode_mapper import KeycodeMapper
from keymapper import utils
from keymapper.injection.event_producer import EventProducer
from keymapper.injection.macros import parse, is_this_a_macro
from keymapper.state import system_mapping
from keymapper.mapping import DISABLE_CODE
from keymapper.config import NONE, MOUSE, WHEEL
from keymapper.injection.context import Context
DEV_NAME = 'key-mapper'
@ -148,11 +146,12 @@ class Injector(multiprocessing.Process):
mapping : Mapping
"""
self.device = device
self.mapping = mapping
self._key_to_code = self._map_keys_to_codes()
self._event_producer = None
self._state = UNKNOWN
self._msg_pipe = multiprocessing.Pipe()
self.context = Context(mapping)
super().__init__()
# Functions to interact with the running process:
@ -200,54 +199,6 @@ class Injector(multiprocessing.Process):
# Process internal stuff:
def _forwards_joystick(self):
"""If at least one of the joysticks remains a regular joystick."""
left_purpose = self.mapping.get('gamepad.joystick.left_purpose')
right_purpose = self.mapping.get('gamepad.joystick.right_purpose')
return NONE in (left_purpose, right_purpose)
def _maps_joystick(self):
"""If at least one of the joysticks will serve a special purpose."""
left_purpose = self.mapping.get('gamepad.joystick.left_purpose')
right_purpose = self.mapping.get('gamepad.joystick.right_purpose')
return (left_purpose, right_purpose) != (NONE, NONE)
def _joystick_as_mouse(self):
"""If at least one joystick maps to an EV_REL capability."""
purposes = (
self.mapping.get('gamepad.joystick.left_purpose'),
self.mapping.get('gamepad.joystick.right_purpose')
)
return MOUSE in purposes or WHEEL in purposes
def _map_keys_to_codes(self):
"""To quickly get target keycodes during operation.
Returns a mapping of one or more 3-tuples to ints.
Examples:
((1, 2, 1),): 3
((1, 5, 1), (1, 4, 1)): 4
"""
key_to_code = {}
for key, output in self.mapping:
if is_this_a_macro(output):
continue
target_code = system_mapping.get(output)
if target_code is None:
logger.error('Don\'t know what %s is', output)
continue
for permutation in key.get_permutations():
if permutation.keys[-1][-1] not in [-1, 1]:
logger.error(
'Expected values to be -1 or 1 at this point: %s',
permutation.keys
)
key_to_code[permutation.keys] = target_code
return key_to_code
def _grab_device(self, path):
"""Try to grab the device, return None if not needed/possible."""
try:
@ -259,14 +210,14 @@ class Injector(multiprocessing.Process):
capabilities = device.capabilities(absinfo=False)
needed = False
for key, _ in self.mapping:
for key, _ in self.context.mapping:
if is_in_capabilities(key, capabilities):
needed = True
break
gamepad = is_gamepad(device)
if gamepad and self._maps_joystick():
if gamepad and self.context.maps_joystick():
needed = True
if not needed:
@ -301,7 +252,7 @@ class Injector(multiprocessing.Process):
return device
def _modify_capabilities(self, macros, input_device, gamepad):
def _modify_capabilities(self, input_device, gamepad):
"""Adds all used keycodes into a copy of a devices capabilities.
Sometimes capabilities are a bit tricky and change how the system
@ -309,8 +260,6 @@ class Injector(multiprocessing.Process):
Parameters
----------
macros : dict
mapping of int to _Macro
input_device : evdev.InputDevice
gamepad : bool
if ABS capabilities should be removed in favor of REL
@ -326,11 +275,11 @@ class Injector(multiprocessing.Process):
# to act like the device.
capabilities = input_device.capabilities(absinfo=True)
if (self._key_to_code or macros) and capabilities.get(EV_KEY) is None:
if self.context.writes_keys and capabilities.get(EV_KEY) is None:
capabilities[EV_KEY] = []
# Furthermore, support all injected keycodes
for code in self._key_to_code.values():
for code in self.context.key_to_code.values():
if code == DISABLE_CODE:
continue
@ -338,10 +287,10 @@ class Injector(multiprocessing.Process):
capabilities[EV_KEY].append(code)
# and all keycodes that are injected by macros
for macro in macros.values():
for macro in self.context.macros.values():
capabilities[EV_KEY] += list(macro.get_capabilities())
if gamepad and self._joystick_as_mouse():
if gamepad and self.context.joystick_as_mouse():
# REL_WHEEL was also required to recognize the gamepad
# as mouse, even if no joystick is used as wheel.
capabilities[EV_REL] = [
@ -364,7 +313,7 @@ class Injector(multiprocessing.Process):
del capabilities[ecodes.EV_SYN]
if ecodes.EV_FF in capabilities:
del capabilities[ecodes.EV_FF]
if gamepad and not self._forwards_joystick():
if gamepad and not self.context.forwards_joystick():
# Key input to text inputs and such only works without ABS
# events in the capabilities, possibly due to some intentional
# constraints in wayland/X. So if the joysticks are not used
@ -408,6 +357,8 @@ class Injector(multiprocessing.Process):
logger.error('Cannot inject for unknown device "%s"', self.device)
return
logger.info('Starting injecting the mapping for "%s"', self.device)
# create a new event loop, because somehow running an infinite loop
# that sleeps on iterations (event_producer) in one process causes
# another injection process to screw up reading from the grabbed
@ -415,32 +366,13 @@ class Injector(multiprocessing.Process):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
numlock_state = is_numlock_on()
self._event_producer = EventProducer(self.context)
numlock_state = is_numlock_on()
coroutines = []
logger.info('Starting injecting the mapping for "%s"', self.device)
paths = get_devices()[self.device]['paths']
self._event_producer = EventProducer(self.mapping)
logger.debug('Parsing macros')
macros = {}
for key, output in self.mapping:
if is_this_a_macro(output):
macro = parse(output, self.mapping)
if macro is None:
continue
for permutation in key.get_permutations():
macros[permutation.keys] = macro
if len(macros) == 0:
logger.debug('No macros configured')
# Watch over each one of the potentially multiple devices per hardware
for path in paths:
for path in get_devices()[self.device]['paths']:
source = self._grab_device(path)
if source is None:
# this path doesn't need to be grabbed for injection, because
@ -459,7 +391,7 @@ class Injector(multiprocessing.Process):
uinput = evdev.UInput(
name=f'{DEV_NAME} {self.device}',
phys=DEV_NAME,
events=self._modify_capabilities(macros, source, gamepad)
events=self._modify_capabilities(source, gamepad)
)
logger.spam(
@ -468,12 +400,12 @@ class Injector(multiprocessing.Process):
)
# actual reading of events
coroutines.append(self._event_consumer(macros, source, uinput))
coroutines.append(self._event_consumer(source, uinput))
# The event source of the current iteration will deliver events
# that are needed for this. It is that one that will be mapped
# to a mouse-like devnode.
if gamepad and self._joystick_as_mouse():
if gamepad and self.context.joystick_as_mouse():
self._event_producer.set_max_abs_from(source)
self._event_producer.set_mouse_uinput(uinput)
@ -513,7 +445,7 @@ class Injector(multiprocessing.Process):
uinput.write(EV_KEY, code, value)
uinput.syn()
async def _event_consumer(self, macros, source, uinput):
async def _event_consumer(self, source, uinput):
"""Reads input events to inject keycodes or talk to the event_producer.
Can be stopped by stopping the asyncio loop. This loop
@ -523,8 +455,6 @@ class Injector(multiprocessing.Process):
Parameters
----------
macros : int: _Macro
macro with a handler that writes to the provided uinput
source : evdev.InputDevice
where to read keycodes from
uinput : evdev.UInput
@ -535,10 +465,7 @@ class Injector(multiprocessing.Process):
source.path, source.fd
)
keycode_handler = KeycodeMapper(
source, self.mapping, uinput,
self._key_to_code, macros
)
keycode_handler = KeycodeMapper(self.context, source, uinput)
async for event in source.async_read_loop():
if self._event_producer.is_handled(event):
@ -547,12 +474,10 @@ class Injector(multiprocessing.Process):
continue
# for mapped stuff
if utils.should_map_event_as_btn(event, self.mapping):
if utils.should_map_event_as_btn(event, self.context.mapping):
will_report_key_up = utils.will_report_key_up(event)
keycode_handler.handle_keycode(
event,
)
keycode_handler.handle_keycode(event)
if not will_report_key_up:
# simulate a key-up event if no down event arrives anymore.
@ -561,10 +486,7 @@ class Injector(multiprocessing.Process):
self._event_producer.debounce(
debounce_id=(event.type, event.code, event.value),
func=keycode_handler.handle_keycode,
args=(
release,
False
),
args=(release, False),
ticks=3,
)

@ -193,7 +193,7 @@ def print_unreleased():
class KeycodeMapper:
"""Injects keycodes and starts macros."""
def __init__(self, source, mapping, uinput, key_to_code, macros):
def __init__(self, context, source, uinput):
"""Create a keycode mapper for one virtual device.
There may be multiple KeycodeMappers for one hardware device. They
@ -201,34 +201,21 @@ class KeycodeMapper:
Parameters
----------
context : Context
the configuration of the Injector process
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.context = context
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, to not provide redundant parameters
for key in key_to_code:
for key in context.key_to_code:
for sub_key in key:
if abs(sub_key[2]) > 1:
raise ValueError(
@ -236,9 +223,6 @@ class KeycodeMapper:
f'but got {key}'
)
self.key_to_code = key_to_code
self.macros = macros
def macro_write(self, code, value):
"""Handler for macros."""
self.uinput.write(EV_KEY, code, value)
@ -301,7 +285,7 @@ class KeycodeMapper:
# the newest input are of interest
continue
if subset in self.macros or subset in self.key_to_code:
if self.context.is_mapped(subset):
key = subset
break
else:
@ -347,7 +331,7 @@ class KeycodeMapper:
active_macro = active_macros.get(type_code)
key = self._get_key(event_tuple)
is_mapped = key in self.macros or key in self.key_to_code
is_mapped = self.context.is_mapped(key)
"""Releasing keys and macros"""
@ -402,7 +386,9 @@ class KeycodeMapper:
return
# it would start a macro usually
if key in self.macros and active_macro and active_macro.running:
in_macros = key in self.context.macros
running = active_macro and active_macro.running
if in_macros and running:
# for key-down events and running macros, don't do anything.
# This avoids spawning a second macro while the first one is
# not finished, especially since gamepad-triggers report a ton
@ -417,8 +403,8 @@ class KeycodeMapper:
# triggering a combination, so they should be remembered in
# unreleased
if key in self.macros:
macro = self.macros[key]
if key in self.context.macros:
macro = self.context.macros[key]
active_macros[type_code] = macro
Unreleased((None, None), event_tuple, key)
macro.press_key()
@ -426,8 +412,8 @@ class KeycodeMapper:
asyncio.ensure_future(macro.run(self.macro_write))
return
if key in self.key_to_code:
target_code = self.key_to_code[key]
if key in self.context.key_to_code:
target_code = self.context.key_to_code[key]
# remember the key that triggered this
# (this combination or this single key)
Unreleased((EV_KEY, target_code), event_tuple, key)

@ -2,11 +2,5 @@
This folder contains all classes that are only relevant for the injection
process. There is one process for each device that is being injected for,
and one context object that is being passed around everywhere for all to use.
The benefit of the context object over regular parameters is that the same
parameters don't have to be passed to classes and stored redundantly all
the time. The context is like the processes global configuration and you
can use whatever is inside. Just don't modify it. If you access a context
member in two classes you definitely know that those two are working with
the same thing without having to rely on scattering your pointers everywhere.
and one context object per process that is being passed around for all
classes to use.

@ -353,7 +353,7 @@ class TestDaemon(unittest.TestCase):
# after passing device and preset, the correct preset is read
# from the path
injector = daemon.injectors[device]
self.assertEqual(injector.mapping.get_character(Key(3, 2, 1)), 'a')
self.assertEqual(injector.context.mapping.get_character(Key(3, 2, 1)), 'a')
# start again
previous_injector = daemon.injectors[device]
@ -367,7 +367,7 @@ class TestDaemon(unittest.TestCase):
self.assertNotEqual(previous_injector, daemon.injectors[device])
self.assertNotEqual(daemon.injectors[device].get_state(), STOPPED)
injector = daemon.injectors[device]
self.assertEqual(injector.mapping.get_character(Key(3, 2, 1)), 'a')
self.assertEqual(injector.context.mapping.get_character(Key(3, 2, 1)), 'a')
# trying to inject a non existing preset keeps the previous inejction
# alive

@ -27,6 +27,7 @@ from evdev.ecodes import EV_REL, REL_X, REL_Y, REL_WHEEL, REL_HWHEEL, \
from keymapper.config import config
from keymapper.mapping import Mapping
from keymapper.injection.context import Context
from keymapper.injection.event_producer import EventProducer, MOUSE, WHEEL
from tests.test import InputDevice, UInput, MAX_ABS, clear_write_history, \
@ -37,16 +38,16 @@ abs_state = [0, 0, 0, 0]
class TestEventProducer(unittest.TestCase):
# there is also `test_abs_to_rel` in test_injector.py
def setUp(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
self.mapping = Mapping()
self.context = Context(self.mapping)
device = InputDevice('/dev/input/event30')
uinput = UInput()
self.event_producer = EventProducer(self.mapping)
self.event_producer = EventProducer(self.context)
self.event_producer.set_max_abs_from(device)
self.event_producer.set_mouse_uinput(uinput)
asyncio.ensure_future(self.event_producer.run())
@ -113,7 +114,7 @@ class TestEventProducer(unittest.TestCase):
def do(self, a, b, c, d, expectation):
"""Present fake values to the loop and observe the outcome."""
clear_write_history()
self.event_producer.update_purposes()
self.event_producer.context.update_purposes()
self.event_producer.notify(new_event(EV_ABS, ABS_X, a))
self.event_producer.notify(new_event(EV_ABS, ABS_Y, b))
self.event_producer.notify(new_event(EV_ABS, ABS_RX, c))

@ -86,8 +86,8 @@ class TestInjector(unittest.TestCase):
# this test needs to pass around all other constraints of
# _grab_device
device = self.injector._grab_device(path)
abs_to_rel = is_gamepad(device)
self.assertFalse(abs_to_rel)
gamepad = is_gamepad(device)
self.assertFalse(gamepad)
self.assertEqual(self.failed, 2)
# success on the third try
device.name = fixtures[path]['name']
@ -132,15 +132,11 @@ class TestInjector(unittest.TestCase):
path = '/dev/input/event30'
device = self.injector._grab_device(path)
abs_to_rel = is_gamepad(device)
gamepad = is_gamepad(device)
self.assertIsNotNone(device)
self.assertTrue(abs_to_rel)
self.assertTrue(gamepad)
capabilities = self.injector._modify_capabilities(
{},
device,
abs_to_rel
)
capabilities = self.injector._modify_capabilities(device, gamepad)
self.assertNotIn(EV_ABS, capabilities)
self.assertIn(EV_REL, capabilities)
@ -168,11 +164,7 @@ class TestInjector(unittest.TestCase):
self.assertIsNotNone(device)
gamepad = is_gamepad(device)
self.assertTrue(gamepad)
capabilities = self.injector._modify_capabilities(
{},
device,
gamepad
)
capabilities = self.injector._modify_capabilities(device, gamepad)
self.assertIn(EV_ABS, capabilities)
def test_gamepad_purpose_none_2(self):
@ -189,11 +181,7 @@ class TestInjector(unittest.TestCase):
self.assertIsNotNone(device)
gamepad = is_gamepad(device)
self.assertTrue(gamepad)
capabilities = self.injector._modify_capabilities(
{},
device,
gamepad
)
capabilities = self.injector._modify_capabilities(device, gamepad)
self.assertIn(EV_ABS, capabilities)
self.assertIn(EV_REL, capabilities)
@ -202,11 +190,7 @@ class TestInjector(unittest.TestCase):
gamepad = is_gamepad(device)
self.assertIsNotNone(device)
self.assertTrue(gamepad)
capabilities = self.injector._modify_capabilities(
{},
device,
gamepad
)
capabilities = self.injector._modify_capabilities(device, gamepad)
self.assertIn(EV_ABS, capabilities)
self.assertIn(EV_REL, capabilities)
self.assertIn(EV_KEY, capabilities)
@ -228,9 +212,7 @@ class TestInjector(unittest.TestCase):
device = self.injector._grab_device(path)
gamepad = is_gamepad(device)
self.assertNotIn(EV_KEY, device.capabilities())
capabilities = self.injector._modify_capabilities(
{}, device, gamepad
)
capabilities = self.injector._modify_capabilities(device, gamepad)
self.assertIn(EV_KEY, capabilities)
self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
@ -246,9 +228,7 @@ class TestInjector(unittest.TestCase):
fixtures[path]['capabilities'][EV_KEY].append(KEY_A)
device = self.injector._grab_device(path)
gamepad = is_gamepad(device)
capabilities = self.injector._modify_capabilities(
{}, device, gamepad
)
capabilities = self.injector._modify_capabilities(device, gamepad)
self.assertIn(EV_KEY, capabilities)
self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
self.assertIn(evdev.ecodes.KEY_A, capabilities[EV_KEY])
@ -260,9 +240,7 @@ class TestInjector(unittest.TestCase):
gamepad = is_gamepad(device)
self.assertIn(EV_KEY, device.capabilities())
self.assertNotIn(evdev.ecodes.BTN_MOUSE, device.capabilities()[EV_KEY])
capabilities = self.injector._modify_capabilities(
{}, device, gamepad
)
capabilities = self.injector._modify_capabilities(device, gamepad)
self.assertIn(EV_KEY, capabilities)
self.assertGreater(len(capabilities), 1)
self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
@ -731,9 +709,10 @@ class TestInjector(unittest.TestCase):
# one call
self.assertEqual(len(history), 1)
# first argument of the first call
self.assertEqual(len(history[0][0]), 2)
self.assertEqual(history[0][0][(ev_1, ev_2, ev_3)].code, 'k(a)')
self.assertEqual(history[0][0][(ev_2, ev_1, ev_3)].code, 'k(a)')
macros = self.injector.context.macros
self.assertEqual(len(macros), 2)
self.assertEqual(macros[(ev_1, ev_2, ev_3)].code, 'k(a)')
self.assertEqual(macros[(ev_2, ev_1, ev_3)].code, 'k(a)')
def test_key_to_code(self):
mapping = Mapping()
@ -751,11 +730,11 @@ class TestInjector(unittest.TestCase):
system_mapping._set('b', 52)
injector = Injector('device 1', mapping)
self.assertEqual(injector._key_to_code.get((ev_1,)), 51)
self.assertEqual(injector.context.key_to_code.get((ev_1,)), 51)
# permutations to make matching combinations easier
self.assertEqual(injector._key_to_code.get((ev_2, ev_3, ev_4)), 52)
self.assertEqual(injector._key_to_code.get((ev_3, ev_2, ev_4)), 52)
self.assertEqual(len(injector._key_to_code), 3)
self.assertEqual(injector.context.key_to_code.get((ev_2, ev_3, ev_4)), 52)
self.assertEqual(injector.context.key_to_code.get((ev_3, ev_2, ev_4)), 52)
self.assertEqual(len(injector.context.key_to_code), 3)
def test_is_in_capabilities(self):
key = Key(1, 2, 1)
@ -840,10 +819,11 @@ class TestModifyCapabilities(unittest.TestCase):
quick_cleanup()
def test_modify_capabilities(self):
self.mapping.change(Key(EV_KEY, 60, 1), self.macro.code)
self.injector = Injector('foo', self.mapping)
capabilities = self.injector._modify_capabilities(
{60: self.macro},
self.fake_device,
gamepad=False
)
@ -851,7 +831,8 @@ class TestModifyCapabilities(unittest.TestCase):
self.assertIn(EV_ABS, capabilities)
self.check_keys(capabilities)
keys = capabilities[EV_KEY]
# mouse capabilities are not needed
# mouse capabilities were not present in the fake_device and are
# still not needed
self.assertNotIn(self.left, keys)
self.assertNotIn(evdev.ecodes.EV_SYN, capabilities)
@ -870,6 +851,8 @@ class TestModifyCapabilities(unittest.TestCase):
self.assertEqual(capabilities[EV_ABS][2], 3)
def test_no_abs_volume(self):
self.mapping.change(Key(EV_KEY, 60, 1), self.macro.code)
# I don't know what ABS_VOLUME is, for now I would like to just always
# remove it until somebody complains
self.injector = Injector('foo', self.mapping)
@ -878,23 +861,23 @@ class TestModifyCapabilities(unittest.TestCase):
}
capabilities = self.injector._modify_capabilities(
{60: self.macro},
self.fake_device,
gamepad=False
)
self.assertNotIn(ABS_VOLUME, capabilities[EV_ABS])
def test_modify_capabilities_gamepad(self):
self.mapping.change(Key((EV_KEY, 60, 1)), self.macro.code)
config.set('gamepad.joystick.left_purpose', MOUSE)
self.mapping.set('gamepad.joystick.right_purpose', WHEEL)
self.injector = Injector('foo', self.mapping)
self.assertFalse(self.injector._forwards_joystick())
self.assertTrue(self.injector._maps_joystick())
self.assertTrue(self.injector._joystick_as_mouse())
self.assertFalse(self.injector.context.forwards_joystick())
self.assertTrue(self.injector.context.maps_joystick())
self.assertTrue(self.injector.context.joystick_as_mouse())
capabilities = self.injector._modify_capabilities(
{60: self.macro},
self.fake_device,
gamepad=True
)
@ -909,16 +892,17 @@ class TestModifyCapabilities(unittest.TestCase):
self.assertIn(self.left, keys)
def test_modify_capabilities_gamepad_none_none(self):
self.mapping.change(Key(EV_KEY, 60, 1), self.macro.code)
config.set('gamepad.joystick.left_purpose', NONE)
self.mapping.set('gamepad.joystick.right_purpose', NONE)
self.injector = Injector('foo', self.mapping)
self.assertTrue(self.injector._forwards_joystick())
self.assertFalse(self.injector._maps_joystick())
self.assertFalse(self.injector._joystick_as_mouse())
self.assertTrue(self.injector.context.forwards_joystick())
self.assertFalse(self.injector.context.maps_joystick())
self.assertFalse(self.injector.context.joystick_as_mouse())
capabilities = self.injector._modify_capabilities(
{60: self.macro},
self.fake_device,
gamepad=True
)
@ -927,16 +911,17 @@ class TestModifyCapabilities(unittest.TestCase):
self.assertIn(EV_ABS, capabilities)
def test_modify_capabilities_gamepad_buttons_buttons(self):
self.mapping.change(Key((EV_KEY, 60, 1)), self.macro.code)
config.set('gamepad.joystick.left_purpose', BUTTONS)
self.mapping.set('gamepad.joystick.right_purpose', BUTTONS)
self.injector = Injector('foo', self.mapping)
self.assertFalse(self.injector._forwards_joystick())
self.assertTrue(self.injector._maps_joystick())
self.assertFalse(self.injector._joystick_as_mouse())
self.assertFalse(self.injector.context.forwards_joystick())
self.assertTrue(self.injector.context.maps_joystick())
self.assertFalse(self.injector.context.joystick_as_mouse())
capabilities = self.injector._modify_capabilities(
{60: self.macro},
self.fake_device,
gamepad=True
)
@ -946,6 +931,8 @@ class TestModifyCapabilities(unittest.TestCase):
self.assertNotIn(EV_REL, capabilities)
def test_modify_capabilities_buttons_buttons(self):
self.mapping.change(Key(EV_KEY, 60, 1), self.macro.code)
# those settings shouldn't have an effect with gamepad=False
config.set('gamepad.joystick.left_purpose', BUTTONS)
self.mapping.set('gamepad.joystick.right_purpose', BUTTONS)
@ -953,7 +940,6 @@ class TestModifyCapabilities(unittest.TestCase):
self.injector = Injector('foo', self.mapping)
capabilities = self.injector._modify_capabilities(
{60: self.macro},
self.fake_device,
gamepad=False
)

@ -30,6 +30,7 @@ from keymapper.injection.keycode_mapper import active_macros, KeycodeMapper, \
unreleased, subsets
from keymapper.state import system_mapping
from keymapper.injection.macros import parse
from keymapper.injection.context import Context
from keymapper.config import config, BUTTONS
from keymapper.mapping import Mapping, DISABLE_CODE
@ -110,30 +111,33 @@ class TestKeycodeMapper(unittest.TestCase):
ev_5 = (EV_ABS, ABS_HAT0Y, -1)
ev_6 = (EV_ABS, ABS_HAT0Y, 0)
_key_to_code = {
uinput = UInput()
context = Context(self.mapping)
context.key_to_code = {
(ev_1,): 51,
(ev_2,): 52,
(ev_4,): 54,
(ev_5,): 55,
}
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
_key_to_code, {}
)
keycode_mapper = KeycodeMapper(context, self.source, uinput)
# a bunch of d-pad key down events at once
keycode_mapper.handle_keycode(new_event(*ev_1))
keycode_mapper.handle_keycode(new_event(*ev_4))
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, context.key_to_code[(ev_1,)])
)
self.assertEqual(unreleased.get(ev_1[:2]).input_event_tuple, ev_1)
self.assertEqual(unreleased.get(ev_1[:2]).key, (ev_1,)) # as seen in _key_to_code
self.assertEqual(unreleased.get(ev_1[:2]).key, (ev_1,)) # as seen in key_to_code
self.assertEqual(unreleased.get(ev_4[:2]).target_type_code, (EV_KEY, _key_to_code[(ev_4,)]), ev_4)
self.assertEqual(
unreleased.get(ev_4[:2]).target_type_code,
(EV_KEY, context.key_to_code[(ev_4,)]), ev_4
)
self.assertEqual(unreleased.get(ev_4[:2]).input_event_tuple, ev_4)
self.assertEqual(unreleased.get(ev_4[:2]).key, (ev_4,))
@ -146,10 +150,22 @@ class TestKeycodeMapper(unittest.TestCase):
keycode_mapper.handle_keycode(new_event(*ev_2))
keycode_mapper.handle_keycode(new_event(*ev_5))
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]).input_event_tuple, ev_2)
self.assertEqual(unreleased.get(ev_5[:2]).target_type_code, (EV_KEY, _key_to_code[(ev_5,)]))
self.assertEqual(unreleased.get(ev_5[:2]).input_event_tuple, ev_5)
self.assertEqual(
unreleased.get(ev_2[:2]).target_type_code,
(EV_KEY, context.key_to_code[(ev_2,)])
)
self.assertEqual(
unreleased.get(ev_2[:2]).input_event_tuple,
ev_2
)
self.assertEqual(
unreleased.get(ev_5[:2]).target_type_code,
(EV_KEY, context.key_to_code[(ev_5,)])
)
self.assertEqual(
unreleased.get(ev_5[:2]).input_event_tuple,
ev_5
)
# release all of them again
keycode_mapper.handle_keycode(new_event(*ev_3))
@ -175,10 +191,8 @@ class TestKeycodeMapper(unittest.TestCase):
up = (EV_KEY, 91, 0)
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
{}, {}
)
context = Context(self.mapping)
keycode_mapper = KeycodeMapper(context, self.source, uinput)
keycode_mapper.handle_keycode(new_event(*down), False)
self.assertEqual(unreleased[(EV_KEY, 91)].input_event_tuple, down)
@ -207,10 +221,9 @@ class TestKeycodeMapper(unittest.TestCase):
# something with gamepad capabilities
source = InputDevice('/dev/input/event30')
keycode_mapper = KeycodeMapper(
source, self.mapping, uinput,
_key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
keycode_mapper = KeycodeMapper(context, source, uinput)
keycode_mapper.handle_keycode(new_event(*ev_3))
keycode_mapper.handle_keycode(new_event(*ev_1))
@ -234,10 +247,8 @@ class TestKeycodeMapper(unittest.TestCase):
up = (EV_KEY, 91, 0)
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
{}, {}
)
context = Context(self.mapping)
keycode_mapper = KeycodeMapper(context, self.source, uinput)
for _ in range(10):
keycode_mapper.handle_keycode(new_event(*down))
@ -266,10 +277,9 @@ class TestKeycodeMapper(unittest.TestCase):
(down_1, down_2): 71
}
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = key_to_code
keycode_mapper = KeycodeMapper(context, self.source, uinput)
keycode_mapper.handle_keycode(new_event(*down_1))
for _ in range(10):
@ -302,10 +312,9 @@ class TestKeycodeMapper(unittest.TestCase):
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
_key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
keycode_mapper = KeycodeMapper(context, self.source, uinput)
# a bunch of d-pad key down events at once
keycode_mapper.handle_keycode(new_event(*ev_1))
@ -339,10 +348,9 @@ class TestKeycodeMapper(unittest.TestCase):
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
_key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
keycode_mapper = KeycodeMapper(context, self.source, uinput)
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 1))
keycode_mapper.handle_keycode(new_event(EV_KEY, 3, 1))
@ -361,10 +369,9 @@ class TestKeycodeMapper(unittest.TestCase):
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
_key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
keycode_mapper = KeycodeMapper(context, self.source, uinput)
keycode_mapper.handle_keycode(new_event(*combination[0]))
keycode_mapper.handle_keycode(new_event(*combination[1]))
@ -425,10 +432,9 @@ class TestKeycodeMapper(unittest.TestCase):
source = InputDevice('/dev/input/event30')
keycode_mapper = KeycodeMapper(
source, self.mapping, uinput,
_key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
keycode_mapper = KeycodeMapper(context, source, uinput)
# 10 and 11: insert some more arbitrary key-down events,
# they should not break the combinations
@ -478,10 +484,9 @@ class TestKeycodeMapper(unittest.TestCase):
((EV_KEY, 2, 1),): parse('r(5, k(b))', self.mapping)
}
keycode_mapper = KeycodeMapper(
self.source, self.mapping, None,
{}, macro_mapping
)
context = Context(self.mapping)
context.macros = macro_mapping
keycode_mapper = KeycodeMapper(context, self.source, None)
keycode_mapper.macro_write = lambda *args: history.append(args)
keycode_mapper.macro_write = lambda *args: history.append(args)
@ -531,10 +536,9 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args):
history.append(args)
keycode_mapper = KeycodeMapper(
self.source, self.mapping, None,
{}, macro_mapping
)
context = Context(self.mapping)
context.macros = macro_mapping
keycode_mapper = KeycodeMapper(context, self.source, None)
keycode_mapper.macro_write = handler
@ -604,10 +608,9 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args):
history.append(args)
keycode_mapper = KeycodeMapper(
self.source, self.mapping, None,
{}, macro_mapping
)
context = Context(self.mapping)
context.macros = macro_mapping
keycode_mapper = KeycodeMapper(context, self.source, None)
keycode_mapper.macro_write = handler
keycode_mapper.macro_write = handler
@ -730,10 +733,9 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args):
history.append(args)
keycode_mapper = KeycodeMapper(
self.source, self.mapping, None,
{}, macro_mapping
)
context = Context(self.mapping)
context.macros = macro_mapping
keycode_mapper = KeycodeMapper(context, self.source, None)
keycode_mapper.macro_write = handler
@ -808,12 +810,12 @@ class TestKeycodeMapper(unittest.TestCase):
loop = asyncio.get_event_loop()
context = Context(self.mapping)
context.macros = macro_mapping
uinput_1 = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput_1,
{}, macro_mapping
)
keycode_mapper = KeycodeMapper(context, self.source, uinput_1)
keycode_mapper.macro_write = handler
@ -828,10 +830,7 @@ class TestKeycodeMapper(unittest.TestCase):
uinput_2 = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput_2,
{}, macro_mapping
)
keycode_mapper = KeycodeMapper(context, self.source, uinput_2)
keycode_mapper.macro_write = handler
@ -858,10 +857,7 @@ class TestKeycodeMapper(unittest.TestCase):
"""stop macros"""
keycode_mapper = KeycodeMapper(
self.source, self.mapping, None,
{}, macro_mapping
)
keycode_mapper = KeycodeMapper(context, self.source, None)
# releasing the last key of a combination releases the whole macro
keycode_mapper.handle_keycode(new_event(*up_1))
@ -934,10 +930,9 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args):
history.append(args)
keycode_mapper = KeycodeMapper(
self.source, self.mapping, None,
{}, macro_mapping
)
context = Context(self.mapping)
context.macros = macro_mapping
keycode_mapper = KeycodeMapper(context, self.source, None)
keycode_mapper.macro_write = handler
keycode_mapper.macro_write = handler
@ -970,10 +965,9 @@ class TestKeycodeMapper(unittest.TestCase):
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
_key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
keycode_mapper = KeycodeMapper(context, self.source, uinput)
"""positive"""
@ -1016,10 +1010,9 @@ class TestKeycodeMapper(unittest.TestCase):
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
_key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
keycode_mapper = KeycodeMapper(context, self.source, uinput)
keycode_mapper.handle_keycode(new_event(*ev_1))
@ -1056,10 +1049,9 @@ class TestKeycodeMapper(unittest.TestCase):
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
_key_to_code, {}
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
keycode_mapper = KeycodeMapper(context, self.source, uinput)
"""single keys"""
@ -1152,10 +1144,10 @@ class TestKeycodeMapper(unittest.TestCase):
loop = asyncio.get_event_loop()
keycode_mapper = KeycodeMapper(
self.source, self.mapping, uinput,
_key_to_code, macro_mapping
)
context = Context(self.mapping)
context.key_to_code = _key_to_code
context.macros = macro_mapping
keycode_mapper = KeycodeMapper(context, self.source, uinput)
keycode_mapper.macro_write = handler
@ -1221,10 +1213,9 @@ class TestKeycodeMapper(unittest.TestCase):
uinput = UInput()
keycode_mapper = KeycodeMapper(
self.source, self.mapping,
uinput, k2c, {}
)
context = Context(self.mapping)
context.key_to_code = k2c
keycode_mapper = KeycodeMapper(context, self.source, uinput)
keycode_mapper.handle_keycode(new_event(*btn_down))
# "forwarding"

Loading…
Cancel
Save