""" Hotkey Manager Module. ====================== This module wraps the `keyboard` library to provide Global Hotkey functionality. It allows the application to respond to key presses even when it is not in focus (background operation). Classes: HotkeyManager: Qt-compatible wrapper for keyboard hooks. """ import keyboard import logging from PySide6.QtCore import QObject, Signal from typing import Optional class HotkeyManager(QObject): """ Manages global keyboard shortcuts using the `keyboard` library. inherits from QObject to allow Signal/Slot integration with PySide6. Signals: triggered: Emitted when the hotkey is pressed. Attributes: hotkey (str): The key combination as a string (e.g. "f8", "ctrl+alt+r"). is_listening (bool): State of the listener. """ triggered = Signal() def __init__(self, config_key: str = "hotkey"): """ Initialize the HotkeyManager. Args: config_key (str): The configuration key to look up (e.g. "hotkey"). """ super().__init__() self.config_key = config_key self.hotkey = "f8" # Placeholder self.is_listening = False self._enabled = True def set_enabled(self, enabled: bool): """Enable or disable the hotkey trigger without unhooking.""" self._enabled = enabled logging.info(f"Hotkey listener {'enabled' if enabled else 'suspended'}") def start(self): """Start listening for the hotkey.""" self.reload_hotkey() def reload_hotkey(self): """Unregister old hotkey and register new one from Config.""" if self.is_listening: self.stop() from src.core.config import ConfigManager config = ConfigManager() self.hotkey = config.get(self.config_key) logging.info(f"Registering global hotkey ({self.config_key}): {self.hotkey}") try: # We don't suppress=True here because we want the app to see keys during recording # (Wait, actually if we are recording we WANT keyboard to see it, # but usually global hotkeys should be suppressed if we don't want them leaking to other apps) # However, the user is fixing the internal collision. keyboard.add_hotkey(self.hotkey, self.on_press, suppress=False) self.is_listening = True except Exception as e: logging.error(f"Failed to bind hotkey: {e}") def stop(self): """ Stop listening and unregister the hook. Safe to call even if not listening. """ if self.is_listening: try: keyboard.remove_hotkey(self.hotkey) except: pass self.is_listening = False logging.info(f"Unregistered global hotkey: {self.hotkey}") def on_press(self): """ Callback triggered internally by the keyboard library when the key is pressed. Emits the Qt `triggered` signal. """ if not self._enabled: return logging.info(f"Hotkey {self.hotkey} detected.") self.triggered.emit()