1 /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
2 #include "dbus.h"
3 
4 #include <gio/gio.h>
5 #include <glib.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 
9 #include "dunst.h"
10 #include "log.h"
11 #include "menu.h"
12 #include "notification.h"
13 #include "queues.h"
14 #include "settings.h"
15 #include "utils.h"
16 
17 #define FDN_PATH "/org/freedesktop/Notifications"
18 #define FDN_IFAC "org.freedesktop.Notifications"
19 #define FDN_NAME "org.freedesktop.Notifications"
20 
21 #define DUNST_PATH "/org/freedesktop/Notifications"
22 #define DUNST_IFAC "org.dunstproject.cmd0"
23 #define DUNST_NAME "org.freedesktop.Notifications"
24 
25 #define PROPERTIES_IFAC "org.freedesktop.DBus.Properties"
26 
27 GDBusConnection *dbus_conn;
28 
29 static GDBusNodeInfo *introspection_data = NULL;
30 
31 static const char *introspection_xml =
32     "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
33     "<node name=\""FDN_PATH"\">"
34     "    <interface name=\""FDN_IFAC"\">"
35 
36     "        <method name=\"GetCapabilities\">"
37     "            <arg direction=\"out\" name=\"capabilities\"    type=\"as\"/>"
38     "        </method>"
39 
40     "        <method name=\"Notify\">"
41     "            <arg direction=\"in\"  name=\"app_name\"        type=\"s\"/>"
42     "            <arg direction=\"in\"  name=\"replaces_id\"     type=\"u\"/>"
43     "            <arg direction=\"in\"  name=\"app_icon\"        type=\"s\"/>"
44     "            <arg direction=\"in\"  name=\"summary\"         type=\"s\"/>"
45     "            <arg direction=\"in\"  name=\"body\"            type=\"s\"/>"
46     "            <arg direction=\"in\"  name=\"actions\"         type=\"as\"/>"
47     "            <arg direction=\"in\"  name=\"hints\"           type=\"a{sv}\"/>"
48     "            <arg direction=\"in\"  name=\"expire_timeout\"  type=\"i\"/>"
49     "            <arg direction=\"out\" name=\"id\"              type=\"u\"/>"
50     "        </method>"
51 
52     "        <method name=\"CloseNotification\">"
53     "            <arg direction=\"in\"  name=\"id\"              type=\"u\"/>"
54     "        </method>"
55 
56     "        <method name=\"GetServerInformation\">"
57     "            <arg direction=\"out\" name=\"name\"            type=\"s\"/>"
58     "            <arg direction=\"out\" name=\"vendor\"          type=\"s\"/>"
59     "            <arg direction=\"out\" name=\"version\"         type=\"s\"/>"
60     "            <arg direction=\"out\" name=\"spec_version\"    type=\"s\"/>"
61     "        </method>"
62 
63     "        <signal name=\"NotificationClosed\">"
64     "            <arg name=\"id\"         type=\"u\"/>"
65     "            <arg name=\"reason\"     type=\"u\"/>"
66     "        </signal>"
67 
68     "        <signal name=\"ActionInvoked\">"
69     "            <arg name=\"id\"         type=\"u\"/>"
70     "            <arg name=\"action_key\" type=\"s\"/>"
71     "        </signal>"
72     "    </interface>"
73     "    <interface name=\""DUNST_IFAC"\">"
74 
75     "        <method name=\"ContextMenuCall\"       />"
76     "        <method name=\"NotificationAction\">"
77     "            <arg name=\"number\"     type=\"i\"/>"
78     "        </method>"
79     "        <method name=\"NotificationCloseLast\" />"
80     "        <method name=\"NotificationCloseAll\"  />"
81     "        <method name=\"NotificationShow\"      />"
82     "        <method name=\"Ping\"                  />"
83 
84     "        <property name=\"paused\" type=\"b\" access=\"readwrite\">"
85     "            <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
86     "        </property>"
87 
88     "        <property name=\"displayedLength\" type=\"u\" access=\"read\" />"
89     "        <property name=\"historyLength\" type=\"u\" access=\"read\" />"
90     "        <property name=\"waitingLength\" type=\"u\" access=\"read\" />"
91 
92     "    </interface>"
93     "</node>";
94 
95 static const char *stack_tag_hints[] = {
96         "synchronous",
97         "private-synchronous",
98         "x-canonical-private-synchronous",
99         "x-dunst-stack-tag"
100 };
101 
102 struct dbus_method {
103   const char *method_name;
104   void (*method)  (GDBusConnection *connection,
105                    const gchar *sender,
106                    GVariant *parameters,
107                    GDBusMethodInvocation *invocation);
108 };
109 
110 #define DBUS_METHOD(name) static void dbus_cb_##name( \
111                         GDBusConnection *connection, \
112                         const gchar *sender, \
113                         GVariant *parameters, \
114                         GDBusMethodInvocation *invocation)
115 
cmp_methods(const void * vkey,const void * velem)116 int cmp_methods(const void *vkey, const void *velem)
117 {
118         const char *key = (const char*)vkey;
119         const struct dbus_method *m = (const struct dbus_method*)velem;
120 
121         return strcmp(key, m->method_name);
122 }
123 
124 DBUS_METHOD(Notify);
125 DBUS_METHOD(CloseNotification);
126 DBUS_METHOD(GetCapabilities);
127 DBUS_METHOD(GetServerInformation);
128 static struct dbus_method methods_fdn[] = {
129         {"CloseNotification",     dbus_cb_CloseNotification},
130         {"GetCapabilities",       dbus_cb_GetCapabilities},
131         {"GetServerInformation",  dbus_cb_GetServerInformation},
132         {"Notify",                dbus_cb_Notify},
133 };
134 
dbus_cb_fdn_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)135 void dbus_cb_fdn_methods(GDBusConnection *connection,
136                         const gchar *sender,
137                         const gchar *object_path,
138                         const gchar *interface_name,
139                         const gchar *method_name,
140                         GVariant *parameters,
141                         GDBusMethodInvocation *invocation,
142                         gpointer user_data)
143 {
144 
145         struct dbus_method *m = bsearch(method_name,
146                                         methods_fdn,
147                                         G_N_ELEMENTS(methods_fdn),
148                                         sizeof(struct dbus_method),
149                                         cmp_methods);
150 
151         if (m) {
152                 m->method(connection, sender, parameters, invocation);
153         } else {
154                 LOG_M("Unknown method name: '%s' (sender: '%s').",
155                       method_name,
156                       sender);
157         }
158 }
159 
160 DBUS_METHOD(dunst_ContextMenuCall);
161 DBUS_METHOD(dunst_NotificationAction);
162 DBUS_METHOD(dunst_NotificationCloseAll);
163 DBUS_METHOD(dunst_NotificationCloseLast);
164 DBUS_METHOD(dunst_NotificationShow);
165 DBUS_METHOD(dunst_Ping);
166 static struct dbus_method methods_dunst[] = {
167         {"ContextMenuCall",        dbus_cb_dunst_ContextMenuCall},
168         {"NotificationAction",     dbus_cb_dunst_NotificationAction},
169         {"NotificationCloseAll",   dbus_cb_dunst_NotificationCloseAll},
170         {"NotificationCloseLast",  dbus_cb_dunst_NotificationCloseLast},
171         {"NotificationShow",       dbus_cb_dunst_NotificationShow},
172         {"Ping",                   dbus_cb_dunst_Ping},
173 };
174 
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)175 void dbus_cb_dunst_methods(GDBusConnection *connection,
176                            const gchar *sender,
177                            const gchar *object_path,
178                            const gchar *interface_name,
179                            const gchar *method_name,
180                            GVariant *parameters,
181                            GDBusMethodInvocation *invocation,
182                            gpointer user_data)
183 {
184 
185         struct dbus_method *m = bsearch(method_name,
186                                         methods_dunst,
187                                         G_N_ELEMENTS(methods_dunst),
188                                         sizeof(struct dbus_method),
189                                         cmp_methods);
190 
191         if (m) {
192                 m->method(connection, sender, parameters, invocation);
193         } else {
194                 LOG_M("Unknown method name: '%s' (sender: '%s').",
195                       method_name,
196                       sender);
197         }
198 }
199 
dbus_cb_dunst_ContextMenuCall(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)200 static void dbus_cb_dunst_ContextMenuCall(GDBusConnection *connection,
201                                           const gchar *sender,
202                                           GVariant *parameters,
203                                           GDBusMethodInvocation *invocation)
204 {
205         LOG_D("CMD: Calling context menu");
206         context_menu();
207 
208         g_dbus_method_invocation_return_value(invocation, NULL);
209         g_dbus_connection_flush(connection, NULL, NULL, NULL);
210 }
211 
dbus_cb_dunst_NotificationAction(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)212 static void dbus_cb_dunst_NotificationAction(GDBusConnection *connection,
213                                              const gchar *sender,
214                                              GVariant *parameters,
215                                              GDBusMethodInvocation *invocation)
216 {
217         int notification_nr = 0;
218         g_variant_get(parameters, "(i)", &notification_nr);
219 
220         LOG_D("CMD: Calling action for notification %d", notification_nr);
221 
222         if (notification_nr < 0 || queues_length_waiting() < notification_nr) {
223                 g_dbus_method_invocation_return_error(invocation,
224                         G_DBUS_ERROR,
225                         G_DBUS_ERROR_INVALID_ARGS,
226                         "Couldn't activate action for notification in position %d, %d notifications currently open",
227                         notification_nr, queues_length_waiting());
228                 return;
229         }
230 
231         struct notification *n = g_list_nth_data(queues_get_displayed(), notification_nr);
232 
233         if (n) {
234                 LOG_D("CMD: Calling action for notification %s", n->summary);
235                 notification_do_action(n);
236         }
237 
238         g_dbus_method_invocation_return_value(invocation, NULL);
239         g_dbus_connection_flush(connection, NULL, NULL, NULL);
240 }
241 
dbus_cb_dunst_NotificationCloseAll(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)242 static void dbus_cb_dunst_NotificationCloseAll(GDBusConnection *connection,
243                                               const gchar *sender,
244                                               GVariant *parameters,
245                                               GDBusMethodInvocation *invocation)
246 {
247         LOG_D("CMD: Pushing all to history");
248         queues_history_push_all();
249         wake_up();
250 
251         g_dbus_method_invocation_return_value(invocation, NULL);
252         g_dbus_connection_flush(connection, NULL, NULL, NULL);
253 }
254 
dbus_cb_dunst_NotificationCloseLast(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)255 static void dbus_cb_dunst_NotificationCloseLast(GDBusConnection *connection,
256                                                 const gchar *sender,
257                                                 GVariant *parameters,
258                                                 GDBusMethodInvocation *invocation)
259 {
260         LOG_D("CMD: Closing last notification");
261         const GList *list = queues_get_displayed();
262         if (list && list->data) {
263                 struct notification *n = list->data;
264                 queues_notification_close_id(n->id, REASON_USER);
265                 wake_up();
266         }
267 
268         g_dbus_method_invocation_return_value(invocation, NULL);
269         g_dbus_connection_flush(connection, NULL, NULL, NULL);
270 }
271 
dbus_cb_dunst_NotificationShow(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)272 static void dbus_cb_dunst_NotificationShow(GDBusConnection *connection,
273                                            const gchar *sender,
274                                            GVariant *parameters,
275                                            GDBusMethodInvocation *invocation)
276 {
277         LOG_D("CMD: Showing last notification from history");
278         queues_history_pop();
279         wake_up();
280 
281         g_dbus_method_invocation_return_value(invocation, NULL);
282         g_dbus_connection_flush(connection, NULL, NULL, NULL);
283 }
284 
285 /* Just a simple Ping command to give the ability to dunstctl to test for the existence of this interface
286  * Any other way requires parsing the XML of the Introspection or other foo. Just calling the Ping on an old dunst version will fail. */
dbus_cb_dunst_Ping(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)287 static void dbus_cb_dunst_Ping(GDBusConnection *connection,
288                                const gchar *sender,
289                                GVariant *parameters,
290                                GDBusMethodInvocation *invocation)
291 {
292         g_dbus_method_invocation_return_value(invocation, NULL);
293         g_dbus_connection_flush(connection, NULL, NULL, NULL);
294 }
295 
296 
dbus_cb_GetCapabilities(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)297 static void dbus_cb_GetCapabilities(
298                 GDBusConnection *connection,
299                 const gchar *sender,
300                 GVariant *parameters,
301                 GDBusMethodInvocation *invocation)
302 {
303         GVariantBuilder *builder;
304         GVariant *value;
305 
306         builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
307         g_variant_builder_add(builder, "s", "actions");
308         g_variant_builder_add(builder, "s", "body");
309         g_variant_builder_add(builder, "s", "body-hyperlinks");
310 
311         for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i)
312                 g_variant_builder_add(builder, "s", stack_tag_hints[i]);
313 
314         if (settings.markup != MARKUP_NO)
315                 g_variant_builder_add(builder, "s", "body-markup");
316 
317         value = g_variant_new("(as)", builder);
318         g_clear_pointer(&builder, g_variant_builder_unref);
319         g_dbus_method_invocation_return_value(invocation, value);
320 
321         g_dbus_connection_flush(connection, NULL, NULL, NULL);
322 }
323 
dbus_message_to_notification(const gchar * sender,GVariant * parameters)324 static struct notification *dbus_message_to_notification(const gchar *sender, GVariant *parameters)
325 {
326         /* Assert that the parameters' type is actually correct. Albeit usually DBus
327          * already rejects ill typed parameters, it may not be always the case. */
328         GVariantType *required_type = g_variant_type_new("(susssasa{sv}i)");
329         if (!g_variant_is_of_type(parameters, required_type)) {
330                 g_variant_type_free(required_type);
331                 return NULL;
332         }
333 
334         struct notification *n = notification_create();
335         n->dbus_client = g_strdup(sender);
336         n->dbus_valid = true;
337 
338         GVariant *hints;
339         gchar **actions;
340         int timeout;
341 
342         GVariantIter i;
343         g_variant_iter_init(&i, parameters);
344 
345         g_variant_iter_next(&i, "s", &n->appname);
346         g_variant_iter_next(&i, "u", &n->id);
347         g_variant_iter_next(&i, "s", &n->iconname);
348         g_variant_iter_next(&i, "s", &n->summary);
349         g_variant_iter_next(&i, "s", &n->body);
350         g_variant_iter_next(&i, "^a&s", &actions);
351         g_variant_iter_next(&i, "@a{?*}", &hints);
352         g_variant_iter_next(&i, "i", &timeout);
353 
354         gsize num = 0;
355         while (actions[num]) {
356                 if (actions[num+1]) {
357                         g_hash_table_insert(n->actions,
358                                         g_strdup(actions[num]),
359                                         g_strdup(actions[num+1]));
360                         num+=2;
361                 } else {
362                         LOG_W("Odd length in actions array. Ignoring element: %s", actions[num]);
363                         break;
364                 }
365         }
366 
367         GVariant *dict_value;
368         if ((dict_value = g_variant_lookup_value(hints, "urgency", G_VARIANT_TYPE_BYTE))) {
369                 n->urgency = g_variant_get_byte(dict_value);
370                 g_variant_unref(dict_value);
371         }
372 
373         if ((dict_value = g_variant_lookup_value(hints, "fgcolor", G_VARIANT_TYPE_STRING))) {
374                 n->colors.fg = g_variant_dup_string(dict_value, NULL);
375                 g_variant_unref(dict_value);
376         }
377 
378         if ((dict_value = g_variant_lookup_value(hints, "bgcolor", G_VARIANT_TYPE_STRING))) {
379                 n->colors.bg = g_variant_dup_string(dict_value, NULL);
380                 g_variant_unref(dict_value);
381         }
382 
383         if ((dict_value = g_variant_lookup_value(hints, "frcolor", G_VARIANT_TYPE_STRING))) {
384                 n->colors.frame = g_variant_dup_string(dict_value, NULL);
385                 g_variant_unref(dict_value);
386         }
387 
388         if ((dict_value = g_variant_lookup_value(hints, "category", G_VARIANT_TYPE_STRING))) {
389                 n->category = g_variant_dup_string(dict_value, NULL);
390                 g_variant_unref(dict_value);
391         }
392 
393         if ((dict_value = g_variant_lookup_value(hints, "desktop-entry", G_VARIANT_TYPE_STRING))) {
394                 n->desktop_entry = g_variant_dup_string(dict_value, NULL);
395                 g_variant_unref(dict_value);
396         }
397 
398         if ((dict_value = g_variant_lookup_value(hints, "image-path", G_VARIANT_TYPE_STRING))) {
399                 g_free(n->iconname);
400                 n->iconname = g_variant_dup_string(dict_value, NULL);
401                 g_variant_unref(dict_value);
402         }
403 
404         dict_value = g_variant_lookup_value(hints, "image-data", G_VARIANT_TYPE("(iiibiiay)"));
405         if (!dict_value)
406                 dict_value = g_variant_lookup_value(hints, "image_data", G_VARIANT_TYPE("(iiibiiay)"));
407         if (!dict_value)
408                 dict_value = g_variant_lookup_value(hints, "icon_data", G_VARIANT_TYPE("(iiibiiay)"));
409         if (dict_value) {
410                 notification_icon_replace_data(n, dict_value);
411                 g_variant_unref(dict_value);
412         }
413 
414         /* Check for transient hints
415          *
416          * According to the spec, the transient hint should be boolean.
417          * But notify-send does not support hints of type 'boolean'.
418          * So let's check for int and boolean until notify-send is fixed.
419          */
420         if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_BOOLEAN))) {
421                 n->transient = g_variant_get_boolean(dict_value);
422                 g_variant_unref(dict_value);
423         } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_UINT32))) {
424                 n->transient = g_variant_get_uint32(dict_value) > 0;
425                 g_variant_unref(dict_value);
426         } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_INT32))) {
427                 n->transient = g_variant_get_int32(dict_value) > 0;
428                 g_variant_unref(dict_value);
429         }
430 
431         if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_INT32))) {
432                 n->progress = g_variant_get_int32(dict_value);
433                 g_variant_unref(dict_value);
434         } else if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_UINT32))) {
435                 n->progress = g_variant_get_uint32(dict_value);
436                 g_variant_unref(dict_value);
437         }
438 
439         /* Check for hints that define the stack_tag
440          *
441          * Only accept to first one we find.
442          */
443         for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) {
444                 if ((dict_value = g_variant_lookup_value(hints, stack_tag_hints[i], G_VARIANT_TYPE_STRING))) {
445                         n->stack_tag = g_variant_dup_string(dict_value, NULL);
446                         g_variant_unref(dict_value);
447                         break;
448                 }
449         }
450 
451         if (timeout >= 0)
452                 n->timeout = ((gint64)timeout) * 1000;
453 
454         g_variant_unref(hints);
455         g_variant_type_free(required_type);
456         g_free(actions); // the strv is only a shallow copy
457 
458         notification_init(n);
459         return n;
460 }
461 
dbus_cb_Notify(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)462 static void dbus_cb_Notify(
463                 GDBusConnection *connection,
464                 const gchar *sender,
465                 GVariant *parameters,
466                 GDBusMethodInvocation *invocation)
467 {
468         struct notification *n = dbus_message_to_notification(sender, parameters);
469         if (!n) {
470                 LOG_W("A notification failed to decode.");
471                 g_dbus_method_invocation_return_dbus_error(
472                                 invocation,
473                                 FDN_IFAC".Error",
474                                 "Cannot decode notification!");
475                 return;
476         }
477 
478         int id = queues_notification_insert(n);
479 
480         GVariant *reply = g_variant_new("(u)", id);
481         g_dbus_method_invocation_return_value(invocation, reply);
482         g_dbus_connection_flush(connection, NULL, NULL, NULL);
483 
484         // The message got discarded
485         if (id == 0) {
486                 signal_notification_closed(n, REASON_USER);
487                 notification_unref(n);
488         }
489 
490         wake_up();
491 }
492 
dbus_cb_CloseNotification(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)493 static void dbus_cb_CloseNotification(
494                 GDBusConnection *connection,
495                 const gchar *sender,
496                 GVariant *parameters,
497                 GDBusMethodInvocation *invocation)
498 {
499         guint32 id;
500         g_variant_get(parameters, "(u)", &id);
501         if (settings.ignore_dbusclose) {
502                 LOG_D("Ignoring CloseNotification message");
503                 // Stay commpliant by lying to the sender,  telling him we closed the notification
504                 if (id > 0) {
505                         struct notification *n = queues_get_by_id(id);
506                         if (n)
507                                 signal_notification_closed(n, REASON_SIG);
508                 }
509         } else {
510                 queues_notification_close_id(id, REASON_SIG);
511         }
512         wake_up();
513         g_dbus_method_invocation_return_value(invocation, NULL);
514         g_dbus_connection_flush(connection, NULL, NULL, NULL);
515 }
516 
dbus_cb_GetServerInformation(GDBusConnection * connection,const gchar * sender,GVariant * parameters,GDBusMethodInvocation * invocation)517 static void dbus_cb_GetServerInformation(
518                 GDBusConnection *connection,
519                 const gchar *sender,
520                 GVariant *parameters,
521                 GDBusMethodInvocation *invocation)
522 {
523         GVariant *answer = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2");
524 
525         g_dbus_method_invocation_return_value(invocation, answer);
526         g_dbus_connection_flush(connection, NULL, NULL, NULL);
527 }
528 
signal_notification_closed(struct notification * n,enum reason reason)529 void signal_notification_closed(struct notification *n, enum reason reason)
530 {
531         if (!n->dbus_valid) {
532                 return;
533         }
534 
535         if (reason < REASON_MIN || REASON_MAX < reason) {
536                 LOG_W("Closing notification with reason '%d' not supported. "
537                       "Closing it with reason '%d'.", reason, REASON_UNDEF);
538                 reason = REASON_UNDEF;
539         }
540 
541         if (!dbus_conn) {
542                 LOG_E("Unable to close notification: No DBus connection.");
543         }
544 
545         GVariant *body = g_variant_new("(uu)", n->id, reason);
546         GError *err = NULL;
547 
548         g_dbus_connection_emit_signal(dbus_conn,
549                                       n->dbus_client,
550                                       FDN_PATH,
551                                       FDN_IFAC,
552                                       "NotificationClosed",
553                                       body,
554                                       &err);
555 
556         notification_invalidate_actions(n);
557 
558         n->dbus_valid = false;
559 
560         if (err) {
561                 LOG_W("Unable to close notification: %s", err->message);
562                 g_error_free(err);
563         } else {
564                 char* reason_string;
565                 switch (reason) {
566                         case REASON_TIME:
567                                 reason_string="time";
568                                 break;
569                         case REASON_USER:
570                                 reason_string="user";
571                                 break;
572                         case REASON_SIG:
573                                 reason_string="signal";
574                                 break;
575                         case REASON_UNDEF:
576                                 reason_string="undfined";
577                                 break;
578                         default:
579                                 reason_string="unknown";
580                 }
581 
582                 LOG_D("Queues: Closing notification for reason: %s", reason_string);
583 
584         }
585 
586 }
587 
signal_action_invoked(const struct notification * n,const char * identifier)588 void signal_action_invoked(const struct notification *n, const char *identifier)
589 {
590         if (!n->dbus_valid) {
591                 LOG_W("Invoking action '%s' not supported. "
592                       "Notification already closed.", identifier);
593                 return;
594         }
595 
596         GVariant *body = g_variant_new("(us)", n->id, identifier);
597         GError *err = NULL;
598 
599         g_dbus_connection_emit_signal(dbus_conn,
600                                       n->dbus_client,
601                                       FDN_PATH,
602                                       FDN_IFAC,
603                                       "ActionInvoked",
604                                       body,
605                                       &err);
606 
607         if (err) {
608                 LOG_W("Unable to invoke action: %s", err->message);
609                 g_error_free(err);
610         }
611 }
612 
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)613 GVariant *dbus_cb_dunst_Properties_Get(GDBusConnection *connection,
614                                        const gchar *sender,
615                                        const gchar *object_path,
616                                        const gchar *interface_name,
617                                        const gchar *property_name,
618                                        GError **error,
619                                        gpointer user_data)
620 {
621         struct dunst_status status = dunst_status_get();
622 
623         if (STR_EQ(property_name, "paused")) {
624                 return g_variant_new_boolean(!status.running);
625         } else if (STR_EQ(property_name, "displayedLength")) {
626                 unsigned int displayed = queues_length_displayed();
627                 return g_variant_new_uint32(displayed);
628         } else if (STR_EQ(property_name, "historyLength")) {
629                 unsigned int history =  queues_length_history();
630                 return g_variant_new_uint32(history);
631         } else if (STR_EQ(property_name, "waitingLength")) {
632                 unsigned int waiting =  queues_length_waiting();
633                 return g_variant_new_uint32(waiting);
634         } else {
635                 LOG_W("Unknown property!\n");
636                 *error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property");
637                 return NULL;
638         }
639 }
640 
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)641 gboolean dbus_cb_dunst_Properties_Set(GDBusConnection *connection,
642                                       const gchar *sender,
643                                       const gchar *object_path,
644                                       const gchar *interface_name,
645                                       const gchar *property_name,
646                                       GVariant *value,
647                                       GError **error,
648                                       gpointer user_data)
649 {
650         if (STR_EQ(property_name, "paused")) {
651                 dunst_status(S_RUNNING, !g_variant_get_boolean(value));
652                 wake_up();
653 
654                 GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
655                 GVariantBuilder *invalidated_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
656                 g_variant_builder_add(builder,
657                                       "{sv}",
658                                       "paused", g_variant_new_boolean(g_variant_get_boolean(value)));
659                 g_dbus_connection_emit_signal(connection,
660                                               NULL,
661                                               object_path,
662                                               "org.freedesktop.DBus.Properties",
663                                               "PropertiesChanged",
664                                               g_variant_new("(sa{sv}as)",
665                                                             interface_name,
666                                                             builder,
667                                                             invalidated_builder),
668                                               NULL);
669                 return true;
670         }
671 
672         *error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property");
673 
674         return false;
675 }
676 
677 
678 static const GDBusInterfaceVTable interface_vtable_fdn = {
679         dbus_cb_fdn_methods
680 };
681 
682 static const GDBusInterfaceVTable interface_vtable_dunst = {
683         dbus_cb_dunst_methods,
684         dbus_cb_dunst_Properties_Get,
685         dbus_cb_dunst_Properties_Set,
686 };
687 
dbus_cb_bus_acquired(GDBusConnection * connection,const gchar * name,gpointer user_data)688 static void dbus_cb_bus_acquired(GDBusConnection *connection,
689                                  const gchar *name,
690                                  gpointer user_data)
691 {
692         GError *err = NULL;
693         if(!g_dbus_connection_register_object(
694                                 connection,
695                                 FDN_PATH,
696                                 introspection_data->interfaces[0],
697                                 &interface_vtable_fdn,
698                                 NULL,
699                                 NULL,
700                                 &err)) {
701                 DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[0]->name, err->message);
702         }
703 
704         if(!g_dbus_connection_register_object(
705                                 connection,
706                                 FDN_PATH,
707                                 introspection_data->interfaces[1],
708                                 &interface_vtable_dunst,
709                                 NULL,
710                                 NULL,
711                                 &err)) {
712                 DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[1]->name, err->message);
713         }
714 }
715 
dbus_cb_name_acquired(GDBusConnection * connection,const gchar * name,gpointer user_data)716 static void dbus_cb_name_acquired(GDBusConnection *connection,
717                                   const gchar *name,
718                                   gpointer user_data)
719 {
720         // If we're not able to get org.fd.N bus, we've still got a problem
721         if (STR_EQ(name, FDN_NAME))
722                 dbus_conn = connection;
723 }
724 
725 /**
726  * Get the PID of the current process, which acquired FDN DBus Name.
727  *
728  * If name or vendor specified, the name and vendor
729  * will get additionally get via the FDN GetServerInformation method
730  *
731  * @param connection The DBus connection
732  * @param pid The place to report the PID to
733  * @param name The place to report the name to, if not required set to NULL
734  * @param vendor The place to report the vendor to, if not required set to NULL
735  *
736  * @retval true: on success
737  * @retval false: Any error happened
738  */
dbus_get_fdn_daemon_info(GDBusConnection * connection,guint * pid,char ** name,char ** vendor)739 static bool dbus_get_fdn_daemon_info(GDBusConnection  *connection,
740                                      guint   *pid,
741                                      char   **name,
742                                      char   **vendor)
743 {
744         ASSERT_OR_RET(pid, false);
745         ASSERT_OR_RET(connection, false);
746 
747         char *owner = NULL;
748         GError *error = NULL;
749 
750         GDBusProxy *proxy_fdn;
751         GDBusProxy *proxy_dbus;
752 
753         proxy_fdn = g_dbus_proxy_new_sync(
754                                      connection,
755                                      /* do not trigger a start of the notification daemon */
756                                      G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
757                                      NULL, /* info */
758                                      FDN_NAME,
759                                      FDN_PATH,
760                                      FDN_IFAC,
761                                      NULL, /* cancelable */
762                                      &error);
763 
764         if (error) {
765                 g_error_free(error);
766                 return false;
767         }
768 
769         GVariant *daemoninfo = NULL;
770         if (name || vendor) {
771                 daemoninfo = g_dbus_proxy_call_sync(
772                                      proxy_fdn,
773                                      FDN_IFAC ".GetServerInformation",
774                                      NULL,
775                                      G_DBUS_CALL_FLAGS_NONE,
776                                      /* It's not worth to wait for the info
777                                       * longer than half a second when dying */
778                                      500,
779                                      NULL, /* cancelable */
780                                      &error);
781         }
782 
783         if (error) {
784                 /* Ignore the error, we may still be able to retrieve the PID */
785                 g_clear_pointer(&error, g_error_free);
786         } else {
787                 g_variant_get(daemoninfo, "(ssss)", name, vendor, NULL, NULL);
788         }
789 
790         owner = g_dbus_proxy_get_name_owner(proxy_fdn);
791 
792         proxy_dbus = g_dbus_proxy_new_sync(
793                                      connection,
794                                      G_DBUS_PROXY_FLAGS_NONE,
795                                      NULL, /* info */
796                                      "org.freedesktop.DBus",
797                                      "/org/freedesktop/DBus",
798                                      "org.freedesktop.DBus",
799                                      NULL, /* cancelable */
800                                      &error);
801 
802         if (error) {
803                 g_error_free(error);
804                 return false;
805         }
806 
807         GVariant *pidinfo = g_dbus_proxy_call_sync(
808                                      proxy_dbus,
809                                      "org.freedesktop.DBus.GetConnectionUnixProcessID",
810                                      g_variant_new("(s)", owner),
811                                      G_DBUS_CALL_FLAGS_NONE,
812                                      /* It's not worth to wait for the PID
813                                       * longer than half a second when dying */
814                                      500,
815                                      NULL,
816                                      &error);
817 
818         if (error) {
819                 g_error_free(error);
820                 return false;
821         }
822 
823         g_object_unref(proxy_fdn);
824         g_object_unref(proxy_dbus);
825         g_free(owner);
826         if (daemoninfo)
827                 g_variant_unref(daemoninfo);
828 
829         if (pidinfo) {
830                 g_variant_get(pidinfo, "(u)", pid);
831                 g_variant_unref(pidinfo);
832                 return true;
833         } else {
834                 return false;
835         }
836 }
837 
838 
dbus_cb_name_lost(GDBusConnection * connection,const gchar * name,gpointer user_data)839 static void dbus_cb_name_lost(GDBusConnection *connection,
840                               const gchar *name,
841                               gpointer user_data)
842 {
843         if (connection) {
844                 char *name;
845                 unsigned int pid;
846                 if (dbus_get_fdn_daemon_info(connection, &pid, &name, NULL)) {
847                         DIE("Cannot acquire '"FDN_NAME"': "
848                             "Name is acquired by '%s' with PID '%d'.", name, pid);
849                 } else {
850                         DIE("Cannot acquire '"FDN_NAME"'.");
851                 }
852         } else {
853                 DIE("Cannot connect to DBus.");
854         }
855         exit(1);
856 }
857 
dbus_init(void)858 int dbus_init(void)
859 {
860         guint owner_id;
861 
862         introspection_data = g_dbus_node_info_new_for_xml(introspection_xml,
863                                                           NULL);
864 
865         owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
866                                   FDN_NAME,
867                                   G_BUS_NAME_OWNER_FLAGS_NONE,
868                                   dbus_cb_bus_acquired,
869                                   dbus_cb_name_acquired,
870                                   dbus_cb_name_lost,
871                                   NULL,
872                                   NULL);
873 
874         return owner_id;
875 }
876 
dbus_teardown(int owner_id)877 void dbus_teardown(int owner_id)
878 {
879         g_clear_pointer(&introspection_data, g_dbus_node_info_unref);
880 
881         g_bus_unown_name(owner_id);
882 }
883 
884 /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
885