1 #define _POSIX_C_SOURCE 200809L
2 #include <stdbool.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include "swaybar/bar.h"
8 #include "swaybar/tray/host.h"
9 #include "swaybar/tray/item.h"
10 #include "swaybar/tray/tray.h"
11 #include "list.h"
12 #include "log.h"
13 
14 static const char *watcher_path = "/StatusNotifierWatcher";
15 
cmp_sni_id(const void * item,const void * cmp_to)16 static int cmp_sni_id(const void *item, const void *cmp_to) {
17 	const struct swaybar_sni *sni = item;
18 	return strcmp(sni->watcher_id, cmp_to);
19 }
20 
add_sni(struct swaybar_tray * tray,char * id)21 static void add_sni(struct swaybar_tray *tray, char *id) {
22 	int idx = list_seq_find(tray->items, cmp_sni_id, id);
23 	if (idx == -1) {
24 		sway_log(SWAY_INFO, "Registering Status Notifier Item '%s'", id);
25 		struct swaybar_sni *sni = create_sni(id, tray);
26 		if (sni) {
27 			list_add(tray->items, sni);
28 		}
29 	}
30 }
31 
handle_sni_registered(sd_bus_message * msg,void * data,sd_bus_error * error)32 static int handle_sni_registered(sd_bus_message *msg, void *data,
33 		sd_bus_error *error) {
34 	char *id;
35 	int ret = sd_bus_message_read(msg, "s", &id);
36 	if (ret < 0) {
37 		sway_log(SWAY_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
38 	}
39 
40 	struct swaybar_tray *tray = data;
41 	add_sni(tray, id);
42 
43 	return ret;
44 }
45 
handle_sni_unregistered(sd_bus_message * msg,void * data,sd_bus_error * error)46 static int handle_sni_unregistered(sd_bus_message *msg, void *data,
47 		sd_bus_error *error) {
48 	char *id;
49 	int ret = sd_bus_message_read(msg, "s", &id);
50 	if (ret < 0) {
51 		sway_log(SWAY_ERROR, "Failed to parse unregister SNI message: %s", strerror(-ret));
52 	}
53 
54 	struct swaybar_tray *tray = data;
55 	int idx = list_seq_find(tray->items, cmp_sni_id, id);
56 	if (idx != -1) {
57 		sway_log(SWAY_INFO, "Unregistering Status Notifier Item '%s'", id);
58 		destroy_sni(tray->items->items[idx]);
59 		list_del(tray->items, idx);
60 		set_bar_dirty(tray->bar);
61 	}
62 	return ret;
63 }
64 
get_registered_snis_callback(sd_bus_message * msg,void * data,sd_bus_error * error)65 static int get_registered_snis_callback(sd_bus_message *msg, void *data,
66 		sd_bus_error *error) {
67 	if (sd_bus_message_is_method_error(msg, NULL)) {
68 		const sd_bus_error *err = sd_bus_message_get_error(msg);
69 		sway_log(SWAY_ERROR, "Failed to get registered SNIs: %s", err->message);
70 		return -sd_bus_error_get_errno(err);
71 	}
72 
73 	int ret = sd_bus_message_enter_container(msg, 'v', NULL);
74 	if (ret < 0) {
75 		sway_log(SWAY_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
76 		return ret;
77 	}
78 
79 	char **ids;
80 	ret = sd_bus_message_read_strv(msg, &ids);
81 	if (ret < 0) {
82 		sway_log(SWAY_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
83 		return ret;
84 	}
85 
86 	if (ids) {
87 		struct swaybar_tray *tray = data;
88 		for (char **id = ids; *id; ++id) {
89 			add_sni(tray, *id);
90 			free(*id);
91 		}
92 	}
93 
94 	free(ids);
95 	return ret;
96 }
97 
register_to_watcher(struct swaybar_host * host)98 static bool register_to_watcher(struct swaybar_host *host) {
99 	// this is called asynchronously in case the watcher is owned by this process
100 	int ret = sd_bus_call_method_async(host->tray->bus, NULL,
101 			host->watcher_interface, watcher_path, host->watcher_interface,
102 			"RegisterStatusNotifierHost", NULL, NULL, "s", host->service);
103 	if (ret < 0) {
104 		sway_log(SWAY_ERROR, "Failed to send register call: %s", strerror(-ret));
105 		return false;
106 	}
107 
108 	ret = sd_bus_call_method_async(host->tray->bus, NULL,
109 			host->watcher_interface, watcher_path,
110 			"org.freedesktop.DBus.Properties", "Get",
111 			get_registered_snis_callback, host->tray, "ss",
112 			host->watcher_interface, "RegisteredStatusNotifierItems");
113 	if (ret < 0) {
114 		sway_log(SWAY_ERROR, "Failed to get registered SNIs: %s", strerror(-ret));
115 	}
116 
117 	return ret >= 0;
118 }
119 
handle_new_watcher(sd_bus_message * msg,void * data,sd_bus_error * error)120 static int handle_new_watcher(sd_bus_message *msg,
121 		void *data, sd_bus_error *error) {
122 	char *service, *old_owner, *new_owner;
123 	int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
124 	if (ret < 0) {
125 		sway_log(SWAY_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
126 		return ret;
127 	}
128 
129 	if (!*old_owner) {
130 		struct swaybar_host *host = data;
131 		if (strcmp(service, host->watcher_interface) == 0) {
132 			register_to_watcher(host);
133 		}
134 	}
135 
136 	return 0;
137 }
138 
init_host(struct swaybar_host * host,char * protocol,struct swaybar_tray * tray)139 bool init_host(struct swaybar_host *host, char *protocol,
140 		struct swaybar_tray *tray) {
141 	size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
142 	host->watcher_interface = malloc(len);
143 	if (!host->watcher_interface) {
144 		return false;
145 	}
146 	snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol);
147 
148 	sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL;
149 	int ret = sd_bus_match_signal(tray->bus, &reg_slot, host->watcher_interface,
150 			watcher_path, host->watcher_interface,
151 			"StatusNotifierItemRegistered", handle_sni_registered, tray);
152 	if (ret < 0) {
153 		sway_log(SWAY_ERROR, "Failed to subscribe to registering events: %s",
154 				strerror(-ret));
155 		goto error;
156 	}
157 	ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface,
158 			watcher_path, host->watcher_interface,
159 			"StatusNotifierItemUnregistered", handle_sni_unregistered, tray);
160 	if (ret < 0) {
161 		sway_log(SWAY_ERROR, "Failed to subscribe to unregistering events: %s",
162 				strerror(-ret));
163 		goto error;
164 	}
165 
166 	ret = sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus",
167 			"/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged",
168 			handle_new_watcher, host);
169 	if (ret < 0) {
170 		sway_log(SWAY_ERROR, "Failed to subscribe to unregistering events: %s",
171 				strerror(-ret));
172 		goto error;
173 	}
174 
175 	pid_t pid = getpid();
176 	size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d",
177 			protocol, pid) + 1;
178 	host->service = malloc(service_len);
179 	if (!host->service) {
180 		goto error;
181 	}
182 	snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid);
183 	ret = sd_bus_request_name(tray->bus, host->service, 0);
184 	if (ret < 0) {
185 		sway_log(SWAY_DEBUG, "Failed to acquire service name: %s", strerror(-ret));
186 		goto error;
187 	}
188 
189 	host->tray = tray;
190 	if (!register_to_watcher(host)) {
191 		goto error;
192 	}
193 
194 	sd_bus_slot_set_floating(reg_slot, 0);
195 	sd_bus_slot_set_floating(unreg_slot, 0);
196 	sd_bus_slot_set_floating(watcher_slot, 0);
197 
198 	sway_log(SWAY_DEBUG, "Registered %s", host->service);
199 	return true;
200 error:
201 	sd_bus_slot_unref(reg_slot);
202 	sd_bus_slot_unref(unreg_slot);
203 	sd_bus_slot_unref(watcher_slot);
204 	finish_host(host);
205 	return false;
206 }
207 
finish_host(struct swaybar_host * host)208 void finish_host(struct swaybar_host *host) {
209 	sd_bus_release_name(host->tray->bus, host->service);
210 	free(host->service);
211 	free(host->watcher_interface);
212 }
213