Compare commits
16 Commits
3ee3942c01
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b3d9eb07f | ||
|
|
d0941fd7ab | ||
|
|
efdc63e1ab | ||
|
|
4c3b6925e5 | ||
|
|
09f85c5902 | ||
|
|
a797f4ccf1 | ||
|
|
2108568f50 | ||
|
|
4e4fd9cdc9 | ||
|
|
a8a03321f2 | ||
|
|
490044d9a7 | ||
|
|
a3ae246580 | ||
|
|
0730c160f6 | ||
|
|
ed815fb459 | ||
|
|
05da3a8295 | ||
|
|
f40f154f30 | ||
|
|
4e34c5e614 |
54
.github/workflows/develop.yml
vendored
54
.github/workflows/develop.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
build-win64:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
python-version: '3.13'
|
||||
- name: Upgrade pip and enable wheel support
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- name: Install Requirements
|
||||
@@ -30,49 +30,19 @@ jobs:
|
||||
mv inno-output\gotify-tray-installer.exe gotify-tray-installer-win.exe
|
||||
shell: cmd
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gotify-tray-installer-win.exe
|
||||
path: gotify-tray-installer-win.exe
|
||||
|
||||
build-ubuntu:
|
||||
strategy:
|
||||
matrix:
|
||||
tag: [jammy]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
- name: Upgrade pip and enable wheel support
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.0'
|
||||
- name: Build
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
gem install fpm
|
||||
chmod +x build-linux.sh
|
||||
./build-linux.sh deb
|
||||
mv "dist/gotify-tray_$(cat version.txt)_amd64.deb" "gotify-tray_$(cat version.txt)_amd64_${{ matrix.tag }}.deb"
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: "gotify-tray_*_amd64_${{ matrix.tag }}.deb"
|
||||
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
python-version: '3.13'
|
||||
- name: Upgrade pip and enable wheel support
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- name: Build
|
||||
@@ -81,7 +51,7 @@ jobs:
|
||||
brew install create-dmg
|
||||
create-dmg --volname "Gotify Tray" --app-drop-link 0 0 --no-internet-enable "gotify-tray.dmg" "./dist/Gotify Tray.app"
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gotify-tray.dmg
|
||||
path: gotify-tray.dmg
|
||||
@@ -89,11 +59,11 @@ jobs:
|
||||
build-pip:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
python-version: '3.13'
|
||||
- name: Upgrade pip and enable wheel support
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- name: install requirements
|
||||
@@ -103,6 +73,6 @@ jobs:
|
||||
- name: create pip package
|
||||
run: python -m build
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: dist/gotify_tray-*.whl
|
||||
|
||||
62
.github/workflows/release.yml
vendored
62
.github/workflows/release.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
||||
build-win64:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
python-version: '3.13'
|
||||
- name: Upgrade pip and enable wheel support
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- name: Install Requirements
|
||||
@@ -28,49 +28,19 @@ jobs:
|
||||
mv inno-output\gotify-tray-installer.exe gotify-tray-installer-win.exe
|
||||
shell: cmd
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gotify-tray-installer-win.exe
|
||||
path: gotify-tray-installer-win.exe
|
||||
|
||||
build-debian:
|
||||
strategy:
|
||||
matrix:
|
||||
tag: [bullseye, bookworm]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
- name: Upgrade pip and enable wheel support
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.0'
|
||||
- name: Build
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
gem install fpm
|
||||
chmod +x build-linux.sh
|
||||
./build-linux.sh deb
|
||||
mv "dist/gotify-tray_$(cat version.txt)_amd64.deb" "gotify-tray_$(cat version.txt)_amd64_${{ matrix.tag }}.deb"
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gotify-tray_${{github.ref_name}}_amd64_${{ matrix.tag }}.deb
|
||||
path: gotify-tray_${{github.ref_name}}_amd64_${{ matrix.tag }}.deb
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
python-version: '3.13'
|
||||
- name: Upgrade pip and enable wheel support
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- name: Build
|
||||
@@ -79,7 +49,7 @@ jobs:
|
||||
brew install create-dmg
|
||||
create-dmg --volname "Gotify Tray" --app-drop-link 0 0 --no-internet-enable "gotify-tray.dmg" "./dist/Gotify Tray.app"
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gotify-tray.dmg
|
||||
path: gotify-tray.dmg
|
||||
@@ -92,11 +62,11 @@ jobs:
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
python-version: '3.13'
|
||||
- name: Upgrade pip and enable wheel support
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- name: install requirements
|
||||
@@ -108,17 +78,17 @@ jobs:
|
||||
- name: upload to pypi
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gotify_tray-${{github.ref_name}}-py3-none-any.whl
|
||||
path: dist/gotify_tray-${{github.ref_name}}-py3-none-any.whl
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-win64, build-debian, build-macos, pypi]
|
||||
needs: [build-win64, build-macos, pypi]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Release
|
||||
uses: marvinpinto/action-automatic-releases@latest
|
||||
with:
|
||||
@@ -127,6 +97,4 @@ jobs:
|
||||
files: |
|
||||
gotify-tray-installer-win.exe
|
||||
gotify-tray.dmg
|
||||
gotify-tray_${{github.ref_name}}_amd64_bullseye.deb
|
||||
gotify-tray_${{github.ref_name}}_amd64_bookworm.deb
|
||||
gotify_tray-${{github.ref_name}}-py3-none-any.whl
|
||||
|
||||
22
AGENTS.md
Normal file
22
AGENTS.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Agent Guidelines
|
||||
|
||||
## Repository
|
||||
This project uses a customized repository at `http://192.168.88.97:3000/kadu/gotify-tray-customized.git`. Clone from this repository only for the latest changes and customizations.
|
||||
|
||||
## Type Checking
|
||||
Run `pyright .` in the project root to perform static type checking with Pyright. Address any critical errors before committing changes.
|
||||
|
||||
Note: Pyright may report many Qt-related type issues that are false positives due to PyQt6 stubs limitations. Focus on logical errors rather than Qt API mismatches.
|
||||
|
||||
To ignore PyQt6-related errors, create a `pyrightconfig.json` in the project root with:
|
||||
```json
|
||||
{
|
||||
"reportOptionalMemberAccess": false,
|
||||
"reportAttributeAccessIssue": false,
|
||||
"reportIncompatibleMethodOverride": false,
|
||||
"reportArgumentType": false,
|
||||
"reportAssignmentType": false,
|
||||
"reportReturnType": false
|
||||
}
|
||||
```
|
||||
This suppresses common PyQt6 false positives while still catching real issues.
|
||||
@@ -30,6 +30,13 @@ A tray notification application for receiving messages from a [Gotify server](ht
|
||||
- Go through a history of all previously received messages.
|
||||
- Receive missed messages after losing network connection.
|
||||
|
||||
## Customizations
|
||||
|
||||
- **Persistent Notifications for Priority 10**: Messages with priority 10 display as persistent pop-up windows that stay on screen until clicked, with a flashing background for attention.
|
||||
- **Sound Notification Control**: Option to play notification sound only for priority 10 messages.
|
||||
- **Multiple Persistent Notifications**: Multiple priority 10 messages stack vertically and can be closed all at once by clicking any one or the tray icon.
|
||||
- **Enhanced UI Settings**: Added configurable options for persistent notifications and sound behavior in the settings dialog.
|
||||
|
||||
|
||||
## Images
|
||||
|
||||
|
||||
0
build-linux.sh
Normal file → Executable file
0
build-linux.sh
Normal file → Executable file
13
build.sh
Executable file
13
build.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Clean previous builds
|
||||
rm -rf dist build
|
||||
|
||||
# Install requirements
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
|
||||
# Build
|
||||
pyinstaller gotify-tray.spec
|
||||
|
||||
echo "Build complete. Executable in dist/gotify-tray/"
|
||||
@@ -8,7 +8,7 @@ logo = "gotify_tray/gui/images/logo.ico" if platform.system() != "Darwin" else "
|
||||
|
||||
a = Analysis(['gotify_tray/__main__.py'],
|
||||
pathex=[os.getcwd()],
|
||||
binaries=[],
|
||||
binaries=[('/lib/x86_64-linux-gnu/libpython3.10.so', '.'), ('/lib/x86_64-linux-gnu/libpython3.10.so.1', '.')],
|
||||
datas=[('gotify_tray/gui/images', 'gotify_tray/gui/images'), ('gotify_tray/gui/themes', 'gotify_tray/gui/themes')],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
|
||||
@@ -3,4 +3,4 @@ __description__ = (
|
||||
"A tray notification application for receiving messages from a Gotify server."
|
||||
)
|
||||
__url__ = "https://github.com/seird/gotify-tray"
|
||||
__version__ = "0.5.2"
|
||||
__version__ = "0.5.3"
|
||||
|
||||
@@ -16,6 +16,8 @@ DEFAULT_SETTINGS = {
|
||||
"tray/notifications/duration_ms": 5000,
|
||||
"tray/notifications/icon/show": True,
|
||||
"tray/notifications/click": True,
|
||||
"tray/notifications/priority10_persistent": True,
|
||||
"tray/notifications/sound_only_priority10": True,
|
||||
"tray/icon/unread": False,
|
||||
"watchdog/enabled": True,
|
||||
"watchdog/interval/s": 60,
|
||||
|
||||
@@ -8,6 +8,7 @@ import tempfile
|
||||
from gotify_tray import gotify
|
||||
from gotify_tray.__version__ import __title__
|
||||
from gotify_tray.database import Downloader, Settings
|
||||
from .widgets.PersistentNotification import PersistentNotification
|
||||
from gotify_tray.tasks import (
|
||||
ClearCacheTask,
|
||||
DeleteApplicationMessagesTask,
|
||||
@@ -22,6 +23,7 @@ from gotify_tray.tasks import (
|
||||
from gotify_tray.gui.themes import set_theme
|
||||
from gotify_tray.utils import get_icon, verify_server
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtMultimedia import QSoundEffect
|
||||
|
||||
from ..__version__ import __title__
|
||||
from .models import (
|
||||
@@ -32,6 +34,7 @@ from .models import (
|
||||
ApplicationProxyModel,
|
||||
MessagesModel,
|
||||
MessagesModelItem,
|
||||
MessagesProxyModel,
|
||||
MessageItemDataRole,
|
||||
)
|
||||
from .widgets import ImagePopup, MainWindow, MessageWidget, SettingsDialog, Tray
|
||||
@@ -47,7 +50,9 @@ def init_logger(logger: logging.Logger):
|
||||
else:
|
||||
logging.disable()
|
||||
|
||||
logdir = QtCore.QStandardPaths.standardLocations(QtCore.QStandardPaths.StandardLocation.AppDataLocation)[0]
|
||||
logdir = QtCore.QStandardPaths.standardLocations(
|
||||
QtCore.QStandardPaths.StandardLocation.AppDataLocation
|
||||
)[0]
|
||||
if not os.path.exists(logdir):
|
||||
os.mkdir(logdir)
|
||||
logging.basicConfig(
|
||||
@@ -57,6 +62,28 @@ def init_logger(logger: logging.Logger):
|
||||
|
||||
|
||||
class MainApplication(QtWidgets.QApplication):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Initialize notification sound effect
|
||||
self.notification_sound = QSoundEffect()
|
||||
sound_path = os.path.join(
|
||||
os.path.dirname(__file__), "images", "notification.wav"
|
||||
)
|
||||
self.notification_sound.setSource(QtCore.QUrl.fromLocalFile(sound_path))
|
||||
self.notification_sound.setVolume(0.5) # Set volume (0.0 to 1.0)
|
||||
self.persistent_notifications = []
|
||||
self.next_y_offset = 0
|
||||
|
||||
def close_all_persistent_notifications(self):
|
||||
for notification in self.persistent_notifications:
|
||||
notification.close()
|
||||
self.persistent_notifications.clear()
|
||||
self.next_y_offset = 0
|
||||
|
||||
def _on_tray_activated(self, reason):
|
||||
if reason == QtWidgets.QSystemTrayIcon.ActivationReason.Trigger:
|
||||
self.close_all_persistent_notifications()
|
||||
|
||||
def init_ui(self):
|
||||
self.gotify_client = gotify.GotifyClient(
|
||||
settings.value("Server/url", type=str),
|
||||
@@ -66,10 +93,17 @@ class MainApplication(QtWidgets.QApplication):
|
||||
self.downloader = Downloader()
|
||||
|
||||
self.messages_model = MessagesModel()
|
||||
self.messages_proxy_model = MessagesProxyModel()
|
||||
self.messages_proxy_model.setSourceModel(self.messages_model)
|
||||
self.application_model = ApplicationModel()
|
||||
self.application_proxy_model = ApplicationProxyModel(self.application_model)
|
||||
|
||||
self.main_window = MainWindow(self.application_model, self.application_proxy_model, self.messages_model)
|
||||
self.main_window = MainWindow(
|
||||
self.application_model,
|
||||
self.application_proxy_model,
|
||||
self.messages_model,
|
||||
self.messages_proxy_model,
|
||||
)
|
||||
self.main_window.show() # The initial .show() is necessary to get the correct sizes when adding MessageWigets
|
||||
QtCore.QTimer.singleShot(0, self.main_window.hide)
|
||||
|
||||
@@ -77,12 +111,16 @@ class MainApplication(QtWidgets.QApplication):
|
||||
|
||||
self.tray = Tray()
|
||||
self.tray.show()
|
||||
self.tray.activated.connect(self._on_tray_activated)
|
||||
|
||||
self.first_connect = True
|
||||
|
||||
self.watchdog = ServerConnectionWatchdogTask(self.gotify_client)
|
||||
|
||||
self.link_callbacks()
|
||||
self.main_window.priority_filter_changed.connect(
|
||||
self.on_priority_filter_changed
|
||||
)
|
||||
self.init_shortcuts()
|
||||
|
||||
self.gotify_client.listen()
|
||||
@@ -100,17 +138,30 @@ class MainApplication(QtWidgets.QApplication):
|
||||
self.application_model.setItem(0, 0, ApplicationAllMessagesItem())
|
||||
|
||||
self.get_applications_task = GetApplicationsTask(self.gotify_client)
|
||||
self.get_applications_task.success.connect(self.get_applications_success_callback)
|
||||
self.get_applications_task.started.connect(self.main_window.disable_applications)
|
||||
self.get_applications_task.finished.connect(self.main_window.enable_applications)
|
||||
self.get_applications_task.success.connect(
|
||||
self.get_applications_success_callback
|
||||
)
|
||||
self.get_applications_task.started.connect(
|
||||
self.main_window.disable_applications
|
||||
)
|
||||
self.get_applications_task.finished.connect(
|
||||
self.main_window.enable_applications
|
||||
)
|
||||
self.get_applications_task.start()
|
||||
|
||||
def get_applications_success_callback(
|
||||
self, applications: list[gotify.GotifyApplicationModel],
|
||||
self,
|
||||
applications: list[gotify.GotifyApplicationModel],
|
||||
):
|
||||
for i, application in enumerate(applications):
|
||||
icon = QtGui.QIcon(self.downloader.get_filename(f"{self.gotify_client.url}/{application.image}"))
|
||||
self.application_model.setItem(i + 1, 0, ApplicationModelItem(application, icon))
|
||||
icon = QtGui.QIcon(
|
||||
self.downloader.get_filename(
|
||||
f"{self.gotify_client.url}/{application.image}"
|
||||
)
|
||||
)
|
||||
self.application_model.setItem(
|
||||
i + 1, 0, ApplicationModelItem(application, icon)
|
||||
)
|
||||
|
||||
def update_last_id(self, i: int):
|
||||
if i > settings.value("message/last", type=int):
|
||||
@@ -157,6 +208,9 @@ class MainApplication(QtWidgets.QApplication):
|
||||
else:
|
||||
self.gotify_client.stop()
|
||||
|
||||
def on_priority_filter_changed(self, priorities: set[int]):
|
||||
self.messages_proxy_model.set_allowed_priorities(priorities)
|
||||
|
||||
def abort_get_messages_task(self):
|
||||
"""
|
||||
Abort any tasks that will result in new messages getting appended to messages_model
|
||||
@@ -174,15 +228,24 @@ class MainApplication(QtWidgets.QApplication):
|
||||
for task in aborted_tasks:
|
||||
task.wait()
|
||||
|
||||
def application_selection_changed_callback(self, item: ApplicationModelItem | ApplicationAllMessagesItem):
|
||||
def application_selection_changed_callback(
|
||||
self, item: ApplicationModelItem | ApplicationAllMessagesItem
|
||||
):
|
||||
self.main_window.disable_buttons()
|
||||
self.abort_get_messages_task()
|
||||
self.messages_model.clear()
|
||||
|
||||
if isinstance(item, ApplicationModelItem):
|
||||
self.get_application_messages_task = GetApplicationMessagesTask(item.data(ApplicationItemDataRole.ApplicationRole).id, self.gotify_client)
|
||||
self.get_application_messages_task.message.connect(self.messages_model.append_message)
|
||||
self.get_application_messages_task.finished.connect(self.main_window.enable_buttons)
|
||||
self.get_application_messages_task = GetApplicationMessagesTask(
|
||||
item.data(ApplicationItemDataRole.ApplicationRole).id,
|
||||
self.gotify_client,
|
||||
)
|
||||
self.get_application_messages_task.message.connect(
|
||||
self.messages_model.append_message
|
||||
)
|
||||
self.get_application_messages_task.finished.connect(
|
||||
self.main_window.enable_buttons
|
||||
)
|
||||
self.get_application_messages_task.start()
|
||||
|
||||
elif isinstance(item, ApplicationAllMessagesItem):
|
||||
@@ -191,10 +254,14 @@ class MainApplication(QtWidgets.QApplication):
|
||||
self.get_messages_task.finished.connect(self.main_window.enable_buttons)
|
||||
self.get_messages_task.start()
|
||||
|
||||
def add_message_to_model(self, message: gotify.GotifyMessageModel, process: bool = True):
|
||||
def add_message_to_model(
|
||||
self, message: gotify.GotifyMessageModel, process: bool = True
|
||||
):
|
||||
if self.application_model.itemFromId(message.appid):
|
||||
application_index = self.main_window.currentApplicationIndex()
|
||||
if selected_application_item := self.application_model.itemFromIndex(self.application_proxy_model.mapToSource(application_index)):
|
||||
if selected_application_item := self.application_model.itemFromIndex(
|
||||
self.application_proxy_model.mapToSource(application_index)
|
||||
):
|
||||
|
||||
def insert_message_helper():
|
||||
if isinstance(selected_application_item, ApplicationModelItem):
|
||||
@@ -202,10 +269,14 @@ class MainApplication(QtWidgets.QApplication):
|
||||
# -> Only insert the message if the appid matches the selected appid
|
||||
if (
|
||||
message.appid
|
||||
== selected_application_item.data(ApplicationItemDataRole.ApplicationRole).id
|
||||
== selected_application_item.data(
|
||||
ApplicationItemDataRole.ApplicationRole
|
||||
).id
|
||||
):
|
||||
self.messages_model.insert_message(0, message)
|
||||
elif isinstance(selected_application_item, ApplicationAllMessagesItem):
|
||||
elif isinstance(
|
||||
selected_application_item, ApplicationAllMessagesItem
|
||||
):
|
||||
# "All messages' is selected
|
||||
self.messages_model.insert_message(0, message)
|
||||
|
||||
@@ -216,10 +287,14 @@ class MainApplication(QtWidgets.QApplication):
|
||||
else:
|
||||
insert_message_helper()
|
||||
else:
|
||||
logger.error(f"App id {message.appid} could not be found. Refreshing applications.")
|
||||
logger.error(
|
||||
f"App id {message.appid} could not be found. Refreshing applications."
|
||||
)
|
||||
self.refresh_applications()
|
||||
|
||||
def new_message_callback(self, message: gotify.GotifyMessageModel, process: bool = True):
|
||||
def new_message_callback(
|
||||
self, message: gotify.GotifyMessageModel, process: bool = True
|
||||
):
|
||||
self.add_message_to_model(message, process=process)
|
||||
|
||||
# Don't show a notification if it's low priority or the window is active
|
||||
@@ -237,21 +312,52 @@ class MainApplication(QtWidgets.QApplication):
|
||||
self.tray.set_icon_unread()
|
||||
|
||||
# Get the application icon
|
||||
if (
|
||||
settings.value("tray/notifications/icon/show", type=bool)
|
||||
and (application_item := self.application_model.itemFromId(message.appid))
|
||||
if settings.value("tray/notifications/icon/show", type=bool) and (
|
||||
application_item := self.application_model.itemFromId(message.appid)
|
||||
):
|
||||
icon = application_item.icon()
|
||||
else:
|
||||
icon = QtWidgets.QSystemTrayIcon.MessageIcon.Information
|
||||
|
||||
# Show notification
|
||||
if message.priority == 10 and settings.value(
|
||||
"tray/notifications/priority10_persistent", type=bool
|
||||
):
|
||||
# Create persistent notification
|
||||
notification = PersistentNotification(
|
||||
message.title or "",
|
||||
message.message or "",
|
||||
icon,
|
||||
y_offset=self.next_y_offset,
|
||||
flash=True,
|
||||
)
|
||||
notification.close_all_requested.connect(
|
||||
self.close_all_persistent_notifications
|
||||
)
|
||||
self.persistent_notifications.append(notification)
|
||||
notification.show()
|
||||
self.next_y_offset += notification.height() + 10
|
||||
else:
|
||||
# Use system tray notification
|
||||
msecs = settings.value("tray/notifications/duration_ms", type=int)
|
||||
self.tray.showMessage(
|
||||
message.title,
|
||||
message.message,
|
||||
icon,
|
||||
msecs=settings.value("tray/notifications/duration_ms", type=int),
|
||||
msecs=msecs,
|
||||
)
|
||||
|
||||
# Play notification sound
|
||||
if (
|
||||
not settings.value("tray/notifications/sound_only_priority10", type=bool)
|
||||
or message.priority == 10
|
||||
):
|
||||
if self.notification_sound.isLoaded():
|
||||
self.notification_sound.play()
|
||||
else:
|
||||
# Try to play anyway (QSoundEffect will queue if not loaded yet)
|
||||
self.notification_sound.play()
|
||||
|
||||
def delete_message_callback(self, message_item: MessagesModelItem):
|
||||
self.delete_message_task = DeleteMessageTask(
|
||||
message_item.data(MessageItemDataRole.MessageRole).id, self.gotify_client
|
||||
@@ -299,7 +405,11 @@ class MainApplication(QtWidgets.QApplication):
|
||||
|
||||
# Update the message widget icons
|
||||
for r in range(self.messages_model.rowCount()):
|
||||
message_widget: MessageWidget = self.main_window.listView_messages.indexWidget(self.messages_model.index(r, 0))
|
||||
message_widget: MessageWidget = (
|
||||
self.main_window.listView_messages.indexWidget(
|
||||
self.messages_model.index(r, 0)
|
||||
)
|
||||
)
|
||||
message_widget.set_icons()
|
||||
|
||||
def settings_callback(self):
|
||||
@@ -341,15 +451,21 @@ class MainApplication(QtWidgets.QApplication):
|
||||
|
||||
self.main_window.refresh.connect(self.refresh_applications)
|
||||
self.main_window.delete_all.connect(self.delete_all_messages_callback)
|
||||
self.main_window.application_selection_changed.connect(self.application_selection_changed_callback)
|
||||
self.main_window.application_selection_changed.connect(
|
||||
self.application_selection_changed_callback
|
||||
)
|
||||
self.main_window.delete_message.connect(self.delete_message_callback)
|
||||
self.main_window.image_popup.connect(self.image_popup_callback)
|
||||
self.main_window.hidden.connect(self.main_window_hidden_callback)
|
||||
self.main_window.activated.connect(self.tray.revert_icon)
|
||||
|
||||
self.styleHints().colorSchemeChanged.connect(self.theme_change_requested_callback)
|
||||
self.styleHints().colorSchemeChanged.connect(
|
||||
self.theme_change_requested_callback
|
||||
)
|
||||
|
||||
self.messages_model.rowsInserted.connect(self.main_window.display_message_widgets)
|
||||
self.messages_model.rowsInserted.connect(
|
||||
self.main_window.display_message_widgets
|
||||
)
|
||||
|
||||
self.gotify_client.opened.connect(self.listener_opened_callback)
|
||||
self.gotify_client.closed.connect(self.listener_closed_callback)
|
||||
@@ -366,7 +482,9 @@ class MainApplication(QtWidgets.QApplication):
|
||||
|
||||
def acquire_lock(self) -> bool:
|
||||
temp_dir = tempfile.gettempdir()
|
||||
lock_filename = os.path.join(temp_dir, __title__ + "-" + getpass.getuser() + ".lock")
|
||||
lock_filename = os.path.join(
|
||||
temp_dir, __title__ + "-" + getpass.getuser() + ".lock"
|
||||
)
|
||||
self.lock_file = QtCore.QLockFile(lock_filename)
|
||||
self.lock_file.setStaleLockTime(0)
|
||||
return self.lock_file.tryLock()
|
||||
|
||||
@@ -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.5.0
|
||||
# Created by: PyQt6 UI code generator 6.9.1
|
||||
#
|
||||
# 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.
|
||||
@@ -47,6 +47,40 @@ class Ui_MainWindow(object):
|
||||
self.pb_delete_all.setObjectName("pb_delete_all")
|
||||
self.horizontalLayout.addWidget(self.pb_delete_all)
|
||||
self.verticalLayout_2.addLayout(self.horizontalLayout)
|
||||
self.filtersLayout = QtWidgets.QHBoxLayout()
|
||||
self.filtersLayout.setObjectName("filtersLayout")
|
||||
self.label_priority = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
|
||||
self.label_priority.setObjectName("label_priority")
|
||||
self.filtersLayout.addWidget(self.label_priority)
|
||||
self.pb_low = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
|
||||
self.pb_low.setCheckable(True)
|
||||
self.pb_low.setChecked(True)
|
||||
self.pb_low.setObjectName("pb_low")
|
||||
self.filtersLayout.addWidget(self.pb_low)
|
||||
self.pb_normal = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
|
||||
self.pb_normal.setCheckable(True)
|
||||
self.pb_normal.setChecked(True)
|
||||
self.pb_normal.setObjectName("pb_normal")
|
||||
self.filtersLayout.addWidget(self.pb_normal)
|
||||
self.pb_high = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
|
||||
self.pb_high.setCheckable(True)
|
||||
self.pb_high.setChecked(True)
|
||||
self.pb_high.setObjectName("pb_high")
|
||||
self.filtersLayout.addWidget(self.pb_high)
|
||||
self.pb_critical = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
|
||||
self.pb_critical.setCheckable(True)
|
||||
self.pb_critical.setChecked(True)
|
||||
self.pb_critical.setObjectName("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)
|
||||
self.filtersLayout.addItem(spacerItem2)
|
||||
self.pb_remove_filters = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
|
||||
self.pb_remove_filters.setObjectName("pb_remove_filters")
|
||||
self.filtersLayout.addWidget(self.pb_remove_filters)
|
||||
self.verticalLayout_2.addLayout(self.filtersLayout)
|
||||
self.listView_messages = QtWidgets.QListView(parent=self.verticalLayoutWidget)
|
||||
self.listView_messages.setAutoScroll(True)
|
||||
self.listView_messages.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
@@ -66,6 +100,13 @@ class Ui_MainWindow(object):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "Form"))
|
||||
self.label_application.setText(_translate("MainWindow", "Application"))
|
||||
self.label_priority.setText(_translate("MainWindow", "Priority:"))
|
||||
self.pb_low.setText(_translate("MainWindow", "LOW"))
|
||||
self.pb_normal.setText(_translate("MainWindow", "NORMAL"))
|
||||
self.pb_high.setText(_translate("MainWindow", "HIGH"))
|
||||
self.pb_critical.setText(_translate("MainWindow", "CRITICAL"))
|
||||
self.le_search.setPlaceholderText(_translate("MainWindow", "Search messages..."))
|
||||
self.pb_remove_filters.setText(_translate("MainWindow", "Remove Filters"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -90,6 +90,96 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="filtersLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_priority">
|
||||
<property name="text">
|
||||
<string>Priority:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_low">
|
||||
<property name="text">
|
||||
<string>LOW</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_normal">
|
||||
<property name="text">
|
||||
<string>NORMAL</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_high">
|
||||
<property name="text">
|
||||
<string>HIGH</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_critical">
|
||||
<property name="text">
|
||||
<string>CRITICAL</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="le_search">
|
||||
<property name="placeholderText">
|
||||
<string>Search messages...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_remove_filters">
|
||||
<property name="text">
|
||||
<string>Remove Filters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="listView_messages">
|
||||
<property name="autoScroll">
|
||||
|
||||
@@ -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.5.0
|
||||
# Created by: PyQt6 UI code generator 6.9.1
|
||||
#
|
||||
# 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.
|
||||
|
||||
@@ -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.5.0
|
||||
# Created by: PyQt6 UI code generator 6.9.1
|
||||
#
|
||||
# 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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_settings.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.4.2
|
||||
# Created by: PyQt6 UI code generator 6.9.1
|
||||
#
|
||||
# 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.
|
||||
@@ -51,7 +51,7 @@ class Ui_Dialog(object):
|
||||
self.cb_notify.setObjectName("cb_notify")
|
||||
self.gridLayout_4.addWidget(self.cb_notify, 2, 0, 1, 3)
|
||||
self.spin_priority = QtWidgets.QSpinBox(parent=self.groupBox_notifications)
|
||||
self.spin_priority.setMinimum(1)
|
||||
self.spin_priority.setMinimum(0)
|
||||
self.spin_priority.setMaximum(10)
|
||||
self.spin_priority.setProperty("value", 5)
|
||||
self.spin_priority.setObjectName("spin_priority")
|
||||
@@ -62,6 +62,9 @@ class Ui_Dialog(object):
|
||||
self.cb_tray_icon_unread = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
|
||||
self.cb_tray_icon_unread.setObjectName("cb_tray_icon_unread")
|
||||
self.gridLayout_4.addWidget(self.cb_tray_icon_unread, 4, 0, 1, 3)
|
||||
self.cb_priority10_persistent = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
|
||||
self.cb_priority10_persistent.setObjectName("cb_priority10_persistent")
|
||||
self.gridLayout_4.addWidget(self.cb_priority10_persistent, 5, 0, 1, 3)
|
||||
self.verticalLayout_4.addWidget(self.groupBox_notifications)
|
||||
self.groupBox_2 = QtWidgets.QGroupBox(parent=self.tab_general)
|
||||
self.groupBox_2.setObjectName("groupBox_2")
|
||||
@@ -290,6 +293,7 @@ class Ui_Dialog(object):
|
||||
self.cb_notify.setText(_translate("Dialog", "Show a notification for missed messages after reconnecting"))
|
||||
self.label_notification_priority.setText(_translate("Dialog", "Minimum priority to show notifications:"))
|
||||
self.cb_tray_icon_unread.setText(_translate("Dialog", "Change the tray icon color when there are unread notifications"))
|
||||
self.cb_priority10_persistent.setText(_translate("Dialog", "Show persistent notifications for priority 10 messages"))
|
||||
self.groupBox_2.setTitle(_translate("Dialog", "Interface"))
|
||||
self.cb_priority_colors.setToolTip(_translate("Dialog", "4..7 -> medium\n"
|
||||
"8..10 -> high"))
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="spin_priority">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10</number>
|
||||
@@ -121,6 +121,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="cb_priority10_persistent">
|
||||
<property name="text">
|
||||
<string>Show persistent notifications for priority 10 messages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
BIN
gotify_tray/gui/images/notification.wav
Normal file
BIN
gotify_tray/gui/images/notification.wav
Normal file
Binary file not shown.
@@ -1,5 +1,4 @@
|
||||
import enum
|
||||
|
||||
from typing import cast
|
||||
from PyQt6 import QtCore, QtGui
|
||||
from gotify_tray import gotify
|
||||
@@ -39,3 +38,34 @@ class MessagesModel(QtGui.QStandardItemModel):
|
||||
|
||||
def itemFromIndex(self, index: QtCore.QModelIndex) -> MessagesModelItem:
|
||||
return cast(MessagesModelItem, super(MessagesModel, self).itemFromIndex(index))
|
||||
|
||||
|
||||
class MessagesProxyModel(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.allowed_priorities = set(range(11)) # 0-10
|
||||
self.text_filter = ""
|
||||
|
||||
def set_allowed_priorities(self, priorities: set[int]):
|
||||
self.allowed_priorities = priorities
|
||||
self.invalidateFilter()
|
||||
|
||||
def set_text_filter(self, text: str):
|
||||
self.text_filter = text.lower()
|
||||
self.invalidateFilter()
|
||||
|
||||
def filterAcceptsRow(
|
||||
self, source_row: int, source_parent: QtCore.QModelIndex
|
||||
) -> bool:
|
||||
index = self.sourceModel().index(source_row, 0, source_parent)
|
||||
item = self.sourceModel().itemFromIndex(index)
|
||||
message = item.data(MessageItemDataRole.MessageRole)
|
||||
priority = message.priority if message.priority is not None else 0
|
||||
if self.allowed_priorities and priority not in self.allowed_priorities:
|
||||
return False
|
||||
if self.text_filter:
|
||||
title = (message.title or "").lower()
|
||||
msg = message.message.lower()
|
||||
if self.text_filter not in title and self.text_filter not in msg:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -5,4 +5,9 @@ from .ApplicationModel import (
|
||||
ApplicationProxyModel,
|
||||
ApplicationItemDataRole,
|
||||
)
|
||||
from .MessagesModel import MessagesModelItem, MessagesModel, MessageItemDataRole
|
||||
from .MessagesModel import (
|
||||
MessagesModelItem,
|
||||
MessagesModel,
|
||||
MessagesProxyModel,
|
||||
MessageItemDataRole,
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ from ..models import (
|
||||
ApplicationModel,
|
||||
MessagesModel,
|
||||
MessagesModelItem,
|
||||
MessagesProxyModel,
|
||||
)
|
||||
from . import MessageWidget
|
||||
|
||||
@@ -26,8 +27,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
image_popup = QtCore.pyqtSignal(str, QtCore.QPoint)
|
||||
hidden = QtCore.pyqtSignal()
|
||||
activated = QtCore.pyqtSignal()
|
||||
priority_filter_changed = QtCore.pyqtSignal(set)
|
||||
|
||||
def __init__(self, application_model: ApplicationModel, application_proxy_model: QtCore.QSortFilterProxyModel, messages_model: MessagesModel):
|
||||
def __init__(
|
||||
self,
|
||||
application_model: ApplicationModel,
|
||||
application_proxy_model: QtCore.QSortFilterProxyModel,
|
||||
messages_model: MessagesModel,
|
||||
messages_proxy_model: MessagesProxyModel,
|
||||
):
|
||||
super(MainWindow, self).__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
@@ -38,9 +46,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.application_model = application_model
|
||||
self.application_proxy_model = application_proxy_model
|
||||
self.messages_model = messages_model
|
||||
self.messages_proxy_model = messages_proxy_model
|
||||
|
||||
self.listView_applications.setModel(application_proxy_model)
|
||||
self.listView_messages.setModel(messages_model)
|
||||
self.listView_messages.setModel(messages_proxy_model)
|
||||
|
||||
self.messages_proxy_model.rowsInserted.connect(self.display_message_widgets)
|
||||
self.messages_proxy_model.layoutChanged.connect(self.redisplay_message_widgets)
|
||||
|
||||
# Do not expand the applications listview when resizing
|
||||
self.splitter.setStretchFactor(0, 0)
|
||||
@@ -69,6 +81,33 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
self.link_callbacks()
|
||||
|
||||
# Setup filters
|
||||
self.pb_low.toggled.connect(self.on_priority_button_toggled)
|
||||
self.pb_normal.toggled.connect(self.on_priority_button_toggled)
|
||||
self.pb_high.toggled.connect(self.on_priority_button_toggled)
|
||||
# Critical is always shown, no toggle
|
||||
|
||||
# Set styles for priority buttons
|
||||
button_style = "QPushButton { background-color: grey; border: 1px solid black; } QPushButton:checked { background-color: green; }"
|
||||
self.pb_low.setStyleSheet(button_style)
|
||||
self.pb_normal.setStyleSheet(button_style)
|
||||
self.pb_high.setStyleSheet(button_style)
|
||||
# Critical always green
|
||||
self.pb_critical.setStyleSheet(
|
||||
"QPushButton { background-color: green; border: 1px solid black; }"
|
||||
)
|
||||
self.pb_critical.setChecked(True)
|
||||
self.pb_critical.setCheckable(False)
|
||||
|
||||
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)
|
||||
# unfortunately this cannot be done with designer
|
||||
self.pb_refresh.setShortcut(
|
||||
QtGui.QKeySequence(QtGui.QKeySequence.StandardKey.Refresh)
|
||||
)
|
||||
|
||||
def set_icons(self):
|
||||
# Set button icons
|
||||
self.pb_refresh.setIcon(QtGui.QIcon(get_theme_file("refresh.svg")))
|
||||
@@ -102,20 +141,36 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
def set_error(self):
|
||||
self.status_widget.set_error()
|
||||
|
||||
def display_message_widgets(self, parent: QtCore.QModelIndex, first: int, last: int):
|
||||
def display_message_widgets(
|
||||
self, parent: QtCore.QModelIndex, first: int, last: int
|
||||
):
|
||||
for i in range(first, last + 1):
|
||||
if index := self.messages_model.index(i, 0, parent):
|
||||
message_item = self.messages_model.itemFromIndex(index)
|
||||
|
||||
message: gotify.GotifyMessageModel = self.messages_model.data(index, MessageItemDataRole.MessageRole)
|
||||
if proxy_index := self.messages_proxy_model.index(i, 0, parent):
|
||||
source_index = self.messages_proxy_model.mapToSource(proxy_index)
|
||||
message_item = self.messages_model.itemFromIndex(source_index)
|
||||
message: gotify.GotifyMessageModel = message_item.data(
|
||||
MessageItemDataRole.MessageRole
|
||||
)
|
||||
|
||||
application_item = self.application_model.itemFromId(message.appid)
|
||||
|
||||
message_widget = MessageWidget(self.listView_messages, message_item, icon=application_item.icon())
|
||||
message_widget = MessageWidget(
|
||||
self.listView_messages, message_item, icon=application_item.icon()
|
||||
)
|
||||
message_widget.deletion_requested.connect(self.delete_message.emit)
|
||||
message_widget.image_popup.connect(self.image_popup.emit)
|
||||
|
||||
self.listView_messages.setIndexWidget(index, message_widget)
|
||||
self.listView_messages.setIndexWidget(proxy_index, message_widget)
|
||||
|
||||
def redisplay_message_widgets(self):
|
||||
# Clear existing widgets
|
||||
for row in range(self.messages_proxy_model.rowCount()):
|
||||
index = self.messages_proxy_model.index(row, 0)
|
||||
self.listView_messages.setIndexWidget(index, None)
|
||||
# Redisplay for current visible rows
|
||||
self.display_message_widgets(
|
||||
QtCore.QModelIndex(), 0, self.messages_proxy_model.rowCount() - 1
|
||||
)
|
||||
|
||||
def currentApplicationIndex(self) -> QtCore.QModelIndex:
|
||||
return self.listView_applications.selectionModel().currentIndex()
|
||||
@@ -123,13 +178,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
def application_selection_changed_callback(
|
||||
self, current: QtCore.QModelIndex, previous: QtCore.QModelIndex
|
||||
):
|
||||
if item := self.application_model.itemFromIndex(self.application_proxy_model.mapToSource(current)):
|
||||
if item := self.application_model.itemFromIndex(
|
||||
self.application_proxy_model.mapToSource(current)
|
||||
):
|
||||
self.label_application.setText(item.text())
|
||||
self.application_selection_changed.emit(item)
|
||||
|
||||
def delete_all_callback(self):
|
||||
if (
|
||||
self.messages_model.rowCount() == 0
|
||||
self.messages_proxy_model.rowCount() == 0
|
||||
or QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Are you sure?",
|
||||
@@ -143,7 +200,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
return
|
||||
|
||||
index = self.currentApplicationIndex()
|
||||
if item := self.application_model.itemFromIndex(self.application_proxy_model.mapToSource(index)):
|
||||
if item := self.application_model.itemFromIndex(
|
||||
self.application_proxy_model.mapToSource(index)
|
||||
):
|
||||
self.delete_all.emit(item)
|
||||
|
||||
def disable_applications(self):
|
||||
@@ -152,7 +211,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
def enable_applications(self):
|
||||
self.listView_applications.setEnabled(True)
|
||||
self.listView_applications.setCurrentIndex(self.application_proxy_model.index(0, 0))
|
||||
self.listView_applications.setCurrentIndex(
|
||||
self.application_proxy_model.index(0, 0)
|
||||
)
|
||||
|
||||
def disable_buttons(self):
|
||||
self.pb_delete_all.setDisabled(True)
|
||||
@@ -177,7 +238,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.pb_refresh.clicked.connect(self.refresh.emit)
|
||||
self.pb_delete_all.clicked.connect(self.delete_all_callback)
|
||||
|
||||
self.listView_applications.selectionModel().currentChanged.connect(self.application_selection_changed_callback)
|
||||
self.listView_applications.selectionModel().currentChanged.connect(
|
||||
self.application_selection_changed_callback
|
||||
)
|
||||
|
||||
def store_state(self):
|
||||
settings.setValue("MainWindow/geometry", self.saveGeometry())
|
||||
@@ -202,3 +265,27 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.activated.emit()
|
||||
|
||||
return super().eventFilter(object, event)
|
||||
|
||||
def on_priority_button_toggled(self, checked):
|
||||
priorities = {10} # Critical always included
|
||||
if self.pb_low.isChecked():
|
||||
priorities.update({0, 1, 2, 3})
|
||||
if self.pb_normal.isChecked():
|
||||
priorities.update({4, 5, 6, 7, 8})
|
||||
if self.pb_high.isChecked():
|
||||
priorities.add(9)
|
||||
self.priority_filter_changed.emit(priorities)
|
||||
|
||||
def on_remove_filters_clicked(self):
|
||||
# Reset priority buttons
|
||||
self.pb_low.setChecked(True)
|
||||
self.pb_normal.setChecked(True)
|
||||
self.pb_high.setChecked(True)
|
||||
# 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)
|
||||
|
||||
145
gotify_tray/gui/widgets/PersistentNotification.py
Normal file
145
gotify_tray/gui/widgets/PersistentNotification.py
Normal file
@@ -0,0 +1,145 @@
|
||||
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)
|
||||
@@ -41,29 +41,51 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.link_callbacks()
|
||||
|
||||
def initUI(self):
|
||||
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(False)
|
||||
self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Apply
|
||||
).setEnabled(False)
|
||||
|
||||
# Notifications
|
||||
self.spin_priority.setValue(settings.value("tray/notifications/priority", type=int))
|
||||
self.spin_priority.setValue(
|
||||
settings.value("tray/notifications/priority", type=int)
|
||||
)
|
||||
|
||||
self.spin_duration.setValue(settings.value("tray/notifications/duration_ms", type=int))
|
||||
self.spin_duration.setValue(
|
||||
settings.value("tray/notifications/duration_ms", type=int)
|
||||
)
|
||||
if platform.system() == "Windows":
|
||||
# The notification duration setting is ignored by windows
|
||||
self.label_notification_duration.hide()
|
||||
self.spin_duration.hide()
|
||||
self.label_notification_duration_ms.hide()
|
||||
|
||||
self.cb_notify.setChecked(settings.value("message/check_missed/notify", type=bool))
|
||||
self.cb_notify.setChecked(
|
||||
settings.value("message/check_missed/notify", type=bool)
|
||||
)
|
||||
|
||||
self.cb_notification_click.setChecked(settings.value("tray/notifications/click", type=bool))
|
||||
self.cb_notification_click.setChecked(
|
||||
settings.value("tray/notifications/click", type=bool)
|
||||
)
|
||||
|
||||
self.cb_tray_icon_unread.setChecked(settings.value("tray/icon/unread", type=bool))
|
||||
self.cb_tray_icon_unread.setChecked(
|
||||
settings.value("tray/icon/unread", type=bool)
|
||||
)
|
||||
|
||||
self.cb_priority10_persistent.setChecked(
|
||||
settings.value("tray/notifications/priority10_persistent", type=bool)
|
||||
)
|
||||
|
||||
# Interface
|
||||
self.cb_priority_colors.setChecked(settings.value("MessageWidget/priority_color", type=bool))
|
||||
self.cb_image_urls.setChecked(settings.value("MessageWidget/image_urls", type=bool))
|
||||
self.cb_priority_colors.setChecked(
|
||||
settings.value("MessageWidget/priority_color", type=bool)
|
||||
)
|
||||
self.cb_image_urls.setChecked(
|
||||
settings.value("MessageWidget/image_urls", type=bool)
|
||||
)
|
||||
self.cb_locale.setChecked(settings.value("locale", type=bool))
|
||||
self.cb_sort_applications.setChecked(settings.value("ApplicationModel/sort", type=bool))
|
||||
self.cb_sort_applications.setChecked(
|
||||
settings.value("ApplicationModel/sort", type=bool)
|
||||
)
|
||||
|
||||
# Logging
|
||||
self.combo_logging.addItems(
|
||||
@@ -81,13 +103,17 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.add_message_widget()
|
||||
|
||||
# Advanced
|
||||
self.groupbox_image_popup.setChecked(settings.value("ImagePopup/enabled", type=bool))
|
||||
self.groupbox_image_popup.setChecked(
|
||||
settings.value("ImagePopup/enabled", type=bool)
|
||||
)
|
||||
self.spin_popup_w.setValue(settings.value("ImagePopup/w", type=int))
|
||||
self.spin_popup_h.setValue(settings.value("ImagePopup/h", type=int))
|
||||
self.label_cache.setText("0 MB")
|
||||
self.compute_cache_size()
|
||||
self.groupbox_watchdog.setChecked(settings.value("watchdog/enabled", type=bool))
|
||||
self.spin_watchdog_interval.setValue(settings.value("watchdog/interval/s", type=int))
|
||||
self.spin_watchdog_interval.setValue(
|
||||
settings.value("watchdog/interval/s", type=int)
|
||||
)
|
||||
|
||||
self.label_app_version.setText(__version__)
|
||||
self.label_qt_version.setText(QtCore.QT_VERSION_STR)
|
||||
@@ -113,18 +139,18 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
def compute_cache_size(self):
|
||||
self.cache_size_task = CacheSizeTask()
|
||||
self.cache_size_task.size.connect(lambda size: self.label_cache.setText(f"{round(size/1e6, 1)} MB"))
|
||||
self.cache_size_task.size.connect(
|
||||
lambda size: self.label_cache.setText(f"{round(size / 1e6, 1)} MB")
|
||||
)
|
||||
self.cache_size_task.start()
|
||||
|
||||
def set_value(self, key: str, value: Any, widget: QtWidgets.QWidget):
|
||||
"""Set a Settings value, only if the widget's value_changed attribute has been set
|
||||
"""
|
||||
"""Set a Settings value, only if the widget's value_changed attribute has been set"""
|
||||
if hasattr(widget, "value_changed"):
|
||||
settings.setValue(key, value)
|
||||
|
||||
def connect_signal(self, signal: QtCore.pyqtBoundSignal, widget: QtWidgets.QWidget):
|
||||
"""Connect to a signal and set the value_changed attribute for a widget on trigger
|
||||
"""
|
||||
"""Connect to a signal and set the value_changed attribute for a widget on trigger"""
|
||||
signal.connect(lambda *args: self.setting_changed_callback(widget))
|
||||
|
||||
def change_server_info_callback(self):
|
||||
@@ -132,13 +158,17 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
def setting_changed_callback(self, widget: QtWidgets.QWidget):
|
||||
self.settings_changed = True
|
||||
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(True)
|
||||
self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Apply
|
||||
).setEnabled(True)
|
||||
setattr(widget, "value_changed", True)
|
||||
|
||||
def change_font_callback(self, name: str):
|
||||
label: QtWidgets.QLabel = getattr(self.message_widget, "label_" + name)
|
||||
|
||||
font, accepted = QtWidgets.QFontDialog.getFont(label.font(), self, f"Select a {name} font")
|
||||
font, accepted = QtWidgets.QFontDialog.getFont(
|
||||
label.font(), self, f"Select a {name} font"
|
||||
)
|
||||
|
||||
if accepted:
|
||||
self.setting_changed_callback(label)
|
||||
@@ -146,7 +176,10 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
def export_callback(self):
|
||||
fname = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self, "Export Settings", settings.value("export/path", type=str), "*",
|
||||
self,
|
||||
"Export Settings",
|
||||
settings.value("export/path", type=str),
|
||||
"*",
|
||||
)[0]
|
||||
if fname and os.path.exists(os.path.dirname(fname)):
|
||||
self.export_settings_task = ExportSettingsTask(fname)
|
||||
@@ -162,7 +195,10 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
def import_callback(self):
|
||||
fname = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, "Import Settings", settings.value("export/path", type=str), "*",
|
||||
self,
|
||||
"Import Settings",
|
||||
settings.value("export/path", type=str),
|
||||
"*",
|
||||
)[0]
|
||||
if fname and os.path.exists(fname):
|
||||
self.import_settings_task = ImportSettingsTask(fname)
|
||||
@@ -202,60 +238,130 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.label_cache.setText("0 MB")
|
||||
|
||||
def link_callbacks(self):
|
||||
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self.apply_settings)
|
||||
self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Apply
|
||||
).clicked.connect(self.apply_settings)
|
||||
|
||||
# Notifications
|
||||
self.connect_signal(self.spin_priority.valueChanged, self.spin_priority)
|
||||
self.connect_signal(self.spin_duration.valueChanged, self.spin_duration)
|
||||
self.connect_signal(self.cb_notify.stateChanged, self.cb_notify)
|
||||
self.connect_signal(self.cb_notification_click.stateChanged, self.cb_notification_click)
|
||||
self.connect_signal(self.cb_tray_icon_unread.stateChanged, self.cb_tray_icon_unread)
|
||||
self.connect_signal(
|
||||
self.cb_notification_click.stateChanged, self.cb_notification_click
|
||||
)
|
||||
self.connect_signal(
|
||||
self.cb_tray_icon_unread.stateChanged, self.cb_tray_icon_unread
|
||||
)
|
||||
self.connect_signal(
|
||||
self.cb_priority10_persistent.stateChanged, self.cb_priority10_persistent
|
||||
)
|
||||
|
||||
# Interface
|
||||
self.connect_signal(self.cb_priority_colors.stateChanged, self.cb_priority_colors)
|
||||
self.connect_signal(
|
||||
self.cb_priority_colors.stateChanged, self.cb_priority_colors
|
||||
)
|
||||
self.connect_signal(self.cb_image_urls.stateChanged, self.cb_image_urls)
|
||||
self.connect_signal(self.cb_locale.stateChanged, self.cb_locale)
|
||||
self.connect_signal(self.cb_sort_applications.stateChanged, self.cb_sort_applications)
|
||||
self.connect_signal(
|
||||
self.cb_sort_applications.stateChanged, self.cb_sort_applications
|
||||
)
|
||||
|
||||
# Server info
|
||||
self.pb_change_server_info.clicked.connect(self.change_server_info_callback)
|
||||
|
||||
# Logging
|
||||
self.connect_signal(self.combo_logging.currentTextChanged, self.combo_logging)
|
||||
self.pb_open_log.clicked.connect(lambda: open_file(logger.root.handlers[0].baseFilename))
|
||||
self.pb_open_log.clicked.connect(
|
||||
lambda: open_file(logger.root.handlers[0].baseFilename)
|
||||
)
|
||||
|
||||
# Fonts
|
||||
self.pb_reset_fonts.clicked.connect(self.reset_fonts_callback)
|
||||
|
||||
self.pb_font_message_title.clicked.connect(lambda: self.change_font_callback("title"))
|
||||
self.pb_font_message_date.clicked.connect(lambda: self.change_font_callback("date"))
|
||||
self.pb_font_message_content.clicked.connect(lambda: self.change_font_callback("message"))
|
||||
self.pb_font_message_title.clicked.connect(
|
||||
lambda: self.change_font_callback("title")
|
||||
)
|
||||
self.pb_font_message_date.clicked.connect(
|
||||
lambda: self.change_font_callback("date")
|
||||
)
|
||||
self.pb_font_message_content.clicked.connect(
|
||||
lambda: self.change_font_callback("message")
|
||||
)
|
||||
|
||||
# Advanced
|
||||
self.pb_export.clicked.connect(self.export_callback)
|
||||
self.pb_import.clicked.connect(self.import_callback)
|
||||
self.pb_reset.clicked.connect(self.reset_callback)
|
||||
self.connect_signal(self.groupbox_image_popup.toggled, self.groupbox_image_popup)
|
||||
self.connect_signal(
|
||||
self.groupbox_image_popup.toggled, self.groupbox_image_popup
|
||||
)
|
||||
self.connect_signal(self.spin_popup_w.valueChanged, self.spin_popup_w)
|
||||
self.connect_signal(self.spin_popup_h.valueChanged, self.spin_popup_h)
|
||||
self.pb_clear_cache.clicked.connect(self.clear_cache_callback)
|
||||
self.pb_open_cache_dir.clicked.connect(lambda: open_file(Cache().directory()))
|
||||
self.connect_signal(self.groupbox_watchdog.toggled, self.groupbox_watchdog)
|
||||
self.connect_signal(self.spin_watchdog_interval.valueChanged, self.spin_watchdog_interval)
|
||||
self.connect_signal(
|
||||
self.spin_watchdog_interval.valueChanged, self.spin_watchdog_interval
|
||||
)
|
||||
|
||||
def apply_settings(self):
|
||||
# Priority
|
||||
self.set_value("tray/notifications/priority", self.spin_priority.value(), self.spin_priority)
|
||||
self.set_value("tray/notifications/duration_ms", self.spin_duration.value(), self.spin_duration)
|
||||
self.set_value("message/check_missed/notify", self.cb_notify.isChecked(), self.cb_notify)
|
||||
self.set_value("tray/notifications/click", self.cb_notification_click.isChecked(), self.cb_notification_click)
|
||||
self.set_value("tray/icon/unread", self.cb_tray_icon_unread.isChecked(), self.cb_tray_icon_unread)
|
||||
self.set_value(
|
||||
"tray/notifications/priority",
|
||||
self.spin_priority.value(),
|
||||
self.spin_priority,
|
||||
)
|
||||
self.set_value(
|
||||
"tray/notifications/duration_ms",
|
||||
self.spin_duration.value(),
|
||||
self.spin_duration,
|
||||
)
|
||||
self.set_value(
|
||||
"message/check_missed/notify", self.cb_notify.isChecked(), self.cb_notify
|
||||
)
|
||||
self.set_value(
|
||||
"tray/notifications/click",
|
||||
self.cb_notification_click.isChecked(),
|
||||
self.cb_notification_click,
|
||||
)
|
||||
self.set_value(
|
||||
"tray/icon/unread",
|
||||
self.cb_tray_icon_unread.isChecked(),
|
||||
self.cb_tray_icon_unread,
|
||||
)
|
||||
self.set_value(
|
||||
"tray/notifications/priority10_persistent",
|
||||
self.cb_priority10_persistent.isChecked(),
|
||||
self.cb_priority10_persistent,
|
||||
)
|
||||
|
||||
# Interface
|
||||
self.set_value("MessageWidget/priority_color", self.cb_priority_colors.isChecked(), self.cb_priority_colors)
|
||||
self.set_value("MessageWidget/image_urls", self.cb_image_urls.isChecked(), self.cb_image_urls)
|
||||
self.set_value(
|
||||
"tray/notifications/priority10_persistent",
|
||||
True, # Always persistent for priority 10
|
||||
None,
|
||||
)
|
||||
self.set_value(
|
||||
"tray/notifications/sound_only_priority10",
|
||||
False, # Not sound only
|
||||
None,
|
||||
)
|
||||
self.set_value(
|
||||
"MessageWidget/priority_color",
|
||||
self.cb_priority_colors.isChecked(),
|
||||
self.cb_priority_colors,
|
||||
)
|
||||
self.set_value(
|
||||
"MessageWidget/image_urls",
|
||||
self.cb_image_urls.isChecked(),
|
||||
self.cb_image_urls,
|
||||
)
|
||||
self.set_value("locale", self.cb_locale.isChecked(), self.cb_locale)
|
||||
self.set_value("ApplicationModel/sort", self.cb_sort_applications.isChecked(), self.cb_sort_applications)
|
||||
self.set_value(
|
||||
"ApplicationModel/sort",
|
||||
self.cb_sort_applications.isChecked(),
|
||||
self.cb_sort_applications,
|
||||
)
|
||||
|
||||
# Logging
|
||||
selected_level = self.combo_logging.currentText()
|
||||
@@ -267,18 +373,44 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
logger.setLevel(selected_level)
|
||||
|
||||
# Fonts
|
||||
self.set_value("MessageWidget/font/title", self.message_widget.label_title.font().toString(), self.message_widget.label_title)
|
||||
self.set_value("MessageWidget/font/date", self.message_widget.label_date.font().toString(), self.message_widget.label_date)
|
||||
self.set_value("MessageWidget/font/message", self.message_widget.label_message.font().toString(), self.message_widget.label_message)
|
||||
self.set_value(
|
||||
"MessageWidget/font/title",
|
||||
self.message_widget.label_title.font().toString(),
|
||||
self.message_widget.label_title,
|
||||
)
|
||||
self.set_value(
|
||||
"MessageWidget/font/date",
|
||||
self.message_widget.label_date.font().toString(),
|
||||
self.message_widget.label_date,
|
||||
)
|
||||
self.set_value(
|
||||
"MessageWidget/font/message",
|
||||
self.message_widget.label_message.font().toString(),
|
||||
self.message_widget.label_message,
|
||||
)
|
||||
|
||||
# Advanced
|
||||
self.set_value("ImagePopup/enabled", self.groupbox_image_popup.isChecked(), self.groupbox_image_popup)
|
||||
self.set_value(
|
||||
"ImagePopup/enabled",
|
||||
self.groupbox_image_popup.isChecked(),
|
||||
self.groupbox_image_popup,
|
||||
)
|
||||
self.set_value("ImagePopup/w", self.spin_popup_w.value(), self.spin_popup_w)
|
||||
self.set_value("ImagePopup/h", self.spin_popup_h.value(), self.spin_popup_h)
|
||||
self.set_value("watchdog/enabled", self.groupbox_watchdog.isChecked(), self.groupbox_watchdog)
|
||||
self.set_value("watchdog/interval/s", self.spin_watchdog_interval.value(), self.spin_watchdog_interval)
|
||||
self.set_value(
|
||||
"watchdog/enabled",
|
||||
self.groupbox_watchdog.isChecked(),
|
||||
self.groupbox_watchdog,
|
||||
)
|
||||
self.set_value(
|
||||
"watchdog/interval/s",
|
||||
self.spin_watchdog_interval.value(),
|
||||
self.spin_watchdog_interval,
|
||||
)
|
||||
|
||||
self.settings_changed = False
|
||||
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(False)
|
||||
self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Apply
|
||||
).setEnabled(False)
|
||||
|
||||
self.changes_applied = True
|
||||
|
||||
8
pyrightconfig.json
Normal file
8
pyrightconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"reportOptionalMemberAccess": false,
|
||||
"reportAttributeAccessIssue": false,
|
||||
"reportIncompatibleMethodOverride": false,
|
||||
"reportArgumentType": false,
|
||||
"reportAssignmentType": false,
|
||||
"reportReturnType": false
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
requests==2.32.3
|
||||
pyqt6==6.7.1
|
||||
requests
|
||||
pyqt6>=6.7.1
|
||||
pyqt6-qt6
|
||||
|
||||
5
setup.py
5
setup.py
@@ -15,7 +15,7 @@ with open("version.txt", "r") as f:
|
||||
|
||||
# What packages are required for this module to be executed?
|
||||
REQUIRED = [
|
||||
'requests==2.32.3', 'pyqt6==6.7.1'
|
||||
'requests', 'pyqt6>=6.7.1'
|
||||
]
|
||||
|
||||
# What packages are optional?
|
||||
@@ -86,6 +86,7 @@ setup(
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12'
|
||||
'Programming Language :: Python :: 3.12',
|
||||
'Programming Language :: Python :: 3.13'
|
||||
]
|
||||
)
|
||||
|
||||
@@ -6,8 +6,8 @@ VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
|
||||
# Set not needed items to zero 0.
|
||||
filevers=(0, 5, 2, 0),
|
||||
prodvers=(0, 5, 2, 0),
|
||||
filevers=(0, 5, 3, 0),
|
||||
prodvers=(0, 5, 3, 0),
|
||||
# Contains a bitmask that specifies the valid bits 'flags'r
|
||||
mask=0x3F,
|
||||
# Contains a bitmask that specifies the Boolean attributes of the file.
|
||||
@@ -34,12 +34,12 @@ VSVersionInfo(
|
||||
StringStruct(u"Comments", u"Gotify Tray"),
|
||||
StringStruct(u"CompanyName", u""),
|
||||
StringStruct(u"FileDescription", u"Gotifiy Tray"),
|
||||
StringStruct(u"FileVersion", u"0.5.2"),
|
||||
StringStruct(u"FileVersion", u"0.5.3"),
|
||||
StringStruct(u"InternalName", u"gotify-tray"),
|
||||
StringStruct(u"LegalCopyright", u""),
|
||||
StringStruct(u"OriginalFilename", u"gotify-tray.exe"),
|
||||
StringStruct(u"ProductName", u"Gotify Tray"),
|
||||
StringStruct(u"ProductVersion", u"0.5.2"),
|
||||
StringStruct(u"ProductVersion", u"0.5.3"),
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.5.2
|
||||
0.5.3
|
||||
Reference in New Issue
Block a user