summaryrefslogtreecommitdiffstats
path: root/swaybar/tray/sni_watcher.c
diff options
context:
space:
mode:
Diffstat (limited to 'swaybar/tray/sni_watcher.c')
-rw-r--r--swaybar/tray/sni_watcher.c487
1 files changed, 487 insertions, 0 deletions
diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c
new file mode 100644
index 00000000..388e181d
--- /dev/null
+++ b/swaybar/tray/sni_watcher.c
@@ -0,0 +1,487 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include "swaybar/tray/dbus.h"
9#include "list.h"
10#include "log.h"
11
12static list_t *items = NULL;
13static list_t *hosts = NULL;
14
15/**
16 * Describes the function of the StatusNotifierWatcher
17 * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/
18 *
19 * We also implement KDE's special snowflake protocol, it's like this but with
20 * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect.
21 */
22static const char *interface_xml =
23 "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'"
24 "'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>"
25 "<node>"
26 " <interface name='org.freedesktop.DBus.Introspectable'>"
27 " <method name='Introspect'>"
28 " <arg name='xml_data' direction='out' type='s'/>"
29 " </method>"
30 " </interface>"
31 " <interface name='org.freedesktop.DBus.Properties'>"
32 " <method name='Get'>"
33 " <arg name='interface' direction='in' type='s'/>"
34 " <arg name='propname' direction='in' type='s'/>"
35 " <arg name='value' direction='out' type='v'/>"
36 " </method>"
37 " <method name='Set'>"
38 " <arg name='interface' direction='in' type='s'/>"
39 " <arg name='propname' direction='in' type='s'/>"
40 " <arg name='value' direction='in' type='v'/>"
41 " </method>"
42 " <method name='GetAll'>"
43 " <arg name='interface' direction='in' type='s'/>"
44 " <arg name='props' direction='out' type='a{sv}'/>"
45 " </method>"
46 " </interface>"
47 " <interface name='org.freedesktop.StatusNotifierWatcher'>"
48 " <method name='RegisterStatusNotifierItem'>"
49 " <arg type='s' name='service' direction='in'/>"
50 " </method>"
51 " <method name='RegisterStatusNotifierHost'>"
52 " <arg type='s' name='service' direction='in'/>"
53 " </method>"
54 " <property name='RegisteredStatusNotifierItems' type='as' access='read'/>"
55 " <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>"
56 " <property name='ProtocolVersion' type='i' access='read'/>"
57 " <signal name='StatusNotifierItemRegistered'>"
58 " <arg type='s' name='service' direction='out'/>"
59 " </signal>"
60 " <signal name='StatusNotifierItemUnregistered'>"
61 " <arg type='s' name='service' direction='out'/>"
62 " </signal>"
63 " <signal name='StatusNotifierHostRegistered'>"
64 " <arg type='' name='service' direction='out'/>"
65 " </signal>"
66 " </interface>"
67 "</node>";
68
69static void host_registered_signal(DBusConnection *connection) {
70 // Send one signal for each protocol
71 DBusMessage *signal = dbus_message_new_signal(
72 "/StatusNotifierWatcher",
73 "org.freedesktop.StatusNotifierWatcher",
74 "StatusNotifierHostRegistered");
75
76 dbus_connection_send(connection, signal, NULL);
77 dbus_message_unref(signal);
78
79
80 signal = dbus_message_new_signal(
81 "/StatusNotifierWatcher",
82 "org.kde.StatusNotifierWatcher",
83 "StatusNotifierHostRegistered");
84
85 dbus_connection_send(connection, signal, NULL);
86 dbus_message_unref(signal);
87}
88static void item_registered_signal(DBusConnection *connection, const char *name) {
89 DBusMessage *signal = dbus_message_new_signal(
90 "/StatusNotifierWatcher",
91 "org.freedesktop.StatusNotifierWatcher",
92 "StatusNotifierItemRegistered");
93 dbus_message_append_args(signal,
94 DBUS_TYPE_STRING, &name,
95 DBUS_TYPE_INVALID);
96 dbus_connection_send(connection, signal, NULL);
97 dbus_message_unref(signal);
98
99 signal = dbus_message_new_signal(
100 "/StatusNotifierWatcher",
101 "org.kde.StatusNotifierWatcher",
102 "StatusNotifierItemRegistered");
103 dbus_message_append_args(signal,
104 DBUS_TYPE_STRING, &name,
105 DBUS_TYPE_INVALID);
106 dbus_connection_send(connection, signal, NULL);
107 dbus_message_unref(signal);
108}
109static void item_unregistered_signal(DBusConnection *connection, const char *name) {
110 DBusMessage *signal = dbus_message_new_signal(
111 "/StatusNotifierWatcher",
112 "org.freedesktop.StatusNotifierWatcher",
113 "StatusNotifierItemUnregistered");
114 dbus_message_append_args(signal,
115 DBUS_TYPE_STRING, &name,
116 DBUS_TYPE_INVALID);
117 dbus_connection_send(connection, signal, NULL);
118 dbus_message_unref(signal);
119
120 signal = dbus_message_new_signal(
121 "/StatusNotifierWatcher",
122 "org.kde.StatusNotifierWatcher",
123 "StatusNotifierItemUnregistered");
124 dbus_message_append_args(signal,
125 DBUS_TYPE_STRING, &name,
126 DBUS_TYPE_INVALID);
127 dbus_connection_send(connection, signal, NULL);
128 dbus_message_unref(signal);
129}
130
131static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) {
132 DBusMessage *reply;
133
134 reply = dbus_message_new_method_return(request);
135 dbus_message_append_args(reply,
136 DBUS_TYPE_STRING, &interface_xml,
137 DBUS_TYPE_INVALID);
138 dbus_connection_send(connection, reply, NULL);
139 dbus_message_unref(reply);
140}
141
142static void register_item(DBusConnection *connection, DBusMessage *message) {
143 DBusError error;
144 char *name;
145
146 dbus_error_init(&error);
147 if (!dbus_message_get_args(message, &error,
148 DBUS_TYPE_STRING, &name,
149 DBUS_TYPE_INVALID)) {
150 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
151 }
152
153 name = strdup(name);
154 sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name);
155
156 // Don't add duplicate or not real item
157 if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
158 return;
159 }
160 if (!dbus_bus_name_has_owner(connection, name, &error)) {
161 return;
162 }
163
164 list_add(items, name);
165 item_registered_signal(connection, name);
166
167 // It's silly, but xembedsniproxy wants a reply for this function
168 DBusMessage *reply = dbus_message_new_method_return(message);
169 dbus_connection_send(connection, reply, NULL);
170 dbus_message_unref(reply);
171}
172
173static void register_host(DBusConnection *connection, DBusMessage *message) {
174 DBusError error;
175 char *name;
176
177 dbus_error_init(&error);
178 if (!dbus_message_get_args(message, &error,
179 DBUS_TYPE_STRING, &name,
180 DBUS_TYPE_INVALID)) {
181 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
182 }
183
184 sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name);
185
186 // Don't add duplicate or not real host
187 if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) {
188 return;
189 }
190 if (!dbus_bus_name_has_owner(connection, name, &error)) {
191 return;
192 }
193
194 list_add(hosts, strdup(name));
195 host_registered_signal(connection);
196}
197
198static void get_property(DBusConnection *connection, DBusMessage *message) {
199 DBusError error;
200 char *interface;
201 char *property;
202
203 dbus_error_init(&error);
204 if (!dbus_message_get_args(message, &error,
205 DBUS_TYPE_STRING, &interface,
206 DBUS_TYPE_STRING, &property,
207 DBUS_TYPE_INVALID)) {
208 sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message);
209 return;
210 }
211
212 if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
213 sway_log(L_INFO, "Replying with items\n");
214 DBusMessage *reply;
215 reply = dbus_message_new_method_return(message);
216 DBusMessageIter iter;
217 DBusMessageIter sub;
218 DBusMessageIter subsub;
219
220 dbus_message_iter_init_append(reply, &iter);
221
222 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
223 "as", &sub);
224 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
225 "s", &subsub);
226
227 for (int i = 0; i < items->length; ++i) {
228 dbus_message_iter_append_basic(&subsub,
229 DBUS_TYPE_STRING, &items->items[i]);
230 }
231
232 dbus_message_iter_close_container(&sub, &subsub);
233 dbus_message_iter_close_container(&iter, &sub);
234
235 dbus_connection_send(connection, reply, NULL);
236 dbus_message_unref(reply);
237 } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) {
238 DBusMessage *reply;
239 DBusMessageIter iter;
240 DBusMessageIter sub;
241 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
242
243 reply = dbus_message_new_method_return(message);
244
245 dbus_message_iter_init_append(reply, &iter);
246
247 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
248 "b", &sub);
249 dbus_message_iter_append_basic(&sub,
250 DBUS_TYPE_BOOLEAN, &registered);
251
252 dbus_message_iter_close_container(&iter, &sub);
253
254 dbus_connection_send(connection, reply, NULL);
255 dbus_message_unref(reply);
256 } else if (strcmp(property, "ProtocolVersion") == 0) {
257 DBusMessage *reply;
258 DBusMessageIter iter;
259 DBusMessageIter sub;
260 const int version = 0;
261
262 reply = dbus_message_new_method_return(message);
263
264 dbus_message_iter_init_append(reply, &iter);
265
266 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
267 "i", &sub);
268 dbus_message_iter_append_basic(&sub,
269 DBUS_TYPE_INT32, &version);
270
271 dbus_message_iter_close_container(&iter, &sub);
272 dbus_connection_send(connection, reply, NULL);
273 dbus_message_unref(reply);
274 }
275}
276
277static void set_property(DBusConnection *connection, DBusMessage *message) {
278 // All properties are read only and we don't allow new properties
279 return;
280}
281
282static void get_all(DBusConnection *connection, DBusMessage *message) {
283 DBusMessage *reply;
284 reply = dbus_message_new_method_return(message);
285 DBusMessageIter iter; /* a{v} */
286 DBusMessageIter arr;
287 DBusMessageIter dict;
288 DBusMessageIter sub;
289 DBusMessageIter subsub;
290 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
291 const int version = 0;
292 const char *prop;
293
294 // Could clean this up with a function for each prop
295 dbus_message_iter_init_append(reply, &iter);
296 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
297 "{sv}", &arr);
298
299 prop = "RegisteredStatusNotifierItems";
300 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
301 NULL, &dict);
302 dbus_message_iter_append_basic(&dict,
303 DBUS_TYPE_STRING, &prop);
304 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
305 "as", &sub);
306 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
307 "s", &subsub);
308 for (int i = 0; i < items->length; ++i) {
309 dbus_message_iter_append_basic(&subsub,
310 DBUS_TYPE_STRING, &items->items[i]);
311 }
312 dbus_message_iter_close_container(&sub, &subsub);
313 dbus_message_iter_close_container(&dict, &sub);
314 dbus_message_iter_close_container(&arr, &dict);
315
316 prop = "IsStatusNotifierHostRegistered";
317 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
318 NULL, &dict);
319 dbus_message_iter_append_basic(&dict,
320 DBUS_TYPE_STRING, &prop);
321 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
322 "b", &sub);
323 dbus_message_iter_append_basic(&sub,
324 DBUS_TYPE_BOOLEAN, &registered);
325 dbus_message_iter_close_container(&dict, &sub);
326 dbus_message_iter_close_container(&arr, &dict);
327
328 prop = "ProtocolVersion";
329 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
330 NULL, &dict);
331 dbus_message_iter_append_basic(&dict,
332 DBUS_TYPE_STRING, &prop);
333 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
334 "i", &sub);
335 dbus_message_iter_append_basic(&sub,
336 DBUS_TYPE_INT32, &version);
337 dbus_message_iter_close_container(&dict, &sub);
338 dbus_message_iter_close_container(&arr, &dict);
339
340 dbus_message_iter_close_container(&iter, &arr);
341
342 dbus_connection_send(connection, reply, NULL);
343 dbus_message_unref(reply);
344}
345
346static DBusHandlerResult message_handler(DBusConnection *connection,
347 DBusMessage *message, void *data) {
348 const char *interface_name = dbus_message_get_interface(message);
349 const char *member_name = dbus_message_get_member(message);
350
351 // In order of the xml above
352 if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
353 strcmp(member_name, "Introspect") == 0) {
354 // We don't have an introspect for KDE
355 respond_to_introspect(connection, message);
356 return DBUS_HANDLER_RESULT_HANDLED;
357 } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) {
358 if (strcmp(member_name, "Get") == 0) {
359 get_property(connection, message);
360 return DBUS_HANDLER_RESULT_HANDLED;
361 } else if (strcmp(member_name, "Set") == 0) {
362 set_property(connection, message);
363 return DBUS_HANDLER_RESULT_HANDLED;
364 } else if (strcmp(member_name, "GetAll") == 0) {
365 get_all(connection, message);
366 return DBUS_HANDLER_RESULT_HANDLED;
367 } else {
368 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
369 }
370 } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 ||
371 strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) {
372 if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) {
373 register_item(connection, message);
374 return DBUS_HANDLER_RESULT_HANDLED;
375 } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) {
376 register_host(connection, message);
377 return DBUS_HANDLER_RESULT_HANDLED;
378 } else {
379 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
380 }
381 }
382 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
383}
384
385static DBusHandlerResult signal_handler(DBusConnection *connection,
386 DBusMessage *message, void *_data) {
387 if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) {
388 // Only eat the message if it is name that we are watching
389 const char *name;
390 const char *old_owner;
391 const char *new_owner;
392 int index;
393 if (!dbus_message_get_args(message, NULL,
394 DBUS_TYPE_STRING, &name,
395 DBUS_TYPE_STRING, &old_owner,
396 DBUS_TYPE_STRING, &new_owner,
397 DBUS_TYPE_INVALID)) {
398 sway_log(L_ERROR, "Error getting LostName args");
399 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
400 }
401 if (strcmp(new_owner, "") != 0) {
402 // Name is not lost
403 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
404 }
405 if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) {
406 sway_log(L_INFO, "Status Notifier Item lost %s", name);
407 free(items->items[index]);
408 list_del(items, index);
409 item_unregistered_signal(connection, name);
410
411 return DBUS_HANDLER_RESULT_HANDLED;
412 }
413 if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) {
414 sway_log(L_INFO, "Status Notifier Host lost %s", name);
415 free(hosts->items[index]);
416 list_del(hosts, index);
417
418 return DBUS_HANDLER_RESULT_HANDLED;
419 }
420 }
421 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
422}
423
424static const DBusObjectPathVTable vtable = {
425 .message_function = message_handler,
426 .unregister_function = NULL,
427};
428
429int init_sni_watcher() {
430 DBusError error;
431 dbus_error_init(&error);
432 if (!conn) {
433 sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher");
434 return -1;
435 }
436
437 items = create_list();
438 hosts = create_list();
439
440 int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
441 DBUS_NAME_FLAG_REPLACE_EXISTING,
442 &error);
443 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
444 sway_log(L_DEBUG, "Got watcher name");
445 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
446 sway_log(L_INFO, "Could not get watcher name, it may start later");
447 }
448 if (dbus_error_is_set(&error)) {
449 sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message);
450 return -1;
451 }
452
453 status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher",
454 DBUS_NAME_FLAG_REPLACE_EXISTING,
455 &error);
456 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
457 sway_log(L_DEBUG, "Got kde watcher name");
458 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
459 sway_log(L_INFO, "Could not get kde watcher name, it may start later");
460 }
461 if (dbus_error_is_set(&error)) {
462 sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message);
463 return -1;
464 }
465
466 dbus_connection_try_register_object_path(conn,
467 "/StatusNotifierWatcher",
468 &vtable, NULL, &error);
469 if (dbus_error_is_set(&error)) {
470 sway_log(L_ERROR, "dbus_err: %s\n", error.message);
471 return -1;
472 }
473
474 dbus_bus_add_match(conn,
475 "type='signal',\
476 sender='org.freedesktop.DBus',\
477 interface='org.freedesktop.DBus',\
478 member='NameOwnerChanged'",
479 &error);
480
481 if (dbus_error_is_set(&error)) {
482 sway_log(L_ERROR, "DBus error getting match args: %s", error.message);
483 }
484
485 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
486 return 0;
487}