From 178a4451d4bc551af81ed64516dfb7087a430c2c Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Sun, 1 Feb 2026 16:10:42 +0100 Subject: [PATCH] refactor notifications --- CMakeLists.txt | 53 ++++--- include/services/dbus/messages.hpp | 33 +++++ include/services/dbus/mpris.hpp | 10 -- include/services/dbus/notification.hpp | 4 - include/services/notificationController.hpp | 11 +- include/widgets/notification.hpp | 10 -- .../widgets/notification/baseNotification.hpp | 24 ++++ include/widgets/notification/notification.hpp | 11 ++ .../notification/spotifyNotification.hpp | 16 +++ resources/bar.css | 41 ++---- resources/notification.css | 42 ++++++ src/services/dbus/mpris.cpp | 3 +- src/services/dbus/notification.cpp | 63 ++++++-- src/services/notificationController.cpp | 136 ++++-------------- src/widgets/notification.cpp | 34 ----- src/widgets/notification/baseNotification.cpp | 51 +++++++ src/widgets/notification/notification.cpp | 48 +++++++ .../notification/spotifyNotification.cpp | 95 ++++++++++++ 18 files changed, 449 insertions(+), 236 deletions(-) create mode 100644 include/services/dbus/messages.hpp delete mode 100644 include/widgets/notification.hpp create mode 100644 include/widgets/notification/baseNotification.hpp create mode 100644 include/widgets/notification/notification.hpp create mode 100644 include/widgets/notification/spotifyNotification.hpp create mode 100644 resources/notification.css delete mode 100644 src/widgets/notification.cpp create mode 100644 src/widgets/notification/baseNotification.cpp create mode 100644 src/widgets/notification/notification.cpp create mode 100644 src/widgets/notification/spotifyNotification.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4410f1c..484eae6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,9 @@ target_sources(bar_lib src/widgets/clock.cpp src/widgets/date.cpp - src/widgets/notification.cpp + src/widgets/notification/baseNotification.cpp + src/widgets/notification/notification.cpp + src/widgets/notification/spotifyNotification.cpp src/widgets/volumeWidget.cpp src/widgets/webWidget.cpp @@ -54,28 +56,35 @@ add_executable(bar main.cpp) target_link_libraries(bar bar_lib ${GTKMM_LIBRARIES} ${LAYERSHELL_LIBRARIES} ${WEBKIT_LIBRARIES} ${CURL_LIBRARIES} nlohmann_json::nlohmann_json) -# Copy `resources/bar.css` into the build directory when it changes -set(RES_SRC "${CMAKE_CURRENT_SOURCE_DIR}/resources/bar.css") -set(RES_DST "${CMAKE_CURRENT_BINARY_DIR}/resources/bar.css") -set(USER_CONFIG_CSS "$ENV{HOME}/.config/bar/bar.css") - -add_custom_command( - OUTPUT ${RES_DST} - COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/resources" - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RES_SRC}" "${RES_DST}" - DEPENDS "${RES_SRC}" - COMMENT "Copy resources/bar.css to build directory" - VERBATIM +# Copy all CSS files in resources/ into build and user config directories +file(GLOB RES_CSS_FILES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/resources/*.css" ) +set(RES_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") +set(RES_USER_DIR "$ENV{HOME}/.config/bar") -add_custom_command( - OUTPUT ${USER_CONFIG_CSS} - COMMAND ${CMAKE_COMMAND} -E make_directory "$ENV{HOME}/.config/bar" - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RES_SRC}" "${USER_CONFIG_CSS}" - DEPENDS "${RES_SRC}" - COMMENT "Copy resources/bar.css to ~/.config/bar/bar.css" - VERBATIM -) +set(RES_DST_FILES "") +set(USER_DST_FILES "") -add_custom_target(copy_resources ALL DEPENDS ${RES_DST} ${USER_CONFIG_CSS}) +foreach(RES_FILE IN LISTS RES_CSS_FILES) + get_filename_component(RES_NAME "${RES_FILE}" NAME) + set(RES_DST "${RES_BUILD_DIR}/${RES_NAME}") + set(USER_DST "${RES_USER_DIR}/${RES_NAME}") + + list(APPEND RES_DST_FILES "${RES_DST}") + list(APPEND USER_DST_FILES "${USER_DST}") + + add_custom_command( + OUTPUT "${RES_DST}" "${USER_DST}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${RES_BUILD_DIR}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${RES_USER_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RES_FILE}" "${RES_DST}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RES_FILE}" "${USER_DST}" + DEPENDS "${RES_FILE}" + COMMENT "Copy ${RES_NAME} to build and config directories" + VERBATIM + ) +endforeach() + +add_custom_target(copy_resources ALL DEPENDS ${RES_DST_FILES} ${USER_DST_FILES}) add_dependencies(bar copy_resources) diff --git a/include/services/dbus/messages.hpp b/include/services/dbus/messages.hpp new file mode 100644 index 0000000..658bb3c --- /dev/null +++ b/include/services/dbus/messages.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "glibmm/variant.h" + + + +struct MprisPlayer2Message { + std::string title; + std::string artist; + std::string artwork_url; + + std::function play_pause; + std::function next; + std::function previous; +}; + +struct NotifyMessage { + std::string app_name; + uint32_t replaces_id; + std::string app_icon; + std::string summary; + std::string body; + std::vector actions; + std::map hints; + int32_t expire_timeout; + // Callback to invoke when an action is triggered + std::function on_action; +}; \ No newline at end of file diff --git a/include/services/dbus/mpris.hpp b/include/services/dbus/mpris.hpp index 69d7c28..3c5b6e2 100644 --- a/include/services/dbus/mpris.hpp +++ b/include/services/dbus/mpris.hpp @@ -1,20 +1,10 @@ #pragma once #include -#include #include class MprisController { public: - struct MprisPlayer2Message { - std::string title; - std::string artist; - std::string artwork_url; - - std::function play_pause; - std::function next; - std::function previous; - }; MprisController(); diff --git a/include/services/dbus/notification.hpp b/include/services/dbus/notification.hpp index eddc439..88c1878 100644 --- a/include/services/dbus/notification.hpp +++ b/include/services/dbus/notification.hpp @@ -3,11 +3,8 @@ #include #include #include -#include #include -#include -#include "gdkmm/monitor.h" #include "giomm/dbusconnection.h" #include "giomm/dbusownname.h" #include "glib.h" @@ -43,7 +40,6 @@ const Glib::ustring introspection_xml = R"( )"; class NotificationService { - public: NotificationService() : notificationIdCounter(1) { Gio::DBus::own_name( diff --git a/include/services/notificationController.hpp b/include/services/notificationController.hpp index ded617e..e769945 100644 --- a/include/services/notificationController.hpp +++ b/include/services/notificationController.hpp @@ -2,10 +2,10 @@ #include #include -#include "services/dbus/mpris.hpp" +#include "services/dbus/messages.hpp" #include "gdkmm/monitor.h" -#include "gtkmm/window.h" + class NotificationController { static std::shared_ptr instance; @@ -17,11 +17,10 @@ class NotificationController { return NotificationController::instance; } - void showSpotifyNotification(MprisController::MprisPlayer2Message mpris); - void showNotificationOnAllMonitors(const std::string &title, const std::string &message); + void showSpotifyNotification(MprisPlayer2Message mpris); + void showNotificationOnAllMonitors(NotifyMessage notify); + private: NotificationController(); std::vector> activeMonitors; - - void baseWindowSetup(std::shared_ptr win, std::shared_ptr monitor); }; \ No newline at end of file diff --git a/include/widgets/notification.hpp b/include/widgets/notification.hpp deleted file mode 100644 index 781ea90..0000000 --- a/include/widgets/notification.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include -#include -#include "gdkmm/monitor.h" -class NotificationWidget { - public: - NotificationWidget(std::shared_ptr monitor, const Glib::ustring &title, const Glib::ustring &message); -}; \ No newline at end of file diff --git a/include/widgets/notification/baseNotification.hpp b/include/widgets/notification/baseNotification.hpp new file mode 100644 index 0000000..24e4606 --- /dev/null +++ b/include/widgets/notification/baseNotification.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "helpers/system.hpp" + +#include "gdkmm/monitor.h" +#include "gtk4-layer-shell.h" +#include "gtkmm/cssprovider.h" +#include "gtkmm/window.h" + + +#define DEFAULT_NOTIFICATION_TIMEOUT 4000 + +class BaseNotification : public Gtk::Window { + public: + BaseNotification(std::shared_ptr monitor); + + virtual ~BaseNotification() = default; + + private: + void ensure_notification_css_loaded(); +}; \ No newline at end of file diff --git a/include/widgets/notification/notification.hpp b/include/widgets/notification/notification.hpp new file mode 100644 index 0000000..2e7989f --- /dev/null +++ b/include/widgets/notification/notification.hpp @@ -0,0 +1,11 @@ + +#pragma once + +#include "services/dbus/messages.hpp" +#include "widgets/notification/baseNotification.hpp" + +class NotificationWindow : public BaseNotification { + public: + NotificationWindow(std::shared_ptr monitor, NotifyMessage message); + virtual ~NotificationWindow() = default; +}; diff --git a/include/widgets/notification/spotifyNotification.hpp b/include/widgets/notification/spotifyNotification.hpp new file mode 100644 index 0000000..6381b0c --- /dev/null +++ b/include/widgets/notification/spotifyNotification.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include "services/dbus/messages.hpp" +#include "widgets/notification/baseNotification.hpp" +#include "gtkmm/box.h" +#include "gtkmm/centerbox.h" + +class SpotifyNotification : public BaseNotification { + public: + SpotifyNotification(std::shared_ptr monitor, MprisPlayer2Message message); + virtual ~SpotifyNotification() = default; + private: + std::unique_ptr createButtonBox(MprisPlayer2Message mpris); +}; diff --git a/resources/bar.css b/resources/bar.css index f25404c..2539cf9 100644 --- a/resources/bar.css +++ b/resources/bar.css @@ -3,6 +3,14 @@ all: unset; } +/* variable */ +:root { + --icon-font-material: "Material Icons", sans-serif; + --icon-font-awesome: "Font Awesome 7 Free", sans-serif; + --text-font: "Hack Nerd Font Mono", sans-serif; + --text-font-mono: "Hack Nerd Font Mono", monospace; +} + window { background-color: #191919c6; color: #ffffff; @@ -11,15 +19,12 @@ window { padding-top: 2px; padding-bottom: 2px; font-size: 14px; - font-family: - "Hack Nerd Font Mono", "Font Awesome 7 Brands", "Font Awesome 7 Free", - sans-serif; + font-family: var(--text-font); } popover { margin-top: 4px; - font-family: - "Hack Nerd Font Mono", "Material Icons", "Font Awesome 7 Free", sans-serif; + font-family: var(--text-font); padding: 6px; border-radius: 8px; @@ -38,20 +43,15 @@ tooltip { } button { - font-family: "Material Icons", sans-serif; font-size: 20px; + padding: 3px 6px; } #spacer { font-weight: 900; padding: 0 5px; text-shadow: 0 0 5px #ffffffaa; -} - -.button { - padding: 4px 8px; border-radius: 4px; - font-family: "Material Icons", sans-serif; } .button:hover { @@ -83,12 +83,12 @@ button { .workspace-pill-alive { background-color: rgba(255, 255, 255, 0.153); + color: #ffffff; } .workspace-pill-presenting { background-color: #666666; color: #ffffff; - /* animation: workspace-updown 1.2s ease-in-out infinite; */ } .workspace-pill-focused { @@ -111,7 +111,6 @@ button { animation: workspace-updown 1.2s ease-in-out infinite; margin-left: -4px; margin-top: 4px; - font-size: 12px; } .workspace-pill-seven { @@ -119,7 +118,6 @@ button { animation-delay: 0.6s; margin-right: -4px; margin-top: 4px; - font-size: 12px; } @keyframes workspace-updown { @@ -149,18 +147,3 @@ button { opacity: 1; } } - -.notification-popup { - border-radius: 8px; - padding: 8px 12px; - background: rgba(30, 30, 30, 0.948); - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2); - border: 1px solid rgba(80, 80, 80, 0.8); - font-size: 14px; -} - -.notification-button-box { - margin-top: 8px; - border-top: 1px solid rgba(100, 100, 100, 0.3); - padding-top: 6px; - } diff --git a/resources/notification.css b/resources/notification.css new file mode 100644 index 0000000..fb6864f --- /dev/null +++ b/resources/notification.css @@ -0,0 +1,42 @@ +:root { + --icon-font-material: "Material Icons", sans-serif; + --icon-font-awesome: "Font Awesome 7 Free", sans-serif; + --text-font: "Hack Nerd Font Mono", sans-serif; + --text-font-mono: "Hack Nerd Font Mono", monospace; + + --color-notification-bg: rgba(30, 30, 30, 0.95); + --color-border: rgba(80, 80, 80, 0.8); +} + +.notification-popup { + border-radius: 8px; + padding: 8px 12px; + background: var(--color-notification-bg); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2); + border: 1px solid var(--color-border); + font-size: 14px; +} + +.notification-button-box { + border-top: 1px solid var(--color-border); + padding-top: 6px; +} + +.notification-button { + background-color: #444444; + color: #ffffff; + padding: 4px 8px; + border-radius: 6px; + margin-right: 6px; + font-family: var(--text-font-mono); + font-size: 13px; +} + +.notification-icon-button { + font-family: var(--icon-font-material); + font-size: 16px; +} + +.notification-button:hover { + background-color: #555555; +} \ No newline at end of file diff --git a/src/services/dbus/mpris.cpp b/src/services/dbus/mpris.cpp index 9f6dbba..d72e9bc 100644 --- a/src/services/dbus/mpris.cpp +++ b/src/services/dbus/mpris.cpp @@ -1,4 +1,5 @@ #include "services/dbus/mpris.hpp" +#include "services/dbus/messages.hpp" #include #include @@ -94,7 +95,7 @@ void MprisController::launchNotification() { auto notifactionController = NotificationController::getInstance(); - MprisController::MprisPlayer2Message mpris; + MprisPlayer2Message mpris; mpris.title = StringHelper::trimToSize(title, 30); mpris.artist = StringHelper::trimToSize(artist, 30); mpris.artwork_url = artwork_url; diff --git a/src/services/dbus/notification.cpp b/src/services/dbus/notification.cpp index ef7c636..4951bc9 100644 --- a/src/services/dbus/notification.cpp +++ b/src/services/dbus/notification.cpp @@ -1,8 +1,11 @@ #include "services/dbus/notification.hpp" #include + #include "services/notificationController.hpp" -#include "widgets/notification.hpp" + +#include "glib.h" +#include "glibconfig.h" void NotificationService::onBusAcquired(const Glib::RefPtr &connection, const Glib::ustring &name) { std::cout << "Acquired bus name: " << name << std::endl; @@ -52,23 +55,61 @@ void NotificationService::handle_notify(const Glib::VariantContainerBase ¶me Glib::VariantBase app_name_var, replaces_id_var, app_icon_var, summary_var, body_var, actions_var, hints_var, timeout_var; parameters.get_child(app_name_var, 0); + parameters.get_child(replaces_id_var, 1); + parameters.get_child(app_icon_var, 2); parameters.get_child(summary_var, 3); parameters.get_child(body_var, 4); + parameters.get_child(actions_var, 5); + parameters.get_child(hints_var, 6); + parameters.get_child(timeout_var, 7); - Glib::ustring summary = Glib::VariantBase::cast_dynamic>(summary_var).get(); - Glib::ustring body = Glib::VariantBase::cast_dynamic>(body_var).get(); + Glib::ustring app_name = Glib::VariantBase::cast_dynamic>(app_name_var).get(); + guint32 replaces_id = Glib::VariantBase::cast_dynamic>(replaces_id_var).get(); + Glib::ustring app_icon = Glib::VariantBase::cast_dynamic>(app_icon_var).get(); + Glib::ustring summary = Glib::VariantBase::cast_dynamic>(summary_var).get(); + Glib::ustring body = Glib::VariantBase::cast_dynamic>(body_var).get(); + std::vector actions = Glib::VariantBase::cast_dynamic>>(actions_var).get(); + // std::map hints = Glib::VariantBase::cast_dynamic>>(hints_var).get(); + gint32 expire_timeout = Glib::VariantBase::cast_dynamic>(timeout_var).get(); std::cout << "Notification Received: " << summary << " - " << body << std::endl; - createNotificationPopup(summary, body); + NotifyMessage notify; + notify.app_name = app_name; + notify.replaces_id = replaces_id; + notify.app_icon = app_icon; + notify.summary = summary; + notify.body = body; + + std::vector actions_converted; + actions_converted.reserve(actions.size()); + for (const auto &a : actions) { + actions_converted.emplace_back(static_cast(a)); + } + + notify.actions = actions_converted; + + // notify.hints = hints; + + notify.expire_timeout = expire_timeout; guint id = notificationIdCounter++; + // Set up the callback to emit ActionInvoked on D-Bus + Glib::RefPtr dbus_conn = invocation->get_connection(); + notify.on_action = [dbus_conn, id](const std::string &action_id) { + try { + dbus_conn->emit_signal( + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "ActionInvoked", + "", // destination bus name (empty for broadcast) + Glib::VariantContainerBase::create_tuple({Glib::Variant::create(id), + Glib::Variant::create(action_id)})); + } catch (const std::exception &e) { + std::cerr << "Failed to emit ActionInvoked: " << e.what() << std::endl; + } + }; + NotificationController::getInstance()->showNotificationOnAllMonitors(notify); invocation->return_value(Glib::VariantContainerBase::create_tuple( Glib::Variant::create(id))); -} - -void NotificationService::createNotificationPopup(const Glib::ustring &title, const Glib::ustring &message) { - - auto controller = NotificationController::getInstance(); - controller->showNotificationOnAllMonitors(title, message); -} +} \ No newline at end of file diff --git a/src/services/notificationController.cpp b/src/services/notificationController.cpp index 59f8363..26d93f3 100644 --- a/src/services/notificationController.cpp +++ b/src/services/notificationController.cpp @@ -2,19 +2,15 @@ #include -#include "services/dbus/mpris.hpp" -#include "services/textureCache.hpp" +#include "services/dbus/messages.hpp" +#include "widgets/notification/notification.hpp" +#include "widgets/notification/spotifyNotification.hpp" #include "gdkmm/display.h" -#include "giomm/listmodel.h" #include "glibmm/main.h" -#include "gtk4-layer-shell.h" -#include "gtkmm/box.h" -#include "gtkmm/button.h" -#include "gtkmm/centerbox.h" -#include "gtkmm/image.h" -#include "gtkmm/label.h" -#include "gtkmm/window.h" + + +#define DEFAULT_NOTIFICATION_TIMEOUT 4000 std::shared_ptr NotificationController::instance = nullptr; @@ -41,112 +37,34 @@ NotificationController::NotificationController() { } } -void NotificationController::showSpotifyNotification(MprisController::MprisPlayer2Message mpris) { +void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris) { for (const auto &monitor : this->activeMonitors) { + auto notification = std::make_shared(monitor, mpris); + notification->show(); - auto win = std::make_shared(); - win->set_title(mpris.title); - this->baseWindowSetup(win, monitor); + Glib::signal_timeout().connect([notification]() { + notification->close(); + return false; // Don't repeat + }, + DEFAULT_NOTIFICATION_TIMEOUT); + } +} - auto container = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 10); +void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify) { + for (const auto &monitor : this->activeMonitors) { + auto notification = std::make_shared(monitor, notify); - if (auto texture = TextureCacheService::getInstance()->getTexture(mpris.artwork_url)) { - auto img = Gtk::make_managed(texture); - img->set_pixel_size(64); - container->append(*img); + auto timeout = notify.expire_timeout; + notification->show(); + // -1 means use default timeout, 0 means never expire + if (timeout <= 0) { + timeout = DEFAULT_NOTIFICATION_TIMEOUT; // default to 3 seconds } - auto rightArea = Gtk::make_managed(Gtk::Orientation::VERTICAL, 5); - rightArea->set_halign(Gtk::Align::CENTER); - rightArea->set_valign(Gtk::Align::CENTER); - // make sure rigfht area takes the remaining space - rightArea->set_hexpand(true); - // also set a max width - - auto title_label = Gtk::make_managed("" + mpris.title + ""); - title_label->set_use_markup(true); - title_label->set_halign(Gtk::Align::START); - rightArea->append(*title_label); - - auto artistLabel = Gtk::make_managed(mpris.artist); - artistLabel->set_halign(Gtk::Align::CENTER); - rightArea->append(*artistLabel); - - auto buttonBox = Gtk::make_managed(); - buttonBox->add_css_class("notification-button-box"); - buttonBox->set_hexpand(true); - - auto backButton = Gtk::make_managed("<"); - backButton->add_css_class("flat-button"); - auto playPauseButton = Gtk::make_managed("=<"); - playPauseButton->add_css_class("flat-button"); - auto nextButton = Gtk::make_managed(">"); - nextButton->add_css_class("flat-button"); - - backButton->signal_clicked().connect([mpris]() { - if (mpris.previous) { - mpris.previous(); - } - }); - - playPauseButton->signal_clicked().connect([mpris]() { - if (mpris.play_pause) { - mpris.play_pause(); - } - }); - - nextButton->signal_clicked().connect([mpris]() { - if (mpris.next) { - mpris.next(); - } - }); - buttonBox->set_start_widget(*backButton); - buttonBox->set_center_widget(*playPauseButton); - buttonBox->set_end_widget(*nextButton); - - rightArea->append(*buttonBox); - - container->append(*rightArea); - - win->set_child(*container); - win->show(); - - Glib::signal_timeout().connect([win]() { - win->close(); + Glib::signal_timeout().connect([notification]() { + notification->close(); return false; // Don't repeat }, - 3000); - } -} - -void NotificationController::baseWindowSetup(std::shared_ptr win, std::shared_ptr monitor) { - win->set_default_size(300, 100); - gtk_layer_init_for_window(win->gobj()); - gtk_layer_set_monitor(win->gobj(), monitor->gobj()); - gtk_layer_set_layer(win->gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY); - gtk_layer_set_anchor(win->gobj(), GTK_LAYER_SHELL_EDGE_TOP, TRUE); - gtk_layer_set_anchor(win->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); - gtk_layer_set_margin(win->gobj(), GTK_LAYER_SHELL_EDGE_TOP, 2); - win->add_css_class("notification-popup"); -} - -void NotificationController::showNotificationOnAllMonitors(const std::string &title, const std::string &message) { - for (const auto &monitor : this->activeMonitors) { - auto win = std::make_shared(); - win->set_title(title); - - this->baseWindowSetup(win, monitor); - - auto label = Gtk::make_managed(message); - label->set_use_markup(true); - win->set_child(*label); - - win->show(); - - Glib::signal_timeout().connect([win]() { - win->close(); - return false; // Don't repeat - }, - 3000); + timeout); } } \ No newline at end of file diff --git a/src/widgets/notification.cpp b/src/widgets/notification.cpp deleted file mode 100644 index f587c3b..0000000 --- a/src/widgets/notification.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "widgets/notification.hpp" - -#include "gtk4-layer-shell.h" - -NotificationWidget::NotificationWidget(std::shared_ptr monitor, const Glib::ustring &title, const Glib::ustring &message) { - if (!monitor) return; - - auto win = new Gtk::Window(); - win->set_title(title); - win->set_default_size(300, 100); - - auto label = Gtk::make_managed(message); - label->set_use_markup(true); - win->set_child(*label); - - gtk_layer_init_for_window(win->gobj()); - gtk_layer_set_monitor(win->gobj(), monitor->gobj()); - gtk_layer_set_layer(win->gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY); - gtk_layer_set_anchor(win->gobj(), GTK_LAYER_SHELL_EDGE_TOP, TRUE); - gtk_layer_set_anchor(win->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); - gtk_layer_set_margin(win->gobj(), GTK_LAYER_SHELL_EDGE_TOP, 2); - - win->add_css_class("notification-popup"); - - win->show(); - - // Auto close after 3 seconds for demo purposes - Glib::signal_timeout().connect([win]() { - win->close(); - delete win; - return false; // Don't repeat - }, - 3000); -} diff --git a/src/widgets/notification/baseNotification.cpp b/src/widgets/notification/baseNotification.cpp new file mode 100644 index 0000000..4e5960a --- /dev/null +++ b/src/widgets/notification/baseNotification.cpp @@ -0,0 +1,51 @@ +#include "widgets/notification/baseNotification.hpp" + +#include +#include + +#include "helpers/system.hpp" + +#include "gdkmm/monitor.h" +#include "gtk4-layer-shell.h" +#include "gtkmm/cssprovider.h" + +BaseNotification::BaseNotification(std::shared_ptr monitor) { + ensure_notification_css_loaded(); + set_default_size(300, 100); + gtk_layer_init_for_window(gobj()); + gtk_layer_set_monitor(gobj(), monitor->gobj()); + gtk_layer_set_layer(gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY); + gtk_layer_set_anchor(gobj(), GTK_LAYER_SHELL_EDGE_TOP, TRUE); + gtk_layer_set_anchor(gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); + gtk_layer_set_margin(gobj(), GTK_LAYER_SHELL_EDGE_TOP, 2); + gtk_layer_set_margin(gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, 2); + add_css_class("notification-popup"); +} + +void BaseNotification::ensure_notification_css_loaded() { + static bool loaded = false; + if (loaded) { + return; + } + + auto css_provider = Gtk::CssProvider::create(); + + std::string css_path = "resources/notification.css"; + const char *home = std::getenv("HOME"); + if (home) { + std::filesystem::path config_path = + std::filesystem::path(home) / ".config/bar/notification.css"; + if (std::filesystem::exists(config_path)) { + css_path = config_path.string(); + } + } + + const std::string css = SystemHelper::read_file_to_string(css_path); + css_provider->load_from_data(css); + + Gtk::StyleContext::add_provider_for_display( + Gdk::Display::get_default(), css_provider, + GTK_STYLE_PROVIDER_PRIORITY_USER + 2); + + loaded = true; +} \ No newline at end of file diff --git a/src/widgets/notification/notification.cpp b/src/widgets/notification/notification.cpp new file mode 100644 index 0000000..953865f --- /dev/null +++ b/src/widgets/notification/notification.cpp @@ -0,0 +1,48 @@ +#include "widgets/notification/notification.hpp" + +#include "gtkmm/box.h" +#include "gtkmm/button.h" +#include "gtkmm/label.h" + +NotificationWindow::NotificationWindow(std::shared_ptr monitor, NotifyMessage notify) : BaseNotification(monitor) { + set_title(notify.summary); + + // Main vertical box + auto vbox = Gtk::make_managed(Gtk::Orientation::VERTICAL, 8); + + // Summary label + auto summary_label = Gtk::make_managed("" + notify.summary + ""); + summary_label->set_use_markup(true); + summary_label->set_halign(Gtk::Align::START); + vbox->append(*summary_label); + + // Body label + auto body_label = Gtk::make_managed(notify.body); + body_label->set_use_markup(true); + body_label->set_halign(Gtk::Align::START); + vbox->append(*body_label); + + // If actions exist, add buttons + if (!notify.actions.empty()) { + auto actions_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 6); + for (size_t i = 0; i + 1 < notify.actions.size(); i += 2) { + std::string action_id = notify.actions[i]; + std::string action_label = notify.actions[i + 1]; + + auto btn = Gtk::make_managed(); + btn->set_label(action_label); + + btn->add_css_class("notification-button"); + btn->signal_clicked().connect([this, action_id, cb = notify.on_action]() { + if (cb) { + cb(action_id); + this->close(); + } + }); + actions_box->append(*btn); + } + vbox->append(*actions_box); + } + + set_child(*vbox); +} \ No newline at end of file diff --git a/src/widgets/notification/spotifyNotification.cpp b/src/widgets/notification/spotifyNotification.cpp new file mode 100644 index 0000000..dc97704 --- /dev/null +++ b/src/widgets/notification/spotifyNotification.cpp @@ -0,0 +1,95 @@ +#include "widgets/notification/spotifyNotification.hpp" + +#include "services/textureCache.hpp" + +#include "gtkmm/box.h" +#include "gtkmm/button.h" +#include "gtkmm/centerbox.h" +#include "gtkmm/image.h" +#include "gtkmm/label.h" + +SpotifyNotification::SpotifyNotification(std::shared_ptr monitor, MprisPlayer2Message mpris) : BaseNotification(monitor) { + auto container = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 10); + container->set_hexpand(true); + + if (auto texture = TextureCacheService::getInstance()->getTexture(mpris.artwork_url)) { + auto img = Gtk::make_managed(texture); + img->set_pixel_size(64); + container->append(*img); + } + + auto rightArea = Gtk::make_managed(Gtk::Orientation::VERTICAL, 5); + rightArea->set_halign(Gtk::Align::FILL); + rightArea->set_valign(Gtk::Align::CENTER); + rightArea->set_hexpand(true); + rightArea->set_size_request(220, -1); + + auto title_label = Gtk::make_managed("" + mpris.title + ""); + title_label->set_use_markup(true); + title_label->set_hexpand(true); + title_label->set_halign(Gtk::Align::CENTER); + title_label->set_ellipsize(Pango::EllipsizeMode::END); + + auto artistLabel = Gtk::make_managed(mpris.artist); + artistLabel->set_hexpand(true); + artistLabel->set_halign(Gtk::Align::CENTER); + + auto buttonBox = createButtonBox(mpris); + + rightArea->append(*artistLabel); + rightArea->append(*title_label); + rightArea->append(*buttonBox); + + container->append(*rightArea); + + set_child(*container); +} + +std::unique_ptr SpotifyNotification::createButtonBox(MprisPlayer2Message mpris) { + auto buttonBox = std::make_unique(); + buttonBox->add_css_class("notification-button-box"); + buttonBox->set_hexpand(true); + buttonBox->set_halign(Gtk::Align::CENTER); + + auto backButton = Gtk::make_managed("\ue045"); + backButton->add_css_class("notification-icon-button"); + backButton->add_css_class("notification-button"); + backButton->signal_clicked().connect([this, mpris]() { + if (mpris.previous) { + mpris.previous(); + this->close(); + } + }); + + auto playPauseButton = Gtk::make_managed("\ue037"); + playPauseButton->add_css_class("notification-icon-button"); + playPauseButton->add_css_class("notification-button"); + playPauseButton->signal_clicked().connect([playPauseButton, mpris]() { + if (mpris.play_pause) { + mpris.play_pause(); + + static bool isPlaying = false; + if (isPlaying) { + playPauseButton->set_label("\ue037"); + } else { + playPauseButton->set_label("\ue034"); + } + isPlaying = !isPlaying; + } + }); + + auto nextButton = Gtk::make_managed("\ue044"); + nextButton->add_css_class("notification-icon-button"); + nextButton->add_css_class("notification-button"); + nextButton->signal_clicked().connect([this, mpris]() { + if (mpris.next) { + mpris.next(); + this->close(); + } + }); + buttonBox->set_start_widget(*backButton); + buttonBox->set_center_widget(*playPauseButton); + buttonBox->set_end_widget(*nextButton); + + return buttonBox; +} \ No newline at end of file