Compare commits

...

2 Commits

Author SHA1 Message Date
kdusek
7b695d7b7f Fix compatibility issues with Qt/PyQt6 versions
Some checks failed
build / build-win64 (push) Waiting to run
build / build-macos (push) Waiting to run
build / build-pip (push) Failing after 11s
- Add null check for message.message in search filter
- Handle missing colorScheme/colorSchemeChanged methods for older Qt versions
- Add display check to prevent hanging in headless environments
- Update build documentation with system package alternative
- Update PyInstaller spec for Python 3.12
- Improve run.sh script with venv management
2025-12-06 04:00:10 +01:00
kdusek
2b3d9eb07f Add search feature for Gotify alerts
Some checks failed
build / build-win64 (push) Waiting to run
build / build-macos (push) Waiting to run
build / build-pip (push) Failing after 12s
2025-12-06 03:10:41 +01:00
13 changed files with 92 additions and 24 deletions

View File

@@ -7,6 +7,16 @@ $ pip install -r requirements.txt
$ pip install pyinstaller $ pip install pyinstaller
``` ```
**Alternative: System packages (Debian/Ubuntu)**
If you prefer to use system packages instead of pip, install the required PyQt6 packages:
```shell
$ apt install python3-pyqt6 python3-pyqt6.qtwebsockets python3-pyqt6.qtmultimedia
```
Note: This may require specific Python versions and may not include the latest features.
Currently it's only possible to create installer packages from the pyinstaller output. For any target platform, first create the executable with pyinstaller: Currently it's only possible to create installer packages from the pyinstaller output. For any target platform, first create the executable with pyinstaller:
```shell ```shell

View File

