Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mod_tap macro function #1004

Draft
wants to merge 35 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cedf74c
Refactor macro system
sezanzeb Nov 10, 2024
cabbb5f
various
sezanzeb Nov 10, 2024
d71ccf6
some more minor cleanup
sezanzeb Nov 10, 2024
3c41054
more cleanup
sezanzeb Nov 10, 2024
49648fa
restored old task docstrings
sezanzeb Nov 10, 2024
3f64818
tiny cleanup
sezanzeb Nov 10, 2024
cf538f9
Parsing arguments type-aware, instead of guessing during parsing and …
sezanzeb Nov 10, 2024
a1352b6
No redundant validations
sezanzeb Nov 10, 2024
d50a9e9
removed debug stuff
sezanzeb Nov 10, 2024
9372f74
smaller improvements to type parsing and names
sezanzeb Nov 11, 2024
82bc7fe
klsdajflksdj
sezanzeb Nov 11, 2024
b689880
variable docstring
sezanzeb Nov 11, 2024
d699ccf
types
sezanzeb Nov 11, 2024
d6aa36e
more cleanup and renaming
sezanzeb Nov 11, 2024
d42fd0f
Parser class
sezanzeb Nov 11, 2024
a1f1f7d
mypy
sezanzeb Nov 11, 2024
ef26df8
Parser class 2
sezanzeb Nov 11, 2024
ee4f96e
mypy
sezanzeb Nov 11, 2024
a92ffa2
black
sezanzeb Nov 11, 2024
fc670f8
mypy
sezanzeb Nov 11, 2024
3bf45be
new tests, minor fix for incorrect floats
sezanzeb Nov 13, 2024
9d159c7
add mod tap
sezanzeb Nov 13, 2024
34202d1
mypy
sezanzeb Nov 13, 2024
59c3434
typo
sezanzeb Nov 13, 2024
ef69997
mypy
sezanzeb Nov 13, 2024
def0b3e
add examples, fix tapping_term is_symbol
sezanzeb Nov 13, 2024
d05d128
example default key
sezanzeb Nov 13, 2024
df90041
replaying release events properly
sezanzeb Nov 13, 2024
65a1844
fixed NewType import
sezanzeb Nov 14, 2024
02fd138
run all handlers and listeners
sezanzeb Nov 14, 2024
78e54fd
comments
sezanzeb Nov 14, 2024
f651d31
tests
sezanzeb Nov 17, 2024
fcc31ef
tests
sezanzeb Nov 17, 2024
468bcd1
test tapping_term
sezanzeb Nov 17, 2024
a3b46fe
test tapping_term
sezanzeb Nov 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions inputremapper/configs/input_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
from __future__ import annotations

import itertools
from typing import Tuple, Iterable, Union, List, Dict, Optional, Hashable, NewType
from typing import Tuple, Iterable, Union, List, Dict, Optional, Hashable

from evdev import ecodes

from inputremapper.configs.paths import PathUtils
from inputremapper.input_event import InputEvent

Expand All @@ -35,7 +34,7 @@
from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.gui.messages.message_types import MessageType
from inputremapper.logging.logger import logger
from inputremapper.utils import get_evdev_constant_name
from inputremapper.utils import get_evdev_constant_name, DeviceHash

# having shift in combinations modifies the configured output,
# ctrl might not work at all
Expand All @@ -48,8 +47,6 @@
ecodes.KEY_RIGHTALT,
]

DeviceHash = NewType("DeviceHash", str)

EMPTY_TYPE = 99


Expand Down
2 changes: 1 addition & 1 deletion inputremapper/configs/keyboard_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def _set(self, name: str, code: int):
self._mapping[str(name)] = code
self._case_insensitive_mapping[str(name).lower()] = name

def get(self, name: str) -> int:
def get(self, name: str) -> Optional[int]:
"""Return the code mapped to the key."""
# the correct casing should be shown when asking the keyboard_layout
# for stuff. indexing case insensitive to support old presets.
Expand Down
14 changes: 7 additions & 7 deletions inputremapper/configs/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

import enum
from collections import namedtuple
from packaging import version
from typing import Optional, Callable, Tuple, TypeVar, Union, Any, Dict

from evdev.ecodes import (
Expand All @@ -33,6 +32,7 @@
REL_HWHEEL_HI_RES,
REL_WHEEL_HI_RES,
)
from packaging import version

try:
from pydantic.v1 import (
Expand Down Expand Up @@ -77,7 +77,7 @@
from inputremapper.gui.gettext import _
from inputremapper.gui.messages.message_types import MessageType
from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.macros.parse import is_this_a_macro, parse
from inputremapper.injection.macros.parse import Parser
from inputremapper.utils import get_evdev_constant_name

# TODO: remove pydantic VERSION check as soon as we no longer support
Expand Down Expand Up @@ -291,7 +291,7 @@ def get_output_type_code(self) -> Optional[Tuple[int, int]]:
"""
if self.output_code and self.output_type:
return self.output_type, self.output_code
if self.output_symbol and not is_this_a_macro(self.output_symbol):
if self.output_symbol and not Parser.is_this_a_macro(self.output_symbol):
return EV_KEY, keyboard_layout.get(self.output_symbol)
return None

Expand Down Expand Up @@ -379,10 +379,10 @@ def validate_symbol(cls, values):
if symbol == DISABLE_NAME:
return values

if is_this_a_macro(symbol):
if Parser.is_this_a_macro(symbol):
mapping_mock = namedtuple("Mapping", values.keys())(**values)
# raises MacroParsingError
parse(symbol, mapping=mapping_mock, verbose=False)
# raises MacroError
Parser.parse(symbol, mapping=mapping_mock, verbose=False)
return values

code = keyboard_layout.get(symbol)
Expand Down Expand Up @@ -445,7 +445,7 @@ def validate_output_integrity(cls, values):
# we have a symbol: no type and code is fine
return values

if is_this_a_macro(symbol):
if Parser.is_this_a_macro(symbol):
# disallow output type and code for macros
if type_ is not None or code is not None:
raise MacroButTypeOrCodeSetError()
Expand Down
10 changes: 5 additions & 5 deletions inputremapper/configs/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import re
import shutil
from pathlib import Path
from packaging import version
from typing import Iterator, Tuple, Dict, List, Optional, TypedDict

from evdev.ecodes import (
Expand All @@ -46,14 +45,15 @@
REL_WHEEL_HI_RES,
REL_HWHEEL_HI_RES,
)
from packaging import version

from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.configs.mapping import Mapping, UIMapping
from inputremapper.configs.paths import PathUtils
from inputremapper.configs.preset import Preset
from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.macros.parse import is_this_a_macro
from inputremapper.injection.macros.parse import Parser
from inputremapper.logging.logger import logger, VERSION
from inputremapper.user import UserUtils

Expand Down Expand Up @@ -225,7 +225,7 @@ def _find_target(self, symbol):
"""Try to find a uinput with the required capabilities for the symbol."""
capabilities = {EV_KEY: set(), EV_REL: set()}

if is_this_a_macro(symbol):
if Parser.is_this_a_macro(symbol):
# deprecated mechanic, cannot figure this out anymore
# capabilities = parse(symbol).get_capabilities()
return None
Expand Down Expand Up @@ -294,7 +294,7 @@ def _otherwise_to_else(self):

changed = False
for key, symbol in preset_structure["mapping"].copy().items():
if not is_this_a_macro(symbol[0]):
if not Parser.is_this_a_macro(symbol[0]):
continue

symbol_before = symbol[0]
Expand Down
2 changes: 1 addition & 1 deletion inputremapper/configs/validation_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def __init__(self, analog_input_config, output_type):
)


class MacroParsingError(ValueError):
class MacroError(ValueError):
"""Macro syntax errors."""

def __init__(self, symbol: Optional[str] = None, msg="Error while parsing a macro"):
Expand Down
30 changes: 17 additions & 13 deletions inputremapper/gui/autocompletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,18 @@
from evdev.ecodes import EV_KEY
from gi.repository import Gdk, Gtk, GLib, GObject

from inputremapper.configs.mapping import MappingData
from inputremapper.configs.keyboard_layout import keyboard_layout, DISABLE_NAME
from inputremapper.configs.mapping import MappingData
from inputremapper.gui.components.editor import CodeEditor
from inputremapper.gui.controller import Controller
from inputremapper.gui.messages.message_broker import MessageBroker, MessageType
from inputremapper.gui.messages.message_data import UInputsData
from inputremapper.gui.utils import debounce
from inputremapper.injection.macros.parse import (
TASK_FACTORIES,
get_macro_argument_names,
remove_comments,
)
from inputremapper.injection.macros.parse import Parser
from inputremapper.logging.logger import logger

# no deprecated shorthand function-names
FUNCTION_NAMES = [name for name in TASK_FACTORIES.keys() if len(name) > 1]
FUNCTION_NAMES = [name for name in Parser.TASK_CLASSES.keys() if len(name) > 1]
# no deprecated functions
FUNCTION_NAMES.remove("ifeq")

Expand All @@ -52,7 +48,7 @@
def _get_left_text(iter_: Gtk.TextIter) -> str:
buffer = iter_.get_buffer()
result = buffer.get_text(buffer.get_start_iter(), iter_, True)
result = remove_comments(result)
result = Parser.remove_comments(result)
result = result.replace("\n", " ")
return result.lower()

Expand Down Expand Up @@ -129,11 +125,19 @@ def propose_function_names(text_iter: Gtk.TextIter) -> List[Tuple[str, str]]:

incomplete_name = incomplete_name.lower()

return [
(name, f"{name}({', '.join(get_macro_argument_names(TASK_FACTORIES[name]))})")
for name in FUNCTION_NAMES
if incomplete_name in name.lower() and incomplete_name != name.lower()
]
# A list of
# - ("key", "key(symbol)")
# - ("repeat", "repeat(repeats, macro)")
# etc.
function_names: List[Tuple[str, str]] = []

for name in FUNCTION_NAMES:
if incomplete_name in name.lower() and incomplete_name != name.lower():
task_class = Parser.TASK_CLASSES[name]
argument_names = task_class.get_macro_argument_names()
function_names.append((name, f"{name}({', '.join(argument_names)})"))

return function_names


class SuggestionLabel(Gtk.Label):
Expand Down
4 changes: 0 additions & 4 deletions inputremapper/gui/messages/message_broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,11 @@
Tuple,
Deque,
Any,
TYPE_CHECKING,
)

from inputremapper.gui.messages.message_types import MessageType
from inputremapper.logging.logger import logger

if TYPE_CHECKING:
pass


class Message(Protocol):
"""The protocol any message must follow to be sent with the MessageBroker."""
Expand Down
7 changes: 3 additions & 4 deletions inputremapper/injection/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
from typing import List, Dict, Set, Hashable

import evdev

from inputremapper.configs.input_config import DeviceHash
from inputremapper.configs.preset import Preset
from inputremapper.injection.mapping_handlers.mapping_handler import (
EventListener,
Expand All @@ -39,6 +37,7 @@
)
from inputremapper.input_event import InputEvent
from inputremapper.logging.logger import logger
from inputremapper.utils import DeviceHash


class Context:
Expand Down Expand Up @@ -85,10 +84,10 @@ def __init__(
mapping_parser: MappingParser,
):
if len(forward_devices) == 0:
logger.warning("Not forward_devices set")
logger.warning("forward_devices not set")

if len(source_devices) == 0:
logger.warning("Not source_devices set")
logger.warning("source_devices not set")

self.listeners = set()
self._source_devices = source_devices
Expand Down
22 changes: 13 additions & 9 deletions inputremapper/injection/event_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,19 @@ def send_to_handlers(self, event: InputEvent) -> bool:

return handled

async def send_to_listeners(self, event: InputEvent) -> None:
async def send_to_listeners(self, event: InputEvent) -> bool:
"""Send the event to listeners."""
if event.type == evdev.ecodes.EV_MSC:
return
return False

if event.type == evdev.ecodes.EV_SYN:
return
return False

handled = False
for listener in self.context.listeners.copy():
# use a copy, since the listeners might remove themselves form the set

# fire and forget, run them in parallel and don't wait for them, since
# a listener might be blocking forever while waiting for more events.
asyncio.ensure_future(listener(event))
handled = await listener(event) | handled

# Running macros have priority, give them a head-start for processing the
# event. If if_single injects a modifier, this modifier should be active
Expand All @@ -157,6 +156,10 @@ async def send_to_listeners(self, event: InputEvent) -> None:
for _ in range(5):
await asyncio.sleep(0)

# If this is True, then similar to handlers, a listener took care of the event,
# and we do not want to forward it.
return handled

def forward(self, event: InputEvent) -> None:
"""Forward an event, which injects it unmodified."""
forward_to = self.context.get_forward_uinput(self._device_hash)
Expand All @@ -173,9 +176,10 @@ async def handle(self, event: InputEvent) -> None:
# won't appear, no need to forward or map them.
return

await self.send_to_listeners(event)
handled = await self.send_to_listeners(event)
handled = self.send_to_handlers(event) | handled

if not self.send_to_handlers(event):
if not handled:
# no handler took care of it, forward it
self.forward(event)

Expand All @@ -197,7 +201,7 @@ async def run(self):
InputEvent.from_event(event, origin_hash=self._device_hash)
)
except Exception as e:
logger.error("Handling event %s failed: %s", event, e)
logger.error("Handling event %s failed with %s", event, type(e))
traceback.print_exception(e)

self.context.reset()
Expand Down
4 changes: 2 additions & 2 deletions inputremapper/injection/injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

import evdev

from inputremapper.configs.input_config import InputCombination, InputConfig, DeviceHash
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.preset import Preset
from inputremapper.groups import (
_Group,
Expand All @@ -46,7 +46,7 @@
from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
from inputremapper.injection.numlock import set_numlock, is_numlock_on, ensure_numlock
from inputremapper.logging.logger import logger
from inputremapper.utils import get_device_hash
from inputremapper.utils import get_device_hash, DeviceHash

CapabilitiesDict = Dict[int, List[int]]

Expand Down
Loading
Loading