#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; } }