aboutsummaryrefslogtreecommitdiffstats
path: root/swaybar/tray/icon.c
diff options
context:
space:
mode:
authorLibravatar Calvin Lee <cyrus296@gmail.com>2017-06-07 16:45:28 -0700
committerLibravatar Calvin Lee <cyrus296@gmail.com>2017-06-07 17:49:16 -0700
commit843ad38b3c427adb0bf319e9613d9813c8d9246c (patch)
treee02a5b06e2b6923371fd53724791c147c18a1fa4 /swaybar/tray/icon.c
parentMerge pull request #1232 from johalun/master-freebsd (diff)
downloadsway-843ad38b3c427adb0bf319e9613d9813c8d9246c.tar.gz
sway-843ad38b3c427adb0bf319e9613d9813c8d9246c.tar.zst
sway-843ad38b3c427adb0bf319e9613d9813c8d9246c.zip
Implement Tray Icons
This commit implements the StatusNotifierItem protocol, and enables swaybar to show tray icons. It also uses `xembedsniproxy` in order to communicate with xembed applications. The tray is completely optional, and can be disabled on compile time with the `enable-tray` option. Or on runtime with the bar config option `tray_output none`. Overview of changes: In swaybar very little is changed outside the tray subfolder except that all events are now polled in `event_loop.c`, this creates no functional difference. Six bar configuration options were added, these are detailed in sway-bar(5) The tray subfolder is where all protocol implementation takes place and is organised as follows: tray/sni_watcher.c: This file contains the StatusNotifierWatcher. It keeps track of items and hosts and reports when they come or go. tray/tray.c This file contains the StatusNotifierHost. It keeps track of sway's version of the items and represents the tray itself. tray/sni.c This file contains the StatusNotifierItem struct and all communication with individual items. tray/icon.c This file implements the icon theme protocol. It allows for finding icons by name, rather than by pixmap. tray/dbus.c This file allows for asynchronous DBus communication. See #986 #343
Diffstat (limited to 'swaybar/tray/icon.c')
-rw-r--r--swaybar/tray/icon.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
new file mode 100644
index 00000000..29151a74
--- /dev/null
+++ b/swaybar/tray/icon.c
@@ -0,0 +1,404 @@
1#define _XOPEN_SOURCE 500
2#define _POSIX_C_SOURCE 200809L
3#include <stdio.h>
4#include <stdlib.h>
5#include <unistd.h>
6#include <string.h>
7#include <dirent.h>
8#include <sys/stat.h>
9#include <stdbool.h>
10#include <stdint.h>
11#include <limits.h>
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "swaybar/config.h"
15#include "stringop.h"
16#include "log.h"
17
18/**
19 * REVIEW:
20 * This file repeats lots of "costly" operations that are the same for every
21 * icon. It's possible to create a dictionary or some other structure to cache
22 * these, though it may complicate things somewhat.
23 *
24 * Also parsing (index.theme) is currently pretty messy, so that could be made
25 * much better as well. Over all, things work, but are not optimal.
26 */
27
28/* Finds all themes that the given theme inherits */
29static list_t *find_inherits(const char *theme_dir) {
30 const char inherits[] = "Inherits";
31 const char index_name[] = "index.theme";
32 list_t *themes = create_list();
33 FILE *index = NULL;
34 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
35 if (!path) {
36 goto fail;
37 }
38 if (!themes) {
39 goto fail;
40 }
41
42 strcpy(path, theme_dir);
43 strcat(path, index_name);
44
45 index = fopen(path, "r");
46 if (!index) {
47 goto fail;
48 }
49
50 char *buf = NULL;
51 size_t n = 0;
52 while (!feof(index)) {
53 getline(&buf, &n, index);
54 if (n <= sizeof(inherits) + 1) {
55 continue;
56 }
57 if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) {
58 char *themestr = buf + sizeof(inherits);
59 themes = split_string(themestr, ",");
60 break;
61 }
62 }
63 free(buf);
64
65fail:
66 free(path);
67 if (index) {
68 fclose(index);
69 }
70 return themes;
71}
72
73static bool isdir(const char *path) {
74 struct stat statbuf;
75 if (stat(path, &statbuf) != -1) {
76 if (S_ISDIR(statbuf.st_mode)) {
77 return true;
78 }
79 }
80 return false;
81
82}
83
84/**
85 * Returns the directory of a given theme if it exists.
86 * The returned pointer must be freed.
87 */
88static char *find_theme_dir(const char *theme) {
89 char *basedir;
90 char *icon_dir;
91
92 if (!theme) {
93 return NULL;
94 }
95
96 if (!(icon_dir = malloc(1024))) {
97 sway_log(L_ERROR, "Out of memory!");
98 goto fail;
99 }
100
101 if ((basedir = getenv("HOME"))) {
102 if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) {
103 sway_log(L_ERROR, "Path too long to render");
104 // XXX perhaps just goto trying in /usr/share? This
105 // shouldn't happen anyway, but might with a long global
106 goto fail;
107 }
108
109 if (isdir(icon_dir)) {
110 return icon_dir;
111 }
112 }
113
114 if ((basedir = getenv("XDG_DATA_DIRS"))) {
115 if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) {
116 sway_log(L_ERROR, "Path too long to render");
117 // ditto
118 goto fail;
119 }
120
121 if (isdir(icon_dir)) {
122 return icon_dir;
123 }
124 }
125
126 // Spec says use "/usr/share/pixmaps/", but I see everything in
127 // "/usr/share/icons/" look it both, I suppose.
128 if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) {
129 sway_log(L_ERROR, "Path too long to render");
130 goto fail;
131 }
132 if (isdir(icon_dir)) {
133 return icon_dir;
134 }
135
136 if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) {
137 sway_log(L_ERROR, "Path too long to render");
138 goto fail;
139 }
140 if (isdir(icon_dir)) {
141 return icon_dir;
142 }
143
144fail:
145 free(icon_dir);
146 sway_log(L_ERROR, "Could not find dir for theme: %s", theme);
147 return NULL;
148}
149
150/**
151 * Returns all theme dirs needed to be looked in for an icon.
152 * Does not check for duplicates
153 */
154static list_t *find_all_theme_dirs(const char *theme) {
155 list_t *dirs = create_list();
156 if (!dirs) {
157 return NULL;
158 }
159 char *dir = find_theme_dir(theme);
160 if (dir) {
161 list_add(dirs, dir);
162 list_t *inherits = find_inherits(dir);
163 list_cat(dirs, inherits);
164 list_free(inherits);
165 }
166 dir = find_theme_dir("hicolor");
167 if (dir) {
168 list_add(dirs, dir);
169 }
170
171 return dirs;
172}
173
174struct subdir {
175 int size;
176 char name[];
177};
178
179static int subdir_str_cmp(const void *_subdir, const void *_str) {
180 const struct subdir *subdir = _subdir;
181 const char *str = _str;
182 return strcmp(subdir->name, str);
183}
184/**
185 * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but
186 * generates a list of struct subdirs
187 */
188static list_t *split_subdirs(char *subdir_str) {
189 list_t *subdir_list = create_list();
190 char *copy = strdup(subdir_str);
191 if (!subdir_list || !copy) {
192 list_free(subdir_list);
193 free(copy);
194 return NULL;
195 }
196
197 char *token;
198 token = strtok(copy, ",");
199 while(token) {
200 int len = strlen(token) + 1;
201 struct subdir *subdir =
202 malloc(sizeof(struct subdir) + sizeof(char [len]));
203 if (!subdir) {
204 // Return what we have
205 return subdir_list;
206 }
207 subdir->size = 0;
208 strcpy(subdir->name, token);
209
210 list_add(subdir_list, subdir);
211
212 token = strtok(NULL, ",");
213 }
214 free(copy);
215
216 return subdir_list;
217}
218/**
219 * Returns a list of all subdirectories of a theme.
220 * Take note: the subdir names are all relative to `theme_dir` and must be
221 * combined with it to form a valid directory.
222 *
223 * Each member of the list is of type (struct subdir *) this struct contains
224 * the name of the subdir, along with size information. These must be freed
225 * bye the caller.
226 *
227 * This currently ignores min and max sizes of icons.
228 */
229static list_t* find_theme_subdirs(const char *theme_dir) {
230 const char index_name[] = "/index.theme";
231 list_t *dirs = NULL;
232 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
233 FILE *index = NULL;
234 if (!path) {
235 sway_log(L_ERROR, "Failed to allocate memory");
236 goto fail;
237 }
238
239 strcpy(path, theme_dir);
240 strcat(path, index_name);
241
242 index = fopen(path, "r");
243 if (!index) {
244 sway_log(L_ERROR, "Could not open file: %s", path);
245 goto fail;
246 }
247
248 char *buf = NULL;
249 size_t n = 0;
250 while (!feof(index)) {
251 const char directories[] = "Directories";
252 getline(&buf, &n, index);
253 if (n <= sizeof(directories) + 1) {
254 continue;
255 }
256 if (strncmp(directories, buf, sizeof(directories) - 1) == 0) {
257 char *dirstr = buf + sizeof(directories);
258 dirs = split_subdirs(dirstr);
259 break;
260 }
261 }
262 // Now, find the size of each dir
263 struct subdir *current_subdir = NULL;
264 while (!feof(index)) {
265 const char size[] = "Size";
266 getline(&buf, &n, index);
267
268 if (buf[0] == '[') {
269 int len = strlen(buf);
270 if (buf[len-1] == '\n') {
271 len--;
272 }
273 // replace ']'
274 buf[len-1] = '\0';
275
276 int index;
277 if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) {
278 current_subdir = (dirs->items[index]);
279 }
280 }
281
282 if (strncmp(size, buf, sizeof(size) - 1) == 0) {
283 if (current_subdir) {
284 current_subdir->size = atoi(buf + sizeof(size));
285 }
286 }
287 }
288 free(buf);
289fail:
290 free(path);
291 if (index) {
292 fclose(index);
293 }
294 return dirs;
295}
296
297/* Returns the file of an icon given its name and size */
298static char *find_icon_file(const char *name, int size) {
299 int namelen = strlen(name);
300 list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme);
301 if (!dirs) {
302 return NULL;
303 }
304 int min_size_diff = INT_MAX;
305 char *current_file = NULL;
306
307 for (int i = 0; i < dirs->length; ++i) {
308 char *dir = dirs->items[i];
309 list_t *subdirs = find_theme_subdirs(dir);
310
311 if (!subdirs) {
312 continue;
313 }
314
315 for (int i = 0; i < subdirs->length; ++i) {
316 struct subdir *subdir = subdirs->items[i];
317
318 // Only use an unsized if we don't already have a
319 // canidate this should probably change to allow svgs
320 if (!subdir->size && current_file) {
321 continue;
322 }
323
324 int size_diff = abs(size - subdir->size);
325
326 if (size_diff >= min_size_diff) {
327 continue;
328 }
329
330 char *path = malloc(strlen(subdir->name) + strlen(dir) + 2);
331
332 strcpy(path, dir);
333 path[strlen(dir)] = '/';
334 strcpy(path + strlen(dir) + 1, subdir->name);
335
336 DIR *icons = opendir(path);
337 if (!icons) {
338 free(path);
339 continue;
340 }
341
342 struct dirent *direntry;
343 while ((direntry = readdir(icons)) != NULL) {
344 int len = strlen(direntry->d_name);
345 if (len <= namelen + 2) { //must have some ext
346 continue;
347 }
348 if (strncmp(direntry->d_name, name, namelen) == 0) {
349 char *ext = direntry->d_name + namelen + 1;
350#ifdef WITH_GDK_PIXBUF
351 if (strcmp(ext, "png") == 0 ||
352 strcmp(ext, "xpm") == 0 ||
353 strcmp(ext, "svg") == 0) {
354#else
355 if (strcmp(ext, "png") == 0) {
356#endif
357 free(current_file);
358 char *icon_path = malloc(strlen(path) + len + 2);
359
360 strcpy(icon_path, path);
361 icon_path[strlen(path)] = '/';
362 strcpy(icon_path + strlen(path) + 1, direntry->d_name);
363 current_file = icon_path;
364 min_size_diff = size_diff;
365 }
366 }
367 }
368 free(path);
369 closedir(icons);
370 }
371 free_flat_list(subdirs);
372 }
373 free_flat_list(dirs);
374
375 return current_file;
376}
377
378cairo_surface_t *find_icon(const char *name, int size) {
379 char *image_path = find_icon_file(name, size);
380 if (image_path == NULL) {
381 return NULL;
382 }
383
384 cairo_surface_t *image = NULL;
385#ifdef WITH_GDK_PIXBUF
386 GError *err = NULL;
387 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err);
388 if (!pixbuf) {
389 sway_log(L_ERROR, "Failed to load icon image: %s", err->message);
390 }
391 image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
392 g_object_unref(pixbuf);
393#else
394 // TODO make svg work? cairo supports it. maybe remove gdk alltogether
395 image = cairo_image_surface_create_from_png(image_path);
396#endif //WITH_GDK_PIXBUF
397 if (!image) {
398 sway_log(L_ERROR, "Could not read icon image");
399 return NULL;
400 }
401
402 free(image_path);
403 return image;
404}