# -*- coding: utf-8 -*- # input-remapper - GUI for device specific keyboard mappings # Copyright (C) 2025 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 . """The base class and event for all focus backends. This lives in its own module to avoid a circular import between the backend registry (``focus_watcher``) and the concrete backends. """ from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Callable, Optional @dataclass(frozen=True) class FocusEvent: """A normalized description of the currently focused window. Attributes ---------- app_id The canonical application identifier (WM_CLASS on X11, app_id on Wayland). This is the value bindings are matched against. May be empty for transient focus changes (menus, popups, no focus). title The human readable window title (_NET_WM_NAME / title). backend The name of the backend that produced this event. """ app_id: str title: str backend: str # Type alias for the focus callback passed to FocusBackend.start. OnFocus = Callable[[FocusEvent], None] class FocusBackend(ABC): """Detects the focused window for a specific compositor / display server. Backends integrate into the GLib main loop (e.g. via ``GLib.io_add_watch`` on a file descriptor) and call the ``on_focus`` callback whenever the focused window changes. """ # A short, human readable identifier, also used as FocusEvent.backend. name: str = "base" @staticmethod @abstractmethod def is_available() -> bool: """Whether this backend can run in the current session.""" raise NotImplementedError @abstractmethod def start(self, on_focus: OnFocus) -> None: """Begin watching for focus changes, invoking on_focus on each change.""" raise NotImplementedError @abstractmethod def stop(self) -> None: """Stop watching and release all resources.""" raise NotImplementedError @abstractmethod def get_current(self) -> Optional[FocusEvent]: """Return the currently focused window, or None if unavailable.""" raise NotImplementedError