diff --git a/bin/input-remapper-control b/bin/input-remapper-control
index 499e1262d..438fb2d74 100755
--- a/bin/input-remapper-control
+++ b/bin/input-remapper-control
@@ -29,8 +29,9 @@ import sys
from enum import Enum
from typing import Union, Optional
-from inputremapper.configs.global_config import global_config
+from inputremapper.configs.global_config import GlobalConfig
from inputremapper.configs.migrations import Migrations
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput, FrontendUInput
from inputremapper.logging.logger import logger
@@ -66,6 +67,14 @@ class Options:
class InputRemapperControl:
+ def __init__(
+ self,
+ global_config: GlobalConfig,
+ migrations: Migrations,
+ ):
+ self.global_config = global_config
+ self.migrations = migrations
+
def run(self, cmd) -> None:
"""Run and log a command."""
logger.info("Running `%s`...", cmd)
@@ -81,9 +90,9 @@ class InputRemapperControl:
print(group.key)
def list_key_names(self):
- from inputremapper.configs.system_mapping import system_mapping
+ from inputremapper.configs.keyboard_layout import keyboard_layout
- print("\n".join(system_mapping.list_names()))
+ print("\n".join(keyboard_layout.list_names()))
def communicate(
self,
@@ -131,7 +140,7 @@ class InputRemapperControl:
sys.exit(6)
logger.info('Using config from "%s" instead', path)
- global_config.load_config(path)
+ self.global_config.load_config(path)
def ensure_migrated(self) -> None:
# import stuff late to make sure the correct log level is applied
@@ -144,9 +153,9 @@ class InputRemapperControl:
# This will also refresh the config of the daemon if the user changed
# it in the meantime.
# config_dir is either the cli arg or the default path in home
- config_dir = os.path.dirname(global_config.path)
+ config_dir = os.path.dirname(self.global_config.path)
self.daemon.set_config_dir(config_dir)
- Migrations.migrate()
+ self.migrations.migrate()
def _stop(self, device: str) -> None:
group = self._require_group(device)
@@ -267,7 +276,14 @@ class InputRemapperControl:
def main(options: Options) -> None:
- input_remapper_control = InputRemapperControl()
+ global_config = GlobalConfig()
+ global_uinputs = GlobalUInputs(FrontendUInput)
+ migrations = Migrations(global_uinputs)
+ input_remapper_control = InputRemapperControl(
+ global_config,
+ migrations,
+ )
+
if options.debug:
logger.update_verbosity(True)
diff --git a/bin/input-remapper-gtk b/bin/input-remapper-gtk
index 40cc25630..68cd9cf48 100755
--- a/bin/input-remapper-gtk
+++ b/bin/input-remapper-gtk
@@ -74,21 +74,26 @@ if __name__ == "__main__":
# log-level is set.
# import input-remapper stuff after setting the log verbosity
from inputremapper.gui.messages.message_broker import MessageBroker, MessageType
- from inputremapper.configs.system_mapping import system_mapping
+ from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.gui.data_manager import DataManager
from inputremapper.gui.user_interface import UserInterface
from inputremapper.gui.controller import Controller
- from inputremapper.injection.global_uinputs import GlobalUInputs
+ from inputremapper.injection.global_uinputs import GlobalUInputs, FrontendUInput
from inputremapper.groups import _Groups
from inputremapper.gui.reader_client import ReaderClient
from inputremapper.daemon import Daemon
from inputremapper.configs.global_config import GlobalConfig
from inputremapper.configs.migrations import Migrations
- Migrations.migrate()
+ global_uinputs = GlobalUInputs(FrontendUInput)
+ migrations = Migrations(global_uinputs)
+
+ Migrations(global_uinputs).migrate()
message_broker = MessageBroker()
+ global_config = GlobalConfig()
+
# create the reader before we start the reader-service (start_processes) otherwise
# it can come to race conditions with the creation of pipes
reader_client = ReaderClient(message_broker, _Groups())
@@ -96,11 +101,11 @@ if __name__ == "__main__":
data_manager = DataManager(
message_broker,
- GlobalConfig(),
+ global_config,
reader_client,
daemon,
- GlobalUInputs(),
- system_mapping,
+ global_uinputs,
+ keyboard_layout,
)
controller = Controller(message_broker, data_manager)
user_interface = UserInterface(message_broker, controller)
diff --git a/bin/input-remapper-reader-service b/bin/input-remapper-reader-service
index e564b0426..dde437b4c 100755
--- a/bin/input-remapper-reader-service
+++ b/bin/input-remapper-reader-service
@@ -28,6 +28,7 @@ import sys
from argparse import ArgumentParser
from inputremapper.groups import _Groups
+from inputremapper.injection.global_uinputs import GlobalUInputs, FrontendUInput
from inputremapper.logging.logger import logger
if __name__ == "__main__":
@@ -57,5 +58,6 @@ if __name__ == "__main__":
atexit.register(on_exit)
groups = _Groups()
- reader_service = ReaderService(groups)
+ global_uinputs = GlobalUInputs(FrontendUInput)
+ reader_service = ReaderService(groups, global_uinputs)
asyncio.run(reader_service.run())
diff --git a/bin/input-remapper-service b/bin/input-remapper-service
index 7e3cc2d9e..ad7ccff71 100755
--- a/bin/input-remapper-service
+++ b/bin/input-remapper-service
@@ -25,6 +25,9 @@
import sys
from argparse import ArgumentParser
+from inputremapper.configs.global_config import GlobalConfig
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
from inputremapper.logging.logger import logger
if __name__ == "__main__":
@@ -55,6 +58,10 @@ if __name__ == "__main__":
if not options.hide_info:
logger.log_info("input-remapper-service")
- daemon = Daemon()
+ global_config = GlobalConfig()
+ global_uinputs = GlobalUInputs(UInput)
+ mapping_parser = MappingParser(global_uinputs)
+
+ daemon = Daemon(global_config, global_uinputs, mapping_parser)
daemon.publish()
daemon.run()
diff --git a/inputremapper/configs/global_config.py b/inputremapper/configs/global_config.py
index 3c08a5cd0..ee6487f6b 100644
--- a/inputremapper/configs/global_config.py
+++ b/inputremapper/configs/global_config.py
@@ -25,8 +25,8 @@
from inputremapper.configs.base_config import ConfigBase, INITIAL_CONFIG
from inputremapper.configs.paths import PathUtils
-from inputremapper.user import UserUtils
from inputremapper.logging.logger import logger
+from inputremapper.user import UserUtils
MOUSE = "mouse"
WHEEL = "wheel"
@@ -35,7 +35,7 @@
class GlobalConfig(ConfigBase):
- """Global default configuration.
+ """Global default configuration, from which all presets inherit.
It can also contain some extra stuff not relevant for presets, like the
autoload stuff. If presets have a config key set, it will ignore
the default global configuration for that one. If none of the configs
@@ -129,7 +129,3 @@ def _save_config(self):
json.dump(self._config, file, indent=4)
logger.info("Saved config to %s", self.path)
file.write("\n")
-
-
-# TODO DI
-global_config = GlobalConfig()
diff --git a/inputremapper/configs/input_config.py b/inputremapper/configs/input_config.py
index 5648859a7..eaf020a3c 100644
--- a/inputremapper/configs/input_config.py
+++ b/inputremapper/configs/input_config.py
@@ -31,7 +31,7 @@
except ImportError:
from pydantic import BaseModel, root_validator, validator
-from inputremapper.configs.system_mapping import system_mapping
+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
@@ -142,7 +142,7 @@ def _get_name(self) -> Optional[str]:
# first try to find the name in xmodmap to not display wrong
# names due to the keyboard layout
if self.type == ecodes.EV_KEY:
- key_name = system_mapping.get_name(self.code)
+ key_name = keyboard_layout.get_name(self.code)
if key_name is None:
# if no result, look in the linux combination constants. On a german
diff --git a/inputremapper/configs/system_mapping.py b/inputremapper/configs/keyboard_layout.py
similarity index 97%
rename from inputremapper/configs/system_mapping.py
rename to inputremapper/configs/keyboard_layout.py
index f88493b3a..0cc91220d 100644
--- a/inputremapper/configs/system_mapping.py
+++ b/inputremapper/configs/keyboard_layout.py
@@ -41,7 +41,7 @@
LAZY_LOAD = None
-class SystemMapping:
+class KeyboardLayout:
"""Stores information about all available keycodes."""
_mapping: Optional[dict] = LAZY_LOAD
@@ -49,7 +49,7 @@ class SystemMapping:
_case_insensitive_mapping: Optional[dict] = LAZY_LOAD
def __getattribute__(self, wanted: str):
- """To lazy load system_mapping info only when needed.
+ """To lazy load keyboard_layout info only when needed.
For example, this helps to keep logs of input-remapper-control clear when it
doesn't need it the information.
@@ -160,7 +160,7 @@ def _set(self, name: str, code: int):
def get(self, name: str) -> int:
"""Return the code mapped to the key."""
- # the correct casing should be shown when asking the system_mapping
+ # the correct casing should be shown when asking the keyboard_layout
# for stuff. indexing case insensitive to support old presets.
if name not in self._mapping:
# only if not e.g. both "a" and "A" are in the mapping
@@ -216,4 +216,4 @@ def _find_legit_mappings(self) -> dict:
# TODO DI
# this mapping represents the xmodmap output, which stays constant
-system_mapping = SystemMapping()
+keyboard_layout = KeyboardLayout()
diff --git a/inputremapper/configs/mapping.py b/inputremapper/configs/mapping.py
index 819e8238f..25f760e5c 100644
--- a/inputremapper/configs/mapping.py
+++ b/inputremapper/configs/mapping.py
@@ -62,7 +62,7 @@
)
from inputremapper.configs.input_config import InputCombination
-from inputremapper.configs.system_mapping import system_mapping, DISABLE_NAME
+from inputremapper.configs.keyboard_layout import keyboard_layout, DISABLE_NAME
from inputremapper.configs.validation_errors import (
OutputSymbolUnknownError,
SymbolNotAvailableInTargetError,
@@ -286,13 +286,13 @@ def remove_combination_changed_callback(self):
def get_output_type_code(self) -> Optional[Tuple[int, int]]:
"""Returns the output_type and output_code if set,
- otherwise looks the output_symbol up in the system_mapping
+ otherwise looks the output_symbol up in the keyboard_layout
return None for unknown symbols and macros
"""
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):
- return EV_KEY, system_mapping.get(self.output_symbol)
+ return EV_KEY, keyboard_layout.get(self.output_symbol)
return None
def get_output_name_constant(self) -> str:
@@ -385,7 +385,7 @@ def validate_symbol(cls, values):
parse(symbol, mapping=mapping_mock, verbose=False)
return values
- code = system_mapping.get(symbol)
+ code = keyboard_layout.get(symbol)
if code is None:
raise OutputSymbolUnknownError(symbol)
@@ -450,7 +450,7 @@ def validate_output_integrity(cls, values):
if type_ is not None or code is not None:
raise MacroButTypeOrCodeSetError()
- if code is not None and code != system_mapping.get(symbol) or type_ != EV_KEY:
+ if code is not None and code != keyboard_layout.get(symbol) or type_ != EV_KEY:
raise SymbolAndCodeMismatchError(symbol, code)
return values
diff --git a/inputremapper/configs/migrations.py b/inputremapper/configs/migrations.py
index 84d762959..26e143426 100644
--- a/inputremapper/configs/migrations.py
+++ b/inputremapper/configs/migrations.py
@@ -51,8 +51,8 @@
from inputremapper.configs.mapping import Mapping, UIMapping
from inputremapper.configs.paths import PathUtils
from inputremapper.configs.preset import Preset
-from inputremapper.configs.system_mapping import system_mapping
-from inputremapper.injection.global_uinputs import global_uinputs
+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.logging.logger import logger, VERSION
from inputremapper.user import UserUtils
@@ -66,43 +66,46 @@ class Config(TypedDict):
class Migrations:
- @staticmethod
- def migrate():
+ def __init__(self, global_uinputs: GlobalUInputs):
+ self.global_uinputs = global_uinputs
+
+ def migrate(self):
"""Migrate config files to the current release."""
- Migrations._rename_to_input_remapper()
+ self._rename_to_input_remapper()
- Migrations._copy_to_v2()
+ self._copy_to_v2()
- v = Migrations.config_version()
+ v = self.config_version()
if v < version.parse("0.4.0"):
- Migrations._config_suffix()
- Migrations._preset_path()
+ self._config_suffix()
+ self._preset_path()
if v < version.parse("1.2.2"):
- Migrations._mapping_keys()
+ self._mapping_keys()
if v < version.parse("1.4.0"):
- global_uinputs.prepare_all()
- Migrations._add_target()
+ self.global_uinputs.prepare_all()
+ self._add_target()
if v < version.parse("1.4.1"):
- Migrations._otherwise_to_else()
+ self._otherwise_to_else()
if v < version.parse("1.5.0"):
- Migrations._remove_logs()
+ self._remove_logs()
if v < version.parse("1.6.0-beta"):
- Migrations._convert_to_individual_mappings()
+ self._convert_to_individual_mappings()
# add new migrations here
if v < version.parse(VERSION):
- Migrations._update_version()
+ self._update_version()
- @staticmethod
- def all_presets() -> Iterator[Tuple[os.PathLike, Dict | List]]:
+ def all_presets(
+ self,
+ ) -> Iterator[Tuple[os.PathLike, Dict | List]]:
"""Get all presets for all groups as list."""
if not os.path.exists(PathUtils.get_preset_path()):
return
@@ -124,8 +127,7 @@ def all_presets() -> Iterator[Tuple[os.PathLike, Dict | List]]:
logger.warning('Invalid json format in preset "%s"', preset)
continue
- @staticmethod
- def config_version():
+ def config_version(self):
"""Get the version string in config.json as packaging.Version object."""
config_path = os.path.join(PathUtils.config_path(), "config.json")
@@ -140,8 +142,7 @@ def config_version():
return version.parse("0.0.0")
- @staticmethod
- def _config_suffix():
+ def _config_suffix(self):
"""Append the .json suffix to the config file."""
deprecated_path = os.path.join(PathUtils.config_path(), "config")
config_path = os.path.join(PathUtils.config_path(), "config.json")
@@ -149,8 +150,7 @@ def _config_suffix():
logger.info('Moving "%s" to "%s"', deprecated_path, config_path)
os.rename(deprecated_path, config_path)
- @staticmethod
- def _preset_path():
+ def _preset_path(self):
"""Migrate the folder structure from < 0.4.0.
Move existing presets into the new subfolder 'presets'
@@ -173,13 +173,12 @@ def _preset_path():
logger.info("done")
- @staticmethod
- def _mapping_keys():
+ def _mapping_keys(self):
"""Update all preset mappings.
Update all keys in preset to include value e.g.: '1,5'->'1,5,1'
"""
- for preset, preset_structure in Migrations.all_presets():
+ for preset, preset_structure in self.all_presets():
if isinstance(preset_structure, list):
continue # the preset must be at least 1.6-beta version
@@ -199,8 +198,7 @@ def _mapping_keys():
json.dump(preset_structure, file, indent=4)
file.write("\n")
- @staticmethod
- def _update_version():
+ def _update_version(self):
"""Write the current version to the config file."""
config_file = os.path.join(PathUtils.config_path(), "config.json")
if not os.path.exists(config_file):
@@ -214,8 +212,7 @@ def _update_version():
logger.info('Updating version in config to "%s"', VERSION)
json.dump(config, file, indent=4)
- @staticmethod
- def _rename_to_input_remapper():
+ def _rename_to_input_remapper(self):
"""Rename .config/key-mapper to .config/input-remapper."""
old_config_path = os.path.join(UserUtils.home, ".config/key-mapper")
if not os.path.exists(PathUtils.config_path()) and os.path.exists(
@@ -224,8 +221,7 @@ def _rename_to_input_remapper():
logger.info("Moving %s to %s", old_config_path, PathUtils.config_path())
shutil.move(old_config_path, PathUtils.config_path())
- @staticmethod
- def _find_target(symbol):
+ def _find_target(self, symbol):
"""Try to find a uinput with the required capabilities for the symbol."""
capabilities = {EV_KEY: set(), EV_REL: set()}
@@ -234,22 +230,21 @@ def _find_target(symbol):
# capabilities = parse(symbol).get_capabilities()
return None
- capabilities[EV_KEY] = {system_mapping.get(symbol)}
+ capabilities[EV_KEY] = {keyboard_layout.get(symbol)}
if len(capabilities[EV_REL]) > 0:
return "mouse"
- for name, uinput in global_uinputs.devices.items():
+ for name, uinput in self.global_uinputs.devices.items():
if capabilities[EV_KEY].issubset(uinput.capabilities()[EV_KEY]):
return name
logger.info('could not find a suitable target UInput for "%s"', symbol)
return None
- @staticmethod
- def _add_target():
+ def _add_target(self):
"""Add the target field to each preset mapping."""
- for preset, preset_structure in Migrations.all_presets():
+ for preset, preset_structure in self.all_presets():
if isinstance(preset_structure, list):
continue
@@ -261,7 +256,7 @@ def _add_target():
if isinstance(symbol, list):
continue
- target = Migrations._find_target(symbol)
+ target = self._find_target(symbol)
if target is None:
target = "keyboard"
symbol = (
@@ -288,10 +283,9 @@ def _add_target():
json.dump(preset_structure, file, indent=4)
file.write("\n")
- @staticmethod
- def _otherwise_to_else():
+ def _otherwise_to_else(self):
"""Conditional macros should use an "else" parameter instead of "otherwise"."""
- for preset, preset_structure in Migrations.all_presets():
+ for preset, preset_structure in self.all_presets():
if isinstance(preset_structure, list):
continue
@@ -328,8 +322,9 @@ def _otherwise_to_else():
json.dump(preset_structure, file, indent=4)
file.write("\n")
- @staticmethod
- def _input_combination_from_string(combination_string: str) -> InputCombination:
+ def _input_combination_from_string(
+ self, combination_string: str
+ ) -> InputCombination:
configs = []
for event_str in combination_string.split("+"):
type_, code, analog_threshold = event_str.split(",")
@@ -343,14 +338,15 @@ def _input_combination_from_string(combination_string: str) -> InputCombination:
return InputCombination(configs)
- @staticmethod
- def _convert_to_individual_mappings() -> None:
+ def _convert_to_individual_mappings(
+ self,
+ ) -> None:
"""Convert preset.json
from {key: [symbol, target]}
to [{input_combination: ..., output_symbol: symbol, ...}]
"""
- for old_preset_path, old_preset in Migrations.all_presets():
+ for old_preset_path, old_preset in self.all_presets():
if isinstance(old_preset, list):
continue
@@ -363,9 +359,7 @@ def _convert_to_individual_mappings() -> None:
symbol_target,
)
try:
- combination = Migrations._input_combination_from_string(
- combination
- )
+ combination = self._input_combination_from_string(combination)
except ValueError:
logger.error(
"unable to migrate mapping with invalid combination %s",
@@ -482,8 +476,7 @@ def _convert_to_individual_mappings() -> None:
migrated_preset.save()
- @staticmethod
- def _copy_to_v2():
+ def _copy_to_v2(self):
"""Move the beta config to the v2 path, or copy the v1 config to the v2 path."""
# TODO test
if os.path.exists(PathUtils.config_path()):
@@ -495,7 +488,7 @@ def _copy_to_v2():
old_path = os.path.join(UserUtils.home, ".config/input-remapper")
if os.path.exists(os.path.join(old_path, "config.json")):
# no beta path, only old presets exist. COPY to v2 path, which will then be
- # migrated by the various migrations.
+ # migrated by the various self.
logger.debug("copying all from %s to %s", old_path, PathUtils.config_path())
shutil.copytree(old_path, PathUtils.config_path())
return
@@ -511,8 +504,7 @@ def _copy_to_v2():
logger.debug("moving %s to %s", beta_path, PathUtils.config_path())
shutil.move(beta_path, PathUtils.config_path())
- @staticmethod
- def _remove_logs():
+ def _remove_logs(self):
"""We will try to rely on journalctl for this in the future."""
try:
PathUtils.remove(f"{UserUtils.home}/.log/input-remapper")
diff --git a/inputremapper/configs/validation_errors.py b/inputremapper/configs/validation_errors.py
index 156a7c980..e1c163959 100644
--- a/inputremapper/configs/validation_errors.py
+++ b/inputremapper/configs/validation_errors.py
@@ -31,7 +31,7 @@
from evdev.ecodes import EV_KEY
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.injection.global_uinputs import GlobalUInputs
@@ -62,7 +62,7 @@ def __init__(self, analog_events):
class SymbolNotAvailableInTargetError(ValueError):
def __init__(self, symbol, target):
- code = system_mapping.get(symbol)
+ code = keyboard_layout.get(symbol)
fitting_targets = GlobalUInputs.find_fitting_default_uinputs(EV_KEY, code)
fitting_targets_string = '", "'.join(fitting_targets)
@@ -92,8 +92,8 @@ class SymbolAndCodeMismatchError(ValueError):
def __init__(self, symbol, code):
super().__init__(
"output_symbol and output_code mismatch: "
- f"output macro is {symbol} -> {system_mapping.get(symbol)} "
- f"but output_code is {code} -> {system_mapping.get_name(code)} "
+ f"output macro is {symbol} -> {keyboard_layout.get(symbol)} "
+ f"but output_code is {code} -> {keyboard_layout.get_name(code)} "
)
diff --git a/inputremapper/daemon.py b/inputremapper/daemon.py
index cf949e9cd..eb2d0d6b8 100644
--- a/inputremapper/daemon.py
+++ b/inputremapper/daemon.py
@@ -35,19 +35,21 @@
import gi
from pydbus import SystemBus
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
+
gi.require_version("GLib", "2.0")
from gi.repository import GLib
from inputremapper.logging.logger import logger
from inputremapper.injection.injector import Injector, InjectorState
from inputremapper.configs.preset import Preset
-from inputremapper.configs.global_config import global_config
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.global_config import GlobalConfig
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.groups import groups
from inputremapper.configs.paths import PathUtils
from inputremapper.user import UserUtils
from inputremapper.injection.macros.macro import macro_variables
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs
BUS_NAME = "inputremapper.Control"
@@ -184,9 +186,19 @@ class Daemon:
"""
- def __init__(self):
+ def __init__(
+ self,
+ global_config: GlobalConfig,
+ global_uinputs: GlobalUInputs,
+ mapping_parser: MappingParser,
+ ):
"""Constructs the daemon."""
logger.debug("Creating daemon")
+
+ self.global_config = global_config
+ self.global_uinputs = global_uinputs
+ self.mapping_parser = mapping_parser
+
self.injectors: Dict[str, Injector] = {}
self.config_dir = None
@@ -333,7 +345,7 @@ def set_config_dir(self, config_dir: str):
return
self.config_dir = config_dir
- global_config.load_config(config_path)
+ self.global_config.load_config(str(config_path))
def _autoload(self, group_key: str):
"""Check if autoloading is a good idea, and if so do it.
@@ -351,7 +363,7 @@ def _autoload(self, group_key: str):
# either not relevant for input-remapper, or not connected yet
return
- preset = global_config.get(["autoload", group.key], log_unknown=False)
+ preset = self.global_config.get(["autoload", group.key], log_unknown=False)
if preset is None:
# no autoloading is configured for this device
@@ -415,7 +427,7 @@ def autoload(self):
)
return
- autoload_presets = list(global_config.iterate_autoload_presets())
+ autoload_presets = list(self.global_config.iterate_autoload_presets())
logger.info("Autoloading for all devices")
@@ -475,13 +487,13 @@ def start_injecting(self, group_key: str, preset_name: str) -> bool:
xmodmap = json.load(file)
logger.debug('Using keycodes from "%s"', xmodmap_path)
- # this creates the system_mapping._xmodmap, which we need to do now
+ # this creates the keyboard_layout._xmodmap, which we need to do now
# otherwise it might be created later which will override the changes
# we do here.
- # Do we really need to lazyload in the system_mapping?
+ # Do we really need to lazyload in the keyboard_layout?
# this kind of bug is stupid to track down
- system_mapping.get_name(0)
- system_mapping.update(xmodmap)
+ keyboard_layout.get_name(0)
+ keyboard_layout.update(xmodmap)
# the service now has process wide knowledge of xmodmap
# keys of the users session
except FileNotFoundError:
@@ -500,13 +512,13 @@ def start_injecting(self, group_key: str, preset_name: str) -> bool:
# confusing the system. Seems to be especially important with
# gamepads, because some apps treat the first gamepad they found
# as the only gamepad they'll ever care about.
- global_uinputs.prepare_single(mapping.target_uinput)
+ self.global_uinputs.prepare_single(mapping.target_uinput)
if self.injectors.get(group_key) is not None:
self.stop_injecting(group_key)
try:
- injector = Injector(group, preset)
+ injector = Injector(group, preset, self.mapping_parser)
injector.start()
self.injectors[group.key] = injector
except OSError:
diff --git a/inputremapper/gui/autocompletion.py b/inputremapper/gui/autocompletion.py
index 455be58cb..28bb6f4fd 100644
--- a/inputremapper/gui/autocompletion.py
+++ b/inputremapper/gui/autocompletion.py
@@ -28,7 +28,7 @@
from gi.repository import Gdk, Gtk, GLib, GObject
from inputremapper.configs.mapping import MappingData
-from inputremapper.configs.system_mapping import system_mapping, DISABLE_NAME
+from inputremapper.configs.keyboard_layout import keyboard_layout, DISABLE_NAME
from inputremapper.gui.components.editor import CodeEditor
from inputremapper.gui.controller import Controller
from inputremapper.gui.messages.message_broker import MessageBroker, MessageType
@@ -111,7 +111,7 @@ def propose_symbols(text_iter: Gtk.TextIter, codes: List[int]) -> List[Tuple[str
incomplete_name = incomplete_name.lower()
- names = list(system_mapping.list_names(codes=codes)) + [DISABLE_NAME]
+ names = list(keyboard_layout.list_names(codes=codes)) + [DISABLE_NAME]
return [
(name, name)
diff --git a/inputremapper/gui/components/editor.py b/inputremapper/gui/components/editor.py
index f250a42f4..80ec4818d 100644
--- a/inputremapper/gui/components/editor.py
+++ b/inputremapper/gui/components/editor.py
@@ -57,7 +57,7 @@
from inputremapper.gui.utils import HandlerDisabled, Colors
from inputremapper.injection.mapping_handlers.axis_transform import Transformation
from inputremapper.input_event import InputEvent
-from inputremapper.configs.system_mapping import system_mapping, XKB_KEYCODE_OFFSET
+from inputremapper.configs.keyboard_layout import keyboard_layout, XKB_KEYCODE_OFFSET
from inputremapper.utils import get_evdev_constant_name
Capabilities = Dict[int, List]
@@ -384,9 +384,9 @@ def _display(self, event):
if is_press and len(self._combination) > 0:
names = [
- system_mapping.get_name(code)
+ keyboard_layout.get_name(code)
for code in self._combination
- if code is not None and system_mapping.get_name(code) is not None
+ if code is not None and keyboard_layout.get_name(code) is not None
]
self._gui.set_text(" + ".join(names))
diff --git a/inputremapper/gui/data_manager.py b/inputremapper/gui/data_manager.py
index f4d89feae..69577f12b 100644
--- a/inputremapper/gui/data_manager.py
+++ b/inputremapper/gui/data_manager.py
@@ -30,7 +30,7 @@
from inputremapper.configs.mapping import UIMapping, MappingData
from inputremapper.configs.paths import PathUtils
from inputremapper.configs.preset import Preset
-from inputremapper.configs.system_mapping import SystemMapping
+from inputremapper.configs.keyboard_layout import KeyboardLayout
from inputremapper.daemon import DaemonProxy
from inputremapper.exceptions import DataManagementError
from inputremapper.groups import _Group
@@ -73,13 +73,13 @@ def __init__(
reader_client: ReaderClient,
daemon: DaemonProxy,
uinputs: GlobalUInputs,
- system_mapping: SystemMapping,
+ keyboard_layout: KeyboardLayout,
):
self.message_broker = message_broker
self._reader_client = reader_client
self._daemon = daemon
self._uinputs = uinputs
- self._system_mapping = system_mapping
+ self._keyboard_layout = keyboard_layout
uinputs.prepare_all()
self._config = config
@@ -454,7 +454,7 @@ def update_mapping(self, **kwargs):
raise DataManagementError("Cannot modify Mapping: Mapping is not set")
if symbol := kwargs.get("output_symbol"):
- kwargs["output_symbol"] = self._system_mapping.correct_case(symbol)
+ kwargs["output_symbol"] = self._keyboard_layout.correct_case(symbol)
combination = self.active_mapping.input_combination
for key, value in kwargs.items():
diff --git a/inputremapper/gui/reader_service.py b/inputremapper/gui/reader_service.py
index b0180e38d..3f1f1d9a4 100644
--- a/inputremapper/gui/reader_service.py
+++ b/inputremapper/gui/reader_service.py
@@ -55,6 +55,7 @@
from inputremapper.configs.mapping import Mapping
from inputremapper.groups import _Groups, _Group
from inputremapper.injection.event_reader import EventReader
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.abs_to_btn_handler import AbsToBtnHandler
from inputremapper.injection.mapping_handlers.mapping_handler import (
NotifyCallback,
@@ -113,10 +114,11 @@ class ReaderService:
_maximum_lifetime: int = 60 * 15
_timeout_tolerance: int = 60
- def __init__(self, groups: _Groups):
+ def __init__(self, groups: _Groups, global_uinputs: GlobalUInputs):
"""Construct the reader-service and initialize its communication pipes."""
self._start_time = time.time()
self.groups = groups
+ self.global_uinputs = global_uinputs
self._results_pipe = Pipe(get_pipe_paths()[0])
self._commands_pipe = Pipe(get_pipe_paths()[1])
self._pipe = multiprocessing.Pipe()
@@ -291,7 +293,9 @@ def _create_event_pipeline(self, sources: List[evdev.InputDevice]) -> ContextDum
output_symbol="KEY_A",
)
handler: MappingHandler = AbsToBtnHandler(
- InputCombination([input_config]), mapping
+ InputCombination([input_config]),
+ mapping,
+ self.global_uinputs,
)
handler.set_sub_handler(ForwardToUIHandler(self._results_pipe))
context_dummy.add_handler(input_config, handler)
@@ -303,7 +307,11 @@ def _create_event_pipeline(self, sources: List[evdev.InputDevice]) -> ContextDum
target_uinput="keyboard",
output_symbol="KEY_A",
)
- handler = AbsToBtnHandler(InputCombination([input_config]), mapping)
+ handler = AbsToBtnHandler(
+ InputCombination([input_config]),
+ mapping,
+ self.global_uinputs,
+ )
handler.set_sub_handler(ForwardToUIHandler(self._results_pipe))
context_dummy.add_handler(input_config, handler)
@@ -322,7 +330,11 @@ def _create_event_pipeline(self, sources: List[evdev.InputDevice]) -> ContextDum
release_timeout=0.3,
force_release_timeout=True,
)
- handler = RelToBtnHandler(InputCombination([input_config]), mapping)
+ handler = RelToBtnHandler(
+ InputCombination([input_config]),
+ mapping,
+ self.global_uinputs,
+ )
handler.set_sub_handler(ForwardToUIHandler(self._results_pipe))
context_dummy.add_handler(input_config, handler)
@@ -337,7 +349,11 @@ def _create_event_pipeline(self, sources: List[evdev.InputDevice]) -> ContextDum
release_timeout=0.3,
force_release_timeout=True,
)
- handler = RelToBtnHandler(InputCombination([input_config]), mapping)
+ handler = RelToBtnHandler(
+ InputCombination([input_config]),
+ mapping,
+ self.global_uinputs,
+ )
handler.set_sub_handler(ForwardToUIHandler(self._results_pipe))
context_dummy.add_handler(input_config, handler)
diff --git a/inputremapper/gui/utils.py b/inputremapper/gui/utils.py
index 15122514d..20b55c511 100644
--- a/inputremapper/gui/utils.py
+++ b/inputremapper/gui/utils.py
@@ -145,9 +145,6 @@ def run_all_now(self):
logger.error(exception)
-debounce_manager = DebounceManager()
-
-
def debounce(timeout):
"""Debounce a method call to improve performance.
@@ -177,6 +174,9 @@ def wrapped(self, *args, **kwargs):
return decorator
+debounce_manager = DebounceManager()
+
+
class HandlerDisabled:
"""Safely modify a widget without causing handlers to be called.
diff --git a/inputremapper/injection/context.py b/inputremapper/injection/context.py
index cf56608b1..ce73b3f4f 100644
--- a/inputremapper/injection/context.py
+++ b/inputremapper/injection/context.py
@@ -34,7 +34,7 @@
NotifyCallback,
)
from inputremapper.injection.mapping_handlers.mapping_parser import (
- parse_mappings,
+ MappingParser,
EventPipelines,
)
from inputremapper.input_event import InputEvent
@@ -82,6 +82,7 @@ def __init__(
preset: Preset,
source_devices: Dict[DeviceHash, evdev.InputDevice],
forward_devices: Dict[DeviceHash, evdev.UInput],
+ mapping_parser: MappingParser,
):
if len(forward_devices) == 0:
logger.warning("Not forward_devices set")
@@ -93,7 +94,7 @@ def __init__(
self._source_devices = source_devices
self._forward_devices = forward_devices
self._notify_callbacks = defaultdict(list)
- self._handlers = parse_mappings(preset, self)
+ self._handlers = mapping_parser.parse_mappings(preset, self)
self._create_callbacks()
diff --git a/inputremapper/injection/global_uinputs.py b/inputremapper/injection/global_uinputs.py
index 3fba120c7..6802db5aa 100644
--- a/inputremapper/injection/global_uinputs.py
+++ b/inputremapper/injection/global_uinputs.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with input-remapper. If not, see .
-from typing import Dict, Union, Tuple, Optional, List
+from typing import Dict, Union, Tuple, Optional, List, Type
import evdev
@@ -83,7 +83,7 @@ def can_emit(self, event: Tuple[int, int, int]):
class FrontendUInput:
"""Uinput which can not actually send events, for use in the frontend."""
- def __init__(self, *args, events=None, name="py-evdev-uinput", **kwargs):
+ def __init__(self, *_, events=None, name="py-evdev-uinput", **__):
# see https://python-evdev.readthedocs.io/en/latest/apidoc.html#module-evdev.uinput # noqa pylint: disable=line-too-long
self.events = events
self.name = name
@@ -97,10 +97,12 @@ def capabilities(self):
class GlobalUInputs:
"""Manages all UInputs that are shared between all injection processes."""
- def __init__(self):
+ def __init__(
+ self,
+ uinput_factory: Union[Type[UInput], Type[FrontendUInput]],
+ ):
self.devices: Dict[str, Union[UInput, FrontendUInput]] = {}
- self._uinput_factory = None
- self.is_service = inputremapper.utils.is_service()
+ self._uinput_factory = uinput_factory
def __iter__(self):
return iter(uinput for _, uinput in self.devices.items())
@@ -121,28 +123,11 @@ def find_fitting_default_uinputs(type_: int, code: int) -> List[str]:
]
def reset(self):
- self.is_service = inputremapper.utils.is_service()
- self._uinput_factory = None
self.devices = {}
self.prepare_all()
- def ensure_uinput_factory_set(self):
- if self._uinput_factory is not None:
- return
-
- # TODO this should be solved via DI
- # overwrite global_uinputs.is_service in tests to control this
- if self.is_service:
- logger.debug("Creating regular UInputs")
- self._uinput_factory = UInput
- else:
- logger.debug("Creating FrontendUInputs")
- self._uinput_factory = FrontendUInput
-
def prepare_all(self):
"""Generate UInputs."""
- self.ensure_uinput_factory_set()
-
for name, events in DEFAULT_UINPUTS.items():
if name in self.devices.keys():
continue
@@ -158,8 +143,6 @@ def prepare_single(self, name: str):
This has to be done in the main process before injections that use it start.
"""
- self.ensure_uinput_factory_set()
-
if name not in DEFAULT_UINPUTS:
raise KeyError("Could not find a matching uinput to generate.")
@@ -204,7 +187,3 @@ def get_uinput(self, name: str) -> Optional[evdev.UInput]:
return None
return self.devices.get(name)
-
-
-# TODO DI
-global_uinputs = GlobalUInputs()
diff --git a/inputremapper/injection/injector.py b/inputremapper/injection/injector.py
index 35db52f49..2636765d9 100644
--- a/inputremapper/injection/injector.py
+++ b/inputremapper/injection/injector.py
@@ -43,6 +43,7 @@
from inputremapper.gui.messages.message_broker import MessageType
from inputremapper.injection.context import Context
from inputremapper.injection.event_reader import EventReader
+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
@@ -119,7 +120,12 @@ class Injector(multiprocessing.Process):
regrab_timeout = 0.2
- def __init__(self, group: _Group, preset: Preset) -> None:
+ def __init__(
+ self,
+ group: _Group,
+ preset: Preset,
+ mapping_parser: MappingParser,
+ ) -> None:
"""
Parameters
@@ -128,6 +134,7 @@ def __init__(self, group: _Group, preset: Preset) -> None:
the device group
"""
self.group = group
+ self.mapping_parser = mapping_parser
self._state = InjectorState.UNKNOWN
# used to interact with the parts of this class that are running within
@@ -415,7 +422,12 @@ def run(self) -> None:
# create this within the process after the event loop creation,
# so that the macros use the correct loop
- self.context = Context(self.preset, sources, forward_devices)
+ self.context = Context(
+ self.preset,
+ sources,
+ forward_devices,
+ self.mapping_parser,
+ )
self._stop_event = asyncio.Event()
if len(sources) == 0:
diff --git a/inputremapper/injection/macros/macro.py b/inputremapper/injection/macros/macro.py
index b0d3fd0e9..90eb4f57f 100644
--- a/inputremapper/injection/macros/macro.py
+++ b/inputremapper/injection/macros/macro.py
@@ -54,7 +54,7 @@
REL_HWHEEL,
)
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.configs.validation_errors import (
SymbolNotAvailableInTargetError,
MacroParsingError,
@@ -89,78 +89,6 @@ def __repr__(self):
return f''
-def _type_check(value: Any, allowed_types, display_name=None, position=None) -> Any:
- """Validate a parameter used in a macro.
-
- If the value is a Variable, it will be returned and should be resolved
- during runtime with _resolve.
- """
- if isinstance(value, Variable):
- # it is a variable and will be read at runtime
- return value
-
- for allowed_type in allowed_types:
- if allowed_type is None:
- if value is None:
- return value
-
- continue
-
- # try to parse "1" as 1 if possible
- if allowed_type != Macro:
- # the macro constructor with a single argument always succeeds,
- # but will definitely not result in the correct macro
- try:
- return allowed_type(value)
- except (TypeError, ValueError):
- pass
-
- if isinstance(value, allowed_type):
- return value
-
- if display_name is not None and position is not None:
- raise MacroParsingError(
- msg=f"Expected parameter {position} for {display_name} to be "
- f"one of {allowed_types}, but got {value}"
- )
-
- raise MacroParsingError(
- msg=f"Expected parameter to be one of {allowed_types}, but got {value}"
- )
-
-
-def _type_check_variablename(name: str):
- """Check if this is a legit variable name.
-
- Because they could clash with language features. If the macro is able to be
- parsed at all due to a problematic choice of a variable name.
-
- Allowed examples: "foo", "Foo1234_", "_foo_1234"
- Not allowed: "1_foo", "foo=blub", "$foo", "foo,1234", "foo()"
- """
- if not isinstance(name, str) or not re.match(r"^[A-Za-z_][A-Za-z_0-9]*$", name):
- raise MacroParsingError(msg=f'"{name}" is not a legit variable name')
-
-
-def _resolve(argument, allowed_types=None):
- """If the argument is a variable, figure out its value and cast it.
-
- Variables are prefixed with `$` in the syntax.
-
- Use this just-in-time when you need the actual value of the variable
- during runtime.
- """
- if isinstance(argument, Variable):
- value = argument.resolve()
- logger.debug('"%s" is "%s"', argument, value)
- if allowed_types:
- return _type_check(value, allowed_types)
- else:
- return value
-
- return argument
-
-
class Macro:
"""Supports chaining and preparing actions.
@@ -312,10 +240,10 @@ def add_key(self, symbol: str):
async def task(handler: Callable):
# if the code is $foo, figure out the correct code now.
- resolved_symbol = _resolve(symbol, [str])
+ resolved_symbol = self._resolve(symbol, [str])
code = self._type_check_symbol(resolved_symbol)
- resolved_code = _resolve(code, [int])
+ resolved_code = self._resolve(code, [int])
handler(EV_KEY, resolved_code, 1)
await self._keycode_pause()
handler(EV_KEY, resolved_code, 0)
@@ -328,10 +256,10 @@ def add_key_down(self, symbol: str):
self._type_check_symbol(symbol)
async def task(handler: Callable):
- resolved_symbol = _resolve(symbol, [str])
+ resolved_symbol = self._resolve(symbol, [str])
code = self._type_check_symbol(resolved_symbol)
- resolved_code = _resolve(code, [int])
+ resolved_code = self._resolve(code, [int])
handler(EV_KEY, resolved_code, 1)
self.tasks.append(task)
@@ -341,17 +269,17 @@ def add_key_up(self, symbol: str):
self._type_check_symbol(symbol)
async def task(handler: Callable):
- resolved_symbol = _resolve(symbol, [str])
+ resolved_symbol = self._resolve(symbol, [str])
code = self._type_check_symbol(resolved_symbol)
- resolved_code = _resolve(code, [int])
+ resolved_code = self._resolve(code, [int])
handler(EV_KEY, resolved_code, 0)
self.tasks.append(task)
def add_hold(self, macro=None):
"""Loops the execution until key release."""
- _type_check(macro, [Macro, str, None], "hold", 1)
+ self._type_check(macro, [Macro, str, None], "hold", 1)
if macro is None:
self.tasks.append(lambda _: self._trigger_release_event.wait())
@@ -364,10 +292,10 @@ def add_hold(self, macro=None):
self._type_check_symbol(symbol)
async def task(handler: Callable):
- resolved_symbol = _resolve(symbol, [str])
+ resolved_symbol = self._resolve(symbol, [str])
code = self._type_check_symbol(resolved_symbol)
- resolved_code = _resolve(code, [int])
+ resolved_code = self._resolve(code, [int])
handler(EV_KEY, resolved_code, 1)
await self._trigger_release_event.wait()
handler(EV_KEY, resolved_code, 0)
@@ -395,14 +323,14 @@ def add_modify(self, modifier: str, macro: Macro):
modifier
macro
"""
- _type_check(macro, [Macro], "modify", 2)
+ self._type_check(macro, [Macro], "modify", 2)
self._type_check_symbol(modifier)
self.child_macros.append(macro)
async def task(handler: Callable):
# TODO test var
- resolved_modifier = _resolve(modifier, [str])
+ resolved_modifier = self._resolve(modifier, [str])
code = self._type_check_symbol(resolved_modifier)
handler(EV_KEY, code, 1)
@@ -419,7 +347,7 @@ def add_hold_keys(self, *symbols):
self._type_check_symbol(symbol)
async def task(handler: Callable):
- resolved_symbols = [_resolve(symbol, [str]) for symbol in symbols]
+ resolved_symbols = [self._resolve(symbol, [str]) for symbol in symbols]
codes = [self._type_check_symbol(symbol) for symbol in resolved_symbols]
for code in codes:
@@ -436,11 +364,11 @@ async def task(handler: Callable):
def add_repeat(self, repeats: Union[str, int], macro: Macro):
"""Repeat actions."""
- repeats = _type_check(repeats, [int], "repeat", 1)
- _type_check(macro, [Macro], "repeat", 2)
+ repeats = self._type_check(repeats, [int], "repeat", 1)
+ self._type_check(macro, [Macro], "repeat", 2)
async def task(handler: Callable):
- for _ in range(_resolve(repeats, [int])):
+ for _ in range(self._resolve(repeats, [int])):
await macro.run(handler)
self.tasks.append(task)
@@ -457,9 +385,9 @@ def add_event(self, type_: Union[str, int], code: Union[str, int], value: int):
examples: 52, 'KEY_A'
value
"""
- type_ = _type_check(type_, [int, str], "event", 1)
- code = _type_check(code, [int, str], "event", 2)
- value = _type_check(value, [int, str], "event", 3)
+ type_ = self._type_check(type_, [int, str], "event", 1)
+ code = self._type_check(code, [int, str], "event", 2)
+ value = self._type_check(value, [int, str], "event", 3)
if isinstance(type_, str):
type_ = ecodes[type_.upper()]
@@ -476,9 +404,9 @@ def add_mouse(
acceleration: Optional[float] = None,
):
"""Move the mouse cursor."""
- _type_check(direction, [str], "mouse", 1)
- speed = _type_check(speed, [int], "mouse", 2)
- acceleration = _type_check(acceleration, [float, None], "mouse", 3)
+ self._type_check(direction, [str], "mouse", 1)
+ speed = self._type_check(speed, [int], "mouse", 2)
+ acceleration = self._type_check(acceleration, [float, None], "mouse", 3)
code, value = {
"up": (REL_Y, -1),
@@ -488,8 +416,8 @@ def add_mouse(
}[direction.lower()]
async def task(handler: Callable):
- resolved_speed = _resolve(speed, [int])
- resolved_accel = _resolve(acceleration, [float, None])
+ resolved_speed = self._resolve(speed, [int])
+ resolved_accel = self._resolve(acceleration, [float, None])
if resolved_accel:
current_speed = 0.0
@@ -517,8 +445,8 @@ async def task(handler: Callable):
def add_wheel(self, direction: str, speed: int):
"""Move the scroll wheel."""
- _type_check(direction, [str], "wheel", 1)
- speed = _type_check(speed, [int], "wheel", 2)
+ self._type_check(direction, [str], "wheel", 1)
+ speed = self._type_check(speed, [int], "wheel", 2)
code, value = {
"up": ([REL_WHEEL, REL_WHEEL_HI_RES], [1 / 120, 1]),
@@ -528,7 +456,7 @@ def add_wheel(self, direction: str, speed: int):
}[direction.lower()]
async def task(handler: Callable):
- resolved_speed = _resolve(speed, [int])
+ resolved_speed = self._resolve(speed, [int])
remainder = [0.0, 0.0]
while self.is_holding():
for i in range(0, 2):
@@ -542,20 +470,20 @@ async def task(handler: Callable):
def add_wait(self, time: Union[int, float]):
"""Wait time in milliseconds."""
- time = _type_check(time, [int, float], "wait", 1)
+ time = self._type_check(time, [int, float], "wait", 1)
async def task(_):
- await asyncio.sleep(_resolve(time, [int, float]) / 1000)
+ await asyncio.sleep(self._resolve(time, [int, float]) / 1000)
self.tasks.append(task)
def add_set(self, variable: str, value):
"""Set a variable to a certain value."""
- _type_check_variablename(variable)
+ self._type_check_variablename(variable)
async def task(_):
# can also copy with set(a, $b)
- resolved_value = _resolve(value)
+ resolved_value = self._resolve(value)
logger.debug('"%s" set to "%s"', variable, resolved_value)
macro_variables[variable] = value
@@ -563,8 +491,8 @@ async def task(_):
def add_add(self, variable: str, value: Union[int, float]):
"""Add a number to a variable."""
- _type_check_variablename(variable)
- _type_check(value, [int, float], "value", 1)
+ self._type_check_variablename(variable)
+ self._type_check(value, [int, float], "value", 1)
async def task(_):
current = macro_variables[variable]
@@ -573,7 +501,7 @@ async def task(_):
macro_variables[variable] = 0
current = 0
- resolved_value = _resolve(value)
+ resolved_value = self._resolve(value)
if not isinstance(resolved_value, (int, float)):
logger.error('Expected delta "%s" to be a number', resolved_value)
return
@@ -598,8 +526,8 @@ def add_ifeq(self, variable, value, then=None, else_=None):
"foo" without breaking old functionality, because "foo" is treated as a
variable name.
"""
- _type_check(then, [Macro, None], "ifeq", 3)
- _type_check(else_, [Macro, None], "ifeq", 4)
+ self._type_check(then, [Macro, None], "ifeq", 3)
+ self._type_check(else_, [Macro, None], "ifeq", 4)
async def task(handler: Callable):
set_value = macro_variables.get(variable)
@@ -619,12 +547,12 @@ async def task(handler: Callable):
def add_if_eq(self, value_1, value_2, then=None, else_=None):
"""Compare two values."""
- _type_check(then, [Macro, None], "if_eq", 3)
- _type_check(else_, [Macro, None], "if_eq", 4)
+ self._type_check(then, [Macro, None], "if_eq", 3)
+ self._type_check(else_, [Macro, None], "if_eq", 4)
async def task(handler: Callable):
- resolved_value_1 = _resolve(value_1)
- resolved_value_2 = _resolve(value_2)
+ resolved_value_1 = self._resolve(value_1)
+ resolved_value_2 = self._resolve(value_2)
if resolved_value_1 == resolved_value_2:
if then is not None:
await then.run(handler)
@@ -646,9 +574,9 @@ def add_if_tap(self, then=None, else_=None, timeout=300):
macro key pressed -> released (does other stuff in the meantime)
-> if_tap starts -> pressed -> released -> then
"""
- _type_check(then, [Macro, None], "if_tap", 1)
- _type_check(else_, [Macro, None], "if_tap", 2)
- timeout = _type_check(timeout, [int, float], "if_tap", 3)
+ self._type_check(then, [Macro, None], "if_tap", 1)
+ self._type_check(else_, [Macro, None], "if_tap", 2)
+ timeout = self._type_check(timeout, [int, float], "if_tap", 3)
if isinstance(then, Macro):
self.child_macros.append(then)
@@ -664,7 +592,7 @@ async def wait():
await self._trigger_release_event.wait()
async def task(handler: Callable):
- resolved_timeout = _resolve(timeout, [int, float]) / 1000
+ resolved_timeout = self._resolve(timeout, [int, float]) / 1000
try:
await asyncio.wait_for(wait(), resolved_timeout)
if then:
@@ -677,8 +605,8 @@ async def task(handler: Callable):
def add_if_single(self, then, else_, timeout=None):
"""If a key was pressed without combining it."""
- _type_check(then, [Macro, None], "if_single", 1)
- _type_check(else_, [Macro, None], "if_single", 2)
+ self._type_check(then, [Macro, None], "if_single", 1)
+ self._type_check(else_, [Macro, None], "if_single", 2)
if isinstance(then, Macro):
self.child_macros.append(then)
@@ -700,7 +628,7 @@ async def listener(event):
self.context.listeners.add(listener)
- resolved_timeout = _resolve(timeout, allowed_types=[int, float, None])
+ resolved_timeout = Macro._resolve(timeout, allowed_types=[int, float, None])
await asyncio.wait(
[
asyncio.Task(listener_done.wait()),
@@ -728,7 +656,7 @@ def _type_check_symbol(self, keyname: Union[str, Variable]) -> Union[Variable, i
return keyname
symbol = str(keyname)
- code = system_mapping.get(symbol)
+ code = keyboard_layout.get(symbol)
if code is None:
raise MacroParsingError(msg=f'Unknown key "{symbol}"')
@@ -741,3 +669,75 @@ def _type_check_symbol(self, keyname: Union[str, Variable]) -> Union[Variable, i
raise SymbolNotAvailableInTargetError(symbol, target)
return code
+
+ @staticmethod
+ def _type_check(value: Any, allowed_types, display_name=None, position=None) -> Any:
+ """Validate a parameter used in a macro.
+
+ If the value is a Variable, it will be returned and should be resolved
+ during runtime with _resolve.
+ """
+ if isinstance(value, Variable):
+ # it is a variable and will be read at runtime
+ return value
+
+ for allowed_type in allowed_types:
+ if allowed_type is None:
+ if value is None:
+ return value
+
+ continue
+
+ # try to parse "1" as 1 if possible
+ if allowed_type != Macro:
+ # the macro constructor with a single argument always succeeds,
+ # but will definitely not result in the correct macro
+ try:
+ return allowed_type(value)
+ except (TypeError, ValueError):
+ pass
+
+ if isinstance(value, allowed_type):
+ return value
+
+ if display_name is not None and position is not None:
+ raise MacroParsingError(
+ msg=f"Expected parameter {position} for {display_name} to be "
+ f"one of {allowed_types}, but got {value}"
+ )
+
+ raise MacroParsingError(
+ msg=f"Expected parameter to be one of {allowed_types}, but got {value}"
+ )
+
+ @staticmethod
+ def _type_check_variablename(name: str):
+ """Check if this is a legit variable name.
+
+ Because they could clash with language features. If the macro is able to be
+ parsed at all due to a problematic choice of a variable name.
+
+ Allowed examples: "foo", "Foo1234_", "_foo_1234"
+ Not allowed: "1_foo", "foo=blub", "$foo", "foo,1234", "foo()"
+ """
+ if not isinstance(name, str) or not re.match(r"^[A-Za-z_][A-Za-z_0-9]*$", name):
+ raise MacroParsingError(msg=f'"{name}" is not a legit variable name')
+
+ @staticmethod
+ def _resolve(argument, allowed_types=None):
+ """If the argument is a variable, figure out its value and cast it.
+
+ Variables are prefixed with `$` in the syntax.
+
+ Use this just-in-time when you need the actual value of the variable
+ during runtime.
+ """
+ if isinstance(argument, Variable):
+ value = argument.resolve()
+ logger.debug('"%s" is "%s"', argument, value)
+ if allowed_types:
+ return Macro._type_check(value, allowed_types)
+ else:
+ return value
+
+ return argument
diff --git a/inputremapper/injection/mapping_handlers/abs_to_abs_handler.py b/inputremapper/injection/mapping_handlers/abs_to_abs_handler.py
index 2159896fe..0f06a1e18 100644
--- a/inputremapper/injection/mapping_handlers/abs_to_abs_handler.py
+++ b/inputremapper/injection/mapping_handlers/abs_to_abs_handler.py
@@ -25,7 +25,7 @@
from inputremapper import exceptions
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import Mapping
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.axis_transform import Transformation
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
@@ -49,9 +49,10 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
**_,
) -> None:
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
# find the input event we are supposed to map. If the input combination is
# BTN_A + ABS_X + BTN_B, then use the value of ABS_X for the transformation
@@ -133,7 +134,7 @@ def _scale_to_target(self, x: float) -> int:
def _write(self, value: int):
"""Inject."""
try:
- global_uinputs.write(
+ self.global_uinputs.write(
(*self._output_axis, value), self.mapping.target_uinput
)
except OverflowError:
diff --git a/inputremapper/injection/mapping_handlers/abs_to_btn_handler.py b/inputremapper/injection/mapping_handlers/abs_to_btn_handler.py
index 544786fde..e173d7e91 100644
--- a/inputremapper/injection/mapping_handlers/abs_to_btn_handler.py
+++ b/inputremapper/injection/mapping_handlers/abs_to_btn_handler.py
@@ -24,6 +24,7 @@
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import Mapping
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
InputEventHandler,
@@ -43,9 +44,10 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
**_,
):
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
self._active = False
self._input_config = combination[0]
diff --git a/inputremapper/injection/mapping_handlers/abs_to_rel_handler.py b/inputremapper/injection/mapping_handlers/abs_to_rel_handler.py
index fe323b1a0..ebde56417 100644
--- a/inputremapper/injection/mapping_handlers/abs_to_rel_handler.py
+++ b/inputremapper/injection/mapping_handlers/abs_to_rel_handler.py
@@ -41,7 +41,7 @@
WHEEL_HI_RES_SCALING,
DEFAULT_REL_RATE,
)
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.axis_transform import Transformation
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
@@ -134,9 +134,10 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
**_,
) -> None:
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
# find the input event we are supposed to map
assert (map_axis := combination.find_analog_input_config(type_=EV_ABS))
@@ -228,7 +229,9 @@ def _write(self, type_, keycode, value):
return # rel 0 does not make sense
try:
- global_uinputs.write((type_, keycode, value), self.mapping.target_uinput)
+ self.global_uinputs.write(
+ (type_, keycode, value), self.mapping.target_uinput
+ )
except OverflowError:
# screwed up the calculation of mouse movements
logger.error("OverflowError (%s, %s, %s)", type_, keycode, value)
diff --git a/inputremapper/injection/mapping_handlers/axis_switch_handler.py b/inputremapper/injection/mapping_handlers/axis_switch_handler.py
index f96b1fc0e..2f86ef178 100644
--- a/inputremapper/injection/mapping_handlers/axis_switch_handler.py
+++ b/inputremapper/injection/mapping_handlers/axis_switch_handler.py
@@ -24,6 +24,7 @@
from inputremapper.configs.input_config import InputCombination
from inputremapper.configs.input_config import InputConfig
from inputremapper.configs.mapping import Mapping
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
HandlerEnums,
@@ -59,9 +60,10 @@ def __init__(
combination: InputCombination,
mapping: Mapping,
context: ContextProtocol,
+ global_uinputs: GlobalUInputs,
**_,
):
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
trigger_keys = tuple(
event.input_match_hash
for event in combination
diff --git a/inputremapper/injection/mapping_handlers/combination_handler.py b/inputremapper/injection/mapping_handlers/combination_handler.py
index 0ec784867..52429e251 100644
--- a/inputremapper/injection/mapping_handlers/combination_handler.py
+++ b/inputremapper/injection/mapping_handlers/combination_handler.py
@@ -26,6 +26,7 @@
from inputremapper.configs.input_config import InputCombination
from inputremapper.configs.mapping import Mapping
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
InputEventHandler,
@@ -52,10 +53,11 @@ def __init__(
combination: InputCombination,
mapping: Mapping,
context: Context,
+ global_uinputs: GlobalUInputs,
**_,
) -> None:
logger.debug(str(mapping))
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
self._pressed_keys = {}
self._output_state = False
self._context = context
diff --git a/inputremapper/injection/mapping_handlers/hierarchy_handler.py b/inputremapper/injection/mapping_handlers/hierarchy_handler.py
index 4f71581eb..2cc80f5ee 100644
--- a/inputremapper/injection/mapping_handlers/hierarchy_handler.py
+++ b/inputremapper/injection/mapping_handlers/hierarchy_handler.py
@@ -23,6 +23,7 @@
from evdev.ecodes import EV_ABS, EV_REL
from inputremapper.configs.input_config import InputCombination, InputConfig
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
InputEventHandler,
@@ -41,14 +42,17 @@ class HierarchyHandler(MappingHandler):
_input_config: InputConfig
def __init__(
- self, handlers: List[MappingHandler], input_config: InputConfig
+ self,
+ handlers: List[MappingHandler],
+ input_config: InputConfig,
+ global_uinputs: GlobalUInputs,
) -> None:
self.handlers = handlers
self._input_config = input_config
combination = InputCombination([input_config])
# use the mapping from the first child TODO: find a better solution
mapping = handlers[0].mapping
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
def __str__(self):
return f"HierarchyHandler for {self._input_config}"
diff --git a/inputremapper/injection/mapping_handlers/key_handler.py b/inputremapper/injection/mapping_handlers/key_handler.py
index 7ce023fb4..871128506 100644
--- a/inputremapper/injection/mapping_handlers/key_handler.py
+++ b/inputremapper/injection/mapping_handlers/key_handler.py
@@ -23,7 +23,7 @@
from inputremapper.configs.input_config import InputCombination
from inputremapper.configs.mapping import Mapping
from inputremapper.exceptions import MappingParsingError
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
HandlerEnums,
@@ -43,9 +43,10 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
**_,
):
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
maps_to = mapping.get_output_type_code()
if not maps_to:
raise MappingParsingError(
@@ -71,7 +72,7 @@ def notify(self, event: InputEvent, *_, **__) -> bool:
event_tuple = (*self._maps_to, event.value)
try:
- global_uinputs.write(event_tuple, self.mapping.target_uinput)
+ self.global_uinputs.write(event_tuple, self.mapping.target_uinput)
self._active = bool(event.value)
return True
except exceptions.Error:
@@ -81,7 +82,7 @@ def reset(self) -> None:
logger.debug("resetting key_handler")
if self._active:
event_tuple = (*self._maps_to, 0)
- global_uinputs.write(event_tuple, self.mapping.target_uinput)
+ self.global_uinputs.write(event_tuple, self.mapping.target_uinput)
self._active = False
def needs_wrapping(self) -> bool:
diff --git a/inputremapper/injection/mapping_handlers/macro_handler.py b/inputremapper/injection/mapping_handlers/macro_handler.py
index ed6af7103..e61a6bb0f 100644
--- a/inputremapper/injection/mapping_handlers/macro_handler.py
+++ b/inputremapper/injection/mapping_handlers/macro_handler.py
@@ -22,7 +22,7 @@
from inputremapper.configs.input_config import InputCombination
from inputremapper.configs.mapping import Mapping
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.macros.macro import Macro
from inputremapper.injection.macros.parse import parse
from inputremapper.injection.mapping_handlers.mapping_handler import (
@@ -45,10 +45,11 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
*,
context: ContextProtocol,
):
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
self._active = False
assert self.mapping.output_symbol is not None
self._macro = parse(self.mapping.output_symbol, context, mapping)
@@ -79,7 +80,9 @@ def notify(self, event: InputEvent, *_, **__) -> bool:
def handler(type_, code, value) -> None:
"""Handler for macros."""
- global_uinputs.write((type_, code, value), self.mapping.target_uinput)
+ self.global_uinputs.write(
+ (type_, code, value), self.mapping.target_uinput
+ )
asyncio.ensure_future(self.run_macro(handler))
return True
diff --git a/inputremapper/injection/mapping_handlers/mapping_handler.py b/inputremapper/injection/mapping_handlers/mapping_handler.py
index 87608a043..0eb6f7ecb 100644
--- a/inputremapper/injection/mapping_handlers/mapping_handler.py
+++ b/inputremapper/injection/mapping_handlers/mapping_handler.py
@@ -68,6 +68,7 @@
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import Mapping
from inputremapper.exceptions import MappingParsingError
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.input_event import InputEvent
from inputremapper.logging.logger import logger
@@ -154,6 +155,7 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
**_,
) -> None:
"""Initialize the handler
@@ -167,6 +169,7 @@ def __init__(
self.mapping = mapping
self.input_configs = list(combination)
self._sub_handler = None
+ self.global_uinputs = global_uinputs
def notify(
self,
diff --git a/inputremapper/injection/mapping_handlers/mapping_parser.py b/inputremapper/injection/mapping_handlers/mapping_parser.py
index 65012e833..31477ab33 100644
--- a/inputremapper/injection/mapping_handlers/mapping_parser.py
+++ b/inputremapper/injection/mapping_handlers/mapping_parser.py
@@ -27,8 +27,9 @@
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import Mapping
from inputremapper.configs.preset import Preset
-from inputremapper.configs.system_mapping import DISABLE_CODE, DISABLE_NAME
+from inputremapper.configs.keyboard_layout import DISABLE_CODE, DISABLE_NAME
from inputremapper.exceptions import MappingParsingError
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.macros.parse import is_this_a_macro
from inputremapper.injection.mapping_handlers.abs_to_abs_handler import AbsToAbsHandler
from inputremapper.injection.mapping_handlers.abs_to_btn_handler import AbsToBtnHandler
@@ -76,250 +77,285 @@
}
-def parse_mappings(preset: Preset, context: ContextProtocol) -> EventPipelines:
- """Create a dict with a list of MappingHandler for each InputEvent."""
- 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:
- logger.warning(
- "a mapping handler '%s' for %s is not implemented",
- handler_enum,
- mapping.format_name(),
- )
- continue
+class MappingParser:
+ def __init__(
+ self,
+ global_uinputs: GlobalUInputs,
+ ):
+ self.global_uinputs = global_uinputs
+
+ def parse_mappings(
+ self,
+ preset: Preset,
+ context: ContextProtocol,
+ ) -> EventPipelines:
+ """Create a dict with a list of MappingHandler for each InputEvent."""
+ 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 = self._get_output_handler(mapping)
+ constructor = mapping_handler_classes[handler_enum]
+ if not constructor:
+ logger.warning(
+ "a mapping handler '%s' for %s is not implemented",
+ handler_enum,
+ mapping.format_name(),
+ )
+ continue
- output_handler = constructor(
- mapping.input_combination,
- mapping,
- context=context,
- )
+ output_handler = constructor(
+ mapping.input_combination,
+ mapping,
+ context=context,
+ global_uinputs=self.global_uinputs,
+ )
- # 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))
+ # layer other handlers on top until the outer handler needs ranking or can
+ # directly handle a input event
+ handlers.extend(self._create_event_pipeline(output_handler, context))
+
+ # figure out which handlers need ranking and wrap them with hierarchy_handlers
+ need_ranking = defaultdict(set)
+ 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,
+ )
+
+ need_ranking[combination].add(handler)
+ handlers.remove(handler)
+
+ # the HierarchyHandler's might not be the starting point of the event pipeline,
+ # layer other handlers on top again.
+ ranked_handlers = self._create_hierarchy_handlers(need_ranking)
+ for handler in ranked_handlers:
+ handlers.extend(
+ self._create_event_pipeline(handler, context, ignore_ranking=True)
+ )
- # figure out which handlers need ranking and wrap them with hierarchy_handlers
- need_ranking = defaultdict(set)
- 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,
+ # 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
+ event_pipelines: EventPipelines = defaultdict(set)
+ for handler in handlers:
+ assert handler.input_configs
+ for input_config in handler.input_configs:
+ logger.debug(
+ "event-pipeline with entry point: %s %s",
+ get_evdev_constant_name(*input_config.type_and_code),
+ input_config.input_match_hash,
+ )
+ logger.debug_mapping_handler(handler)
+ event_pipelines[input_config].add(handler)
+
+ return event_pipelines
+
+ def _create_event_pipeline(
+ self,
+ handler: MappingHandler,
+ context: ContextProtocol,
+ ignore_ranking=False,
+ ) -> List[MappingHandler]:
+ """Recursively wrap a handler with other handlers until the
+ outer handler needs ranking or is finished wrapping.
+ """
+ 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"
)
- need_ranking[combination].add(handler)
- handlers.remove(handler)
-
- # the HierarchyHandler's might not be the starting point of the event pipeline,
- # 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
- event_pipelines: EventPipelines = defaultdict(set)
- for handler in handlers:
- assert handler.input_configs
- for input_config in handler.input_configs:
- logger.debug(
- "event-pipeline with entry point: %s %s",
- get_evdev_constant_name(*input_config.type_and_code),
- input_config.input_match_hash,
- )
- logger.debug_mapping_handler(handler)
- event_pipelines[input_config].add(handler)
-
- return event_pipelines
-
-
-def _create_event_pipeline(
- handler: MappingHandler, context: ContextProtocol, ignore_ranking=False
-) -> List[MappingHandler]:
- """Recursively wrap a handler with other handlers until the
- outer handler needs ranking or is finished wrapping.
- """
- 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,
+ global_uinputs=self.global_uinputs,
)
+ 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)
- 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(self._create_event_pipeline(super_handler, context))
- handlers.extend(_create_event_pipeline(super_handler, context))
+ if handler.input_configs:
+ # the handler was only partially wrapped,
+ # we need to return it as a toplevel handler
+ handlers.append(handler)
- if handler.input_configs:
- # the handler was only partially wrapped,
- # we need to return it as a toplevel handler
- handlers.append(handler)
+ return handlers
- return handlers
+ def _get_output_handler(self, mapping: Mapping) -> HandlerEnums:
+ """Determine the correct output handler.
+ 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
-def _get_output_handler(mapping: Mapping) -> HandlerEnums:
- """Determine the correct output handler.
+ if mapping.output_symbol:
+ if is_this_a_macro(mapping.output_symbol):
+ return HandlerEnums.macro
- 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
+ return HandlerEnums.key
- if mapping.output_symbol:
- if is_this_a_macro(mapping.output_symbol):
- return HandlerEnums.macro
+ if mapping.output_type == EV_KEY:
+ return HandlerEnums.key
- return HandlerEnums.key
+ input_event = self._maps_axis(mapping.input_combination)
+ if not input_event:
+ raise MappingParsingError(
+ f"This {mapping = } does not map to an axis, key or macro",
+ mapping=Mapping,
+ )
- if mapping.output_type == EV_KEY:
- return HandlerEnums.key
+ 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
- input_event = _maps_axis(mapping.input_combination)
- if not input_event:
raise MappingParsingError(
- f"This {mapping = } does not map to an axis, key or macro",
- mapping=Mapping,
+ f"the output of {mapping = } is unknown", mapping=Mapping
)
- 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)
-
-
-def _maps_axis(combination: InputCombination) -> Optional[InputConfig]:
- """Whether this InputCombination contains an InputEvent that is treated as
- an axis and not a binary (key or button) event.
- """
- for event in combination:
- if event.defines_analog_input:
- return event
- return None
-
-
-def _create_hierarchy_handlers(
- handlers: Dict[InputCombination, Set[MappingHandler]]
-) -> Set[MappingHandler]:
- """Sort handlers by input events and create Hierarchy handlers."""
- sorted_handlers = set()
- all_combinations = handlers.keys()
- events = set()
-
- # gather all InputEvents from all handlers
- for combination in all_combinations:
+ def _maps_axis(self, combination: InputCombination) -> Optional[InputConfig]:
+ """Whether this InputCombination contains an InputEvent that is treated as
+ an axis and not a binary (key or button) event.
+ """
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
- sorted_handlers.update(handlers[combinations_with_event[0]])
- continue
-
- # there are multiple handler with the same event.
- # rank them and create the HierarchyHandler
- sorted_combinations = _order_combinations(combinations_with_event, event)
- sub_handlers: List[MappingHandler] = []
- for combination in sorted_combinations:
- sub_handlers.append(*handlers[combination])
-
- 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(
- combinations: List[InputCombination], common_config: InputConfig
-) -> List[InputCombination]:
- """Reorder the keys according to some rules.
-
- 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
- ----------
- combinations
- the list which needs ordering
- common_config
- the InputConfig all InputCombination's in combinations have in common
- """
- combinations.sort(key=len)
-
- for start, end in _ranges_with_constant_length(combinations.copy()):
- sub_list = combinations[start:end]
- sub_list.sort(key=lambda x: x.index(common_config))
- combinations[start:end] = sub_list
-
- combinations.reverse()
- return combinations
-
-
-def _ranges_with_constant_length(x: Sequence[Sized]) -> Iterable[Tuple[int, int]]:
- """Get all ranges of x for which the elements have constant length
-
- 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(
- "ranges_with_constant_length was called with an unordered list"
+ if event.defines_analog_input:
+ return event
+ return None
+
+ def _create_hierarchy_handlers(
+ self,
+ handlers: Dict[InputCombination, Set[MappingHandler]],
+ ) -> Set[MappingHandler]:
+ """Sort handlers by input events and create Hierarchy handlers."""
+ 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
+ sorted_handlers.update(handlers[combinations_with_event[0]])
+ continue
+
+ # there are multiple handler with the same event.
+ # rank them and create the HierarchyHandler
+ sorted_combinations = self._order_combinations(
+ combinations_with_event,
+ event,
)
- last_len = len(y)
+ sub_handlers: List[MappingHandler] = []
+ for combination in sorted_combinations:
+ sub_handlers.append(*handlers[combination])
+
+ sorted_handlers.add(
+ HierarchyHandler(
+ sub_handlers,
+ event,
+ self.global_uinputs,
+ )
+ )
+ 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(
+ self,
+ combinations: List[InputCombination],
+ common_config: InputConfig,
+ ) -> List[InputCombination]:
+ """Reorder the keys according to some rules.
+
+ 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
+ ----------
+ combinations
+ the list which needs ordering
+ common_config
+ the InputConfig all InputCombination's in combinations have in common
+ """
+ combinations.sort(key=len)
+
+ for start, end in self._ranges_with_constant_length(combinations.copy()):
+ sub_list = combinations[start:end]
+ sub_list.sort(key=lambda x: x.index(common_config))
+ combinations[start:end] = sub_list
+
+ combinations.reverse()
+ return combinations
+
+ def _ranges_with_constant_length(
+ self,
+ x: Sequence[Sized],
+ ) -> Iterable[Tuple[int, int]]:
+ """Get all ranges of x for which the elements have constant length
+
+ 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(
+ "ranges_with_constant_length was called with an unordered list"
+ )
+ last_len = len(y)
diff --git a/inputremapper/injection/mapping_handlers/rel_to_abs_handler.py b/inputremapper/injection/mapping_handlers/rel_to_abs_handler.py
index e8b5ec6a4..ec6de883e 100644
--- a/inputremapper/injection/mapping_handlers/rel_to_abs_handler.py
+++ b/inputremapper/injection/mapping_handlers/rel_to_abs_handler.py
@@ -39,7 +39,7 @@
REL_XY_SCALING,
DEFAULT_REL_RATE,
)
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.axis_transform import Transformation
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
@@ -74,9 +74,10 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
**_,
) -> None:
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
# find the input event we are supposed to map. If the input combination is
# BTN_A + REL_X + BTN_B, then use the value of REL_X for the transformation
@@ -227,7 +228,7 @@ def _scale_to_target(self, x: float) -> int:
def _write(self, value: int) -> None:
"""Inject."""
try:
- global_uinputs.write(
+ self.global_uinputs.write(
(*self._output_axis, value), self.mapping.target_uinput
)
except OverflowError:
diff --git a/inputremapper/injection/mapping_handlers/rel_to_btn_handler.py b/inputremapper/injection/mapping_handlers/rel_to_btn_handler.py
index bbc39d6c8..387de3823 100644
--- a/inputremapper/injection/mapping_handlers/rel_to_btn_handler.py
+++ b/inputremapper/injection/mapping_handlers/rel_to_btn_handler.py
@@ -25,6 +25,7 @@
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import Mapping
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
InputEventHandler,
@@ -49,9 +50,10 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
**_,
) -> None:
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
self._active = False
self._input_config = combination[0]
diff --git a/inputremapper/injection/mapping_handlers/rel_to_rel_handler.py b/inputremapper/injection/mapping_handlers/rel_to_rel_handler.py
index d41a2c465..227c89e06 100644
--- a/inputremapper/injection/mapping_handlers/rel_to_rel_handler.py
+++ b/inputremapper/injection/mapping_handlers/rel_to_rel_handler.py
@@ -37,7 +37,7 @@
WHEEL_SCALING,
WHEEL_HI_RES_SCALING,
)
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs
from inputremapper.injection.mapping_handlers.axis_transform import Transformation
from inputremapper.injection.mapping_handlers.mapping_handler import (
MappingHandler,
@@ -91,9 +91,10 @@ def __init__(
self,
combination: InputCombination,
mapping: Mapping,
+ global_uinputs: GlobalUInputs,
**_,
) -> None:
- super().__init__(combination, mapping)
+ super().__init__(combination, mapping, global_uinputs)
assert self.mapping.output_code is not None
@@ -256,7 +257,7 @@ def _write(self, code: int, value: int):
if value == 0:
return
- global_uinputs.write(
+ self.global_uinputs.write(
(EV_REL, code, value),
self.mapping.target_uinput,
)
diff --git a/tests/integration/test_components.py b/tests/integration/test_components.py
index 6d1f23b70..37c1a67ad 100644
--- a/tests/integration/test_components.py
+++ b/tests/integration/test_components.py
@@ -37,7 +37,7 @@
from tests.lib.logger import logger
from inputremapper.gui.controller import Controller
-from inputremapper.configs.system_mapping import XKB_KEYCODE_OFFSET
+from inputremapper.configs.keyboard_layout import XKB_KEYCODE_OFFSET
from inputremapper.gui.utils import CTX_ERROR, CTX_WARNING, gtk_iteration
from inputremapper.gui.messages.message_broker import (
MessageBroker,
diff --git a/tests/integration/test_daemon.py b/tests/integration/test_daemon.py
index 1ed96839f..27593a24f 100644
--- a/tests/integration/test_daemon.py
+++ b/tests/integration/test_daemon.py
@@ -43,6 +43,8 @@ def gtk_iteration():
@test_setup
class TestDBusDaemon(unittest.TestCase):
def setUp(self):
+ # You need to install input-remapper into your system in order for this test
+ # to work.
self.process = multiprocessing.Process(
target=os.system, args=("input-remapper-service -d",)
)
diff --git a/tests/integration/test_gui.py b/tests/integration/test_gui.py
index be79a778f..9aa395283 100644
--- a/tests/integration/test_gui.py
+++ b/tests/integration/test_gui.py
@@ -46,13 +46,14 @@
get_incomplete_parameter,
get_incomplete_function_name,
)
+from inputremapper.injection.global_uinputs import GlobalUInputs, FrontendUInput, UInput
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
from inputremapper.input_event import InputEvent
from tests.integration.test_components import FlowBoxTestUtils
from tests.lib.cleanup import cleanup
from tests.lib.constants import EVENT_READ_TIMEOUT
from tests.lib.fixtures import fixtures
from tests.lib.fixtures import prepare_presets
-from tests.lib.global_uinputs import reset_global_uinputs_for_service
from tests.lib.logger import logger
from tests.lib.pipes import push_event, push_events, uinput_write_history_pipe
from tests.lib.project_root import get_project_root
@@ -64,10 +65,10 @@
gi.require_version("GLib", "2.0")
from gi.repository import Gtk, GLib, Gdk, GtkSource
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.configs.mapping import Mapping
from inputremapper.configs.paths import PathUtils
-from inputremapper.configs.global_config import global_config
+from inputremapper.configs.global_config import GlobalConfig
from inputremapper.groups import _Groups
from inputremapper.gui.data_manager import DataManager
from inputremapper.gui.messages.message_broker import (
@@ -102,7 +103,14 @@
def launch(
argv=None,
-) -> Tuple[UserInterface, Controller, DataManager, MessageBroker, DaemonProxy]:
+) -> Tuple[
+ UserInterface,
+ Controller,
+ DataManager,
+ MessageBroker,
+ DaemonProxy,
+ GlobalConfig,
+]:
"""Start input-remapper-gtk with the command line argument array argv."""
bin_path = os.path.join(get_project_root(), "bin", "input-remapper-gtk")
@@ -127,12 +135,14 @@ def launch(
module.data_manager,
module.message_broker,
module.daemon,
+ module.global_config,
)
def start_reader_service():
def process():
- reader_service = ReaderService(_Groups())
+ global_uinputs = GlobalUInputs(FrontendUInput)
+ reader_service = ReaderService(_Groups(), global_uinputs)
loop = asyncio.new_event_loop()
loop.run_until_complete(reader_service.run())
@@ -154,15 +164,25 @@ def os_system_patch(cmd, original_os_system=os.system):
def patch_launch():
"""patch the launch function such that we don't connect to
the dbus and don't use pkexec to start the reader-service"""
+
+ def bootstrap_daemon():
+ # The daemon gets fresh instances of everything, because as far as I remember
+ # it runs in a separate process.
+ global_config = GlobalConfig()
+ global_uinputs = GlobalUInputs(UInput)
+ mapping_parser = MappingParser(global_uinputs)
+
+ return Daemon(
+ global_config,
+ global_uinputs,
+ mapping_parser,
+ )
+
with patch.object(
os,
"system",
os_system_patch,
- ), patch.object(
- Daemon,
- "connect",
- Daemon,
- ):
+ ), patch.object(Daemon, "connect", bootstrap_daemon):
yield
@@ -213,12 +233,25 @@ def os_system(cmd, original_os_system=os.system):
# time for the application
self.os_system_patch.start()
+ def bootstrap_daemon(self):
+ # The daemon gets fresh instances of everything, because as far as I remember
+ # it runs in a separate process.
+ global_config = GlobalConfig()
+ global_uinputs = GlobalUInputs(UInput)
+ mapping_parser = MappingParser(global_uinputs)
+
+ return Daemon(
+ global_config,
+ global_uinputs,
+ mapping_parser,
+ )
+
def patch_daemon(self):
# don't try to connect, return an object instance of it instead
self.daemon_connect_patch = patch.object(
Daemon,
"connect",
- Daemon,
+ lambda: self.bootstrap_daemon(),
)
self.daemon_connect_patch.start()
@@ -233,6 +266,7 @@ def setUp(self):
self.data_manager,
self.message_broker,
self.daemon,
+ self.global_config,
) = launch()
def tearDown(self):
@@ -306,6 +340,7 @@ def setUp(self):
self.data_manager,
self.message_broker,
self.daemon,
+ self.global_config,
) = launch()
get = self.user_interface.get
@@ -340,7 +375,7 @@ def grab(_):
evdev.InputDevice.grab = grab
- global_config._save_config()
+ self.global_config._save_config()
self.throttle(20)
@@ -1781,11 +1816,6 @@ def test_cannot_record_keys(self):
self.assertIn("Stop", text)
def test_start_injecting(self):
- # It's 2023 everyone! That means this test randomly stopped working because it
- # used FrontendUInputs instead of regular UInputs. I guess a fucking ghost
- # was fixing this for us during 2022, but it seems to have disappeared.
- reset_global_uinputs_for_service()
-
self.controller.load_group("Foo Device 2")
with spy(self.daemon, "set_config_dir") as spy1:
@@ -1840,8 +1870,6 @@ def test_start_injecting(self):
self.assertNotIn("input-remapper", device_group_entry.name)
def test_stop_injecting(self):
- reset_global_uinputs_for_service()
-
self.controller.load_group("Foo Device 2")
self.start_injector_btn.clicked()
gtk_iteration()
@@ -2128,9 +2156,9 @@ def test_autocomplete_key(self):
complete_key_name = "Test_Foo_Bar"
- system_mapping.clear()
- system_mapping._set(complete_key_name, 1)
- system_mapping._set("KEY_A", 30) # we need this for the UIMapping to work
+ keyboard_layout.clear()
+ keyboard_layout._set(complete_key_name, 1)
+ keyboard_layout._set("KEY_A", 30) # we need this for the UIMapping to work
# it can autocomplete a combination inbetween other things
incomplete = "qux_1\n + + qux_2"
diff --git a/tests/lib/cleanup.py b/tests/lib/cleanup.py
index 14b688a70..daee127ee 100644
--- a/tests/lib/cleanup.py
+++ b/tests/lib/cleanup.py
@@ -20,30 +20,31 @@
from __future__ import annotations
+import asyncio
import copy
import os
import shutil
import time
-import asyncio
-import psutil
from pickle import UnpicklingError
-# TODO on it. You don't need a framework for this by the way:
-# don't import anything from input_remapper gloablly here, because some files execute
-# code when imported, which can screw up patches. I wish we had a dependency injection
-# framework that patches together the dependencies during runtime...
+import psutil
+from tests.lib.constants import EVENT_READ_TIMEOUT
+from tests.lib.fixtures import fixtures
from tests.lib.logger import logger
+from tests.lib.patches import uinputs
from tests.lib.pipes import (
uinput_write_history_pipe,
uinput_write_history,
pending_events,
setup_pipe,
)
-from tests.lib.constants import EVENT_READ_TIMEOUT
from tests.lib.tmp import tmp
-from tests.lib.fixtures import fixtures
-from tests.lib.patches import uinputs
+
+# TODO on it. You don't need a framework for this by the way:
+# don't import anything from input_remapper gloablly here, because some files execute
+# code when imported, which can screw up patches. I wish we had a dependency injection
+# framework that patches together the dependencies during runtime...
environ_copy = copy.deepcopy(os.environ)
@@ -81,12 +82,9 @@ def quick_cleanup(log=True):
# Reminder: before patches are applied in test.py, no inputremapper module
# may be imported. So tests.lib imports them just-in-time in functions instead.
from inputremapper.injection.macros.macro import macro_variables
- from inputremapper.configs.global_config import global_config
- from inputremapper.configs.system_mapping import system_mapping
+ from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.gui.utils import debounce_manager
- from inputremapper.configs.paths import PathUtils
- from inputremapper.injection.global_uinputs import global_uinputs
- from tests.lib.global_uinputs import reset_global_uinputs_for_service
+ from inputremapper.injection.global_uinputs import GlobalUInputs
if log:
logger.info("Quick cleanup...")
@@ -130,11 +128,7 @@ def quick_cleanup(log=True):
if os.path.exists(tmp):
shutil.rmtree(tmp)
- global_config.path = os.path.join(PathUtils.get_config_path(), "config.json")
- global_config.clear_config()
- global_config._save_config()
-
- system_mapping.populate()
+ keyboard_layout.populate()
clear_write_history()
@@ -155,11 +149,6 @@ def quick_cleanup(log=True):
assert not pipe.poll()
assert macro_variables.is_alive(1)
- for uinput in global_uinputs.devices.values():
- uinput.write_count = 0
- uinput.write_history = []
-
- reset_global_uinputs_for_service()
if log:
logger.info("Quick cleanup done")
diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py
index 2cbe9b6c2..4d3614f24 100644
--- a/tests/lib/fixtures.py
+++ b/tests/lib/fixtures.py
@@ -22,12 +22,16 @@
import dataclasses
import json
+import time
from hashlib import md5
from typing import Dict, Optional
-import time
import evdev
+from inputremapper.configs.input_config import InputCombination
+from inputremapper.configs.mapping import Mapping
+from inputremapper.configs.paths import PathUtils
+from inputremapper.configs.preset import Preset
from tests.lib.logger import logger
# input-remapper is only interested in devices that have EV_KEY, add some
@@ -347,12 +351,6 @@ def prepare_presets():
"""prepare a few presets for use in tests
"Foo Device 2/preset3" is the newest and "Foo Device 2/preset2" is set to autoload
"""
- from inputremapper.configs.preset import Preset
- from inputremapper.configs.mapping import Mapping
- from inputremapper.configs.paths import PathUtils
- from inputremapper.configs.global_config import global_config
- from inputremapper.configs.input_config import InputCombination
-
preset1 = Preset(PathUtils.get_preset_path("Foo Device", "preset1"))
preset1.add(
Mapping.from_combination(
@@ -379,6 +377,4 @@ def prepare_presets():
with open(PathUtils.get_config_path("config.json"), "w") as file:
json.dump({"autoload": {"Foo Device 2": "preset2"}}, file, indent=4)
- global_config.load_config()
-
return preset1, preset2, preset3
diff --git a/tests/lib/global_uinputs.py b/tests/lib/global_uinputs.py
deleted file mode 100644
index cf6608c3c..000000000
--- a/tests/lib/global_uinputs.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/python3
-# -*- coding: utf-8 -*-
-# input-remapper - GUI for device specific keyboard mappings
-# Copyright (C) 2024 sezanzeb
-#
-# 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 .
-
-import sys
-from unittest.mock import patch
-
-from inputremapper.injection.global_uinputs import global_uinputs
-
-
-def reset_global_uinputs_for_service():
- with patch.object(sys, "argv", ["input-remapper-service"]):
- # patch argv for global_uinputs to think it is a service
- global_uinputs.reset()
-
-
-def reset_global_uinputs_for_gui():
- with patch.object(sys, "argv", ["input-remapper-gtk"]):
- global_uinputs.reset()
diff --git a/tests/lib/test_setup.py b/tests/lib/test_setup.py
index 4961128e1..19b71724b 100644
--- a/tests/lib/test_setup.py
+++ b/tests/lib/test_setup.py
@@ -23,8 +23,6 @@
import os
import tracemalloc
-from inputremapper.configs.global_config import global_config
-from inputremapper.configs.paths import PathUtils
from tests.lib.cleanup import cleanup, quick_cleanup
from tests.lib.fixture_pipes import create_fixture_pipes, remove_fixture_pipes
from tests.lib.is_service_running import is_service_running
@@ -64,14 +62,6 @@ def setUpClass():
for patch in patches:
patch.start()
- # TODO if global_config is injected instead, it could work without doing this
- # load_config call here. Because right now the constructor uses variables
- # that are unpatched once global_config.py is imported.
- global_config.path = os.path.join(
- PathUtils.config_path(),
- "config.json",
- )
-
original_setUpClass()
def tearDownClass():
@@ -95,11 +85,6 @@ def tearDown(self):
quick_cleanup()
- # This assertion was important to me somehow in other tests after the cleanup,
- # so I added it to the test setup. I don't remember if this is important to
- # check.
- assert len(global_config.iterate_autoload_presets()) == 0
-
for patch in patches:
patch.stop()
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
index f80720eeb..317cd3803 100644
--- a/tests/unit/test_config.py
+++ b/tests/unit/test_config.py
@@ -22,16 +22,16 @@
import os
import unittest
-from inputremapper.configs.global_config import global_config
+from inputremapper.configs.global_config import GlobalConfig
from inputremapper.configs.paths import PathUtils
-
-from tests.lib.tmp import tmp
from tests.lib.test_setup import test_setup
+from tests.lib.tmp import tmp
@test_setup
class TestConfig(unittest.TestCase):
def test_basic(self):
+ global_config = GlobalConfig()
self.assertEqual(global_config.get("a"), None)
global_config.set("a", 1)
@@ -48,6 +48,7 @@ def test_basic(self):
self.assertEqual(global_config._config["a"]["b"]["c"], 3)
def test_autoload(self):
+ global_config = GlobalConfig()
self.assertEqual(len(global_config.iterate_autoload_presets()), 0)
self.assertFalse(global_config.is_autoloaded("d1", "a"))
self.assertFalse(global_config.is_autoloaded("d2.foo", "b"))
@@ -92,9 +93,9 @@ def test_autoload(self):
self.assertRaises(ValueError, global_config.is_autoloaded, None, "a")
def test_initial(self):
+ global_config = GlobalConfig()
# when loading for the first time, create a config file with
# the default values
- os.remove(global_config.path)
self.assertFalse(os.path.exists(global_config.path))
global_config.load_config()
self.assertTrue(os.path.exists(global_config.path))
@@ -104,6 +105,7 @@ def test_initial(self):
self.assertIn('"autoload": {}', contents)
def test_save_load(self):
+ global_config = GlobalConfig()
self.assertEqual(len(global_config.iterate_autoload_presets()), 0)
global_config.load_config()
diff --git a/tests/unit/test_context.py b/tests/unit/test_context.py
index 87b6dacd6..650606177 100644
--- a/tests/unit/test_context.py
+++ b/tests/unit/test_context.py
@@ -33,6 +33,8 @@
from inputremapper.configs.mapping import Mapping
from inputremapper.configs.preset import Preset
from inputremapper.injection.context import Context
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
from inputremapper.input_event import InputEvent
from tests.lib.test_setup import test_setup
@@ -40,6 +42,9 @@
@test_setup
class TestContext(unittest.TestCase):
def test_callbacks(self):
+ global_uinputs = GlobalUInputs(UInput)
+ mapping_parser = MappingParser(global_uinputs)
+
preset = Preset()
cfg = {
"input_combination": InputCombination.from_tuples((EV_ABS, ABS_X)),
@@ -81,7 +86,7 @@ def test_callbacks(self):
),
)
- context = Context(preset, {}, {})
+ context = Context(preset, {}, {}, mapping_parser)
expected_num_callbacks = {
# ABS_X -> "d" and ABS_X -> wheel have the same type and code
diff --git a/tests/unit/test_control.py b/tests/unit/test_control.py
index f2d800385..a7e4fe31a 100644
--- a/tests/unit/test_control.py
+++ b/tests/unit/test_control.py
@@ -27,12 +27,16 @@
from importlib.machinery import SourceFileLoader
from importlib.util import spec_from_loader, module_from_spec
from unittest.mock import patch
+import re
-from inputremapper.configs.global_config import global_config
+from inputremapper.configs.global_config import GlobalConfig
+from inputremapper.configs.migrations import Migrations
from inputremapper.configs.paths import PathUtils
from inputremapper.configs.preset import Preset
from inputremapper.daemon import Daemon
from inputremapper.groups import groups
+from inputremapper.injection.global_uinputs import GlobalUInputs, FrontendUInput
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
from tests.lib.test_setup import test_setup
from tests.lib.tmp import tmp
@@ -40,7 +44,7 @@
def import_control():
"""Import the core function of the input-remapper-control command."""
bin_path = os.path.join(
- os.getcwd().replace("/tests", ""),
+ re.sub("/tests.*", "", os.getcwd()),
"bin",
"input-remapper-control",
)
@@ -65,7 +69,13 @@ def import_control():
@test_setup
class TestControl(unittest.TestCase):
def setUp(self):
- self.input_remapper_control = InputRemapperControl()
+ self.global_config = GlobalConfig()
+ self.global_uinputs = GlobalUInputs(FrontendUInput)
+ self.migrations = Migrations(self.global_uinputs)
+ self.mapping_parser = MappingParser(self.global_uinputs)
+ self.input_remapper_control = InputRemapperControl(
+ self.global_config, self.migrations
+ )
def test_autoload(self):
device_keys = ["Foo Device 2", "Bar Device"]
@@ -81,7 +91,7 @@ def test_autoload(self):
Preset(paths[1]).save()
Preset(paths[2]).save()
- daemon = Daemon()
+ daemon = Daemon(self.global_config, self.global_uinputs, self.mapping_parser)
self.input_remapper_control.set_daemon(daemon)
@@ -101,8 +111,8 @@ def start_injecting(device: str, preset: str):
patch.object(daemon, "start_injecting", start_injecting).start()
- global_config.set_autoload_preset(groups_[0].key, presets[0])
- global_config.set_autoload_preset(groups_[1].key, presets[1])
+ self.global_config.set_autoload_preset(groups_[0].key, presets[0])
+ self.global_config.set_autoload_preset(groups_[1].key, presets[1])
self.input_remapper_control.communicate(
command="autoload",
@@ -181,7 +191,7 @@ def start_injecting(device: str, preset: str):
daemon.autoload_history.may_autoload(groups_[1].key, presets[1])
)
self.assertEqual(stop_counter, 3)
- global_config.set_autoload_preset(groups_[1].key, presets[2])
+ self.global_config.set_autoload_preset(groups_[1].key, presets[2])
self.input_remapper_control.communicate(
command="autoload",
config_dir=None,
@@ -236,16 +246,16 @@ def test_autoload_other_path(self):
Preset(paths[0]).save()
Preset(paths[1]).save()
- daemon = Daemon()
+ daemon = Daemon(self.global_config, self.global_uinputs, self.mapping_parser)
self.input_remapper_control.set_daemon(daemon)
start_history = []
daemon.start_injecting = lambda *args: start_history.append(args)
- global_config.path = os.path.join(config_dir, "config.json")
- global_config.load_config()
- global_config.set_autoload_preset(device_names[0], presets[0])
- global_config.set_autoload_preset(device_names[1], presets[1])
+ self.global_config.path = os.path.join(config_dir, "config.json")
+ self.global_config.load_config()
+ self.global_config.set_autoload_preset(device_names[0], presets[0])
+ self.global_config.set_autoload_preset(device_names[1], presets[1])
self.input_remapper_control.communicate(
command="autoload",
@@ -262,7 +272,7 @@ def test_start_stop(self):
group = groups.find(key="Foo Device 2")
preset = "preset9"
- daemon = Daemon()
+ daemon = Daemon(self.global_config, self.global_uinputs, self.mapping_parser)
self.input_remapper_control.set_daemon(daemon)
start_history = []
@@ -306,7 +316,7 @@ def test_config_not_found(self):
path = "~/a/preset.json"
config_dir = "/foo/bar"
- daemon = Daemon()
+ daemon = Daemon(self.global_config, self.global_uinputs, self.mapping_parser)
self.input_remapper_control.set_daemon(daemon)
start_history = []
@@ -335,26 +345,26 @@ def test_config_not_found(self):
)
def test_autoload_config_dir(self):
- daemon = Daemon()
+ daemon = Daemon(self.global_config, self.global_uinputs, self.mapping_parser)
path = os.path.join(tmp, "foo")
os.makedirs(path)
with open(os.path.join(path, "config.json"), "w") as file:
file.write('{"foo":"bar"}')
- self.assertIsNone(global_config.get("foo"))
+ self.assertIsNone(self.global_config.get("foo"))
daemon.set_config_dir(path)
# since daemon and this test share the same memory, the global_config
# object that this test can access will be modified
- self.assertEqual(global_config.get("foo"), "bar")
+ self.assertEqual(self.global_config.get("foo"), "bar")
# passing a path that doesn't exist or a path that doesn't contain
# a config.json file won't do anything
os.makedirs(os.path.join(tmp, "bar"))
daemon.set_config_dir(os.path.join(tmp, "bar"))
- self.assertEqual(global_config.get("foo"), "bar")
+ self.assertEqual(self.global_config.get("foo"), "bar")
daemon.set_config_dir(os.path.join(tmp, "qux"))
- self.assertEqual(global_config.get("foo"), "bar")
+ self.assertEqual(self.global_config.get("foo"), "bar")
def test_internals_reader(self):
with patch.object(os, "system") as os_system_patch:
diff --git a/tests/unit/test_controller.py b/tests/unit/test_controller.py
index 39015835d..f28aab23c 100644
--- a/tests/unit/test_controller.py
+++ b/tests/unit/test_controller.py
@@ -26,7 +26,7 @@
import gi
from evdev.ecodes import EV_ABS, ABS_X, ABS_Y, ABS_RX
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.injection.injector import InjectorState
gi.require_version("Gtk", "3.0")
@@ -50,7 +50,7 @@
from inputremapper.gui.reader_client import ReaderClient
from inputremapper.gui.utils import CTX_ERROR, CTX_APPLY, gtk_iteration
from inputremapper.gui.gettext import _
-from inputremapper.injection.global_uinputs import GlobalUInputs
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput, FrontendUInput
from inputremapper.configs.mapping import UIMapping, MappingData, Mapping
from tests.lib.spy import spy
from tests.lib.patches import FakeDaemonProxy
@@ -68,7 +68,7 @@ class TestController(unittest.TestCase):
def setUp(self) -> None:
super().setUp()
self.message_broker = MessageBroker()
- uinputs = GlobalUInputs()
+ uinputs = GlobalUInputs(FrontendUInput)
uinputs.prepare_all()
self.data_manager = DataManager(
self.message_broker,
@@ -76,7 +76,7 @@ def setUp(self) -> None:
ReaderClient(self.message_broker, _Groups()),
FakeDaemonProxy(),
uinputs,
- system_mapping,
+ keyboard_layout,
)
self.user_interface = MagicMock()
self.controller = Controller(self.message_broker, self.data_manager)
diff --git a/tests/unit/test_daemon.py b/tests/unit/test_daemon.py
index e6f01314a..6e0c0529b 100644
--- a/tests/unit/test_daemon.py
+++ b/tests/unit/test_daemon.py
@@ -18,38 +18,36 @@
# You should have received a copy of the GNU General Public License
# along with input-remapper. If not, see .
-from unittest.mock import patch, MagicMock
-
-from evdev._ecodes import EV_ABS
-
-from inputremapper.input_event import InputEvent
-from tests.lib.logger import logger
-from tests.lib.cleanup import cleanup
-from tests.lib.fixtures import Fixture
-from tests.lib.pipes import push_events, uinput_write_history_pipe
-from tests.lib.tmp import tmp
-from tests.lib.fixtures import fixtures
-
+import json
import os
-import unittest
import time
-import json
+import unittest
+from unittest.mock import patch, MagicMock
import evdev
+from evdev._ecodes import EV_ABS
from evdev.ecodes import EV_KEY, KEY_B, KEY_A, ABS_X, BTN_A, BTN_B
from pydbus import SystemBus
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.global_config import GlobalConfig
+from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import Mapping
-from inputremapper.configs.global_config import global_config
-from inputremapper.groups import groups
from inputremapper.configs.paths import PathUtils
-from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.preset import Preset
-from inputremapper.injection.injector import InjectorState
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.daemon import Daemon
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.groups import groups
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
+from inputremapper.injection.injector import InjectorState
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
+from inputremapper.input_event import InputEvent
+from tests.lib.cleanup import cleanup
+from tests.lib.fixtures import Fixture
+from tests.lib.fixtures import fixtures
+from tests.lib.logger import logger
+from tests.lib.pipes import push_events, uinput_write_history_pipe
from tests.lib.test_setup import test_setup, is_service_running
+from tests.lib.tmp import tmp
@test_setup
@@ -58,12 +56,15 @@ class TestDaemon(unittest.TestCase):
def setUp(self):
self.daemon = None
+ self.global_config = GlobalConfig()
+ self.global_uinputs = GlobalUInputs(UInput)
PathUtils.mkdir(PathUtils.get_config_path())
- global_config._save_config()
+ self.global_config._save_config()
+ self.mapping_parser = MappingParser(self.global_uinputs)
# the daemon should be able to create them on demand:
- global_uinputs.devices = {}
- global_uinputs.is_service = True
+ self.global_uinputs.devices = {}
+ self.global_uinputs.is_service = True
def tearDown(self):
# avoid race conditions with other tests, daemon may run processes
@@ -134,7 +135,7 @@ def test_daemon(self):
)
)
preset.save()
- global_config.set_autoload_preset(group.key, preset_name)
+ self.global_config.set_autoload_preset(group.key, preset_name)
"""Injection 1"""
@@ -144,19 +145,23 @@ def test_daemon(self):
[InputEvent.key(BTN_B, 1, fixtures.gamepad.get_device_hash())],
)
- self.daemon = Daemon()
+ self.daemon = Daemon(
+ self.global_config,
+ self.global_uinputs,
+ self.mapping_parser,
+ )
self.assertFalse(uinput_write_history_pipe[0].poll())
# has been cleanedUp in setUp
- self.assertNotIn("keyboard", global_uinputs.devices)
+ self.assertNotIn("keyboard", self.global_uinputs.devices)
logger.info(f"start injector for {group.key}")
self.daemon.start_injecting(group.key, preset_name)
# created on demand
- self.assertIn("keyboard", global_uinputs.devices)
- self.assertNotIn("gamepad", global_uinputs.devices)
+ self.assertIn("keyboard", self.global_uinputs.devices)
+ self.assertNotIn("gamepad", self.global_uinputs.devices)
self.assertEqual(self.daemon.get_state(group.key), InjectorState.STARTING)
self.assertEqual(self.daemon.get_state(group2.key), InjectorState.UNKNOWN)
@@ -202,15 +207,19 @@ def test_daemon(self):
self.assertEqual(event.value, 1)
def test_config_dir(self):
- global_config.set("foo", "bar")
- self.assertEqual(global_config.get("foo"), "bar")
+ self.global_config.set("foo", "bar")
+ self.assertEqual(self.global_config.get("foo"), "bar")
# freshly loads the config and therefore removes the previosly added key.
# This is important so that if the service is started via sudo or pkexec
# it knows where to look for configuration files.
- self.daemon = Daemon()
+ self.daemon = Daemon(
+ self.global_config,
+ self.global_uinputs,
+ self.mapping_parser,
+ )
self.assertEqual(self.daemon.config_dir, PathUtils.get_config_path())
- self.assertIsNone(global_config.get("foo"))
+ self.assertIsNone(self.global_config.get("foo"))
def test_refresh_on_start(self):
if os.path.exists(PathUtils.get_config_path("xmodmap.json")):
@@ -227,8 +236,8 @@ def test_refresh_on_start(self):
# this test only makes sense if this device is unknown yet
self.assertIsNone(group)
- system_mapping.clear()
- system_mapping._set("a", KEY_A)
+ keyboard_layout.clear()
+ keyboard_layout._set("a", KEY_A)
preset = Preset(PathUtils.get_preset_path(group_name, preset_name))
preset.add(
@@ -241,12 +250,16 @@ def test_refresh_on_start(self):
# make the daemon load the file instead
with open(PathUtils.get_config_path("xmodmap.json"), "w") as file:
- json.dump(system_mapping._mapping, file, indent=4)
- system_mapping.clear()
+ json.dump(keyboard_layout._mapping, file, indent=4)
+ keyboard_layout.clear()
preset.save()
- global_config.set_autoload_preset(group_key, preset_name)
- self.daemon = Daemon()
+ self.global_config.set_autoload_preset(group_key, preset_name)
+ self.daemon = Daemon(
+ self.global_config,
+ self.global_uinputs,
+ self.mapping_parser,
+ )
# make sure the devices are populated
groups.refresh()
@@ -283,7 +296,11 @@ def test_refresh_for_unknown_key(self):
# this test only makes sense if this device is unknown yet
self.assertIsNone(groups.find(name=device_9876))
- self.daemon = Daemon()
+ self.daemon = Daemon(
+ self.global_config,
+ self.global_uinputs,
+ self.mapping_parser,
+ )
# make sure the devices are populated
groups.refresh()
@@ -329,7 +346,7 @@ def test_xmodmap_file(self):
)
preset.save()
- system_mapping.clear()
+ keyboard_layout.clear()
push_events(
fixtures.bar_device,
@@ -345,8 +362,8 @@ def test_xmodmap_file(self):
# an existing config file is needed otherwise set_config_dir refuses
# to use the directory
config_path = os.path.join(config_dir, "config.json")
- global_config.path = config_path
- global_config._save_config()
+ self.global_config.path = config_path
+ self.global_config._save_config()
# finally, create the xmodmap file
xmodmap_path = os.path.join(config_dir, "xmodmap.json")
@@ -355,7 +372,11 @@ def test_xmodmap_file(self):
# test setup complete
- self.daemon = Daemon()
+ self.daemon = Daemon(
+ self.global_config,
+ self.global_uinputs,
+ self.mapping_parser,
+ )
self.daemon.set_config_dir(config_dir)
self.daemon.start_injecting(group.key, preset_name)
@@ -373,7 +394,11 @@ def test_start_stop(self):
group = groups.find(key=group_key)
preset_name = "preset8"
- daemon = Daemon()
+ daemon = Daemon(
+ self.global_config,
+ self.global_uinputs,
+ self.mapping_parser,
+ )
self.daemon = daemon
pereset = Preset(group.get_preset_path(preset_name))
@@ -441,7 +466,7 @@ def test_autoload(self):
group_key = "Qux/Device?"
group = groups.find(key=group_key)
- daemon = Daemon()
+ daemon = Daemon(self.global_config, self.global_uinputs, self.mapping_parser)
self.daemon = daemon
preset = Preset(group.get_preset_path(preset_name))
@@ -459,7 +484,7 @@ def test_autoload(self):
self.assertNotIn(group_key, daemon.autoload_history._autoload_history)
self.assertTrue(daemon.autoload_history.may_autoload(group_key, preset_name))
- global_config.set_autoload_preset(group_key, preset_name)
+ self.global_config.set_autoload_preset(group_key, preset_name)
len_before = len(self.daemon.autoload_history._autoload_history)
# now autoloading is configured, so it will autoload
self.daemon._autoload(group_key)
@@ -498,7 +523,11 @@ def test_autoload(self):
self.assertEqual(len_before, len_after)
def test_autoload_2(self):
- self.daemon = Daemon()
+ self.daemon = Daemon(
+ self.global_config,
+ self.global_uinputs,
+ self.mapping_parser,
+ )
history = self.daemon.autoload_history._autoload_history
# existing device
@@ -513,10 +542,10 @@ def test_autoload_2(self):
)
)
preset.save()
- global_config.set_autoload_preset(group.key, preset_name)
+ self.global_config.set_autoload_preset(group.key, preset_name)
# ignored, won't cause problems:
- global_config.set_autoload_preset("non-existant-key", "foo")
+ self.global_config.set_autoload_preset("non-existant-key", "foo")
self.daemon.autoload()
self.assertEqual(len(history), 1)
@@ -537,9 +566,13 @@ def test_autoload_3(self):
)
preset.save()
- global_config.set_autoload_preset(group.key, preset_name)
+ self.global_config.set_autoload_preset(group.key, preset_name)
- self.daemon = Daemon()
+ self.daemon = Daemon(
+ self.global_config,
+ self.global_uinputs,
+ self.mapping_parser,
+ )
groups.set_groups([]) # caused the bug
self.assertIsNone(groups.find(key="Foo Device 2"))
self.daemon.autoload()
diff --git a/tests/unit/test_data_manager.py b/tests/unit/test_data_manager.py
index 18c5873b3..770c7caad 100644
--- a/tests/unit/test_data_manager.py
+++ b/tests/unit/test_data_manager.py
@@ -25,12 +25,12 @@
from typing import List
from unittest.mock import MagicMock, call
-from inputremapper.configs.global_config import global_config
+from inputremapper.configs.global_config import GlobalConfig
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import UIMapping, MappingData
from inputremapper.configs.paths import PathUtils
from inputremapper.configs.preset import Preset
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.exceptions import DataManagementError
from inputremapper.groups import _Groups
from inputremapper.gui.data_manager import DataManager, DEFAULT_PRESET_NAME
@@ -43,7 +43,7 @@
CombinationUpdate,
)
from inputremapper.gui.reader_client import ReaderClient
-from inputremapper.injection.global_uinputs import GlobalUInputs
+from inputremapper.injection.global_uinputs import GlobalUInputs, FrontendUInput
from tests.lib.fixtures import prepare_presets
from tests.lib.patches import FakeDaemonProxy
from tests.lib.test_setup import test_setup
@@ -62,15 +62,16 @@ class TestDataManager(unittest.TestCase):
def setUp(self) -> None:
self.message_broker = MessageBroker()
self.reader = ReaderClient(self.message_broker, _Groups())
- self.uinputs = GlobalUInputs()
+ self.uinputs = GlobalUInputs(FrontendUInput)
self.uinputs.prepare_all()
+ self.global_config = GlobalConfig()
self.data_manager = DataManager(
self.message_broker,
- global_config,
+ self.global_config,
self.reader,
FakeDaemonProxy(),
self.uinputs,
- system_mapping,
+ keyboard_layout,
)
def test_load_group_provides_presets(self):
diff --git a/tests/unit/test_event_pipeline/test_event_pipeline.py b/tests/unit/test_event_pipeline/test_event_pipeline.py
index 97271993c..bd542d9e5 100644
--- a/tests/unit/test_event_pipeline/test_event_pipeline.py
+++ b/tests/unit/test_event_pipeline/test_event_pipeline.py
@@ -54,11 +54,12 @@
DEFAULT_REL_RATE,
)
from inputremapper.configs.preset import Preset
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.injection.context import Context
from inputremapper.injection.event_reader import EventReader
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
from inputremapper.input_event import InputEvent
from tests.lib.cleanup import cleanup
from tests.lib.logger import logger
@@ -71,8 +72,11 @@ class EventPipelineTestBase(unittest.IsolatedAsyncioTestCase):
"""Test the event pipeline form event_reader to UInput."""
def setUp(self):
- global_uinputs.is_service = True
- global_uinputs.prepare_all()
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
+ self.mapping_parser = MappingParser(self.global_uinputs)
+ self.global_uinputs.is_service = True
+ self.global_uinputs.prepare_all()
self.forward_uinput = evdev.UInput()
self.stop_event = asyncio.Event()
@@ -100,6 +104,7 @@ def create_event_reader(
preset,
source_devices={},
forward_devices={source.get_device_hash(): self.forward_uinput},
+ mapping_parser=self.mapping_parser,
)
reader = EventReader(
context,
@@ -136,19 +141,19 @@ async def test_any_event_as_button(self):
c_up = (EV_ABS, ABS_HAT0X, 0)
# first change the system mapping because Mapping will validate against it
- system_mapping.clear()
+ keyboard_layout.clear()
code_w = 71
code_b = 72
code_c = 73
code_d = 74
code_a = 75
code_s = 76
- system_mapping._set("w", code_w)
- system_mapping._set("d", code_d)
- system_mapping._set("a", code_a)
- system_mapping._set("s", code_s)
- system_mapping._set("b", code_b)
- system_mapping._set("c", code_c)
+ keyboard_layout._set("w", code_w)
+ keyboard_layout._set("d", code_d)
+ keyboard_layout._set("a", code_a)
+ keyboard_layout._set("s", code_s)
+ keyboard_layout._set("b", code_b)
+ keyboard_layout._set("c", code_c)
preset = Preset()
preset.add(
@@ -213,7 +218,7 @@ async def test_any_event_as_button(self):
# wait a bit for the rel_to_btn handler to send the key up
await asyncio.sleep(0.1)
- history = global_uinputs.get_uinput("keyboard").write_history
+ history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(history.count((EV_KEY, code_b, 1)), 1)
self.assertEqual(history.count((EV_KEY, code_c, 1)), 1)
@@ -251,10 +256,10 @@ async def test_reset_releases_keys(self):
)
event_reader = self.create_event_reader(preset, fixtures.foo_device_2_keyboard)
- a = system_mapping.get("a")
- b = system_mapping.get("b")
- c = system_mapping.get("c")
- d = system_mapping.get("d")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
+ c = keyboard_layout.get("c")
+ d = keyboard_layout.get("d")
await self.send_events(
[
@@ -267,7 +272,7 @@ async def test_reset_releases_keys(self):
await asyncio.sleep(0.1)
forwarded_history = self.forward_uinput.write_history
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(len(forwarded_history), 0)
# a down, b down, c down, d down
@@ -277,7 +282,7 @@ async def test_reset_releases_keys(self):
await asyncio.sleep(0.1)
forwarded_history = self.forward_uinput.write_history
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(len(forwarded_history), 0)
# all a, b, c, d down+up
@@ -292,7 +297,7 @@ async def test_forward_abs(self):
"""Test if EV_ABS events are forwarded when other events of the same input are not."""
preset = Preset()
# BTN_A -> 77
- system_mapping._set("b", 77)
+ keyboard_layout._set("b", 77)
preset.add(
Mapping.from_combination(
InputCombination([InputConfig(type=EV_KEY, code=BTN_A)]),
@@ -317,7 +322,7 @@ async def test_forward_abs(self):
)
history = self.forward_uinput.write_history
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(history.count((EV_ABS, ABS_X, 10)), 1)
self.assertEqual(history.count((EV_ABS, ABS_Y, 20)), 1)
@@ -330,7 +335,7 @@ async def test_forward_rel(self):
"""Test if EV_REL events are forwarded when other events of the same input are not."""
preset = Preset()
# BTN_A -> 77
- system_mapping._set("b", 77)
+ keyboard_layout._set("b", 77)
preset.add(
Mapping.from_combination(
InputCombination([InputConfig(type=EV_KEY, code=BTN_LEFT)]),
@@ -356,7 +361,7 @@ async def test_forward_rel(self):
await asyncio.sleep(0.1)
history = self.forward_uinput.write_history
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(history.count((EV_REL, REL_X, 10)), 1)
self.assertEqual(history.count((EV_REL, REL_Y, 20)), 1)
@@ -367,9 +372,9 @@ async def test_forward_rel(self):
async def test_combination(self):
"""Test if combinations map to keys properly."""
- a = system_mapping.get("a")
- b = system_mapping.get("b")
- c = system_mapping.get("c")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
+ c = keyboard_layout.get("c")
origin = fixtures.gamepad
origin_hash = origin.get_device_hash()
@@ -439,7 +444,7 @@ async def test_combination(self):
event_reader,
)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
@@ -461,7 +466,7 @@ async def test_combination(self):
event_reader,
)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertNotIn((EV_KEY, a, 1), keyboard_history)
self.assertNotIn((EV_KEY, a, 0), keyboard_history)
@@ -486,7 +491,7 @@ async def test_ignore_hold(self):
output_symbol="a",
)
)
- a = system_mapping.get("a")
+ a = keyboard_layout.get("a")
event_reader = self.create_event_reader(preset, fixtures.gamepad)
await self.send_events(
@@ -494,7 +499,7 @@ async def test_ignore_hold(self):
event_reader,
)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertEqual(len(keyboard_history), 2)
self.assertEqual(len(forwarded_history), 0)
@@ -560,9 +565,9 @@ async def test_ignore_disabled(self):
)
)
- a = system_mapping.get("a")
- b = system_mapping.get("b")
- c = system_mapping.get("c")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
+ c = keyboard_layout.get("c")
event_reader = self.create_event_reader(preset, origin)
@@ -576,7 +581,7 @@ async def test_ignore_disabled(self):
],
event_reader,
)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertIn((EV_KEY, a, 1), keyboard_history)
self.assertIn((EV_KEY, a, 0), keyboard_history)
@@ -586,7 +591,7 @@ async def test_ignore_disabled(self):
"""A combination that ends in a disabled key"""
# ev_5 should be forwarded and the combination triggered
await self.send_events(combi_1, event_reader)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertIn((EV_KEY, b, 1), keyboard_history)
self.assertEqual(len(keyboard_history), 3)
@@ -596,7 +601,7 @@ async def test_ignore_disabled(self):
# release what the combination maps to
await self.send_events([ev_4, ev_6], event_reader)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertIn((EV_KEY, b, 0), keyboard_history)
self.assertEqual(len(keyboard_history), 4)
@@ -606,7 +611,7 @@ async def test_ignore_disabled(self):
"""A combination that starts with a disabled key"""
# only the combination should get triggered
await self.send_events(combi_2, event_reader)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertIn((EV_KEY, c, 1), keyboard_history)
self.assertEqual(len(keyboard_history), 5)
@@ -616,7 +621,7 @@ async def test_ignore_disabled(self):
# release what the combination maps to
await self.send_events([ev_4, ev_6], event_reader)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
for event in keyboard_history:
print(event.event_tuple)
@@ -634,8 +639,8 @@ async def test_combination_keycode_macro_mix(self):
up_1 = (EV_ABS, ABS_HAT0X, 0)
up_2 = (EV_ABS, ABS_HAT0Y, 0)
- a = system_mapping.get("a")
- b = system_mapping.get("b")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
preset = Preset()
preset.add(
@@ -655,7 +660,7 @@ async def test_combination_keycode_macro_mix(self):
# macro starts
await self.send_events([InputEvent.from_tuple(down_1)], event_reader)
await asyncio.sleep(0.05)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertEqual(len(forwarded_history), 0)
self.assertGreater(len(keyboard_history), 1)
@@ -666,29 +671,29 @@ async def test_combination_keycode_macro_mix(self):
# combination triggered
await self.send_events([InputEvent.from_tuple(down_2)], event_reader)
await asyncio.sleep(0)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertIn((EV_KEY, b, 1), keyboard_history)
- len_a = len(global_uinputs.get_uinput("keyboard").write_history)
+ len_a = len(self.global_uinputs.get_uinput("keyboard").write_history)
await asyncio.sleep(0.05)
- len_b = len(global_uinputs.get_uinput("keyboard").write_history)
+ len_b = len(self.global_uinputs.get_uinput("keyboard").write_history)
# still running
self.assertGreater(len_b, len_a)
# release
await self.send_events([InputEvent.from_tuple(up_1)], event_reader)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(keyboard_history[-1], (EV_KEY, b, 0))
await asyncio.sleep(0.05)
- len_c = len(global_uinputs.get_uinput("keyboard").write_history)
+ len_c = len(self.global_uinputs.get_uinput("keyboard").write_history)
await asyncio.sleep(0.05)
- len_d = len(global_uinputs.get_uinput("keyboard").write_history)
+ len_d = len(self.global_uinputs.get_uinput("keyboard").write_history)
# not running anymore
self.assertEqual(len_c, len_d)
await self.send_events([InputEvent.from_tuple(up_2)], event_reader)
await asyncio.sleep(0.05)
- len_e = len(global_uinputs.get_uinput("keyboard").write_history)
+ len_e = len(self.global_uinputs.get_uinput("keyboard").write_history)
self.assertEqual(len_e, len_d)
async def test_wheel_combination_release_failure(self):
@@ -717,8 +722,8 @@ async def test_wheel_combination_release_failure(self):
InputCombination.from_tuples((1, 276, 1), (2, 8, -1))
)
- system_mapping.clear()
- system_mapping._set("a", 30)
+ keyboard_layout.clear()
+ keyboard_layout._set("a", 30)
a = 30
m = Mapping.from_combination(combination, output_symbol="a")
@@ -735,18 +740,18 @@ async def test_wheel_combination_release_failure(self):
await self.send_events([scroll], event_reader)
# "maps to 30"
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(keyboard_history[0], (EV_KEY, a, 1))
await self.send_events([scroll] * 5, event_reader)
# nothing new since all of them were duplicate key downs
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(len(keyboard_history), 1)
await self.send_events([btn_up], event_reader)
# releasing the combination
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(keyboard_history[1], (EV_KEY, a, 0))
# more scroll events
@@ -800,7 +805,7 @@ async def test_can_not_map(self):
event_reader,
)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertEqual(len(forwarded_history), 4)
@@ -821,7 +826,7 @@ async def test_axis_switch(self):
rel_rate = 60 # rate [Hz] at which events are produced
gain = 0.5 # halve the speed of the rel axis
preset = Preset()
- mouse = global_uinputs.get_uinput("mouse")
+ mouse = self.global_uinputs.get_uinput("mouse")
forward_history = self.forward_uinput.write_history
mouse_history = mouse.write_history
@@ -976,7 +981,7 @@ async def test_abs_to_abs(self):
await asyncio.sleep(0.2)
- history = global_uinputs.get_uinput("gamepad").write_history
+ history = self.global_uinputs.get_uinput("gamepad").write_history
self.assertEqual(
history,
[
@@ -1026,7 +1031,7 @@ async def test_abs_to_abs_with_input_switch(self):
await asyncio.sleep(0.2)
- history = global_uinputs.get_uinput("gamepad").write_history
+ history = self.global_uinputs.get_uinput("gamepad").write_history
self.assertEqual(
history,
[
@@ -1083,7 +1088,7 @@ def next_usec_time():
await asyncio.sleep(0.1)
- history = global_uinputs.get_uinput("gamepad").write_history
+ history = self.global_uinputs.get_uinput("gamepad").write_history
self.assertEqual(
history,
[
@@ -1102,7 +1107,7 @@ def next_usec_time():
event_reader,
)
await asyncio.sleep(0.7)
- history = global_uinputs.get_uinput("gamepad").write_history
+ history = self.global_uinputs.get_uinput("gamepad").write_history
self.assertEqual(
history,
[
@@ -1163,7 +1168,7 @@ async def test_rel_to_abs_with_input_switch(self):
await asyncio.sleep(0.2)
- history = global_uinputs.get_uinput("gamepad").write_history
+ history = self.global_uinputs.get_uinput("gamepad").write_history
self.assertEqual(
history,
[
@@ -1229,7 +1234,7 @@ async def test_abs_to_rel(self):
event_reader,
)
- mouse_history = global_uinputs.get_uinput("mouse").write_history
+ mouse_history = self.global_uinputs.get_uinput("mouse").write_history
if mouse_history[0].type == EV_ABS:
raise AssertionError(
@@ -1303,7 +1308,7 @@ async def test_abs_to_wheel_hi_res_quirk(self):
],
event_reader,
)
- m_history = global_uinputs.get_uinput("mouse").write_history
+ m_history = self.global_uinputs.get_uinput("mouse").write_history
rel_wheel = sum([event.value for event in m_history if event.code == REL_WHEEL])
rel_wheel_hi_res = sum(
@@ -1332,11 +1337,11 @@ async def test_rel_to_btn(self):
# should be forwarded and present in the capabilities
hw_left = (EV_REL, REL_HWHEEL, -1)
- system_mapping.clear()
+ keyboard_layout.clear()
code_b = 91
code_c = 92
- system_mapping._set("b", code_b)
- system_mapping._set("c", code_c)
+ keyboard_layout._set("b", code_b)
+ keyboard_layout._set("c", code_c)
# set a high release timeout to make sure the tests pass
release_timeout = 0.2
@@ -1370,7 +1375,7 @@ async def test_rel_to_btn(self):
# wait more than the release_timeout to make sure all handlers finish
await asyncio.sleep(release_timeout * 1.2)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertEqual(keyboard_history.count((EV_KEY, code_b, 1)), 1)
self.assertEqual(keyboard_history.count((EV_KEY, code_c, 1)), 1)
@@ -1407,8 +1412,8 @@ async def test_rel_trigger_threshold(self):
preset.add(mapping_1)
preset.add(mapping_2)
- a = system_mapping.get("a")
- b = system_mapping.get("b")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
event_reader = self.create_event_reader(preset, fixtures.foo_device_2_mouse)
@@ -1422,7 +1427,7 @@ async def test_rel_trigger_threshold(self):
event_reader,
)
await asyncio.sleep(release_timeout * 1.5) # release a
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(keyboard_history, [(EV_KEY, a, 1), (EV_KEY, a, 0)])
self.assertEqual(keyboard_history.count((EV_KEY, a, 1)), 1)
@@ -1437,14 +1442,14 @@ async def test_rel_trigger_threshold(self):
],
event_reader,
)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(keyboard_history.count((EV_KEY, a, 1)), 2)
self.assertEqual(keyboard_history.count((EV_KEY, b, 1)), 1)
self.assertEqual(keyboard_history.count((EV_KEY, b, 0)), 1)
self.assertEqual(keyboard_history.count((EV_KEY, a, 0)), 1)
await asyncio.sleep(release_timeout * 1.5) # release a
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertEqual(keyboard_history.count((EV_KEY, a, 0)), 2)
self.assertEqual(
@@ -1476,8 +1481,8 @@ async def test_abs_trigger_threshold(self):
preset.add(mapping_1)
preset.add(mapping_2)
- a = system_mapping.get("a")
- b = system_mapping.get("b")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
event_reader = self.create_event_reader(preset, fixtures.gamepad)
@@ -1494,7 +1499,7 @@ async def test_abs_trigger_threshold(self):
],
event_reader,
)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(keyboard_history.count((EV_KEY, a, 1)), 1)
self.assertNotIn((EV_KEY, a, 0), keyboard_history)
@@ -1508,7 +1513,7 @@ async def test_abs_trigger_threshold(self):
],
event_reader,
)
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertEqual(keyboard_history.count((EV_KEY, a, 1)), 1)
self.assertEqual(keyboard_history.count((EV_KEY, b, 1)), 1)
self.assertEqual(keyboard_history.count((EV_KEY, b, 0)), 1)
@@ -1516,7 +1521,7 @@ async def test_abs_trigger_threshold(self):
# 0% release a
await event_reader.handle(InputEvent.abs(ABS_X, 0))
- keyboard_history = global_uinputs.get_uinput("keyboard").write_history
+ keyboard_history = self.global_uinputs.get_uinput("keyboard").write_history
forwarded_history = self.forward_uinput.write_history
self.assertEqual(keyboard_history.count((EV_KEY, a, 0)), 1)
self.assertEqual(len(forwarded_history), 0)
@@ -1545,7 +1550,7 @@ async def _test(self, input_code, input_value, output_code, output_value, gain=1
event_reader,
)
- history = global_uinputs.get_uinput("mouse").write_history
+ history = self.global_uinputs.get_uinput("mouse").write_history
self.assertEqual(len(history), 1)
self.assertEqual(
@@ -1602,7 +1607,7 @@ async def test_x_to_hwheel(self):
event_reader,
)
- history = global_uinputs.get_uinput("mouse").write_history
+ history = self.global_uinputs.get_uinput("mouse").write_history
# injects both REL_WHEEL and REL_WHEEL_HI_RES events
self.assertEqual(len(history), 2)
self.assertEqual(
@@ -1629,7 +1634,7 @@ async def test_x_to_hwheel(self):
async def test_remainder(self):
preset = Preset()
- history = global_uinputs.get_uinput("mouse").write_history
+ history = self.global_uinputs.get_uinput("mouse").write_history
# REL_WHEEL_HI_RES to REL_Y
input_config = InputConfig(type=EV_REL, code=REL_WHEEL_HI_RES)
diff --git a/tests/unit/test_event_pipeline/test_mapping_handlers.py b/tests/unit/test_event_pipeline/test_mapping_handlers.py
index 1b3262740..361d5d335 100644
--- a/tests/unit/test_event_pipeline/test_mapping_handlers.py
+++ b/tests/unit/test_event_pipeline/test_mapping_handlers.py
@@ -48,7 +48,7 @@
from inputremapper.configs.mapping import Mapping, DEFAULT_REL_RATE
from inputremapper.configs.input_config import InputCombination, InputConfig
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
from inputremapper.injection.mapping_handlers.abs_to_abs_handler import AbsToAbsHandler
from inputremapper.injection.mapping_handlers.abs_to_btn_handler import AbsToBtnHandler
from inputremapper.injection.mapping_handlers.abs_to_rel_handler import AbsToRelHandler
@@ -99,6 +99,8 @@ def setUp(self):
InputConfig(type=1, code=3),
)
)
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = AxisSwitchHandler(
input_combination,
Mapping(
@@ -108,6 +110,7 @@ def setUp(self):
output_code=1,
),
MagicMock(),
+ self.global_uinputs,
)
@@ -117,6 +120,8 @@ def setUp(self):
input_combination = InputCombination(
[InputConfig(type=3, code=5, analog_threshold=10)]
)
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = AbsToBtnHandler(
input_combination,
Mapping(
@@ -124,6 +129,7 @@ def setUp(self):
target_uinput="mouse",
output_symbol="BTN_LEFT",
),
+ global_uinputs=self.global_uinputs,
)
@@ -131,6 +137,8 @@ def setUp(self):
class TestAbsToAbsHandler(BaseTests, unittest.IsolatedAsyncioTestCase):
def setUp(self):
input_combination = InputCombination([InputConfig(type=EV_ABS, code=ABS_X)])
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = AbsToAbsHandler(
input_combination,
Mapping(
@@ -139,6 +147,7 @@ def setUp(self):
output_type=EV_ABS,
output_code=ABS_X,
),
+ global_uinputs=self.global_uinputs,
)
async def test_reset(self):
@@ -147,7 +156,7 @@ async def test_reset(self):
source=InputDevice("/dev/input/event15"),
)
self.handler.reset()
- history = global_uinputs.get_uinput("gamepad").write_history
+ history = self.global_uinputs.get_uinput("gamepad").write_history
self.assertEqual(
history,
[InputEvent.from_tuple((3, 0, MAX_ABS)), InputEvent.from_tuple((3, 0, 0))],
@@ -158,6 +167,8 @@ async def test_reset(self):
class TestRelToAbsHandler(BaseTests, unittest.IsolatedAsyncioTestCase):
def setUp(self):
input_combination = InputCombination([InputConfig(type=EV_REL, code=REL_X)])
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = RelToAbsHandler(
input_combination,
Mapping(
@@ -166,6 +177,7 @@ def setUp(self):
output_type=EV_ABS,
output_code=ABS_X,
),
+ self.global_uinputs,
)
async def test_reset(self):
@@ -174,7 +186,7 @@ async def test_reset(self):
source=InputDevice("/dev/input/event15"),
)
self.handler.reset()
- history = global_uinputs.get_uinput("gamepad").write_history
+ history = self.global_uinputs.get_uinput("gamepad").write_history
self.assertEqual(len(history), 2)
# something large, doesn't matter
@@ -222,6 +234,8 @@ async def test_rate_stays(self):
class TestAbsToRelHandler(BaseTests, unittest.IsolatedAsyncioTestCase):
def setUp(self):
input_combination = InputCombination([InputConfig(type=EV_ABS, code=ABS_X)])
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = AbsToRelHandler(
input_combination,
Mapping(
@@ -230,6 +244,7 @@ def setUp(self):
output_type=EV_REL,
output_code=REL_X,
),
+ self.global_uinputs,
)
async def test_reset(self):
@@ -241,10 +256,10 @@ async def test_reset(self):
self.handler.reset()
await asyncio.sleep(0.05)
- count = global_uinputs.get_uinput("mouse").write_count
+ count = self.global_uinputs.get_uinput("mouse").write_count
self.assertGreater(count, 6) # count should be 60*0.2 = 12
await asyncio.sleep(0.2)
- self.assertEqual(count, global_uinputs.get_uinput("mouse").write_count)
+ self.assertEqual(count, self.global_uinputs.get_uinput("mouse").write_count)
@test_setup
@@ -286,6 +301,8 @@ def setUp(self):
self.context_mock = MagicMock()
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = CombinationHandler(
input_combination,
Mapping(
@@ -294,6 +311,7 @@ def setUp(self):
output_symbol="BTN_LEFT",
),
self.context_mock,
+ global_uinputs=self.global_uinputs,
)
def test_forward_correctly(self):
@@ -394,9 +412,12 @@ def setUp(self):
self.mock1 = MagicMock()
self.mock2 = MagicMock()
self.mock3 = MagicMock()
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = HierarchyHandler(
[self.mock1, self.mock2, self.mock3],
InputConfig(type=EV_KEY, code=KEY_A),
+ self.global_uinputs,
)
def test_reset(self):
@@ -415,6 +436,8 @@ def setUp(self):
InputConfig(type=1, code=3),
)
)
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = KeyHandler(
input_combination,
Mapping(
@@ -422,6 +445,7 @@ def setUp(self):
target_uinput="mouse",
output_symbol="BTN_LEFT",
),
+ self.global_uinputs,
)
def test_reset(self):
@@ -429,12 +453,12 @@ def test_reset(self):
InputEvent(0, 0, EV_REL, REL_X, 1, actions=(EventActions.as_key,)),
source=InputDevice("/dev/input/event11"),
)
- history = global_uinputs.get_uinput("mouse").write_history
+ history = self.global_uinputs.get_uinput("mouse").write_history
self.assertEqual(history[0], InputEvent.key(BTN_LEFT, 1))
self.assertEqual(len(history), 1)
self.handler.reset()
- history = global_uinputs.get_uinput("mouse").write_history
+ history = self.global_uinputs.get_uinput("mouse").write_history
self.assertEqual(history[1], InputEvent.key(BTN_LEFT, 0))
self.assertEqual(len(history), 2)
@@ -449,6 +473,8 @@ def setUp(self):
)
)
self.context_mock = MagicMock()
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = MacroHandler(
input_combination,
Mapping(
@@ -457,6 +483,7 @@ def setUp(self):
output_symbol="hold_keys(BTN_LEFT, BTN_RIGHT)",
),
context=self.context_mock,
+ global_uinputs=self.global_uinputs,
)
async def test_reset(self):
@@ -466,14 +493,14 @@ async def test_reset(self):
)
await asyncio.sleep(0.1)
- history = global_uinputs.get_uinput("mouse").write_history
+ history = self.global_uinputs.get_uinput("mouse").write_history
self.assertIn(InputEvent.key(BTN_LEFT, 1), history)
self.assertIn(InputEvent.key(BTN_RIGHT, 1), history)
self.assertEqual(len(history), 2)
self.handler.reset()
await asyncio.sleep(0.1)
- history = global_uinputs.get_uinput("mouse").write_history
+ history = self.global_uinputs.get_uinput("mouse").write_history
self.assertIn(InputEvent.key(BTN_LEFT, 0), history[-2:])
self.assertIn(InputEvent.key(BTN_RIGHT, 0), history[-2:])
self.assertEqual(len(history), 4)
@@ -485,6 +512,8 @@ def setUp(self):
input_combination = InputCombination(
[InputConfig(type=2, code=0, analog_threshold=10)]
)
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = RelToBtnHandler(
input_combination,
Mapping(
@@ -492,6 +521,7 @@ def setUp(self):
target_uinput="mouse",
output_symbol="BTN_LEFT",
),
+ self.global_uinputs,
)
@@ -501,6 +531,8 @@ class TestRelToRelHanlder(BaseTests, unittest.IsolatedAsyncioTestCase):
def setUp(self):
input_combination = InputCombination([InputConfig(type=EV_REL, code=REL_X)])
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
self.handler = RelToRelHandler(
input_combination,
Mapping(
@@ -510,6 +542,7 @@ def setUp(self):
output_value=20,
target_uinput="mouse",
),
+ self.global_uinputs,
)
def test_should_map(self):
diff --git a/tests/unit/test_event_reader.py b/tests/unit/test_event_reader.py
index c3e39a3ff..cbbae18d9 100644
--- a/tests/unit/test_event_reader.py
+++ b/tests/unit/test_event_reader.py
@@ -39,10 +39,11 @@
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import Mapping
from inputremapper.configs.preset import Preset
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.injection.context import Context
from inputremapper.injection.event_reader import EventReader
-from inputremapper.injection.global_uinputs import global_uinputs
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
from inputremapper.input_event import InputEvent
from inputremapper.utils import get_device_hash
from tests.lib.fixtures import fixtures
@@ -53,15 +54,17 @@
class TestEventReader(unittest.IsolatedAsyncioTestCase):
def setUp(self):
self.gamepad_source = evdev.InputDevice(fixtures.gamepad.path)
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.mapping_parser = MappingParser(self.global_uinputs)
self.stop_event = asyncio.Event()
self.preset = Preset()
- global_uinputs.is_service = True
- global_uinputs.prepare_all()
+ self.global_uinputs.is_service = True
+ self.global_uinputs.prepare_all()
async def setup(self, source, mapping):
"""Set a EventReader up for the test and run it in the background."""
- context = Context(mapping, {}, {})
+ context = Context(mapping, {}, {}, self.mapping_parser)
context.uinput = evdev.UInput()
event_reader = EventReader(context, source, self.stop_event)
asyncio.ensure_future(event_reader.run())
@@ -72,8 +75,8 @@ async def test_if_single_joystick_then(self):
# TODO: Move this somewhere more sensible
# Integration test style for if_single.
# won't care about the event, because the purpose is not set to BUTTON
- code_a = system_mapping.get("a")
- code_shift = system_mapping.get("KEY_LEFTSHIFT")
+ code_a = keyboard_layout.get("a")
+ code_shift = keyboard_layout.get("KEY_LEFTSHIFT")
trigger = evdev.ecodes.BTN_A
self.preset.add(
@@ -174,7 +177,7 @@ async def test_if_single_joystick_then(self):
await asyncio.sleep(0.1)
self.stop_event.set() # stop the reader
- history = global_uinputs.get_uinput("keyboard").write_history
+ history = self.global_uinputs.get_uinput("keyboard").write_history
self.assertIn((EV_KEY, code_a, 1), history)
self.assertIn((EV_KEY, code_a, 0), history)
self.assertNotIn((EV_KEY, code_shift, 1), history)
@@ -186,7 +189,7 @@ async def test_if_single_joystick_then(self):
async def test_if_single_joystick_under_threshold(self):
"""Triggers then because the joystick events value is too low."""
# TODO: Move this somewhere more sensible
- code_a = system_mapping.get("a")
+ code_a = keyboard_layout.get("a")
trigger = evdev.ecodes.BTN_A
self.preset.add(
Mapping.from_combination(
@@ -233,7 +236,7 @@ async def test_if_single_joystick_under_threshold(self):
)
await asyncio.sleep(0.1)
self.assertEqual(len(context.listeners), 0)
- history = global_uinputs.get_uinput("keyboard").write_history
+ history = self.global_uinputs.get_uinput("keyboard").write_history
# the key that triggered if_single should be injected after
# if_single had a chance to inject keys (if the macro is fast enough),
diff --git a/tests/unit/test_global_uinputs.py b/tests/unit/test_global_uinputs.py
index 458b8c1a7..6e5f94655 100644
--- a/tests/unit/test_global_uinputs.py
+++ b/tests/unit/test_global_uinputs.py
@@ -30,9 +30,9 @@
from inputremapper.exceptions import EventNotHandled, UinputNotAvailable
from inputremapper.injection.global_uinputs import (
- global_uinputs,
FrontendUInput,
GlobalUInputs,
+ UInput,
)
from inputremapper.input_event import InputEvent
from tests.lib.cleanup import cleanup
@@ -58,11 +58,12 @@ def test_init(self):
@test_setup
-class TestGlobalUinputs(unittest.TestCase):
+class TestGlobalUInputs(unittest.TestCase):
def setUp(self) -> None:
cleanup()
def test_iter(self):
+ global_uinputs = GlobalUInputs(FrontendUInput)
for uinput in global_uinputs:
self.assertIsInstance(uinput, evdev.UInput)
@@ -71,6 +72,9 @@ def test_write(self):
implicitly tests get_uinput and UInput.can_emit
"""
+ global_uinputs = GlobalUInputs(UInput)
+ global_uinputs.prepare_all()
+
ev_1 = InputEvent.key(KEY_A, 1)
ev_2 = InputEvent.abs(ABS_X, 10)
@@ -86,9 +90,13 @@ def test_write(self):
global_uinputs.write(ev_1.event_tuple, "foo")
def test_creates_frontend_uinputs(self):
- frontend_uinputs = GlobalUInputs()
- with patch.object(sys, "argv", ["foo"]):
- frontend_uinputs.prepare_all()
-
+ frontend_uinputs = GlobalUInputs(FrontendUInput)
+ frontend_uinputs.prepare_all()
uinput = frontend_uinputs.get_uinput("keyboard")
self.assertIsInstance(uinput, FrontendUInput)
+
+ def test_creates_backend_service_uinputs(self):
+ frontend_uinputs = GlobalUInputs(UInput)
+ frontend_uinputs.prepare_all()
+ uinput = frontend_uinputs.get_uinput("keyboard")
+ self.assertIsInstance(uinput, UInput)
diff --git a/tests/unit/test_injector.py b/tests/unit/test_injector.py
index 9636b375c..7fc2fa921 100644
--- a/tests/unit/test_injector.py
+++ b/tests/unit/test_injector.py
@@ -17,6 +17,8 @@
#
# You should have received a copy of the GNU General Public License
# along with input-remapper. If not, see .
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
try:
from pydantic.v1 import ValidationError
@@ -43,8 +45,8 @@
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import Mapping
from inputremapper.configs.preset import Preset
-from inputremapper.configs.system_mapping import (
- system_mapping,
+from inputremapper.configs.keyboard_layout import (
+ keyboard_layout,
DISABLE_CODE,
DISABLE_NAME,
)
@@ -87,6 +89,9 @@ def setUpClass(cls):
def setUp(self):
self.failed = 0
self.make_it_fail = 2
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
+ self.mapping_parser = MappingParser(self.global_uinputs)
def grab_fail_twice(_):
if self.failed < self.make_it_fail:
@@ -107,7 +112,7 @@ def tearDown(self):
evdev.InputDevice.grab = self.grab
def initialize_injector(self, group, preset: Preset):
- self.injector = Injector(group, preset)
+ self.injector = Injector(group, preset, self.mapping_parser)
self.injector._devices = self.injector.group.get_devices()
self.injector._update_preset()
@@ -123,10 +128,14 @@ def test_grab(self):
)
)
- self.injector = Injector(groups.find(key="Foo Device 2"), preset)
+ self.injector = Injector(
+ groups.find(key="Foo Device 2"),
+ preset,
+ self.mapping_parser,
+ )
# this test needs to pass around all other constraints of
# _grab_device
- self.injector.context = Context(preset, {}, {})
+ self.injector.context = Context(preset, {}, {}, self.mapping_parser)
device = self.injector._grab_device(evdev.InputDevice(path))
gamepad = classify(device) == DeviceType.GAMEPAD
self.assertFalse(gamepad)
@@ -145,9 +154,13 @@ def test_fail_grab(self):
)
)
- self.injector = Injector(groups.find(key="Foo Device 2"), preset)
+ self.injector = Injector(
+ groups.find(key="Foo Device 2"),
+ preset,
+ self.mapping_parser,
+ )
path = "/dev/input/event10"
- self.injector.context = Context(preset, {}, {})
+ self.injector.context = Context(preset, {}, {}, self.mapping_parser)
device = self.injector._grab_device(evdev.InputDevice(path))
self.assertIsNone(device)
self.assertGreaterEqual(self.failed, 1)
@@ -182,7 +195,7 @@ def test_grab_device_1(self):
),
)
self.initialize_injector(groups.find(name="gamepad"), preset)
- self.injector.context = Context(preset, {}, {})
+ self.injector.context = Context(preset, {}, {}, self.mapping_parser)
self.injector.group.paths = [
"/dev/input/event10",
"/dev/input/event30",
@@ -209,7 +222,7 @@ def test_forward_gamepad_events(self):
)
self.initialize_injector(groups.find(name="gamepad"), preset)
- self.injector.context = Context(preset, {}, {})
+ self.injector.context = Context(preset, {}, {}, self.mapping_parser)
path = "/dev/input/event30"
devices = self.injector._grab_devices()
@@ -229,7 +242,7 @@ def test_skip_unused_device(self):
)
)
self.initialize_injector(groups.find(key="Foo Device 2"), preset)
- self.injector.context = Context(preset, {}, {})
+ self.injector.context = Context(preset, {}, {}, self.mapping_parser)
# grabs only one device even though the group has 4 devices
devices = self.injector._grab_devices()
@@ -248,7 +261,7 @@ def test_skip_unknown_device(self):
# skips a device because its capabilities are not used in the preset
self.initialize_injector(groups.find(key="Foo Device 2"), preset)
- self.injector.context = Context(preset, {}, {})
+ self.injector.context = Context(preset, {}, {}, self.mapping_parser)
devices = self.injector._grab_devices()
# skips the device alltogether, so no grab attempts fail
@@ -256,7 +269,11 @@ def test_skip_unknown_device(self):
self.assertEqual(devices, {})
def test_get_udev_name(self):
- self.injector = Injector(groups.find(key="Foo Device 2"), Preset())
+ self.injector = Injector(
+ groups.find(key="Foo Device 2"),
+ Preset(),
+ self.mapping_parser,
+ )
suffix = "mapped"
prefix = "input-remapper"
expected = f'{prefix} {"a" * (80 - len(suffix) - len(prefix) - 2)} {suffix}'
@@ -301,7 +318,11 @@ def test_capabilities_and_uinput_presence(self, ungrab_patch):
)
preset.add(m1)
preset.add(m2)
- self.injector = Injector(groups.find(key="Foo Device 2"), preset)
+ self.injector = Injector(
+ groups.find(key="Foo Device 2"),
+ preset,
+ self.mapping_parser,
+ )
self.injector.stop_injecting()
self.injector.run()
@@ -353,13 +374,13 @@ def test_injector(self):
numlock_before = is_numlock_on()
# stuff the preset outputs
- system_mapping.clear()
+ keyboard_layout.clear()
code_a = 100
code_q = 101
code_w = 102
- system_mapping._set("a", code_a)
- system_mapping._set("key_q", code_q)
- system_mapping._set("w", code_w)
+ keyboard_layout._set("a", code_a)
+ keyboard_layout._set("key_q", code_q)
+ keyboard_layout._set("w", code_w)
preset = Preset()
preset.add(
@@ -398,7 +419,7 @@ def test_injector(self):
"a",
)
)
- # one mapping that is unknown in the system_mapping on purpose
+ # one mapping that is unknown in the keyboard_layout on purpose
input_b = 10
with self.assertRaises(ValidationError):
preset.add(
@@ -417,7 +438,11 @@ def test_injector(self):
)
)
- self.injector = Injector(groups.find(key="Foo Device 2"), preset)
+ self.injector = Injector(
+ groups.find(key="Foo Device 2"),
+ preset,
+ self.mapping_parser,
+ )
self.assertEqual(self.injector.get_state(), InjectorState.UNKNOWN)
self.injector.start()
self.assertEqual(self.injector.get_state(), InjectorState.STARTING)
@@ -544,6 +569,10 @@ def test_is_in_capabilities(self):
@test_setup
class TestModifyCapabilities(unittest.TestCase):
def setUp(self):
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.global_uinputs.prepare_all()
+ self.mapping_parser = MappingParser(self.global_uinputs)
+
class FakeDevice:
def __init__(self):
self._capabilities = {
@@ -619,11 +648,11 @@ def capabilities(self, absinfo=False):
),
)
- self.a = system_mapping.get("a")
- self.shift_l = system_mapping.get("ShIfT_L")
- self.one = system_mapping.get(1)
- self.two = system_mapping.get("2")
- self.left = system_mapping.get("BtN_lEfT")
+ self.a = keyboard_layout.get("a")
+ self.shift_l = keyboard_layout.get("ShIfT_L")
+ self.one = keyboard_layout.get(1)
+ self.two = keyboard_layout.get("2")
+ self.left = keyboard_layout.get("BtN_lEfT")
self.fake_device = FakeDevice()
self.preset = preset
self.macro = macro
@@ -641,7 +670,7 @@ def check_keys(self, capabilities):
def test_copy_capabilities(self):
# I don't know what ABS_VOLUME is, for now I would like to just always
# remove it until somebody complains, since its presence broke stuff
- self.injector = Injector(mock.Mock(), self.preset)
+ self.injector = Injector(mock.Mock(), self.preset, self.mapping_parser)
self.fake_device._capabilities = {
EV_ABS: [ABS_VOLUME, (ABS_X, evdev.AbsInfo(0, 0, 500, 0, 0, 0))],
EV_KEY: [1, 2, 3],
diff --git a/tests/unit/test_macros.py b/tests/unit/test_macros.py
index d05b2b78d..6f0b5ce44 100644
--- a/tests/unit/test_macros.py
+++ b/tests/unit/test_macros.py
@@ -40,18 +40,16 @@
)
from inputremapper.configs.preset import Preset
-from inputremapper.configs.system_mapping import system_mapping
+from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.configs.validation_errors import (
MacroParsingError,
SymbolNotAvailableInTargetError,
)
from inputremapper.injection.context import Context
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
from inputremapper.injection.macros.macro import (
Macro,
- _type_check,
macro_variables,
- _type_check_variablename,
- _resolve,
Variable,
)
from inputremapper.injection.macros.parse import (
@@ -67,14 +65,21 @@
get_macro_argument_names,
get_num_parameters,
)
+from inputremapper.injection.mapping_handlers.mapping_parser import MappingParser
from inputremapper.input_event import InputEvent
from tests.lib.logger import logger
from tests.lib.test_setup import test_setup
class MacroTestBase(unittest.IsolatedAsyncioTestCase):
+ @classmethod
+ def setUpClass(cls):
+ macro_variables.start()
+
def setUp(self):
self.result = []
+ self.global_uinputs = GlobalUInputs(UInput)
+ self.mapping_parser = MappingParser(self.global_uinputs)
try:
self.loop = asyncio.get_event_loop()
@@ -84,7 +89,12 @@ def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
- self.context = Context(Preset(), source_devices={}, forward_devices={})
+ self.context = Context(
+ Preset(),
+ source_devices={},
+ forward_devices={},
+ mapping_parser=self.mapping_parser,
+ )
def tearDown(self):
self.result = []
@@ -210,80 +220,110 @@ async def test_count_brackets(self):
self.assertEqual(_count_brackets("a(b(c))d()"), 7)
def test_resolve(self):
- self.assertEqual(_resolve("a"), "a")
- self.assertEqual(_resolve(1), 1)
- self.assertEqual(_resolve(None), None)
+ self.assertEqual(Macro._resolve("a"), "a")
+ self.assertEqual(Macro._resolve(1), 1)
+ self.assertEqual(Macro._resolve(None), None)
# $ is part of a custom string here
- self.assertEqual(_resolve('"$a"'), '"$a"')
- self.assertEqual(_resolve("'$a'"), "'$a'")
+ self.assertEqual(Macro._resolve('"$a"'), '"$a"')
+ self.assertEqual(Macro._resolve("'$a'"), "'$a'")
# variables are expected to be of the Variable type here, not a $string
- self.assertEqual(_resolve("$a"), "$a")
+ self.assertEqual(Macro._resolve("$a"), "$a")
variable = Variable("a")
- self.assertEqual(_resolve(variable), None)
+ self.assertEqual(Macro._resolve(variable), None)
macro_variables["a"] = 1
- self.assertEqual(_resolve(variable), 1)
+ self.assertEqual(Macro._resolve(variable), 1)
def test_type_check(self):
# allows params that can be cast to the target type
- self.assertEqual(_type_check(1, [str, None], "foo", 0), "1")
- self.assertEqual(_type_check("1", [int, None], "foo", 1), 1)
- self.assertEqual(_type_check(1.2, [str], "foo", 2), "1.2")
+ self.assertEqual(Macro._type_check(1, [str, None], "foo", 0), "1")
+ self.assertEqual(Macro._type_check("1", [int, None], "foo", 1), 1)
+ self.assertEqual(Macro._type_check(1.2, [str], "foo", 2), "1.2")
self.assertRaises(
MacroParsingError,
- lambda: _type_check("1.2", [int], "foo", 3),
+ lambda: Macro._type_check("1.2", [int], "foo", 3),
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check("a", [None], "foo", 0)
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check("a", [int], "foo", 1)
)
- self.assertRaises(MacroParsingError, lambda: _type_check("a", [None], "foo", 0))
- self.assertRaises(MacroParsingError, lambda: _type_check("a", [int], "foo", 1))
self.assertRaises(
MacroParsingError,
- lambda: _type_check("a", [int, float], "foo", 2),
+ lambda: Macro._type_check("a", [int, float], "foo", 2),
)
self.assertRaises(
MacroParsingError,
- lambda: _type_check("a", [int, None], "foo", 3),
+ lambda: Macro._type_check("a", [int, None], "foo", 3),
)
- self.assertEqual(_type_check("a", [int, float, None, str], "foo", 4), "a")
+ self.assertEqual(Macro._type_check("a", [int, float, None, str], "foo", 4), "a")
# variables are expected to be of the Variable type here, not a $string
- self.assertRaises(MacroParsingError, lambda: _type_check("$a", [int], "foo", 4))
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check("$a", [int], "foo", 4)
+ )
variable = Variable("a")
- self.assertEqual(_type_check(variable, [int], "foo", 4), variable)
+ self.assertEqual(Macro._type_check(variable, [int], "foo", 4), variable)
self.assertRaises(
MacroParsingError,
- lambda: _type_check("a", [Macro], "foo", 0),
+ lambda: Macro._type_check("a", [Macro], "foo", 0),
)
- self.assertRaises(MacroParsingError, lambda: _type_check(1, [Macro], "foo", 0))
- self.assertEqual(_type_check("1", [Macro, int], "foo", 4), 1)
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check(1, [Macro], "foo", 0)
+ )
+ self.assertEqual(Macro._type_check("1", [Macro, int], "foo", 4), 1)
def test_type_check_variablename(self):
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("1a"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("$a"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("a()"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("1"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("+"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("-"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("*"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("a,b"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("a,b"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename("#"))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename(1))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename(None))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename([]))
- self.assertRaises(MacroParsingError, lambda: _type_check_variablename(()))
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("1a")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("$a")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("a()")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("1")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("+")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("-")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("*")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("a,b")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("a,b")
+ )
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename("#")
+ )
+ self.assertRaises(MacroParsingError, lambda: Macro._type_check_variablename(1))
+ self.assertRaises(
+ MacroParsingError, lambda: Macro._type_check_variablename(None)
+ )
+ self.assertRaises(MacroParsingError, lambda: Macro._type_check_variablename([]))
+ self.assertRaises(MacroParsingError, lambda: Macro._type_check_variablename(()))
# doesn't raise
- _type_check_variablename("a")
- _type_check_variablename("_a")
- _type_check_variablename("_A")
- _type_check_variablename("A")
- _type_check_variablename("Abcd")
- _type_check_variablename("Abcd_")
- _type_check_variablename("Abcd_1234")
- _type_check_variablename("Abcd1234_")
+ Macro._type_check_variablename("a")
+ Macro._type_check_variablename("_a")
+ Macro._type_check_variablename("_A")
+ Macro._type_check_variablename("A")
+ Macro._type_check_variablename("Abcd")
+ Macro._type_check_variablename("Abcd_")
+ Macro._type_check_variablename("Abcd_1234")
+ Macro._type_check_variablename("Abcd1234_")
def test_split_keyword_arg(self):
self.assertTupleEqual(_split_keyword_arg("_A=b"), ("_A", "b"))
@@ -346,19 +386,19 @@ async def test_run_plus_syntax(self):
self.assertTrue(macro.is_holding())
# starting from the left, presses each one down
- self.assertEqual(self.result[0], (EV_KEY, system_mapping.get("a"), 1))
- self.assertEqual(self.result[1], (EV_KEY, system_mapping.get("b"), 1))
- self.assertEqual(self.result[2], (EV_KEY, system_mapping.get("c"), 1))
- self.assertEqual(self.result[3], (EV_KEY, system_mapping.get("d"), 1))
+ self.assertEqual(self.result[0], (EV_KEY, keyboard_layout.get("a"), 1))
+ self.assertEqual(self.result[1], (EV_KEY, keyboard_layout.get("b"), 1))
+ self.assertEqual(self.result[2], (EV_KEY, keyboard_layout.get("c"), 1))
+ self.assertEqual(self.result[3], (EV_KEY, keyboard_layout.get("d"), 1))
# and then releases starting with the previously pressed key
macro.release_trigger()
await asyncio.sleep(0.2)
self.assertFalse(macro.is_holding())
- self.assertEqual(self.result[4], (EV_KEY, system_mapping.get("d"), 0))
- self.assertEqual(self.result[5], (EV_KEY, system_mapping.get("c"), 0))
- self.assertEqual(self.result[6], (EV_KEY, system_mapping.get("b"), 0))
- self.assertEqual(self.result[7], (EV_KEY, system_mapping.get("a"), 0))
+ self.assertEqual(self.result[4], (EV_KEY, keyboard_layout.get("d"), 0))
+ self.assertEqual(self.result[5], (EV_KEY, keyboard_layout.get("c"), 0))
+ self.assertEqual(self.result[6], (EV_KEY, keyboard_layout.get("b"), 0))
+ self.assertEqual(self.result[7], (EV_KEY, keyboard_layout.get("a"), 0))
async def test_extract_params(self):
# splits strings, doesn't try to understand their meaning yet
@@ -438,7 +478,7 @@ async def test_parse_params(self):
async def test_0(self):
macro = parse("key(1)", self.context, DummyMapping, True)
- one_code = system_mapping.get("1")
+ one_code = keyboard_layout.get("1")
await macro.run(self.handler)
self.assertListEqual(
@@ -454,12 +494,12 @@ async def test_1(self):
self.assertListEqual(
self.result,
[
- (EV_KEY, system_mapping.get("1"), 1),
- (EV_KEY, system_mapping.get("1"), 0),
- (EV_KEY, system_mapping.get("a"), 1),
- (EV_KEY, system_mapping.get("a"), 0),
- (EV_KEY, system_mapping.get("3"), 1),
- (EV_KEY, system_mapping.get("3"), 0),
+ (EV_KEY, keyboard_layout.get("1"), 1),
+ (EV_KEY, keyboard_layout.get("1"), 0),
+ (EV_KEY, keyboard_layout.get("a"), 1),
+ (EV_KEY, keyboard_layout.get("a"), 0),
+ (EV_KEY, keyboard_layout.get("3"), 1),
+ (EV_KEY, keyboard_layout.get("3"), 0),
],
)
self.assertEqual(len(macro.child_macros), 0)
@@ -560,8 +600,8 @@ async def test_raises_error(self):
)
async def test_key(self):
- code_a = system_mapping.get("a")
- code_b = system_mapping.get("b")
+ code_a = keyboard_layout.get("a")
+ code_b = keyboard_layout.get("b")
macro = parse("set(foo, b).key($foo).key(a)", self.context, DummyMapping)
await macro.run(self.handler)
self.assertListEqual(
@@ -575,8 +615,8 @@ async def test_key(self):
)
async def test_key_down_up(self):
- code_a = system_mapping.get("a")
- code_b = system_mapping.get("b")
+ code_a = keyboard_layout.get("a")
+ code_b = keyboard_layout.get("b")
macro = parse(
"set(foo, b).key_down($foo).key_up($foo).key_up(a).key_down(a)",
self.context,
@@ -594,9 +634,9 @@ async def test_key_down_up(self):
)
async def test_modify(self):
- code_a = system_mapping.get("a")
- code_b = system_mapping.get("b")
- code_c = system_mapping.get("c")
+ code_a = keyboard_layout.get("a")
+ code_b = keyboard_layout.get("b")
+ code_c = keyboard_layout.get("c")
macro = parse(
"set(foo, b).modify($foo, modify(a, key(c)))",
self.context,
@@ -616,7 +656,7 @@ async def test_modify(self):
)
async def test_hold_variable(self):
- code_a = system_mapping.get("a")
+ code_a = keyboard_layout.get("a")
macro = parse("set(foo, a).hold($foo)", self.context, DummyMapping)
await macro.run(self.handler)
self.assertListEqual(
@@ -634,9 +674,9 @@ async def test_hold_keys(self):
# then run, just like how it is going to happen during runtime
asyncio.ensure_future(macro.run(self.handler))
- code_a = system_mapping.get("a")
- code_b = system_mapping.get("b")
- code_c = system_mapping.get("c")
+ code_a = keyboard_layout.get("a")
+ code_b = keyboard_layout.get("b")
+ code_c = keyboard_layout.get("c")
await asyncio.sleep(0.2)
self.assertListEqual(
@@ -685,10 +725,10 @@ async def test_hold(self):
await asyncio.sleep(0.05)
self.assertFalse(macro.is_holding())
- self.assertEqual(self.result[0], (EV_KEY, system_mapping.get("1"), 1))
- self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get("3"), 0))
+ self.assertEqual(self.result[0], (EV_KEY, keyboard_layout.get("1"), 1))
+ self.assertEqual(self.result[-1], (EV_KEY, keyboard_layout.get("3"), 0))
- code_a = system_mapping.get("a")
+ code_a = keyboard_layout.get("a")
self.assertGreater(self.result.count((EV_KEY, code_a, 1)), 2)
self.assertEqual(len(macro.child_macros), 1)
@@ -722,8 +762,8 @@ async def test_dont_hold(self):
# and the child macro of hold is never called.
self.assertEqual(len(self.result), 4)
- self.assertEqual(self.result[0], (EV_KEY, system_mapping.get("1"), 1))
- self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get("3"), 0))
+ self.assertEqual(self.result[0], (EV_KEY, keyboard_layout.get("1"), 1))
+ self.assertEqual(self.result[-1], (EV_KEY, keyboard_layout.get("3"), 0))
self.assertEqual(len(macro.child_macros), 1)
@@ -748,8 +788,8 @@ async def test_just_hold(self):
self.assertFalse(macro.is_holding())
self.assertEqual(len(self.result), 4)
- self.assertEqual(self.result[0], (EV_KEY, system_mapping.get("1"), 1))
- self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get("3"), 0))
+ self.assertEqual(self.result[0], (EV_KEY, keyboard_layout.get("1"), 1))
+ self.assertEqual(self.result[-1], (EV_KEY, keyboard_layout.get("3"), 0))
self.assertEqual(len(macro.child_macros), 0)
@@ -763,8 +803,8 @@ async def test_dont_just_hold(self):
# completely
self.assertEqual(len(self.result), 4)
- self.assertEqual(self.result[0], (EV_KEY, system_mapping.get("1"), 1))
- self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get("3"), 0))
+ self.assertEqual(self.result[0], (EV_KEY, keyboard_layout.get("1"), 1))
+ self.assertEqual(self.result[-1], (EV_KEY, keyboard_layout.get("3"), 0))
self.assertEqual(len(macro.child_macros), 0)
@@ -784,7 +824,7 @@ async def test_hold_down(self):
await asyncio.sleep(0.2)
self.assertTrue(macro.is_holding())
self.assertEqual(len(self.result), 1)
- self.assertEqual(self.result[0], (EV_KEY, system_mapping.get("a"), 1))
+ self.assertEqual(self.result[0], (EV_KEY, keyboard_layout.get("a"), 1))
"""up"""
@@ -793,8 +833,8 @@ async def test_hold_down(self):
self.assertFalse(macro.is_holding())
self.assertEqual(len(self.result), 2)
- self.assertEqual(self.result[0], (EV_KEY, system_mapping.get("a"), 1))
- self.assertEqual(self.result[1], (EV_KEY, system_mapping.get("a"), 0))
+ self.assertEqual(self.result[0], (EV_KEY, keyboard_layout.get("a"), 1))
+ self.assertEqual(self.result[1], (EV_KEY, keyboard_layout.get("a"), 0))
async def test_2(self):
start = time.time()
@@ -805,7 +845,7 @@ async def test_2(self):
self.context,
DummyMapping,
)
- k_code = system_mapping.get("k")
+ k_code = keyboard_layout.get("k")
await macro.run(self.handler)
keystroke_sleep = DummyMapping.macro_key_sleep_ms
@@ -824,7 +864,7 @@ async def test_2(self):
async def test_3(self):
start = time.time()
macro = parse("repeat(3, key(m).w(100))", self.context, DummyMapping)
- m_code = system_mapping.get("m")
+ m_code = keyboard_layout.get("m")
await macro.run(self.handler)
keystroke_time = 6 * DummyMapping.macro_key_sleep_ms
@@ -854,9 +894,9 @@ async def test_4(self):
DummyMapping,
)
- r = system_mapping.get("r")
- minus = system_mapping.get("minus")
- m = system_mapping.get("m")
+ r = keyboard_layout.get("r")
+ minus = keyboard_layout.get("minus")
+ m = keyboard_layout.get("m")
await macro.run(self.handler)
self.assertListEqual(
@@ -888,9 +928,9 @@ async def test_5(self):
self.assertEqual(len(macro.child_macros), 1)
self.assertEqual(len(macro.child_macros[0].child_macros), 1)
- w = system_mapping.get("w")
- left = system_mapping.get("bTn_lEfT")
- k = system_mapping.get("k")
+ w = keyboard_layout.get("w")
+ left = keyboard_layout.get("bTn_lEfT")
+ k = keyboard_layout.get("k")
await macro.run(self.handler)
@@ -919,9 +959,9 @@ async def test_duplicate_run(self):
# internal state (in particular the _trigger_release_event).
# I actually don't know at all what kind of bugs that might produce,
# lets just avoid it. It might cause it to be held down forever.
- a = system_mapping.get("a")
- b = system_mapping.get("b")
- c = system_mapping.get("c")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
+ c = keyboard_layout.get("c")
macro = parse("key(a).modify(b, hold()).key(c)", self.context, DummyMapping)
asyncio.ensure_future(macro.run(self.handler))
@@ -1027,7 +1067,7 @@ async def test_mouse_accel(self):
async def test_event_1(self):
macro = parse("e(EV_KEY, KEY_A, 1)", self.context, DummyMapping)
- a_code = system_mapping.get("a")
+ a_code = keyboard_layout.get("a")
await macro.run(self.handler)
self.assertListEqual(self.result, [(EV_KEY, a_code, 1)])
@@ -1160,15 +1200,15 @@ async def test_ifeq_runs(self):
self.context,
DummyMapping,
)
- code_a = system_mapping.get("a")
- code_b = system_mapping.get("b")
+ code_a = keyboard_layout.get("a")
+ code_b = keyboard_layout.get("b")
await macro.run(self.handler)
self.assertListEqual(self.result, [(EV_KEY, code_a, 1), (EV_KEY, code_a, 0)])
self.assertEqual(len(macro.child_macros), 2)
async def test_ifeq_none(self):
- code_a = system_mapping.get("a")
+ code_a = keyboard_layout.get("a")
# first param None
macro = parse(
@@ -1205,8 +1245,8 @@ async def test_ifeq_none(self):
async def test_ifeq_unknown_key(self):
macro = parse("ifeq(qux, 2, key(a), key(b))", self.context, DummyMapping)
- code_a = system_mapping.get("a")
- code_b = system_mapping.get("b")
+ code_a = keyboard_layout.get("a")
+ code_b = keyboard_layout.get("b")
await macro.run(self.handler)
self.assertListEqual(self.result, [(EV_KEY, code_b, 1), (EV_KEY, code_b, 0)])
@@ -1214,8 +1254,8 @@ async def test_ifeq_unknown_key(self):
async def test_if_eq(self):
"""new version of ifeq"""
- code_a = system_mapping.get("a")
- code_b = system_mapping.get("b")
+ code_a = keyboard_layout.get("a")
+ code_b = keyboard_layout.get("b")
a_press = [(EV_KEY, code_a, 1), (EV_KEY, code_a, 0)]
b_press = [(EV_KEY, code_b, 1), (EV_KEY, code_b, 0)]
@@ -1268,8 +1308,8 @@ async def test(macro, expected):
async def test_if_eq_runs_multiprocessed(self):
"""ifeq on variables that have been set in other processes works."""
macro = parse("if_eq($foo, 3, key(a), key(b))", self.context, DummyMapping)
- code_a = system_mapping.get("a")
- code_b = system_mapping.get("b")
+ code_a = keyboard_layout.get("a")
+ code_b = keyboard_layout.get("b")
self.assertEqual(len(macro.child_macros), 2)
@@ -1310,10 +1350,10 @@ async def test_if_single(self):
macro = parse("if_single(key(x), key(y))", self.context, DummyMapping)
self.assertEqual(len(macro.child_macros), 2)
- a = system_mapping.get("a")
+ a = keyboard_layout.get("a")
- x = system_mapping.get("x")
- y = system_mapping.get("y")
+ x = keyboard_layout.get("x")
+ y = keyboard_layout.get("y")
await self.trigger_sequence(macro, InputEvent.key(a, 1))
await asyncio.sleep(0.1)
@@ -1334,11 +1374,11 @@ async def test_if_single_ignores_releases(self):
)
self.assertEqual(len(macro.child_macros), 2)
- a = system_mapping.get("a")
- b = system_mapping.get("b")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
- x = system_mapping.get("x")
- y = system_mapping.get("y")
+ x = keyboard_layout.get("x")
+ y = keyboard_layout.get("y")
# pressing the macro key
await self.trigger_sequence(macro, InputEvent.key(a, 1))
@@ -1372,11 +1412,11 @@ async def test_if_not_single(self):
self.assertEqual(len(macro.child_macros), 1)
self.assertEqual(len(macro.child_macros[0].child_macros), 2)
- a = system_mapping.get("a")
- b = system_mapping.get("b")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
- x = system_mapping.get("x")
- y = system_mapping.get("y")
+ x = keyboard_layout.get("x")
+ y = keyboard_layout.get("y")
# press the trigger key
await self.trigger_sequence(macro, InputEvent.key(a, 1))
@@ -1393,10 +1433,10 @@ async def test_if_not_single_none(self):
macro = parse("if_single(key(x),)", self.context, DummyMapping)
self.assertEqual(len(macro.child_macros), 1)
- a = system_mapping.get("a")
- b = system_mapping.get("b")
+ a = keyboard_layout.get("a")
+ b = keyboard_layout.get("b")
- x = system_mapping.get("x")
+ x = keyboard_layout.get("x")
# press trigger key
await self.trigger_sequence(macro, InputEvent.key(a, 1))
@@ -1417,8 +1457,8 @@ async def test_if_single_times_out(self):
)
self.assertEqual(len(macro.child_macros), 2)
- a = system_mapping.get("a")
- y = system_mapping.get("y")
+ a = keyboard_layout.get("a")
+ y = keyboard_layout.get("y")
await self.trigger_sequence(macro, InputEvent.key(a, 1))
@@ -1437,8 +1477,8 @@ async def test_if_single_ignores_joystick(self):
# Integration test style for if_single.
# If a joystick that is mapped to a button is moved, if_single stops
macro = parse("if_single(k(a), k(KEY_LEFTSHIFT))", self.context, DummyMapping)
- code_shift = system_mapping.get("KEY_LEFTSHIFT")
- code_a = system_mapping.get("a")
+ code_shift = keyboard_layout.get("KEY_LEFTSHIFT")
+ code_a = keyboard_layout.get("a")
trigger = 1
await self.trigger_sequence(macro, InputEvent.key(trigger, 1))
@@ -1458,8 +1498,8 @@ async def test_if_tap(self):
macro = parse("if_tap(key(x), key(y), 100)", self.context, DummyMapping)
self.assertEqual(len(macro.child_macros), 2)
- x = system_mapping.get("x")
- y = system_mapping.get("y")
+ x = keyboard_layout.get("x")
+ y = keyboard_layout.get("y")
# this is the regular routine of how a macro is started. the tigger is pressed
# already when the macro runs, and released during if_tap within the timeout.
@@ -1539,7 +1579,7 @@ async def test_if_tap_none(self):
# first param none
macro = parse("if_tap(, key(y), 100)", self.context, DummyMapping)
self.assertEqual(len(macro.child_macros), 1)
- y = system_mapping.get("y")
+ y = keyboard_layout.get("y")
macro.press_trigger()
asyncio.ensure_future(macro.run(self.handler))
await asyncio.sleep(0.05)
@@ -1550,7 +1590,7 @@ async def test_if_tap_none(self):
# second param none
macro = parse("if_tap(key(y), , 50)", self.context, DummyMapping)
self.assertEqual(len(macro.child_macros), 1)
- y = system_mapping.get("y")
+ y = keyboard_layout.get("y")
macro.press_trigger()
asyncio.ensure_future(macro.run(self.handler))
await asyncio.sleep(0.1)
@@ -1564,8 +1604,8 @@ async def test_if_not_tap(self):
macro = parse("if_tap(key(x), key(y), 50)", self.context, DummyMapping)
self.assertEqual(len(macro.child_macros), 2)
- x = system_mapping.get("x")
- y = system_mapping.get("y")
+ x = keyboard_layout.get("x")
+ y = keyboard_layout.get("y")
macro.press_trigger()
asyncio.ensure_future(macro.run(self.handler))
@@ -1580,8 +1620,8 @@ async def test_if_not_tap_named(self):
macro = parse("if_tap(key(x), key(y), timeout=50)", self.context, DummyMapping)
self.assertEqual(len(macro.child_macros), 2)
- x = system_mapping.get("x")
- y = system_mapping.get("y")
+ x = keyboard_layout.get("x")
+ y = keyboard_layout.get("y")
macro.press_trigger()
asyncio.ensure_future(macro.run(self.handler))
diff --git a/tests/unit/test_mapping.py b/tests/unit/test_mapping.py
index 283747c10..372f4e873 100644
--- a/tests/unit/test_mapping.py
+++ b/tests/unit/test_mapping.py
@@ -37,7 +37,7 @@
from pydantic import ValidationError
from inputremapper.configs.mapping import Mapping, UIMapping
-from inputremapper.configs.system_mapping import system_mapping, DISABLE_NAME
+from inputremapper.configs.keyboard_layout import keyboard_layout, DISABLE_NAME
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.gui.messages.message_broker import MessageType
from tests.lib.test_setup import test_setup
@@ -105,7 +105,7 @@ def test_get_output_type_code(self):
"output_symbol": "a",
}
m = Mapping(**cfg)
- a = system_mapping.get("a")
+ a = keyboard_layout.get("a")
self.assertEqual(m.get_output_type_code(), (EV_KEY, a))
m.output_symbol = "key(a)"
@@ -127,7 +127,7 @@ def test_strips_output_symbol(self):
"output_symbol": "\t a \n",
}
m = Mapping(**cfg)
- a = system_mapping.get("a")
+ a = keyboard_layout.get("a")
self.assertEqual(m.get_output_type_code(), (EV_KEY, a))
def test_combination_changed_callback(self):
@@ -191,7 +191,7 @@ def test_init_fails(self):
Mapping(**cfg)
# matching type, code and symbol
- a = system_mapping.get("a")
+ a = keyboard_layout.get("a")
cfg["output_code"] = a
cfg["output_symbol"] = "a"
cfg["output_type"] = EV_KEY
diff --git a/tests/unit/test_migrations.py b/tests/unit/test_migrations.py
index 08c494e74..7a95af480 100644
--- a/tests/unit/test_migrations.py
+++ b/tests/unit/test_migrations.py
@@ -38,6 +38,7 @@
from inputremapper.configs.migrations import Migrations
from inputremapper.configs.paths import PathUtils
from inputremapper.configs.preset import Preset
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput
from inputremapper.logging.logger import VERSION
from inputremapper.user import UserUtils
from tests.lib.test_setup import test_setup
@@ -60,6 +61,10 @@ def setUp(self):
UserUtils.home, ".config", "input-remapper", "beta_1.6.0-beta"
)
+ global_uinputs = GlobalUInputs(UInput)
+ global_uinputs.prepare_all()
+ self.migrations = Migrations(global_uinputs)
+
def test_migrate_suffix(self):
old = os.path.join(PathUtils.config_path(), "config")
new = os.path.join(PathUtils.config_path(), "config.json")
@@ -73,7 +78,7 @@ def test_migrate_suffix(self):
with open(old, "w") as f:
f.write("{}")
- Migrations.migrate()
+ self.migrations.migrate()
self.assertTrue(os.path.exists(new))
self.assertFalse(os.path.exists(old))
@@ -94,7 +99,7 @@ def test_rename_config(self):
with open(old_config_json, "w") as f:
f.write('{"foo":"bar"}')
- Migrations.migrate()
+ self.migrations.migrate()
self.assertTrue(os.path.exists(new))
self.assertFalse(os.path.exists(old))
@@ -116,7 +121,7 @@ def test_wont_migrate_suffix(self):
with open(old, "w") as f:
f.write("{}")
- Migrations.migrate()
+ self.migrations.migrate()
self.assertTrue(os.path.exists(new))
self.assertTrue(os.path.exists(old))
@@ -135,7 +140,7 @@ def test_migrate_preset(self):
with open(p2, "w") as f:
f.write("{}")
- Migrations.migrate()
+ self.migrations.migrate()
self.assertFalse(
os.path.exists(os.path.join(PathUtils.config_path(), "foo1", "bar1.json"))
@@ -173,7 +178,7 @@ def test_wont_migrate_preset(self):
# already migrated
PathUtils.mkdir(os.path.join(PathUtils.config_path(), "presets"))
- Migrations.migrate()
+ self.migrations.migrate()
self.assertTrue(
os.path.exists(os.path.join(PathUtils.config_path(), "foo1", "bar1.json"))
@@ -225,7 +230,7 @@ def test_migrate_mappings(self):
},
file,
)
- Migrations.migrate()
+ self.migrations.migrate()
# use UIMapping to also load invalid mappings
preset = Preset(PathUtils.get_preset_path("Foo Device", "test"), UIMapping)
preset.load()
@@ -332,7 +337,7 @@ def test_migrate_otherwise(self):
file,
)
- Migrations.migrate()
+ self.migrations.migrate()
preset = Preset(PathUtils.get_preset_path("Foo Device", "test"), UIMapping)
preset.load()
@@ -384,8 +389,11 @@ def test_add_version(self):
with open(path, "w") as file:
file.write("{}")
- Migrations.migrate()
- self.assertEqual(version.parse(VERSION), Migrations.config_version())
+ self.migrations.migrate()
+ self.assertEqual(
+ version.parse(VERSION),
+ self.migrations.config_version(),
+ )
def test_update_version(self):
path = os.path.join(PathUtils.config_path(), "config.json")
@@ -393,22 +401,25 @@ def test_update_version(self):
with open(path, "w") as file:
json.dump({"version": "0.1.0"}, file)
- Migrations.migrate()
- self.assertEqual(version.parse(VERSION), Migrations.config_version())
+ self.migrations.migrate()
+ self.assertEqual(
+ version.parse(VERSION),
+ self.migrations.config_version(),
+ )
def test_config_version(self):
path = os.path.join(PathUtils.config_path(), "config.json")
with open(path, "w") as file:
file.write("{}")
- self.assertEqual("0.0.0", Migrations.config_version().public)
+ self.assertEqual("0.0.0", self.migrations.config_version().public)
try:
os.remove(path)
except FileNotFoundError:
pass
- self.assertEqual("0.0.0", Migrations.config_version().public)
+ self.assertEqual("0.0.0", self.migrations.config_version().public)
def test_migrate_left_and_right_purpose(self):
path = os.path.join(
@@ -430,7 +441,7 @@ def test_migrate_left_and_right_purpose(self):
},
file,
)
- Migrations.migrate()
+ self.migrations.migrate()
preset = Preset(PathUtils.get_preset_path("Foo Device", "test"), UIMapping)
preset.load()
@@ -516,7 +527,7 @@ def test_migrate_left_and_right_purpose2(self):
},
file,
)
- Migrations.migrate()
+ self.migrations.migrate()
preset = Preset(PathUtils.get_preset_path("Foo Device", "test"), UIMapping)
preset.load()
@@ -632,7 +643,7 @@ def test_prioritize_v1_over_beta_configs(self):
self.assertFalse(os.path.exists(PathUtils.get_preset_path(device_name, "foo")))
self.assertFalse(os.path.exists(PathUtils.get_config_path("config.json")))
- Migrations.migrate()
+ self.migrations.migrate()
self.assertTrue(os.path.exists(PathUtils.get_preset_path(device_name, "foo")))
self.assertTrue(os.path.exists(PathUtils.get_config_path("config.json")))
@@ -678,7 +689,7 @@ def test_copy_over_beta_configs(self):
self.assertFalse(os.path.exists(PathUtils.get_preset_path(device_name, "bar")))
self.assertFalse(os.path.exists(PathUtils.get_config_path("config.json")))
- Migrations.migrate()
+ self.migrations.migrate()
self.assertTrue(os.path.exists(PathUtils.get_preset_path(device_name, "bar")))
self.assertTrue(os.path.exists(PathUtils.get_config_path("config.json")))
diff --git a/tests/unit/test_reader.py b/tests/unit/test_reader.py
index c0e0b5e46..690753b54 100644
--- a/tests/unit/test_reader.py
+++ b/tests/unit/test_reader.py
@@ -52,6 +52,7 @@
from inputremapper.gui.messages.message_types import MessageType
from inputremapper.gui.reader_client import ReaderClient
from inputremapper.gui.reader_service import ReaderService, ContextDummy
+from inputremapper.injection.global_uinputs import GlobalUInputs, UInput, FrontendUInput
from inputremapper.input_event import InputEvent
from tests.lib.constants import (
EVENT_READ_TIMEOUT,
@@ -109,7 +110,9 @@ async def create_reader_service(self, groups: Optional[_Groups] = None):
if not groups:
groups = self.groups
- self.reader_service = ReaderService(groups)
+ global_uinputs = GlobalUInputs(UInput)
+ assert groups is not None
+ self.reader_service = ReaderService(groups, global_uinputs)
asyncio.ensure_future(self.reader_service.run())
async def test_should_forward_to_dummy(self):
@@ -164,6 +167,7 @@ def setUp(self):
self.reader_service_process = None
self.groups = _Groups()
self.message_broker = MessageBroker()
+ self.global_uinputs = GlobalUInputs(UInput)
self.reader_client = ReaderClient(self.message_broker, self.groups)
def tearDown(self):
@@ -182,10 +186,13 @@ def create_reader_service(self, groups: Optional[_Groups] = None):
groups = self.groups
def start_reader_service():
- reader_service = ReaderService(groups)
- # this is a new process, so create a new event loop, or something
+ # this is a new process, so create a new event loop, and all dependencies
+ # from scratch, or something
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
+ global_uinputs = GlobalUInputs(FrontendUInput)
+ global_uinputs.reset()
+ reader_service = ReaderService(groups, global_uinputs)
loop.run_until_complete(reader_service.run())
self.reader_service_process = multiprocessing.Process(
diff --git a/tests/unit/test_system_mapping.py b/tests/unit/test_system_mapping.py
index 3b2a5d462..a7aa2cee9 100644
--- a/tests/unit/test_system_mapping.py
+++ b/tests/unit/test_system_mapping.py
@@ -27,25 +27,25 @@
from evdev.ecodes import BTN_LEFT, KEY_A
from inputremapper.configs.paths import PathUtils
-from inputremapper.configs.system_mapping import SystemMapping, XMODMAP_FILENAME
+from inputremapper.configs.keyboard_layout import KeyboardLayout, XMODMAP_FILENAME
from tests.lib.test_setup import test_setup
@test_setup
class TestSystemMapping(unittest.TestCase):
def test_update(self):
- system_mapping = SystemMapping()
- system_mapping.update({"foo1": 101, "bar1": 102})
- system_mapping.update({"foo2": 201, "bar2": 202})
- self.assertEqual(system_mapping.get("foo1"), 101)
- self.assertEqual(system_mapping.get("bar2"), 202)
+ keyboard_layout = KeyboardLayout()
+ keyboard_layout.update({"foo1": 101, "bar1": 102})
+ keyboard_layout.update({"foo2": 201, "bar2": 202})
+ self.assertEqual(keyboard_layout.get("foo1"), 101)
+ self.assertEqual(keyboard_layout.get("bar2"), 202)
def test_xmodmap_file(self):
- system_mapping = SystemMapping()
+ keyboard_layout = KeyboardLayout()
path = os.path.join(PathUtils.config_path(), XMODMAP_FILENAME)
os.remove(path)
- system_mapping.populate()
+ keyboard_layout.populate()
self.assertTrue(os.path.exists(path))
with open(path, "r") as file:
content = json.load(file)
@@ -67,11 +67,11 @@ def check_output(*args, **kwargs):
return SubprocessMock()
with patch.object(subprocess, "check_output", check_output):
- system_mapping = SystemMapping()
+ keyboard_layout = KeyboardLayout()
path = os.path.join(PathUtils.config_path(), XMODMAP_FILENAME)
os.remove(path)
- system_mapping.populate()
+ keyboard_layout.populate()
self.assertFalse(os.path.exists(path))
def test_xmodmap_command_missing(self):
@@ -80,61 +80,61 @@ def check_output(*args, **kwargs):
raise FileNotFoundError
with patch.object(subprocess, "check_output", check_output):
- system_mapping = SystemMapping()
+ keyboard_layout = KeyboardLayout()
path = os.path.join(PathUtils.config_path(), XMODMAP_FILENAME)
os.remove(path)
- system_mapping.populate()
+ keyboard_layout.populate()
self.assertFalse(os.path.exists(path))
def test_correct_case(self):
- system_mapping = SystemMapping()
- system_mapping.clear()
- system_mapping._set("A", 31)
- system_mapping._set("a", 32)
- system_mapping._set("abcd_B", 33)
-
- self.assertEqual(system_mapping.correct_case("a"), "a")
- self.assertEqual(system_mapping.correct_case("A"), "A")
- self.assertEqual(system_mapping.correct_case("ABCD_b"), "abcd_B")
+ keyboard_layout = KeyboardLayout()
+ keyboard_layout.clear()
+ keyboard_layout._set("A", 31)
+ keyboard_layout._set("a", 32)
+ keyboard_layout._set("abcd_B", 33)
+
+ self.assertEqual(keyboard_layout.correct_case("a"), "a")
+ self.assertEqual(keyboard_layout.correct_case("A"), "A")
+ self.assertEqual(keyboard_layout.correct_case("ABCD_b"), "abcd_B")
# unknown stuff is returned as is
- self.assertEqual(system_mapping.correct_case("FOo"), "FOo")
+ self.assertEqual(keyboard_layout.correct_case("FOo"), "FOo")
- self.assertEqual(system_mapping.get("A"), 31)
- self.assertEqual(system_mapping.get("a"), 32)
- self.assertEqual(system_mapping.get("ABCD_b"), 33)
- self.assertEqual(system_mapping.get("abcd_B"), 33)
+ self.assertEqual(keyboard_layout.get("A"), 31)
+ self.assertEqual(keyboard_layout.get("a"), 32)
+ self.assertEqual(keyboard_layout.get("ABCD_b"), 33)
+ self.assertEqual(keyboard_layout.get("abcd_B"), 33)
- def test_system_mapping(self):
- system_mapping = SystemMapping()
- system_mapping.populate()
- self.assertGreater(len(system_mapping._mapping), 100)
+ def test_keyboard_layout(self):
+ keyboard_layout = KeyboardLayout()
+ keyboard_layout.populate()
+ self.assertGreater(len(keyboard_layout._mapping), 100)
# this is case-insensitive
- self.assertEqual(system_mapping.get("1"), 2)
- self.assertEqual(system_mapping.get("KeY_1"), 2)
+ self.assertEqual(keyboard_layout.get("1"), 2)
+ self.assertEqual(keyboard_layout.get("KeY_1"), 2)
- self.assertEqual(system_mapping.get("AlT_L"), 56)
- self.assertEqual(system_mapping.get("KEy_LEFtALT"), 56)
+ self.assertEqual(keyboard_layout.get("AlT_L"), 56)
+ self.assertEqual(keyboard_layout.get("KEy_LEFtALT"), 56)
- self.assertEqual(system_mapping.get("kEY_LeFTSHIFT"), 42)
- self.assertEqual(system_mapping.get("ShiFt_L"), 42)
+ self.assertEqual(keyboard_layout.get("kEY_LeFTSHIFT"), 42)
+ self.assertEqual(keyboard_layout.get("ShiFt_L"), 42)
- self.assertEqual(system_mapping.get("BTN_left"), 272)
+ self.assertEqual(keyboard_layout.get("BTN_left"), 272)
- self.assertIsNotNone(system_mapping.get("KEY_KP4"))
- self.assertEqual(system_mapping.get("KP_Left"), system_mapping.get("KEY_KP4"))
+ self.assertIsNotNone(keyboard_layout.get("KEY_KP4"))
+ self.assertEqual(keyboard_layout.get("KP_Left"), keyboard_layout.get("KEY_KP4"))
# this only lists the correct casing,
# includes linux constants and xmodmap symbols
- names = system_mapping.list_names()
+ names = keyboard_layout.list_names()
self.assertIn("2", names)
self.assertIn("c", names)
self.assertIn("KEY_3", names)
self.assertNotIn("key_3", names)
self.assertIn("KP_Down", names)
self.assertNotIn("kp_down", names)
- names = system_mapping._mapping.keys()
+ names = keyboard_layout._mapping.keys()
self.assertIn("F4", names)
self.assertNotIn("f4", names)
self.assertIn("BTN_RIGHT", names)
@@ -143,22 +143,22 @@ def test_system_mapping(self):
self.assertIn("KP_Home", names)
self.assertNotIn("kp_home", names)
- self.assertEqual(system_mapping.get("disable"), -1)
+ self.assertEqual(keyboard_layout.get("disable"), -1)
def test_get_name_no_xmodmap(self):
# if xmodmap is not installed, uses the linux constant names
- system_mapping = SystemMapping()
+ keyboard_layout = KeyboardLayout()
def check_output(*args, **kwargs):
raise FileNotFoundError
with patch.object(subprocess, "check_output", check_output):
- system_mapping.populate()
- self.assertEqual(system_mapping.get_name(KEY_A), "KEY_A")
+ keyboard_layout.populate()
+ self.assertEqual(keyboard_layout.get_name(KEY_A), "KEY_A")
# testing for BTN_LEFT is especially important, because
# `evdev.ecodes.BTN.get(code)` returns an array of ['BTN_LEFT', 'BTN_MOUSE']
- self.assertEqual(system_mapping.get_name(BTN_LEFT), "BTN_LEFT")
+ self.assertEqual(keyboard_layout.get_name(BTN_LEFT), "BTN_LEFT")
if __name__ == "__main__":
diff --git a/tests/unit/test_test.py b/tests/unit/test_test.py
index 913eb1912..b57f48d69 100644
--- a/tests/unit/test_test.py
+++ b/tests/unit/test_test.py
@@ -31,6 +31,7 @@
from inputremapper.gui.messages.message_broker import MessageBroker
from inputremapper.gui.reader_client import ReaderClient
from inputremapper.gui.reader_service import ReaderService
+from inputremapper.injection.global_uinputs import UInput, GlobalUInputs
from inputremapper.input_event import InputEvent
from inputremapper.utils import get_device_hash
from tests.lib.cleanup import cleanup
@@ -91,9 +92,10 @@ def create_reader_service():
# this will cause pending events to be copied over to the reader-service
# process
def start_reader_service():
- # there is no point in using the global groups object
- # because the reader-service runs in a different process
- reader_service = ReaderService(_Groups())
+ # Create dependencies from scratch, because the reader-service runs
+ # in a different process
+ global_uinputs = GlobalUInputs(UInput)
+ reader_service = ReaderService(_Groups(), global_uinputs)
loop = asyncio.new_event_loop()
loop.run_until_complete(reader_service.run())