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-04-18 11:52:59 +00:00
|
|
|
"""Provides protocols for mapping handlers
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
*** The architecture behind mapping handlers ***
|
|
|
|
|
|
|
|
Handling an InputEvent is done in 3 steps:
|
|
|
|
1. Input Event Handling
|
2022-11-12 16:45:32 +00:00
|
|
|
A MappingHandler that does Input event handling receives Input Events directly
|
|
|
|
from the EventReader.
|
2022-04-17 10:19:23 +00:00
|
|
|
To do so it must implement the InputEventHandler protocol.
|
2022-11-01 11:07:12 +00:00
|
|
|
An InputEventHandler may handle multiple events (InputEvent.type_and_code)
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
2. Event Transformation
|
|
|
|
The event gets transformed as described by the mapping.
|
|
|
|
e.g.: combining multiple events to a single one
|
|
|
|
transforming EV_ABS to EV_REL
|
|
|
|
macros
|
|
|
|
...
|
|
|
|
Multiple transformations may get chained
|
|
|
|
|
|
|
|
3. Event Injection
|
|
|
|
The transformed event gets injected to a global_uinput
|
|
|
|
|
|
|
|
MappingHandlers can implement one or more of these steps.
|
|
|
|
|
|
|
|
Overview of implemented handlers and the steps they implement:
|
|
|
|
|
|
|
|
Step 1:
|
|
|
|
- HierarchyHandler
|
|
|
|
|
|
|
|
Step 1 and 2:
|
|
|
|
- CombinationHandler
|
|
|
|
- AbsToBtnHandler
|
|
|
|
- RelToBtnHandler
|
|
|
|
|
|
|
|
Step 1, 2 and 3:
|
|
|
|
- AbsToRelHandler
|
2022-07-23 08:53:41 +00:00
|
|
|
- NullHandler
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
Step 2 and 3:
|
|
|
|
- KeyHandler
|
|
|
|
- MacroHandler
|
|
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import enum
|
2022-07-23 08:53:41 +00:00
|
|
|
from typing import Dict, Protocol, Set, Optional, List
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
import evdev
|
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
from inputremapper.configs.input_config import InputCombination, InputConfig
|
2022-04-17 10:19:23 +00:00
|
|
|
from inputremapper.configs.mapping import Mapping
|
|
|
|
from inputremapper.exceptions import MappingParsingError
|
2022-12-15 13:43:03 +00:00
|
|
|
from inputremapper.input_event import InputEvent
|
2022-04-17 10:19:23 +00:00
|
|
|
from inputremapper.logger import logger
|
|
|
|
|
|
|
|
|
|
|
|
class EventListener(Protocol):
|
|
|
|
async def __call__(self, event: evdev.InputEvent) -> None:
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
class ContextProtocol(Protocol):
|
2023-02-19 20:19:29 +00:00
|
|
|
"""The parts from context needed for handlers."""
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
listeners: Set[EventListener]
|
|
|
|
|
2023-02-19 20:19:29 +00:00
|
|
|
def get_forward_uinput(self, origin_hash) -> evdev.UInput:
|
|
|
|
pass
|
|
|
|
|
2022-04-17 10:19:23 +00:00
|
|
|
|
2022-07-23 08:53:41 +00:00
|
|
|
class NotifyCallback(Protocol):
|
|
|
|
"""Type signature of InputEventHandler.notify
|
|
|
|
|
|
|
|
return True if the event was actually taken care of
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __call__(
|
|
|
|
self,
|
|
|
|
event: InputEvent,
|
|
|
|
source: evdev.InputDevice,
|
2022-11-01 11:07:12 +00:00
|
|
|
suppress: bool = False,
|
2022-07-23 08:53:41 +00:00
|
|
|
) -> bool:
|
|
|
|
...
|
|
|
|
|
|
|
|
|
2022-04-17 10:19:23 +00:00
|
|
|
class InputEventHandler(Protocol):
|
2022-04-18 11:52:59 +00:00
|
|
|
"""The protocol any handler, which can be part of an event pipeline, must follow."""
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
def notify(
|
|
|
|
self,
|
|
|
|
event: InputEvent,
|
|
|
|
source: evdev.InputDevice,
|
2022-11-01 11:07:12 +00:00
|
|
|
suppress: bool = False,
|
2022-04-17 10:19:23 +00:00
|
|
|
) -> bool:
|
|
|
|
...
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""Reset the state of the handler e.g. release any buttons."""
|
2022-04-17 10:19:23 +00:00
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
class HandlerEnums(enum.Enum):
|
|
|
|
# converting to btn
|
|
|
|
abs2btn = enum.auto()
|
|
|
|
rel2btn = enum.auto()
|
|
|
|
|
|
|
|
macro = enum.auto()
|
|
|
|
key = enum.auto()
|
|
|
|
|
|
|
|
# converting to "analog"
|
|
|
|
btn2rel = enum.auto()
|
|
|
|
rel2rel = enum.auto()
|
|
|
|
abs2rel = enum.auto()
|
|
|
|
|
|
|
|
btn2abs = enum.auto()
|
|
|
|
rel2abs = enum.auto()
|
|
|
|
abs2abs = enum.auto()
|
|
|
|
|
|
|
|
# special handlers
|
|
|
|
combination = enum.auto()
|
|
|
|
hierarchy = enum.auto()
|
|
|
|
axisswitch = enum.auto()
|
|
|
|
disable = enum.auto()
|
|
|
|
|
|
|
|
|
2022-07-23 08:53:41 +00:00
|
|
|
class MappingHandler:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""The protocol an InputEventHandler must follow if it should be
|
2022-04-17 10:19:23 +00:00
|
|
|
dynamically integrated in an event-pipeline by the mapping parser
|
|
|
|
"""
|
|
|
|
|
|
|
|
mapping: Mapping
|
|
|
|
# all input events this handler cares about
|
2022-12-15 13:43:03 +00:00
|
|
|
# should always be a subset of mapping.input_combination
|
|
|
|
input_configs: List[InputConfig]
|
2022-04-17 10:19:23 +00:00
|
|
|
_sub_handler: Optional[InputEventHandler]
|
|
|
|
|
|
|
|
# https://bugs.python.org/issue44807
|
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-15 13:43:03 +00:00
|
|
|
combination: InputCombination,
|
2022-04-17 10:19:23 +00:00
|
|
|
mapping: Mapping,
|
|
|
|
**_,
|
|
|
|
) -> None:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""Initialize the handler
|
2022-04-17 10:19:23 +00:00
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2022-11-01 11:07:12 +00:00
|
|
|
combination
|
2022-04-17 10:19:23 +00:00
|
|
|
the combination from sub_handler.wrap_with()
|
2022-11-01 11:07:12 +00:00
|
|
|
mapping
|
2022-04-17 10:19:23 +00:00
|
|
|
"""
|
|
|
|
self.mapping = mapping
|
2022-12-15 13:43:03 +00:00
|
|
|
self.input_configs = list(combination)
|
2022-04-17 10:19:23 +00:00
|
|
|
self._sub_handler = None
|
|
|
|
|
2022-07-23 08:53:41 +00:00
|
|
|
def notify(
|
|
|
|
self,
|
|
|
|
event: InputEvent,
|
|
|
|
source: evdev.InputDevice,
|
2022-11-01 11:07:12 +00:00
|
|
|
suppress: bool = False,
|
2022-07-23 08:53:41 +00:00
|
|
|
) -> bool:
|
2022-11-01 11:07:12 +00:00
|
|
|
"""Notify this handler about an incoming event.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
event
|
|
|
|
The newest event that came from `source`, and that should be mapped to
|
|
|
|
something else
|
|
|
|
source
|
|
|
|
Where `event` comes from
|
|
|
|
"""
|
2022-07-23 08:53:41 +00:00
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
|
|
|
"""Reset the state of the handler e.g. release any buttons."""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2022-04-17 10:19:23 +00:00
|
|
|
def needs_wrapping(self) -> bool:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""If this handler needs to be wrapped in another MappingHandler."""
|
2022-04-17 10:19:23 +00:00
|
|
|
return len(self.wrap_with()) > 0
|
|
|
|
|
|
|
|
def needs_ranking(self) -> bool:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""If this handler needs ranking and wrapping with a HierarchyHandler."""
|
2022-04-17 10:19:23 +00:00
|
|
|
return False
|
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
def rank_by(self) -> Optional[InputCombination]:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""The combination for which this handler needs ranking."""
|
2022-04-17 10:19:23 +00:00
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
def wrap_with(self) -> Dict[InputCombination, HandlerEnums]:
|
|
|
|
"""A dict of InputCombination -> HandlerEnums.
|
2022-07-23 08:53:41 +00:00
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
for each InputCombination this handler should be wrapped
|
2022-11-12 16:45:32 +00:00
|
|
|
with the given MappingHandler.
|
|
|
|
"""
|
2022-04-17 10:19:23 +00:00
|
|
|
return {}
|
|
|
|
|
|
|
|
def set_sub_handler(self, handler: InputEventHandler) -> None:
|
2022-04-18 11:52:59 +00:00
|
|
|
"""Give this handler a sub_handler."""
|
2022-04-17 10:19:23 +00:00
|
|
|
self._sub_handler = handler
|
|
|
|
|
2022-12-15 13:43:03 +00:00
|
|
|
def occlude_input_event(self, input_config: InputConfig) -> None:
|
|
|
|
"""Remove the config from self.input_configs."""
|
|
|
|
if not self.input_configs:
|
2022-04-17 10:19:23 +00:00
|
|
|
logger.debug_mapping_handler(self)
|
|
|
|
raise MappingParsingError(
|
2022-12-15 13:43:03 +00:00
|
|
|
"Cannot remove a non existing config", mapping_handler=self
|
2022-04-17 10:19:23 +00:00
|
|
|
)
|
2022-11-12 16:45:32 +00:00
|
|
|
|
2022-04-17 10:19:23 +00:00
|
|
|
# should be called for each event a wrapping-handler
|
2022-12-15 13:43:03 +00:00
|
|
|
# has in its input_configs InputCombination
|
|
|
|
self.input_configs.remove(input_config)
|