input-remapper/keymapper/injection/context.py

167 lines
6.1 KiB
Python

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# key-mapper - GUI for device specific keyboard mappings
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.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, BUTTONS
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
- 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
uinput : evdev.UInput
Where to inject stuff to. This is an extra node in /dev so that
existing capabilities won't clash.
For example a gamepad can keep being a gamepad, while injected
keycodes appear as keyboard input.
This way the stylus buttons of a graphics tablet can also be mapped
to keys, while the stylus keeps being a stylus.
The main issue is, that the window manager handles events differently
depending on the overall capabilities, and with EV_ABS capabilities
keycodes are pretty much ignored and not written to the desktop.
So this uinput should not have EV_ABS capabilities. Only EV_REL
and EV_KEY is allowed.
"""
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()
self.uinput = None
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 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 joystick_as_dpad(self):
"""If at least one joystick may be mapped to keys."""
purposes = (self.left_purpose, self.right_purpose)
return BUTTONS 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