diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 558cdf6..5d6f1d6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -93,7 +93,7 @@ jobs:
run: |
make build-macos
brew install create-dmg
- create-dmg --volname "Gotify Tray" --app-drop-link 0 0 --no-internet-enable "gotify-tray.dmg" "./dist/Gotify-Tray.app"
+ 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@v2
with:
diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
index 3f03028..4f7020d 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -74,7 +74,7 @@ jobs:
run: |
make build-macos
brew install create-dmg
- create-dmg --volname "Gotify Tray" --app-drop-link 0 0 --no-internet-enable "gotify-tray.dmg" "./dist/Gotify-Tray.app"
+ 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@v2
with:
diff --git a/BUILDING.md b/BUILDING.md
new file mode 100644
index 0000000..0cf9c92
--- /dev/null
+++ b/BUILDING.md
@@ -0,0 +1,64 @@
+## Get the source and install the requirements:
+
+```shell
+$ git clone https://github.com/seird/gotify-tray.git
+$ cd gotify-tray
+$ pip install -r requirements.txt
+```
+
+
+### Run from source
+
+```shell
+$ python -m gotify_tray
+```
+
+### Create a pyinstaller executable
+
+```shell
+$ pip install pyinstaller
+$ pyinstaller gotify-tray.spec
+```
+An executable is created at `dist/gotify-tray/`.
+
+### Create a macos .app
+
+```shell
+$ pip install pyinstaller Pillow
+$ pyinstaller gotify-tray.spec
+```
+
+### Inno setup (Windows)
+
+Create an installer for windows with inno setup from pyinstaller output:
+
+```shell
+$ iscc gotify-tray.iss
+```
+
+### Create and install a pip package
+
+- Create the pip package:
+ ```shell
+ $ python -m build
+ ```
+
+- Install the pip package:
+ ```shell
+ $ pip install dist/gotify_tray-0.1.14-py3-none-any.whl
+ ```
+
+- Launch:
+ ```shell
+ $ gotify-tray
+ ```
+
+### Create a deb package
+
+```shell
+$ make build
+
+# or install
+
+$ sudo make install
+```
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 6630b98..ed766d7 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ build-macos: clean
pip install -r requirements.txt
pip install pyinstaller
pip install Pillow
- pyinstaller gotify-tray-macos.spec
+ pyinstaller gotify-tray.spec
install: build
sudo dpkg -i dist/gotify-tray_amd64.deb
diff --git a/README.md b/README.md
index ea06673..0e3d7ad 100644
--- a/README.md
+++ b/README.md
@@ -4,15 +4,21 @@
A tray notification application for receiving messages from a [Gotify server](https://github.com/gotify/server).
-## Download
+## Getting started
-[Download the latest release.](https://github.com/seird/gotify-tray/releases/latest)
+- [Download the latest release.](https://github.com/seird/gotify-tray/releases/latest)
-or, install via pip:
-```
-$ pip install gotify-tray
-```
+- or, install via pip:
+ ```shell
+ $ pip install gotify-tray
+ ```
+
+- or, run from source:
+ ```shell
+ $ pip install -r requirements.txt
+ $ python -m gotify_tray
+ ```
## Features
@@ -41,77 +47,11 @@ Windows 10 | KDE

-## Manual Installation
+## Build instructions
-Get the source and install the requirements:
-
-```
-$ git clone https://github.com/seird/gotify-tray.git
-$ cd gotify-tray
-$ pip install -r requirements.txt
-```
-
-
-### Run from source
-
-```
-$ python -m gotify_tray
-```
-
-### Create a pyinstaller executable
-
-```
-$ pip install pyinstaller
-$ pyinstaller gotify-tray.spec
-```
-An executable is created at `dist/gotify-tray/`.
-
-### Create a macos .app
-
-```
-$ pip install pyinstaller Pillow
-$ pyinstaller gotify-tray-macos.spec
-```
-
-### Inno setup (Windows)
-
-Create an installer for windows with inno setup from pyinstaller output:
-
-```
-$ iscc gotify-tray.iss
-```
-
-### Create and install a pip package
-
-- Create the pip package:
- ```
- $ python -m build
- ```
-
-- Install the pip package:
- ```
- $ pip install dist/gotify_tray-0.1.14-py3-none-any.whl
- ```
-
-- Launch:
- ```
- $ gotify-tray
- ```
-
-### Create a deb package
-
-```
-$ make build
-
-# or install
-
-$ sudo make install
-```
+See [BUILDING](BUILDING.md).
## Requirements
- python >=3.8
-- PyQt6
-- requests
-- websocket-client
diff --git a/debian/DEBIAN/control b/debian/DEBIAN/control
index aeec92e..c7df3f9 100644
--- a/debian/DEBIAN/control
+++ b/debian/DEBIAN/control
@@ -2,5 +2,5 @@ Package: gotify-tray
Version: 0.1.14
Architecture: amd64
Maintainer: k.dries@protonmail.com
-Description: gotify-tray
+Description: Gotify Tray
A tray notification application for receiving messages from a Gotify server.
diff --git a/debian/usr/share/applications/gotifytray.desktop b/debian/usr/share/applications/gotifytray.desktop
index 81ad25b..c98aaff 100644
--- a/debian/usr/share/applications/gotifytray.desktop
+++ b/debian/usr/share/applications/gotifytray.desktop
@@ -1,5 +1,5 @@
[Desktop Entry]
-Name=gotify-tray
+Name=Gotify Tray
Comment=A tray notification application for receiving messages from a Gotify server.
Path=/usr/lib/gotify-tray
Exec=/usr/lib/gotify-tray/gotify-tray
diff --git a/gotify-tray-macos.spec b/gotify-tray-macos.spec
deleted file mode 100644
index 7821b50..0000000
--- a/gotify-tray-macos.spec
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- mode: python -*-
-
-block_cipher = None
-
-a = Analysis(['gotify_tray/__main__.py'],
- pathex=[os.getcwd()],
- binaries=[],
- datas=[('gotify_tray/gui/images', 'gotify_tray/gui/images')],
- hiddenimports=[],
- hookspath=[],
- runtime_hooks=[],
- excludes=[],
- win_no_prefer_redirects=False,
- win_private_assemblies=False,
- cipher=block_cipher,
- noarchive=False)
-pyz = PYZ(a.pure, a.zipped_data,
- cipher=block_cipher)
-exe = EXE(pyz,
- a.scripts,
- [],
- exclude_binaries=True,
- name='gotify-tray',
- debug=False,
- bootloader_ignore_signals=False,
- strip=False,
- upx=True,
- console=False,
- version='version.py',
- icon='logo.ico')
-coll = COLLECT(exe,
- a.binaries,
- a.zipfiles,
- a.datas,
- strip=False,
- upx=True,
- name='gotify-tray')
-app = BUNDLE(coll,
- name='Gotify-Tray.app',
- icon='logo.ico',
- bundle_identifier=None)
diff --git a/gotify-tray.spec b/gotify-tray.spec
index fd27d06..60b6a66 100644
--- a/gotify-tray.spec
+++ b/gotify-tray.spec
@@ -1,5 +1,7 @@
# -*- mode: python -*-
+import platform
+
block_cipher = None
a = Analysis(['gotify_tray/__main__.py'],
@@ -35,3 +37,9 @@ coll = COLLECT(exe,
strip=False,
upx=True,
name='gotify-tray')
+
+if platform.system() == "Darwin":
+ app = BUNDLE(coll,
+ name='Gotify Tray.app',
+ icon='logo.ico',
+ bundle_identifier=None)
diff --git a/gotify_tray/database/default_settings.py b/gotify_tray/database/default_settings.py
index 6ce350d..2c8b65a 100644
--- a/gotify_tray/database/default_settings.py
+++ b/gotify_tray/database/default_settings.py
@@ -14,9 +14,14 @@ DEFAULT_SETTINGS = {
"tray/notifications/priority": 5,
"tray/notifications/duration_ms": 5000,
"tray/notifications/icon/show": True,
+ "tray/notifications/click": True,
"watchdog/interval/s": 60,
"MessageWidget/image/size": 33,
"MainWindow/label/size": 25,
"MainWindow/button/size": 33,
"MainWindow/application/icon/size": 40,
+ "ImagePopup/enabled": False,
+ "ImagePopup/extensions": [".jpg", ".jpeg", ".png", ".svg"],
+ "ImagePopup/w": 400,
+ "ImagePopup/h": 400,
}
diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py
index a3107c9..d5651b6 100644
--- a/gotify_tray/gui/MainApplication.py
+++ b/gotify_tray/gui/MainApplication.py
@@ -31,7 +31,7 @@ from .models import (
MessagesModelItem,
MessageItemDataRole,
)
-from .widgets import MainWindow, SettingsDialog, Tray
+from .widgets import ImagePopup, MainWindow, SettingsDialog, Tray
settings = Settings("gotify-tray")
@@ -309,6 +309,17 @@ class MainApplication(QtWidgets.QApplication):
self.messages_model.clear()
+ def image_popup_callback(self, link: str, pos: QtCore.QPoint):
+ if filename := self.downloader.get_filename(link):
+ self.image_popup = ImagePopup(filename, pos, link)
+ self.image_popup.show()
+ else:
+ logger.warning(f"Image {link} is not in the cache")
+
+ def main_window_hidden_callback(self):
+ if image_popup := getattr(self, "image_popup", None):
+ image_popup.close()
+
def refresh_callback(self):
# Manual refresh -> also reset the image cache
Cache().clear()
@@ -335,6 +346,10 @@ class MainApplication(QtWidgets.QApplication):
closed_callback=self.listener_closed_callback,
)
+ def tray_notification_clicked_callback(self):
+ if settings.value("tray/notifications/click", type=bool):
+ self.main_window.bring_to_front()
+
def tray_activated_callback(
self, reason: QtWidgets.QSystemTrayIcon.ActivationReason
):
@@ -349,7 +364,7 @@ class MainApplication(QtWidgets.QApplication):
self.tray.actionSettings.triggered.connect(self.settings_callback)
self.tray.actionShowWindow.triggered.connect(self.main_window.bring_to_front)
self.tray.actionReconnect.triggered.connect(self.reconnect_callback)
- self.tray.messageClicked.connect(self.main_window.bring_to_front)
+ self.tray.messageClicked.connect(self.tray_notification_clicked_callback)
self.tray.activated.connect(self.tray_activated_callback)
self.main_window.refresh.connect(self.refresh_callback)
@@ -358,6 +373,8 @@ class MainApplication(QtWidgets.QApplication):
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.watchdog.closed.connect(lambda: self.listener_closed_callback(None, None))
diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py
index 3a0f95c..fe146ac 100644
--- a/gotify_tray/gui/designs/widget_settings.py
+++ b/gotify_tray/gui/designs/widget_settings.py
@@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
- Dialog.resize(384, 274)
+ Dialog.resize(384, 285)
self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setObjectName("gridLayout")
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
@@ -30,32 +30,35 @@ class Ui_Dialog(object):
self.groupBox_notifications.setObjectName("groupBox_notifications")
self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_notifications)
self.gridLayout_4.setObjectName("gridLayout_4")
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1)
- self.label_notification_duration_ms = QtWidgets.QLabel(self.groupBox_notifications)
- self.label_notification_duration_ms.setObjectName("label_notification_duration_ms")
- self.gridLayout_4.addWidget(self.label_notification_duration_ms, 1, 2, 1, 1)
- self.label_notification_priority = QtWidgets.QLabel(self.groupBox_notifications)
- self.label_notification_priority.setObjectName("label_notification_priority")
- self.gridLayout_4.addWidget(self.label_notification_priority, 0, 0, 1, 1)
+ self.cb_notify = QtWidgets.QCheckBox(self.groupBox_notifications)
+ self.cb_notify.setObjectName("cb_notify")
+ self.gridLayout_4.addWidget(self.cb_notify, 2, 0, 1, 3)
self.label_notification_duration = QtWidgets.QLabel(self.groupBox_notifications)
self.label_notification_duration.setObjectName("label_notification_duration")
self.gridLayout_4.addWidget(self.label_notification_duration, 1, 0, 1, 1)
- self.spin_duration = QtWidgets.QSpinBox(self.groupBox_notifications)
- self.spin_duration.setMinimum(500)
- self.spin_duration.setMaximum(30000)
- self.spin_duration.setSingleStep(100)
- self.spin_duration.setObjectName("spin_duration")
- self.gridLayout_4.addWidget(self.spin_duration, 1, 1, 1, 1)
+ self.label_notification_duration_ms = QtWidgets.QLabel(self.groupBox_notifications)
+ self.label_notification_duration_ms.setObjectName("label_notification_duration_ms")
+ self.gridLayout_4.addWidget(self.label_notification_duration_ms, 1, 2, 1, 1)
self.spin_priority = QtWidgets.QSpinBox(self.groupBox_notifications)
self.spin_priority.setMinimum(1)
self.spin_priority.setMaximum(10)
self.spin_priority.setProperty("value", 5)
self.spin_priority.setObjectName("spin_priority")
self.gridLayout_4.addWidget(self.spin_priority, 0, 1, 1, 1)
- self.cb_notify = QtWidgets.QCheckBox(self.groupBox_notifications)
- self.cb_notify.setObjectName("cb_notify")
- self.gridLayout_4.addWidget(self.cb_notify, 2, 0, 1, 3)
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
+ self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1)
+ self.label_notification_priority = QtWidgets.QLabel(self.groupBox_notifications)
+ self.label_notification_priority.setObjectName("label_notification_priority")
+ self.gridLayout_4.addWidget(self.label_notification_priority, 0, 0, 1, 1)
+ self.spin_duration = QtWidgets.QSpinBox(self.groupBox_notifications)
+ self.spin_duration.setMinimum(500)
+ self.spin_duration.setMaximum(30000)
+ self.spin_duration.setSingleStep(100)
+ self.spin_duration.setObjectName("spin_duration")
+ self.gridLayout_4.addWidget(self.spin_duration, 1, 1, 1, 1)
+ self.cb_notification_click = QtWidgets.QCheckBox(self.groupBox_notifications)
+ self.cb_notification_click.setObjectName("cb_notification_click")
+ self.gridLayout_4.addWidget(self.cb_notification_click, 3, 0, 1, 3)
self.verticalLayout_4.addWidget(self.groupBox_notifications)
self.groupBox_server_info = QtWidgets.QGroupBox(self.tab_general)
self.groupBox_server_info.setObjectName("groupBox_server_info")
@@ -102,37 +105,66 @@ class Ui_Dialog(object):
self.verticalLayout.setObjectName("verticalLayout")
self.groupBox = QtWidgets.QGroupBox(self.tab_advanced)
self.groupBox.setObjectName("groupBox")
- self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox)
- self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.pb_export = QtWidgets.QPushButton(self.groupBox)
- self.pb_export.setObjectName("pb_export")
- self.verticalLayout_2.addWidget(self.pb_export)
- self.pb_import = QtWidgets.QPushButton(self.groupBox)
- self.pb_import.setObjectName("pb_import")
- self.verticalLayout_2.addWidget(self.pb_import)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.pb_reset = QtWidgets.QPushButton(self.groupBox)
self.pb_reset.setObjectName("pb_reset")
- self.verticalLayout_2.addWidget(self.pb_reset)
+ self.horizontalLayout_2.addWidget(self.pb_reset)
+ self.pb_import = QtWidgets.QPushButton(self.groupBox)
+ self.pb_import.setObjectName("pb_import")
+ self.horizontalLayout_2.addWidget(self.pb_import)
+ self.pb_export = QtWidgets.QPushButton(self.groupBox)
+ self.pb_export.setObjectName("pb_export")
+ self.horizontalLayout_2.addWidget(self.pb_export)
self.verticalLayout.addWidget(self.groupBox)
+ self.groupbox_image_popup = QtWidgets.QGroupBox(self.tab_advanced)
+ self.groupbox_image_popup.setCheckable(True)
+ self.groupbox_image_popup.setObjectName("groupbox_image_popup")
+ self.gridLayout_2 = QtWidgets.QGridLayout(self.groupbox_image_popup)
+ self.gridLayout_2.setObjectName("gridLayout_2")
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ self.label = QtWidgets.QLabel(self.groupbox_image_popup)
+ self.label.setObjectName("label")
+ self.horizontalLayout_4.addWidget(self.label)
+ self.spin_popup_w = QtWidgets.QSpinBox(self.groupbox_image_popup)
+ self.spin_popup_w.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.spin_popup_w.setMinimum(100)
+ self.spin_popup_w.setMaximum(10000)
+ self.spin_popup_w.setObjectName("spin_popup_w")
+ self.horizontalLayout_4.addWidget(self.spin_popup_w)
+ self.label_2 = QtWidgets.QLabel(self.groupbox_image_popup)
+ self.label_2.setObjectName("label_2")
+ self.horizontalLayout_4.addWidget(self.label_2)
+ self.spin_popup_h = QtWidgets.QSpinBox(self.groupbox_image_popup)
+ self.spin_popup_h.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.spin_popup_h.setMinimum(100)
+ self.spin_popup_h.setMaximum(10000)
+ self.spin_popup_h.setObjectName("spin_popup_h")
+ self.horizontalLayout_4.addWidget(self.spin_popup_h)
+ spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
+ self.horizontalLayout_4.addItem(spacerItem4)
+ self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 0, 1, 1)
+ self.verticalLayout.addWidget(self.groupbox_image_popup)
self.groupBox_logging = QtWidgets.QGroupBox(self.tab_advanced)
self.groupBox_logging.setObjectName("groupBox_logging")
self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_logging)
self.gridLayout_6.setObjectName("gridLayout_6")
- self.label_logging = QtWidgets.QLabel(self.groupBox_logging)
- self.label_logging.setObjectName("label_logging")
- self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1)
self.combo_logging = QtWidgets.QComboBox(self.groupBox_logging)
self.combo_logging.setObjectName("combo_logging")
self.gridLayout_6.addWidget(self.combo_logging, 0, 1, 1, 1)
+ spacerItem5 = QtWidgets.QSpacerItem(190, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
+ self.gridLayout_6.addItem(spacerItem5, 0, 3, 1, 1)
self.pb_open_log = QtWidgets.QPushButton(self.groupBox_logging)
self.pb_open_log.setMaximumSize(QtCore.QSize(30, 16777215))
self.pb_open_log.setObjectName("pb_open_log")
self.gridLayout_6.addWidget(self.pb_open_log, 0, 2, 1, 1)
- spacerItem4 = QtWidgets.QSpacerItem(190, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.gridLayout_6.addItem(spacerItem4, 0, 3, 1, 1)
+ self.label_logging = QtWidgets.QLabel(self.groupBox_logging)
+ self.label_logging.setObjectName("label_logging")
+ self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1)
self.verticalLayout.addWidget(self.groupBox_logging)
- spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout.addItem(spacerItem5)
+ spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+ self.verticalLayout.addItem(spacerItem6)
self.tabWidget.addTab(self.tab_advanced, "")
self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1)
@@ -153,10 +185,11 @@ class Ui_Dialog(object):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.groupBox_notifications.setTitle(_translate("Dialog", "Notifications"))
+ self.cb_notify.setText(_translate("Dialog", "Show a notification for missed messages after reconnecting"))
+ self.label_notification_duration.setText(_translate("Dialog", "Notification duration:"))
self.label_notification_duration_ms.setText(_translate("Dialog", "ms"))
self.label_notification_priority.setText(_translate("Dialog", "Minimum priority to show notifications:"))
- self.label_notification_duration.setText(_translate("Dialog", "Notification duration:"))
- self.cb_notify.setText(_translate("Dialog", "Show a notification for missed messages after reconnecting"))
+ self.cb_notification_click.setText(_translate("Dialog", "Clicking the notification pop-up opens the main window"))
self.groupBox_server_info.setTitle(_translate("Dialog", "Server info"))
self.pb_change_server_info.setText(_translate("Dialog", "Change server info"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_general), _translate("Dialog", "General"))
@@ -166,13 +199,20 @@ class Ui_Dialog(object):
self.pb_font_message_content.setText(_translate("Dialog", "Message"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_fonts), _translate("Dialog", "Fonts"))
self.groupBox.setTitle(_translate("Dialog", "Settings"))
- self.pb_export.setText(_translate("Dialog", "Export"))
- self.pb_import.setText(_translate("Dialog", "Import"))
self.pb_reset.setText(_translate("Dialog", "Reset"))
+ self.pb_import.setText(_translate("Dialog", "Import"))
+ self.pb_export.setText(_translate("Dialog", "Export"))
+ self.groupbox_image_popup.setTitle(_translate("Dialog", "Image pop-up for URLs"))
+ self.label.setToolTip(_translate("Dialog", "Maximum pop-up width"))
+ self.label.setText(_translate("Dialog", "Width"))
+ self.spin_popup_w.setToolTip(_translate("Dialog", "Maximum pop-up width"))
+ self.label_2.setToolTip(_translate("Dialog", "Maximum pop-up height"))
+ self.label_2.setText(_translate("Dialog", "Height"))
+ self.spin_popup_h.setToolTip(_translate("Dialog", "Maximum pop-up height"))
self.groupBox_logging.setTitle(_translate("Dialog", "Logging"))
- self.label_logging.setText(_translate("Dialog", "Level"))
self.pb_open_log.setToolTip(_translate("Dialog", "Open logfile"))
self.pb_open_log.setText(_translate("Dialog", "..."))
+ self.label_logging.setText(_translate("Dialog", "Level"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_advanced), _translate("Dialog", "Advanced"))
diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui
index 3f3579b..2b32ffa 100644
--- a/gotify_tray/gui/designs/widget_settings.ui
+++ b/gotify_tray/gui/designs/widget_settings.ui
@@ -7,7 +7,7 @@
0
0
384
- 274
+ 285
@@ -40,30 +40,10 @@
Notifications
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
+
-
+
- ms
-
-
-
- -
-
-
- Minimum priority to show notifications:
+ Show a notification for missed messages after reconnecting
@@ -74,16 +54,10 @@
- -
-
-
- 500
-
-
- 30000
-
-
- 100
+
-
+
+
+ ms
@@ -100,10 +74,43 @@
- -
-
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
- Show a notification for missed messages after reconnecting
+ Minimum priority to show notifications:
+
+
+
+ -
+
+
+ 500
+
+
+ 30000
+
+
+ 100
+
+
+
+ -
+
+
+ Clicking the notification pop-up opens the main window
@@ -233,11 +240,11 @@
Settings
-
+
-
-
+
- Export
+ Reset
@@ -249,31 +256,118 @@
-
-
+
- Reset
+ Export
+ -
+
+
+ Image pop-up for URLs
+
+
+ true
+
+
+
-
+
+
-
+
+
+ Maximum pop-up width
+
+
+ Width
+
+
+
+ -
+
+
+ Maximum pop-up width
+
+
+ Qt::AlignCenter
+
+
+ 100
+
+
+ 10000
+
+
+
+ -
+
+
+ Maximum pop-up height
+
+
+ Height
+
+
+
+ -
+
+
+ Maximum pop-up height
+
+
+ Qt::AlignCenter
+
+
+ 100
+
+
+ 10000
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
-
Logging
-
-
-
-
- Level
-
-
-
-
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 190
+ 20
+
+
+
+
-
@@ -290,18 +384,12 @@
- -
-
-
- Qt::Horizontal
+
-
+
+
+ Level
-
-
- 190
- 20
-
-
-
+
diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py
new file mode 100644
index 0000000..8850c6d
--- /dev/null
+++ b/gotify_tray/gui/widgets/ImagePopup.py
@@ -0,0 +1,65 @@
+import platform
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+from gotify_tray.database import Settings
+
+
+settings = Settings("gotify-tray")
+
+
+class ImagePopup(QtWidgets.QLabel):
+ def __init__(self, filename: str, pos: QtCore.QPoint, link: str = None):
+ """Create and show a pop-up image under the cursor
+
+ Args:
+ filename (str): The path to the image to display
+ pos (QtCore.QPoint): The location at which the image should be displayed
+ link (str, optional): The URL of the image. Defaults to None.
+ """
+ super(ImagePopup, self).__init__()
+ self.link = link
+
+ self.setWindowFlags(QtCore.Qt.WindowType.Popup)
+ self.installEventFilter(self)
+
+ # Prevent leaving the pop-up open when moving quickly out of the widget
+ self.popup_timer = QtCore.QTimer()
+ self.popup_timer.timeout.connect(self.check_mouse)
+
+ pixmap = QtGui.QPixmap(filename)
+ W = settings.value("ImagePopup/w", type=int)
+ H = settings.value("ImagePopup/h", type=int)
+ if pixmap.height() > H or pixmap.width() > W:
+ pixmap = pixmap.scaled(
+ W,
+ H,
+ aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio,
+ transformMode=QtCore.Qt.TransformationMode.SmoothTransformation,
+ )
+ self.setPixmap(pixmap)
+
+ self.move(pos - QtCore.QPoint(15, 15))
+
+ self.popup_timer.start(500)
+
+ def check_mouse(self):
+ if not self.underMouse():
+ self.close()
+
+ def close(self):
+ self.popup_timer.stop()
+ super(ImagePopup, self).close()
+
+ def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool:
+ if platform.system() != "Darwin" and event.type() == QtCore.QEvent.Type.Leave:
+ # Close the pop-up on mouse leave
+ self.close()
+ elif (
+ event.type() == QtCore.QEvent.Type.MouseButtonPress
+ and event.button() == QtCore.Qt.MouseButton.LeftButton
+ and self.link
+ ):
+ # Open the image URL on left click
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.link))
+
+ return super().eventFilter(object, event)
diff --git a/gotify_tray/gui/widgets/MainWindow.py b/gotify_tray/gui/widgets/MainWindow.py
index ad4b974..26ed8b3 100644
--- a/gotify_tray/gui/widgets/MainWindow.py
+++ b/gotify_tray/gui/widgets/MainWindow.py
@@ -21,6 +21,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
delete_all = QtCore.pyqtSignal(QtGui.QStandardItem)
delete_message = QtCore.pyqtSignal(MessagesModelItem)
application_selection_changed = QtCore.pyqtSignal(QtGui.QStandardItem)
+ image_popup = QtCore.pyqtSignal(str, QtCore.QPoint)
+ hidden = QtCore.pyqtSignal()
def __init__(
self, application_model: ApplicationModel, messages_model: MessagesModel
@@ -72,6 +74,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
font_title.fromString(s)
else:
font_title.setBold(True)
+ font_title.setPointSize(font_title.pointSize() + 2)
self.label_application.setFont(font_title)
# Set tooltips
@@ -102,6 +105,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.messages_model.indexFromItem(message_item), message_widget
)
message_widget.deletion_requested.connect(self.delete_message.emit)
+ message_widget.image_popup.connect(self.image_popup.emit)
def currentApplicationIndex(self) -> QtCore.QModelIndex:
return self.listView_applications.selectionModel().currentIndex()
@@ -172,3 +176,4 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def closeEvent(self, e: QtGui.QCloseEvent) -> None:
self.hide()
+ self.hidden.emit()
diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py
index db1cde6..3aab648 100644
--- a/gotify_tray/gui/widgets/MessageWidget.py
+++ b/gotify_tray/gui/widgets/MessageWidget.py
@@ -1,3 +1,5 @@
+import os
+
from PyQt6 import QtCore, QtGui, QtWidgets
from ..models.MessagesModel import MessageItemDataRole, MessagesModelItem
@@ -11,6 +13,7 @@ settings = Settings("gotify-tray")
class MessageWidget(QtWidgets.QWidget, Ui_Form):
deletion_requested = QtCore.pyqtSignal(MessagesModelItem)
+ image_popup = QtCore.pyqtSignal(str, QtCore.QPoint)
def __init__(self, message_item: MessagesModelItem, image_path: str = ""):
super(MessageWidget, self).__init__()
@@ -86,7 +89,17 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
self.label_date.setFont(font_date)
self.label_message.setFont(font_content)
+ def link_hovered_callback(self, link: str):
+ if not settings.value("ImagePopup/enabled", type=bool):
+ return
+
+ qurl = QtCore.QUrl(link)
+ _, ext = os.path.splitext(qurl.fileName())
+ if ext in settings.value("ImagePopup/extensions", type=list):
+ self.image_popup.emit(link, QtGui.QCursor.pos())
+
def link_callbacks(self):
self.pb_delete.clicked.connect(
lambda: self.deletion_requested.emit(self.message_item)
)
+ self.label_message.linkHovered.connect(self.link_hovered_callback)
diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py
index 0c98358..90ee6aa 100644
--- a/gotify_tray/gui/widgets/SettingsDialog.py
+++ b/gotify_tray/gui/widgets/SettingsDialog.py
@@ -1,13 +1,12 @@
import logging
import platform
import os
-import webbrowser
from gotify_tray.database import Settings
from gotify_tray.gotify import GotifyMessageModel
from gotify_tray.gui.models import MessagesModelItem
from . import MessageWidget
-from gotify_tray.utils import verify_server
+from gotify_tray.utils import verify_server, open_file
from gotify_tray.tasks import ExportSettingsTask, ImportSettingsTask
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -57,6 +56,10 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
settings.value("message/check_missed/notify", type=bool)
)
+ self.cb_notification_click.setChecked(
+ settings.value("tray/notifications/click", type=bool)
+ )
+
# Logging
self.combo_logging.addItems(
[
@@ -83,6 +86,11 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
)
self.layout_fonts_message.addWidget(self.message_widget)
+ # Advanced
+ 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))
+
def change_server_info_callback(self):
self.server_changed = verify_server(force_new=True, enable_import=False)
@@ -150,6 +158,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
self.spin_priority.valueChanged.connect(self.settings_changed_callback)
self.spin_duration.valueChanged.connect(self.settings_changed_callback)
self.cb_notify.stateChanged.connect(self.settings_changed_callback)
+ self.cb_notification_click.stateChanged.connect(self.settings_changed_callback)
# Server info
self.pb_change_server_info.clicked.connect(self.change_server_info_callback)
@@ -157,7 +166,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
# Logging
self.combo_logging.currentTextChanged.connect(self.settings_changed_callback)
self.pb_open_log.clicked.connect(
- lambda: webbrowser.open(logger.root.handlers[0].baseFilename)
+ lambda: open_file(logger.root.handlers[0].baseFilename)
)
# Fonts
@@ -175,12 +184,18 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
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.groupbox_image_popup.toggled.connect(self.settings_changed_callback)
+ self.spin_popup_w.valueChanged.connect(self.settings_changed_callback)
+ self.spin_popup_h.valueChanged.connect(self.settings_changed_callback)
def apply_settings(self):
# Priority
settings.setValue("tray/notifications/priority", self.spin_priority.value())
settings.setValue("tray/notifications/duration_ms", self.spin_duration.value())
settings.setValue("message/check_missed/notify", self.cb_notify.isChecked())
+ settings.setValue(
+ "tray/notifications/click", self.cb_notification_click.isChecked()
+ )
# Logging
selected_level = self.combo_logging.currentText()
@@ -204,6 +219,11 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
self.message_widget.label_message.font().toString(),
)
+ # Advanced
+ settings.setValue("ImagePopup/enabled", self.groupbox_image_popup.isChecked())
+ settings.setValue("ImagePopup/w", self.spin_popup_w.value())
+ settings.setValue("ImagePopup/h", self.spin_popup_h.value())
+
self.settings_changed = False
self.buttonBox.button(
QtWidgets.QDialogButtonBox.StandardButton.Apply
diff --git a/gotify_tray/gui/widgets/Tray.py b/gotify_tray/gui/widgets/Tray.py
index e901dc4..74ffdbb 100644
--- a/gotify_tray/gui/widgets/Tray.py
+++ b/gotify_tray/gui/widgets/Tray.py
@@ -23,13 +23,13 @@ class Tray(QtWidgets.QSystemTrayIcon):
# Tray menu items
menu = QtWidgets.QMenu()
- self.actionSettings = QtGui.QAction("Settings", self)
- menu.addAction(self.actionSettings)
+ self.actionShowWindow = QtGui.QAction("Show Window", self)
+ menu.addAction(self.actionShowWindow)
menu.addSeparator()
- self.actionShowWindow = QtGui.QAction("Show Window", self)
- menu.addAction(self.actionShowWindow)
+ self.actionSettings = QtGui.QAction("Settings", self)
+ menu.addAction(self.actionSettings)
menu.addSeparator()
diff --git a/gotify_tray/gui/widgets/__init__.py b/gotify_tray/gui/widgets/__init__.py
index d43f65f..2f9952f 100644
--- a/gotify_tray/gui/widgets/__init__.py
+++ b/gotify_tray/gui/widgets/__init__.py
@@ -1,3 +1,4 @@
+from .ImagePopup import ImagePopup
from .MessageWidget import MessageWidget
from .MainWindow import MainWindow
from .ServerInfoDialog import ServerInfoDialog
diff --git a/gotify_tray/utils.py b/gotify_tray/utils.py
index dbcbb30..f1f581a 100644
--- a/gotify_tray/utils.py
+++ b/gotify_tray/utils.py
@@ -1,5 +1,7 @@
import os
+import platform
import re
+import subprocess
from pathlib import Path
@@ -46,3 +48,12 @@ def get_abs_path(s) -> str:
h = Path(__file__).parent.parent
p = Path(s)
return os.path.join(h, p).replace("\\", "/")
+
+
+def open_file(filename: str):
+ if platform.system() == "Linux":
+ subprocess.call(["xdg-open", filename])
+ elif platform.system() == "Windows":
+ os.startfile(filename)
+ elif platform.system() == "Darwin":
+ subprocess.call(["open", filename])
diff --git a/images/main_window.png b/images/main_window.png
index c9e9611..9270c66 100644
Binary files a/images/main_window.png and b/images/main_window.png differ
diff --git a/images/settings.png b/images/settings.png
index c1e44e4..309ceae 100644
Binary files a/images/settings.png and b/images/settings.png differ
diff --git a/requirements.txt b/requirements.txt
index e36d7dd..9da2385 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
requests==2.28.1
-websocket-client==1.3.3
+websocket-client==1.4.0
pyqt6==6.3.1
python-dateutil==2.8.2