forked from quickshell/quickshell
		
	service/notifications: add notifications service
This commit is contained in:
		
							parent
							
								
									79cbfba48a
								
							
						
					
					
						commit
						d630cc7f76
					
				
					 15 changed files with 1249 additions and 0 deletions
				
			
		| 
						 | 
					@ -26,6 +26,7 @@ option(SERVICE_MPRIS "Mpris service" ON)
 | 
				
			||||||
option(SERVICE_PAM "Pam service" ON)
 | 
					option(SERVICE_PAM "Pam service" ON)
 | 
				
			||||||
option(SERVICE_GREETD "Greet service" ON)
 | 
					option(SERVICE_GREETD "Greet service" ON)
 | 
				
			||||||
option(SERVICE_UPOWER "UPower service" ON)
 | 
					option(SERVICE_UPOWER "UPower service" ON)
 | 
				
			||||||
 | 
					option(SERVICE_NOTIFICATIONS "Notification server" ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message(STATUS "Quickshell configuration")
 | 
					message(STATUS "Quickshell configuration")
 | 
				
			||||||
message(STATUS "  Jemalloc: ${USE_JEMALLOC}")
 | 
					message(STATUS "  Jemalloc: ${USE_JEMALLOC}")
 | 
				
			||||||
| 
						 | 
					@ -45,6 +46,7 @@ message(STATUS "    Mpris: ${SERVICE_MPRIS}")
 | 
				
			||||||
message(STATUS "    Pam: ${SERVICE_PAM}")
 | 
					message(STATUS "    Pam: ${SERVICE_PAM}")
 | 
				
			||||||
message(STATUS "    Greetd: ${SERVICE_GREETD}")
 | 
					message(STATUS "    Greetd: ${SERVICE_GREETD}")
 | 
				
			||||||
message(STATUS "    UPower: ${SERVICE_UPOWER}")
 | 
					message(STATUS "    UPower: ${SERVICE_UPOWER}")
 | 
				
			||||||
 | 
					message(STATUS "    Notifications: ${SERVICE_NOTIFICATIONS}")
 | 
				
			||||||
message(STATUS "  Hyprland: ${HYPRLAND}")
 | 
					message(STATUS "  Hyprland: ${HYPRLAND}")
 | 
				
			||||||
if (HYPRLAND)
 | 
					if (HYPRLAND)
 | 
				
			||||||
	message(STATUS "    IPC: ${HYPRLAND_IPC}")
 | 
						message(STATUS "    IPC: ${HYPRLAND_IPC}")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,3 +72,8 @@ bool UntypedObjectModel::removeObject(const QObject* object) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); }
 | 
					qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UntypedObjectModel* UntypedObjectModel::emptyInstance() {
 | 
				
			||||||
 | 
						static auto* instance = new UntypedObjectModel(nullptr); // NOLINT
 | 
				
			||||||
 | 
						return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,8 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Q_INVOKABLE qsizetype indexOf(QObject* object);
 | 
						Q_INVOKABLE qsizetype indexOf(QObject* object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static UntypedObjectModel* emptyInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signals:
 | 
					signals:
 | 
				
			||||||
	void valuesChanged();
 | 
						void valuesChanged();
 | 
				
			||||||
	/// Sent immediately before an object is inserted into the list.
 | 
						/// Sent immediately before an object is inserted into the list.
 | 
				
			||||||
| 
						 | 
					@ -82,6 +84,10 @@ class ObjectModel: public UntypedObjectModel {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
 | 
						explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QVector<T*>& valueList() {
 | 
				
			||||||
 | 
							return *reinterpret_cast<QVector<T*>*>(&this->valuesList); // NOLINT
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] const QVector<T*>& valueList() const {
 | 
						[[nodiscard]] const QVector<T*>& valueList() const {
 | 
				
			||||||
		return *reinterpret_cast<const QVector<T*>*>(&this->valuesList); // NOLINT
 | 
							return *reinterpret_cast<const QVector<T*>*>(&this->valuesList); // NOLINT
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -91,4 +97,8 @@ public:
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); }
 | 
						void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static ObjectModel<T>* emptyInstance() {
 | 
				
			||||||
 | 
							return static_cast<ObjectModel<T>*>(UntypedObjectModel::emptyInstance());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,3 +21,7 @@ endif()
 | 
				
			||||||
if (SERVICE_UPOWER)
 | 
					if (SERVICE_UPOWER)
 | 
				
			||||||
	add_subdirectory(upower)
 | 
						add_subdirectory(upower)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (SERVICE_NOTIFICATIONS)
 | 
				
			||||||
 | 
						add_subdirectory(notifications)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/services/notifications/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/services/notifications/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					qt_add_dbus_adaptor(DBUS_INTERFACES
 | 
				
			||||||
 | 
						org.freedesktop.Notifications.xml
 | 
				
			||||||
 | 
						server.hpp
 | 
				
			||||||
 | 
						qs::service::notifications::NotificationServer
 | 
				
			||||||
 | 
						dbus_notifications
 | 
				
			||||||
 | 
						DBusNotificationServer
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_library(quickshell-service-notifications STATIC
 | 
				
			||||||
 | 
						server.cpp
 | 
				
			||||||
 | 
						notification.cpp
 | 
				
			||||||
 | 
						dbusimage.cpp
 | 
				
			||||||
 | 
						qml.cpp
 | 
				
			||||||
 | 
						${DBUS_INTERFACES}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# dbus headers
 | 
				
			||||||
 | 
					target_include_directories(quickshell-service-notifications PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_qml_module(quickshell-service-notifications
 | 
				
			||||||
 | 
						URI Quickshell.Services.Notifications
 | 
				
			||||||
 | 
						VERSION 0.1
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(quickshell-service-notifications PRIVATE ${QT_DEPS} quickshell-dbus)
 | 
				
			||||||
 | 
					target_link_libraries(quickshell PRIVATE quickshell-service-notificationsplugin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qs_pch(quickshell-service-notifications)
 | 
				
			||||||
 | 
					qs_pch(quickshell-service-notificationsplugin)
 | 
				
			||||||
							
								
								
									
										72
									
								
								src/services/notifications/dbusimage.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/services/notifications/dbusimage.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					#include "dbusimage.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qdbusargument.h>
 | 
				
			||||||
 | 
					#include <qimage.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qsize.h>
 | 
				
			||||||
 | 
					#include <qsysinfo.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::notifications {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_DECLARE_LOGGING_CATEGORY(logNotifications); // server.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QImage DBusNotificationImage::createImage() const {
 | 
				
			||||||
 | 
						auto format = this->hasAlpha ? QImage::Format_RGBA8888 : QImage::Format_RGB888;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return QImage(
 | 
				
			||||||
 | 
						    reinterpret_cast<const uchar*>(this->data.data()), // NOLINT
 | 
				
			||||||
 | 
						    this->width,
 | 
				
			||||||
 | 
						    this->height,
 | 
				
			||||||
 | 
						    format
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const QDBusArgument& operator>>(const QDBusArgument& argument, DBusNotificationImage& pixmap) {
 | 
				
			||||||
 | 
						argument.beginStructure();
 | 
				
			||||||
 | 
						argument >> pixmap.width;
 | 
				
			||||||
 | 
						argument >> pixmap.height;
 | 
				
			||||||
 | 
						auto rowstride = qdbus_cast<qint32>(argument);
 | 
				
			||||||
 | 
						argument >> pixmap.hasAlpha;
 | 
				
			||||||
 | 
						auto sampleBits = qdbus_cast<qint32>(argument);
 | 
				
			||||||
 | 
						auto channels = qdbus_cast<qint32>(argument);
 | 
				
			||||||
 | 
						argument >> pixmap.data;
 | 
				
			||||||
 | 
						argument.endStructure();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (sampleBits != 8) {
 | 
				
			||||||
 | 
							qCWarning(logNotifications) << "Unable to parse pixmap as sample count is incorrect. Got"
 | 
				
			||||||
 | 
							                            << sampleBits << "expected" << 8;
 | 
				
			||||||
 | 
						} else if (channels != (pixmap.hasAlpha ? 4 : 3)) {
 | 
				
			||||||
 | 
							qCWarning(logNotifications) << "Unable to parse pixmap as channel count is incorrect."
 | 
				
			||||||
 | 
							                            << "Got " << channels << "expected" << (pixmap.hasAlpha ? 4 : 3);
 | 
				
			||||||
 | 
						} else if (rowstride != pixmap.width * sampleBits * channels) {
 | 
				
			||||||
 | 
							qCWarning(logNotifications) << "Unable to parse pixmap as rowstride is incorrect. Got"
 | 
				
			||||||
 | 
							                            << rowstride << "expected"
 | 
				
			||||||
 | 
							                            << (pixmap.width * sampleBits * channels);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return argument;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const QDBusArgument& operator<<(QDBusArgument& argument, const DBusNotificationImage& pixmap) {
 | 
				
			||||||
 | 
						argument.beginStructure();
 | 
				
			||||||
 | 
						argument << pixmap.width;
 | 
				
			||||||
 | 
						argument << pixmap.height;
 | 
				
			||||||
 | 
						argument << pixmap.width * (pixmap.hasAlpha ? 4 : 3) * 8;
 | 
				
			||||||
 | 
						argument << pixmap.hasAlpha;
 | 
				
			||||||
 | 
						argument << 8;
 | 
				
			||||||
 | 
						argument << (pixmap.hasAlpha ? 4 : 3);
 | 
				
			||||||
 | 
						argument << pixmap.data;
 | 
				
			||||||
 | 
						argument.endStructure();
 | 
				
			||||||
 | 
						return argument;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QImage
 | 
				
			||||||
 | 
					NotificationImage::requestImage(const QString& /*unused*/, QSize* size, const QSize& /*unused*/) {
 | 
				
			||||||
 | 
						auto image = this->image.createImage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (size != nullptr) *size = image.size();
 | 
				
			||||||
 | 
						return image;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::notifications
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/services/notifications/dbusimage.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/services/notifications/dbusimage.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qdbusargument.h>
 | 
				
			||||||
 | 
					#include <qimage.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../core/imageprovider.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::notifications {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct DBusNotificationImage {
 | 
				
			||||||
 | 
						qint32 width = 0;
 | 
				
			||||||
 | 
						qint32 height = 0;
 | 
				
			||||||
 | 
						bool hasAlpha = false;
 | 
				
			||||||
 | 
						QByteArray data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// valid only for the lifetime of the pixmap
 | 
				
			||||||
 | 
						[[nodiscard]] QImage createImage() const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const QDBusArgument& operator>>(const QDBusArgument& argument, DBusNotificationImage& pixmap);
 | 
				
			||||||
 | 
					const QDBusArgument& operator<<(QDBusArgument& argument, const DBusNotificationImage& pixmap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotificationImage: public QsImageHandle {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit NotificationImage(DBusNotificationImage image, QObject* parent)
 | 
				
			||||||
 | 
						    : QsImageHandle(QQuickAsyncImageProvider::Image, parent)
 | 
				
			||||||
 | 
						    , image(std::move(image)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DBusNotificationImage image;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					} // namespace qs::service::notifications
 | 
				
			||||||
							
								
								
									
										4
									
								
								src/services/notifications/module.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/services/notifications/module.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					name = "Quickshell.Services.Notifications"
 | 
				
			||||||
 | 
					description = "Types for implementing a notification daemon"
 | 
				
			||||||
 | 
					headers = [ "qml.hpp", "notification.hpp" ]
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
							
								
								
									
										252
									
								
								src/services/notifications/notification.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								src/services/notifications/notification.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,252 @@
 | 
				
			||||||
 | 
					#include "notification.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdbusargument.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../core/desktopentry.hpp"
 | 
				
			||||||
 | 
					#include "../../core/iconimageprovider.hpp"
 | 
				
			||||||
 | 
					#include "dbusimage.hpp"
 | 
				
			||||||
 | 
					#include "server.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::notifications {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_DECLARE_LOGGING_CATEGORY(logNotifications); // server.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString NotificationUrgency::toString(NotificationUrgency::Enum value) {
 | 
				
			||||||
 | 
						switch (value) {
 | 
				
			||||||
 | 
						case NotificationUrgency::Low: return "Low";
 | 
				
			||||||
 | 
						case NotificationUrgency::Normal: return "Normal";
 | 
				
			||||||
 | 
						case NotificationUrgency::Critical: return "Critical";
 | 
				
			||||||
 | 
						default: return "Invalid notification urgency";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString NotificationCloseReason::toString(NotificationCloseReason::Enum value) {
 | 
				
			||||||
 | 
						switch (value) {
 | 
				
			||||||
 | 
						case NotificationCloseReason::Expired: return "Expired";
 | 
				
			||||||
 | 
						case NotificationCloseReason::Dismissed: return "Dismissed";
 | 
				
			||||||
 | 
						case NotificationCloseReason::CloseRequested: return "CloseRequested";
 | 
				
			||||||
 | 
						default: return "Invalid notification close reason";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString NotificationAction::identifier() const { return this->mIdentifier; }
 | 
				
			||||||
 | 
					QString NotificationAction::text() const { return this->mText; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationAction::invoke() {
 | 
				
			||||||
 | 
						NotificationServer::instance()->ActionInvoked(this->notification->id(), this->mIdentifier);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->notification->isResident()) {
 | 
				
			||||||
 | 
							this->notification->close(NotificationCloseReason::Dismissed);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationAction::setText(const QString& text) {
 | 
				
			||||||
 | 
						if (text != this->mText) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mText = text;
 | 
				
			||||||
 | 
						emit this->textChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Notification::expire() { this->close(NotificationCloseReason::Expired); }
 | 
				
			||||||
 | 
					void Notification::dismiss() { this->close(NotificationCloseReason::Dismissed); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Notification::close(NotificationCloseReason::Enum reason) {
 | 
				
			||||||
 | 
						this->mCloseReason = reason;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (reason != 0) {
 | 
				
			||||||
 | 
							NotificationServer::instance()->deleteNotification(this, reason);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Notification::updateProperties(
 | 
				
			||||||
 | 
					    const QString& appName,
 | 
				
			||||||
 | 
					    QString appIcon,
 | 
				
			||||||
 | 
					    const QString& summary,
 | 
				
			||||||
 | 
					    const QString& body,
 | 
				
			||||||
 | 
					    const QStringList& actions,
 | 
				
			||||||
 | 
					    QVariantMap hints,
 | 
				
			||||||
 | 
					    qint32 expireTimeout
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						auto urgency = hints.contains("urgency") ? hints.value("urgency").value<quint8>()
 | 
				
			||||||
 | 
						                                         : NotificationUrgency::Normal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto hasActionIcons = hints.value("action-icons").value<bool>();
 | 
				
			||||||
 | 
						auto isResident = hints.value("resident").value<bool>();
 | 
				
			||||||
 | 
						auto isTransient = hints.value("transient").value<bool>();
 | 
				
			||||||
 | 
						auto desktopEntry = hints.value("desktop-entry").value<QString>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QString imageDataName;
 | 
				
			||||||
 | 
						if (hints.contains("image-data")) imageDataName = "image-data";
 | 
				
			||||||
 | 
						else if (hints.contains("image_data")) imageDataName = "image_data";
 | 
				
			||||||
 | 
						else if (hints.contains("icon_data")) imageDataName = "icon_data";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NotificationImage* imagePixmap = nullptr;
 | 
				
			||||||
 | 
						if (!imageDataName.isEmpty()) {
 | 
				
			||||||
 | 
							auto value = hints.value(imageDataName).value<QDBusArgument>();
 | 
				
			||||||
 | 
							DBusNotificationImage image;
 | 
				
			||||||
 | 
							value >> image;
 | 
				
			||||||
 | 
							imagePixmap = new NotificationImage(std::move(image), this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// don't store giant byte arrays more than necessary
 | 
				
			||||||
 | 
						hints.remove("image-data");
 | 
				
			||||||
 | 
						hints.remove("image_data");
 | 
				
			||||||
 | 
						hints.remove("icon_data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QString imagePath;
 | 
				
			||||||
 | 
						if (!imagePixmap) {
 | 
				
			||||||
 | 
							QString imagePathName;
 | 
				
			||||||
 | 
							if (hints.contains("image-path")) imagePathName = "image-path";
 | 
				
			||||||
 | 
							else if (hints.contains("image_path")) imagePathName = "image_path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!imagePathName.isEmpty()) {
 | 
				
			||||||
 | 
								imagePath = hints.value(imagePathName).value<QString>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!imagePath.startsWith("file:")) {
 | 
				
			||||||
 | 
									imagePath = IconImageProvider::requestString(imagePath, "");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (appIcon.isEmpty() && !desktopEntry.isEmpty()) {
 | 
				
			||||||
 | 
							if (auto* entry = DesktopEntryManager::instance()->byId(desktopEntry)) {
 | 
				
			||||||
 | 
								appIcon = entry->mIcon;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto appNameChanged = appName != this->mAppName;
 | 
				
			||||||
 | 
						auto appIconChanged = appIcon != this->mAppIcon;
 | 
				
			||||||
 | 
						auto summaryChanged = summary != this->mSummary;
 | 
				
			||||||
 | 
						auto bodyChanged = body != this->mBody;
 | 
				
			||||||
 | 
						auto expireTimeoutChanged = expireTimeout != this->mExpireTimeout;
 | 
				
			||||||
 | 
						auto urgencyChanged = urgency != this->mUrgency;
 | 
				
			||||||
 | 
						auto hasActionIconsChanged = hasActionIcons != this->mHasActionIcons;
 | 
				
			||||||
 | 
						auto isResidentChanged = isResident != this->mIsResident;
 | 
				
			||||||
 | 
						auto isTransientChanged = isTransient != this->mIsTransient;
 | 
				
			||||||
 | 
						auto desktopEntryChanged = desktopEntry != this->mDesktopEntry;
 | 
				
			||||||
 | 
						auto imageChanged = imagePixmap || imagePath != this->mImagePath;
 | 
				
			||||||
 | 
						auto hintsChanged = hints != this->mHints;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (appNameChanged) this->mAppName = appName;
 | 
				
			||||||
 | 
						if (appIconChanged) this->mAppIcon = appIcon;
 | 
				
			||||||
 | 
						if (summaryChanged) this->mSummary = summary;
 | 
				
			||||||
 | 
						if (bodyChanged) this->mBody = body;
 | 
				
			||||||
 | 
						if (expireTimeoutChanged) this->mExpireTimeout = expireTimeout;
 | 
				
			||||||
 | 
						if (urgencyChanged) this->mUrgency = static_cast<NotificationUrgency::Enum>(urgency);
 | 
				
			||||||
 | 
						if (hasActionIcons) this->mHasActionIcons = hasActionIcons;
 | 
				
			||||||
 | 
						if (isResidentChanged) this->mIsResident = isResident;
 | 
				
			||||||
 | 
						if (isTransientChanged) this->mIsTransient = isTransient;
 | 
				
			||||||
 | 
						if (desktopEntryChanged) this->mDesktopEntry = desktopEntry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NotificationImage* oldImage = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (imageChanged) {
 | 
				
			||||||
 | 
							oldImage = this->mImagePixmap;
 | 
				
			||||||
 | 
							this->mImagePixmap = imagePixmap;
 | 
				
			||||||
 | 
							this->mImagePath = imagePath;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (hintsChanged) this->mHints = hints;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool actionsChanged = false;
 | 
				
			||||||
 | 
						auto deletedActions = QVector<NotificationAction*>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (actions.length() % 2 == 0) {
 | 
				
			||||||
 | 
							int ai = 0;
 | 
				
			||||||
 | 
							for (auto i = 0; i != actions.length(); i += 2) {
 | 
				
			||||||
 | 
								ai = i / 2;
 | 
				
			||||||
 | 
								const auto& identifier = actions.at(i);
 | 
				
			||||||
 | 
								const auto& text = actions.at(i + 1);
 | 
				
			||||||
 | 
								auto* action = ai < this->mActions.length() ? this->mActions.at(ai) : nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (action && identifier == action->identifier()) {
 | 
				
			||||||
 | 
									action->setText(text);
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									auto* newAction = new NotificationAction(identifier, text, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (action) {
 | 
				
			||||||
 | 
										deletedActions.push_back(action);
 | 
				
			||||||
 | 
										this->mActions.replace(ai, newAction);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										this->mActions.push_back(newAction);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									actionsChanged = true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ai++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto i = this->mActions.length(); i > ai; i--) {
 | 
				
			||||||
 | 
								deletedActions.push_back(this->mActions.at(i - 1));
 | 
				
			||||||
 | 
								this->mActions.remove(i - 1);
 | 
				
			||||||
 | 
								actionsChanged = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							qCWarning(logNotifications) << this << '(' << appName << ')'
 | 
				
			||||||
 | 
							                            << "sent an action set of an invalid length.";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (appNameChanged) emit this->appNameChanged();
 | 
				
			||||||
 | 
						if (appIconChanged) emit this->appIconChanged();
 | 
				
			||||||
 | 
						if (summaryChanged) emit this->summaryChanged();
 | 
				
			||||||
 | 
						if (bodyChanged) emit this->bodyChanged();
 | 
				
			||||||
 | 
						if (expireTimeoutChanged) emit this->expireTimeoutChanged();
 | 
				
			||||||
 | 
						if (urgencyChanged) emit this->urgencyChanged();
 | 
				
			||||||
 | 
						if (actionsChanged) emit this->actionsChanged();
 | 
				
			||||||
 | 
						if (hasActionIconsChanged) emit this->hasActionIconsChanged();
 | 
				
			||||||
 | 
						if (isResidentChanged) emit this->isResidentChanged();
 | 
				
			||||||
 | 
						if (isTransientChanged) emit this->isTransientChanged();
 | 
				
			||||||
 | 
						if (desktopEntryChanged) emit this->desktopEntryChanged();
 | 
				
			||||||
 | 
						if (imageChanged) emit this->imageChanged();
 | 
				
			||||||
 | 
						if (hintsChanged) emit this->hintsChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto* action: deletedActions) {
 | 
				
			||||||
 | 
							delete action;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						delete oldImage;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					quint32 Notification::id() const { return this->mId; }
 | 
				
			||||||
 | 
					bool Notification::isTracked() const { return this->mCloseReason == 0; }
 | 
				
			||||||
 | 
					NotificationCloseReason::Enum Notification::closeReason() const { return this->mCloseReason; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Notification::setTracked(bool tracked) {
 | 
				
			||||||
 | 
						this->close(
 | 
				
			||||||
 | 
						    tracked ? static_cast<NotificationCloseReason::Enum>(0) : NotificationCloseReason::Dismissed
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Notification::isLastGeneration() const { return this->mLastGeneration; }
 | 
				
			||||||
 | 
					void Notification::setLastGeneration() { this->mLastGeneration = true; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qreal Notification::expireTimeout() const { return this->mExpireTimeout; }
 | 
				
			||||||
 | 
					QString Notification::appName() const { return this->mAppName; }
 | 
				
			||||||
 | 
					QString Notification::appIcon() const { return this->mAppIcon; }
 | 
				
			||||||
 | 
					QString Notification::summary() const { return this->mSummary; }
 | 
				
			||||||
 | 
					QString Notification::body() const { return this->mBody; }
 | 
				
			||||||
 | 
					NotificationUrgency::Enum Notification::urgency() const { return this->mUrgency; }
 | 
				
			||||||
 | 
					QVector<NotificationAction*> Notification::actions() const { return this->mActions; }
 | 
				
			||||||
 | 
					bool Notification::hasActionIcons() const { return this->mHasActionIcons; }
 | 
				
			||||||
 | 
					bool Notification::isResident() const { return this->mIsResident; }
 | 
				
			||||||
 | 
					bool Notification::isTransient() const { return this->mIsTransient; }
 | 
				
			||||||
 | 
					QString Notification::desktopEntry() const { return this->mDesktopEntry; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString Notification::image() const {
 | 
				
			||||||
 | 
						if (this->mImagePixmap) {
 | 
				
			||||||
 | 
							return this->mImagePixmap->url();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return this->mImagePath;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVariantMap Notification::hints() const { return this->mHints; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::notifications
 | 
				
			||||||
							
								
								
									
										225
									
								
								src/services/notifications/notification.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								src/services/notifications/notification.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,225 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qmap.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::notifications {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotificationImage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotificationUrgency: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						enum Enum {
 | 
				
			||||||
 | 
							Low = 0,
 | 
				
			||||||
 | 
							Normal = 1,
 | 
				
			||||||
 | 
							Critical = 2,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q_INVOKABLE static QString toString(NotificationUrgency::Enum value);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotificationCloseReason: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						enum Enum {
 | 
				
			||||||
 | 
							/// The notification expired due to a timeout.
 | 
				
			||||||
 | 
							Expired = 1,
 | 
				
			||||||
 | 
							/// The notification was explicitly dismissed by the user.
 | 
				
			||||||
 | 
							Dismissed = 2,
 | 
				
			||||||
 | 
							/// The remote application requested the notification be removed.
 | 
				
			||||||
 | 
							CloseRequested = 3,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q_INVOKABLE static QString toString(NotificationCloseReason::Enum value);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotificationAction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! A notification emitted by a NotificationServer.
 | 
				
			||||||
 | 
					class Notification: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						/// Id of the notification as given to the client.
 | 
				
			||||||
 | 
						Q_PROPERTY(quint32 id READ id CONSTANT);
 | 
				
			||||||
 | 
						/// If the notification is tracked by the notification server.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Setting this property to false is equivalent to calling `dismiss()`.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool tracked READ isTracked WRITE setTracked NOTIFY trackedChanged);
 | 
				
			||||||
 | 
						/// If this notification was carried over from the last generation
 | 
				
			||||||
 | 
						/// when quickshell reloaded.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Notifications from the last generation will only be emitted if
 | 
				
			||||||
 | 
						/// [NotificationServer.keepOnReload](../notificationserver#prop.keepOnReload) is true.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool lastGeneration READ isLastGeneration CONSTANT);
 | 
				
			||||||
 | 
						/// Time in seconds the notification should be valid for
 | 
				
			||||||
 | 
						Q_PROPERTY(qreal expireTimeout READ expireTimeout NOTIFY expireTimeoutChanged);
 | 
				
			||||||
 | 
						/// The sending application's name.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString appName READ appName NOTIFY appNameChanged);
 | 
				
			||||||
 | 
						/// The sending application's icon. If none was provided, then the icon from an associated
 | 
				
			||||||
 | 
						/// desktop entry will be retrieved. If none was found then "".
 | 
				
			||||||
 | 
						Q_PROPERTY(QString appIcon READ appIcon NOTIFY appIconChanged);
 | 
				
			||||||
 | 
						/// The image associated with this notification, or "" if none.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString summary READ summary NOTIFY summaryChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(QString body READ body NOTIFY bodyChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(NotificationUrgency::Enum urgency READ urgency NOTIFY urgencyChanged);
 | 
				
			||||||
 | 
						/// Actions that can be taken for this notification.
 | 
				
			||||||
 | 
						Q_PROPERTY(QVector<NotificationAction*> actions READ actions NOTIFY actionsChanged);
 | 
				
			||||||
 | 
						/// If actions associated with this notification have icons available.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// See [NotificationAction.identifier](../notificationaction#prop.identifier) for details.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool hasActionIcons READ hasActionIcons NOTIFY hasActionIconsChanged);
 | 
				
			||||||
 | 
						/// If true, the notification will not be destroyed after an action is invoked.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool resident READ isResident NOTIFY isResidentChanged);
 | 
				
			||||||
 | 
						/// If true, the notification should skip any kind of persistence function like a notification area.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool transient READ isTransient NOTIFY isTransientChanged);
 | 
				
			||||||
 | 
						/// The name of the sender's desktop entry or "" if none was supplied.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString desktopEntry READ desktopEntry NOTIFY desktopEntryChanged);
 | 
				
			||||||
 | 
						/// An image associated with the notification.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// This image is often something like a profile picture in instant messaging applications.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString image READ image NOTIFY imageChanged);
 | 
				
			||||||
 | 
						/// All hints sent by the client application as a javascript object.
 | 
				
			||||||
 | 
						/// Many common hints are exposed via other properties.
 | 
				
			||||||
 | 
						Q_PROPERTY(QVariantMap hints READ hints NOTIFY hintsChanged);
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("Notifications must be acquired from a NotificationServer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit Notification(quint32 id, QObject* parent): QObject(parent), mId(id) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Destroy the notification and hint to the remote application that it has
 | 
				
			||||||
 | 
						/// timed out an expired.
 | 
				
			||||||
 | 
						Q_INVOKABLE void expire();
 | 
				
			||||||
 | 
						/// Destroy the notification and hint to the remote application that it was
 | 
				
			||||||
 | 
						/// explicitly closed by the user.
 | 
				
			||||||
 | 
						Q_INVOKABLE void dismiss();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void updateProperties(
 | 
				
			||||||
 | 
						    const QString& appName,
 | 
				
			||||||
 | 
						    QString appIcon,
 | 
				
			||||||
 | 
						    const QString& summary,
 | 
				
			||||||
 | 
						    const QString& body,
 | 
				
			||||||
 | 
						    const QStringList& actions,
 | 
				
			||||||
 | 
						    QVariantMap hints,
 | 
				
			||||||
 | 
						    qint32 expireTimeout
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void close(NotificationCloseReason::Enum reason);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] quint32 id() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool isTracked() const;
 | 
				
			||||||
 | 
						[[nodiscard]] NotificationCloseReason::Enum closeReason() const;
 | 
				
			||||||
 | 
						void setTracked(bool tracked);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool isLastGeneration() const;
 | 
				
			||||||
 | 
						void setLastGeneration();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] qreal expireTimeout() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString appName() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString appIcon() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString summary() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString body() const;
 | 
				
			||||||
 | 
						[[nodiscard]] NotificationUrgency::Enum urgency() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QVector<NotificationAction*> actions() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool hasActionIcons() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool isResident() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool isTransient() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString desktopEntry() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString image() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QVariantMap hints() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						/// Sent when a notification has been closed.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// The notification object will be destroyed as soon as all signal handlers exit.
 | 
				
			||||||
 | 
						void closed(NotificationCloseReason::Enum reason);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void trackedChanged();
 | 
				
			||||||
 | 
						void expireTimeoutChanged();
 | 
				
			||||||
 | 
						void appNameChanged();
 | 
				
			||||||
 | 
						void appIconChanged();
 | 
				
			||||||
 | 
						void summaryChanged();
 | 
				
			||||||
 | 
						void bodyChanged();
 | 
				
			||||||
 | 
						void urgencyChanged();
 | 
				
			||||||
 | 
						void actionsChanged();
 | 
				
			||||||
 | 
						void hasActionIconsChanged();
 | 
				
			||||||
 | 
						void isResidentChanged();
 | 
				
			||||||
 | 
						void isTransientChanged();
 | 
				
			||||||
 | 
						void desktopEntryChanged();
 | 
				
			||||||
 | 
						void imageChanged();
 | 
				
			||||||
 | 
						void hintsChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						quint32 mId;
 | 
				
			||||||
 | 
						NotificationCloseReason::Enum mCloseReason = NotificationCloseReason::Dismissed;
 | 
				
			||||||
 | 
						bool mLastGeneration = false;
 | 
				
			||||||
 | 
						qreal mExpireTimeout = 0;
 | 
				
			||||||
 | 
						QString mAppName;
 | 
				
			||||||
 | 
						QString mAppIcon;
 | 
				
			||||||
 | 
						QString mSummary;
 | 
				
			||||||
 | 
						QString mBody;
 | 
				
			||||||
 | 
						NotificationUrgency::Enum mUrgency = NotificationUrgency::Normal;
 | 
				
			||||||
 | 
						QVector<NotificationAction*> mActions;
 | 
				
			||||||
 | 
						bool mHasActionIcons = false;
 | 
				
			||||||
 | 
						bool mIsResident = false;
 | 
				
			||||||
 | 
						bool mIsTransient = false;
 | 
				
			||||||
 | 
						QString mImagePath;
 | 
				
			||||||
 | 
						NotificationImage* mImagePixmap = nullptr;
 | 
				
			||||||
 | 
						QString mDesktopEntry;
 | 
				
			||||||
 | 
						QVariantMap mHints;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotificationAction: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						/// The identifier of the action.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// When [Notification.hasActionIcons] is true, this property will be an icon name.
 | 
				
			||||||
 | 
						/// When it is false, this property is irrelevant.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// [Notification.hasActionIcons]: ../notification#prop.hasActionIcons
 | 
				
			||||||
 | 
						Q_PROPERTY(QString identifier READ identifier CONSTANT);
 | 
				
			||||||
 | 
						/// The localized text that should be displayed on a button.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString text READ text NOTIFY textChanged);
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("NotificationActions must be acquired from a Notification");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit NotificationAction(QString identifier, QString text, Notification* notification)
 | 
				
			||||||
 | 
						    : QObject(notification)
 | 
				
			||||||
 | 
						    , notification(notification)
 | 
				
			||||||
 | 
						    , mIdentifier(std::move(identifier))
 | 
				
			||||||
 | 
						    , mText(std::move(text)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Invoke the action. If [Notification.resident] is false it will be dismissed.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// [Notification.resident]: ../notification#prop.resident
 | 
				
			||||||
 | 
						Q_INVOKABLE void invoke();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString identifier() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString text() const;
 | 
				
			||||||
 | 
						void setText(const QString& text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void textChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						Notification* notification;
 | 
				
			||||||
 | 
						QString mIdentifier;
 | 
				
			||||||
 | 
						QString mText;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::notifications
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/services/notifications/org.freedesktop.Notifications.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/services/notifications/org.freedesktop.Notifications.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					<node>
 | 
				
			||||||
 | 
						<interface name="org.freedesktop.Notifications">
 | 
				
			||||||
 | 
							<method name="GetCapabilities">
 | 
				
			||||||
 | 
								<arg name="capabilities" type="as" direction="out"/>
 | 
				
			||||||
 | 
							</method>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<method name="Notify">
 | 
				
			||||||
 | 
								<arg name="appName" type="s" direction="in"/>
 | 
				
			||||||
 | 
								<arg name="replacesId" type="u" direction="in"/>
 | 
				
			||||||
 | 
								<arg name="appIcon" type="s" direction="in"/>
 | 
				
			||||||
 | 
								<arg name="summary" type="s" direction="in"/>
 | 
				
			||||||
 | 
								<arg name="body" type="s" direction="in"/>
 | 
				
			||||||
 | 
								<arg name="actions" type="as" direction="in"/>
 | 
				
			||||||
 | 
								<arg name="hints" type="a{sv}" direction="in"/>
 | 
				
			||||||
 | 
								<arg name="expireTimeout" type="i" direction="in"/>
 | 
				
			||||||
 | 
								<arg name="id" type="u" direction="out"/>
 | 
				
			||||||
 | 
								<annotation name="org.qtproject.QtDBus.QtTypeName.In6" value="QVariantMap"/>
 | 
				
			||||||
 | 
							</method>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<method name="CloseNotification">
 | 
				
			||||||
 | 
								<arg name="id" type="u" direction="in"/>
 | 
				
			||||||
 | 
							</method>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<method name="GetServerInformation">
 | 
				
			||||||
 | 
								<arg name="name" type="s" direction="out"/>
 | 
				
			||||||
 | 
								<arg name="vendor" type="s" direction="out"/>
 | 
				
			||||||
 | 
								<arg name="version" type="s" direction="out"/>
 | 
				
			||||||
 | 
								<arg name="specVersion" type="s" direction="out"/>
 | 
				
			||||||
 | 
							</method>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<signal name="NotificationClosed">
 | 
				
			||||||
 | 
								<arg name="id" type="u" direction="out"/>
 | 
				
			||||||
 | 
								<arg name="reason" type="u" direction="out"/>
 | 
				
			||||||
 | 
							</signal>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<signal name="ActionInvoked">
 | 
				
			||||||
 | 
								<arg name="id" type="u" direction="out"/>
 | 
				
			||||||
 | 
								<arg name="actionKey" type="s" direction="out"/>
 | 
				
			||||||
 | 
							</signal>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<signal name="ActivationToken">
 | 
				
			||||||
 | 
								<arg name="id" type="u" direction="out"/>
 | 
				
			||||||
 | 
								<arg name="activationToken" type="s" direction="out"/>
 | 
				
			||||||
 | 
							</signal>
 | 
				
			||||||
 | 
						</interface>
 | 
				
			||||||
 | 
					</node>
 | 
				
			||||||
							
								
								
									
										141
									
								
								src/services/notifications/qml.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/services/notifications/qml.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,141 @@
 | 
				
			||||||
 | 
					#include "qml.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../core/model.hpp"
 | 
				
			||||||
 | 
					#include "notification.hpp"
 | 
				
			||||||
 | 
					#include "server.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::notifications {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::onPostReload() {
 | 
				
			||||||
 | 
						auto* instance = NotificationServer::instance();
 | 
				
			||||||
 | 
						instance->support = this->support;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    instance,
 | 
				
			||||||
 | 
						    &NotificationServer::notification,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    &NotificationServerQml::notification
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						instance->switchGeneration(this->mKeepOnReload, [this]() {
 | 
				
			||||||
 | 
							this->live = true;
 | 
				
			||||||
 | 
							emit this->trackedNotificationsChanged();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::keepOnReload() const { return this->mKeepOnReload; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setKeepOnReload(bool keepOnReload) {
 | 
				
			||||||
 | 
						if (keepOnReload == this->mKeepOnReload) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->live) {
 | 
				
			||||||
 | 
							qCritical() << "Cannot set NotificationServer.keepOnReload after the server has been started.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mKeepOnReload = keepOnReload;
 | 
				
			||||||
 | 
						emit this->keepOnReloadChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::persistenceSupported() const { return this->support.persistence; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setPersistenceSupported(bool persistenceSupported) {
 | 
				
			||||||
 | 
						if (persistenceSupported == this->support.persistence) return;
 | 
				
			||||||
 | 
						this->support.persistence = persistenceSupported;
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->persistenceSupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::bodySupported() const { return this->support.body; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setBodySupported(bool bodySupported) {
 | 
				
			||||||
 | 
						if (bodySupported == this->support.body) return;
 | 
				
			||||||
 | 
						this->support.body = bodySupported;
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->bodySupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::bodyMarkupSupported() const { return this->support.bodyMarkup; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setBodyMarkupSupported(bool bodyMarkupSupported) {
 | 
				
			||||||
 | 
						if (bodyMarkupSupported == this->support.bodyMarkup) return;
 | 
				
			||||||
 | 
						this->support.bodyMarkup = bodyMarkupSupported;
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->bodyMarkupSupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::bodyHyperlinksSupported() const { return this->support.bodyHyperlinks; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setBodyHyperlinksSupported(bool bodyHyperlinksSupported) {
 | 
				
			||||||
 | 
						if (bodyHyperlinksSupported == this->support.bodyHyperlinks) return;
 | 
				
			||||||
 | 
						this->support.bodyHyperlinks = bodyHyperlinksSupported;
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->bodyHyperlinksSupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::bodyImagesSupported() const { return this->support.bodyImages; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setBodyImagesSupported(bool bodyImagesSupported) {
 | 
				
			||||||
 | 
						if (bodyImagesSupported == this->support.bodyImages) return;
 | 
				
			||||||
 | 
						this->support.bodyImages = bodyImagesSupported;
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->bodyImagesSupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::actionsSupported() const { return this->support.actions; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setActionsSupported(bool actionsSupported) {
 | 
				
			||||||
 | 
						if (actionsSupported == this->support.actions) return;
 | 
				
			||||||
 | 
						this->support.actions = actionsSupported;
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->actionsSupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::actionIconsSupported() const { return this->support.actionIcons; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setActionIconsSupported(bool actionIconsSupported) {
 | 
				
			||||||
 | 
						if (actionIconsSupported == this->support.actionIcons) return;
 | 
				
			||||||
 | 
						this->support.actionIcons = actionIconsSupported;
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->actionIconsSupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool NotificationServerQml::imageSupported() const { return this->support.image; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setImageSupported(bool imageSupported) {
 | 
				
			||||||
 | 
						if (imageSupported == this->support.image) return;
 | 
				
			||||||
 | 
						this->support.image = imageSupported;
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->imageSupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVector<QString> NotificationServerQml::extraHints() const { return this->support.extraHints; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::setExtraHints(QVector<QString> extraHints) {
 | 
				
			||||||
 | 
						if (extraHints == this->support.extraHints) return;
 | 
				
			||||||
 | 
						this->support.extraHints = std::move(extraHints);
 | 
				
			||||||
 | 
						this->updateSupported();
 | 
				
			||||||
 | 
						emit this->extraHintsChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ObjectModel<Notification>* NotificationServerQml::trackedNotifications() const {
 | 
				
			||||||
 | 
						if (this->live) {
 | 
				
			||||||
 | 
							return NotificationServer::instance()->trackedNotifications();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return ObjectModel<Notification>::emptyInstance();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServerQml::updateSupported() {
 | 
				
			||||||
 | 
						if (this->live) {
 | 
				
			||||||
 | 
							NotificationServer::instance()->support = this->support;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::notifications
 | 
				
			||||||
							
								
								
									
										139
									
								
								src/services/notifications/qml.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/services/notifications/qml.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,139 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QtCore/qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../core/model.hpp"
 | 
				
			||||||
 | 
					#include "../../core/reload.hpp"
 | 
				
			||||||
 | 
					#include "notification.hpp"
 | 
				
			||||||
 | 
					#include "server.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::notifications {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Desktop Notifications Server.
 | 
				
			||||||
 | 
					/// An implementation of the [Desktop Notifications Specification] for receiving notifications
 | 
				
			||||||
 | 
					/// from external applications.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// The server does not advertise most capabilities by default. See the individual properties for details.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// [Desktop Notifications Specification]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html
 | 
				
			||||||
 | 
					class NotificationServerQml
 | 
				
			||||||
 | 
					    : public QObject
 | 
				
			||||||
 | 
					    , public PostReloadHook {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						// clang-format off
 | 
				
			||||||
 | 
						/// If notifications should be re-emitted when quickshell reloads. Defaults to true.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// The [lastGeneration](../notification#prop.lastGeneration) flag will be
 | 
				
			||||||
 | 
						/// set on notifications from the prior generation for further filtering/handling.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool keepOnReload READ keepOnReload WRITE setKeepOnReload NOTIFY keepOnReloadChanged);
 | 
				
			||||||
 | 
						/// If the notification server should advertise that it can persist notifications in the background
 | 
				
			||||||
 | 
						/// after going offscreen. Defaults to false.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool persistenceSupported READ persistenceSupported WRITE setPersistenceSupported NOTIFY persistenceSupportedChanged);
 | 
				
			||||||
 | 
						/// If notification body text should be advertised as supported by the notification server.
 | 
				
			||||||
 | 
						/// Defaults to true.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Note that returned notifications are likely to return body text even if this property is false,
 | 
				
			||||||
 | 
						/// as it is only a hint.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool bodySupported READ bodySupported WRITE setBodySupported NOTIFY bodySupportedChanged);
 | 
				
			||||||
 | 
						/// If notification body text should be advertised as supporting markup as described in [the specification]
 | 
				
			||||||
 | 
						/// Defaults to false.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Note that returned notifications may still contain markup if this property is false, as it is only a hint.
 | 
				
			||||||
 | 
						/// By default Text objects will try to render markup. To avoid this if any is sent, change [Text.textFormat] to `PlainText`.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// [the specification]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#markup
 | 
				
			||||||
 | 
						/// [Text.textFormat]: https://doc.qt.io/qt-6/qml-qtquick-text.html#textFormat-prop
 | 
				
			||||||
 | 
						Q_PROPERTY(bool bodyMarkupSupported READ bodyMarkupSupported WRITE setBodyMarkupSupported NOTIFY bodyMarkupSupportedChanged);
 | 
				
			||||||
 | 
						/// If notification body text should be advertised as supporting hyperlinks as described in [the specification]
 | 
				
			||||||
 | 
						/// Defaults to false.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Note that returned notifications may still contain hyperlinks if this property is false, as it is only a hint.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// [the specification]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#hyperlinks
 | 
				
			||||||
 | 
						Q_PROPERTY(bool bodyHyperlinksSupported READ bodyHyperlinksSupported WRITE setBodyHyperlinksSupported NOTIFY bodyHyperlinksSupportedChanged);
 | 
				
			||||||
 | 
						/// If notification body text should be advertised as supporting images as described in [the specification]
 | 
				
			||||||
 | 
						/// Defaults to false.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Note that returned notifications may still contain images if this property is false, as it is only a hint.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// [the specification]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#images
 | 
				
			||||||
 | 
						Q_PROPERTY(bool bodyImagesSupported READ bodyImagesSupported WRITE setBodyImagesSupported NOTIFY bodyImagesSupportedChanged);
 | 
				
			||||||
 | 
						/// If notification actions should be advertised as supported by the notification server. Defaults to false.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool actionsSupported READ actionsSupported WRITE setActionsSupported NOTIFY actionsSupportedChanged);
 | 
				
			||||||
 | 
						/// If notification actions should be advertised as supporting the display of icons. Defaults to false.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool actionIconsSupported READ actionIconsSupported WRITE setActionIconsSupported NOTIFY actionIconsSupportedChanged);
 | 
				
			||||||
 | 
						/// If the notification server should advertise that it supports images. Defaults to false.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool imageSupported READ imageSupported WRITE setImageSupported NOTIFY imageSupportedChanged);
 | 
				
			||||||
 | 
						/// All notifications currently tracked by the server.
 | 
				
			||||||
 | 
						Q_PROPERTY(ObjectModel<Notification>* trackedNotifications READ trackedNotifications NOTIFY trackedNotificationsChanged);
 | 
				
			||||||
 | 
						/// Extra hints to expose to notification clients.
 | 
				
			||||||
 | 
						Q_PROPERTY(QVector<QString> extraHints READ extraHints WRITE setExtraHints NOTIFY extraHintsChanged);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
						QML_NAMED_ELEMENT(NotificationServer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						void onPostReload() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool keepOnReload() const;
 | 
				
			||||||
 | 
						void setKeepOnReload(bool keepOnReload);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool persistenceSupported() const;
 | 
				
			||||||
 | 
						void setPersistenceSupported(bool persistenceSupported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool bodySupported() const;
 | 
				
			||||||
 | 
						void setBodySupported(bool bodySupported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool bodyMarkupSupported() const;
 | 
				
			||||||
 | 
						void setBodyMarkupSupported(bool bodyMarkupSupported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool bodyHyperlinksSupported() const;
 | 
				
			||||||
 | 
						void setBodyHyperlinksSupported(bool bodyHyperlinksSupported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool bodyImagesSupported() const;
 | 
				
			||||||
 | 
						void setBodyImagesSupported(bool bodyImagesSupported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool actionsSupported() const;
 | 
				
			||||||
 | 
						void setActionsSupported(bool actionsSupported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool actionIconsSupported() const;
 | 
				
			||||||
 | 
						void setActionIconsSupported(bool actionIconsSupported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool imageSupported() const;
 | 
				
			||||||
 | 
						void setImageSupported(bool imageSupported);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QVector<QString> extraHints() const;
 | 
				
			||||||
 | 
						void setExtraHints(QVector<QString> extraHints);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] ObjectModel<Notification>* trackedNotifications() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						/// Sent when a notification is received by the server.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// If this notification should not be discarded, set its `tracked` property to true.
 | 
				
			||||||
 | 
						void notification(Notification* notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void keepOnReloadChanged();
 | 
				
			||||||
 | 
						void persistenceSupportedChanged();
 | 
				
			||||||
 | 
						void bodySupportedChanged();
 | 
				
			||||||
 | 
						void bodyMarkupSupportedChanged();
 | 
				
			||||||
 | 
						void bodyHyperlinksSupportedChanged();
 | 
				
			||||||
 | 
						void bodyImagesSupportedChanged();
 | 
				
			||||||
 | 
						void actionsSupportedChanged();
 | 
				
			||||||
 | 
						void actionIconsSupportedChanged();
 | 
				
			||||||
 | 
						void imageSupportedChanged();
 | 
				
			||||||
 | 
						void extraHintsChanged();
 | 
				
			||||||
 | 
						void trackedNotificationsChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						void updateSupported();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool live = false;
 | 
				
			||||||
 | 
						bool mKeepOnReload = true;
 | 
				
			||||||
 | 
						NotificationServerSupport support;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::notifications
 | 
				
			||||||
							
								
								
									
										205
									
								
								src/services/notifications/server.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								src/services/notifications/server.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,205 @@
 | 
				
			||||||
 | 
					#include "server.hpp"
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qcoreapplication.h>
 | 
				
			||||||
 | 
					#include <qdbusconnection.h>
 | 
				
			||||||
 | 
					#include <qdbusmetatype.h>
 | 
				
			||||||
 | 
					#include <qdbusservicewatcher.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../core/model.hpp"
 | 
				
			||||||
 | 
					#include "dbus_notifications.h"
 | 
				
			||||||
 | 
					#include "dbusimage.hpp"
 | 
				
			||||||
 | 
					#include "notification.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::notifications {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logNotifications, "quickshell.service.notifications");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NotificationServer::NotificationServer() {
 | 
				
			||||||
 | 
						qDBusRegisterMetaType<DBusNotificationImage>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						new DBusNotificationServer(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCInfo(logNotifications) << "Starting notification server";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto bus = QDBusConnection::sessionBus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!bus.isConnected()) {
 | 
				
			||||||
 | 
							qCWarning(logNotifications) << "Could not connect to DBus. Notification service will not work.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!bus.registerObject("/org/freedesktop/Notifications", this)) {
 | 
				
			||||||
 | 
							qCWarning(logNotifications) << "Could not register Notification server object with DBus. "
 | 
				
			||||||
 | 
							                               "Notification server will not work.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    &this->serviceWatcher,
 | 
				
			||||||
 | 
						    &QDBusServiceWatcher::serviceUnregistered,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    &NotificationServer::onServiceUnregistered
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
 | 
				
			||||||
 | 
						this->serviceWatcher.addWatchedService("org.freedesktop.Notifications");
 | 
				
			||||||
 | 
						this->serviceWatcher.setConnection(bus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NotificationServer::tryRegister();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NotificationServer* NotificationServer::instance() {
 | 
				
			||||||
 | 
						static auto* instance = new NotificationServer(); // NOLINT
 | 
				
			||||||
 | 
						return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServer::switchGeneration(bool reEmit, const std::function<void()>& clearHook) {
 | 
				
			||||||
 | 
						auto notifications = this->mNotifications.valueList();
 | 
				
			||||||
 | 
						this->mNotifications.valueList().clear();
 | 
				
			||||||
 | 
						this->idMap.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clearHook();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (reEmit) {
 | 
				
			||||||
 | 
							for (auto* notification: notifications) {
 | 
				
			||||||
 | 
								notification->setLastGeneration();
 | 
				
			||||||
 | 
								notification->setTracked(false);
 | 
				
			||||||
 | 
								emit this->notification(notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!notification->isTracked()) {
 | 
				
			||||||
 | 
									emit this->NotificationClosed(notification->id(), notification->closeReason());
 | 
				
			||||||
 | 
									delete notification;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									this->idMap.insert(notification->id(), notification);
 | 
				
			||||||
 | 
									this->mNotifications.insertObject(notification);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							for (auto* notification: notifications) {
 | 
				
			||||||
 | 
								emit this->NotificationClosed(notification->id(), NotificationCloseReason::Expired);
 | 
				
			||||||
 | 
								delete notification;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ObjectModel<Notification>* NotificationServer::trackedNotifications() {
 | 
				
			||||||
 | 
						return &this->mNotifications;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServer::deleteNotification(
 | 
				
			||||||
 | 
					    Notification* notification,
 | 
				
			||||||
 | 
					    NotificationCloseReason::Enum reason
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						if (!this->idMap.contains(notification->id())) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit notification->closed(reason);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mNotifications.removeObject(notification);
 | 
				
			||||||
 | 
						this->idMap.remove(notification->id());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->NotificationClosed(notification->id(), reason);
 | 
				
			||||||
 | 
						delete notification;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServer::tryRegister() {
 | 
				
			||||||
 | 
						auto bus = QDBusConnection::sessionBus();
 | 
				
			||||||
 | 
						auto success = bus.registerService("org.freedesktop.Notifications");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (success) {
 | 
				
			||||||
 | 
							qCInfo(logNotifications) << "Registered notification server with dbus.";
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							qCWarning(logNotifications
 | 
				
			||||||
 | 
							) << "Could not register notification server at org.freedesktop.Notifications, presumably "
 | 
				
			||||||
 | 
							     "because one is already registered.";
 | 
				
			||||||
 | 
							qCWarning(logNotifications
 | 
				
			||||||
 | 
							) << "Registration will be attempted again if the active service is unregistered.";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void NotificationServer::onServiceUnregistered(const QString& /*unused*/) {
 | 
				
			||||||
 | 
						qCDebug(logNotifications) << "Active notification server unregistered, attempting registration";
 | 
				
			||||||
 | 
						this->tryRegister();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NotificationServer::CloseNotification(uint id) {
 | 
				
			||||||
 | 
						auto* notification = this->idMap.value(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (notification) {
 | 
				
			||||||
 | 
							this->deleteNotification(notification, NotificationCloseReason::CloseRequested);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QStringList NotificationServer::GetCapabilities() const {
 | 
				
			||||||
 | 
						auto capabilities = QStringList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->support.persistence) capabilities += "persistence";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->support.body) {
 | 
				
			||||||
 | 
							capabilities += "body";
 | 
				
			||||||
 | 
							if (this->support.bodyMarkup) capabilities += "body-markup";
 | 
				
			||||||
 | 
							if (this->support.bodyHyperlinks) capabilities += "body-hyperlinks";
 | 
				
			||||||
 | 
							if (this->support.bodyImages) capabilities += "body-images";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->support.actions) {
 | 
				
			||||||
 | 
							capabilities += "actions";
 | 
				
			||||||
 | 
							if (this->support.actionIcons) capabilities += "action-icons";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->support.image) capabilities += "icon-static";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						capabilities += this->support.extraHints;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return capabilities;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString
 | 
				
			||||||
 | 
					NotificationServer::GetServerInformation(QString& vendor, QString& version, QString& specVersion) {
 | 
				
			||||||
 | 
						vendor = "quickshell";
 | 
				
			||||||
 | 
						version = QCoreApplication::applicationVersion();
 | 
				
			||||||
 | 
						specVersion = "1.2";
 | 
				
			||||||
 | 
						return "quickshell";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint NotificationServer::Notify(
 | 
				
			||||||
 | 
					    const QString& appName,
 | 
				
			||||||
 | 
					    uint replacesId,
 | 
				
			||||||
 | 
					    const QString& appIcon,
 | 
				
			||||||
 | 
					    const QString& summary,
 | 
				
			||||||
 | 
					    const QString& body,
 | 
				
			||||||
 | 
					    const QStringList& actions,
 | 
				
			||||||
 | 
					    const QVariantMap& hints,
 | 
				
			||||||
 | 
					    int expireTimeout
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						auto* notification = replacesId == 0 ? nullptr : this->idMap.value(replacesId);
 | 
				
			||||||
 | 
						auto old = notification != nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!notification) {
 | 
				
			||||||
 | 
							notification = new Notification(this->nextId++, this);
 | 
				
			||||||
 | 
							QQmlEngine::setObjectOwnership(notification, QQmlEngine::CppOwnership);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						notification->updateProperties(appName, appIcon, summary, body, actions, hints, expireTimeout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!old) {
 | 
				
			||||||
 | 
							emit this->notification(notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!notification->isTracked()) {
 | 
				
			||||||
 | 
								emit this->NotificationClosed(notification->id(), notification->closeReason());
 | 
				
			||||||
 | 
								delete notification;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								this->idMap.insert(notification->id(), notification);
 | 
				
			||||||
 | 
								this->mNotifications.insertObject(notification);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return notification->id();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::notifications
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/services/notifications/server.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/services/notifications/server.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdbusservicewatcher.h>
 | 
				
			||||||
 | 
					#include <qhash.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../core/model.hpp"
 | 
				
			||||||
 | 
					#include "notification.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::notifications {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct NotificationServerSupport {
 | 
				
			||||||
 | 
						bool persistence = false;
 | 
				
			||||||
 | 
						bool body = true;
 | 
				
			||||||
 | 
						bool bodyMarkup = false;
 | 
				
			||||||
 | 
						bool bodyHyperlinks = false;
 | 
				
			||||||
 | 
						bool bodyImages = false;
 | 
				
			||||||
 | 
						bool actions = false;
 | 
				
			||||||
 | 
						bool actionIcons = false;
 | 
				
			||||||
 | 
						bool image = false;
 | 
				
			||||||
 | 
						QVector<QString> extraHints;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotificationServer: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						static NotificationServer* instance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void switchGeneration(bool reEmit, const std::function<void()>& clearHook);
 | 
				
			||||||
 | 
						ObjectModel<Notification>* trackedNotifications();
 | 
				
			||||||
 | 
						void deleteNotification(Notification* notification, NotificationCloseReason::Enum reason);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// NOLINTBEGIN
 | 
				
			||||||
 | 
						void CloseNotification(uint id);
 | 
				
			||||||
 | 
						QStringList GetCapabilities() const;
 | 
				
			||||||
 | 
						static QString GetServerInformation(QString& vendor, QString& version, QString& specVersion);
 | 
				
			||||||
 | 
						uint Notify(
 | 
				
			||||||
 | 
						    const QString& appName,
 | 
				
			||||||
 | 
						    uint replacesId,
 | 
				
			||||||
 | 
						    const QString& appIcon,
 | 
				
			||||||
 | 
						    const QString& summary,
 | 
				
			||||||
 | 
						    const QString& body,
 | 
				
			||||||
 | 
						    const QStringList& actions,
 | 
				
			||||||
 | 
						    const QVariantMap& hints,
 | 
				
			||||||
 | 
						    int expireTimeout
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						// NOLINTEND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NotificationServerSupport support;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void notification(Notification* notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// NOLINTBEGIN
 | 
				
			||||||
 | 
						void NotificationClosed(quint32 id, quint32 reason);
 | 
				
			||||||
 | 
						void ActionInvoked(quint32 id, QString action);
 | 
				
			||||||
 | 
						// NOLINTEND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void onServiceUnregistered(const QString& service);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						explicit NotificationServer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static void tryRegister();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QDBusServiceWatcher serviceWatcher;
 | 
				
			||||||
 | 
						quint32 nextId = 1;
 | 
				
			||||||
 | 
						QHash<quint32, Notification*> idMap;
 | 
				
			||||||
 | 
						ObjectModel<Notification> mNotifications {this};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::notifications
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue