From 3558fd3ebcb9010816cd4de73e0314da425956d0 Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Sat, 20 Dec 2025 18:53:19 +0100 Subject: [PATCH] get notifications from dbus --- CMakeLists.txt | 13 +- include/app.hpp | 2 + include/services/notifications.hpp | 20 ++ resources/bar.css | 2 - src/app.cpp | 1 + src/services/notifications.cpp | 298 +++++++++++++++++++++++++++++ 6 files changed, 322 insertions(+), 14 deletions(-) create mode 100644 include/services/notifications.hpp create mode 100644 src/services/notifications.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 07272b8..6365ce7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,18 +9,6 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") find_package(PkgConfig REQUIRED) -# Some CMake versions may enable C++ modules and add compiler flags -# like `-fmodules-ts`. Older or certain `clang`/`clangd` builds may -# not accept this flag and will report "Unknown argument: '-fmodules-ts'". -# Strip that flag when using Clang to avoid diagnostics from clang/clangd. -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") - string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}") - string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") -endif() - pkg_check_modules(GTKMM REQUIRED gtkmm-4.0) pkg_check_modules(LAYERSHELL REQUIRED gtk4-layer-shell-0) pkg_check_modules(WEBKIT REQUIRED webkitgtk-6.0) @@ -40,6 +28,7 @@ target_sources(bar_lib src/widgets/webWidget.cpp src/services/hyprland.cpp src/services/tray.cpp + src/services/notifications.cpp src/widgets/tray.cpp ) include_directories(bar_lib PRIVATE diff --git a/include/app.hpp b/include/app.hpp index 8c67798..9170361 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -6,6 +6,7 @@ #include "glibmm/refptr.h" #include "gtkmm/application.h" #include "services/hyprland.hpp" +#include "services/notifications.hpp" #include "services/tray.hpp" class App { @@ -18,6 +19,7 @@ class App { Glib::RefPtr app; std::vector bars; HyprlandService hyprlandService; + NotificationService notificationService; TrayService trayService; void setupServices(); diff --git a/include/services/notifications.hpp b/include/services/notifications.hpp new file mode 100644 index 0000000..c3e63ef --- /dev/null +++ b/include/services/notifications.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +class NotificationService { +public: + void intialize(); + + NotificationService() = default; + ~NotificationService(); + + guint32 allocateNotificationId(guint32 replacesId); + GDBusConnection* getConnection() const { return connection; } + +private: + GDBusConnection* connection = nullptr; + guint registrationId = 0; + GDBusNodeInfo* nodeInfo = nullptr; + guint32 nextNotificationId = 1; +}; diff --git a/resources/bar.css b/resources/bar.css index 1d5a1f9..91f15a3 100644 --- a/resources/bar.css +++ b/resources/bar.css @@ -44,8 +44,6 @@ window { .workspace-pill-urgent { background-color: #ff5555; color: #fff; - - /* base glow (will be animated) */ animation: workspace-blink 1s linear infinite; } diff --git a/src/app.cpp b/src/app.cpp index 6b47997..be64910 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -6,6 +6,7 @@ App::App() { this->setupServices(); + this->notificationService.intialize(); this->app = Gtk::Application::create("org.example.mybar"); diff --git a/src/services/notifications.cpp b/src/services/notifications.cpp new file mode 100644 index 0000000..f7412e1 --- /dev/null +++ b/src/services/notifications.cpp @@ -0,0 +1,298 @@ +#include "services/notifications.hpp" + +#include +#include + +static constexpr const char* kNotificationsObjectPath = "/org/freedesktop/Notifications"; +static constexpr const char* kNotificationsInterface = "org.freedesktop.Notifications"; + +static const char* kNotificationsIntrospectionXml = R"XML( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +)XML"; + +static void on_method_call(GDBusConnection* /*connection*/, + const gchar* /*sender*/, + const gchar* /*object_path*/, + const gchar* interface_name, + const gchar* method_name, + GVariant* parameters, + GDBusMethodInvocation* invocation, + gpointer user_data) { + auto* self = static_cast(user_data); + + if (g_strcmp0(interface_name, kNotificationsInterface) != 0) { + g_dbus_method_invocation_return_dbus_error( + invocation, + "org.freedesktop.DBus.Error.UnknownInterface", + "Unknown interface"); + return; + } + + if (g_strcmp0(method_name, "Notify") == 0) { + const gchar* app_name = ""; + guint32 replaces_id = 0; + const gchar* app_icon = ""; + const gchar* summary = ""; + const gchar* body = ""; + GVariant* actions = nullptr; + GVariant* hints = nullptr; + gint32 expire_timeout = -1; + + g_variant_get(parameters, "(&su&s&s&s@as@a{sv}i)", + &app_name, + &replaces_id, + &app_icon, + &summary, + &body, + &actions, + &hints, + &expire_timeout); + + std::cout << "--- Notification ---" << std::endl; + std::cout << "App: " << (app_name ? app_name : "") << std::endl; + std::cout << "Title: " << (summary ? summary : "") << std::endl; + std::cout << "Body: " << (body ? body : "") << std::endl; + + if (actions) g_variant_unref(actions); + if (hints) g_variant_unref(hints); + + guint32 id = self->allocateNotificationId(replaces_id); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", id)); + return; + } + + if (g_strcmp0(method_name, "GetCapabilities") == 0) { + // Advertise common capabilities so clients don't disable notifications. + // (Many apps probe this first and may skip Notify if it's empty.) + const gchar* caps[] = { + "body", + "actions", + "body-markup", + "icon-static", + "persistence", + nullptr + }; + GVariant* capsV = g_variant_new_strv(caps, -1); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(@as)", capsV)); + return; + } + + if (g_strcmp0(method_name, "GetServerInformation") == 0) { + g_dbus_method_invocation_return_value( + invocation, + g_variant_new("(ssss)", "bar", "bar", "0.1", "1.2")); + return; + } + + if (g_strcmp0(method_name, "CloseNotification") == 0) { + guint32 id = 0; + g_variant_get(parameters, "(u)", &id); + + // reason: 3 = closed by call to CloseNotification + if (self && self->getConnection()) { + g_dbus_connection_emit_signal( + self->getConnection(), + nullptr, + kNotificationsObjectPath, + kNotificationsInterface, + "NotificationClosed", + g_variant_new("(uu)", id, 3u), + nullptr); + } + + g_dbus_method_invocation_return_value(invocation, nullptr); + return; + } + + g_dbus_method_invocation_return_dbus_error( + invocation, + "org.freedesktop.DBus.Error.UnknownMethod", + "Unknown method"); +} + +guint32 NotificationService::allocateNotificationId(guint32 replacesId) { + if (replacesId != 0) return replacesId; + return this->nextNotificationId++; +} + +static const GDBusInterfaceVTable kVTable = { + .method_call = on_method_call, + .get_property = nullptr, + .set_property = nullptr, +}; + +NotificationService::~NotificationService() { + if (this->connection) { + // Best-effort release of the well-known name. + { + GError* error = nullptr; + GVariant* releaseResult = g_dbus_connection_call_sync( + this->connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ReleaseName", + g_variant_new("(s)", "org.freedesktop.Notifications"), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + if (releaseResult) g_variant_unref(releaseResult); + if (error) g_error_free(error); + } + + if (this->registrationId != 0) { + g_dbus_connection_unregister_object(this->connection, this->registrationId); + this->registrationId = 0; + } + + g_object_unref(this->connection); + this->connection = nullptr; + } + + if (this->nodeInfo) { + g_dbus_node_info_unref(this->nodeInfo); + this->nodeInfo = nullptr; + } +} + +void NotificationService::intialize() { + GError* error = nullptr; + + if (this->connection) { + return; + } + + gchar* address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, nullptr, &error); + if (!address) { + std::cerr << "Failed to get session bus address: " << (error ? error->message : "unknown error") << std::endl; + if (error) g_error_free(error); + return; + } + + if (error) { + g_error_free(error); + error = nullptr; + } + + this->connection = g_dbus_connection_new_for_address_sync( + address, + static_cast( + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), + nullptr, + nullptr, + &error); + g_free(address); + + if (!this->connection) { + std::cerr << "Failed to connect to session bus: " << (error ? error->message : "unknown error") << std::endl; + if (error) g_error_free(error); + return; + } + + if (error) { + g_error_free(error); + error = nullptr; + } + + this->nodeInfo = g_dbus_node_info_new_for_xml(kNotificationsIntrospectionXml, &error); + if (!this->nodeInfo) { + std::cerr << "Failed to create introspection data: " << (error ? error->message : "unknown error") << std::endl; + if (error) g_error_free(error); + return; + } + + GDBusInterfaceInfo* iface = g_dbus_node_info_lookup_interface(this->nodeInfo, kNotificationsInterface); + if (!iface) { + std::cerr << "Missing interface info for org.freedesktop.Notifications" << std::endl; + return; + } + + this->registrationId = g_dbus_connection_register_object( + this->connection, + kNotificationsObjectPath, + iface, + &kVTable, + this, + nullptr, + &error); + + if (this->registrationId == 0) { + std::cerr << "Failed to register notifications object: " << (error ? error->message : "unknown error") << std::endl; + if (error) g_error_free(error); + return; + } + + // Request the well-known name synchronously so we can detect conflicts. + // Reply codes: 1=PRIMARY_OWNER, 2=IN_QUEUE, 3=EXISTS, 4=ALREADY_OWNER + GVariant* requestResult = g_dbus_connection_call_sync( + this->connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "RequestName", + g_variant_new("(su)", "org.freedesktop.Notifications", 0u), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + + if (!requestResult) { + std::cerr << "Failed to RequestName(org.freedesktop.Notifications): " + << (error ? error->message : "unknown error") << std::endl; + if (error) g_error_free(error); + return; + } + + guint32 reply = 0; + g_variant_get(requestResult, "(u)", &reply); + g_variant_unref(requestResult); + + if (reply != 1u && reply != 4u) { + std::cerr << "org.freedesktop.Notifications is already owned (RequestName reply=" << reply + << "). Stop your existing notification daemon (e.g. dunst/mako/swaync) or allow replacement." << std::endl; + return; + } + + std::cout << "Notifications daemon active (org.freedesktop.Notifications)." << std::endl; +}