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(&notif->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, &notif->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, &notif->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, &notif->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