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)", ¬ification_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