""" EU-Utility - Main Entry Point Launch the overlay, floating icon, dashboard, and plugin system. """ import sys import os from pathlib import Path # Add project root to path project_root = Path(__file__).parent.parent if str(project_root) not in sys.path: sys.path.insert(0, str(project_root)) try: from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import Qt, QObject, pyqtSignal PYQT_AVAILABLE = True except ImportError: PYQT_AVAILABLE = False print("Error: PyQt6 is required.") print("Install with: pip install PyQt6") sys.exit(1) try: import keyboard KEYBOARD_AVAILABLE = True except ImportError: KEYBOARD_AVAILABLE = False print("Warning: 'keyboard' library not installed.") print("Global hotkeys won't work. Install with: pip install keyboard") from core.plugin_manager import PluginManager from core.perfect_ux import create_perfect_window from core.tray_icon import TrayIcon from core.settings import get_settings from core.overlay_widgets import OverlayManager from core.api import get_api, get_widget_api, get_external_api from core.log_reader import get_log_reader from core.ocr_service import get_ocr_service from core.screenshot import get_screenshot_service from core.notifications import get_notification_manager, NotificationManager from core.nexus_api import get_nexus_api from core.http_client import get_http_client from core.window_manager import get_window_manager from core.event_bus import get_event_bus from core.tasks import get_task_manager from core.audio import get_audio_manager from core.clipboard import get_clipboard_manager from core.data_store import get_data_store class HotkeyHandler(QObject): """Signal bridge for thread-safe hotkey handling.""" toggle_signal = pyqtSignal() hide_overlays_signal = pyqtSignal() class EUUtilityApp: """Main application controller.""" def __init__(self): self.app = None self.overlay = None self.floating_icon = None self.plugin_manager = None self.hotkey_handler = None self.settings = None self.overlay_manager = None self.api = None self.notification_manager = None def run(self): """Start the application.""" # Create Qt Application self.app = QApplication(sys.argv) self.app.setQuitOnLastWindowClosed(False) # Enable high DPI scaling if hasattr(Qt, 'AA_EnableHighDpiScaling'): self.app.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling) # Initialize Plugin API print("Initializing Plugin API...") self.api = get_api() self._setup_api_services() # Initialize Event Bus print("Initializing Event Bus...") self.event_bus = get_event_bus() self._print_event_bus_stats() # Initialize Notification Manager print("Initializing Notification Manager...") self.notification_manager = get_notification_manager() self.notification_manager.initialize(self.app) self.api.register_notification_service(self.notification_manager) print("[Core] Notification service registered") # Load settings self.settings = get_settings() # Create hotkey handler (must be in main thread) self.hotkey_handler = HotkeyHandler() # Initialize plugin manager print("Loading plugins...") self.plugin_manager = PluginManager(None) self.plugin_manager.load_all_plugins() # Create overlay manager self.overlay_manager = OverlayManager(self.app) # Create perfect UX main window print("Creating main window with perfect UX...") self.dashboard = create_perfect_window(self.plugin_manager) self.plugin_manager.overlay = self.dashboard self.dashboard.show() # Create system tray icon (replaces floating icon) print("Creating system tray icon...") self.tray_icon = TrayIcon(self.app) self.tray_icon.show_dashboard.connect(self._toggle_overlay) self.tray_icon.toggle_activity_bar.connect(self._toggle_activity_bar) self.tray_icon.quit_app.connect(self.quit) # Create Activity Bar (in-game overlay) - hidden by default print("Creating Activity Bar...") from core.activity_bar import get_activity_bar self.activity_bar = get_activity_bar(self.plugin_manager) if self.activity_bar and self.activity_bar.config.enabled: print("[Core] Activity Bar created (will show when EU is focused)") # Connect signals self.activity_bar.widget_requested.connect(self._on_activity_bar_widget) # Start EU focus detection self._start_eu_focus_detection() else: print("[Core] Activity Bar disabled") # Connect hotkey signals self.hotkey_handler.toggle_signal.connect(self._on_toggle_signal) # Setup global hotkeys self._setup_hotkeys() # Load saved overlay widgets self._load_overlay_widgets() print("EU-Utility started!") print("Dashboard window is open") print("Press Ctrl+Shift+U to toggle dashboard") print("Press Ctrl+Shift+H to hide all overlays") print("Press Ctrl+Shift+B to toggle activity bar") print(f"Loaded {len(self.plugin_manager.get_all_plugins())} plugins") # Show Event Bus stats self._print_event_bus_stats() # Run return self.app.exec() def _setup_api_services(self): """Setup shared API services - Window, OCR and Log are core services.""" # Initialize and start Log Reader print("[Core] Initializing Log Reader...") self.log_reader = get_log_reader() if self.log_reader.start(): print(f"[Core] Log Reader started - watching: {self.log_reader.log_path}") else: print("[Core] Log Reader not available - log file not found") # Register Log service with API self.api.register_log_service(self.log_reader.read_lines) # Initialize Window Manager (Windows-only, gracefully handles Linux) print("[Core] Initializing Window Manager...") self.window_manager = get_window_manager() if self.window_manager.is_available(): # Try to find EU window on startup eu_window = self.window_manager.find_eu_window() if eu_window: print(f"[Core] Found EU window: {eu_window.title} ({eu_window.width}x{eu_window.height})") else: print("[Core] EU window not found - will retry when needed") # Register Window service with API self.api.register_window_service(self.window_manager) else: print("[Core] Window Manager not available (Windows only)") # Screenshot Service - Initialize on startup (lightweight) print("[Core] Initializing Screenshot Service...") self.screenshot_service = get_screenshot_service() if self.screenshot_service.is_available(): self.api.register_screenshot_service(self.screenshot_service) backends = self.screenshot_service.get_available_backends() print(f"[Core] Screenshot Service ready (backends: {', '.join(backends)})") else: print("[Core] Screenshot Service not available - install pillow or pyautogui") # OCR Service - LAZY INITIALIZATION (don't init on startup) # It will initialize on first use print("[Core] OCR Service configured (lazy init)") self.ocr_service = get_ocr_service() # Register OCR service with API (lazy - will init on first call) self.api.register_ocr_service(self._lazy_ocr_handler) # Initialize Nexus API Service print("[Core] Initializing Nexus API Service...") self.nexus_api = get_nexus_api() self.api.register_nexus_service(self.nexus_api) # HTTP Client - Initialize on startup print("[Core] Initializing HTTP Client...") try: self.http_client = get_http_client( cache_dir="cache/http", default_cache_ttl=3600, rate_limit_delay=0.1, # Small delay between requests max_retries=3, backoff_factor=0.5, respect_cache_control=True ) self.api.register_http_service(self.http_client) print("[Core] HTTP Client initialized with caching") except Exception as e: print(f"[Core] HTTP Client initialization failed: {e}") # Initialize Audio Service print("[Core] Initializing Audio Service...") self.audio_manager = get_audio_manager() if self.audio_manager.is_available(): self.api.register_audio_service(self.audio_manager) backend = self.audio_manager.get_backend() volume = int(self.audio_manager.get_volume() * 100) print(f"[Core] Audio Service ready (backend: {backend}, volume: {volume}%)") else: print("[Core] Audio Service not available - no audio backend found") # Initialize Task Manager print("[Core] Initializing Task Manager...") try: self.task_manager = get_task_manager(max_workers=4) self.task_manager.initialize() self.api.register_task_service(self.task_manager) print("[Core] Task Manager initialized with 4 workers") except Exception as e: print(f"[Core] Task Manager initialization failed: {e}") # Initialize Clipboard Manager print("[Core] Initializing Clipboard Manager...") self.clipboard_manager = get_clipboard_manager() if self.clipboard_manager.is_available(): self.api.register_clipboard_service(self.clipboard_manager) print("[Core] Clipboard Service ready") else: print("[Core] Clipboard Service not available - install pyperclip") # Initialize Data Store print("[Core] Initializing Data Store...") self.data_store = get_data_store() self.api.register_data_service(self.data_store) print("[Core] Data Store ready") print("[Core] API services registered: Window, Screenshot, OCR (lazy), Log, Nexus, HTTP, Audio, Tasks, Clipboard, Data") def _lazy_ocr_handler(self, region=None): """Lazy OCR handler - triggers init on first use.""" return self.ocr_service.recognize(region=region) def _print_event_bus_stats(self): """Print Event Bus statistics on startup.""" if not hasattr(self, 'event_bus') or not self.event_bus: return stats = self.event_bus.get_stats() print("\n" + "=" * 50) print("📊 Event Bus Statistics") print("=" * 50) print(f" Total Events Published: {stats.get('total_published', 0)}") print(f" Total Events Delivered: {stats.get('total_delivered', 0)}") print(f" Active Subscriptions: {stats.get('active_subscriptions', 0)}") print(f" Events Per Minute: {stats.get('events_per_minute', 0)}") print(f" Avg Delivery Time: {stats.get('avg_delivery_ms', 0)} ms") print(f" Errors: {stats.get('errors', 0)}") top_types = stats.get('top_event_types', {}) if top_types: print(f"\n Top Event Types:") for event_type, count in list(top_types.items())[:5]: print(f" • {event_type}: {count}") print("=" * 50 + "\n") def _setup_hotkeys(self): """Setup global hotkeys.""" if KEYBOARD_AVAILABLE: try: # Toggle main overlay keyboard.add_hotkey('ctrl+shift+u', self._on_hotkey_pressed) # Hide all overlays keyboard.add_hotkey('ctrl+shift+h', self._on_hide_overlays_pressed) # Toggle activity bar keyboard.add_hotkey('ctrl+shift+b', self._on_activity_bar_hotkey) except Exception as e: print(f"Failed to register hotkey: {e}") def _on_hotkey_pressed(self): """Called when toggle hotkey is pressed.""" if self.hotkey_handler: self.hotkey_handler.toggle_signal.emit() def _on_hide_overlays_pressed(self): """Called when hide hotkey is pressed.""" if self.overlay_manager: self.overlay_manager.hide_all() def _on_activity_bar_hotkey(self): """Called when activity bar hotkey is pressed.""" self._toggle_activity_bar() def _on_toggle_signal(self): """Handle toggle signal in main thread.""" self._toggle_overlay() def _on_activity_bar_widget(self, plugin_id: str): """Handle activity bar widget request.""" print(f"[Main] Activity bar requested plugin: {plugin_id}") # Get plugin class all_plugins = self.plugin_manager.get_all_discovered_plugins() if plugin_id not in all_plugins: print(f"[Main] Plugin not found: {plugin_id}") return plugin_class = all_plugins[plugin_id] # Check if plugin has a mini widget if hasattr(plugin_class, 'get_mini_widget'): try: # Create mini widget widget = plugin_class.get_mini_widget() if widget: widget.show() print(f"[Main] Created mini widget for {plugin_class.name}") except Exception as e: print(f"[Main] Error creating mini widget: {e}") else: # No mini widget, try to show main dashboard self._toggle_overlay() # Switch to this plugin if self.dashboard: self.dashboard.show_plugin(plugin_id) def _toggle_overlay(self): """Toggle dashboard visibility.""" if self.dashboard: if self.dashboard.isVisible(): self.dashboard.hide() else: self.dashboard.show() self.dashboard.raise_() self.dashboard.activateWindow() def _toggle_activity_bar(self): """Toggle activity bar visibility.""" if self.activity_bar: if self.activity_bar.isVisible(): self.activity_bar.hide() self.tray_icon.set_activity_bar_checked(False) else: self.activity_bar.show() self.tray_icon.set_activity_bar_checked(True) def _start_eu_focus_detection(self): """Start timer to detect EU window focus and show/hide activity bar.""" from PyQt6.QtCore import QTimer self.eu_focus_timer = QTimer(self) self.eu_focus_timer.timeout.connect(self._check_eu_focus) self.eu_focus_timer.start(500) # Check every 500ms self._last_eu_focused = False print("[Core] EU focus detection started") def _check_eu_focus(self): """Check if EU window is focused and show/hide activity bar.""" if not self.activity_bar or not hasattr(self, 'window_manager'): return if not self.window_manager.is_available(): return try: eu_window = self.window_manager.find_eu_window() if eu_window: is_focused = eu_window.is_focused() if is_focused != self._last_eu_focused: self._last_eu_focused = is_focused if is_focused: # EU just got focused - show activity bar if not self.activity_bar.isVisible(): self.activity_bar.show() self.tray_icon.set_activity_bar_checked(True) print("[Core] EU focused - Activity Bar shown") else: # EU lost focus - hide activity bar if self.activity_bar.isVisible(): self.activity_bar.hide() self.tray_icon.set_activity_bar_checked(False) print("[Core] EU unfocused - Activity Bar hidden") except Exception as e: # Silently ignore errors (EU window might not exist) pass def _load_overlay_widgets(self): """Load saved overlay widgets.""" widget_settings = self.settings.get('overlay_widgets', {}) for widget_name, config in widget_settings.items(): if config.get('enabled', False): try: self.overlay_manager.create_widget( widget_name.replace('_overlay', ''), widget_name ) except Exception as e: print(f"Failed to load overlay widget {widget_name}: {e}") def create_overlay_widget(self, widget_type, name): """Create an overlay widget.""" if self.overlay_manager: return self.overlay_manager.create_widget(widget_type, name) return None def quit(self): """Quit the application.""" print("[Core] Shutting down...") # Stop EU focus timer if hasattr(self, 'eu_focus_timer'): self.eu_focus_timer.stop() # Stop log reader if hasattr(self, 'log_reader'): self.log_reader.stop() # Close all notifications if self.notification_manager: self.notification_manager.close_all() # Shutdown Event Bus if hasattr(self, 'event_bus') and self.event_bus: print("[Core] Shutting down Event Bus...") self.event_bus.shutdown() # Shutdown Audio if hasattr(self, 'audio_manager') and self.audio_manager: print("[Core] Shutting down Audio...") self.audio_manager.shutdown() # Shutdown Task Manager if hasattr(self, 'task_manager') and self.task_manager: print("[Core] Shutting down Task Manager...") self.task_manager.shutdown(wait=True, timeout=30.0) # Window manager has no persistent resources to clean up if self.overlay_manager: self.overlay_manager.hide_all() if self.plugin_manager: self.plugin_manager.shutdown_all() self.app.quit() def main(): """Entry point.""" app = EUUtilityApp() sys.exit(app.run()) if __name__ == "__main__": main()