summaryrefslogtreecommitdiffstats
path: root/swaybar
diff options
context:
space:
mode:
Diffstat (limited to 'swaybar')
-rw-r--r--swaybar/bar.c83
-rw-r--r--swaybar/config.c23
-rw-r--r--swaybar/i3bar.c36
-rw-r--r--swaybar/input.c84
-rw-r--r--swaybar/ipc.c115
-rw-r--r--swaybar/main.c4
-rw-r--r--swaybar/meson.build44
-rw-r--r--swaybar/render.c190
-rw-r--r--swaybar/status_line.c2
-rw-r--r--swaybar/tray/host.c210
-rw-r--r--swaybar/tray/icon.c462
-rw-r--r--swaybar/tray/item.c472
-rw-r--r--swaybar/tray/tray.c131
-rw-r--r--swaybar/tray/watcher.c212
14 files changed, 1885 insertions, 183 deletions
diff --git a/swaybar/bar.c b/swaybar/bar.c
index 08c386a7..d36367fc 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -1,4 +1,4 @@
1#define _XOPEN_SOURCE 500 1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 2#include <assert.h>
3#include <errno.h> 3#include <errno.h>
4#include <fcntl.h> 4#include <fcntl.h>
@@ -11,6 +11,7 @@
11#include <wayland-client.h> 11#include <wayland-client.h>
12#include <wayland-cursor.h> 12#include <wayland-cursor.h>
13#include <wlr/util/log.h> 13#include <wlr/util/log.h>
14#include "config.h"
14#include "swaybar/bar.h" 15#include "swaybar/bar.h"
15#include "swaybar/config.h" 16#include "swaybar/config.h"
16#include "swaybar/i3bar.h" 17#include "swaybar/i3bar.h"
@@ -18,6 +19,9 @@
18#include "swaybar/ipc.h" 19#include "swaybar/ipc.h"
19#include "swaybar/status_line.h" 20#include "swaybar/status_line.h"
20#include "swaybar/render.h" 21#include "swaybar/render.h"
22#if HAVE_TRAY
23#include "swaybar/tray/tray.h"
24#endif
21#include "ipc-client.h" 25#include "ipc-client.h"
22#include "list.h" 26#include "list.h"
23#include "log.h" 27#include "log.h"
@@ -31,6 +35,7 @@ void free_workspaces(struct wl_list *list) {
31 wl_list_for_each_safe(ws, tmp, list, link) { 35 wl_list_for_each_safe(ws, tmp, list, link) {
32 wl_list_remove(&ws->link); 36 wl_list_remove(&ws->link);
33 free(ws->name); 37 free(ws->name);
38 free(ws->label);
34 free(ws); 39 free(ws);
35 } 40 }
36} 41}
@@ -54,6 +59,7 @@ static void swaybar_output_free(struct swaybar_output *output) {
54 free_workspaces(&output->workspaces); 59 free_workspaces(&output->workspaces);
55 wl_list_remove(&output->link); 60 wl_list_remove(&output->link);
56 free(output->name); 61 free(output->name);
62 free(output->identifier);
57 free(output); 63 free(output);
58} 64}
59 65
@@ -119,7 +125,7 @@ static void destroy_layer_surface(struct swaybar_output *output) {
119 output->frame_scheduled = false; 125 output->frame_scheduled = false;
120} 126}
121 127
122static void set_bar_dirty(struct swaybar *bar) { 128void set_bar_dirty(struct swaybar *bar) {
123 struct swaybar_output *output; 129 struct swaybar_output *output;
124 wl_list_for_each(output, &bar->outputs, link) { 130 wl_list_for_each(output, &bar->outputs, link) {
125 set_output_dirty(output); 131 set_output_dirty(output);
@@ -161,13 +167,15 @@ bool determine_bar_visibility(struct swaybar *bar, bool moving_layer) {
161 return visible; 167 return visible;
162} 168}
163 169
164static bool bar_uses_output(struct swaybar *bar, const char *name) { 170static bool bar_uses_output(struct swaybar_output *output) {
165 if (bar->config->all_outputs) { 171 if (output->bar->config->all_outputs) {
166 return true; 172 return true;
167 } 173 }
174 char *identifier = output->identifier;
168 struct config_output *coutput; 175 struct config_output *coutput;
169 wl_list_for_each(coutput, &bar->config->outputs, link) { 176 wl_list_for_each(coutput, &output->bar->config->outputs, link) {
170 if (strcmp(coutput->name, name) == 0) { 177 if (strcmp(coutput->name, output->name) == 0 ||
178 (identifier && strcmp(coutput->name, identifier) == 0)) {
171 return true; 179 return true;
172 } 180 }
173 } 181 }
@@ -195,6 +203,10 @@ static void output_scale(void *data, struct wl_output *wl_output,
195 int32_t factor) { 203 int32_t factor) {
196 struct swaybar_output *output = data; 204 struct swaybar_output *output = data;
197 output->scale = factor; 205 output->scale = factor;
206 if (output == output->bar->pointer.current) {
207 update_cursor(output->bar);
208 render_frame(output);
209 }
198} 210}
199 211
200struct wl_output_listener output_listener = { 212struct wl_output_listener output_listener = {
@@ -206,12 +218,16 @@ struct wl_output_listener output_listener = {
206 218
207static void xdg_output_handle_logical_position(void *data, 219static void xdg_output_handle_logical_position(void *data,
208 struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { 220 struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
209 // Who cares 221 struct swaybar_output *output = data;
222 output->output_x = x;
223 output->output_y = y;
210} 224}
211 225
212static void xdg_output_handle_logical_size(void *data, 226static void xdg_output_handle_logical_size(void *data,
213 struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { 227 struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
214 // Who cares 228 struct swaybar_output *output = data;
229 output->output_height = height;
230 output->output_width = width;
215} 231}
216 232
217static void xdg_output_handle_done(void *data, 233static void xdg_output_handle_done(void *data,
@@ -220,7 +236,7 @@ static void xdg_output_handle_done(void *data,
220 struct swaybar *bar = output->bar; 236 struct swaybar *bar = output->bar;
221 237
222 assert(output->name != NULL); 238 assert(output->name != NULL);
223 if (!bar_uses_output(bar, output->name)) { 239 if (!bar_uses_output(output)) {
224 swaybar_output_free(output); 240 swaybar_output_free(output);
225 return; 241 return;
226 } 242 }
@@ -245,7 +261,22 @@ static void xdg_output_handle_name(void *data,
245 261
246static void xdg_output_handle_description(void *data, 262static void xdg_output_handle_description(void *data,
247 struct zxdg_output_v1 *xdg_output, const char *description) { 263 struct zxdg_output_v1 *xdg_output, const char *description) {
248 // Who cares 264 // wlroots currently sets the description to `make model serial (name)`
265 // If this changes in the future, this will need to be modified.
266 struct swaybar_output *output = data;
267 free(output->identifier);
268 output->identifier = NULL;
269 char *paren = strrchr(description, '(');
270 if (paren) {
271 size_t length = paren - description;
272 output->identifier = malloc(length);
273 if (!output->identifier) {
274 wlr_log(WLR_ERROR, "Failed to allocate output identifier");
275 return;
276 }
277 strncpy(output->identifier, description, length);
278 output->identifier[length - 1] = '\0';
279 }
249} 280}
250 281
251struct zxdg_output_v1_listener xdg_output_listener = { 282struct zxdg_output_v1_listener xdg_output_listener = {
@@ -272,7 +303,7 @@ static void handle_global(void *data, struct wl_registry *registry,
272 struct swaybar *bar = data; 303 struct swaybar *bar = data;
273 if (strcmp(interface, wl_compositor_interface.name) == 0) { 304 if (strcmp(interface, wl_compositor_interface.name) == 0) {
274 bar->compositor = wl_registry_bind(registry, name, 305 bar->compositor = wl_registry_bind(registry, name,
275 &wl_compositor_interface, 3); 306 &wl_compositor_interface, 4);
276 } else if (strcmp(interface, wl_seat_interface.name) == 0) { 307 } else if (strcmp(interface, wl_seat_interface.name) == 0) {
277 bar->seat = wl_registry_bind(registry, name, 308 bar->seat = wl_registry_bind(registry, name,
278 &wl_seat_interface, 3); 309 &wl_seat_interface, 3);
@@ -354,25 +385,15 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
354 wl_display_roundtrip(bar->display); 385 wl_display_roundtrip(bar->display);
355 386
356 struct swaybar_pointer *pointer = &bar->pointer; 387 struct swaybar_pointer *pointer = &bar->pointer;
357
358 int max_scale = 1;
359 struct swaybar_output *output;
360 wl_list_for_each(output, &bar->outputs, link) {
361 if (output->scale > max_scale) {
362 max_scale = output->scale;
363 }
364 }
365
366 pointer->cursor_theme =
367 wl_cursor_theme_load(NULL, 24 * max_scale, bar->shm);
368 assert(pointer->cursor_theme);
369 struct wl_cursor *cursor;
370 cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr");
371 assert(cursor);
372 pointer->cursor_image = cursor->images[0];
373 pointer->cursor_surface = wl_compositor_create_surface(bar->compositor); 388 pointer->cursor_surface = wl_compositor_create_surface(bar->compositor);
374 assert(pointer->cursor_surface); 389 assert(pointer->cursor_surface);
375 390
391#if HAVE_TRAY
392 if (!bar->config->tray_hidden) {
393 bar->tray = create_tray(bar);
394 }
395#endif
396
376 if (bar->config->workspace_buttons) { 397 if (bar->config->workspace_buttons) {
377 ipc_get_workspaces(bar); 398 ipc_get_workspaces(bar);
378 } 399 }
@@ -414,6 +435,11 @@ void bar_run(struct swaybar *bar) {
414 loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN, 435 loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN,
415 status_in, bar); 436 status_in, bar);
416 } 437 }
438#if HAVE_TRAY
439 if (bar->tray) {
440 loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus);
441 }
442#endif
417 while (1) { 443 while (1) {
418 errno = 0; 444 errno = 0;
419 if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) { 445 if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) {
@@ -431,6 +457,9 @@ static void free_outputs(struct wl_list *list) {
431} 457}
432 458
433void bar_teardown(struct swaybar *bar) { 459void bar_teardown(struct swaybar *bar) {
460#if HAVE_TRAY
461 destroy_tray(bar->tray);
462#endif
434 free_outputs(&bar->outputs); 463 free_outputs(&bar->outputs);
435 if (bar->config) { 464 if (bar->config) {
436 free_config(bar->config); 465 free_config(bar->config);
diff --git a/swaybar/config.c b/swaybar/config.c
index 0fd1f02e..d4cc9b1a 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -1,9 +1,10 @@
1#define _XOPEN_SOURCE 500 1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 2#include <stdlib.h>
3#include <string.h> 3#include <string.h>
4#include <wlr/util/log.h> 4#include <wlr/util/log.h>
5#include "swaybar/config.h" 5#include "swaybar/config.h"
6#include "wlr-layer-shell-unstable-v1-client-protocol.h" 6#include "wlr-layer-shell-unstable-v1-client-protocol.h"
7#include "config.h"
7#include "stringop.h" 8#include "stringop.h"
8#include "list.h" 9#include "list.h"
9 10
@@ -30,15 +31,24 @@ struct swaybar_config *init_config(void) {
30 config->hidden_state = strdup("hide"); 31 config->hidden_state = strdup("hide");
31 config->sep_symbol = NULL; 32 config->sep_symbol = NULL;
32 config->strip_workspace_numbers = false; 33 config->strip_workspace_numbers = false;
34 config->strip_workspace_name = false;
33 config->binding_mode_indicator = true; 35 config->binding_mode_indicator = true;
34 config->wrap_scroll = false; 36 config->wrap_scroll = false;
35 config->workspace_buttons = true; 37 config->workspace_buttons = true;
36 config->bindings = create_list(); 38 config->bindings = create_list();
37 wl_list_init(&config->outputs); 39 wl_list_init(&config->outputs);
40 config->status_padding = 1;
41 config->status_edge_padding = 3;
38 42
39 /* height */ 43 /* height */
40 config->height = 0; 44 config->height = 0;
41 45
46 /* gaps */
47 config->gaps.top = 0;
48 config->gaps.right = 0;
49 config->gaps.bottom = 0;
50 config->gaps.left = 0;
51
42 /* colors */ 52 /* colors */
43 config->colors.background = 0x000000FF; 53 config->colors.background = 0x000000FF;
44 config->colors.focused_background = 0x000000FF; 54 config->colors.focused_background = 0x000000FF;
@@ -66,6 +76,10 @@ struct swaybar_config *init_config(void) {
66 config->colors.binding_mode.background = 0x900000FF; 76 config->colors.binding_mode.background = 0x900000FF;
67 config->colors.binding_mode.text = 0xFFFFFFFF; 77 config->colors.binding_mode.text = 0xFFFFFFFF;
68 78
79#if HAVE_TRAY
80 config->tray_padding = 2;
81#endif
82
69 return config; 83 return config;
70} 84}
71 85
@@ -95,5 +109,12 @@ void free_config(struct swaybar_config *config) {
95 free(coutput->name); 109 free(coutput->name);
96 free(coutput); 110 free(coutput);
97 } 111 }
112#if HAVE_TRAY
113 list_free_items_and_destroy(config->tray_outputs);
114 for (int i = 0; i < 10; ++i) {
115 free(config->tray_bindings[i]);
116 }
117 free(config->icon_theme);
118#endif
98 free(config); 119 free(config);
99} 120}
diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c
index 3ea74e13..116c8f6e 100644
--- a/swaybar/i3bar.c
+++ b/swaybar/i3bar.c
@@ -259,8 +259,34 @@ bool i3bar_handle_readable(struct status_line *status) {
259 } 259 }
260} 260}
261 261
262static uint32_t event_to_x11_button(uint32_t event) {
263 switch (event) {
264 case BTN_LEFT:
265 return 1;
266 case BTN_MIDDLE:
267 return 2;
268 case BTN_RIGHT:
269 return 3;
270 case SWAY_SCROLL_UP:
271 return 4;
272 case SWAY_SCROLL_DOWN:
273 return 5;
274 case SWAY_SCROLL_LEFT:
275 return 6;
276 case SWAY_SCROLL_RIGHT:
277 return 7;
278 case BTN_SIDE:
279 return 8;
280 case BTN_EXTRA:
281 return 9;
282 default:
283 return 0;
284 }
285}
286
262enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, 287enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
263 struct i3bar_block *block, int x, int y, enum x11_button button) { 288 struct i3bar_block *block, int x, int y, int rx, int ry, int w, int h,
289 uint32_t button) {
264 wlr_log(WLR_DEBUG, "block %s clicked", block->name); 290 wlr_log(WLR_DEBUG, "block %s clicked", block->name);
265 if (!block->name || !status->click_events) { 291 if (!block->name || !status->click_events) {
266 return HOTSPOT_PROCESS; 292 return HOTSPOT_PROCESS;
@@ -274,9 +300,15 @@ enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
274 json_object_new_string(block->instance)); 300 json_object_new_string(block->instance));
275 } 301 }
276 302
277 json_object_object_add(event_json, "button", json_object_new_int(button)); 303 json_object_object_add(event_json, "button",
304 json_object_new_int(event_to_x11_button(button)));
305 json_object_object_add(event_json, "event", json_object_new_int(button));
278 json_object_object_add(event_json, "x", json_object_new_int(x)); 306 json_object_object_add(event_json, "x", json_object_new_int(x));
279 json_object_object_add(event_json, "y", json_object_new_int(y)); 307 json_object_object_add(event_json, "y", json_object_new_int(y));
308 json_object_object_add(event_json, "relative_x", json_object_new_int(rx));
309 json_object_object_add(event_json, "relative_y", json_object_new_int(ry));
310 json_object_object_add(event_json, "width", json_object_new_int(w));
311 json_object_object_add(event_json, "height", json_object_new_int(h));
280 if (dprintf(status->write_fd, "%s%s\n", status->clicked ? "," : "", 312 if (dprintf(status->write_fd, "%s%s\n", status->clicked ? "," : "",
281 json_object_to_json_string(event_json)) < 0) { 313 json_object_to_json_string(event_json)) < 0) {
282 status_error(status, "[failed to write click event]"); 314 status_error(status, "[failed to write click event]");
diff --git a/swaybar/input.c b/swaybar/input.c
index 263d0253..bdd55e58 100644
--- a/swaybar/input.c
+++ b/swaybar/input.c
@@ -1,9 +1,5 @@
1#include <assert.h> 1#include <assert.h>
2#ifdef __FreeBSD__
3#include <dev/evdev/input-event-codes.h>
4#else
5#include <linux/input-event-codes.h> 2#include <linux/input-event-codes.h>
6#endif
7#include <stdlib.h> 3#include <stdlib.h>
8#include <wayland-client.h> 4#include <wayland-client.h>
9#include <wayland-cursor.h> 5#include <wayland-cursor.h>
@@ -26,33 +22,39 @@ void free_hotspots(struct wl_list *list) {
26 } 22 }
27} 23}
28 24
29static enum x11_button wl_button_to_x11_button(uint32_t button) { 25static uint32_t wl_axis_to_button(uint32_t axis, wl_fixed_t value) {
30 switch (button) { 26 bool negative = wl_fixed_to_double(value) < 0;
31 case BTN_LEFT:
32 return LEFT;
33 case BTN_MIDDLE:
34 return MIDDLE;
35 case BTN_RIGHT:
36 return RIGHT;
37 case BTN_SIDE:
38 return BACK;
39 case BTN_EXTRA:
40 return FORWARD;
41 default:
42 return NONE;
43 }
44}
45
46static enum x11_button wl_axis_to_x11_button(uint32_t axis, wl_fixed_t value) {
47 switch (axis) { 27 switch (axis) {
48 case WL_POINTER_AXIS_VERTICAL_SCROLL: 28 case WL_POINTER_AXIS_VERTICAL_SCROLL:
49 return wl_fixed_to_double(value) < 0 ? SCROLL_UP : SCROLL_DOWN; 29 return negative ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN;
50 case WL_POINTER_AXIS_HORIZONTAL_SCROLL: 30 case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
51 return wl_fixed_to_double(value) < 0 ? SCROLL_LEFT : SCROLL_RIGHT; 31 return negative ? SWAY_SCROLL_LEFT : SWAY_SCROLL_RIGHT;
52 default: 32 default:
53 wlr_log(WLR_DEBUG, "Unexpected axis value on mouse scroll"); 33 wlr_log(WLR_DEBUG, "Unexpected axis value on mouse scroll");
54 return NONE; 34 return 0;
35 }
36}
37
38void update_cursor(struct swaybar *bar) {
39 struct swaybar_pointer *pointer = &bar->pointer;
40 if (pointer->cursor_theme) {
41 wl_cursor_theme_destroy(pointer->cursor_theme);
55 } 42 }
43 int scale = pointer->current ? pointer->current->scale : 1;
44 pointer->cursor_theme = wl_cursor_theme_load(NULL, 24 * scale, bar->shm);
45 struct wl_cursor *cursor;
46 cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr");
47 pointer->cursor_image = cursor->images[0];
48 wl_surface_set_buffer_scale(pointer->cursor_surface, scale);
49 wl_surface_attach(pointer->cursor_surface,
50 wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0);
51 wl_pointer_set_cursor(pointer->pointer, pointer->serial,
52 pointer->cursor_surface,
53 pointer->cursor_image->hotspot_x / scale,
54 pointer->cursor_image->hotspot_y / scale);
55 wl_surface_damage_buffer(pointer->cursor_surface, 0, 0,
56 INT32_MAX, INT32_MAX);
57 wl_surface_commit(pointer->cursor_surface);
56} 58}
57 59
58static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, 60static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
@@ -60,6 +62,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
60 wl_fixed_t surface_x, wl_fixed_t surface_y) { 62 wl_fixed_t surface_x, wl_fixed_t surface_y) {
61 struct swaybar *bar = data; 63 struct swaybar *bar = data;
62 struct swaybar_pointer *pointer = &bar->pointer; 64 struct swaybar_pointer *pointer = &bar->pointer;
65 pointer->serial = serial;
63 struct swaybar_output *output; 66 struct swaybar_output *output;
64 wl_list_for_each(output, &bar->outputs, link) { 67 wl_list_for_each(output, &bar->outputs, link) {
65 if (output->surface == surface) { 68 if (output->surface == surface) {
@@ -67,20 +70,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
67 break; 70 break;
68 } 71 }
69 } 72 }
70 int max_scale = 1; 73 update_cursor(bar);
71 struct swaybar_output *_output;
72 wl_list_for_each(_output, &bar->outputs, link) {
73 if (_output->scale > max_scale) {
74 max_scale = _output->scale;
75 }
76 }
77 wl_surface_set_buffer_scale(pointer->cursor_surface, max_scale);
78 wl_surface_attach(pointer->cursor_surface,
79 wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0);
80 wl_pointer_set_cursor(wl_pointer, serial, pointer->cursor_surface,
81 pointer->cursor_image->hotspot_x / max_scale,
82 pointer->cursor_image->hotspot_y / max_scale);
83 wl_surface_commit(pointer->cursor_surface);
84} 74}
85 75
86static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, 76static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
@@ -96,12 +86,12 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
96 bar->pointer.y = wl_fixed_to_int(surface_y); 86 bar->pointer.y = wl_fixed_to_int(surface_y);
97} 87}
98 88
99static bool check_bindings(struct swaybar *bar, uint32_t x11_button, 89static bool check_bindings(struct swaybar *bar, uint32_t button,
100 uint32_t state) { 90 uint32_t state) {
101 bool released = state == WL_POINTER_BUTTON_STATE_RELEASED; 91 bool released = state == WL_POINTER_BUTTON_STATE_RELEASED;
102 for (int i = 0; i < bar->config->bindings->length; i++) { 92 for (int i = 0; i < bar->config->bindings->length; i++) {
103 struct swaybar_binding *binding = bar->config->bindings->items[i]; 93 struct swaybar_binding *binding = bar->config->bindings->items[i];
104 if (binding->button == x11_button && binding->release == released) { 94 if (binding->button == button && binding->release == released) {
105 ipc_execute_binding(bar, binding); 95 ipc_execute_binding(bar, binding);
106 return true; 96 return true;
107 } 97 }
@@ -118,7 +108,7 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
118 return; 108 return;
119 } 109 }
120 110
121 if (check_bindings(bar, wl_button_to_x11_button(button), state)) { 111 if (check_bindings(bar, button, state)) {
122 return; 112 return;
123 } 113 }
124 114
@@ -133,8 +123,8 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
133 && y >= hotspot->y 123 && y >= hotspot->y
134 && x < hotspot->x + hotspot->width 124 && x < hotspot->x + hotspot->width
135 && y < hotspot->y + hotspot->height) { 125 && y < hotspot->y + hotspot->height) {
136 if (HOTSPOT_IGNORE == hotspot->callback(output, pointer->x, pointer->y, 126 if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot,
137 wl_button_to_x11_button(button), hotspot->data)) { 127 pointer->x, pointer->y, button, hotspot->data)) {
138 return; 128 return;
139 } 129 }
140 } 130 }
@@ -152,7 +142,7 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
152 142
153 // If there is a button press binding, execute it, skip default behavior, 143 // If there is a button press binding, execute it, skip default behavior,
154 // and check button release bindings 144 // and check button release bindings
155 enum x11_button button = wl_axis_to_x11_button(axis, value); 145 uint32_t button = wl_axis_to_button(axis, value);
156 if (check_bindings(bar, button, WL_POINTER_BUTTON_STATE_PRESSED)) { 146 if (check_bindings(bar, button, WL_POINTER_BUTTON_STATE_PRESSED)) {
157 check_bindings(bar, button, WL_POINTER_BUTTON_STATE_RELEASED); 147 check_bindings(bar, button, WL_POINTER_BUTTON_STATE_RELEASED);
158 return; 148 return;
@@ -166,8 +156,8 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
166 && y >= hotspot->y 156 && y >= hotspot->y
167 && x < hotspot->x + hotspot->width 157 && x < hotspot->x + hotspot->width
168 && y < hotspot->y + hotspot->height) { 158 && y < hotspot->y + hotspot->height) {
169 if (HOTSPOT_IGNORE == hotspot->callback( 159 if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot,
170 output, pointer->x, pointer->y, button, hotspot->data)) { 160 pointer->x, pointer->y, button, hotspot->data)) {
171 return; 161 return;
172 } 162 }
173 } 163 }
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index 706f968d..097f9161 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -6,6 +6,7 @@
6#include <wlr/util/log.h> 6#include <wlr/util/log.h>
7#include "swaybar/config.h" 7#include "swaybar/config.h"
8#include "swaybar/ipc.h" 8#include "swaybar/ipc.h"
9#include "config.h"
9#include "ipc-client.h" 10#include "ipc-client.h"
10#include "list.h" 11#include "list.h"
11 12
@@ -153,18 +154,21 @@ static bool ipc_parse_config(
153 return false; 154 return false;
154 } 155 }
155 json_object *markup, *mode, *hidden_state, *position, *status_command; 156 json_object *markup, *mode, *hidden_state, *position, *status_command;
156 json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers; 157 json_object *font, *gaps, *bar_height, *wrap_scroll, *workspace_buttons;
157 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs; 158 json_object *strip_workspace_numbers, *strip_workspace_name;
158 json_object *bindings; 159 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol;
160 json_object *outputs, *bindings, *status_padding, *status_edge_padding;
159 json_object_object_get_ex(bar_config, "mode", &mode); 161 json_object_object_get_ex(bar_config, "mode", &mode);
160 json_object_object_get_ex(bar_config, "hidden_state", &hidden_state); 162 json_object_object_get_ex(bar_config, "hidden_state", &hidden_state);
161 json_object_object_get_ex(bar_config, "position", &position); 163 json_object_object_get_ex(bar_config, "position", &position);
162 json_object_object_get_ex(bar_config, "status_command", &status_command); 164 json_object_object_get_ex(bar_config, "status_command", &status_command);
163 json_object_object_get_ex(bar_config, "font", &font); 165 json_object_object_get_ex(bar_config, "font", &font);
166 json_object_object_get_ex(bar_config, "gaps", &gaps);
164 json_object_object_get_ex(bar_config, "bar_height", &bar_height); 167 json_object_object_get_ex(bar_config, "bar_height", &bar_height);
165 json_object_object_get_ex(bar_config, "wrap_scroll", &wrap_scroll); 168 json_object_object_get_ex(bar_config, "wrap_scroll", &wrap_scroll);
166 json_object_object_get_ex(bar_config, "workspace_buttons", &workspace_buttons); 169 json_object_object_get_ex(bar_config, "workspace_buttons", &workspace_buttons);
167 json_object_object_get_ex(bar_config, "strip_workspace_numbers", &strip_workspace_numbers); 170 json_object_object_get_ex(bar_config, "strip_workspace_numbers", &strip_workspace_numbers);
171 json_object_object_get_ex(bar_config, "strip_workspace_name", &strip_workspace_name);
168 json_object_object_get_ex(bar_config, "binding_mode_indicator", &binding_mode_indicator); 172 json_object_object_get_ex(bar_config, "binding_mode_indicator", &binding_mode_indicator);
169 json_object_object_get_ex(bar_config, "verbose", &verbose); 173 json_object_object_get_ex(bar_config, "verbose", &verbose);
170 json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol); 174 json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol);
@@ -172,6 +176,9 @@ static bool ipc_parse_config(
172 json_object_object_get_ex(bar_config, "outputs", &outputs); 176 json_object_object_get_ex(bar_config, "outputs", &outputs);
173 json_object_object_get_ex(bar_config, "pango_markup", &markup); 177 json_object_object_get_ex(bar_config, "pango_markup", &markup);
174 json_object_object_get_ex(bar_config, "bindings", &bindings); 178 json_object_object_get_ex(bar_config, "bindings", &bindings);
179 json_object_object_get_ex(bar_config, "status_padding", &status_padding);
180 json_object_object_get_ex(bar_config, "status_edge_padding",
181 &status_edge_padding);
175 if (status_command) { 182 if (status_command) {
176 free(config->status_command); 183 free(config->status_command);
177 config->status_command = strdup(json_object_get_string(status_command)); 184 config->status_command = strdup(json_object_get_string(status_command));
@@ -190,6 +197,9 @@ static bool ipc_parse_config(
190 if (strip_workspace_numbers) { 197 if (strip_workspace_numbers) {
191 config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers); 198 config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers);
192 } 199 }
200 if (strip_workspace_name) {
201 config->strip_workspace_name = json_object_get_boolean(strip_workspace_name);
202 }
193 if (binding_mode_indicator) { 203 if (binding_mode_indicator) {
194 config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator); 204 config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator);
195 } 205 }
@@ -202,6 +212,30 @@ static bool ipc_parse_config(
202 if (bar_height) { 212 if (bar_height) {
203 config->height = json_object_get_int(bar_height); 213 config->height = json_object_get_int(bar_height);
204 } 214 }
215 if (status_padding) {
216 config->status_padding = json_object_get_int(status_padding);
217 }
218 if (status_edge_padding) {
219 config->status_edge_padding = json_object_get_int(status_edge_padding);
220 }
221 if (gaps) {
222 json_object *top = json_object_object_get(gaps, "top");
223 if (top) {
224 config->gaps.top = json_object_get_int(top);
225 }
226 json_object *right = json_object_object_get(gaps, "right");
227 if (right) {
228 config->gaps.right = json_object_get_int(right);
229 }
230 json_object *bottom = json_object_object_get(gaps, "bottom");
231 if (bottom) {
232 config->gaps.bottom = json_object_get_int(bottom);
233 }
234 json_object *left = json_object_object_get(gaps, "left");
235 if (left) {
236 config->gaps.left = json_object_get_int(left);
237 }
238 }
205 if (markup) { 239 if (markup) {
206 config->pango_markup = json_object_get_boolean(markup); 240 config->pango_markup = json_object_get_boolean(markup);
207 } 241 }
@@ -212,7 +246,7 @@ static bool ipc_parse_config(
212 struct swaybar_binding *binding = 246 struct swaybar_binding *binding =
213 calloc(1, sizeof(struct swaybar_binding)); 247 calloc(1, sizeof(struct swaybar_binding));
214 binding->button = json_object_get_int( 248 binding->button = json_object_get_int(
215 json_object_object_get(bindobj, "input_code")); 249 json_object_object_get(bindobj, "event_code"));
216 binding->command = strdup(json_object_get_string( 250 binding->command = strdup(json_object_get_string(
217 json_object_object_get(bindobj, "command"))); 251 json_object_object_get(bindobj, "command")));
218 binding->release = json_object_get_boolean( 252 binding->release = json_object_get_boolean(
@@ -258,6 +292,40 @@ static bool ipc_parse_config(
258 ipc_parse_colors(config, colors); 292 ipc_parse_colors(config, colors);
259 } 293 }
260 294
295#if HAVE_TRAY
296 json_object *tray_outputs, *tray_padding, *tray_bindings, *icon_theme;
297
298 if ((json_object_object_get_ex(bar_config, "tray_outputs", &tray_outputs))) {
299 config->tray_outputs = create_list();
300 int length = json_object_array_length(tray_outputs);
301 for (int i = 0; i < length; ++i) {
302 json_object *o = json_object_array_get_idx(tray_outputs, i);
303 list_add(config->tray_outputs, strdup(json_object_get_string(o)));
304 }
305 config->tray_hidden = strcmp(config->tray_outputs->items[0], "none") == 0;
306 }
307
308 if ((json_object_object_get_ex(bar_config, "tray_padding", &tray_padding))) {
309 config->tray_padding = json_object_get_int(tray_padding);
310 }
311
312 if ((json_object_object_get_ex(bar_config, "tray_bindings", &tray_bindings))) {
313 int length = json_object_array_length(tray_bindings);
314 for (int i = 0; i < length; ++i) {
315 json_object *bind = json_object_array_get_idx(tray_bindings, i);
316 json_object *button, *command;
317 json_object_object_get_ex(bind, "input_code", &button);
318 json_object_object_get_ex(bind, "command", &command);
319 config->tray_bindings[json_object_get_int(button)] =
320 strdup(json_object_get_string(command));
321 }
322 }
323
324 if ((json_object_object_get_ex(bar_config, "icon_theme", &icon_theme))) {
325 config->icon_theme = strdup(json_object_get_string(icon_theme));
326 }
327#endif
328
261 json_object_put(bar_config); 329 json_object_put(bar_config);
262 return true; 330 return true;
263} 331}
@@ -298,6 +366,24 @@ bool ipc_get_workspaces(struct swaybar *bar) {
298 calloc(1, sizeof(struct swaybar_workspace)); 366 calloc(1, sizeof(struct swaybar_workspace));
299 ws->num = json_object_get_int(num); 367 ws->num = json_object_get_int(num);
300 ws->name = strdup(json_object_get_string(name)); 368 ws->name = strdup(json_object_get_string(name));
369 ws->label = strdup(ws->name);
370 // ws->num will be -1 if workspace name doesn't begin with int.
371 if (ws->num != -1) {
372 size_t len_offset = numlen(ws->num);
373 if (bar->config->strip_workspace_name) {
374 free(ws->label);
375 ws->label = malloc(len_offset + 1 * sizeof(char));
376 ws->label[len_offset] = '\0';
377 strncpy(ws->label, ws->name, len_offset);
378 } else if (bar->config->strip_workspace_numbers) {
379 len_offset += ws->label[len_offset] == ':';
380 if (strlen(ws->name) > len_offset) {
381 free(ws->label);
382 // Strip number prefix [1-?:] using len_offset.
383 ws->label = strdup(ws->name + len_offset);
384 }
385 }
386 }
301 ws->visible = json_object_get_boolean(visible); 387 ws->visible = json_object_get_boolean(visible);
302 ws->focused = json_object_get_boolean(focused); 388 ws->focused = json_object_get_boolean(focused);
303 if (ws->focused) { 389 if (ws->focused) {
@@ -423,6 +509,27 @@ static bool handle_barconfig_update(struct swaybar *bar,
423 config->mode = strdup(json_object_get_string(json_mode)); 509 config->mode = strdup(json_object_get_string(json_mode));
424 wlr_log(WLR_DEBUG, "Changing bar mode to %s", config->mode); 510 wlr_log(WLR_DEBUG, "Changing bar mode to %s", config->mode);
425 511
512 json_object *gaps;
513 json_object_object_get_ex(json_config, "gaps", &gaps);
514 if (gaps) {
515 json_object *top = json_object_object_get(gaps, "top");
516 if (top) {
517 config->gaps.top = json_object_get_int(top);
518 }
519 json_object *right = json_object_object_get(gaps, "right");
520 if (right) {
521 config->gaps.right = json_object_get_int(right);
522 }
523 json_object *bottom = json_object_object_get(gaps, "bottom");
524 if (bottom) {
525 config->gaps.bottom = json_object_get_int(bottom);
526 }
527 json_object *left = json_object_object_get(gaps, "left");
528 if (left) {
529 config->gaps.left = json_object_get_int(left);
530 }
531 }
532
426 return determine_bar_visibility(bar, true); 533 return determine_bar_visibility(bar, true);
427} 534}
428 535
diff --git a/swaybar/main.c b/swaybar/main.c
index 2672abef..fa99b1ba 100644
--- a/swaybar/main.c
+++ b/swaybar/main.c
@@ -1,4 +1,4 @@
1#define _XOPEN_SOURCE 500 1#define _POSIX_C_SOURCE 200809L
2#include <stdio.h> 2#include <stdio.h>
3#include <stdlib.h> 3#include <stdlib.h>
4#include <string.h> 4#include <string.h>
@@ -76,7 +76,7 @@ int main(int argc, char **argv) {
76 if (debug) { 76 if (debug) {
77 wlr_log_init(WLR_DEBUG, NULL); 77 wlr_log_init(WLR_DEBUG, NULL);
78 } else { 78 } else {
79 wlr_log_init(WLR_ERROR, NULL); 79 wlr_log_init(WLR_INFO, NULL);
80 } 80 }
81 81
82 if (!swaybar.id) { 82 if (!swaybar.id) {
diff --git a/swaybar/meson.build b/swaybar/meson.build
index c27cf2c2..312ca97b 100644
--- a/swaybar/meson.build
+++ b/swaybar/meson.build
@@ -1,3 +1,32 @@
1tray_files = get_option('enable-tray') ? [
2 'tray/host.c',
3 'tray/icon.c',
4 'tray/item.c',
5 'tray/tray.c',
6 'tray/watcher.c'
7] : []
8
9swaybar_deps = [
10 cairo,
11 client_protos,
12 gdk_pixbuf,
13 jsonc,
14 math,
15 pango,
16 pangocairo,
17 rt,
18 wayland_client,
19 wayland_cursor,
20 wlroots,
21]
22if get_option('enable-tray')
23 if systemd.found()
24 swaybar_deps += systemd
25 elif elogind.found()
26 swaybar_deps += elogind
27 endif
28endif
29
1executable( 30executable(
2 'swaybar', [ 31 'swaybar', [
3 'bar.c', 32 'bar.c',
@@ -8,21 +37,10 @@ executable(
8 'main.c', 37 'main.c',
9 'render.c', 38 'render.c',
10 'status_line.c', 39 'status_line.c',
40 tray_files
11 ], 41 ],
12 include_directories: [sway_inc], 42 include_directories: [sway_inc],
13 dependencies: [ 43 dependencies: swaybar_deps,
14 cairo,
15 client_protos,
16 gdk_pixbuf,
17 jsonc,
18 math,
19 pango,
20 pangocairo,
21 rt,
22 wayland_client,
23 wayland_cursor,
24 wlroots,
25 ],
26 link_with: [lib_sway_common, lib_sway_client], 44 link_with: [lib_sway_common, lib_sway_client],
27 install_rpath : rpathdir, 45 install_rpath : rpathdir,
28 install: true 46 install: true
diff --git a/swaybar/render.c b/swaybar/render.c
index 4ebf922e..55f680ed 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -1,5 +1,6 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 2#include <assert.h>
3#include <linux/input-event-codes.h>
3#include <limits.h> 4#include <limits.h>
4#include <stdlib.h> 5#include <stdlib.h>
5#include <stdint.h> 6#include <stdint.h>
@@ -14,6 +15,9 @@
14#include "swaybar/ipc.h" 15#include "swaybar/ipc.h"
15#include "swaybar/render.h" 16#include "swaybar/render.h"
16#include "swaybar/status_line.h" 17#include "swaybar/status_line.h"
18#if HAVE_TRAY
19#include "swaybar/tray/tray.h"
20#endif
17#include "wlr-layer-shell-unstable-v1-client-protocol.h" 21#include "wlr-layer-shell-unstable-v1-client-protocol.h"
18 22
19static const int WS_HORIZONTAL_PADDING = 5; 23static const int WS_HORIZONTAL_PADDING = 5;
@@ -32,7 +36,8 @@ static uint32_t render_status_line_error(cairo_t *cairo,
32 cairo_set_source_u32(cairo, 0xFF0000FF); 36 cairo_set_source_u32(cairo, 0xFF0000FF);
33 37
34 int margin = 3 * output->scale; 38 int margin = 3 * output->scale;
35 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 39 double ws_vertical_padding =
40 output->bar->config->status_padding * output->scale;
36 41
37 char *font = output->bar->config->font; 42 char *font = output->bar->config->font;
38 int text_width, text_height; 43 int text_width, text_height;
@@ -41,7 +46,8 @@ static uint32_t render_status_line_error(cairo_t *cairo,
41 46
42 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 47 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
43 uint32_t ideal_surface_height = ideal_height / output->scale; 48 uint32_t ideal_surface_height = ideal_height / output->scale;
44 if (output->height < ideal_surface_height) { 49 if (!output->bar->config->height &&
50 output->height < ideal_surface_height) {
45 return ideal_surface_height; 51 return ideal_surface_height;
46 } 52 }
47 *x -= text_width + margin; 53 *x -= text_width + margin;
@@ -68,12 +74,13 @@ static uint32_t render_status_line_text(cairo_t *cairo,
68 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 74 get_text_size(cairo, config->font, &text_width, &text_height, NULL,
69 output->scale, config->pango_markup, "%s", text); 75 output->scale, config->pango_markup, "%s", text);
70 76
71 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 77 double ws_vertical_padding = config->status_padding * output->scale;
72 int margin = 3 * output->scale; 78 int margin = 3 * output->scale;
73 79
74 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 80 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
75 uint32_t ideal_surface_height = ideal_height / output->scale; 81 uint32_t ideal_surface_height = ideal_height / output->scale;
76 if (output->height < ideal_surface_height) { 82 if (!output->bar->config->height &&
83 output->height < ideal_surface_height) {
77 return ideal_surface_height; 84 return ideal_surface_height;
78 } 85 }
79 86
@@ -87,13 +94,24 @@ static uint32_t render_status_line_text(cairo_t *cairo,
87 return output->height; 94 return output->height;
88} 95}
89 96
90static void render_sharp_line(cairo_t *cairo, uint32_t color, 97static void render_sharp_rectangle(cairo_t *cairo, uint32_t color,
91 double x, double y, double width, double height) { 98 double x, double y, double width, double height) {
99 cairo_save(cairo);
92 cairo_set_source_u32(cairo, color); 100 cairo_set_source_u32(cairo, color);
101 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
102 cairo_rectangle(cairo, x, y, width, height);
103 cairo_fill(cairo);
104 cairo_restore(cairo);
105}
106
107static void render_sharp_line(cairo_t *cairo, uint32_t color,
108 double x, double y, double width, double height) {
93 if (width > 1 && height > 1) { 109 if (width > 1 && height > 1) {
94 cairo_rectangle(cairo, x, y, width, height); 110 render_sharp_rectangle(cairo, color, x, y, width, height);
95 cairo_fill(cairo);
96 } else { 111 } else {
112 cairo_save(cairo);
113 cairo_set_source_u32(cairo, color);
114 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
97 if (width == 1) { 115 if (width == 1) {
98 x += 0.5; 116 x += 0.5;
99 height += y; 117 height += y;
@@ -108,14 +126,17 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
108 cairo_set_line_width(cairo, 1.0); 126 cairo_set_line_width(cairo, 1.0);
109 cairo_line_to(cairo, width, height); 127 cairo_line_to(cairo, width, height);
110 cairo_stroke(cairo); 128 cairo_stroke(cairo);
129 cairo_restore(cairo);
111 } 130 }
112} 131}
113 132
114static enum hotspot_event_handling block_hotspot_callback(struct swaybar_output *output, 133static enum hotspot_event_handling block_hotspot_callback(
115 int x, int y, enum x11_button button, void *data) { 134 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
135 int x, int y, uint32_t button, void *data) {
116 struct i3bar_block *block = data; 136 struct i3bar_block *block = data;
117 struct status_line *status = output->bar->status; 137 struct status_line *status = output->bar->status;
118 return i3bar_block_send_click(status, block, x, y, button); 138 return i3bar_block_send_click(status, block, x, y, x - hotspot->x,
139 y - hotspot->y, hotspot->width, hotspot->height, button);
119} 140}
120 141
121static void i3bar_block_unref_callback(void *data) { 142static void i3bar_block_unref_callback(void *data) {
@@ -136,7 +157,7 @@ static uint32_t render_status_block(cairo_t *cairo,
136 output->scale, block->markup, "%s", block->full_text); 157 output->scale, block->markup, "%s", block->full_text);
137 158
138 int margin = 3 * output->scale; 159 int margin = 3 * output->scale;
139 int ws_vertical_padding = WS_VERTICAL_PADDING * 2; 160 double ws_vertical_padding = config->status_padding * output->scale;
140 161
141 int width = text_width; 162 int width = text_width;
142 if (width < block->min_width) { 163 if (width < block->min_width) {
@@ -146,37 +167,40 @@ static uint32_t render_status_block(cairo_t *cairo,
146 double block_width = width; 167 double block_width = width;
147 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 168 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
148 uint32_t ideal_surface_height = ideal_height / output->scale; 169 uint32_t ideal_surface_height = ideal_height / output->scale;
149 if (output->height < ideal_surface_height) { 170 if (!output->bar->config->height &&
171 output->height < ideal_surface_height) {
150 return ideal_surface_height; 172 return ideal_surface_height;
151 } 173 }
152 174
153 *x -= width; 175 *x -= width;
154 if (block->border && block->border_left > 0) { 176 if ((block->border || block->urgent) && block->border_left > 0) {
155 *x -= (block->border_left + margin); 177 *x -= (block->border_left * output->scale + margin);
156 block_width += block->border_left + margin; 178 block_width += block->border_left * output->scale + margin;
157 } 179 }
158 if (block->border && block->border_right > 0) { 180 if ((block->border || block->urgent) && block->border_right > 0) {
159 *x -= (block->border_right + margin); 181 *x -= (block->border_right * output->scale + margin);
160 block_width += block->border_right + margin; 182 block_width += block->border_right * output->scale + margin;
161 } 183 }
162 184
163 int sep_width, sep_height; 185 int sep_width, sep_height;
186 int sep_block_width = block->separator_block_width;
164 if (!edge) { 187 if (!edge) {
165 if (config->sep_symbol) { 188 if (config->sep_symbol) {
166 get_text_size(cairo, config->font, &sep_width, &sep_height, NULL, 189 get_text_size(cairo, config->font, &sep_width, &sep_height, NULL,
167 output->scale, false, "%s", config->sep_symbol); 190 output->scale, false, "%s", config->sep_symbol);
168 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2; 191 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2;
169 uint32_t _ideal_surface_height = _ideal_height / output->scale; 192 uint32_t _ideal_surface_height = _ideal_height / output->scale;
170 if (output->height < _ideal_surface_height) { 193 if (!output->bar->config->height &&
194 output->height < _ideal_surface_height) {
171 return _ideal_surface_height; 195 return _ideal_surface_height;
172 } 196 }
173 if (sep_width > block->separator_block_width) { 197 if (sep_width > sep_block_width) {
174 block->separator_block_width = sep_width + margin * 2; 198 sep_block_width = sep_width + margin * 2;
175 } 199 }
176 } 200 }
177 *x -= block->separator_block_width; 201 *x -= sep_block_width;
178 } else { 202 } else if (config->status_edge_padding) {
179 *x -= margin; 203 *x -= config->status_edge_padding * output->scale;
180 } 204 }
181 205
182 uint32_t height = output->height * output->scale; 206 uint32_t height = output->height * output->scale;
@@ -193,53 +217,55 @@ static uint32_t render_status_block(cairo_t *cairo,
193 wl_list_insert(&output->hotspots, &hotspot->link); 217 wl_list_insert(&output->hotspots, &hotspot->link);
194 } 218 }
195 219
196 double pos = *x; 220 double x_pos = *x;
197 if (block->background) { 221 double y_pos = ws_vertical_padding;
198 cairo_set_source_u32(cairo, block->background); 222 double render_height = height - ws_vertical_padding * 2;
199 cairo_rectangle(cairo, pos - 0.5 * output->scale, 223
200 output->scale, width, height); 224 uint32_t bg_color = block->urgent
201 cairo_fill(cairo); 225 ? config->colors.urgent_workspace.background : block->background;
226 if (bg_color) {
227 render_sharp_rectangle(cairo, bg_color, x_pos, y_pos,
228 block_width, render_height);
202 } 229 }
203 230
204 if (block->border && block->border_top > 0) { 231 uint32_t border_color = block->urgent
205 render_sharp_line(cairo, block->border, 232 ? config->colors.urgent_workspace.border : block->border;
206 pos - 0.5 * output->scale, output->scale, 233 if (border_color && block->border_top > 0) {
207 block_width, block->border_top); 234 render_sharp_line(cairo, border_color, x_pos, y_pos,
235 block_width, block->border_top * output->scale);
208 } 236 }
209 if (block->border && block->border_bottom > 0) { 237 if (border_color && block->border_bottom > 0) {
210 render_sharp_line(cairo, block->border, 238 render_sharp_line(cairo, border_color, x_pos,
211 pos - 0.5 * output->scale, 239 y_pos + render_height - block->border_bottom * output->scale,
212 height - output->scale - block->border_bottom, 240 block_width, block->border_bottom * output->scale);
213 block_width, block->border_bottom);
214 } 241 }
215 if (block->border != 0 && block->border_left > 0) { 242 if (border_color && block->border_left > 0) {
216 render_sharp_line(cairo, block->border, 243 render_sharp_line(cairo, border_color, x_pos, y_pos,
217 pos - 0.5 * output->scale, output->scale, 244 block->border_left * output->scale, render_height);
218 block->border_left, height); 245 x_pos += block->border_left * output->scale + margin;
219 pos += block->border_left + margin;
220 } 246 }
221 247
222 double offset = 0; 248 double offset = 0;
223 if (strncmp(block->align, "left", 5) == 0) { 249 if (strncmp(block->align, "left", 5) == 0) {
224 offset = pos; 250 offset = x_pos;
225 } else if (strncmp(block->align, "right", 5) == 0) { 251 } else if (strncmp(block->align, "right", 5) == 0) {
226 offset = pos + width - text_width; 252 offset = x_pos + width - text_width;
227 } else if (strncmp(block->align, "center", 6) == 0) { 253 } else if (strncmp(block->align, "center", 6) == 0) {
228 offset = pos + (width - text_width) / 2; 254 offset = x_pos + (width - text_width) / 2;
229 } 255 }
230 cairo_move_to(cairo, offset, height / 2.0 - text_height / 2.0); 256 cairo_move_to(cairo, offset, height / 2.0 - text_height / 2.0);
231 uint32_t color = block->color ? *block->color : config->colors.statusline; 257 uint32_t color = block->color ? *block->color : config->colors.statusline;
258 color = block->urgent ? config->colors.urgent_workspace.text : color;
232 cairo_set_source_u32(cairo, color); 259 cairo_set_source_u32(cairo, color);
233 pango_printf(cairo, config->font, output->scale, 260 pango_printf(cairo, config->font, output->scale,
234 block->markup, "%s", block->full_text); 261 block->markup, "%s", block->full_text);
235 pos += width; 262 x_pos += width;
236 263
237 if (block->border && block->border_right > 0) { 264 if (block->border && block->border_right > 0) {
238 pos += margin; 265 x_pos += margin;
239 render_sharp_line(cairo, block->border, 266 render_sharp_line(cairo, border_color, x_pos, y_pos,
240 pos - 0.5 * output->scale, output->scale, 267 block->border_right * output->scale, render_height);
241 block->border_right, height); 268 x_pos += block->border_right * output->scale;
242 pos += block->border_right;
243 } 269 }
244 270
245 if (!edge && block->separator) { 271 if (!edge && block->separator) {
@@ -249,16 +275,14 @@ static uint32_t render_status_block(cairo_t *cairo,
249 cairo_set_source_u32(cairo, config->colors.separator); 275 cairo_set_source_u32(cairo, config->colors.separator);
250 } 276 }
251 if (config->sep_symbol) { 277 if (config->sep_symbol) {
252 offset = pos + (block->separator_block_width - sep_width) / 2; 278 offset = x_pos + (sep_block_width - sep_width) / 2;
253 cairo_move_to(cairo, offset, height / 2.0 - sep_height / 2.0); 279 cairo_move_to(cairo, offset, height / 2.0 - sep_height / 2.0);
254 pango_printf(cairo, config->font, output->scale, false, 280 pango_printf(cairo, config->font, output->scale, false,
255 "%s", config->sep_symbol); 281 "%s", config->sep_symbol);
256 } else { 282 } else {
257 cairo_set_line_width(cairo, 1); 283 cairo_set_line_width(cairo, 1);
258 cairo_move_to(cairo, 284 cairo_move_to(cairo, x_pos + sep_block_width / 2, margin);
259 pos + block->separator_block_width / 2, margin); 285 cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin);
260 cairo_line_to(cairo,
261 pos + block->separator_block_width / 2, height - margin);
262 cairo_stroke(cairo); 286 cairo_stroke(cairo);
263 } 287 }
264 } 288 }
@@ -268,7 +292,7 @@ static uint32_t render_status_block(cairo_t *cairo,
268static uint32_t render_status_line_i3bar(cairo_t *cairo, 292static uint32_t render_status_line_i3bar(cairo_t *cairo,
269 struct swaybar_output *output, double *x) { 293 struct swaybar_output *output, double *x) {
270 uint32_t max_height = 0; 294 uint32_t max_height = 0;
271 bool edge = true; 295 bool edge = *x == output->width * output->scale;
272 struct i3bar_block *block; 296 struct i3bar_block *block;
273 wl_list_for_each(block, &output->bar->status->blocks, link) { 297 wl_list_for_each(block, &output->bar->status->blocks, link) {
274 uint32_t h = render_status_block(cairo, output, block, x, edge); 298 uint32_t h = render_status_block(cairo, output, block, x, edge);
@@ -314,7 +338,8 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
314 uint32_t ideal_height = text_height + ws_vertical_padding * 2 338 uint32_t ideal_height = text_height + ws_vertical_padding * 2
315 + border_width * 2; 339 + border_width * 2;
316 uint32_t ideal_surface_height = ideal_height / output->scale; 340 uint32_t ideal_surface_height = ideal_height / output->scale;
317 if (output->height < ideal_surface_height) { 341 if (!output->bar->config->height &&
342 output->height < ideal_surface_height) {
318 return ideal_surface_height; 343 return ideal_surface_height;
319 } 344 }
320 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 345 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
@@ -342,22 +367,10 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
342 return output->height; 367 return output->height;
343} 368}
344 369
345static const char *strip_workspace_number(const char *ws_name) { 370static enum hotspot_event_handling workspace_hotspot_callback(
346 size_t len = strlen(ws_name); 371 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
347 for (size_t i = 0; i < len; ++i) { 372 int x, int y, uint32_t button, void *data) {
348 if (ws_name[i] < '0' || ws_name[i] > '9') { 373 if (button != BTN_LEFT) {
349 if (':' == ws_name[i] && i < len - 1 && i > 0) {
350 return ws_name + i + 1;
351 }
352 return ws_name;
353 }
354 }
355 return ws_name;
356}
357
358static enum hotspot_event_handling workspace_hotspot_callback(struct swaybar_output *output,
359 int x, int y, enum x11_button button, void *data) {
360 if (button != LEFT) {
361 return HOTSPOT_PROCESS; 374 return HOTSPOT_PROCESS;
362 } 375 }
363 ipc_send_workspace_command(output->bar, (const char *)data); 376 ipc_send_workspace_command(output->bar, (const char *)data);
@@ -368,11 +381,6 @@ static uint32_t render_workspace_button(cairo_t *cairo,
368 struct swaybar_output *output, 381 struct swaybar_output *output,
369 struct swaybar_workspace *ws, double *x) { 382 struct swaybar_workspace *ws, double *x) {
370 struct swaybar_config *config = output->bar->config; 383 struct swaybar_config *config = output->bar->config;
371 const char *name = ws->name;
372 if (config->strip_workspace_numbers) {
373 name = strip_workspace_number(ws->name);
374 }
375
376 struct box_colors box_colors; 384 struct box_colors box_colors;
377 if (ws->urgent) { 385 if (ws->urgent) {
378 box_colors = config->colors.urgent_workspace; 386 box_colors = config->colors.urgent_workspace;
@@ -388,7 +396,7 @@ static uint32_t render_workspace_button(cairo_t *cairo,
388 396
389 int text_width, text_height; 397 int text_width, text_height;
390 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 398 get_text_size(cairo, config->font, &text_width, &text_height, NULL,
391 output->scale, config->pango_markup, "%s", name); 399 output->scale, config->pango_markup, "%s", ws->label);
392 400
393 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 401 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
394 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 402 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale;
@@ -397,7 +405,8 @@ static uint32_t render_workspace_button(cairo_t *cairo,
397 uint32_t ideal_height = ws_vertical_padding * 2 + text_height 405 uint32_t ideal_height = ws_vertical_padding * 2 + text_height
398 + border_width * 2; 406 + border_width * 2;
399 uint32_t ideal_surface_height = ideal_height / output->scale; 407 uint32_t ideal_surface_height = ideal_height / output->scale;
400 if (output->height < ideal_surface_height) { 408 if (!output->bar->config->height &&
409 output->height < ideal_surface_height) {
401 return ideal_surface_height; 410 return ideal_surface_height;
402 } 411 }
403 412
@@ -421,7 +430,7 @@ static uint32_t render_workspace_button(cairo_t *cairo,
421 cairo_set_source_u32(cairo, box_colors.text); 430 cairo_set_source_u32(cairo, box_colors.text);
422 cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); 431 cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y));
423 pango_printf(cairo, config->font, output->scale, config->pango_markup, 432 pango_printf(cairo, config->font, output->scale, config->pango_markup,
424 "%s", name); 433 "%s", ws->label);
425 434
426 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); 435 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
427 hotspot->x = *x; 436 hotspot->x = *x;
@@ -459,6 +468,12 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
459 * utilize the available space. 468 * utilize the available space.
460 */ 469 */
461 double x = output->width * output->scale; 470 double x = output->width * output->scale;
471#if HAVE_TRAY
472 if (bar->tray) {
473 uint32_t h = render_tray(cairo, output, &x);
474 max_height = h > max_height ? h : max_height;
475 }
476#endif
462 if (bar->status) { 477 if (bar->status) {
463 uint32_t h = render_status_line(cairo, output, &x); 478 uint32_t h = render_status_line(cairo, output, &x);
464 max_height = h > max_height ? h : max_height; 479 max_height = h > max_height ? h : max_height;
@@ -518,12 +533,17 @@ void render_frame(struct swaybar_output *output) {
518 cairo_restore(cairo); 533 cairo_restore(cairo);
519 uint32_t height = render_to_cairo(cairo, output); 534 uint32_t height = render_to_cairo(cairo, output);
520 int config_height = output->bar->config->height; 535 int config_height = output->bar->config->height;
521 if (config_height >= 0 && height < (uint32_t)config_height) { 536 if (config_height > 0) {
522 height = config_height; 537 height = config_height;
523 } 538 }
524 if (height != output->height || output->width == 0) { 539 if (height != output->height || output->width == 0) {
525 // Reconfigure surface 540 // Reconfigure surface
526 zwlr_layer_surface_v1_set_size(output->layer_surface, 0, height); 541 zwlr_layer_surface_v1_set_size(output->layer_surface, 0, height);
542 zwlr_layer_surface_v1_set_margin(output->layer_surface,
543 output->bar->config->gaps.top,
544 output->bar->config->gaps.right,
545 output->bar->config->gaps.bottom,
546 output->bar->config->gaps.left);
527 if (strcmp(output->bar->config->mode, "dock") == 0) { 547 if (strcmp(output->bar->config->mode, "dock") == 0) {
528 zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, height); 548 zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, height);
529 } 549 }
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
index 744d2785..f0e2c300 100644
--- a/swaybar/status_line.c
+++ b/swaybar/status_line.c
@@ -12,7 +12,6 @@
12#include "swaybar/config.h" 12#include "swaybar/config.h"
13#include "swaybar/i3bar.h" 13#include "swaybar/i3bar.h"
14#include "swaybar/status_line.h" 14#include "swaybar/status_line.h"
15#include "readline.h"
16 15
17static void status_line_close_fds(struct status_line *status) { 16static void status_line_close_fds(struct status_line *status) {
18 if (status->read_fd != -1) { 17 if (status->read_fd != -1) {
@@ -185,7 +184,6 @@ void status_line_free(struct status_line *status) {
185 } 184 }
186 free(status->read); 185 free(status->read);
187 free(status->write); 186 free(status->write);
188 free((char*) status->text);
189 free(status->buffer); 187 free(status->buffer);
190 free(status); 188 free(status);
191} 189}
diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c
new file mode 100644
index 00000000..cc8dd188
--- /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
14static const char *watcher_path = "/StatusNotifierWatcher";
15
16static 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
21static 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_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
32static 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
46static 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_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
65static 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
96static 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
118static 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
137bool 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, &reg_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;
198error:
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
206void 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}
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
new file mode 100644
index 00000000..67805858
--- /dev/null
+++ b/swaybar/tray/icon.c
@@ -0,0 +1,462 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h>
3#include <dirent.h>
4#include <stdbool.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <sys/stat.h>
9#include <unistd.h>
10#include <wordexp.h>
11#include "swaybar/tray/icon.h"
12#include "config.h"
13#include "list.h"
14#include "log.h"
15#include "stringop.h"
16
17static bool dir_exists(char *path) {
18 struct stat sb;
19 return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
20}
21
22static list_t *get_basedirs(void) {
23 list_t *basedirs = create_list();
24 list_add(basedirs, strdup("$HOME/.icons")); // deprecated
25
26 char *data_home = getenv("XDG_DATA_HOME");
27 list_add(basedirs, strdup(data_home && *data_home ?
28 "$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons"));
29
30 list_add(basedirs, strdup("/usr/share/pixmaps"));
31
32 char *data_dirs = getenv("XDG_DATA_DIRS");
33 if (!(data_dirs && *data_dirs)) {
34 data_dirs = "/usr/local/share:/usr/share";
35 }
36 data_dirs = strdup(data_dirs);
37 char *dir = strtok(data_dirs, ":");
38 do {
39 size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1;
40 char *path = malloc(path_len);
41 snprintf(path, path_len, "%s/icons", dir);
42 list_add(basedirs, path);
43 } while ((dir = strtok(NULL, ":")));
44 free(data_dirs);
45
46 list_t *basedirs_expanded = create_list();
47 for (int i = 0; i < basedirs->length; ++i) {
48 wordexp_t p;
49 if (wordexp(basedirs->items[i], &p, WRDE_UNDEF) == 0) {
50 if (dir_exists(p.we_wordv[0])) {
51 list_add(basedirs_expanded, strdup(p.we_wordv[0]));
52 }
53 wordfree(&p);
54 }
55 }
56
57 list_free_items_and_destroy(basedirs);
58
59 return basedirs_expanded;
60}
61
62static void destroy_theme(struct icon_theme *theme) {
63 if (!theme) {
64 return;
65 }
66 free(theme->name);
67 free(theme->comment);
68 free(theme->inherits);
69 list_free_items_and_destroy(theme->directories);
70 free(theme->dir);
71
72 for (int i = 0; i < theme->subdirs->length; ++i) {
73 struct icon_theme_subdir *subdir = theme->subdirs->items[i];
74 free(subdir->name);
75 free(subdir);
76 }
77 list_free(theme->subdirs);
78 free(theme);
79}
80
81static int cmp_group(const void *item, const void *cmp_to) {
82 return strcmp(item, cmp_to);
83}
84
85static bool group_handler(char *old_group, char *new_group,
86 struct icon_theme *theme) {
87 if (!old_group) { // first group must be "Icon Theme"
88 return strcmp(new_group, "Icon Theme");
89 }
90
91 if (strcmp(old_group, "Icon Theme") == 0) {
92 if (!(theme->name && theme->comment && theme->directories)) {
93 return true;
94 }
95 } else {
96 if (theme->subdirs->length == 0) { // skip
97 return false;
98 }
99
100 struct icon_theme_subdir *subdir =
101 theme->subdirs->items[theme->subdirs->length - 1];
102 if (!subdir->size) return true;
103
104 switch (subdir->type) {
105 case FIXED: subdir->max_size = subdir->min_size = subdir->size;
106 break;
107 case SCALABLE: {
108 if (!subdir->max_size) subdir->max_size = subdir->size;
109 if (!subdir->min_size) subdir->min_size = subdir->size;
110 break;
111 }
112 case THRESHOLD:
113 subdir->max_size = subdir->size + subdir->threshold;
114 subdir->min_size = subdir->size - subdir->threshold;
115 }
116 }
117
118 if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) {
119 struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir));
120 if (!subdir) {
121 return true;
122 }
123 subdir->name = strdup(new_group);
124 subdir->threshold = 2;
125 list_add(theme->subdirs, subdir);
126 }
127
128 return false;
129}
130
131static int entry_handler(char *group, char *key, char *value,
132 struct icon_theme *theme) {
133 if (strcmp(group, "Icon Theme") == 0) {
134 if (strcmp(key, "Name") == 0) {
135 theme->name = strdup(value);
136 } else if (strcmp(key, "Comment") == 0) {
137 theme->comment = strdup(value);
138 } else if (strcmp(key, "Inherists") == 0) {
139 theme->inherits = strdup(value);
140 } else if (strcmp(key, "Directories") == 0) {
141 theme->directories = split_string(value, ",");
142 } // Ignored: ScaledDirectories, Hidden, Example
143 } else {
144 if (theme->subdirs->length == 0) { // skip
145 return false;
146 }
147
148 struct icon_theme_subdir *subdir =
149 theme->subdirs->items[theme->subdirs->length - 1];
150 if (strcmp(subdir->name, group) != 0) { // skip
151 return false;
152 }
153
154 char *end;
155 int n = strtol(value, &end, 10);
156 if (strcmp(key, "Size") == 0) {
157 subdir->size = n;
158 return *end != '\0';
159 } else if (strcmp(key, "Type") == 0) {
160 if (strcmp(value, "Fixed") == 0) {
161 subdir->type = FIXED;
162 } else if (strcmp(value, "Scalable") == 0) {
163 subdir->type = SCALABLE;
164 } else if (strcmp(value, "Threshold") == 0) {
165 subdir->type = THRESHOLD;
166 } else {
167 return true;
168 }
169 } else if (strcmp(key, "MaxSize") == 0) {
170 subdir->max_size = n;
171 return *end != '\0';
172 } else if (strcmp(key, "MinSize") == 0) {
173 subdir->min_size = n;
174 return *end != '\0';
175 } else if (strcmp(key, "Threshold") == 0) {
176 subdir->threshold = n;
177 return *end != '\0';
178 } // Ignored: Scale, Applications
179 }
180 return false;
181}
182
183/*
184 * This is a Freedesktop Desktop Entry parser (essentially INI)
185 * It calls entry_handler for every entry
186 * and group_handler between every group (as well as at both ends)
187 * Handlers return whether an error occured, which stops parsing
188 */
189static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
190 // look for index.theme file
191 size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir,
192 theme_name) + 1;
193 char *path = malloc(path_len);
194 snprintf(path, path_len, "%s/%s/index.theme", basedir, theme_name);
195 FILE *theme_file = fopen(path, "r");
196 if (!theme_file) {
197 return NULL;
198 }
199
200 struct icon_theme *theme = calloc(1, sizeof(struct icon_theme));
201 if (!theme) {
202 return NULL;
203 }
204 theme->subdirs = create_list();
205
206 bool error = false;
207 char *group = NULL;
208 char *full_line = NULL;
209 size_t full_len = 0;
210 ssize_t nread;
211 while ((nread = getline(&full_line, &full_len, theme_file)) != -1) {
212 char *line = full_line - 1;
213 while (isspace(*++line)) {} // remove leading whitespace
214 if (!*line || line[0] == '#') continue; // ignore blank lines & comments
215
216 int len = nread - (line - full_line);
217 while (isspace(line[--len])) {}
218 line[++len] = '\0'; // remove trailing whitespace
219
220 if (line[0] == '[') { // group header
221 // check well-formed
222 if (line[--len] != ']') {
223 error = true;
224 break;
225 }
226 int i = 1;
227 for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {}
228 if (i < len) {
229 error = true;
230 break;
231 }
232
233 // call handler
234 line[len] = '\0';
235 error = group_handler(group, &line[1], theme);
236 if (error) {
237 break;
238 }
239 free(group);
240 group = strdup(&line[1]);
241 } else { // key-value pair
242 // check well-formed
243 int eok = 0;
244 for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale?
245 int i = eok - 1;
246 while (isspace(line[++i])) {}
247 if (line[i] != '=') {
248 error = true;
249 break;
250 }
251
252 line[eok] = '\0'; // split into key-value pair
253 char *value = &line[i];
254 while (isspace(*++value)) {}
255 // TODO unescape value
256 error = entry_handler(group, line, value, theme);
257 if (error) {
258 break;
259 }
260 }
261 }
262
263 if (!error && group) {
264 error = group_handler(group, NULL, theme);
265 }
266
267 free(group);
268 free(full_line);
269 fclose(theme_file);
270
271 if (!error) {
272 theme->dir = strdup(theme_name);
273 return theme;
274 } else {
275 destroy_theme(theme);
276 return NULL;
277 }
278}
279
280static list_t *load_themes_in_dir(char *basedir) {
281 DIR *dir;
282 if (!(dir = opendir(basedir))) {
283 return NULL;
284 }
285
286 list_t *themes = create_list();
287 struct dirent *entry;
288 while ((entry = readdir(dir))) {
289 if (entry->d_name[0] == '.') continue;
290
291 struct icon_theme *theme = read_theme_file(basedir, entry->d_name);
292 if (theme) {
293 list_add(themes, theme);
294 }
295 }
296 return themes;
297}
298
299void init_themes(list_t **themes, list_t **basedirs) {
300 *basedirs = get_basedirs();
301
302 *themes = create_list();
303 for (int i = 0; i < (*basedirs)->length; ++i) {
304 list_t *dir_themes = load_themes_in_dir((*basedirs)->items[i]);
305 list_cat(*themes, dir_themes);
306 list_free(dir_themes);
307 }
308
309 list_t *theme_names = create_list();
310 for (int i = 0; i < (*themes)->length; ++i) {
311 struct icon_theme *theme = (*themes)->items[i];
312 list_add(theme_names, theme->name);
313 }
314 wlr_log(WLR_DEBUG, "Loaded themes: %s", join_list(theme_names, ", "));
315 list_free(theme_names);
316}
317
318void finish_themes(list_t *themes, list_t *basedirs) {
319 for (int i = 0; i < themes->length; ++i) {
320 destroy_theme(themes->items[i]);
321 }
322 list_free(themes);
323 list_free_items_and_destroy(basedirs);
324}
325
326static char *find_icon_in_subdir(char *name, char *basedir, char *theme,
327 char *subdir) {
328 static const char *extensions[] = {
329#if HAVE_GDK_PIXBUF
330 "svg",
331#endif
332 "png",
333#if HAVE_GDK_PIXBUF
334 "xpm"
335#endif
336 };
337
338 size_t path_len = snprintf(NULL, 0, "%s/%s/%s/%s.EXT", basedir, theme,
339 subdir, name) + 1;
340 char *path = malloc(path_len);
341
342 for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) {
343 snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir,
344 name, extensions[i]);
345 if (access(path, R_OK) == 0) {
346 return path;
347 }
348 }
349
350 free(path);
351 return NULL;
352}
353
354static bool theme_exists_in_basedir(char *theme, char *basedir) {
355 size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1;
356 char *path = malloc(path_len);
357 snprintf(path, path_len, "%s/%s", basedir, theme);
358 bool ret = dir_exists(path);
359 free(path);
360 return ret;
361}
362
363static char *find_icon_with_theme(list_t *basedirs, list_t *themes, char *name,
364 int size, char *theme_name, int *min_size, int *max_size) {
365 struct icon_theme *theme = NULL;
366 for (int i = 0; i < themes->length; ++i) {
367 theme = themes->items[i];
368 if (strcmp(theme->name, theme_name) == 0) {
369 break;
370 }
371 theme = NULL;
372 }
373 if (!theme) return NULL;
374
375 char *icon = NULL;
376 for (int i = 0; i < basedirs->length; ++i) {
377 if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
378 continue;
379 }
380 // search backwards to hopefully hit scalable/larger icons first
381 for (int j = theme->subdirs->length - 1; j >= 0; --j) {
382 struct icon_theme_subdir *subdir = theme->subdirs->items[j];
383 if (size >= subdir->min_size && size <= subdir->max_size) {
384 if ((icon = find_icon_in_subdir(name, basedirs->items[i],
385 theme->dir, subdir->name))) {
386 *min_size = subdir->min_size;
387 *max_size = subdir->max_size;
388 return icon;
389 }
390 }
391 }
392 }
393
394 // inexact match
395 unsigned smallest_error = -1; // UINT_MAX
396 for (int i = 0; i < basedirs->length; ++i) {
397 if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
398 continue;
399 }
400 for (int j = theme->subdirs->length - 1; j >= 0; --j) {
401 struct icon_theme_subdir *subdir = theme->subdirs->items[j];
402 unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0)
403 + (size < subdir->min_size ? subdir->min_size - size : 0);
404 if (error < smallest_error) {
405 char *test_icon = find_icon_in_subdir(name, basedirs->items[i],
406 theme->dir, subdir->name);
407 if (test_icon) {
408 icon = test_icon;
409 smallest_error = error;
410 *min_size = subdir->min_size;
411 *max_size = subdir->max_size;
412 }
413 }
414 }
415 }
416
417 if (!icon && theme->inherits) {
418 icon = find_icon_with_theme(basedirs, themes, name, size,
419 theme->inherits, min_size, max_size);
420 }
421
422 return icon;
423}
424
425char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size) {
426 char *icon = find_icon_in_subdir(name, dir, "", "");
427 if (icon) {
428 *min_size = 1;
429 *max_size = 512;
430 }
431 return icon;
432
433}
434
435static char *find_fallback_icon(list_t *basedirs, char *name, int *min_size,
436 int *max_size) {
437 for (int i = 0; i < basedirs->length; ++i) {
438 char *icon = find_icon_in_dir(name, basedirs->items[i], min_size, max_size);
439 if (icon) {
440 return icon;
441 }
442 }
443 return NULL;
444}
445
446char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
447 char *theme, int *min_size, int *max_size) {
448 // TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes
449 char *icon = NULL;
450 if (theme) {
451 icon = find_icon_with_theme(basedirs, themes, name, size, theme,
452 min_size, max_size);
453 }
454 if (!icon) {
455 icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor",
456 min_size, max_size);
457 }
458 if (!icon) {
459 icon = find_fallback_icon(basedirs, name, min_size, max_size);
460 }
461 return icon;
462}
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
new file mode 100644
index 00000000..0833dcb9
--- /dev/null
+++ b/swaybar/tray/item.c
@@ -0,0 +1,472 @@
1#define _POSIX_C_SOURCE 200809L
2#include <cairo.h>
3#include <stdbool.h>
4#include <stdlib.h>
5#include <string.h>
6#include "swaybar/bar.h"
7#include "swaybar/config.h"
8#include "swaybar/input.h"
9#include "swaybar/tray/host.h"
10#include "swaybar/tray/icon.h"
11#include "swaybar/tray/item.h"
12#include "swaybar/tray/tray.h"
13#include "background-image.h"
14#include "cairo.h"
15#include "list.h"
16#include "log.h"
17#include "wlr-layer-shell-unstable-v1-client-protocol.h"
18
19// TODO menu
20
21static bool sni_ready(struct swaybar_sni *sni) {
22 return sni->status && (sni->status[0] == 'N' ? // NeedsAttention
23 sni->attention_icon_name || sni->attention_icon_pixmap :
24 sni->icon_name || sni->icon_pixmap);
25}
26
27static void set_sni_dirty(struct swaybar_sni *sni) {
28 if (sni_ready(sni)) {
29 sni->min_size = sni->max_size = 0; // invalidate previous icon
30 set_bar_dirty(sni->tray->bar);
31 }
32}
33
34static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni,
35 const char *prop, list_t **dest) {
36 int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)");
37 if (ret < 0) {
38 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
39 return ret;
40 }
41
42 if (sd_bus_message_at_end(msg, 0)) {
43 wlr_log(WLR_DEBUG, "%s %s no. of icons = 0", sni->watcher_id, prop);
44 return ret;
45 }
46
47 list_t *pixmaps = create_list();
48 if (!pixmaps) {
49 return -12; // -ENOMEM
50 }
51
52 while (!sd_bus_message_at_end(msg, 0)) {
53 ret = sd_bus_message_enter_container(msg, 'r', "iiay");
54 if (ret < 0) {
55 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
56 goto error;
57 }
58
59 int size;
60 ret = sd_bus_message_read(msg, "ii", NULL, &size);
61 if (ret < 0) {
62 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
63 goto error;
64 }
65
66 const void *pixels;
67 size_t npixels;
68 ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels);
69 if (ret < 0) {
70 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
71 goto error;
72 }
73
74 struct swaybar_pixmap *pixmap =
75 malloc(sizeof(struct swaybar_pixmap) + npixels);
76 pixmap->size = size;
77 memcpy(pixmap->pixels, pixels, npixels);
78 list_add(pixmaps, pixmap);
79
80 sd_bus_message_exit_container(msg);
81 }
82 list_free_items_and_destroy(*dest);
83 *dest = pixmaps;
84 wlr_log(WLR_DEBUG, "%s %s no. of icons = %d", sni->watcher_id, prop,
85 pixmaps->length);
86
87 return ret;
88error:
89 list_free_items_and_destroy(pixmaps);
90 return ret;
91}
92
93struct get_property_data {
94 struct swaybar_sni *sni;
95 const char *prop;
96 const char *type;
97 void *dest;
98};
99
100static int get_property_callback(sd_bus_message *msg, void *data,
101 sd_bus_error *error) {
102 struct get_property_data *d = data;
103 struct swaybar_sni *sni = d->sni;
104 const char *prop = d->prop;
105 const char *type = d->type;
106 void *dest = d->dest;
107
108 int ret;
109 if (sd_bus_message_is_method_error(msg, NULL)) {
110 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop,
111 sd_bus_message_get_error(msg)->message);
112 ret = sd_bus_message_get_errno(msg);
113 goto cleanup;
114 }
115
116 ret = sd_bus_message_enter_container(msg, 'v', type);
117 if (ret < 0) {
118 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
119 goto cleanup;
120 }
121
122 if (!type) {
123 ret = read_pixmap(msg, sni, prop, dest);
124 if (ret < 0) {
125 goto cleanup;
126 }
127 } else {
128 if (*type == 's' || *type == 'o') {
129 free(*(char **)dest);
130 }
131
132 ret = sd_bus_message_read(msg, type, dest);
133 if (ret < 0) {
134 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
135 goto cleanup;
136 }
137
138 if (*type == 's' || *type == 'o') {
139 char **str = dest;
140 *str = strdup(*str);
141 wlr_log(WLR_DEBUG, "%s %s = '%s'", sni->watcher_id, prop, *str);
142 } else if (*type == 'b') {
143 wlr_log(WLR_DEBUG, "%s %s = %s", sni->watcher_id, prop,
144 *(bool *)dest ? "true" : "false");
145 }
146 }
147
148 if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
149 prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) {
150 set_sni_dirty(sni);
151 }
152cleanup:
153 free(data);
154 return ret;
155}
156
157static void sni_get_property_async(struct swaybar_sni *sni, const char *prop,
158 const char *type, void *dest) {
159 struct get_property_data *data = malloc(sizeof(struct get_property_data));
160 data->sni = sni;
161 data->prop = prop;
162 data->type = type;
163 data->dest = dest;
164 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
165 sni->path, "org.freedesktop.DBus.Properties", "Get",
166 get_property_callback, data, "ss", sni->interface, prop);
167 if (ret < 0) {
168 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
169 }
170}
171
172/*
173 * There is a quirk in sd-bus that in some systems, it is unable to get the
174 * well-known names on the bus, so it cannot identify if an incoming signal,
175 * which uses the sender's unique name, actually matches the callback's matching
176 * sender if the callback uses a well-known name, in which case it just calls
177 * the callback and hopes for the best, resulting in false positives. In the
178 * case of NewIcon & NewAttentionIcon, this doesn't affect anything, but it
179 * means that for NewStatus, if the SNI does not definitely match the sender,
180 * then the safe thing to do is to query the status independently.
181 * This function returns 1 if the SNI definitely matches the signal sender,
182 * which is returned by the calling function to indicate that signal matching
183 * can stop since it has already found the required callback, otherwise, it
184 * returns 0, which allows matching to continue.
185 */
186static int sni_check_msg_sender(struct swaybar_sni *sni, sd_bus_message *msg,
187 const char *signal) {
188 bool has_well_known_names =
189 sd_bus_creds_get_mask(sd_bus_message_get_creds(msg)) & SD_BUS_CREDS_WELL_KNOWN_NAMES;
190 if (sni->service[0] == ':' || has_well_known_names) {
191 wlr_log(WLR_DEBUG, "%s has new %s", sni->watcher_id, signal);
192 return 1;
193 } else {
194 wlr_log(WLR_DEBUG, "%s may have new %s", sni->watcher_id, signal);
195 return 0;
196 }
197}
198
199static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) {
200 struct swaybar_sni *sni = data;
201 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
202 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
203 return sni_check_msg_sender(sni, msg, "icon");
204}
205
206static int handle_new_attention_icon(sd_bus_message *msg, void *data,
207 sd_bus_error *error) {
208 struct swaybar_sni *sni = data;
209 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
210 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
211 return sni_check_msg_sender(sni, msg, "attention icon");
212}
213
214static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) {
215 struct swaybar_sni *sni = data;
216 int ret = sni_check_msg_sender(sni, msg, "status");
217 if (ret == 1) {
218 char *status;
219 int r = sd_bus_message_read(msg, "s", &status);
220 if (r < 0) {
221 wlr_log(WLR_ERROR, "%s new status error: %s", sni->watcher_id, strerror(-ret));
222 ret = r;
223 } else {
224 free(sni->status);
225 sni->status = strdup(status);
226 wlr_log(WLR_DEBUG, "%s has new status = '%s'", sni->watcher_id, status);
227 set_sni_dirty(sni);
228 }
229 } else {
230 sni_get_property_async(sni, "Status", "s", &sni->status);
231 }
232
233 return ret;
234}
235
236static void sni_match_signal(struct swaybar_sni *sni, sd_bus_slot **slot,
237 char *signal, sd_bus_message_handler_t callback) {
238 int ret = sd_bus_match_signal(sni->tray->bus, slot, sni->service, sni->path,
239 sni->interface, signal, callback, sni);
240 if (ret < 0) {
241 wlr_log(WLR_ERROR, "Failed to subscribe to signal %s: %s", signal,
242 strerror(-ret));
243 }
244}
245
246struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) {
247 struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni));
248 if (!sni) {
249 return NULL;
250 }
251 sni->tray = tray;
252 sni->watcher_id = strdup(id);
253 char *path_ptr = strchr(id, '/');
254 if (!path_ptr) {
255 sni->service = strdup(id);
256 sni->path = strdup("/StatusNotifierItem");
257 sni->interface = "org.freedesktop.StatusNotifierItem";
258 } else {
259 sni->service = strndup(id, path_ptr - id);
260 sni->path = strdup(path_ptr);
261 sni->interface = "org.kde.StatusNotifierItem";
262 sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path);
263 }
264
265 // Ignored: Category, Id, Title, WindowId, OverlayIconName,
266 // OverlayIconPixmap, AttentionMovieName, ToolTip
267 sni_get_property_async(sni, "Status", "s", &sni->status);
268 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
269 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
270 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
271 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
272 sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu);
273 sni_get_property_async(sni, "Menu", "o", &sni->menu);
274
275 sni_match_signal(sni, &sni->new_icon_slot, "NewIcon", handle_new_icon);
276 sni_match_signal(sni, &sni->new_attention_icon_slot, "NewAttentionIcon",
277 handle_new_attention_icon);
278 sni_match_signal(sni, &sni->new_status_slot, "NewStatus", handle_new_status);
279
280 return sni;
281}
282
283void destroy_sni(struct swaybar_sni *sni) {
284 if (!sni) {
285 return;
286 }
287
288 sd_bus_slot_unref(sni->new_icon_slot);
289 sd_bus_slot_unref(sni->new_attention_icon_slot);
290 sd_bus_slot_unref(sni->new_status_slot);
291
292 free(sni->watcher_id);
293 free(sni->service);
294 free(sni->path);
295 free(sni->status);
296 free(sni->icon_name);
297 free(sni->icon_pixmap);
298 free(sni->attention_icon_name);
299 free(sni->menu);
300 free(sni);
301}
302
303static void handle_click(struct swaybar_sni *sni, int x, int y,
304 enum x11_button button, int delta) {
305 const char *method = sni->tray->bar->config->tray_bindings[button];
306 if (!method) {
307 static const char *default_bindings[10] = {
308 "nop",
309 "Activate",
310 "SecondaryActivate",
311 "ContextMenu",
312 "ScrollUp",
313 "ScrollDown",
314 "ScrollLeft",
315 "ScrollRight",
316 "nop",
317 "nop"
318 };
319 method = default_bindings[button];
320 }
321 if (strcmp(method, "nop") == 0) {
322 return;
323 }
324 if (sni->item_is_menu && strcmp(method, "Activate") == 0) {
325 method = "ContextMenu";
326 }
327
328 if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
329 char dir = method[strlen("Scroll")];
330 char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal";
331 int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
332
333 sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path,
334 sni->interface, "Scroll", NULL, NULL, "is", delta*sign, orientation);
335 } else {
336 sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path,
337 sni->interface, method, NULL, NULL, "ii", x, y);
338 }
339}
340
341static int cmp_sni_id(const void *item, const void *cmp_to) {
342 const struct swaybar_sni *sni = item;
343 return strcmp(sni->watcher_id, cmp_to);
344}
345
346static enum hotspot_event_handling icon_hotspot_callback(
347 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
348 int x, int y, enum x11_button button, void *data) {
349 wlr_log(WLR_DEBUG, "Clicked on %s", (char *)data);
350
351 struct swaybar_tray *tray = output->bar->tray;
352 int idx = list_seq_find(tray->items, cmp_sni_id, data);
353
354 if (idx != -1) {
355 struct swaybar_sni *sni = tray->items->items[idx];
356 // guess global position since wayland doesn't expose it
357 struct swaybar_config *config = tray->bar->config;
358 int global_x = output->output_x + config->gaps.left + x;
359 bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
360 int global_y = output->output_y + (top_bar ? config->gaps.top + y:
361 (int) output->output_height - config->gaps.bottom - y);
362
363 wlr_log(WLR_DEBUG, "Guessing click position at (%d, %d)", global_x, global_y);
364 handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
365 return HOTSPOT_IGNORE;
366 } else {
367 wlr_log(WLR_DEBUG, "but it doesn't exist");
368 }
369
370 return HOTSPOT_PROCESS;
371}
372
373uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
374 struct swaybar_sni *sni) {
375 uint32_t height = output->height * output->scale;
376 int padding = output->bar->config->tray_padding;
377 int ideal_size = height - 2*padding;
378 if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) {
379 bool icon_found = false;
380 char *icon_name = sni->status[0] == 'N' ?
381 sni->attention_icon_name : sni->icon_name;
382 if (icon_name) {
383 char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs,
384 icon_name, ideal_size, output->bar->config->icon_theme,
385 &sni->min_size, &sni->max_size);
386 if (!icon_path && sni->icon_theme_path) {
387 icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path,
388 &sni->min_size, &sni->max_size);
389 }
390 if (icon_path) {
391 cairo_surface_destroy(sni->icon);
392 sni->icon = load_background_image(icon_path);
393 free(icon_path);
394 icon_found = true;
395 }
396 }
397 if (!icon_found) {
398 list_t *pixmaps = sni->status[0] == 'N' ?
399 sni->attention_icon_pixmap : sni->icon_pixmap;
400 if (pixmaps) {
401 int idx = -1;
402 unsigned smallest_error = -1; // UINT_MAX
403 for (int i = 0; i < pixmaps->length; ++i) {
404 struct swaybar_pixmap *pixmap = pixmaps->items[i];
405 unsigned error = (ideal_size - pixmap->size) *
406 (ideal_size < pixmap->size ? -1 : 1);
407 if (error < smallest_error) {
408 smallest_error = error;
409 idx = i;
410 }
411 }
412 struct swaybar_pixmap *pixmap = pixmaps->items[idx];
413 cairo_surface_destroy(sni->icon);
414 sni->icon = cairo_image_surface_create_for_data(pixmap->pixels,
415 CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size,
416 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
417 }
418 }
419 }
420
421 int icon_size;
422 cairo_surface_t *icon;
423 if (sni->icon) {
424 int actual_size = cairo_image_surface_get_height(sni->icon);
425 icon_size = actual_size < ideal_size ?
426 actual_size*(ideal_size/actual_size) : ideal_size;
427 icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size);
428 } else { // draw a :(
429 icon_size = ideal_size*0.8;
430 icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size);
431 cairo_t *cairo_icon = cairo_create(icon);
432 cairo_set_source_u32(cairo_icon, 0xFF0000FF);
433 cairo_translate(cairo_icon, icon_size/2, icon_size/2);
434 cairo_scale(cairo_icon, icon_size/2, icon_size/2);
435 cairo_arc(cairo_icon, 0, 0, 1, 0, 7);
436 cairo_fill(cairo_icon);
437 cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR);
438 cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7);
439 cairo_fill(cairo_icon);
440 cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7);
441 cairo_fill(cairo_icon);
442 cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
443 cairo_set_line_width(cairo_icon, 0.1);
444 cairo_stroke(cairo_icon);
445 cairo_destroy(cairo_icon);
446 }
447
448 int padded_size = icon_size + 2*padding;
449 *x -= padded_size;
450 int y = floor((height - padded_size) / 2.0);
451
452 cairo_operator_t op = cairo_get_operator(cairo);
453 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
454 cairo_set_source_surface(cairo, icon, *x + padding, y + padding);
455 cairo_rectangle(cairo, *x, y, padded_size, padded_size);
456 cairo_fill(cairo);
457 cairo_set_operator(cairo, op);
458
459 cairo_surface_destroy(icon);
460
461 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
462 hotspot->x = *x;
463 hotspot->y = 0;
464 hotspot->width = height;
465 hotspot->height = height;
466 hotspot->callback = icon_hotspot_callback;
467 hotspot->destroy = free;
468 hotspot->data = strdup(sni->watcher_id);
469 wl_list_insert(&output->hotspots, &hotspot->link);
470
471 return output->height;
472}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
new file mode 100644
index 00000000..d5d0c84e
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,131 @@
1#include <cairo.h>
2#include <stdint.h>
3#include <stdlib.h>
4#include <string.h>
5#include "swaybar/config.h"
6#include "swaybar/bar.h"
7#include "swaybar/tray/icon.h"
8#include "swaybar/tray/host.h"
9#include "swaybar/tray/item.h"
10#include "swaybar/tray/tray.h"
11#include "swaybar/tray/watcher.h"
12#include "list.h"
13#include "log.h"
14
15static int handle_lost_watcher(sd_bus_message *msg,
16 void *data, sd_bus_error *error) {
17 char *service, *old_owner, *new_owner;
18 int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
19 if (ret < 0) {
20 wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
21 return ret;
22 }
23
24 if (!*new_owner) {
25 struct swaybar_tray *tray = data;
26 if (strcmp(service, "org.freedesktop.StatusNotifierWatcher") == 0) {
27 tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
28 } else if (strcmp(service, "org.kde.StatusNotifierWatcher") == 0) {
29 tray->watcher_kde = create_watcher("kde", tray->bus);
30 }
31 }
32
33 return 0;
34}
35
36struct swaybar_tray *create_tray(struct swaybar *bar) {
37 wlr_log(WLR_DEBUG, "Initializing tray");
38
39 sd_bus *bus;
40 int ret = sd_bus_open_user(&bus);
41 if (ret < 0) {
42 wlr_log(WLR_ERROR, "Failed to connect to user bus: %s", strerror(-ret));
43 return NULL;
44 }
45
46 struct swaybar_tray *tray = calloc(1, sizeof(struct swaybar_tray));
47 if (!tray) {
48 return NULL;
49 }
50 tray->bar = bar;
51 tray->bus = bus;
52 tray->fd = sd_bus_get_fd(tray->bus);
53
54 tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
55 tray->watcher_kde = create_watcher("kde", tray->bus);
56
57 ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus",
58 "/org/freedesktop/DBus", "org.freedesktop.DBus",
59 "NameOwnerChanged", handle_lost_watcher, tray);
60 if (ret < 0) {
61 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
62 strerror(-ret));
63 }
64
65 tray->items = create_list();
66
67 init_host(&tray->host_xdg, "freedesktop", tray);
68 init_host(&tray->host_kde, "kde", tray);
69
70 init_themes(&tray->themes, &tray->basedirs);
71
72 return tray;
73}
74
75void destroy_tray(struct swaybar_tray *tray) {
76 if (!tray) {
77 return;
78 }
79 finish_host(&tray->host_xdg);
80 finish_host(&tray->host_kde);
81 for (int i = 0; i < tray->items->length; ++i) {
82 destroy_sni(tray->items->items[i]);
83 }
84 list_free(tray->items);
85 destroy_watcher(tray->watcher_xdg);
86 destroy_watcher(tray->watcher_kde);
87 sd_bus_flush_close_unref(tray->bus);
88 finish_themes(tray->themes, tray->basedirs);
89 free(tray);
90}
91
92void tray_in(int fd, short mask, void *data) {
93 sd_bus *bus = data;
94 int ret;
95 while ((ret = sd_bus_process(bus, NULL)) > 0) {
96 // This space intentionally left blank
97 }
98 if (ret < 0) {
99 wlr_log(WLR_ERROR, "Failed to process bus: %s", strerror(-ret));
100 }
101}
102
103static int cmp_output(const void *item, const void *cmp_to) {
104 const struct swaybar_output *output = cmp_to;
105 if (output->identifier && strcmp(item, output->identifier) == 0) {
106 return 0;
107 }
108 return strcmp(item, output->name);
109}
110
111uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) {
112 struct swaybar_config *config = output->bar->config;
113 if (config->tray_outputs) {
114 if (list_seq_find(config->tray_outputs, cmp_output, output) == -1) {
115 return 0;
116 }
117 } // else display on all
118
119 if ((int) output->height*output->scale <= 2*config->tray_padding) {
120 return 2*config->tray_padding + 1;
121 }
122
123 uint32_t max_height = 0;
124 struct swaybar_tray *tray = output->bar->tray;
125 for (int i = 0; i < tray->items->length; ++i) {
126 uint32_t h = render_sni(cairo, output, x, tray->items->items[i]);
127 max_height = h > max_height ? h : max_height;
128 }
129
130 return max_height;
131}
diff --git a/swaybar/tray/watcher.c b/swaybar/tray/watcher.c
new file mode 100644
index 00000000..198c6c85
--- /dev/null
+++ b/swaybar/tray/watcher.c
@@ -0,0 +1,212 @@
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
11static const char *obj_path = "/StatusNotifierWatcher";
12
13static bool using_standard_protocol(struct swaybar_watcher *watcher) {
14 return watcher->interface[strlen("org.")] == 'f'; // freedesktop
15}
16
17static int cmp_id(const void *item, const void *cmp_to) {
18 return strcmp(item, cmp_to);
19}
20
21static int cmp_service(const void *item, const void *cmp_to) {
22 return strncmp(item, cmp_to, strlen(cmp_to));
23}
24
25static int handle_lost_service(sd_bus_message *msg,
26 void *data, sd_bus_error *error) {
27 char *service, *old_owner, *new_owner;
28 int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
29 if (ret < 0) {
30 wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
31 return ret;
32 }
33
34 if (!*new_owner) {
35 struct swaybar_watcher *watcher = data;
36 int idx = list_seq_find(watcher->items,
37 using_standard_protocol(watcher) ? cmp_id : cmp_service, service);
38 if (idx != -1) {
39 char *id = watcher->items->items[idx];
40 wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
41 list_del(watcher->items, idx);
42 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
43 "StatusNotifierItemUnregistered", "s", id);
44 free(id);
45 }
46
47 idx = list_seq_find(watcher->hosts, cmp_id, service);
48 if (idx != -1) {
49 wlr_log(WLR_DEBUG, "Unregistering Status Notifier Host '%s'", service);
50 free(watcher->hosts->items[idx]);
51 list_del(watcher->hosts, idx);
52 }
53 }
54
55 return 0;
56}
57
58static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
59 char *service_or_path, *id;
60 int ret = sd_bus_message_read(msg, "s", &service_or_path);
61 if (ret < 0) {
62 wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
63 return ret;
64 }
65
66 struct swaybar_watcher *watcher = data;
67 if (using_standard_protocol(watcher)) {
68 id = strdup(service_or_path);
69 } else {
70 const char *service, *path;
71 if (service_or_path[0] == '/') {
72 service = sd_bus_message_get_sender(msg);
73 path = service_or_path;
74 } else {
75 service = service_or_path;
76 path = "/StatusNotifierItem";
77 }
78 size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1;
79 id = malloc(id_len);
80 snprintf(id, id_len, "%s%s", service, path);
81 }
82
83 if (list_seq_find(watcher->items, cmp_id, id) == -1) {
84 wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
85 list_add(watcher->items, id);
86 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
87 "StatusNotifierItemRegistered", "s", id);
88 } else {
89 wlr_log(WLR_DEBUG, "Status Notifier Item '%s' already registered", id);
90 free(id);
91 }
92
93 return sd_bus_reply_method_return(msg, "");
94}
95
96static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) {
97 char *service;
98 int ret = sd_bus_message_read(msg, "s", &service);
99 if (ret < 0) {
100 wlr_log(WLR_ERROR, "Failed to parse register host message: %s", strerror(-ret));
101 return ret;
102 }
103
104 struct swaybar_watcher *watcher = data;
105 if (list_seq_find(watcher->hosts, cmp_id, service) == -1) {
106 wlr_log(WLR_DEBUG, "Registering Status Notifier Host '%s'", service);
107 list_add(watcher->hosts, strdup(service));
108 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
109 "StatusNotifierHostRegistered", "s", service);
110 } else {
111 wlr_log(WLR_DEBUG, "Status Notifier Host '%s' already registered", service);
112 }
113
114 return sd_bus_reply_method_return(msg, "");
115}
116
117static int get_registered_snis(sd_bus *bus, const char *obj_path,
118 const char *interface, const char *property, sd_bus_message *reply,
119 void *data, sd_bus_error *error) {
120 struct swaybar_watcher *watcher = data;
121 list_add(watcher->items, NULL); // strv expects NULL-terminated string array
122 int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items);
123 list_del(watcher->items, watcher->items->length - 1);
124 return ret;
125}
126
127static int is_host_registered(sd_bus *bus, const char *obj_path,
128 const char *interface, const char *property, sd_bus_message *reply,
129 void *data, sd_bus_error *error) {
130 struct swaybar_watcher *watcher = data;
131 int val = watcher->hosts->length > 0; // dbus expects int rather than bool
132 return sd_bus_message_append_basic(reply, 'b', &val);
133}
134
135static const sd_bus_vtable watcher_vtable[] = {
136 SD_BUS_VTABLE_START(0),
137 SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni,
138 SD_BUS_VTABLE_UNPRIVILEGED),
139 SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host,
140 SD_BUS_VTABLE_UNPRIVILEGED),
141 SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis,
142 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
143 SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered,
144 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
145 SD_BUS_PROPERTY("ProtocolVersion", "i", NULL,
146 offsetof(struct swaybar_watcher, version),
147 SD_BUS_VTABLE_PROPERTY_CONST),
148 SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0),
149 SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0),
150 SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0),
151 SD_BUS_VTABLE_END
152};
153
154struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
155 struct swaybar_watcher *watcher =
156 calloc(1, sizeof(struct swaybar_watcher));
157 if (!watcher) {
158 return NULL;
159 }
160
161 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
162 watcher->interface = malloc(len);
163 snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
164
165 sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
166 int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
167 watcher->interface, watcher_vtable, watcher);
168 if (ret < 0) {
169 wlr_log(WLR_ERROR, "Failed to add object vtable: %s", strerror(-ret));
170 goto error;
171 }
172
173 ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus",
174 "/org/freedesktop/DBus", "org.freedesktop.DBus",
175 "NameOwnerChanged", handle_lost_service, watcher);
176 if (ret < 0) {
177 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
178 strerror(-ret));
179 goto error;
180 }
181
182 ret = sd_bus_request_name(bus, watcher->interface, 0);
183 if (ret < 0) {
184 wlr_log(WLR_ERROR, "Failed to acquire service name: %s", strerror(-ret));
185 goto error;
186 }
187
188 sd_bus_slot_set_floating(signal_slot, 0);
189 sd_bus_slot_set_floating(vtable_slot, 0);
190
191 watcher->bus = bus;
192 watcher->hosts = create_list();
193 watcher->items = create_list();
194 watcher->version = 0;
195 wlr_log(WLR_DEBUG, "Registered %s", watcher->interface);
196 return watcher;
197error:
198 sd_bus_slot_unref(signal_slot);
199 sd_bus_slot_unref(vtable_slot);
200 destroy_watcher(watcher);
201 return NULL;
202}
203
204void destroy_watcher(struct swaybar_watcher *watcher) {
205 if (!watcher) {
206 return;
207 }
208 list_free_items_and_destroy(watcher->hosts);
209 list_free_items_and_destroy(watcher->items);
210 free(watcher->interface);
211 free(watcher);
212}