""" System Tray Icon for Python Scheduler Always visible in Windows taskbar with menu and notifications """ import socket from PyQt6.QtWidgets import QSystemTrayIcon, QMenu, QApplication from PyQt6.QtGui import QIcon, QAction from PyQt6.QtCore import QTimer from loguru import logger from core.database import SchedulerDatabase class SchedulerSystemTray(QSystemTrayIcon): """System tray icon with menu""" def __init__(self, parent, db: SchedulerDatabase): super().__init__(parent) self.parent_window = parent self.db = db # Set icon (using default for now) # TODO: Create custom icon app = QApplication.instance() if app: self.setIcon(app.style().standardIcon(app.style().StandardPixmap.SP_ComputerIcon)) self.setToolTip("Frank - Running") # Create menu self._create_menu() # Connect double-click to show window self.activated.connect(self._on_activated) # Update timer for menu self.update_timer = QTimer() self.update_timer.timeout.connect(self._update_menu) self.update_timer.start(10000) # Update every 10 seconds logger.info("System tray icon initialized") def _create_menu(self): """Create context menu""" menu = QMenu() # Status action (disabled, shows info) self.status_action = QAction("Status: Ready") self.status_action.setEnabled(False) menu.addAction(self.status_action) # Open dashboard self.open_action = QAction("Open Dashboard") self.open_action.triggered.connect(self._show_dashboard) menu.addAction(self.open_action) # Exit self.exit_action = QAction("Exit Application") self.exit_action.triggered.connect(self._exit_app) menu.addAction(self.exit_action) self.setContextMenu(menu) # Initial update self._update_menu() def _update_menu(self): """Update dynamic menu items""" try: # Update status active_count = len(self.db.get_active_executions(hostname=socket.gethostname())) if active_count > 0: self.status_action.setText(f"Status: {active_count} job(s) running") else: self.status_action.setText("Status: Ready") except Exception as e: logger.error(f"Error updating system tray menu: {e}") def _update_recent_jobs(self): """Update recent jobs submenu""" self.recent_menu.clear() try: recent = self.db.get_recent_executions(limit=5, hostname=socket.gethostname()) if not recent: no_jobs = QAction("No recent executions") no_jobs.setEnabled(False) self.recent_menu.addAction(no_jobs) else: for execution in recent: status_icon = self._get_status_icon(execution['status']) job_text = f"{status_icon} {execution['job_name']}" action = QAction(job_text) action.setEnabled(False) self.recent_menu.addAction(action) except Exception as e: logger.error(f"Error updating recent jobs menu: {e}") def _update_active_jobs(self): """Update active jobs submenu""" self.active_menu.clear() try: active = self.db.get_active_executions(hostname=socket.gethostname()) if not active: no_active = QAction("No jobs running") no_active.setEnabled(False) self.active_menu.addAction(no_active) else: for execution in active: job_text = f"▶ {execution['job_name']}" action = QAction(job_text) action.setEnabled(False) self.active_menu.addAction(action) except Exception as e: logger.error(f"Error updating active jobs menu: {e}") def _get_status_icon(self, status: str) -> str: """Get status icon""" icons = { 'completed': '✓', 'failed': '✗', 'running': '▶', 'timeout': '⏱', 'cancelled': '⛔' } return icons.get(status, '?') def _on_activated(self, reason): """Handle tray icon activation""" if reason == QSystemTrayIcon.ActivationReason.DoubleClick: self._show_dashboard() def _show_dashboard(self): """Show main window""" self.parent_window.show_window() def _exit_app(self): """Exit application""" from PyQt6.QtWidgets import QApplication logger.info("Exiting application from system tray") QApplication.quit()