import logging from PyQt6 import QtCore, QtGui, QtWidgets logger = logging.getLogger("gotify-tray") class PersistentNotification(QtWidgets.QWidget): close_all_requested = QtCore.pyqtSignal() def __init__( self, title: str, message: str, icon: QtGui.QIcon | QtWidgets.QSystemTrayIcon.MessageIcon, y_offset: int = 0, flash: bool = False, parent=None, ): super().__init__(parent) self.y_offset = y_offset self.flash = flash self.flash_state = False self.original_stylesheet = "" self.setWindowFlags( QtCore.Qt.WindowType.Window | QtCore.Qt.WindowType.FramelessWindowHint | QtCore.Qt.WindowType.WindowStaysOnTopHint ) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_OpaquePaintEvent, True) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_ShowWithoutActivating) # Layout layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(10, 10, 10, 10) layout.setSpacing(10) # Icon self.icon_label = QtWidgets.QLabel() if isinstance(icon, QtGui.QIcon): self.icon_label.setPixmap(icon.pixmap(32, 32)) else: # For MessageIcon if icon == QtWidgets.QSystemTrayIcon.MessageIcon.Information: standard_icon = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxInformation elif icon == QtWidgets.QSystemTrayIcon.MessageIcon.Warning: standard_icon = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning elif icon == QtWidgets.QSystemTrayIcon.MessageIcon.Critical: standard_icon = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxCritical else: standard_icon = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxInformation system_icon = self.style().standardIcon(standard_icon) self.icon_label.setPixmap(system_icon.pixmap(32, 32)) layout.addWidget(self.icon_label) # Text text_layout = QtWidgets.QVBoxLayout() self.title_label = QtWidgets.QLabel(title) self.title_label.setStyleSheet("font-weight: bold;") text_layout.addWidget(self.title_label) self.message_label = QtWidgets.QLabel(message) self.message_label.setWordWrap(True) text_layout.addWidget(self.message_label) layout.addLayout(text_layout, 1) # Close button self.close_button = QtWidgets.QPushButton("×") self.close_button.setFixedSize(20, 20) self.close_button.clicked.connect(self._on_close) layout.addWidget(self.close_button) # Style self.setAutoFillBackground(True) self.setBackgroundRole(QtGui.QPalette.ColorRole.Window) palette = self.palette() palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor("white")) palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor("red")) self.setPalette(palette) self.setStyleSheet(""" PersistentNotification { border-radius: 10px; border: 1px solid rgba(100, 100, 100, 200); } QPushButton { background-color: transparent; color: white; border: none; font-size: 16px; } QPushButton:hover { background-color: rgba(100, 100, 100, 100); } """) if self.flash: self.flash_timer = QtCore.QTimer(self) self.flash_timer.timeout.connect(self._toggle_flash) # Size and position self.adjustSize() self.setFixedWidth(300) self._position() # Timer for fade out if not clicked, but since persistent, maybe not needed # But to avoid staying forever if forgotten, perhaps auto-close after long time # But user wants until clicked, so no timer. def _position(self): screen = QtWidgets.QApplication.primaryScreen().availableGeometry() # Stack from bottom right self.move( screen.width() - self.width() - 10, screen.height() - self.height() - 50 - self.y_offset, ) def _toggle_flash(self): self.flash_state = not self.flash_state palette = self.palette() if self.flash_state: palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor("red")) palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor("white")) else: palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor("white")) palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor("red")) self.setPalette(palette) self.update() self.repaint() def mousePressEvent(self, event): self._on_close() def _on_close(self): if self.flash: self.flash_timer.stop() self.close_all_requested.emit() self.close() def showEvent(self, event): super().showEvent(event) self.raise_() if self.flash: self.flash_timer.start(1000)