feat(app-binding): lier les presets aux applications

Ajoute la configuration des associations application/preset, le service de suivi du focus, l'intégration GUI et les fichiers d'installation nécessaires.

Ajoute les tests unitaires ciblés des bindings, du service de focus, des migrations et des composants GUI associés.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 23:52:34 +02:00
parent 0ab77d2a64
commit 9ef2c1439c
42 changed files with 4353 additions and 12 deletions

View File

@ -35,7 +35,7 @@ from typing import (
)
from evdev.ecodes import EV_KEY, EV_REL, EV_ABS
from gi.repository import Gtk
from gi.repository import Gtk, GLib
from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import (
@ -53,9 +53,11 @@ from inputremapper.configs.validation_errors import (
WrongMappingTypeForKeyError,
OutputSymbolVariantError,
)
from inputremapper.configs.app_binding import AppBinding
from inputremapper.exceptions import DataManagementError
from inputremapper.gui.components.output_type_names import OutputTypeNames
from inputremapper.gui.data_manager import DataManager, DEFAULT_PRESET_NAME
from inputremapper.gui.focus_service_client import ensure_focus_service_running
from inputremapper.gui.gettext import _
from inputremapper.gui.messages.message_broker import (
MessageBroker,
@ -120,6 +122,7 @@ class Controller:
# initial groups
self.data_manager.publish_groups()
self.data_manager.publish_uinputs()
self.data_manager.publish_app_bindings()
def _on_groups_changed(self, _):
"""Load the newest group as soon as everyone got notified
@ -603,6 +606,47 @@ class Controller:
self.data_manager.set_autoload(autoload)
self.data_manager.refresh_service_config_path()
def load_app_bindings(self):
"""(Re)publish the current application bindings and service state."""
self.data_manager.publish_app_bindings()
def set_app_binding_enabled(self, enabled: bool):
"""Enable or disable focus-driven preset binding."""
if enabled:
# make sure the user-level focus-service is running, mirroring how
# the GUI launches the reader-service.
ensure_focus_service_running()
self.data_manager.set_app_binding_enabled(enabled)
if enabled:
# the service might need a moment to come up and detect its backend;
# refresh the status once it had time to settle.
GLib.timeout_add(1500, self._refresh_app_bindings_once)
def _refresh_app_bindings_once(self) -> bool:
self.data_manager.publish_app_bindings()
return False # GLib: do not repeat
def update_app_bindings(self, bindings: List[AppBinding]):
"""Persist the given application bindings."""
try:
self.data_manager.set_app_bindings(bindings)
except PermissionError as e:
self.show_status(CTX_ERROR, _("Permission denied!"), str(e))
def get_presets_for_group(self, group_key: str) -> Tuple[str, ...]:
"""List the presets available for an arbitrary device group."""
return self.data_manager.get_presets_for_group(group_key)
def start_app_detection(self):
"""Start listening for focus changes to auto-fill an app_id."""
self.data_manager.start_app_detection()
def stop_app_detection(self):
"""Stop listening for focus changes."""
self.data_manager.stop_app_detection()
def save(self):
"""Save all data to the disc."""
try: