diff --git a/src/dbus.c b/src/dbus.c index 5bb6210..0f8f93a 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -8,6 +8,7 @@ #include "dunst.h" #include "log.h" +#include "menu.h" #include "notification.h" #include "queues.h" #include "settings.h" @@ -17,6 +18,12 @@ #define FDN_IFAC "org.freedesktop.Notifications" #define FDN_NAME "org.freedesktop.Notifications" +#define DUNST_PATH "/org/freedesktop/Notifications" +#define DUNST_IFAC "org.dunstproject.cmd0" +#define DUNST_NAME "org.freedesktop.Notifications" + +#define PROPERTIES_IFAC "org.freedesktop.DBus.Properties" + GDBusConnection *dbus_conn; static GDBusNodeInfo *introspection_data = NULL; @@ -62,7 +69,19 @@ static const char *introspection_xml = " " " " " " - " " + " " + " " + + " " + " " + " " + " " + + " " + " " + " " + + " " ""; static const char *stack_tag_hints[] = { @@ -80,6 +99,7 @@ struct dbus_method { GDBusMethodInvocation *invocation); }; +// TODO: call it interface methods #define DBUS_METHOD(name) static void dbus_cb_##name( \ GDBusConnection *connection, \ const gchar *sender, \ @@ -98,7 +118,6 @@ DBUS_METHOD(Notify); DBUS_METHOD(CloseNotification); DBUS_METHOD(GetCapabilities); DBUS_METHOD(GetServerInformation); - static struct dbus_method methods_fdn[] = { {"CloseNotification", dbus_cb_CloseNotification}, {"GetCapabilities", dbus_cb_GetCapabilities}, @@ -106,7 +125,7 @@ static struct dbus_method methods_fdn[] = { {"Notify", dbus_cb_Notify}, }; -void handle_method_call(GDBusConnection *connection, +void dbus_cb_fdn_methods(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, @@ -115,12 +134,12 @@ void handle_method_call(GDBusConnection *connection, GDBusMethodInvocation *invocation, gpointer user_data) { - struct dbus_method *m = bsearch( - method_name, - &methods_fdn, - G_N_ELEMENTS(methods_fdn), - sizeof(struct dbus_method), - cmp_methods); + + struct dbus_method *m = bsearch(method_name, + methods_fdn, + G_N_ELEMENTS(methods_fdn), + sizeof(struct dbus_method), + cmp_methods); if (m) { m->method(connection, sender, parameters, invocation); @@ -131,6 +150,98 @@ void handle_method_call(GDBusConnection *connection, } } +DBUS_METHOD(dunst_ContextMenuCall); +DBUS_METHOD(dunst_NotificationCloseAll); +DBUS_METHOD(dunst_NotificationCloseLast); +DBUS_METHOD(dunst_NotificationShow); +static struct dbus_method methods_dunst[] = { + {"ContextMenuCall", dbus_cb_dunst_ContextMenuCall}, + {"NotificationCloseAll", dbus_cb_dunst_NotificationCloseAll}, + {"NotificationCloseLast", dbus_cb_dunst_NotificationCloseLast}, + {"NotificationShow", dbus_cb_dunst_NotificationShow}, +}; + +void dbus_cb_dunst_methods(GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + + struct dbus_method *m = bsearch(method_name, + methods_dunst, + G_N_ELEMENTS(methods_dunst), + sizeof(struct dbus_method), + cmp_methods); + + if (m) { + m->method(connection, sender, parameters, invocation); + } else { + LOG_M("Unknown method name: '%s' (sender: '%s').", + method_name, + sender); + } +} + +static void dbus_cb_dunst_ContextMenuCall(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + LOG_D("CMD: Calling context menu"); + context_menu(); + + g_dbus_method_invocation_return_value(invocation, NULL); + g_dbus_connection_flush(connection, NULL, NULL, NULL); +} + +static void dbus_cb_dunst_NotificationCloseAll(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + LOG_D("CMD: Pushing all to history"); + queues_history_push_all(); + wake_up(); + + g_dbus_method_invocation_return_value(invocation, NULL); + g_dbus_connection_flush(connection, NULL, NULL, NULL); +} + +static void dbus_cb_dunst_NotificationCloseLast(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + LOG_D("CMD: Closing last notification"); + const GList *list = queues_get_displayed(); + if (list && list->data) { + struct notification *n = list->data; + queues_notification_close_id(n->id, REASON_USER); + wake_up(); + } + + g_dbus_method_invocation_return_value(invocation, NULL); + g_dbus_connection_flush(connection, NULL, NULL, NULL); +} + +static void dbus_cb_dunst_NotificationShow(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + LOG_D("CMD: Showing last notification from history"); + queues_history_pop(); + wake_up(); + + g_dbus_method_invocation_return_value(invocation, NULL); + g_dbus_connection_flush(connection, NULL, NULL, NULL); +} + + static void dbus_cb_GetCapabilities( GDBusConnection *connection, const gchar *sender, @@ -347,11 +458,9 @@ static void dbus_cb_GetServerInformation( GVariant *parameters, GDBusMethodInvocation *invocation) { - GVariant *value; - - value = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2"); - g_dbus_method_invocation_return_value(invocation, value); + GVariant *answer = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2"); + g_dbus_method_invocation_return_value(invocation, answer); g_dbus_connection_flush(connection, NULL, NULL, NULL); } @@ -420,28 +529,112 @@ void signal_action_invoked(const struct notification *n, const char *identifier) } } -static const GDBusInterfaceVTable interface_vtable = { - handle_method_call +//FIXME: Is this necessary or alternative question: Is this implemented correctl? +// This had been an old relict from the manual times, when I haven't used the +// interface vtable of GLib +void dbus_signal_status_changed(struct dunst_status status) +{ + // We might have not a working connection yet, so just ignore it. + if (!dbus_conn) + return; + + //TODO: I'm pretty sure this is the right format string, but I don't know how to verify it + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE("(sa{sv}as)")); + g_variant_builder_add(&builder, "s", DUNST_IFAC); + + g_variant_builder_open(&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add(&builder, "{sv}", "running", g_variant_new_boolean(status.running)); + g_variant_builder_close(&builder); + + g_variant_builder_open(&builder, G_VARIANT_TYPE ("as")); + g_variant_builder_add(&builder, "s", "unrelated"); + g_variant_builder_close(&builder); + + GError *err = NULL; + g_dbus_connection_emit_signal(dbus_conn, + NULL, + DUNST_PATH, + PROPERTIES_IFAC, + "PropertiesChanged", + g_variant_builder_end(&builder), + &err); + + if (err) { + LOG_W("Unable send signal 'PropertiesChanged': %s", err->message); + g_error_free(err); + } +} + +GVariant *dbus_cb_dunst_Properties_Get(GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + struct dunst_status status = dunst_status_get(); + + if (STR_EQ(property_name, "running")) + return g_variant_new_boolean(status.running); + else + //TODO: is NULL as return value allowed? + return NULL; +} + +gboolean dbus_cb_dunst_Properties_Set(GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + if (STR_EQ(property_name, "running")) + dunst_status(S_RUNNING, g_variant_get_boolean(value)); + + return true; +} + + +static const GDBusInterfaceVTable interface_vtable_fdn = { + dbus_cb_fdn_methods +}; + +static const GDBusInterfaceVTable interface_vtable_dunst = { + dbus_cb_dunst_methods, + dbus_cb_dunst_Properties_Get, + dbus_cb_dunst_Properties_Set, }; static void dbus_cb_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { - guint registration_id; - + // TODO: deduplicate the code GError *err = NULL; + if(!g_dbus_connection_register_object( + connection, + FDN_PATH, + introspection_data->interfaces[0], + &interface_vtable_fdn, + NULL, + NULL, + &err)) { + DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[0]->name, err->message); + } - registration_id = g_dbus_connection_register_object(connection, - FDN_PATH, - introspection_data->interfaces[0], - &interface_vtable, - NULL, - NULL, - &err); - - if (registration_id == 0) { - DIE("Unable to register dbus connection: %s", err->message); + if(!g_dbus_connection_register_object( + connection, + FDN_PATH, + introspection_data->interfaces[1], + &interface_vtable_dunst, + NULL, + NULL, + &err)) { + DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[1]->name, err->message); } } @@ -449,7 +642,9 @@ static void dbus_cb_name_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { - dbus_conn = connection; + // If we're not able to get org.fd.N bus, we've still got a problem + if (STR_EQ(name, FDN_NAME)) + dbus_conn = connection; } /** diff --git a/src/dbus.h b/src/dbus.h index 4a72479..9301055 100644 --- a/src/dbus.h +++ b/src/dbus.h @@ -3,6 +3,7 @@ #ifndef DUNST_DBUS_H #define DUNST_DBUS_H +#include "dunst.h" #include "notification.h" /// The reasons according to the notification spec @@ -20,5 +21,12 @@ void dbus_teardown(int id); void signal_notification_closed(struct notification *n, enum reason reason); void signal_action_invoked(const struct notification *n, const char *identifier); +/** + * Signal a changed status via DBus + * + * @param status The current status of dunst + * */ +void dbus_signal_status_changed(struct dunst_status status); + #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/dunst.c b/src/dunst.c index 8ba2d15..85b900d 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -31,20 +31,26 @@ static struct dunst_status status; void dunst_status(const enum dunst_status_field field, bool value) { + bool changed; switch (field) { case S_FULLSCREEN: + changed = status.fullscreen != value; status.fullscreen = value; break; case S_IDLE: + changed = status.idle != value; status.idle = value; break; case S_RUNNING: + changed = status.running != value; status.running = value; break; default: LOG_E("Invalid %s enum value in %s:%d", "dunst_status", __FILE__, __LINE__); break; } + if (changed) + dbus_signal_status_changed(status); } /* see dunst.h */ diff --git a/test/dbus.c b/test/dbus.c index 659731a..5db9a7b 100644 --- a/test/dbus.c +++ b/test/dbus.c @@ -799,6 +799,12 @@ TEST assert_methodlists_sorted(void) methods_fdn[i+1].method_name)); } + for (size_t i = 0; i+1 < G_N_ELEMENTS(methods_dunst); i++) { + ASSERT(0 > strcmp( + methods_dunst[i].method_name, + methods_dunst[i+1].method_name)); + } + PASS(); } diff --git a/test/dunst.c b/test/dunst.c index 827f98d..476c518 100644 --- a/test/dunst.c +++ b/test/dunst.c @@ -1,6 +1,15 @@ +#define dbus_signal_status_changed(status) signal_sent_stub(status) #include "../src/dunst.c" #include "greatest.h" +static bool signal_sent = false; + +void signal_sent_stub(struct dunst_status status) +{ + signal_sent = true; + return; +} + TEST test_dunst_status(void) { status = (struct dunst_status) {false, false, false};