1 #define _POSIX_C_SOURCE 200809L
2 #include <errno.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 #include "config.h"
8 #include "criteria.h"
9 #include "dbus.h"
10 #include "mako.h"
11 #include "notification.h"
12 #include "wayland.h"
13
14 #include "icon.h"
15
16 static const char *service_path = "/org/freedesktop/Notifications";
17 static const char *service_interface = "org.freedesktop.Notifications";
18
handle_get_capabilities(sd_bus_message * msg,void * data,sd_bus_error * ret_error)19 static int handle_get_capabilities(sd_bus_message *msg, void *data,
20 sd_bus_error *ret_error) {
21 struct mako_state *state = data;
22
23 sd_bus_message *reply = NULL;
24 int ret = sd_bus_message_new_method_return(msg, &reply);
25 if (ret < 0) {
26 return ret;
27 }
28
29 ret = sd_bus_message_open_container(reply, 'a', "s");
30 if (ret < 0) {
31 return ret;
32 }
33
34 if (strstr(state->config.superstyle.format, "%b") != NULL) {
35 ret = sd_bus_message_append(reply, "s", "body");
36 if (ret < 0) {
37 return ret;
38 }
39 }
40
41 if (state->config.superstyle.markup) {
42 ret = sd_bus_message_append(reply, "s", "body-markup");
43 if (ret < 0) {
44 return ret;
45 }
46 }
47
48 if (state->config.superstyle.actions) {
49 ret = sd_bus_message_append(reply, "s", "actions");
50 if (ret < 0) {
51 return ret;
52 }
53 }
54
55 if (state->config.superstyle.icons) {
56 ret = sd_bus_message_append(reply, "s", "icon-static");
57 if (ret < 0) {
58 return ret;
59 }
60 }
61
62 ret = sd_bus_message_append(reply, "s", "x-canonical-private-synchronous");
63 if (ret < 0) {
64 return ret;
65 }
66
67 ret = sd_bus_message_append(reply, "s", "x-dunst-stack-tag");
68 if (ret < 0) {
69 return ret;
70 }
71
72 ret = sd_bus_message_close_container(reply);
73 if (ret < 0) {
74 return ret;
75 }
76
77 ret = sd_bus_send(NULL, reply, NULL);
78 if (ret < 0) {
79 return ret;
80 }
81
82 sd_bus_message_unref(reply);
83 return 0;
84 }
85
handle_notification_timer(void * data)86 static void handle_notification_timer(void *data) {
87 struct mako_notification *notif = data;
88 struct mako_surface *surface = notif->surface;
89 notif->timer = NULL;
90
91 close_notification(notif, MAKO_NOTIFICATION_CLOSE_EXPIRED);
92 set_dirty(surface);
93 }
94
handle_notify(sd_bus_message * msg,void * data,sd_bus_error * ret_error)95 static int handle_notify(sd_bus_message *msg, void *data,
96 sd_bus_error *ret_error) {
97 struct mako_state *state = data;
98 int ret = 0;
99
100 const char *app_name, *app_icon, *summary, *body;
101 uint32_t replaces_id;
102 ret = sd_bus_message_read(msg, "susss", &app_name, &replaces_id, &app_icon,
103 &summary, &body);
104 if (ret < 0) {
105 return ret;
106 }
107
108 struct mako_notification *notif = NULL;
109 if (replaces_id > 0) {
110 notif = get_notification(state, replaces_id);
111 }
112
113 if (notif) {
114 reset_notification(notif);
115 } else {
116 // Either we had no replaces_id, or the id given was invalid. Either
117 // way, make a new notification.
118 replaces_id = 0; // In case they got lucky and passed the next id.
119 notif = create_notification(state);
120 }
121
122 if (notif == NULL) {
123 return -1;
124 }
125
126 free(notif->app_name);
127 free(notif->app_icon);
128 free(notif->summary);
129 free(notif->body);
130 notif->app_name = strdup(app_name);
131 notif->app_icon = strdup(app_icon);
132 notif->summary = strdup(summary);
133 notif->body = strdup(body);
134
135 ret = sd_bus_message_enter_container(msg, 'a', "s");
136 if (ret < 0) {
137 return ret;
138 }
139
140 while (1) {
141 const char *action_key, *action_title;
142 ret = sd_bus_message_read(msg, "ss", &action_key, &action_title);
143 if (ret < 0) {
144 return ret;
145 } else if (ret == 0) {
146 break;
147 }
148
149 struct mako_action *action = calloc(1, sizeof(struct mako_action));
150 if (action == NULL) {
151 return -1;
152 }
153 action->notification = notif;
154 action->key = strdup(action_key);
155 action->title = strdup(action_title);
156 wl_list_insert(¬if->actions, &action->link);
157 }
158
159 ret = sd_bus_message_exit_container(msg);
160 if (ret < 0) {
161 return ret;
162 }
163
164 ret = sd_bus_message_enter_container(msg, 'a', "{sv}");
165 if (ret < 0) {
166 return ret;
167 }
168
169 while (1) {
170 ret = sd_bus_message_enter_container(msg, 'e', "sv");
171 if (ret < 0) {
172 return ret;
173 } else if (ret == 0) {
174 break;
175 }
176
177 const char *hint = NULL;
178 ret = sd_bus_message_read(msg, "s", &hint);
179 if (ret < 0) {
180 return ret;
181 }
182
183 if (strcmp(hint, "urgency") == 0) {
184 // Should be a byte but some clients (Chromium) send an uint32_t
185 const char *contents = NULL;
186 ret = sd_bus_message_peek_type(msg, NULL, &contents);
187 if (ret < 0) {
188 return ret;
189 }
190
191 if (strcmp(contents, "u") == 0) {
192 uint32_t urgency = 0;
193 ret = sd_bus_message_read(msg, "v", "u", &urgency);
194 if (ret < 0) {
195 return ret;
196 }
197 notif->urgency = urgency;
198 } else if (strcmp(contents, "y") == 0) {
199 uint8_t urgency = 0;
200 ret = sd_bus_message_read(msg, "v", "y", &urgency);
201 if (ret < 0) {
202 return ret;
203 }
204 notif->urgency = urgency;
205 } else if (strcmp(contents, "i") == 0) {
206 int32_t urgency = 0;
207 ret = sd_bus_message_read(msg, "v", "i", &urgency);
208 if (ret < 0) {
209 return ret;
210 }
211 notif->urgency = urgency;
212 } else {
213 fprintf(stderr, "Unsupported variant type for \"urgency\": \"%s\"\n", contents);
214 return -1;
215 }
216 } else if (strcmp(hint, "category") == 0) {
217 const char *category = NULL;
218 ret = sd_bus_message_read(msg, "v", "s", &category);
219 if (ret < 0) {
220 return ret;
221 }
222 free(notif->category);
223 notif->category = strdup(category);
224 } else if (strcmp(hint, "desktop-entry") == 0) {
225 const char *desktop_entry = NULL;
226 ret = sd_bus_message_read(msg, "v", "s", &desktop_entry);
227 if (ret < 0) {
228 return ret;
229 }
230 free(notif->desktop_entry);
231 notif->desktop_entry = strdup(desktop_entry);
232 } else if (strcmp(hint, "value") == 0) {
233 int32_t progress = 0;
234 ret = sd_bus_message_read(msg, "v", "i", &progress);
235 if (ret < 0) {
236 return ret;
237 }
238 notif->progress = progress;
239 } else if (strcmp(hint, "image-path") == 0 ||
240 strcmp(hint, "image_path") == 0) { // Deprecated.
241 const char *image_path = NULL;
242 ret = sd_bus_message_read(msg, "v", "s", &image_path);
243 if (ret < 0) {
244 return ret;
245 }
246 // image-path is higher priority than app_icon, so just overwrite
247 // it. We're guaranteed to be doing this after reading the "real"
248 // app_icon. It's also lower priority than image-data, and that
249 // will win over app_icon if provided.
250 free(notif->app_icon);
251 notif->app_icon = strdup(image_path);
252 } else if (strcmp(hint, "x-canonical-private-synchronous") == 0 ||
253 strcmp(hint, "x-dunst-stack-tag") == 0) {
254 const char *tag = NULL;
255 ret = sd_bus_message_read(msg, "v", "s", &tag);
256 if (ret < 0) {
257 return ret;
258 }
259 notif->tag = strdup(tag);
260 } else if (strcmp(hint, "image-data") == 0 ||
261 strcmp(hint, "image_data") == 0 || // Deprecated.
262 strcmp(hint, "icon_data") == 0) { // Even more deprecated.
263 ret = sd_bus_message_enter_container(msg, 'v', "(iiibiiay)");
264 if (ret < 0) {
265 return ret;
266 }
267
268 ret = sd_bus_message_enter_container(msg, 'r', "iiibiiay");
269 if (ret < 0) {
270 return ret;
271 }
272
273 struct mako_image_data *image_data = calloc(1, sizeof(struct mako_image_data));
274 if (image_data == NULL) {
275 return -1;
276 }
277
278 ret = sd_bus_message_read(msg, "iiibii", &image_data->width,
279 &image_data->height, &image_data->rowstride,
280 &image_data->has_alpha, &image_data->bits_per_sample,
281 &image_data->channels);
282 if (ret < 0) {
283 free(image_data);
284 return ret;
285 }
286
287 // Calculate the expected useful data length without padding in last row
288 // len = size before last row + size of last row
289 // = (height - 1) * rowstride + width * ceil(channels * bits_pre_sample / 8.0)
290 size_t image_len = (image_data->height - 1) * image_data->rowstride +
291 image_data->width * ((image_data->channels *
292 image_data->bits_per_sample + 7) / 8);
293 uint8_t *data = calloc(image_len, sizeof(uint8_t));
294 if (data == NULL) {
295 free(image_data);
296 return -1;
297 }
298
299 ret = sd_bus_message_enter_container(msg, 'a', "y");
300 if (ret < 0) {
301 free(data);
302 free(image_data);
303 return ret;
304 }
305
306 // Ignore the extra padding bytes in the last row if exist
307 for (size_t index = 0; index < image_len; index++) {
308 uint8_t tmp;
309 ret = sd_bus_message_read(msg, "y", &tmp);
310 if (ret < 0){
311 free(data);
312 free(image_data);
313 return ret;
314 }
315 data[index] = tmp;
316 }
317
318 image_data->data = data;
319 if (notif->image_data != NULL) {
320 free(notif->image_data->data);
321 free(notif->image_data);
322 }
323 notif->image_data = image_data;
324
325 ret = sd_bus_message_exit_container(msg);
326 if (ret < 0) {
327 return ret;
328 }
329
330 ret = sd_bus_message_exit_container(msg);
331 if (ret < 0) {
332 return ret;
333 }
334
335 ret = sd_bus_message_exit_container(msg);
336 if (ret < 0) {
337 return ret;
338 }
339 } else {
340 ret = sd_bus_message_skip(msg, "v");
341 if (ret < 0) {
342 return ret;
343 }
344 }
345
346 ret = sd_bus_message_exit_container(msg);
347 if (ret < 0) {
348 return ret;
349 }
350 }
351
352 ret = sd_bus_message_exit_container(msg);
353 if (ret < 0) {
354 return ret;
355 }
356
357 int32_t requested_timeout;
358 ret = sd_bus_message_read(msg, "i", &requested_timeout);
359 if (ret < 0) {
360 return ret;
361 }
362 notif->requested_timeout = requested_timeout;
363
364 if (notif->tag) {
365 // Find and replace the existing notfication with a matching tag
366 struct mako_notification *replace_notif = get_tagged_notification(state, notif->tag, app_name);
367 if (replace_notif) {
368 notif->id = replace_notif->id;
369 wl_list_insert(&replace_notif->link, ¬if->link);
370 destroy_notification(replace_notif);
371 replaces_id = notif->id;
372 }
373 }
374
375 // We can insert a notification prior to matching criteria, because sort is
376 // global. We also know that inserting a notification into the global list
377 // regardless of the configured sort criteria places it in the correct
378 // position relative to any of its potential group mates even before
379 // knowing what criteria we will be grouping them by (proof left as an
380 // exercise to the reader).
381 if (replaces_id != notif->id) {
382 // Only insert notifications if they're actually new, to avoid creating
383 // duplicates in the list.
384 insert_notification(state, notif);
385 }
386
387 int match_count = apply_each_criteria(&state->config.criteria, notif);
388 if (match_count == -1) {
389 // We encountered an allocation failure or similar while applying
390 // criteria. The notification may be partially matched, but the worst
391 // case is that it has an empty style, so bail.
392 fprintf(stderr, "Failed to apply criteria\n");
393 destroy_notification(notif);
394 return -1;
395 } else if (match_count == 0) {
396 // This should be impossible, since the global criteria is always
397 // present in a mako_config and matches everything.
398 fprintf(stderr, "Notification matched zero criteria?!\n");
399 destroy_notification(notif);
400 return -1;
401 }
402
403 int32_t expire_timeout = notif->requested_timeout;
404 if (expire_timeout < 0 || notif->style.ignore_timeout) {
405 expire_timeout = notif->style.default_timeout;
406 }
407
408 if (expire_timeout > 0) {
409 notif->timer = add_event_loop_timer(&state->event_loop, expire_timeout,
410 handle_notification_timer, notif);
411 }
412
413 if (notif->style.icons) {
414 notif->icon = create_icon(notif);
415 }
416
417 // Now we need to perform the grouping based on the new notification's
418 // group criteria specification (list of criteria which must match). We
419 // don't necessarily want to start with the new notification, as depending
420 // on the sort criteria, there may be matching ones earlier in the list.
421 // After this call, the matching notifications will be contiguous in the
422 // list, and the first one that matches will always still be first.
423 struct mako_criteria *notif_criteria = create_criteria_from_notification(
424 notif, ¬if->style.group_criteria_spec);
425 if (!notif_criteria) {
426 destroy_notification(notif);
427 return -1;
428 }
429 group_notifications(state, notif_criteria);
430 destroy_criteria(notif_criteria);
431
432 notification_execute_binding(notif, ¬if->style.notify_binding, NULL);
433
434 set_dirty(notif->surface);
435
436 return sd_bus_reply_method_return(msg, "u", notif->id);
437 }
438
handle_close_notification(sd_bus_message * msg,void * data,sd_bus_error * ret_error)439 static int handle_close_notification(sd_bus_message *msg, void *data,
440 sd_bus_error *ret_error) {
441 struct mako_state *state = data;
442
443 uint32_t id;
444 int ret = sd_bus_message_read(msg, "u", &id);
445 if (ret < 0) {
446 return ret;
447 }
448
449 // TODO: check client
450 struct mako_notification *notif = get_notification(state, id);
451 if (notif) {
452 struct mako_surface *surface = notif->surface;
453 close_notification(notif, MAKO_NOTIFICATION_CLOSE_REQUEST);
454 set_dirty(surface);
455 }
456
457 return sd_bus_reply_method_return(msg, "");
458 }
459
handle_get_server_information(sd_bus_message * msg,void * data,sd_bus_error * ret_error)460 static int handle_get_server_information(sd_bus_message *msg, void *data,
461 sd_bus_error *ret_error) {
462 const char *name = "mako";
463 const char *vendor = "emersion";
464 const char *version = "0.0.0";
465 const char *spec_version = "1.2";
466 return sd_bus_reply_method_return(msg, "ssss", name, vendor, version,
467 spec_version);
468 }
469
470 static const sd_bus_vtable service_vtable[] = {
471 SD_BUS_VTABLE_START(0),
472 SD_BUS_METHOD("GetCapabilities", "", "as", handle_get_capabilities, SD_BUS_VTABLE_UNPRIVILEGED),
473 SD_BUS_METHOD("Notify", "susssasa{sv}i", "u", handle_notify, SD_BUS_VTABLE_UNPRIVILEGED),
474 SD_BUS_METHOD("CloseNotification", "u", "", handle_close_notification, SD_BUS_VTABLE_UNPRIVILEGED),
475 SD_BUS_METHOD("GetServerInformation", "", "ssss", handle_get_server_information, SD_BUS_VTABLE_UNPRIVILEGED),
476 SD_BUS_SIGNAL("ActionInvoked", "us", 0),
477 SD_BUS_SIGNAL("NotificationClosed", "uu", 0),
478 SD_BUS_VTABLE_END
479 };
480
init_dbus_xdg(struct mako_state * state)481 int init_dbus_xdg(struct mako_state *state) {
482 return sd_bus_add_object_vtable(state->bus, &state->xdg_slot, service_path,
483 service_interface, service_vtable, state);
484 }
485
notify_notification_closed(struct mako_notification * notif,enum mako_notification_close_reason reason)486 void notify_notification_closed(struct mako_notification *notif,
487 enum mako_notification_close_reason reason) {
488 struct mako_state *state = notif->state;
489
490 sd_bus_emit_signal(state->bus, service_path, service_interface,
491 "NotificationClosed", "uu", notif->id, reason);
492 }
493
notify_action_invoked(struct mako_action * action,const char * activation_token)494 void notify_action_invoked(struct mako_action *action,
495 const char *activation_token) {
496 if (!action->notification->style.actions) {
497 // Actions are disabled for this notification, bail.
498 return;
499 }
500
501 struct mako_state *state = action->notification->state;
502
503 if (activation_token != NULL) {
504 sd_bus_emit_signal(state->bus, service_path, service_interface,
505 "ActivationToken", "us", action->notification->id, activation_token);
506 }
507
508 sd_bus_emit_signal(state->bus, service_path, service_interface,
509 "ActionInvoked", "us", action->notification->id, action->key);
510 }
511