diff options
Diffstat (limited to 'swaybar/tray/host.c')
-rw-r--r-- | swaybar/tray/host.c | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c new file mode 100644 index 00000000..30339fec --- /dev/null +++ b/swaybar/tray/host.c | |||
@@ -0,0 +1,210 @@ | |||
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 | |||
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 | |||
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 | wlr_log(WLR_DEBUG, "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 | |||
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 | wlr_log(WLR_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 | |||
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 | wlr_log(WLR_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 | wlr_log(WLR_DEBUG, "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 | |||
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 | sd_bus_error err = *sd_bus_message_get_error(msg); | ||
69 | wlr_log(WLR_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 | wlr_log(WLR_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 | wlr_log(WLR_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 | } | ||
91 | } | ||
92 | |||
93 | return ret; | ||
94 | } | ||
95 | |||
96 | static bool register_to_watcher(struct swaybar_host *host) { | ||
97 | // this is called asynchronously in case the watcher is owned by this process | ||
98 | int ret = sd_bus_call_method_async(host->tray->bus, NULL, | ||
99 | host->watcher_interface, watcher_path, host->watcher_interface, | ||
100 | "RegisterStatusNotifierHost", NULL, NULL, "s", host->service); | ||
101 | if (ret < 0) { | ||
102 | wlr_log(WLR_ERROR, "Failed to send register call: %s", strerror(-ret)); | ||
103 | return false; | ||
104 | } | ||
105 | |||
106 | ret = sd_bus_call_method_async(host->tray->bus, NULL, | ||
107 | host->watcher_interface, watcher_path, | ||
108 | "org.freedesktop.DBus.Properties", "Get", | ||
109 | get_registered_snis_callback, host->tray, "ss", | ||
110 | host->watcher_interface, "RegisteredStatusNotifierItems"); | ||
111 | if (ret < 0) { | ||
112 | wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", strerror(-ret)); | ||
113 | } | ||
114 | |||
115 | return ret >= 0; | ||
116 | } | ||
117 | |||
118 | static int handle_new_watcher(sd_bus_message *msg, | ||
119 | void *data, sd_bus_error *error) { | ||
120 | char *service, *old_owner, *new_owner; | ||
121 | int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); | ||
122 | if (ret < 0) { | ||
123 | wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret)); | ||
124 | return ret; | ||
125 | } | ||
126 | |||
127 | if (!*old_owner) { | ||
128 | struct swaybar_host *host = data; | ||
129 | if (strcmp(service, host->watcher_interface) == 0) { | ||
130 | register_to_watcher(host); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | return 0; | ||
135 | } | ||
136 | |||
137 | bool init_host(struct swaybar_host *host, char *protocol, | ||
138 | struct swaybar_tray *tray) { | ||
139 | size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; | ||
140 | host->watcher_interface = malloc(len); | ||
141 | if (!host->watcher_interface) { | ||
142 | return false; | ||
143 | } | ||
144 | snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol); | ||
145 | |||
146 | sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL; | ||
147 | int ret = sd_bus_match_signal(tray->bus, ®_slot, host->watcher_interface, | ||
148 | watcher_path, host->watcher_interface, | ||
149 | "StatusNotifierItemRegistered", handle_sni_registered, tray); | ||
150 | if (ret < 0) { | ||
151 | wlr_log(WLR_ERROR, "Failed to subscribe to registering events: %s", | ||
152 | strerror(-ret)); | ||
153 | goto error; | ||
154 | } | ||
155 | ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface, | ||
156 | watcher_path, host->watcher_interface, | ||
157 | "StatusNotifierItemUnregistered", handle_sni_unregistered, tray); | ||
158 | if (ret < 0) { | ||
159 | wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", | ||
160 | strerror(-ret)); | ||
161 | goto error; | ||
162 | } | ||
163 | |||
164 | ret = sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus", | ||
165 | "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", | ||
166 | handle_new_watcher, host); | ||
167 | if (ret < 0) { | ||
168 | wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", | ||
169 | strerror(-ret)); | ||
170 | goto error; | ||
171 | } | ||
172 | |||
173 | pid_t pid = getpid(); | ||
174 | size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d", | ||
175 | protocol, pid) + 1; | ||
176 | host->service = malloc(service_len); | ||
177 | if (!host->service) { | ||
178 | goto error; | ||
179 | } | ||
180 | snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid); | ||
181 | ret = sd_bus_request_name(tray->bus, host->service, 0); | ||
182 | if (ret < 0) { | ||
183 | wlr_log(WLR_DEBUG, "Failed to acquire service name: %s", strerror(-ret)); | ||
184 | goto error; | ||
185 | } | ||
186 | |||
187 | host->tray = tray; | ||
188 | if (!register_to_watcher(host)) { | ||
189 | goto error; | ||
190 | } | ||
191 | |||
192 | sd_bus_slot_set_floating(reg_slot, 1); | ||
193 | sd_bus_slot_set_floating(unreg_slot, 1); | ||
194 | sd_bus_slot_set_floating(watcher_slot, 1); | ||
195 | |||
196 | wlr_log(WLR_DEBUG, "Registered %s", host->service); | ||
197 | return true; | ||
198 | error: | ||
199 | sd_bus_slot_unref(reg_slot); | ||
200 | sd_bus_slot_unref(unreg_slot); | ||
201 | sd_bus_slot_unref(watcher_slot); | ||
202 | finish_host(host); | ||
203 | return false; | ||
204 | } | ||
205 | |||
206 | void finish_host(struct swaybar_host *host) { | ||
207 | sd_bus_release_name(host->tray->bus, host->service); | ||
208 | free(host->service); | ||
209 | free(host->watcher_interface); | ||
210 | } | ||