""" System Tray Integration for EU-Utility ======================================= Provides system tray icon with menu for controlling the application. """ import sys from typing import Callable, Optional from PyQt6.QtWidgets import ( QSystemTrayIcon, QMenu, QAction, QWidget, QApplication, QMessageBox, QInputDialog ) from PyQt6.QtCore import pyqtSignal, QObject from PyQt6.QtGui import QIcon, QAction as QActionGui class TrayIcon(QObject): """System tray icon with menu. Signals: toggle_overlay: Emitted when overlay toggle is clicked show_settings: Emitted when settings is clicked show_dashboard: Emitted when dashboard is clicked quit: Emitted when quit is clicked """ toggle_overlay = pyqtSignal() show_settings = pyqtSignal() show_dashboard = pyqtSignal() quit_requested = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.tray_icon: Optional[QSystemTrayIcon] = None self.menu: Optional[QMenu] = None self._setup_tray() def _setup_tray(self): """Setup the system tray icon.""" # Create tray icon self.tray_icon = QSystemTrayIcon(self.parent()) # Try to use a custom icon, fallback to system icon icon = self._load_icon() self.tray_icon.setIcon(icon) self.tray_icon.setToolTip("EU-Utility - Entropia Universe Overlay") # Create menu self.menu = QMenu() self._build_menu() self.tray_icon.setContextMenu(self.menu) # Handle left click self.tray_icon.activated.connect(self._on_activated) def _load_icon(self) -> QIcon: """Load tray icon.""" from pathlib import Path # Try custom icon first icon_paths = [ Path(__file__).parent.parent.parent / "assets" / "icon.ico", Path(__file__).parent.parent.parent / "assets" / "icon.png", ] for path in icon_paths: if path.exists(): return QIcon(str(path)) # Fallback to system icon return QIcon.fromTheme("applications-games") def _build_menu(self): """Build the context menu.""" # Toggle Overlay toggle_action = QActionGui("Toggle Overlay (Ctrl+Shift+B)", self) toggle_action.triggered.connect(self.toggle_overlay.emit) self.menu.addAction(toggle_action) self.menu.addSeparator() # Dashboard dashboard_action = QActionGui("Open Dashboard", self) dashboard_action.triggered.connect(self.show_dashboard.emit) self.menu.addAction(dashboard_action) # Settings settings_action = QActionGui("Settings...", self) settings_action.triggered.connect(self.show_settings.emit) self.menu.addAction(settings_action) self.menu.addSeparator() # Status self.status_action = QActionGui("Status: Running", self) self.status_action.setEnabled(False) self.menu.addAction(self.status_action) self.menu.addSeparator() # Quit quit_action = QActionGui("Quit", self) quit_action.triggered.connect(self.quit_requested.emit) self.menu.addAction(quit_action) def _on_activated(self, reason: QSystemTrayIcon.ActivationReason): """Handle tray icon activation.""" if reason == QSystemTrayIcon.ActivationReason.Trigger: # Left click - toggle overlay self.toggle_overlay.emit() elif reason == QSystemTrayIcon.ActivationReason.Context: # Right click - show menu (handled automatically) pass def show(self): """Show the tray icon.""" if self.tray_icon: self.tray_icon.show() self.tray_icon.showMessage( "EU-Utility", "Application is running in system tray.\n" "Press Ctrl+Shift+B to toggle overlay.", QSystemTrayIcon.MessageIcon.Information, 3000 ) def hide(self): """Hide the tray icon.""" if self.tray_icon: self.tray_icon.hide() def update_status(self, status: str): """Update the status menu item.""" if hasattr(self, 'status_action'): self.status_action.setText(f"Status: {status}") def notify(self, title: str, message: str, icon=QSystemTrayIcon.MessageIcon.Information): """Show a tray notification.""" if self.tray_icon and self.tray_icon.isVisible(): self.tray_icon.showMessage(title, message, icon, 3000) class SettingsWindow(QWidget): """Desktop settings window.""" def __init__(self, config: dict, parent=None): super().__init__(parent) self.config = config self._setup_ui() def _setup_ui(self): """Setup the settings UI.""" from PyQt6.QtWidgets import ( QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QCheckBox, QComboBox, QSpinBox, QGroupBox, QTabWidget, QFileDialog ) self.setWindowTitle("EU-Utility Settings") self.setMinimumSize(500, 400) layout = QVBoxLayout(self) # Tabs tabs = QTabWidget() layout.addWidget(tabs) # General tab general_tab = QWidget() general_layout = QVBoxLayout(general_tab) # Game Path path_group = QGroupBox("Game Path") path_layout = QHBoxLayout(path_group) self.path_edit = QLineEdit(self.config.get('game_path', '')) path_layout.addWidget(self.path_edit) browse_btn = QPushButton("Browse...") browse_btn.clicked.connect(self._browse_game_path) path_layout.addWidget(browse_btn) general_layout.addWidget(path_group) # Overlay Settings overlay_group = QGroupBox("Overlay Settings") overlay_layout = QVBoxLayout(overlay_group) self.overlay_checkbox = QCheckBox("Enable Overlay") self.overlay_checkbox.setChecked(self.config.get('overlay_enabled', True)) overlay_layout.addWidget(self.overlay_checkbox) # Hotkey hotkey_layout = QHBoxLayout() hotkey_layout.addWidget(QLabel("Toggle Hotkey:")) self.hotkey_edit = QLineEdit(self.config.get('hotkey', 'ctrl+shift+b')) hotkey_layout.addWidget(self.hotkey_edit) overlay_layout.addLayout(hotkey_layout) # Overlay Mode mode_layout = QHBoxLayout() mode_layout.addWidget(QLabel("Overlay Mode:")) self.mode_combo = QComboBox() self.mode_combo.addItems([ 'overlay_toggle', 'overlay_game', 'overlay_always', 'overlay_temp' ]) self.mode_combo.setCurrentText(self.config.get('overlay_mode', 'overlay_toggle')) mode_layout.addWidget(self.mode_combo) overlay_layout.addLayout(mode_layout) general_layout.addWidget(overlay_group) # Theme Settings theme_group = QGroupBox("Appearance") theme_layout = QVBoxLayout(theme_group) theme_layout.addWidget(QLabel("Theme:")) self.theme_combo = QComboBox() self.theme_combo.addItems(['dark', 'light']) self.theme_combo.setCurrentText(self.config.get('ui', {}).get('theme', 'dark')) theme_layout.addWidget(self.theme_combo) general_layout.addWidget(theme_group) general_layout.addStretch() tabs.addTab(general_tab, "General") # Plugins tab plugins_tab = QWidget() plugins_layout = QVBoxLayout(plugins_tab) plugins_layout.addWidget(QLabel("Plugin settings will appear here")) plugins_layout.addStretch() tabs.addTab(plugins_tab, "Plugins") # Buttons button_layout = QHBoxLayout() button_layout.addStretch() save_btn = QPushButton("Save") save_btn.clicked.connect(self._save_settings) button_layout.addWidget(save_btn) cancel_btn = QPushButton("Cancel") cancel_btn.clicked.connect(self.close) button_layout.addWidget(cancel_btn) layout.addLayout(button_layout) def _browse_game_path(self): """Browse for game path.""" from PyQt6.QtWidgets import QFileDialog path = QFileDialog.getExistingDirectory( self, "Select Entropia Universe Directory", self.path_edit.text() or "C:/" ) if path: self.path_edit.setText(path) def _save_settings(self): """Save settings to config file.""" self.config['game_path'] = self.path_edit.text() self.config['overlay_enabled'] = self.overlay_checkbox.isChecked() self.config['hotkey'] = self.hotkey_edit.text() self.config['overlay_mode'] = self.mode_combo.currentText() if 'ui' not in self.config: self.config['ui'] = {} self.config['ui']['theme'] = self.theme_combo.currentText() # Save to file import json from pathlib import Path config_path = Path.home() / '.eu-utility' / 'config.json' config_path.parent.mkdir(parents=True, exist_ok=True) with open(config_path, 'w') as f: json.dump(self.config, f, indent=2) self.close()