#include "services/dbus/notification.hpp" #include #include #include "helpers/string.hpp" #include "services/notificationController.hpp" #include "glib.h" #include "glibconfig.h" void NotificationService::onBusAcquired(const Glib::RefPtr &connection, const Glib::ustring &name) { spdlog::info("Acquired bus name: {}", name.raw()); auto introspection_data = Gio::DBus::NodeInfo::create_for_xml(introspection_xml); // Register the object at the standard path connection->register_object( "/org/freedesktop/Notifications", introspection_data->lookup_interface("org.freedesktop.Notifications"), getMessageInterfaceVTable()); } const Gio::DBus::InterfaceVTable &NotificationService::getMessageInterfaceVTable() { static Gio::DBus::InterfaceVTable vtable( sigc::mem_fun(*this, &NotificationService::on_method_call)); return vtable; } void NotificationService::on_method_call(const Glib::RefPtr &, const Glib::ustring &, const Glib::ustring &, const Glib::ustring &, const Glib::ustring &method_name, const Glib::VariantContainerBase ¶meters, const Glib::RefPtr &invocation) { if (method_name == "Notify") { handle_notify(parameters, invocation); } else if (method_name == "GetCapabilities") { auto caps = std::vector{ "body", "actions", "actions-icons", "persistence", "icon-static" }; invocation->return_value(Glib::VariantContainerBase::create_tuple( Glib::Variant>::create(caps))); } else if (method_name == "GetServerInformation") { invocation->return_value(Glib::VariantContainerBase::create_tuple({Glib::Variant::create("MyGtkmmNotifier"), Glib::Variant::create("Custom"), Glib::Variant::create("1.0"), Glib::Variant::create("1.2")})); } else { // Handle other methods or return error invocation->return_value(Glib::VariantContainerBase()); } } void NotificationService::handle_notify(const Glib::VariantContainerBase ¶meters, const Glib::RefPtr &invocation) { 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 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(); spdlog::info("Notification Received: {} - {}", summary.raw(), body.raw()); NotifyMessage notify; notify.app_name = app_name; notify.replaces_id = replaces_id; notify.app_icon = app_icon; notify.summary = static_cast(summary); notify.body = static_cast(body); std::vector actions_converted; actions_converted.reserve(actions.size()); for (ulong i = 0; i < actions.size(); i += 2) { auto name = static_cast(actions[i]); auto label = static_cast(actions[i + 1]); if (name == "default") { label = "Open"; } actions_converted.push_back(name); actions_converted.push_back(label); } notify.actions = actions_converted; for (const auto &[key, value] : hints) { if (key == "urgency") { if (value.is_of_type(Glib::VariantType("y"))) { auto urgency = Glib::VariantBase::cast_dynamic>(value).get(); notify.urgency = static_cast(urgency); } } if (key == "image-data") { Glib::VariantBase image_value = value; if (image_value.is_of_type(Glib::VariantType("v"))) { try { image_value = Glib::VariantBase::cast_dynamic>(image_value).get(); } catch (const std::bad_cast &) { spdlog::warn("Failed to unwrap image-data variant"); } } if (image_value.is_of_type(Glib::VariantType("(iiibiiay)"))) { // This is a raw data image format which describes width, height, rowstride, has alpha, // bits per sample, channels and image data respectively. try { auto image_data_variant = Glib::VariantBase::cast_dynamic< Glib::Variant>>>(image_value); auto [width, height, rowstride, has_alpha, bits_per_sample, channels, data] = image_data_variant.get(); (void)channels; auto pixbuf = Gdk::Pixbuf::create_from_data( data.data(), Gdk::Colorspace::RGB, has_alpha, bits_per_sample, width, height, rowstride); notify.imageData = pixbuf; } catch (const std::bad_cast &e) { spdlog::warn("Failed to decode image-data hint: {}", e.what()); } } } } notify.expire_timeout = expire_timeout; if (app_name == "Thunderbird") { notify.expire_timeout = 10000; // 10 seconds for email notifications } 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) { spdlog::error("Failed to emit ActionInvoked: {}", e.what()); } }; NotificationController::getInstance()->showNotificationOnAllMonitors(notify); invocation->return_value(Glib::VariantContainerBase::create_tuple( Glib::Variant::create(id))); }