@@ -8,7 +8,7 @@ logo = "gotify_tray/gui/images/logo.ico" if platform.system() != "Darwin" else "
a = Analysis(['gotify_tray/__main__.py'], a = Analysis(['gotify_tray/__main__.py'],
pathex=[os.getcwd()], pathex=[os.getcwd()],
binaries=[('/lib/x86_64-linux-gnu/libpython3.10.so', '.'), ('/lib/x86_64-linux-gnu/libpython3.10.so.1', '.')], binaries=[('/lib/x86_64-linux-gnu/libpython3.12.so', '.'), ('/lib/x86_64-linux-gnu/libpython3.12.so.1', '.')],
datas=[('gotify_tray/gui/images', 'gotify_tray/gui/images'), ('gotify_tray/gui/themes', 'gotify_tray/gui/themes')], datas=[('gotify_tray/gui/images', 'gotify_tray/gui/images'), ('gotify_tray/gui/themes', 'gotify_tray/gui/themes')],
hiddenimports=[], hiddenimports=[],
hookspath=[], hookspath=[],

View File

@@ -1,11 +1,21 @@
def main(): def main():
import os
import sys import sys
if "--version" in sys.argv: if "--version" in sys.argv:
from gotify_tray.__version__ import __version__ from gotify_tray.__version__ import __version__
print(__version__) print(__version__)
else: else:
# Check for display before importing GUI modules
if not os.environ.get("DISPLAY"):
print(
"Error: No display environment detected. This application requires a graphical desktop environment to run.",
file=sys.stderr,
)
sys.exit(1)
from gotify_tray.gui import start_gui from gotify_tray.gui import start_gui
start_gui() start_gui()

View File

@@ -459,6 +459,7 @@ class MainApplication(QtWidgets.QApplication):
self.main_window.hidden.connect(self.main_window_hidden_callback) self.main_window.hidden.connect(self.main_window_hidden_callback)
self.main_window.activated.connect(self.tray.revert_icon) self.main_window.activated.connect(self.tray.revert_icon)
if hasattr(self.styleHints(), "colorSchemeChanged"):
self.styleHints().colorSchemeChanged.connect( self.styleHints().colorSchemeChanged.connect(
self.theme_change_requested_callback self.theme_change_requested_callback
) )

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_main.ui' # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_main.ui'
# #
# Created by: PyQt6 UI code generator 6.9.1 # Created by: PyQt6 UI code generator 6.10.0
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@@ -72,11 +72,11 @@ class Ui_MainWindow(object):
self.pb_critical.setChecked(True) self.pb_critical.setChecked(True)
self.pb_critical.setObjectName("pb_critical") self.pb_critical.setObjectName("pb_critical")
self.filtersLayout.addWidget(self.pb_critical) self.filtersLayout.addWidget(self.pb_critical)
self.le_search = QtWidgets.QLineEdit(parent=self.verticalLayoutWidget)
self.le_search.setObjectName("le_search")
self.filtersLayout.addWidget(self.le_search)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.filtersLayout.addItem(spacerItem2) self.filtersLayout.addItem(spacerItem2)
self.label_subject = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
self.label_subject.setObjectName("label_subject")
self.filtersLayout.addWidget(self.label_subject)
self.pb_remove_filters = QtWidgets.QPushButton(parent=self.verticalLayoutWidget) self.pb_remove_filters = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
self.pb_remove_filters.setObjectName("pb_remove_filters") self.pb_remove_filters.setObjectName("pb_remove_filters")
self.filtersLayout.addWidget(self.pb_remove_filters) self.filtersLayout.addWidget(self.pb_remove_filters)
@@ -105,7 +105,7 @@ class Ui_MainWindow(object):
self.pb_normal.setText(_translate("MainWindow", "NORMAL")) self.pb_normal.setText(_translate("MainWindow", "NORMAL"))
self.pb_high.setText(_translate("MainWindow", "HIGH")) self.pb_high.setText(_translate("MainWindow", "HIGH"))
self.pb_critical.setText(_translate("MainWindow", "CRITICAL")) self.pb_critical.setText(_translate("MainWindow", "CRITICAL"))
self.label_subject.setText(_translate("MainWindow", "Subject:")) self.le_search.setPlaceholderText(_translate("MainWindow", "Search messages..."))
self.pb_remove_filters.setText(_translate("MainWindow", "Remove Filters")) self.pb_remove_filters.setText(_translate("MainWindow", "Remove Filters"))

View File

@@ -151,6 +151,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLineEdit" name="le_search">
<property name="placeholderText">
<string>Search messages...</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_3">
<property name="orientation"> <property name="orientation">
@@ -164,13 +171,6 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QLabel" name="label_subject">
<property name="text">
<string>Subject:</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="pb_remove_filters"> <widget class="QPushButton" name="pb_remove_filters">
<property name="text"> <property name="text">

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_message.ui' # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_message.ui'
# #
# Created by: PyQt6 UI code generator 6.9.1 # Created by: PyQt6 UI code generator 6.10.0
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_server.ui' # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_server.ui'
# #
# Created by: PyQt6 UI code generator 6.9.1 # Created by: PyQt6 UI code generator 6.10.0
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_settings.ui' # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_settings.ui'
# #
# Created by: PyQt6 UI code generator 6.9.1 # Created by: PyQt6 UI code generator 6.10.0
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.

View File

@@ -44,11 +44,16 @@ class MessagesProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.allowed_priorities = set(range(11)) # 0-10 self.allowed_priorities = set(range(11)) # 0-10
self.text_filter = ""
def set_allowed_priorities(self, priorities: set[int]): def set_allowed_priorities(self, priorities: set[int]):
self.allowed_priorities = priorities self.allowed_priorities = priorities
self.invalidateFilter() self.invalidateFilter()
def set_text_filter(self, text: str):
self.text_filter = text.lower()
self.invalidateFilter()
def filterAcceptsRow( def filterAcceptsRow(
self, source_row: int, source_parent: QtCore.QModelIndex self, source_row: int, source_parent: QtCore.QModelIndex
) -> bool: ) -> bool:
@@ -58,4 +63,9 @@ class MessagesProxyModel(QtCore.QSortFilterProxyModel):
priority = message.priority if message.priority is not None else 0 priority = message.priority if message.priority is not None else 0
if self.allowed_priorities and priority not in self.allowed_priorities: if self.allowed_priorities and priority not in self.allowed_priorities:
return False return False
if self.text_filter:
title = (message.title or "").lower()
msg = (message.message or "").lower()
if self.text_filter not in title and self.text_filter not in msg:
return False
return True return True

View File

@@ -3,14 +3,21 @@ from gotify_tray.utils import get_abs_path
themes = { themes = {
QtCore.Qt.ColorScheme.Dark: "dark", 2: "dark", # Dark
QtCore.Qt.ColorScheme.Light: "light", 1: "light", # Light
QtCore.Qt.ColorScheme.Unknown: "light", 0: "light", # Unknown
} }
def set_theme(app: QtWidgets.QApplication): def set_theme(app: QtWidgets.QApplication):
theme = themes.get(app.styleHints().colorScheme(), "light") if hasattr(app.styleHints(), "colorScheme"):
color_scheme = app.styleHints().colorScheme()
theme = themes.get(
color_scheme.value if hasattr(color_scheme, "value") else color_scheme,
"light",
)
else:
theme = "light" # Default to light theme if colorScheme not available
stylesheet = "" stylesheet = ""
with open(get_abs_path(f"gotify_tray/gui/themes/base.qss"), "r") as f: with open(get_abs_path(f"gotify_tray/gui/themes/base.qss"), "r") as f:
@@ -23,5 +30,12 @@ def set_theme(app: QtWidgets.QApplication):
def get_theme_file(file: str) -> str: def get_theme_file(file: str) -> str:
app = QtCore.QCoreApplication.instance() app = QtCore.QCoreApplication.instance()
theme = themes.get(app.styleHints().colorScheme(), "light") if hasattr(app.styleHints(), "colorScheme"):
color_scheme = app.styleHints().colorScheme()
theme = themes.get(
color_scheme.value if hasattr(color_scheme, "value") else color_scheme,
"light",
)
else:
theme = "light" # Default to light theme if colorScheme not available
return get_abs_path(f"gotify_tray/gui/themes/{theme}/{file}") return get_abs_path(f"gotify_tray/gui/themes/{theme}/{file}")

View File

@@ -100,6 +100,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.pb_critical.setCheckable(False) self.pb_critical.setCheckable(False)
self.pb_remove_filters.clicked.connect(self.on_remove_filters_clicked) self.pb_remove_filters.clicked.connect(self.on_remove_filters_clicked)
self.le_search.returnPressed.connect(self.on_search_return_pressed)
# set refresh shortcut (usually ctrl-r) # set refresh shortcut (usually ctrl-r)
# unfortunately this cannot be done with designer # unfortunately this cannot be done with designer
@@ -281,3 +282,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.pb_normal.setChecked(True) self.pb_normal.setChecked(True)
self.pb_high.setChecked(True) self.pb_high.setChecked(True)
# Critical is always on # Critical is always on
# Clear search
self.le_search.clear()
self.messages_proxy_model.set_text_filter("")
def on_search_return_pressed(self):
text = self.le_search.text()
self.messages_proxy_model.set_text_filter(text)

15
run.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Check if virtual environment exists
if [ ! -d "venv" ]; then
echo "Creating virtual environment..."
python3 -m venv venv
echo "Installing dependencies..."
source venv/bin/activate
pip install -r requirements.txt
else
source venv/bin/activate
fi
# Run the application
python -m gotify_tray