292 lines
9.5 KiB
Python
292 lines
9.5 KiB
Python
"""
|
|
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, QWidget, QApplication,
|
|
QMessageBox, QInputDialog
|
|
)
|
|
from PyQt6.QtCore import pyqtSignal, QObject
|
|
from PyQt6.QtGui import QIcon, QAction
|
|
|
|
|
|
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 = QAction("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 = QAction("Open Dashboard", self)
|
|
dashboard_action.triggered.connect(self.show_dashboard.emit)
|
|
self.menu.addAction(dashboard_action)
|
|
|
|
# Settings
|
|
settings_action = QAction("Settings...", self)
|
|
settings_action.triggered.connect(self.show_settings.emit)
|
|
self.menu.addAction(settings_action)
|
|
|
|
self.menu.addSeparator()
|
|
|
|
# Status
|
|
self.status_action = QAction("Status: Running", self)
|
|
self.status_action.setEnabled(False)
|
|
self.menu.addAction(self.status_action)
|
|
|
|
self.menu.addSeparator()
|
|
|
|
# Quit
|
|
quit_action = QAction("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()
|