From 6d9f016350f2557786dab828f70b7f4dd854d90f Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Mon, 2 Feb 2026 21:47:47 +0100 Subject: [PATCH] close one notification to close all --- include/services/notificationController.hpp | 11 +++- .../widgets/notification/baseNotification.hpp | 22 +++++--- .../notification/notificationWindow.hpp | 4 +- .../notification/spotifyNotification.hpp | 3 +- resources/notification.css | 17 ++++++ src/services/notificationController.cpp | 49 ++++++++++++++--- src/widgets/notification/baseNotification.cpp | 4 +- .../notification/notificationWindow.cpp | 53 ++++++++++++++----- .../notification/spotifyNotification.cpp | 7 +-- 9 files changed, 137 insertions(+), 33 deletions(-) diff --git a/include/services/notificationController.hpp b/include/services/notificationController.hpp index e769945..b95ce99 100644 --- a/include/services/notificationController.hpp +++ b/include/services/notificationController.hpp @@ -1,8 +1,13 @@ #pragma once +#include +#include #include +#include #include + #include "services/dbus/messages.hpp" +#include "widgets/notification/baseNotification.hpp" #include "gdkmm/monitor.h" @@ -21,6 +26,10 @@ class NotificationController { void showNotificationOnAllMonitors(NotifyMessage notify); private: + uint64_t globalNotificationId = 1; + std::map>> activeNotifications; NotificationController(); std::vector> activeMonitors; -}; \ No newline at end of file + + void closeNotification(uint64_t notificationId); + }; \ No newline at end of file diff --git a/include/widgets/notification/baseNotification.hpp b/include/widgets/notification/baseNotification.hpp index 67884ec..a84e3db 100644 --- a/include/widgets/notification/baseNotification.hpp +++ b/include/widgets/notification/baseNotification.hpp @@ -1,24 +1,30 @@ #pragma once -#include -#include - -#include "helpers/system.hpp" +#include +#include #include "gdkmm/monitor.h" -#include "gtk4-layer-shell.h" -#include "gtkmm/cssprovider.h" #include "gtkmm/window.h" - #define DEFAULT_NOTIFICATION_TIMEOUT 7000 + class BaseNotification : public Gtk::Window { public: - BaseNotification(std::shared_ptr monitor); + BaseNotification( uint64_t notificationId, std::shared_ptr monitor); + + sigc::signal signal_close; virtual ~BaseNotification() = default; + uint64_t getNotificationId() const { + return this->notificationId; + } + private: void ensure_notification_css_loaded(); + + // onClose signal can be added here if needed + protected: + uint64_t notificationId; }; \ No newline at end of file diff --git a/include/widgets/notification/notificationWindow.hpp b/include/widgets/notification/notificationWindow.hpp index 2e7989f..5780259 100644 --- a/include/widgets/notification/notificationWindow.hpp +++ b/include/widgets/notification/notificationWindow.hpp @@ -1,11 +1,13 @@ #pragma once +#include #include "services/dbus/messages.hpp" #include "widgets/notification/baseNotification.hpp" class NotificationWindow : public BaseNotification { public: - NotificationWindow(std::shared_ptr monitor, NotifyMessage message); + NotificationWindow(uint64_t notificationId, std::shared_ptr monitor, NotifyMessage message); virtual ~NotificationWindow() = default; + }; diff --git a/include/widgets/notification/spotifyNotification.hpp b/include/widgets/notification/spotifyNotification.hpp index 87db4c3..1a885b4 100644 --- a/include/widgets/notification/spotifyNotification.hpp +++ b/include/widgets/notification/spotifyNotification.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "services/dbus/messages.hpp" @@ -9,7 +10,7 @@ class SpotifyNotification : public BaseNotification { public: - SpotifyNotification(std::shared_ptr monitor, MprisPlayer2Message message); + SpotifyNotification(uint64_t notificationId, std::shared_ptr monitor, MprisPlayer2Message message); virtual ~SpotifyNotification() = default; private: diff --git a/resources/notification.css b/resources/notification.css index a634032..360b59f 100644 --- a/resources/notification.css +++ b/resources/notification.css @@ -69,4 +69,21 @@ .notification-button:hover { background-color: #555555; +} + +.notification-app-icon { + padding-right: 8px; + border-radius: 4px; + border: 1px solid var(--color-border); +} + +.notification-app-name { + font-weight: 600; + font-size: 12px; + color: #ffffff; +} + +.notification-header { + margin-bottom: 6px; + border-bottom: 1px solid var(--color-border); } \ No newline at end of file diff --git a/src/services/notificationController.cpp b/src/services/notificationController.cpp index c68f031..e6e1833 100644 --- a/src/services/notificationController.cpp +++ b/src/services/notificationController.cpp @@ -3,12 +3,13 @@ #include #include "services/dbus/messages.hpp" +#include "widgets/notification/baseNotification.hpp" #include "widgets/notification/notificationWindow.hpp" #include "widgets/notification/spotifyNotification.hpp" #include "gdkmm/display.h" #include "glibmm/main.h" - +#include "sigc++/adaptors/bind.h" std::shared_ptr NotificationController::instance = nullptr; @@ -37,10 +38,20 @@ NotificationController::NotificationController() { void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris) { return; - + + std::vector> notifications; + + uint64_t id = this->globalNotificationId++; + for (const auto &monitor : this->activeMonitors) { - auto notification = std::make_shared(monitor, mpris); + auto notification = std::make_shared(id, monitor, mpris); notification->show(); + notifications.push_back(notification); + notification->signal_close.connect([this, id = notification->getNotificationId()](int) { + closeNotification(id); + }); + + this->activeNotifications[id] = notifications; Glib::signal_timeout().connect([notification]() { notification->close(); @@ -48,19 +59,30 @@ void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris) }, DEFAULT_NOTIFICATION_TIMEOUT); } + + this->activeNotifications[id] = notifications; } void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify) { + uint64_t id = this->globalNotificationId++; + std::vector> notifications; + for (const auto &monitor : this->activeMonitors) { - auto notification = std::make_shared(monitor, notify); + auto notification = std::make_shared(id, monitor, notify); + notifications.push_back(notification); auto timeout = notify.expire_timeout; notification->show(); // -1 means use default timeout, 0 means never expire if (timeout <= 0) { - timeout = DEFAULT_NOTIFICATION_TIMEOUT; + timeout = DEFAULT_NOTIFICATION_TIMEOUT; } + notification->signal_close.connect( + [this, id = notification->getNotificationId()](int) { + closeNotification(id); + }); + if (timeout == 0) { continue; } @@ -71,4 +93,19 @@ void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify) }, timeout); } -} \ No newline at end of file + + this->activeNotifications[id] = notifications; +} + +void NotificationController::closeNotification(uint64_t notificationId) { + if (this->activeNotifications.find(notificationId) == this->activeNotifications.end()) { + return; + } + + auto notifications = this->activeNotifications[notificationId]; + for (const auto ¬ification : notifications) { + notification->close(); + } + + this->activeNotifications.erase(notificationId); +} diff --git a/src/widgets/notification/baseNotification.cpp b/src/widgets/notification/baseNotification.cpp index a0db67b..7d55769 100644 --- a/src/widgets/notification/baseNotification.cpp +++ b/src/widgets/notification/baseNotification.cpp @@ -9,7 +9,7 @@ #include "gtk4-layer-shell.h" #include "gtkmm/cssprovider.h" -BaseNotification::BaseNotification(std::shared_ptr monitor) { +BaseNotification::BaseNotification(uint64_t notificationId, std::shared_ptr monitor) { ensure_notification_css_loaded(); set_default_size(350, -1); gtk_layer_init_for_window(gobj()); @@ -23,6 +23,8 @@ BaseNotification::BaseNotification(std::shared_ptr monitor) { this->set_hexpand(false); this->set_vexpand(false); + + this->notificationId = notificationId; } void BaseNotification::ensure_notification_css_loaded() { diff --git a/src/widgets/notification/notificationWindow.cpp b/src/widgets/notification/notificationWindow.cpp index 72c3b1b..362a4c3 100644 --- a/src/widgets/notification/notificationWindow.cpp +++ b/src/widgets/notification/notificationWindow.cpp @@ -1,25 +1,34 @@ #include "widgets/notification/notificationWindow.hpp" +#include +#include #include "helpers/string.hpp" #include "gtkmm/box.h" #include "gtkmm/button.h" +#include "gtkmm/gestureclick.h" #include "gtkmm/image.h" #include "gtkmm/label.h" -NotificationWindow::NotificationWindow(std::shared_ptr monitor, NotifyMessage notify) : BaseNotification(monitor) { +NotificationWindow::NotificationWindow(uint64_t notificationId, std::shared_ptr monitor, NotifyMessage notify) : BaseNotification(notificationId, monitor) { set_title(notify.summary); - if (notify.imageData) { - auto img = Gtk::make_managed(*(notify.imageData)); - img->set_pixel_size(64); - img->set_halign(Gtk::Align::CENTER); - img->set_valign(Gtk::Align::CENTER); - img->add_css_class("notification-image"); - set_child(*img); - } + auto window_click = Gtk::GestureClick::create(); + window_click->set_button(GDK_BUTTON_PRIMARY); + window_click->set_exclusive(false); + window_click->signal_released().connect([this](int, double x, double y) { + Gtk::Widget *picked = this->pick(x, y, Gtk::PickFlags::DEFAULT); + for (auto *w = picked; w != nullptr; w = w->get_parent()) { + if (dynamic_cast(w) != nullptr) { + return; + } + } + this->signal_close.emit(this->notificationId); + }); + add_controller(window_click); // Main vertical box auto vbox = Gtk::make_managed(Gtk::Orientation::VERTICAL, 8); + vbox->set_halign(Gtk::Align::FILL); switch (notify.urgency) { case NotificationUrgency::CRITICAL: @@ -31,10 +40,30 @@ NotificationWindow::NotificationWindow(std::shared_ptr monitor, No case NotificationUrgency::LOW: add_css_class("notification-low"); break; - default: - break; } + auto header_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + header_box->set_halign(Gtk::Align::START); + header_box->add_css_class("notification-header"); + header_box->set_hexpand(true); + header_box->set_halign(Gtk::Align::FILL); + + if (notify.imageData) { + auto img = Gtk::make_managed(*(notify.imageData)); + img->set_pixel_size(16); + img->set_halign(Gtk::Align::START); + img->set_valign(Gtk::Align::CENTER); + img->add_css_class("notification-app-icon"); + header_box->append(*img); + } + + auto app_label = Gtk::make_managed(StringHelper::trimToSize(notify.app_name, 24)); + app_label->set_halign(Gtk::Align::START); + app_label->set_wrap(false); + app_label->add_css_class("notification-app-name"); + header_box->append(*app_label); + vbox->append(*header_box); + // Summary label auto summary_label = Gtk::make_managed("" + StringHelper::trimToSize(notify.summary, 20) + ""); summary_label->set_use_markup(true); @@ -62,7 +91,7 @@ NotificationWindow::NotificationWindow(std::shared_ptr monitor, No btn->signal_clicked().connect([this, action_id, cb = notify.on_action]() { if (cb) { cb(action_id); - this->close(); + this->signal_close.emit(this->notificationId); } }); actions_box->append(*btn); diff --git a/src/widgets/notification/spotifyNotification.cpp b/src/widgets/notification/spotifyNotification.cpp index e33afb4..b0f1dc3 100644 --- a/src/widgets/notification/spotifyNotification.cpp +++ b/src/widgets/notification/spotifyNotification.cpp @@ -1,4 +1,5 @@ #include "widgets/notification/spotifyNotification.hpp" +#include #include "helpers/string.hpp" #include "services/textureCache.hpp" @@ -9,7 +10,7 @@ #include "gtkmm/image.h" #include "gtkmm/label.h" -SpotifyNotification::SpotifyNotification(std::shared_ptr monitor, MprisPlayer2Message mpris) : BaseNotification(monitor) { +SpotifyNotification::SpotifyNotification(uint64_t notificationId, std::shared_ptr monitor, MprisPlayer2Message mpris) : BaseNotification(notificationId, monitor) { auto container = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 10); container->set_hexpand(true); @@ -59,7 +60,7 @@ std::unique_ptr SpotifyNotification::createButtonBox(MprisPlayer backButton->signal_clicked().connect([this, mpris]() { if (mpris.previous) { mpris.previous(); - this->close(); + this->signal_close.emit(this->notificationId); } }); @@ -86,7 +87,7 @@ std::unique_ptr SpotifyNotification::createButtonBox(MprisPlayer nextButton->signal_clicked().connect([this, mpris]() { if (mpris.next) { mpris.next(); - this->close(); + this->signal_close.emit(this->notificationId); } }); buttonBox->set_start_widget(*backButton);