2022-04-17 10:19:23 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# input-remapper - GUI for device specific keyboard mappings
|
2023-02-27 16:07:42 +00:00
|
|
|
# Copyright (C) 2023 sezanzeb <proxima@sezanzeb.de>
|
2022-04-17 10:19:23 +00:00
|
|
|
#
|
|
|
|
# This file is part of input-remapper.
|
|
|
|
#
|
|
|
|
# input-remapper 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.
|
|
|
|
#
|
|
|
|
# input-remapper 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 input-remapper. If not, see <https://www.gnu.org/licenses/>.
|
2022-11-12 16:45:32 +00:00
|
|
|
|
|
|
|
"""Functions to assemble the mapping handler tree."""
|
|
|
|
|
2022-07-23 08:53:41 +00:00
|
|
|
from collections import defaultdict
|
2022-04-17 10:19:23 +00:00
|
|
|
from typing import Dict, List, Type, Optional, Set, Iterable, Sized, Tuple, Sequence
|
2022-07-23 08:53:41 +00:00
|
|
|
|
2022-10-23 16:39:23 +00:00
|
|
|
from evdev.ecodes import EV_KEY, EV_ABS, EV_REL
|
2022-04-17 10:19:23 +00:00
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
from inputremapper.configs.input_config import InputCombination, InputConfig
|
2022-07-23 08:53:41 +00:00
|
|
|
from inputremapper.configs.mapping import Mapping
|
|
|
|
from inputremapper.configs.preset import Preset
|
|
|
|
from inputremapper.configs.system_mapping import DISABLE_CODE, DISABLE_NAME
|
|
|
|
from inputremapper.exceptions import MappingParsingError
|
|
|
|
from inputremapper.injection.macros.parse import is_this_a_macro
|
2022-07-25 08:55:26 +00:00
|
|
|
from inputremapper.injection.mapping_handlers.abs_to_abs_handler import AbsToAbsHandler
|
2022-07-23 08:53:41 +00:00
|
|
|
from inputremapper.injection.mapping_handlers.abs_to_btn_handler import AbsToBtnHandler
|
|
|
|
from inputremapper.injection.mapping_handlers.abs_to_rel_handler import AbsToRelHandler
|
|
|
|
from inputremapper.injection.mapping_handlers.axis_switch_handler import (
|
|
|
|
AxisSwitchHandler,
|
2022-04-17 10:19:23 +00:00
|
|
|
)
|
|
|
|
from inputremapper.injection.mapping_handlers.combination_handler import (
|
|
|
|
CombinationHandler,
|
|
|
|
)
|
|
|
|
from inputremapper.injection.mapping_handlers.hierarchy_handler import HierarchyHandler
|
|
|
|
from inputremapper.injection.mapping_handlers.key_handler import KeyHandler
|
2022-07-23 08:53:41 +00:00
|
|
|
from inputremapper.injection.mapping_handlers.macro_handler import MacroHandler
|
|
|
|
from inputremapper.injection.mapping_handlers.mapping_handler import (
|
|
|
|
HandlerEnums,
|
|
|
|
MappingHandler,
|
|
|
|
ContextProtocol,
|
|
|
|
InputEventHandler,
|
2022-04-17 10:19:23 +00:00
|
|
|
)
|
|
|
|
from inputremapper.injection.mapping_handlers.null_handler import NullHandler
|
2022-10-02 15:33:52 +00:00
|
|
|
from inputremapper.injection.mapping_handlers.rel_to_abs_handler import RelToAbsHandler
|
2022-07-23 08:53:41 +00:00
|
|
|
from inputremapper.injection.mapping_handlers.rel_to_btn_handler import RelToBtnHandler
|
2022-12-15 13:43:03 +00:00
|
|
|
from inputremapper.injection.mapping_handlers.rel_to_rel_handler import RelToRelHandler
|
2022-07-23 08:53:41 +00:00
|
|
|
from inputremapper.logger import logger
|
2022-10-23 16:39:23 +00:00
|
|
|
from inputremapper.utils import get_evdev_constant_name
|
2022-04-17 10:19:23 +00:00
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
EventPipelines = Dict[InputConfig, Set[InputEventHandler]]
|
2022-04-17 10:19:23 +00:00
|
|
|
|
2022-07-23 08:53:41 +00:00
|
|
|
mapping_handler_classes: Dict[HandlerEnums, Optional[Type[MappingHandler]]] = {
|
2022-04-17 10:19:23 +00:00
|
|
|
# all available mapping_handlers
|
|
|
|
HandlerEnums.abs2btn: AbsToBtnHandler,
|
|
|
|
HandlerEnums.rel2btn: RelToBtnHandler,
|
|
|
|
HandlerEnums.macro: MacroHandler,
|
|
|
|
HandlerEnums.key: KeyHandler,
|
2022-07-23 08:53:41 +00:00
|
|
|
HandlerEnums.btn2rel: None, # can be a macro
|
2022-11-01 11:07:12 +00:00
|
|
|
HandlerEnums.rel2rel: RelToRelHandler,
|
2022-04-17 10:19:23 +00:00
|
|
|
HandlerEnums.abs2rel: AbsToRelHandler,
|
2022-07-23 08:53:41 +00:00
|
|
|
HandlerEnums.btn2abs: None, # can be a macro
|
2022-10-02 15:33:52 +00:00
|
|
|
HandlerEnums.rel2abs: RelToAbsHandler,
|
2022-07-25 08:55:26 +00:00
|
|
|
HandlerEnums.abs2abs: AbsToAbsHandler,
|
2022-04-17 10:19:23 +00:00
|
|
|
HandlerEnums.combination: CombinationHandler,
|
|
|
|
HandlerEnums.hierarchy: HierarchyHandler,
|
|
|
|
HandlerEnums.axisswitch: AxisSwitchHandler,
|
|
|
|
HandlerEnums.disable: NullHandler,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def parse_mappings(preset: Preset, context: ContextProtocol) -> EventPipelines:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""Create a dict with a list of MappingHandler for each InputEvent."""
|
2022-04-17 10:19:23 +00:00
|
|
|
handlers = []
|
|
|
|
for mapping in preset:
|
|
|
|
# start with the last handler in the chain, each mapping only has one output,
|
|
|
|
# but may have multiple inputs, therefore the last handler is a good starting
|
|
|
|
# point to assemble the pipeline
|
|
|
|
handler_enum = _get_output_handler(mapping)
|
|
|
|
constructor = mapping_handler_classes[handler_enum]
|
|
|
|
if not constructor:
|
2022-07-23 08:53:41 +00:00
|
|
|
logger.warning(
|
|
|
|
"a mapping handler '%s' for %s is not implemented",
|
|
|
|
handler_enum,
|
2022-10-16 12:56:21 +00:00
|
|
|
mapping.format_name(),
|
2022-04-17 10:19:23 +00:00
|
|
|
)
|
2022-07-23 08:53:41 +00:00
|
|
|
continue
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
output_handler = constructor(
|
2022-12-15 13:43:03 +00:00
|
|
|
mapping.input_combination,
|
2022-04-18 11:52:59 +00:00
|
|
|
mapping,
|
|
|
|
context=context,
|
2022-04-17 10:19:23 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# layer other handlers on top until the outer handler needs ranking or can
|
|
|
|
# directly handle a input event
|
|
|
|
handlers.extend(_create_event_pipeline(output_handler, context))
|
|
|
|
|
|
|
|
# figure out which handlers need ranking and wrap them with hierarchy_handlers
|
2022-07-23 08:53:41 +00:00
|
|
|
need_ranking = defaultdict(set)
|
2022-04-17 10:19:23 +00:00
|
|
|
for handler in handlers.copy():
|
|
|
|
if handler.needs_ranking():
|
|
|
|
combination = handler.rank_by()
|
|
|
|
if not combination:
|
|
|
|
raise MappingParsingError(
|
|
|
|
f"{type(handler).__name__} claims to need ranking but does not "
|
|
|
|
f"return a combination to rank by",
|
|
|
|
mapping_handler=handler,
|
|
|
|
)
|
2022-11-12 16:45:32 +00:00
|
|
|
|
2022-07-23 08:53:41 +00:00
|
|
|
need_ranking[combination].add(handler)
|
2022-04-17 10:19:23 +00:00
|
|
|
handlers.remove(handler)
|
|
|
|
|
2022-11-01 11:07:12 +00:00
|
|
|
# the HierarchyHandler's might not be the starting point of the event pipeline,
|
2022-04-17 10:19:23 +00:00
|
|
|
# layer other handlers on top again.
|
|
|
|
ranked_handlers = _create_hierarchy_handlers(need_ranking)
|
|
|
|
for handler in ranked_handlers:
|
|
|
|
handlers.extend(_create_event_pipeline(handler, context, ignore_ranking=True))
|
|
|
|
|
|
|
|
# group all handlers by the input events they take care of. One handler might end
|
|
|
|
# up in multiple groups if it takes care of multiple InputEvents
|
2022-07-23 08:53:41 +00:00
|
|
|
event_pipelines: EventPipelines = defaultdict(set)
|
2022-04-17 10:19:23 +00:00
|
|
|
for handler in handlers:
|
2022-12-15 13:43:03 +00:00
|
|
|
assert handler.input_configs
|
|
|
|
for input_config in handler.input_configs:
|
2022-10-23 16:39:23 +00:00
|
|
|
logger.debug(
|
|
|
|
"event-pipeline with entry point: %s %s",
|
2022-12-15 13:43:03 +00:00
|
|
|
get_evdev_constant_name(*input_config.type_and_code),
|
|
|
|
input_config.input_match_hash,
|
2022-10-23 16:39:23 +00:00
|
|
|
)
|
2022-07-23 08:53:41 +00:00
|
|
|
logger.debug_mapping_handler(handler)
|
2022-12-15 13:43:03 +00:00
|
|
|
event_pipelines[input_config].add(handler)
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
return event_pipelines
|
|
|
|
|
|
|
|
|
|
|
|
def _create_event_pipeline(
|
|
|
|
handler: MappingHandler, context: ContextProtocol, ignore_ranking=False
|
|
|
|
) -> List[MappingHandler]:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""Recursively wrap a handler with other handlers until the
|
2022-11-12 16:45:32 +00:00
|
|
|
outer handler needs ranking or is finished wrapping.
|
2022-04-17 10:19:23 +00:00
|
|
|
"""
|
|
|
|
if not handler.needs_wrapping() or (handler.needs_ranking() and not ignore_ranking):
|
|
|
|
return [handler]
|
|
|
|
|
|
|
|
handlers = []
|
|
|
|
for combination, handler_enum in handler.wrap_with().items():
|
|
|
|
constructor = mapping_handler_classes[handler_enum]
|
|
|
|
if not constructor:
|
|
|
|
raise NotImplementedError(
|
|
|
|
f"mapping handler {handler_enum} is not implemented"
|
|
|
|
)
|
|
|
|
|
|
|
|
super_handler = constructor(combination, handler.mapping, context=context)
|
|
|
|
super_handler.set_sub_handler(handler)
|
|
|
|
for event in combination:
|
|
|
|
# the handler now has a super_handler which takes care about the events.
|
|
|
|
# so we need to hide them on the handler
|
|
|
|
handler.occlude_input_event(event)
|
|
|
|
|
|
|
|
handlers.extend(_create_event_pipeline(super_handler, context))
|
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
if handler.input_configs:
|
2022-04-17 10:19:23 +00:00
|
|
|
# the handler was only partially wrapped,
|
|
|
|
# we need to return it as a toplevel handler
|
|
|
|
handlers.append(handler)
|
|
|
|
|
|
|
|
return handlers
|
|
|
|
|
|
|
|
|
|
|
|
def _get_output_handler(mapping: Mapping) -> HandlerEnums:
|
2022-11-12 16:45:32 +00:00
|
|
|
"""Determine the correct output handler.
|
|
|
|
|
2022-04-17 10:19:23 +00:00
|
|
|
this is used as a starting point for the mapping parser
|
|
|
|
"""
|
|
|
|
if mapping.output_code == DISABLE_CODE or mapping.output_symbol == DISABLE_NAME:
|
|
|
|
return HandlerEnums.disable
|
|
|
|
|
|
|
|
if mapping.output_symbol:
|
|
|
|
if is_this_a_macro(mapping.output_symbol):
|
|
|
|
return HandlerEnums.macro
|
2022-11-12 16:45:32 +00:00
|
|
|
|
|
|
|
return HandlerEnums.key
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
if mapping.output_type == EV_KEY:
|
|
|
|
return HandlerEnums.key
|
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
input_event = _maps_axis(mapping.input_combination)
|
2022-04-17 10:19:23 +00:00
|
|
|
if not input_event:
|
|
|
|
raise MappingParsingError(
|
2022-11-12 16:45:32 +00:00
|
|
|
f"This {mapping = } does not map to an axis, key or macro",
|
2022-04-18 11:52:59 +00:00
|
|
|
mapping=Mapping,
|
2022-04-17 10:19:23 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if mapping.output_type == EV_REL:
|
|
|
|
if input_event.type == EV_KEY:
|
|
|
|
return HandlerEnums.btn2rel
|
|
|
|
if input_event.type == EV_REL:
|
|
|
|
return HandlerEnums.rel2rel
|
|
|
|
if input_event.type == EV_ABS:
|
|
|
|
return HandlerEnums.abs2rel
|
|
|
|
|
|
|
|
if mapping.output_type == EV_ABS:
|
|
|
|
if input_event.type == EV_KEY:
|
|
|
|
return HandlerEnums.btn2abs
|
|
|
|
if input_event.type == EV_REL:
|
|
|
|
return HandlerEnums.rel2abs
|
|
|
|
if input_event.type == EV_ABS:
|
|
|
|
return HandlerEnums.abs2abs
|
|
|
|
|
|
|
|
raise MappingParsingError(f"the output of {mapping = } is unknown", mapping=Mapping)
|
|
|
|
|
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
def _maps_axis(combination: InputCombination) -> Optional[InputConfig]:
|
|
|
|
"""Whether this InputCombination contains an InputEvent that is treated as
|
2022-04-17 10:19:23 +00:00
|
|
|
an axis and not a binary (key or button) event.
|
|
|
|
"""
|
|
|
|
for event in combination:
|
2022-12-15 13:43:03 +00:00
|
|
|
if event.defines_analog_input:
|
2022-04-17 10:19:23 +00:00
|
|
|
return event
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def _create_hierarchy_handlers(
|
2022-12-15 13:43:03 +00:00
|
|
|
handlers: Dict[InputCombination, Set[MappingHandler]]
|
2022-04-17 10:19:23 +00:00
|
|
|
) -> Set[MappingHandler]:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""Sort handlers by input events and create Hierarchy handlers."""
|
2022-04-17 10:19:23 +00:00
|
|
|
sorted_handlers = set()
|
|
|
|
all_combinations = handlers.keys()
|
|
|
|
events = set()
|
|
|
|
|
|
|
|
# gather all InputEvents from all handlers
|
|
|
|
for combination in all_combinations:
|
|
|
|
for event in combination:
|
|
|
|
events.add(event)
|
|
|
|
|
|
|
|
# create a ranking for each event
|
|
|
|
for event in events:
|
|
|
|
# find all combinations (from handlers) which contain the event
|
|
|
|
combinations_with_event = [
|
|
|
|
combination for combination in all_combinations if event in combination
|
|
|
|
]
|
|
|
|
|
|
|
|
if len(combinations_with_event) == 1:
|
|
|
|
# there was only one handler containing that event return it as is
|
2022-07-23 08:53:41 +00:00
|
|
|
sorted_handlers.update(handlers[combinations_with_event[0]])
|
2022-04-17 10:19:23 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
# there are multiple handler with the same event.
|
|
|
|
# rank them and create the HierarchyHandler
|
|
|
|
sorted_combinations = _order_combinations(combinations_with_event, event)
|
2022-07-23 08:53:41 +00:00
|
|
|
sub_handlers: List[MappingHandler] = []
|
2022-04-17 10:19:23 +00:00
|
|
|
for combination in sorted_combinations:
|
2022-07-23 08:53:41 +00:00
|
|
|
sub_handlers.append(*handlers[combination])
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
sorted_handlers.add(HierarchyHandler(sub_handlers, event))
|
|
|
|
for handler in sub_handlers:
|
|
|
|
# the handler now has a HierarchyHandler which takes care about this event.
|
|
|
|
# so we hide need to hide it on the handler
|
|
|
|
handler.occlude_input_event(event)
|
|
|
|
|
|
|
|
return sorted_handlers
|
|
|
|
|
|
|
|
|
|
|
|
def _order_combinations(
|
2022-12-15 13:43:03 +00:00
|
|
|
combinations: List[InputCombination], common_config: InputConfig
|
|
|
|
) -> List[InputCombination]:
|
2022-11-12 16:45:32 +00:00
|
|
|
"""Reorder the keys according to some rules.
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
such that a combination a+b+c is in front of a+b which is in front of b
|
|
|
|
for a+b+c vs. b+d+e: a+b+c would be in front of b+d+e, because the common key b
|
|
|
|
has the higher index in the a+b+c (1), than in the b+c+d (0) list
|
|
|
|
in this example b would be the common key
|
|
|
|
as for combinations like a+b+c and e+d+c with the common key c: ¯\\_(ツ)_/¯
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2022-11-12 16:45:32 +00:00
|
|
|
combinations
|
2022-04-17 10:19:23 +00:00
|
|
|
the list which needs ordering
|
2022-12-15 13:43:03 +00:00
|
|
|
common_config
|
|
|
|
the InputConfig all InputCombination's in combinations have in common
|
2022-04-17 10:19:23 +00:00
|
|
|
"""
|
|
|
|
combinations.sort(key=len)
|
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
for start, end in _ranges_with_constant_length(combinations.copy()):
|
2022-04-17 10:19:23 +00:00
|
|
|
sub_list = combinations[start:end]
|
2022-12-15 13:43:03 +00:00
|
|
|
sub_list.sort(key=lambda x: x.index(common_config))
|
2022-04-17 10:19:23 +00:00
|
|
|
combinations[start:end] = sub_list
|
|
|
|
|
|
|
|
combinations.reverse()
|
|
|
|
return combinations
|
|
|
|
|
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
def _ranges_with_constant_length(x: Sequence[Sized]) -> Iterable[Tuple[int, int]]:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""Get all ranges of x for which the elements have constant length
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
x: Sequence[Sized]
|
|
|
|
l must be ordered by increasing length of elements
|
|
|
|
"""
|
|
|
|
start_idx = 0
|
|
|
|
last_len = 0
|
|
|
|
for idx, y in enumerate(x):
|
|
|
|
if len(y) > last_len and idx - start_idx > 1:
|
|
|
|
yield start_idx, idx
|
|
|
|
|
|
|
|
if len(y) == last_len and idx + 1 == len(x):
|
|
|
|
yield start_idx, idx + 1
|
|
|
|
|
|
|
|
if len(y) > last_len:
|
|
|
|
start_idx = idx
|
|
|
|
|
|
|
|
if len(y) < last_len:
|
|
|
|
raise MappingParsingError(
|
2022-11-12 16:45:32 +00:00
|
|
|
"ranges_with_constant_length was called with an unordered list"
|
2022-04-17 10:19:23 +00:00
|
|
|
)
|
|
|
|
last_len = len(y)
|