1 #define _POSIX_C_SOURCE 200809L
2 #include <stdbool.h>
3 #include <stddef.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include "list.h"
8 #include "log.h"
9 #include "swaybar/tray/watcher.h"
10 
11 static const char *obj_path = "/StatusNotifierWatcher";
12 
using_standard_protocol(struct swaybar_watcher * watcher)13 static bool using_standard_protocol(struct swaybar_watcher *watcher) {
14 	return watcher->interface[strlen("org.")] == 'f'; // freedesktop
15 }
16 
cmp_id(const void * item,const void * cmp_to)17 static int cmp_id(const void *item, const void *cmp_to) {
18 	return strcmp(item, cmp_to);
19 }
20 
handle_lost_service(sd_bus_message * msg,void * data,sd_bus_error * error)21 static int handle_lost_service(sd_bus_message *msg,
22 		void *data, sd_bus_error *error) {
23 	char *service, *old_owner, *new_owner;
24 	int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
25 	if (ret < 0) {
26 		sway_log(SWAY_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
27 		return ret;
28 	}
29 
30 	if (!*new_owner) {
31 		struct swaybar_watcher *watcher = data;
32 		for (int idx = 0; idx < watcher->items->length; ++idx) {
33 			char *id = watcher->items->items[idx];
34 			int cmp_res = using_standard_protocol(watcher) ?
35 				cmp_id(id, service) : strncmp(id, service, strlen(service));
36 			if (cmp_res == 0) {
37 				sway_log(SWAY_DEBUG, "Unregistering Status Notifier Item '%s'", id);
38 				list_del(watcher->items, idx--);
39 				sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
40 						"StatusNotifierItemUnregistered", "s", id);
41 				free(id);
42 				if (using_standard_protocol(watcher)) {
43 					break;
44 				}
45 			}
46 		}
47 
48 		int idx = list_seq_find(watcher->hosts, cmp_id, service);
49 		if (idx != -1) {
50 			sway_log(SWAY_DEBUG, "Unregistering Status Notifier Host '%s'", service);
51 			free(watcher->hosts->items[idx]);
52 			list_del(watcher->hosts, idx);
53 		}
54 	}
55 
56 	return 0;
57 }
58 
register_sni(sd_bus_message * msg,void * data,sd_bus_error * error)59 static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
60 	char *service_or_path, *id;
61 	int ret = sd_bus_message_read(msg, "s", &service_or_path);
62 	if (ret < 0) {
63 		sway_log(SWAY_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
64 		return ret;
65 	}
66 
67 	struct swaybar_watcher *watcher = data;
68 	if (using_standard_protocol(watcher)) {
69 		id = strdup(service_or_path);
70 	} else {
71 		const char *service, *path;
72 		if (service_or_path[0] == '/') {
73 			service = sd_bus_message_get_sender(msg);
74 			path = service_or_path;
75 		} else {
76 			service = service_or_path;
77 			path = "/StatusNotifierItem";
78 		}
79 		size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1;
80 		id = malloc(id_len);
81 		snprintf(id, id_len, "%s%s", service, path);
82 	}
83 
84 	if (list_seq_find(watcher->items, cmp_id, id) == -1) {
85 		sway_log(SWAY_DEBUG, "Registering Status Notifier Item '%s'", id);
86 		list_add(watcher->items, id);
87 		sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
88 				"StatusNotifierItemRegistered", "s", id);
89 	} else {
90 		sway_log(SWAY_DEBUG, "Status Notifier Item '%s' already registered", id);
91 		free(id);
92 	}
93 
94 	return sd_bus_reply_method_return(msg, "");
95 }
96 
register_host(sd_bus_message * msg,void * data,sd_bus_error * error)97 static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) {
98 	char *service;
99 	int ret = sd_bus_message_read(msg, "s", &service);
100 	if (ret < 0) {
101 		sway_log(SWAY_ERROR, "Failed to parse register host message: %s", strerror(-ret));
102 		return ret;
103 	}
104 
105 	struct swaybar_watcher *watcher = data;
106 	if (list_seq_find(watcher->hosts, cmp_id, service) == -1) {
107 		sway_log(SWAY_DEBUG, "Registering Status Notifier Host '%s'", service);
108 		list_add(watcher->hosts, strdup(service));
109 		sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
110 				"StatusNotifierHostRegistered", "s", service);
111 	} else {
112 		sway_log(SWAY_DEBUG, "Status Notifier Host '%s' already registered", service);
113 	}
114 
115 	return sd_bus_reply_method_return(msg, "");
116 }
117 
get_registered_snis(sd_bus * bus,const char * obj_path,const char * interface,const char * property,sd_bus_message * reply,void * data,sd_bus_error * error)118 static int get_registered_snis(sd_bus *bus, const char *obj_path,
119 		const char *interface, const char *property, sd_bus_message *reply,
120 		void *data, sd_bus_error *error) {
121 	struct swaybar_watcher *watcher = data;
122 	list_add(watcher->items, NULL); // strv expects NULL-terminated string array
123 	int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items);
124 	list_del(watcher->items, watcher->items->length - 1);
125 	return ret;
126 }
127 
is_host_registered(sd_bus * bus,const char * obj_path,const char * interface,const char * property,sd_bus_message * reply,void * data,sd_bus_error * error)128 static int is_host_registered(sd_bus *bus, const char *obj_path,
129 		const char *interface, const char *property, sd_bus_message *reply,
130 		void *data, sd_bus_error *error) {
131 	struct swaybar_watcher *watcher = data;
132 	int val = watcher->hosts->length > 0; // dbus expects int rather than bool
133 	return sd_bus_message_append_basic(reply, 'b', &val);
134 }
135 
136 static const sd_bus_vtable watcher_vtable[] = {
137 	SD_BUS_VTABLE_START(0),
138 	SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni,
139 			SD_BUS_VTABLE_UNPRIVILEGED),
140 	SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host,
141 			SD_BUS_VTABLE_UNPRIVILEGED),
142 	SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis,
143 			0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
144 	SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered,
145 			0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
146 	SD_BUS_PROPERTY("ProtocolVersion", "i", NULL,
147 			offsetof(struct swaybar_watcher, version),
148 			SD_BUS_VTABLE_PROPERTY_CONST),
149 	SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0),
150 	SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0),
151 	SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0),
152 	SD_BUS_VTABLE_END
153 };
154 
create_watcher(char * protocol,sd_bus * bus)155 struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
156 	struct swaybar_watcher *watcher =
157 		calloc(1, sizeof(struct swaybar_watcher));
158 	if (!watcher) {
159 		return NULL;
160 	}
161 
162 	size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
163 	watcher->interface = malloc(len);
164 	snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
165 
166 	sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
167 	int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
168 			watcher->interface, watcher_vtable, watcher);
169 	if (ret < 0) {
170 		sway_log(SWAY_ERROR, "Failed to add object vtable: %s", strerror(-ret));
171 		goto error;
172 	}
173 
174 	ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus",
175 			"/org/freedesktop/DBus", "org.freedesktop.DBus",
176 			"NameOwnerChanged", handle_lost_service, watcher);
177 	if (ret < 0) {
178 		sway_log(SWAY_ERROR, "Failed to subscribe to unregistering events: %s",
179 				strerror(-ret));
180 		goto error;
181 	}
182 
183 	ret = sd_bus_request_name(bus, watcher->interface, 0);
184 	if (ret < 0) {
185 		if (-ret == EEXIST) {
186 			sway_log(SWAY_DEBUG, "Failed to acquire service name '%s':"
187 					"another tray is already running", watcher->interface);
188 		} else {
189 			sway_log(SWAY_ERROR, "Failed to acquire service name '%s': %s",
190 					watcher->interface, strerror(-ret));
191 		}
192 		goto error;
193 	}
194 
195 	sd_bus_slot_set_floating(signal_slot, 0);
196 	sd_bus_slot_set_floating(vtable_slot, 0);
197 
198 	watcher->bus = bus;
199 	watcher->hosts = create_list();
200 	watcher->items = create_list();
201 	watcher->version = 0;
202 	sway_log(SWAY_DEBUG, "Registered %s", watcher->interface);
203 	return watcher;
204 error:
205 	sd_bus_slot_unref(signal_slot);
206 	sd_bus_slot_unref(vtable_slot);
207 	destroy_watcher(watcher);
208 	return NULL;
209 }
210 
destroy_watcher(struct swaybar_watcher * watcher)211 void destroy_watcher(struct swaybar_watcher *watcher) {
212 	if (!watcher) {
213 		return;
214 	}
215 	list_free_items_and_destroy(watcher->hosts);
216 	list_free_items_and_destroy(watcher->items);
217 	free(watcher->interface);
218 	free(watcher);
219 }
220