Add DBus interface to control dunst
This commit is contained in:
		
							parent
							
								
									0d038021a6
								
							
						
					
					
						commit
						55f4971a92
					
				
							
								
								
									
										251
									
								
								src/dbus.c
									
									
									
									
									
								
							
							
						
						
									
										251
									
								
								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 = | ||||
|     "            <arg name=\"id\"         type=\"u\"/>" | ||||
|     "            <arg name=\"action_key\" type=\"s\"/>" | ||||
|     "        </signal>" | ||||
|     "   </interface>" | ||||
|     "    </interface>" | ||||
|     "    <interface name=\""DUNST_IFAC"\">" | ||||
| 
 | ||||
|     "        <method name=\"ContextMenuCall\"       />" | ||||
|     "        <method name=\"NotificationCloseLast\" />" | ||||
|     "        <method name=\"NotificationCloseAll\"  />" | ||||
|     "        <method name=\"NotificationShow\"      />" | ||||
| 
 | ||||
|     "        <property name=\"running\" type=\"b\" access=\"readwrite\">" | ||||
|     "            <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" | ||||
|     "        </property>" | ||||
| 
 | ||||
|     "    </interface>" | ||||
|     "</node>"; | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | ||||
| @ -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: */ | ||||
|  | ||||
| @ -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 */ | ||||
|  | ||||
| @ -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(); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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}; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Benedikt Heine
						Benedikt Heine