aboutsummaryrefslogtreecommitdiffstats
path: root/swaybar
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2018-04-12 20:19:54 -0400
committerLibravatar Drew DeVault <sir@cmpwn.com>2018-04-12 20:19:54 -0400
commitcd1b32453a9296c18b28bff71607aeb22987b5cd (patch)
treec653c6d525b471914c01a9d7ae543f521b6138ed /swaybar
parentMerge pull request #1634 from aleksander/master (diff)
parentFix separator height calculation (diff)
downloadsway-cd1b32453a9296c18b28bff71607aeb22987b5cd.tar.gz
sway-cd1b32453a9296c18b28bff71607aeb22987b5cd.tar.zst
sway-cd1b32453a9296c18b28bff71607aeb22987b5cd.zip
Merge branch 'wlroots'
Diffstat (limited to 'swaybar')
-rw-r--r--swaybar/CMakeLists.txt51
-rw-r--r--swaybar/bar.c644
-rw-r--r--swaybar/config.c53
-rw-r--r--swaybar/event_loop.c10
-rw-r--r--swaybar/i3bar.c215
-rw-r--r--swaybar/ipc.c445
-rw-r--r--swaybar/main.c33
-rw-r--r--swaybar/meson.build28
-rw-r--r--swaybar/render.c638
-rw-r--r--swaybar/status_line.c594
-rw-r--r--swaybar/tray/dbus.c197
-rw-r--r--swaybar/tray/icon.c400
-rw-r--r--swaybar/tray/sni.c481
-rw-r--r--swaybar/tray/sni_watcher.c497
-rw-r--r--swaybar/tray/tray.c398
15 files changed, 1333 insertions, 3351 deletions
diff --git a/swaybar/CMakeLists.txt b/swaybar/CMakeLists.txt
deleted file mode 100644
index 48ededdd..00000000
--- a/swaybar/CMakeLists.txt
+++ /dev/null
@@ -1,51 +0,0 @@
1include_directories(
2 ${PROTOCOLS_INCLUDE_DIRS}
3 ${WAYLAND_INCLUDE_DIR}
4 ${CAIRO_INCLUDE_DIRS}
5 ${PANGO_INCLUDE_DIRS}
6 ${JSONC_INCLUDE_DIRS}
7 ${XKBCOMMON_INCLUDE_DIRS}
8 ${DBUS_INCLUDE_DIRS}
9)
10if (ENABLE_TRAY)
11 file(GLOB tray
12 tray/*.c
13 )
14endif()
15
16add_executable(swaybar
17 main.c
18 config.c
19 render.c
20 bar.c
21 status_line.c
22 ipc.c
23 event_loop.c
24 ${tray}
25)
26
27target_link_libraries(swaybar
28 sway-common
29 sway-wayland
30 ${WAYLAND_CLIENT_LIBRARIES}
31 ${WAYLAND_CURSOR_LIBRARIES}
32 ${CAIRO_LIBRARIES}
33 ${PANGO_LIBRARIES}
34 ${JSONC_LIBRARIES}
35 ${DBUS_LIBRARIES}
36)
37
38if (WITH_GDK_PIXBUF)
39 include_directories(
40 ${GDK_PIXBUF_INCLUDE_DIRS}
41 )
42endif()
43
44target_link_libraries(swaybar rt)
45
46install(
47 TARGETS swaybar
48 RUNTIME
49 DESTINATION bin
50 COMPONENT runtime
51)
diff --git a/swaybar/bar.c b/swaybar/bar.c
index f12923a8..d51c4ec7 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -1,390 +1,456 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <assert.h>
3#include <errno.h>
4#include <fcntl.h>
5#include <poll.h>
6#include <signal.h>
2#include <stdlib.h> 7#include <stdlib.h>
3#include <unistd.h>
4#include <string.h> 8#include <string.h>
5#include <fcntl.h>
6#include <errno.h>
7#include <sys/wait.h> 9#include <sys/wait.h>
8#include <signal.h> 10#include <unistd.h>
9#include <poll.h> 11#include <wayland-client.h>
12#include <wayland-cursor.h>
13#include <wlr/util/log.h>
10#ifdef __FreeBSD__ 14#ifdef __FreeBSD__
11#include <dev/evdev/input-event-codes.h> 15#include <dev/evdev/input-event-codes.h>
12#else 16#else
13#include <linux/input-event-codes.h> 17#include <linux/input-event-codes.h>
14#endif 18#endif
15#ifdef ENABLE_TRAY
16#include <dbus/dbus.h>
17#include "swaybar/tray/sni_watcher.h"
18#include "swaybar/tray/tray.h"
19#include "swaybar/tray/sni.h"
20#endif
21#include "swaybar/ipc.h"
22#include "swaybar/render.h" 19#include "swaybar/render.h"
23#include "swaybar/config.h" 20#include "swaybar/config.h"
24#include "swaybar/status_line.h"
25#include "swaybar/event_loop.h" 21#include "swaybar/event_loop.h"
22#include "swaybar/status_line.h"
26#include "swaybar/bar.h" 23#include "swaybar/bar.h"
24#include "swaybar/ipc.h"
27#include "ipc-client.h" 25#include "ipc-client.h"
28#include "list.h" 26#include "list.h"
29#include "log.h" 27#include "log.h"
28#include "pango.h"
29#include "pool-buffer.h"
30#include "wlr-layer-shell-unstable-v1-client-protocol.h"
30 31
31static void bar_init(struct bar *bar) { 32static void bar_init(struct swaybar *bar) {
32 bar->config = init_config(); 33 bar->config = init_config();
33 bar->status = init_status_line(); 34 wl_list_init(&bar->outputs);
34 bar->outputs = create_list();
35} 35}
36 36
37static void spawn_status_cmd_proc(struct bar *bar) { 37static void swaybar_output_free(struct swaybar_output *output) {
38 if (bar->config->status_command) { 38 if (!output) {
39 int pipe_read_fd[2]; 39 return;
40 int pipe_write_fd[2]; 40 }
41 41 wlr_log(L_DEBUG, "Removing output %s", output->name);
42 if (pipe(pipe_read_fd) != 0) { 42 zwlr_layer_surface_v1_destroy(output->layer_surface);
43 sway_log(L_ERROR, "Unable to create pipes for status_command fork"); 43 wl_surface_destroy(output->surface);
44 return; 44 wl_output_destroy(output->output);
45 } 45 destroy_buffer(&output->buffers[0]);
46 if (pipe(pipe_write_fd) != 0) { 46 destroy_buffer(&output->buffers[1]);
47 sway_log(L_ERROR, "Unable to create pipe for status_command fork (write)"); 47 struct swaybar_workspace *ws, *ws_tmp;
48 close(pipe_read_fd[0]); 48 wl_list_for_each_safe(ws, ws_tmp, &output->workspaces, link) {
49 close(pipe_read_fd[1]); 49 wl_list_remove(&ws->link);
50 return; 50 free(ws->name);
51 } 51 free(ws);
52 52 }
53 bar->status_command_pid = fork(); 53 struct swaybar_hotspot *hotspot, *hotspot_tmp;
54 if (bar->status_command_pid == 0) { 54 wl_list_for_each_safe(hotspot, hotspot_tmp, &output->hotspots, link) {
55 close(pipe_read_fd[0]); 55 if (hotspot->destroy) {
56 dup2(pipe_read_fd[1], STDOUT_FILENO); 56 hotspot->destroy(hotspot->data);
57 close(pipe_read_fd[1]);
58
59 dup2(pipe_write_fd[0], STDIN_FILENO);
60 close(pipe_write_fd[0]);
61 close(pipe_write_fd[1]);
62
63 char *const cmd[] = {
64 "sh",
65 "-c",
66 bar->config->status_command,
67 NULL,
68 };
69 execvp(cmd[0], cmd);
70 return;
71 } 57 }
72 58 free(hotspot);
73 close(pipe_read_fd[1]);
74 bar->status_read_fd = pipe_read_fd[0];
75 fcntl(bar->status_read_fd, F_SETFL, O_NONBLOCK);
76
77 close(pipe_write_fd[0]);
78 bar->status_write_fd = pipe_write_fd[1];
79 fcntl(bar->status_write_fd, F_SETFL, O_NONBLOCK);
80 } 59 }
60 wl_list_remove(&output->link);
61 free(output->name);
62 free(output);
81} 63}
82 64
83struct output *new_output(const char *name) { 65static void layer_surface_configure(void *data,
84 struct output *output = malloc(sizeof(struct output)); 66 struct zwlr_layer_surface_v1 *surface,
85 output->name = strdup(name); 67 uint32_t serial, uint32_t width, uint32_t height) {
86 output->window = NULL; 68 struct swaybar_output *output = data;
87 output->registry = NULL; 69 output->width = width;
88 output->workspaces = create_list(); 70 output->height = height;
89#ifdef ENABLE_TRAY 71 zwlr_layer_surface_v1_ack_configure(surface, serial);
90 output->items = create_list(); 72 render_frame(output->bar, output);
91#endif
92 return output;
93} 73}
94 74
95static void mouse_button_notify(struct window *window, int x, int y, 75static void layer_surface_closed(void *_output,
96 uint32_t button, uint32_t state_w) { 76 struct zwlr_layer_surface_v1 *surface) {
97 sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w); 77 struct swaybar_output *output = _output;
98 if (!state_w) { 78 swaybar_output_free(output);
99 return; 79}
100 }
101 80
102 struct output *clicked_output = NULL; 81struct zwlr_layer_surface_v1_listener layer_surface_listener = {
103 for (int i = 0; i < swaybar.outputs->length; i++) { 82 .configure = layer_surface_configure,
104 struct output *output = swaybar.outputs->items[i]; 83 .closed = layer_surface_closed,
105 if (window == output->window) { 84};
106 clicked_output = output; 85
86static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
87 uint32_t serial, struct wl_surface *surface,
88 wl_fixed_t surface_x, wl_fixed_t surface_y) {
89 struct swaybar *bar = data;
90 struct swaybar_pointer *pointer = &bar->pointer;
91 struct swaybar_output *output;
92 wl_list_for_each(output, &bar->outputs, link) {
93 if (output->surface == surface) {
94 pointer->current = output;
107 break; 95 break;
108 } 96 }
109 } 97 }
110 98 int max_scale = 1;
111 if (!sway_assert(clicked_output != NULL, "Got pointer event for non-existing output")) { 99 struct swaybar_output *_output;
112 return; 100 wl_list_for_each(_output, &bar->outputs, link) {
113 } 101 if (_output->scale > max_scale) {
114 102 max_scale = _output->scale;
115 double button_x = 0.5;
116 for (int i = 0; i < clicked_output->workspaces->length; i++) {
117 struct workspace *workspace = clicked_output->workspaces->items[i];
118 int button_width, button_height;
119
120 workspace_button_size(window, workspace->name, &button_width, &button_height);
121
122 button_x += button_width;
123 if (x <= button_x) {
124 ipc_send_workspace_command(workspace->name);
125 break;
126 } 103 }
127 } 104 }
105 wl_surface_set_buffer_scale(pointer->cursor_surface, max_scale);
106 wl_surface_attach(pointer->cursor_surface,
107 wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0);
108 wl_pointer_set_cursor(wl_pointer, serial, pointer->cursor_surface,
109 pointer->cursor_image->hotspot_x / max_scale,
110 pointer->cursor_image->hotspot_y / max_scale);
111 wl_surface_commit(pointer->cursor_surface);
112}
128 113
129 switch (button) { 114static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
130 case BTN_LEFT: 115 uint32_t serial, struct wl_surface *surface) {
131 status_line_mouse_event(&swaybar, x, y, 1); 116 struct swaybar *bar = data;
132 break; 117 bar->pointer.current = NULL;
133 case BTN_MIDDLE: 118}
134 status_line_mouse_event(&swaybar, x, y, 2);
135 break;
136 case BTN_RIGHT:
137 status_line_mouse_event(&swaybar, x, y, 3);
138 break;
139 }
140
141#ifdef ENABLE_TRAY
142 tray_mouse_event(clicked_output, x, y, button, state_w);
143#endif
144 119
120static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
121 uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
122 struct swaybar *bar = data;
123 bar->pointer.x = wl_fixed_to_int(surface_x);
124 bar->pointer.y = wl_fixed_to_int(surface_y);
145} 125}
146 126
147static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { 127static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
148 sway_log(L_DEBUG, "Mouse wheel scrolled %s", direction == SCROLL_UP ? "up" : "down"); 128 uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
149 129 struct swaybar *bar = data;
150 // If there are status blocks and click_events are enabled 130 struct swaybar_pointer *pointer = &bar->pointer;
151 // check if the position is within the status area and if so 131 struct swaybar_output *output = pointer->current;
152 // tell the status line to output the event and skip workspace 132 if (!sway_assert(output, "button with no active output")) {
153 // switching below. 133 return;
154 int num_blocks = swaybar.status->block_line->length; 134 }
155 if (swaybar.status->click_events && num_blocks > 0) { 135 if (state != WL_POINTER_BUTTON_STATE_PRESSED) {
156 struct status_block *first_block = swaybar.status->block_line->items[0]; 136 return;
157 int x = window->pointer_input.last_x; 137 }
158 int y = window->pointer_input.last_y; 138 struct swaybar_hotspot *hotspot;
159 if (x > first_block->x) { 139 wl_list_for_each(hotspot, &output->hotspots, link) {
160 if (direction == SCROLL_UP) { 140 double x = pointer->x * output->scale;
161 status_line_mouse_event(&swaybar, x, y, 4); 141 double y = pointer->y * output->scale;
162 } else { 142 if (x >= hotspot->x
163 status_line_mouse_event(&swaybar, x, y, 5); 143 && y >= hotspot->y
164 } 144 && x < hotspot->x + hotspot->width
165 return; 145 && y < hotspot->y + hotspot->height) {
146 hotspot->callback(output, pointer->x, pointer->y,
147 button, hotspot->data);
166 } 148 }
167 } 149 }
150}
168 151
169 if (!swaybar.config->wrap_scroll) { 152static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
170 // Find output this window lives on 153 uint32_t time, uint32_t axis, wl_fixed_t value) {
171 int i; 154 struct swaybar *bar = data;
172 struct output *output = NULL; 155 struct swaybar_output *output = bar->pointer.current;
173 for (i = 0; i < swaybar.outputs->length; ++i) { 156 if (!sway_assert(output, "axis with no active output")) {
174 output = swaybar.outputs->items[i]; 157 return;
175 if (output->window == window) { 158 }
176 break; 159 double amt = wl_fixed_to_double(value);
177 } 160 if (!bar->config->wrap_scroll) {
178 } 161 int i = 0;
179 if (!sway_assert(i != swaybar.outputs->length, "Unknown window in scroll event")) { 162 struct swaybar_workspace *ws = NULL;
180 return; 163 wl_list_for_each(ws, &output->workspaces, link) {
181 }
182 int focused = -1;
183 for (i = 0; i < output->workspaces->length; ++i) {
184 struct workspace *ws = output->workspaces->items[i];
185 if (ws->focused) { 164 if (ws->focused) {
186 focused = i;
187 break; 165 break;
188 } 166 }
167 ++i;
189 } 168 }
190 if (!sway_assert(focused != -1, "Scroll wheel event received on inactive output")) { 169 int len = wl_list_length(&output->workspaces);
170 if (!sway_assert(i != len, "axis with null workspace")) {
191 return; 171 return;
192 } 172 }
193 if ((focused == 0 && direction == SCROLL_UP) || 173 if (i == 0 && amt > 0) {
194 (focused == output->workspaces->length - 1 && direction == SCROLL_DOWN)) { 174 return; // Do not wrap
195 // Do not wrap 175 }
196 return; 176 if (i == len - 1 && amt < 0) {
177 return; // Do not wrap
197 } 178 }
198 } 179 }
199 180
200 const char *workspace_name = direction == SCROLL_UP ? "prev_on_output" : "next_on_output"; 181 const char *workspace_name =
201 ipc_send_workspace_command(workspace_name); 182 amt < 0 ? "prev_on_output" : "next_on_output";
183 ipc_send_workspace_command(bar, workspace_name);
202} 184}
203 185
204void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) { 186static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) {
205 /* initialize bar with default values */ 187 // Who cares
206 bar_init(bar); 188}
207
208 /* Initialize event loop lists */
209 init_event_loop();
210
211 /* connect to sway ipc */
212 bar->ipc_socketfd = ipc_open_socket(socket_path);
213 bar->ipc_event_socketfd = ipc_open_socket(socket_path);
214
215 ipc_bar_init(bar, bar_id);
216
217 int i;
218 for (i = 0; i < bar->outputs->length; ++i) {
219 struct output *bar_output = bar->outputs->items[i];
220
221 bar_output->registry = registry_poll();
222
223 if (!bar_output->registry->desktop_shell) {
224 sway_abort("swaybar requires the compositor to support the desktop-shell extension.");
225 }
226
227 struct output_state *output = bar_output->registry->outputs->items[bar_output->idx];
228
229 bar_output->window = window_setup(bar_output->registry,
230 output->width / output->scale, 30, output->scale, false);
231 if (!bar_output->window) {
232 sway_abort("Failed to create window.");
233 }
234 desktop_shell_set_panel(bar_output->registry->desktop_shell,
235 output->output, bar_output->window->surface);
236 desktop_shell_set_panel_position(bar_output->registry->desktop_shell,
237 bar->config->position);
238 189
239 window_make_shell(bar_output->window); 190static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
191 uint32_t axis_source) {
192 // Who cares
193}
240 194
241 /* set font */ 195static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
242 bar_output->window->font = bar->config->font; 196 uint32_t time, uint32_t axis) {
197 // Who cares
198}
243 199
244 /* set mouse event callbacks */ 200static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
245 bar_output->window->pointer_input.notify_button = mouse_button_notify; 201 uint32_t axis, int32_t discrete) {
246 bar_output->window->pointer_input.notify_scroll = mouse_scroll_notify; 202 // Who cares
203}
247 204
248 /* set window height */ 205struct wl_pointer_listener pointer_listener = {
249 set_window_height(bar_output->window, bar->config->height); 206 .enter = wl_pointer_enter,
207 .leave = wl_pointer_leave,
208 .motion = wl_pointer_motion,
209 .button = wl_pointer_button,
210 .axis = wl_pointer_axis,
211 .frame = wl_pointer_frame,
212 .axis_source = wl_pointer_axis_source,
213 .axis_stop = wl_pointer_axis_stop,
214 .axis_discrete = wl_pointer_axis_discrete,
215};
216
217static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
218 enum wl_seat_capability caps) {
219 struct swaybar *bar = data;
220 if ((caps & WL_SEAT_CAPABILITY_POINTER)) {
221 bar->pointer.pointer = wl_seat_get_pointer(wl_seat);
222 wl_pointer_add_listener(bar->pointer.pointer, &pointer_listener, bar);
250 } 223 }
251 /* spawn status command */ 224}
252 spawn_status_cmd_proc(bar);
253 225
254#ifdef ENABLE_TRAY 226static void seat_handle_name(void *data, struct wl_seat *wl_seat,
255 init_tray(bar); 227 const char *name) {
256#endif 228 // Who cares
257} 229}
258 230
259bool dirty = true; 231const struct wl_seat_listener seat_listener = {
232 .capabilities = seat_handle_capabilities,
233 .name = seat_handle_name,
234};
260 235
261static void respond_ipc(int fd, short mask, void *_bar) { 236static void output_geometry(void *data, struct wl_output *output, int32_t x,
262 struct bar *bar = (struct bar *)_bar; 237 int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel,
263 sway_log(L_DEBUG, "Got IPC event."); 238 const char *make, const char *model, int32_t transform) {
264 dirty = handle_ipc_event(bar); 239 // Who cares
265} 240}
266 241
267static void respond_command(int fd, short mask, void *_bar) { 242static void output_mode(void *data, struct wl_output *output, uint32_t flags,
268 struct bar *bar = (struct bar *)_bar; 243 int32_t width, int32_t height, int32_t refresh) {
269 dirty = handle_status_line(bar); 244 // Who cares
270} 245}
271 246
272static void respond_output(int fd, short mask, void *_output) { 247static void output_done(void *data, struct wl_output *output) {
273 struct output *output = (struct output *)_output; 248 // Who cares
274 if (wl_display_dispatch(output->registry->display) == -1) { 249}
275 sway_log(L_ERROR, "failed to dispatch wl: %d", errno); 250
251static void output_scale(void *data, struct wl_output *wl_output,
252 int32_t factor) {
253 struct swaybar_output *output = data;
254 output->scale = factor;
255 if (output->surface) {
256 render_frame(output->bar, output);
276 } 257 }
277} 258}
278 259
279void bar_run(struct bar *bar) { 260struct wl_output_listener output_listener = {
280 add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar); 261 .geometry = output_geometry,
281 add_event(bar->status_read_fd, POLLIN, respond_command, bar); 262 .mode = output_mode,
263 .done = output_done,
264 .scale = output_scale,
265};
282 266
283 int i; 267static bool bar_uses_output(struct swaybar *bar, size_t output_index) {
284 for (i = 0; i < bar->outputs->length; ++i) { 268 if (bar->config->all_outputs) {
285 struct output *output = bar->outputs->items[i]; 269 return true;
286 add_event(wl_display_get_fd(output->registry->display),
287 POLLIN, respond_output, output);
288 } 270 }
289 271 struct config_output *coutput;
290 while (1) { 272 wl_list_for_each(coutput, &bar->config->outputs, link) {
291 if (dirty) { 273 if (coutput->index == output_index) {
292 int i; 274 return true;
293 for (i = 0; i < bar->outputs->length; ++i) {
294 struct output *output = bar->outputs->items[i];
295 if (window_prerender(output->window) && output->window->cairo) {
296 render(output, bar->config, bar->status);
297 window_render(output->window);
298 wl_display_flush(output->registry->display);
299 }
300 }
301 } 275 }
276 }
277 return false;
278}
302 279
303 dirty = false; 280static void handle_global(void *data, struct wl_registry *registry,
304 281 uint32_t name, const char *interface, uint32_t version) {
305 event_loop_poll(); 282 struct swaybar *bar = data;
306#ifdef ENABLE_TRAY 283 if (strcmp(interface, wl_compositor_interface.name) == 0) {
307 dispatch_dbus(); 284 bar->compositor = wl_registry_bind(registry, name,
308#endif 285 &wl_compositor_interface, 3);
286 } else if (strcmp(interface, wl_seat_interface.name) == 0) {
287 bar->seat = wl_registry_bind(registry, name,
288 &wl_seat_interface, 1);
289 wl_seat_add_listener(bar->seat, &seat_listener, bar);
290 } else if (strcmp(interface, wl_shm_interface.name) == 0) {
291 bar->shm = wl_registry_bind(registry, name,
292 &wl_shm_interface, 1);
293 } else if (strcmp(interface, wl_output_interface.name) == 0) {
294 static size_t output_index = 0;
295 if (bar_uses_output(bar, output_index)) {
296 struct swaybar_output *output =
297 calloc(1, sizeof(struct swaybar_output));
298 output->bar = bar;
299 output->output = wl_registry_bind(registry, name,
300 &wl_output_interface, 3);
301 wl_output_add_listener(output->output, &output_listener, output);
302 output->scale = 1;
303 output->index = output_index;
304 output->wl_name = name;
305 wl_list_init(&output->workspaces);
306 wl_list_init(&output->hotspots);
307 wl_list_insert(&bar->outputs, &output->link);
308 }
309 ++output_index;
310 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
311 bar->layer_shell = wl_registry_bind(
312 registry, name, &zwlr_layer_shell_v1_interface, 1);
309 } 313 }
310} 314}
311 315
312void free_workspaces(list_t *workspaces) { 316static void handle_global_remove(void *data, struct wl_registry *registry,
313 int i; 317 uint32_t name) {
314 for (i = 0; i < workspaces->length; ++i) { 318 struct swaybar *bar = data;
315 struct workspace *ws = workspaces->items[i]; 319 struct swaybar_output *output, *tmp;
316 free(ws->name); 320 wl_list_for_each_safe(output, tmp, &bar->outputs, link) {
317 free(ws); 321 if (output->wl_name == name) {
322 swaybar_output_free(output);
323 break;
324 }
318 } 325 }
319 list_free(workspaces);
320} 326}
321 327
322static void free_output(struct output *output) { 328static const struct wl_registry_listener registry_listener = {
323 window_teardown(output->window); 329 .global = handle_global,
324 if (output->registry) { 330 .global_remove = handle_global_remove,
325 registry_teardown(output->registry); 331};
332
333static void render_all_frames(struct swaybar *bar) {
334 struct swaybar_output *output;
335 wl_list_for_each(output, &bar->outputs, link) {
336 render_frame(bar, output);
326 } 337 }
338}
327 339
328 free(output->name); 340void bar_setup(struct swaybar *bar,
341 const char *socket_path, const char *bar_id) {
342 bar_init(bar);
343 init_event_loop();
329 344
330 if (output->workspaces) { 345 bar->ipc_socketfd = ipc_open_socket(socket_path);
331 free_workspaces(output->workspaces); 346 bar->ipc_event_socketfd = ipc_open_socket(socket_path);
347 ipc_initialize(bar, bar_id);
348 if (bar->config->status_command) {
349 bar->status = status_line_init(bar->config->status_command);
332 } 350 }
333 351
334 free(output); 352 bar->display = wl_display_connect(NULL);
335} 353 assert(bar->display);
336 354
337static void free_outputs(list_t *outputs) { 355 struct wl_registry *registry = wl_display_get_registry(bar->display);
338 int i; 356 wl_registry_add_listener(registry, &registry_listener, bar);
339 for (i = 0; i < outputs->length; ++i) { 357 wl_display_roundtrip(bar->display);
340 free_output(outputs->items[i]); 358 assert(bar->compositor && bar->layer_shell && bar->shm);
359 wl_display_roundtrip(bar->display);
360
361 struct swaybar_pointer *pointer = &bar->pointer;
362
363 int max_scale = 1;
364 struct swaybar_output *output;
365 wl_list_for_each(output, &bar->outputs, link) {
366 if (output->scale > max_scale) {
367 max_scale = output->scale;
368 }
341 } 369 }
342 list_free(outputs);
343}
344 370
345static void terminate_status_command(pid_t pid) { 371 pointer->cursor_theme = wl_cursor_theme_load(
346 if (pid) { 372 NULL, 24 * max_scale, bar->shm);
347 // terminate status_command process 373 assert(pointer->cursor_theme);
348 int ret = kill(pid, SIGTERM); 374 struct wl_cursor *cursor;
349 if (ret != 0) { 375 cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr");
350 sway_log(L_ERROR, "Unable to terminate status_command [pid: %d]", pid); 376 assert(cursor);
351 } else { 377 pointer->cursor_image = cursor->images[0];
352 int status; 378 pointer->cursor_surface = wl_compositor_create_surface(bar->compositor);
353 waitpid(pid, &status, 0); 379 assert(pointer->cursor_surface);
380
381 wl_list_for_each(output, &bar->outputs, link) {
382 struct config_output *coutput;
383 wl_list_for_each(coutput, &bar->config->outputs, link) {
384 if (coutput->index == output->index) {
385 output->name = strdup(coutput->name);
386 break;
387 }
354 } 388 }
389 output->surface = wl_compositor_create_surface(bar->compositor);
390 assert(output->surface);
391 output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
392 bar->layer_shell, output->surface, output->output,
393 ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "panel");
394 assert(output->layer_surface);
395 zwlr_layer_surface_v1_add_listener(output->layer_surface,
396 &layer_surface_listener, output);
397 zwlr_layer_surface_v1_set_anchor(output->layer_surface,
398 bar->config->position);
355 } 399 }
400 ipc_get_workspaces(bar);
401 render_all_frames(bar);
356} 402}
357 403
358void bar_teardown(struct bar *bar) { 404static void display_in(int fd, short mask, void *_bar) {
359 if (bar->config) { 405 struct swaybar *bar = (struct swaybar *)_bar;
360 free_config(bar->config); 406 if (wl_display_dispatch(bar->display) == -1) {
407 bar_teardown(bar);
408 exit(0);
361 } 409 }
410}
362 411
363 if (bar->outputs) { 412static void ipc_in(int fd, short mask, void *_bar) {
364 free_outputs(bar->outputs); 413 struct swaybar *bar = (struct swaybar *)_bar;
414 if (handle_ipc_readable(bar)) {
415 render_all_frames(bar);
365 } 416 }
417}
366 418
367 if (bar->status) { 419static void status_in(int fd, short mask, void *_bar) {
368 free_status_line(bar->status); 420 struct swaybar *bar = (struct swaybar *)_bar;
421 if (status_handle_readable(bar->status)) {
422 render_all_frames(bar);
369 } 423 }
424}
370 425
371 /* close sockets/pipes */ 426void bar_run(struct swaybar *bar) {
372 if (bar->status_read_fd) { 427 add_event(wl_display_get_fd(bar->display), POLLIN, display_in, bar);
373 close(bar->status_read_fd); 428 add_event(bar->ipc_event_socketfd, POLLIN, ipc_in, bar);
429 if (bar->status) {
430 add_event(bar->status->read_fd, POLLIN, status_in, bar);
374 } 431 }
375 432 while (1) {
376 if (bar->status_write_fd) { 433 event_loop_poll();
377 close(bar->status_write_fd);
378 } 434 }
435}
379 436
380 if (bar->ipc_socketfd) { 437static void free_outputs(struct wl_list *list) {
381 close(bar->ipc_socketfd); 438 struct swaybar_output *output, *tmp;
439 wl_list_for_each_safe(output, tmp, list, link) {
440 wl_list_remove(&output->link);
441 free(output->name);
442 free(output);
382 } 443 }
444}
383 445
384 if (bar->ipc_event_socketfd) { 446void bar_teardown(struct swaybar *bar) {
385 close(bar->ipc_event_socketfd); 447 free_outputs(&bar->outputs);
448 if (bar->config) {
449 free_config(bar->config);
450 }
451 close(bar->ipc_event_socketfd);
452 close(bar->ipc_socketfd);
453 if (bar->status) {
454 status_line_free(bar->status);
386 } 455 }
387
388 /* terminate status command process */
389 terminate_status_command(bar->status_command_pid);
390} 456}
diff --git a/swaybar/config.c b/swaybar/config.c
index 8fe552f2..9169ad27 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -1,40 +1,32 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <stdlib.h> 2#include <stdlib.h>
3#include <string.h> 3#include <string.h>
4#include "wayland-desktop-shell-client-protocol.h"
5#include "log.h"
6#include "swaybar/config.h" 4#include "swaybar/config.h"
5#include "wlr-layer-shell-unstable-v1-client-protocol.h"
7 6
8uint32_t parse_position(const char *position) { 7uint32_t parse_position(const char *position) {
8 uint32_t horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
9 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
10 uint32_t vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
11 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
9 if (strcmp("top", position) == 0) { 12 if (strcmp("top", position) == 0) {
10 return DESKTOP_SHELL_PANEL_POSITION_TOP; 13 return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | horiz;
11 } else if (strcmp("bottom", position) == 0) { 14 } else if (strcmp("bottom", position) == 0) {
12 return DESKTOP_SHELL_PANEL_POSITION_BOTTOM; 15 return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | horiz;
13 } else if (strcmp("left", position) == 0) { 16 } else if (strcmp("left", position) == 0) {
14 return DESKTOP_SHELL_PANEL_POSITION_LEFT; 17 return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | vert;
15 } else if (strcmp("right", position) == 0) { 18 } else if (strcmp("right", position) == 0) {
16 return DESKTOP_SHELL_PANEL_POSITION_RIGHT; 19 return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | vert;
17 } else { 20 } else {
18 return DESKTOP_SHELL_PANEL_POSITION_BOTTOM; 21 return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | horiz;
19 } 22 }
20} 23}
21 24
22char *parse_font(const char *font) { 25struct swaybar_config *init_config() {
23 char *new_font = NULL; 26 struct swaybar_config *config = calloc(1, sizeof(struct swaybar_config));
24 if (strncmp("pango:", font, 6) == 0) {
25 font += 6;
26 }
27
28 new_font = strdup(font);
29
30 return new_font;
31}
32
33struct config *init_config() {
34 struct config *config = calloc(1, sizeof(struct config));
35 config->status_command = NULL; 27 config->status_command = NULL;
36 config->pango_markup = false; 28 config->pango_markup = false;
37 config->position = DESKTOP_SHELL_PANEL_POSITION_BOTTOM; 29 config->position = parse_position("bottom");
38 config->font = strdup("monospace 10"); 30 config->font = strdup("monospace 10");
39 config->mode = NULL; 31 config->mode = NULL;
40 config->sep_symbol = NULL; 32 config->sep_symbol = NULL;
@@ -42,27 +34,16 @@ struct config *init_config() {
42 config->binding_mode_indicator = true; 34 config->binding_mode_indicator = true;
43 config->wrap_scroll = false; 35 config->wrap_scroll = false;
44 config->workspace_buttons = true; 36 config->workspace_buttons = true;
45 config->all_outputs = false; 37 wl_list_init(&config->outputs);
46 config->outputs = create_list();
47 38
48 /* height */ 39 /* height */
49 config->height = 0; 40 config->height = 0;
50 41
51#ifdef ENABLE_TRAY
52 config->tray_output = NULL;
53 config->icon_theme = NULL;
54 config->tray_padding = 2;
55 /**
56 * These constants are used by wayland and are defined in
57 * linux/input-event-codes.h
58 */
59 config->activate_button = 0x110; /* BTN_LEFT */
60 config->context_button = 0x111; /* BTN_RIGHT */
61#endif
62
63 /* colors */ 42 /* colors */
64 config->colors.background = 0x000000FF; 43 config->colors.background = 0x000000FF;
44 config->colors.focused_background = 0x000000FF;
65 config->colors.statusline = 0xFFFFFFFF; 45 config->colors.statusline = 0xFFFFFFFF;
46 config->colors.focused_statusline = 0xFFFFFFFF;
66 config->colors.separator = 0x666666FF; 47 config->colors.separator = 0x666666FF;
67 48
68 config->colors.focused_workspace.border = 0x4C7899FF; 49 config->colors.focused_workspace.border = 0x4C7899FF;
@@ -88,7 +69,7 @@ struct config *init_config() {
88 return config; 69 return config;
89} 70}
90 71
91void free_config(struct config *config) { 72void free_config(struct swaybar_config *config) {
92 free(config->status_command); 73 free(config->status_command);
93 free(config->font); 74 free(config->font);
94 free(config->mode); 75 free(config->mode);
diff --git a/swaybar/event_loop.c b/swaybar/event_loop.c
index 0d1be1da..748372ed 100644
--- a/swaybar/event_loop.c
+++ b/swaybar/event_loop.c
@@ -4,19 +4,18 @@
4#include <string.h> 4#include <string.h>
5#include <strings.h> 5#include <strings.h>
6#include <poll.h> 6#include <poll.h>
7#include "swaybar/bar.h" 7#include <time.h>
8#include "swaybar/event_loop.h" 8#include "swaybar/event_loop.h"
9#include "list.h" 9#include "list.h"
10#include "log.h"
11 10
12struct event_item { 11struct event_item {
13 void(*cb)(int fd, short mask, void *data); 12 void (*cb)(int fd, short mask, void *data);
14 void *data; 13 void *data;
15}; 14};
16 15
17struct timer_item { 16struct timer_item {
18 timer_t timer; 17 timer_t timer;
19 void(*cb)(timer_t timer, void *data); 18 void (*cb)(timer_t timer, void *data);
20 void *data; 19 void *data;
21}; 20};
22 21
@@ -138,7 +137,8 @@ void event_loop_poll() {
138void init_event_loop() { 137void init_event_loop() {
139 event_loop.fds.length = 0; 138 event_loop.fds.length = 0;
140 event_loop.fds.capacity = 10; 139 event_loop.fds.capacity = 10;
141 event_loop.fds.items = malloc(event_loop.fds.capacity * sizeof(struct pollfd)); 140 event_loop.fds.items = malloc(
141 event_loop.fds.capacity * sizeof(struct pollfd));
142 event_loop.items = create_list(); 142 event_loop.items = create_list();
143 event_loop.timers = create_list(); 143 event_loop.timers = create_list();
144} 144}
diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c
new file mode 100644
index 00000000..923ad755
--- /dev/null
+++ b/swaybar/i3bar.c
@@ -0,0 +1,215 @@
1#define _POSIX_C_SOURCE 200809L
2#include <json-c/json.h>
3#include <stdlib.h>
4#include <string.h>
5#include <unistd.h>
6#include <wlr/util/log.h>
7#include "swaybar/config.h"
8#include "swaybar/status_line.h"
9
10static void i3bar_block_free(struct i3bar_block *block) {
11 if (!block) {
12 return;
13 }
14 wl_list_remove(&block->link);
15 free(block->full_text);
16 free(block->short_text);
17 free(block->align);
18 free(block->name);
19 free(block->instance);
20 free(block->color);
21}
22
23static bool i3bar_parse_json(struct status_line *status, const char *text) {
24 struct i3bar_block *block, *tmp;
25 wl_list_for_each_safe(block, tmp, &status->blocks, link) {
26 i3bar_block_free(block);
27 }
28 json_object *results = json_tokener_parse(text);
29 if (!results) {
30 status_error(status, "[failed to parse i3bar json]");
31 return false;
32 }
33 wlr_log(L_DEBUG, "Got i3bar json: '%s'", text);
34 for (size_t i = 0; i < json_object_array_length(results); ++i) {
35 json_object *full_text, *short_text, *color, *min_width, *align, *urgent;
36 json_object *name, *instance, *separator, *separator_block_width;
37 json_object *background, *border, *border_top, *border_bottom;
38 json_object *border_left, *border_right, *markup;
39 json_object *json = json_object_array_get_idx(results, i);
40 if (!json) {
41 continue;
42 }
43 json_object_object_get_ex(json, "full_text", &full_text);
44 json_object_object_get_ex(json, "short_text", &short_text);
45 json_object_object_get_ex(json, "color", &color);
46 json_object_object_get_ex(json, "min_width", &min_width);
47 json_object_object_get_ex(json, "align", &align);
48 json_object_object_get_ex(json, "urgent", &urgent);
49 json_object_object_get_ex(json, "name", &name);
50 json_object_object_get_ex(json, "instance", &instance);
51 json_object_object_get_ex(json, "markup", &markup);
52 json_object_object_get_ex(json, "separator", &separator);
53 json_object_object_get_ex(json, "separator_block_width", &separator_block_width);
54 json_object_object_get_ex(json, "background", &background);
55 json_object_object_get_ex(json, "border", &border);
56 json_object_object_get_ex(json, "border_top", &border_top);
57 json_object_object_get_ex(json, "border_bottom", &border_bottom);
58 json_object_object_get_ex(json, "border_left", &border_left);
59 json_object_object_get_ex(json, "border_right", &border_right);
60
61 struct i3bar_block *block = calloc(1, sizeof(struct i3bar_block));
62 block->full_text = full_text ?
63 strdup(json_object_get_string(full_text)) : NULL;
64 block->short_text = short_text ?
65 strdup(json_object_get_string(short_text)) : NULL;
66 if (color) {
67 block->color = malloc(sizeof(uint32_t));
68 *block->color = parse_color(json_object_get_string(color));
69 }
70 if (min_width) {
71 json_type type = json_object_get_type(min_width);
72 if (type == json_type_int) {
73 block->min_width = json_object_get_int(min_width);
74 } else if (type == json_type_string) {
75 /* the width will be calculated when rendering */
76 block->min_width = 0;
77 }
78 }
79 block->align = strdup(align ? json_object_get_string(align) : "left");
80 block->urgent = urgent ? json_object_get_int(urgent) : false;
81 block->name = name ? strdup(json_object_get_string(name)) : NULL;
82 block->instance = instance ?
83 strdup(json_object_get_string(instance)) : NULL;
84 if (markup) {
85 block->markup = false;
86 const char *markup_str = json_object_get_string(markup);
87 if (strcmp(markup_str, "pango") == 0) {
88 block->markup = true;
89 }
90 }
91 block->separator = separator ? json_object_get_int(separator) : true;
92 block->separator_block_width = separator_block_width ?
93 json_object_get_int(separator_block_width) : 9;
94 // Airblader features
95 block->background = background ?
96 parse_color(json_object_get_string(background)) : 0;
97 block->border = border ?
98 parse_color(json_object_get_string(border)) : 0;
99 block->border_top = border_top ? json_object_get_int(border_top) : 1;
100 block->border_bottom = border_bottom ?
101 json_object_get_int(border_bottom) : 1;
102 block->border_left = border_left ? json_object_get_int(border_left) : 1;
103 block->border_right = border_right ?
104 json_object_get_int(border_right) : 1;
105 wl_list_insert(&status->blocks, &block->link);
106 }
107 return true;
108}
109
110bool i3bar_handle_readable(struct status_line *status) {
111 struct i3bar_protocol_state *state = &status->i3bar_state;
112
113 char *cur = &state->buffer[state->buffer_index];
114 ssize_t n = read(status->read_fd, cur,
115 state->buffer_size - state->buffer_index);
116 if (n == -1) {
117 status_error(status, "[failed to read from status command]");
118 return false;
119 }
120
121 if (n == (ssize_t)(state->buffer_size - state->buffer_index)) {
122 state->buffer_size = state->buffer_size * 2;
123 char *new_buffer = realloc(state->buffer, state->buffer_size);
124 if (!new_buffer) {
125 free(state->buffer);
126 status_error(status, "[failed to allocate buffer]");
127 return true;
128 }
129 state->current_node += new_buffer - state->buffer;
130 cur += new_buffer - state->buffer;
131 state->buffer = new_buffer;
132 }
133
134 cur[n] = '\0';
135 bool redraw = false;
136 while (*cur) {
137 if (state->nodes[state->depth] == JSON_NODE_STRING) {
138 if (!state->escape && *cur == '"') {
139 --state->depth;
140 }
141 state->escape = !state->escape && *cur == '\\';
142 } else {
143 switch (*cur) {
144 case '[':
145 ++state->depth;
146 if (state->depth >
147 sizeof(state->nodes) / sizeof(state->nodes[0])) {
148 status_error(status, "[i3bar json too deep]");
149 return false;
150 }
151 state->nodes[state->depth] = JSON_NODE_ARRAY;
152 if (state->depth == 1) {
153 state->current_node = cur;
154 }
155 break;
156 case ']':
157 if (state->nodes[state->depth] != JSON_NODE_ARRAY) {
158 status_error(status, "[failed to parse i3bar json]");
159 return false;
160 }
161 --state->depth;
162 if (state->depth == 0) {
163 // cur[1] is valid since cur[0] != '\0'
164 char p = cur[1];
165 cur[1] = '\0';
166 redraw = i3bar_parse_json(
167 status, state->current_node) || redraw;
168 cur[1] = p;
169 memmove(state->buffer, cur,
170 state->buffer_size - (cur - state->buffer));
171 cur = state->buffer;
172 state->current_node = cur + 1;
173 }
174 break;
175 case '"':
176 ++state->depth;
177 if (state->depth >
178 sizeof(state->nodes) / sizeof(state->nodes[0])) {
179 status_error(status, "[i3bar json too deep]");
180 return false;
181 }
182 state->nodes[state->depth] = JSON_NODE_STRING;
183 break;
184 }
185 }
186 ++cur;
187 }
188 state->buffer_index = cur - state->buffer;
189 return redraw;
190}
191
192void i3bar_block_send_click(struct status_line *status,
193 struct i3bar_block *block, int x, int y, uint32_t button) {
194 wlr_log(L_DEBUG, "block %s clicked", block->name ? block->name : "(nil)");
195 if (!block->name || !status->i3bar_state.click_events) {
196 return;
197 }
198
199 struct json_object *event_json = json_object_new_object();
200 json_object_object_add(event_json, "name",
201 json_object_new_string(block->name));
202 if (block->instance) {
203 json_object_object_add(event_json, "instance",
204 json_object_new_string(block->instance));
205 }
206
207 json_object_object_add(event_json, "button", json_object_new_int(button));
208 json_object_object_add(event_json, "x", json_object_new_int(x));
209 json_object_object_add(event_json, "y", json_object_new_int(y));
210 if (dprintf(status->write_fd, "%s\n",
211 json_object_to_json_string(event_json)) < 0) {
212 status_error(status, "[failed to write click event]");
213 }
214 json_object_put(event_json);
215}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index 93d1219c..ed5d9a31 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -1,37 +1,140 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <limits.h>
2#include <string.h> 3#include <string.h>
3#include <strings.h> 4#include <strings.h>
4#include <json-c/json.h> 5#include <json-c/json.h>
6#include <wlr/util/log.h>
5#include "swaybar/config.h" 7#include "swaybar/config.h"
6#include "swaybar/ipc.h" 8#include "swaybar/ipc.h"
7#include "ipc-client.h" 9#include "ipc-client.h"
8#include "list.h"
9#include "log.h"
10
11void ipc_send_workspace_command(const char *workspace_name) {
12 uint32_t size = strlen("workspace \"\"") + strlen(workspace_name) + 1;
13 10
11void ipc_send_workspace_command(struct swaybar *bar, const char *ws) {
12 const char *fmt = "workspace \"%s\"";
13 uint32_t size = snprintf(NULL, 0, fmt, ws);
14 char command[size]; 14 char command[size];
15 sprintf(command, "workspace \"%s\"", workspace_name); 15 snprintf(command, size, fmt, ws);
16 ipc_single_command(bar->ipc_socketfd, IPC_COMMAND, command, &size);
17}
16 18
17 ipc_single_command(swaybar.ipc_socketfd, IPC_COMMAND, command, &size); 19char *parse_font(const char *font) {
20 char *new_font = NULL;
21 if (strncmp("pango:", font, 6) == 0) {
22 font += 6;
23 }
24 new_font = strdup(font);
25 return new_font;
18} 26}
19 27
20static void ipc_parse_config(struct config *config, const char *payload) { 28static void ipc_parse_colors(
29 struct swaybar_config *config, json_object *colors) {
30 json_object *background, *statusline, *separator;
31 json_object *focused_background, *focused_statusline, *focused_separator;
32 json_object *focused_workspace_border, *focused_workspace_bg, *focused_workspace_text;
33 json_object *inactive_workspace_border, *inactive_workspace_bg, *inactive_workspace_text;
34 json_object *active_workspace_border, *active_workspace_bg, *active_workspace_text;
35 json_object *urgent_workspace_border, *urgent_workspace_bg, *urgent_workspace_text;
36 json_object *binding_mode_border, *binding_mode_bg, *binding_mode_text;
37 json_object_object_get_ex(colors, "background", &background);
38 json_object_object_get_ex(colors, "statusline", &statusline);
39 json_object_object_get_ex(colors, "separator", &separator);
40 json_object_object_get_ex(colors, "focused_background", &focused_background);
41 json_object_object_get_ex(colors, "focused_statusline", &focused_statusline);
42 json_object_object_get_ex(colors, "focused_separator", &focused_separator);
43 json_object_object_get_ex(colors, "focused_workspace_border", &focused_workspace_border);
44 json_object_object_get_ex(colors, "focused_workspace_bg", &focused_workspace_bg);
45 json_object_object_get_ex(colors, "focused_workspace_text", &focused_workspace_text);
46 json_object_object_get_ex(colors, "active_workspace_border", &active_workspace_border);
47 json_object_object_get_ex(colors, "active_workspace_bg", &active_workspace_bg);
48 json_object_object_get_ex(colors, "active_workspace_text", &active_workspace_text);
49 json_object_object_get_ex(colors, "inactive_workspace_border", &inactive_workspace_border);
50 json_object_object_get_ex(colors, "inactive_workspace_bg", &inactive_workspace_bg);
51 json_object_object_get_ex(colors, "inactive_workspace_text", &inactive_workspace_text);
52 json_object_object_get_ex(colors, "urgent_workspace_border", &urgent_workspace_border);
53 json_object_object_get_ex(colors, "urgent_workspace_bg", &urgent_workspace_bg);
54 json_object_object_get_ex(colors, "urgent_workspace_text", &urgent_workspace_text);
55 json_object_object_get_ex(colors, "binding_mode_border", &binding_mode_border);
56 json_object_object_get_ex(colors, "binding_mode_bg", &binding_mode_bg);
57 json_object_object_get_ex(colors, "binding_mode_text", &binding_mode_text);
58 if (background) {
59 config->colors.background = parse_color(
60 json_object_get_string(background));
61 }
62 if (statusline) {
63 config->colors.statusline = parse_color(
64 json_object_get_string(statusline));
65 }
66 if (separator) {
67 config->colors.separator = parse_color(
68 json_object_get_string(separator));
69 }
70 if (focused_background) {
71 config->colors.focused_background = parse_color(
72 json_object_get_string(focused_background));
73 }
74 if (focused_statusline) {
75 config->colors.focused_statusline = parse_color(
76 json_object_get_string(focused_statusline));
77 }
78 if (focused_separator) {
79 config->colors.focused_separator = parse_color(
80 json_object_get_string(focused_separator));
81 }
82 if (focused_workspace_border) {
83 config->colors.focused_workspace.border = parse_color(
84 json_object_get_string(focused_workspace_border));
85 }
86 if (focused_workspace_bg) {
87 config->colors.focused_workspace.background = parse_color(
88 json_object_get_string(focused_workspace_bg));
89 }
90 if (focused_workspace_text) {
91 config->colors.focused_workspace.text = parse_color(
92 json_object_get_string(focused_workspace_text));
93 }
94 if (active_workspace_border) {
95 config->colors.active_workspace.border = parse_color(
96 json_object_get_string(active_workspace_border));
97 }
98 if (active_workspace_bg) {
99 config->colors.active_workspace.background = parse_color(
100 json_object_get_string(active_workspace_bg));
101 }
102 if (active_workspace_text) {
103 config->colors.active_workspace.text = parse_color(
104 json_object_get_string(active_workspace_text));
105 }
106 if (inactive_workspace_border) {
107 config->colors.inactive_workspace.border = parse_color(
108 json_object_get_string(inactive_workspace_border));
109 }
110 if (inactive_workspace_bg) {
111 config->colors.inactive_workspace.background = parse_color(
112 json_object_get_string(inactive_workspace_bg));
113 }
114 if (inactive_workspace_text) {
115 config->colors.inactive_workspace.text = parse_color(
116 json_object_get_string(inactive_workspace_text));
117 }
118 if (binding_mode_border) {
119 config->colors.binding_mode.border = parse_color(
120 json_object_get_string(binding_mode_border));
121 }
122 if (binding_mode_bg) {
123 config->colors.binding_mode.background = parse_color(
124 json_object_get_string(binding_mode_bg));
125 }
126 if (binding_mode_text) {
127 config->colors.binding_mode.text = parse_color(
128 json_object_get_string(binding_mode_text));
129 }
130}
131
132static void ipc_parse_config(
133 struct swaybar_config *config, const char *payload) {
21 json_object *bar_config = json_tokener_parse(payload); 134 json_object *bar_config = json_tokener_parse(payload);
22 json_object *markup, *mode, *hidden_bar, *position, *status_command; 135 json_object *markup, *mode, *hidden_bar, *position, *status_command;
23 json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers; 136 json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers;
24 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs; 137 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs;
25#ifdef ENABLE_TRAY
26 json_object *tray_output, *icon_theme, *tray_padding, *activate_button, *context_button;
27 json_object *secondary_button;
28 json_object_object_get_ex(bar_config, "tray_output", &tray_output);
29 json_object_object_get_ex(bar_config, "icon_theme", &icon_theme);
30 json_object_object_get_ex(bar_config, "tray_padding", &tray_padding);
31 json_object_object_get_ex(bar_config, "activate_button", &activate_button);
32 json_object_object_get_ex(bar_config, "context_button", &context_button);
33 json_object_object_get_ex(bar_config, "secondary_button", &secondary_button);
34#endif
35 json_object_object_get_ex(bar_config, "mode", &mode); 138 json_object_object_get_ex(bar_config, "mode", &mode);
36 json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar); 139 json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar);
37 json_object_object_get_ex(bar_config, "position", &position); 140 json_object_object_get_ex(bar_config, "position", &position);
@@ -47,230 +150,100 @@ static void ipc_parse_config(struct config *config, const char *payload) {
47 json_object_object_get_ex(bar_config, "colors", &colors); 150 json_object_object_get_ex(bar_config, "colors", &colors);
48 json_object_object_get_ex(bar_config, "outputs", &outputs); 151 json_object_object_get_ex(bar_config, "outputs", &outputs);
49 json_object_object_get_ex(bar_config, "pango_markup", &markup); 152 json_object_object_get_ex(bar_config, "pango_markup", &markup);
50
51 if (status_command) { 153 if (status_command) {
52 free(config->status_command); 154 free(config->status_command);
53 config->status_command = strdup(json_object_get_string(status_command)); 155 config->status_command = strdup(json_object_get_string(status_command));
54 } 156 }
55
56 if (position) { 157 if (position) {
57 config->position = parse_position(json_object_get_string(position)); 158 config->position = parse_position(json_object_get_string(position));
58 } 159 }
59
60 if (font) { 160 if (font) {
61 free(config->font); 161 free(config->font);
62 config->font = parse_font(json_object_get_string(font)); 162 config->font = parse_font(json_object_get_string(font));
63 } 163 }
64
65 if (sep_symbol) { 164 if (sep_symbol) {
66 free(config->sep_symbol); 165 free(config->sep_symbol);
67 config->sep_symbol = strdup(json_object_get_string(sep_symbol)); 166 config->sep_symbol = strdup(json_object_get_string(sep_symbol));
68 } 167 }
69
70 if (strip_workspace_numbers) { 168 if (strip_workspace_numbers) {
71 config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers); 169 config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers);
72 } 170 }
73
74 if (binding_mode_indicator) { 171 if (binding_mode_indicator) {
75 config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator); 172 config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator);
76 } 173 }
77
78 if (wrap_scroll) { 174 if (wrap_scroll) {
79 config->wrap_scroll = json_object_get_boolean(wrap_scroll); 175 config->wrap_scroll = json_object_get_boolean(wrap_scroll);
80 } 176 }
81
82 if (workspace_buttons) { 177 if (workspace_buttons) {
83 config->workspace_buttons = json_object_get_boolean(workspace_buttons); 178 config->workspace_buttons = json_object_get_boolean(workspace_buttons);
84 } 179 }
85
86 if (bar_height) { 180 if (bar_height) {
87 config->height = json_object_get_int(bar_height); 181 config->height = json_object_get_int(bar_height);
88 } 182 }
89
90 if (markup) { 183 if (markup) {
91 config->pango_markup = json_object_get_boolean(markup); 184 config->pango_markup = json_object_get_boolean(markup);
92 } 185 }
93 186
94#ifdef ENABLE_TRAY 187 struct config_output *output, *tmp;
95 if (tray_output) { 188 wl_list_for_each_safe(output, tmp, &config->outputs, link) {
96 free(config->tray_output); 189 wl_list_remove(&output->link);
97 config->tray_output = strdup(json_object_get_string(tray_output)); 190 free(output->name);
98 } 191 free(output);
99
100 if (icon_theme) {
101 free(config->icon_theme);
102 config->icon_theme = strdup(json_object_get_string(icon_theme));
103 }
104
105 if (tray_padding) {
106 config->tray_padding = json_object_get_int(tray_padding);
107 }
108
109 if (activate_button) {
110 config->activate_button = json_object_get_int(activate_button);
111 }
112
113 if (context_button) {
114 config->context_button = json_object_get_int(context_button);
115 } 192 }
116
117 if (secondary_button) {
118 config->secondary_button = json_object_get_int(secondary_button);
119 }
120#endif
121
122 // free previous outputs list
123 int i;
124 for (i = 0; i < config->outputs->length; ++i) {
125 free(config->outputs->items[i]);
126 }
127 list_free(config->outputs);
128 config->outputs = create_list();
129
130 if (outputs) { 193 if (outputs) {
131 int length = json_object_array_length(outputs); 194 int length = json_object_array_length(outputs);
132 json_object *output; 195 for (int i = 0; i < length; ++i) {
133 const char *output_str; 196 json_object *output = json_object_array_get_idx(outputs, i);
134 for (i = 0; i < length; ++i) { 197 const char *name = json_object_get_string(output);
135 output = json_object_array_get_idx(outputs, i); 198 if (strcmp("*", name) == 0) {
136 output_str = json_object_get_string(output);
137 if (strcmp("*", output_str) == 0) {
138 config->all_outputs = true; 199 config->all_outputs = true;
139 break; 200 break;
140 } 201 }
141 list_add(config->outputs, strdup(output_str)); 202 struct config_output *coutput = calloc(
203 1, sizeof(struct config_output));
204 coutput->name = strdup(name);
205 coutput->index = SIZE_MAX;
206 wl_list_insert(&config->outputs, &coutput->link);
142 } 207 }
143 } else { 208 } else {
144 config->all_outputs = true; 209 config->all_outputs = true;
145 } 210 }
146 211
147 if (colors) { 212 if (colors) {
148 json_object *background, *statusline, *separator; 213 ipc_parse_colors(config, colors);
149 json_object *focused_background, *focused_statusline, *focused_separator;
150 json_object *focused_workspace_border, *focused_workspace_bg, *focused_workspace_text;
151 json_object *inactive_workspace_border, *inactive_workspace_bg, *inactive_workspace_text;
152 json_object *active_workspace_border, *active_workspace_bg, *active_workspace_text;
153 json_object *urgent_workspace_border, *urgent_workspace_bg, *urgent_workspace_text;
154 json_object *binding_mode_border, *binding_mode_bg, *binding_mode_text;
155 json_object_object_get_ex(colors, "background", &background);
156 json_object_object_get_ex(colors, "statusline", &statusline);
157 json_object_object_get_ex(colors, "separator", &separator);
158 json_object_object_get_ex(colors, "focused_background", &focused_background);
159 json_object_object_get_ex(colors, "focused_statusline", &focused_statusline);
160 json_object_object_get_ex(colors, "focused_separator", &focused_separator);
161 json_object_object_get_ex(colors, "focused_workspace_border", &focused_workspace_border);
162 json_object_object_get_ex(colors, "focused_workspace_bg", &focused_workspace_bg);
163 json_object_object_get_ex(colors, "focused_workspace_text", &focused_workspace_text);
164 json_object_object_get_ex(colors, "active_workspace_border", &active_workspace_border);
165 json_object_object_get_ex(colors, "active_workspace_bg", &active_workspace_bg);
166 json_object_object_get_ex(colors, "active_workspace_text", &active_workspace_text);
167 json_object_object_get_ex(colors, "inactive_workspace_border", &inactive_workspace_border);
168 json_object_object_get_ex(colors, "inactive_workspace_bg", &inactive_workspace_bg);
169 json_object_object_get_ex(colors, "inactive_workspace_text", &inactive_workspace_text);
170 json_object_object_get_ex(colors, "urgent_workspace_border", &urgent_workspace_border);
171 json_object_object_get_ex(colors, "urgent_workspace_bg", &urgent_workspace_bg);
172 json_object_object_get_ex(colors, "urgent_workspace_text", &urgent_workspace_text);
173 json_object_object_get_ex(colors, "binding_mode_border", &binding_mode_border);
174 json_object_object_get_ex(colors, "binding_mode_bg", &binding_mode_bg);
175 json_object_object_get_ex(colors, "binding_mode_text", &binding_mode_text);
176 if (background) {
177 config->colors.background = parse_color(json_object_get_string(background));
178 }
179
180 if (statusline) {
181 config->colors.statusline = parse_color(json_object_get_string(statusline));
182 }
183
184 if (separator) {
185 config->colors.separator = parse_color(json_object_get_string(separator));
186 }
187
188 if (focused_background) {
189 config->colors.focused_background = parse_color(json_object_get_string(focused_background));
190 }
191
192 if (focused_statusline) {
193 config->colors.focused_statusline = parse_color(json_object_get_string(focused_statusline));
194 }
195
196 if (focused_separator) {
197 config->colors.focused_separator = parse_color(json_object_get_string(focused_separator));
198 }
199
200 if (focused_workspace_border) {
201 config->colors.focused_workspace.border = parse_color(json_object_get_string(focused_workspace_border));
202 }
203
204 if (focused_workspace_bg) {
205 config->colors.focused_workspace.background = parse_color(json_object_get_string(focused_workspace_bg));
206 }
207
208 if (focused_workspace_text) {
209 config->colors.focused_workspace.text = parse_color(json_object_get_string(focused_workspace_text));
210 }
211
212 if (active_workspace_border) {
213 config->colors.active_workspace.border = parse_color(json_object_get_string(active_workspace_border));
214 }
215
216 if (active_workspace_bg) {
217 config->colors.active_workspace.background = parse_color(json_object_get_string(active_workspace_bg));
218 }
219
220 if (active_workspace_text) {
221 config->colors.active_workspace.text = parse_color(json_object_get_string(active_workspace_text));
222 }
223
224 if (inactive_workspace_border) {
225 config->colors.inactive_workspace.border = parse_color(json_object_get_string(inactive_workspace_border));
226 }
227
228 if (inactive_workspace_bg) {
229 config->colors.inactive_workspace.background = parse_color(json_object_get_string(inactive_workspace_bg));
230 }
231
232 if (inactive_workspace_text) {
233 config->colors.inactive_workspace.text = parse_color(json_object_get_string(inactive_workspace_text));
234 }
235
236 if (binding_mode_border) {
237 config->colors.binding_mode.border = parse_color(json_object_get_string(binding_mode_border));
238 }
239
240 if (binding_mode_bg) {
241 config->colors.binding_mode.background = parse_color(json_object_get_string(binding_mode_bg));
242 }
243
244 if (binding_mode_text) {
245 config->colors.binding_mode.text = parse_color(json_object_get_string(binding_mode_text));
246 }
247 } 214 }
248 215
249 json_object_put(bar_config); 216 json_object_put(bar_config);
250} 217}
251 218
252static void ipc_update_workspaces(struct bar *bar) { 219static void free_workspaces(struct wl_list *list) {
253 int i; 220 struct swaybar_workspace *ws, *tmp;
254 for (i = 0; i < bar->outputs->length; ++i) { 221 wl_list_for_each_safe(ws, tmp, list, link) {
255 struct output *output = bar->outputs->items[i]; 222 wl_list_remove(&ws->link);
256 if (output->workspaces) { 223 free(ws->name);
257 free_workspaces(output->workspaces); 224 free(ws);
258 }
259 output->workspaces = create_list();
260 } 225 }
226}
261 227
228void ipc_get_workspaces(struct swaybar *bar) {
229 bar->focused_output = NULL;
230 struct swaybar_output *output;
231 wl_list_for_each(output, &bar->outputs, link) {
232 free_workspaces(&output->workspaces);
233 output->focused = false;
234 }
262 uint32_t len = 0; 235 uint32_t len = 0;
263 char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_WORKSPACES, NULL, &len); 236 char *res = ipc_single_command(bar->ipc_socketfd,
237 IPC_GET_WORKSPACES, NULL, &len);
264 json_object *results = json_tokener_parse(res); 238 json_object *results = json_tokener_parse(res);
265 if (!results) { 239 if (!results) {
266 free(res); 240 free(res);
267 return; 241 return;
268 } 242 }
269 243 size_t length = json_object_array_length(results);
270 int length = json_object_array_length(results);
271 json_object *ws_json; 244 json_object *ws_json;
272 json_object *num, *name, *visible, *focused, *out, *urgent; 245 json_object *num, *name, *visible, *focused, *out, *urgent;
273 for (i = 0; i < length; ++i) { 246 for (size_t i = 0; i < length; ++i) {
274 ws_json = json_object_array_get_idx(results, i); 247 ws_json = json_object_array_get_idx(results, i);
275 248
276 json_object_object_get_ex(ws_json, "num", &num); 249 json_object_object_get_ex(ws_json, "num", &num);
@@ -280,113 +253,95 @@ static void ipc_update_workspaces(struct bar *bar) {
280 json_object_object_get_ex(ws_json, "output", &out); 253 json_object_object_get_ex(ws_json, "output", &out);
281 json_object_object_get_ex(ws_json, "urgent", &urgent); 254 json_object_object_get_ex(ws_json, "urgent", &urgent);
282 255
283 int j; 256 wl_list_for_each(output, &bar->outputs, link) {
284 for (j = 0; j < bar->outputs->length; ++j) { 257 const char *ws_output = json_object_get_string(out);
285 struct output *output = bar->outputs->items[j]; 258 if (strcmp(ws_output, output->name) == 0) {
286 if (strcmp(json_object_get_string(out), output->name) == 0) { 259 struct swaybar_workspace *ws =
287 struct workspace *ws = malloc(sizeof(struct workspace)); 260 calloc(1, sizeof(struct swaybar_workspace));
288 ws->num = json_object_get_int(num); 261 ws->num = json_object_get_int(num);
289 ws->name = strdup(json_object_get_string(name)); 262 ws->name = strdup(json_object_get_string(name));
290 ws->visible = json_object_get_boolean(visible); 263 ws->visible = json_object_get_boolean(visible);
291 ws->focused = json_object_get_boolean(focused); 264 ws->focused = json_object_get_boolean(focused);
292 if (ws->focused) { 265 if (ws->focused) {
293 if (bar->focused_output) {
294 bar->focused_output->focused = false;
295 }
296 bar->focused_output = output;
297 output->focused = true; 266 output->focused = true;
298 } 267 }
299 ws->urgent = json_object_get_boolean(urgent); 268 ws->urgent = json_object_get_boolean(urgent);
300 list_add(output->workspaces, ws); 269 wl_list_insert(&output->workspaces, &ws->link);
301 } 270 }
302 } 271 }
303 } 272 }
304
305 json_object_put(results); 273 json_object_put(results);
306 free(res); 274 free(res);
307} 275}
308 276
309void ipc_bar_init(struct bar *bar, const char *bar_id) { 277static void ipc_get_outputs(struct swaybar *bar) {
310 // Get bar config 278 uint32_t len = 0;
311 uint32_t len = strlen(bar_id); 279 char *res = ipc_single_command(bar->ipc_socketfd,
312 char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_BAR_CONFIG, bar_id, &len); 280 IPC_GET_OUTPUTS, NULL, &len);
313
314 ipc_parse_config(bar->config, res);
315 free(res);
316
317 // Get outputs
318 len = 0;
319 res = ipc_single_command(bar->ipc_socketfd, IPC_GET_OUTPUTS, NULL, &len);
320 json_object *outputs = json_tokener_parse(res); 281 json_object *outputs = json_tokener_parse(res);
321 int i; 282 for (size_t i = 0; i < json_object_array_length(outputs); ++i) {
322 int length = json_object_array_length(outputs); 283 json_object *output = json_object_array_get_idx(outputs, i);
323 json_object *output, *output_name, *output_active; 284 json_object *output_name, *output_active;
324 const char *name;
325 bool active;
326 for (i = 0; i < length; ++i) {
327 output = json_object_array_get_idx(outputs, i);
328 json_object_object_get_ex(output, "name", &output_name); 285 json_object_object_get_ex(output, "name", &output_name);
329 json_object_object_get_ex(output, "active", &output_active); 286 json_object_object_get_ex(output, "active", &output_active);
330 name = json_object_get_string(output_name); 287 const char *name = json_object_get_string(output_name);
331 active = json_object_get_boolean(output_active); 288 bool active = json_object_get_boolean(output_active);
332 if (!active) { 289 if (!active) {
333 continue; 290 continue;
334 } 291 }
335
336 bool use_output = false;
337 if (bar->config->all_outputs) { 292 if (bar->config->all_outputs) {
338 use_output = true; 293 struct config_output *coutput = calloc(
294 1, sizeof(struct config_output));
295 coutput->name = strdup(name);
296 coutput->index = i;
297 wl_list_insert(&bar->config->outputs, &coutput->link);
339 } else { 298 } else {
340 int j = 0; 299 struct config_output *coutput;
341 for (j = 0; j < bar->config->outputs->length; ++j) { 300 wl_list_for_each(coutput, &bar->config->outputs, link) {
342 const char *conf_name = bar->config->outputs->items[j]; 301 if (strcmp(name, coutput->name) == 0) {
343 if (strcasecmp(name, conf_name) == 0) { 302 coutput->index = i;
344 use_output = true;
345 break; 303 break;
346 } 304 }
347 } 305 }
348 } 306 }
349
350 if (!use_output) {
351 continue;
352 }
353
354 // add bar to the output
355 struct output *bar_output = new_output(name);
356 bar_output->idx = i;
357 list_add(bar->outputs, bar_output);
358 } 307 }
359 free(res);
360 json_object_put(outputs); 308 json_object_put(outputs);
309 free(res);
310}
361 311
362 const char *subscribe_json = "[ \"workspace\", \"mode\" ]"; 312void ipc_initialize(struct swaybar *bar, const char *bar_id) {
363 len = strlen(subscribe_json); 313 uint32_t len = strlen(bar_id);
364 res = ipc_single_command(bar->ipc_event_socketfd, IPC_SUBSCRIBE, subscribe_json, &len); 314 char *res = ipc_single_command(bar->ipc_socketfd,
315 IPC_GET_BAR_CONFIG, bar_id, &len);
316 ipc_parse_config(bar->config, res);
365 free(res); 317 free(res);
318 ipc_get_outputs(bar);
366 319
367 ipc_update_workspaces(bar); 320 const char *subscribe = "[ \"workspace\", \"mode\" ]";
321 len = strlen(subscribe);
322 free(ipc_single_command(bar->ipc_event_socketfd,
323 IPC_SUBSCRIBE, subscribe, &len));
368} 324}
369 325
370bool handle_ipc_event(struct bar *bar) { 326bool handle_ipc_readable(struct swaybar *bar) {
371 struct ipc_response *resp = ipc_recv_response(bar->ipc_event_socketfd); 327 struct ipc_response *resp = ipc_recv_response(bar->ipc_event_socketfd);
372 if (!resp) { 328 if (!resp) {
373 return false; 329 return false;
374 } 330 }
375 switch (resp->type) { 331 switch (resp->type) {
376 case IPC_EVENT_WORKSPACE: 332 case IPC_EVENT_WORKSPACE:
377 ipc_update_workspaces(bar); 333 ipc_get_workspaces(bar);
378 break; 334 break;
379 case IPC_EVENT_MODE: { 335 case IPC_EVENT_MODE: {
380 json_object *result = json_tokener_parse(resp->payload); 336 json_object *result = json_tokener_parse(resp->payload);
381 if (!result) { 337 if (!result) {
382 free_ipc_response(resp); 338 free_ipc_response(resp);
383 sway_log(L_ERROR, "failed to parse payload as json"); 339 wlr_log(L_ERROR, "failed to parse payload as json");
384 return false; 340 return false;
385 } 341 }
386 json_object *json_change; 342 json_object *json_change, *json_pango_markup;
387 if (json_object_object_get_ex(result, "change", &json_change)) { 343 if (json_object_object_get_ex(result, "change", &json_change)) {
388 const char *change = json_object_get_string(json_change); 344 const char *change = json_object_get_string(json_change);
389
390 free(bar->config->mode); 345 free(bar->config->mode);
391 if (strcmp(change, "default") == 0) { 346 if (strcmp(change, "default") == 0) {
392 bar->config->mode = NULL; 347 bar->config->mode = NULL;
@@ -394,9 +349,16 @@ bool handle_ipc_event(struct bar *bar) {
394 bar->config->mode = strdup(change); 349 bar->config->mode = strdup(change);
395 } 350 }
396 } else { 351 } else {
397 sway_log(L_ERROR, "failed to parse response"); 352 wlr_log(L_ERROR, "failed to parse response");
353 json_object_put(result);
354 free_ipc_response(resp);
355 return false;
356 }
357 if (json_object_object_get_ex(result,
358 "pango_markup", &json_pango_markup)) {
359 bar->config->mode_pango_markup = json_object_get_boolean(
360 json_pango_markup);
398 } 361 }
399
400 json_object_put(result); 362 json_object_put(result);
401 break; 363 break;
402 } 364 }
@@ -404,7 +366,6 @@ bool handle_ipc_event(struct bar *bar) {
404 free_ipc_response(resp); 366 free_ipc_response(resp);
405 return false; 367 return false;
406 } 368 }
407
408 free_ipc_response(resp); 369 free_ipc_response(resp);
409 return true; 370 return true;
410} 371}
diff --git a/swaybar/main.c b/swaybar/main.c
index 0abd0755..c897e1c9 100644
--- a/swaybar/main.c
+++ b/swaybar/main.c
@@ -4,21 +4,20 @@
4#include <string.h> 4#include <string.h>
5#include <stdbool.h> 5#include <stdbool.h>
6#include <getopt.h> 6#include <getopt.h>
7#include <wlr/util/log.h>
7#include "swaybar/bar.h" 8#include "swaybar/bar.h"
8#include "ipc-client.h" 9#include "ipc-client.h"
9#include "log.h"
10 10
11/* global bar state */ 11static struct swaybar swaybar;
12struct bar swaybar;
13 12
14void sway_terminate(int exit_code) { 13void sig_handler(int signal) {
15 bar_teardown(&swaybar); 14 bar_teardown(&swaybar);
16 exit(exit_code); 15 exit(0);
17} 16}
18 17
19void sig_handler(int signal) { 18void sway_terminate(int code) {
20 bar_teardown(&swaybar); 19 bar_teardown(&swaybar);
21 exit(0); 20 exit(code);
22} 21}
23 22
24int main(int argc, char **argv) { 23int main(int argc, char **argv) {
@@ -75,20 +74,23 @@ int main(int argc, char **argv) {
75 } 74 }
76 } 75 }
77 76
78 if (!bar_id) {
79 sway_abort("No bar_id passed. Provide --bar_id or let sway start swaybar");
80 }
81
82 if (debug) { 77 if (debug) {
83 init_log(L_DEBUG); 78 wlr_log_init(L_DEBUG, NULL);
84 } else { 79 } else {
85 init_log(L_ERROR); 80 wlr_log_init(L_ERROR, NULL);
81 }
82
83 if (!bar_id) {
84 wlr_log(L_ERROR, "No bar_id passed. "
85 "Provide --bar_id or let sway start swaybar");
86 return 1;
86 } 87 }
87 88
88 if (!socket_path) { 89 if (!socket_path) {
89 socket_path = get_socketpath(); 90 socket_path = get_socketpath();
90 if (!socket_path) { 91 if (!socket_path) {
91 sway_abort("Unable to retrieve socket path"); 92 wlr_log(L_ERROR, "Unable to retrieve socket path");
93 return 1;
92 } 94 }
93 } 95 }
94 96
@@ -100,9 +102,6 @@ int main(int argc, char **argv) {
100 free(bar_id); 102 free(bar_id);
101 103
102 bar_run(&swaybar); 104 bar_run(&swaybar);
103
104 // gracefully shutdown swaybar and status_command
105 bar_teardown(&swaybar); 105 bar_teardown(&swaybar);
106
107 return 0; 106 return 0;
108} 107}
diff --git a/swaybar/meson.build b/swaybar/meson.build
new file mode 100644
index 00000000..d65edb11
--- /dev/null
+++ b/swaybar/meson.build
@@ -0,0 +1,28 @@
1executable(
2 'swaybar', [
3 'bar.c',
4 'config.c',
5 'event_loop.c',
6 'i3bar.c',
7 'ipc.c',
8 'main.c',
9 'render.c',
10 'status_line.c',
11 ],
12 include_directories: [sway_inc],
13 dependencies: [
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],
27 install: true
28)
diff --git a/swaybar/render.c b/swaybar/render.c
index 6fc09078..26248d35 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -1,35 +1,92 @@
1#define _POSIX_C_SOURCE 200809L
2#include <limits.h>
1#include <stdlib.h> 3#include <stdlib.h>
2#include <stdint.h> 4#include <stdint.h>
3#include <string.h> 5#include <string.h>
4 6#include <wlr/util/log.h>
5#include "client/cairo.h" 7#include "cairo.h"
6#include "client/pango.h" 8#include "pango.h"
7#include "client/window.h" 9#include "pool-buffer.h"
10#include "swaybar/bar.h"
8#include "swaybar/config.h" 11#include "swaybar/config.h"
9#include "swaybar/status_line.h" 12#include "swaybar/ipc.h"
10#include "swaybar/render.h" 13#include "swaybar/render.h"
11#ifdef ENABLE_TRAY 14#include "swaybar/status_line.h"
12#include "swaybar/tray/tray.h" 15#include "wlr-layer-shell-unstable-v1-client-protocol.h"
13#include "swaybar/tray/sni.h" 16
14#endif 17static const int WS_HORIZONTAL_PADDING = 5;
15#include "log.h" 18static const double WS_VERTICAL_PADDING = 1.5;
16 19static const double BORDER_WIDTH = 1;
17 20
18/* internal spacing */ 21static uint32_t render_status_line_error(cairo_t *cairo,
19static int margin = 3; 22 struct swaybar_output *output, struct swaybar_config *config,
20static int ws_horizontal_padding = 5; 23 const char *error, double *x, uint32_t surface_height) {
21static double ws_vertical_padding = 1.5; 24 if (!error) {
22static int ws_spacing = 1; 25 return 0;
23 26 }
24/** 27
25 * Renders a sharp line of any width and height. 28 uint32_t height = surface_height * output->scale;
26 * 29
27 * The line is drawn from (x,y) to (x+width,y+height) where width/height is 0 30 cairo_set_source_u32(cairo, 0xFF0000FF);
28 * if the line has a width/height of one pixel, respectively. 31
29 */ 32 int margin = 3 * output->scale;
30static void render_sharp_line(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) { 33 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
31 cairo_set_source_u32(cairo, color); 34
35 int text_width, text_height;
36 get_text_size(cairo, config->font,
37 &text_width, &text_height, output->scale, false, "%s", error);
38
39 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
40 uint32_t ideal_surface_height = ideal_height / output->scale;
41 if (surface_height < ideal_surface_height) {
42 return ideal_surface_height;
43 }
44 *x -= text_width + margin;
45
46 double text_y = height / 2.0 - text_height / 2.0;
47 cairo_move_to(cairo, *x, (int)floor(text_y));
48 pango_printf(cairo, config->font, output->scale, false, "%s", error);
49 *x -= margin;
50 return surface_height;
51}
52
53static uint32_t render_status_line_text(cairo_t *cairo,
54 struct swaybar_output *output, struct swaybar_config *config,
55 const char *text, bool focused, double *x, uint32_t surface_height) {
56 if (!text) {
57 return 0;
58 }
59
60 uint32_t height = surface_height * output->scale;
61
62 cairo_set_source_u32(cairo, focused ?
63 config->colors.focused_statusline : config->colors.statusline);
32 64
65 int text_width, text_height;
66 get_text_size(cairo, config->font, &text_width, &text_height,
67 output->scale, config->pango_markup, "%s", text);
68
69 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
70 int margin = 3 * output->scale;
71
72 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
73 uint32_t ideal_surface_height = ideal_height / output->scale;
74 if (surface_height < ideal_surface_height) {
75 return ideal_surface_height;
76 }
77
78 *x -= text_width + margin;
79 double text_y = height / 2.0 - text_height / 2.0;
80 cairo_move_to(cairo, *x, (int)floor(text_y));
81 pango_printf(cairo, config->font, output->scale,
82 config->pango_markup, "%s", text);
83 *x -= margin;
84 return surface_height;
85}
86
87static void render_sharp_line(cairo_t *cairo, uint32_t color,
88 double x, double y, double width, double height) {
89 cairo_set_source_u32(cairo, color);
33 if (width > 1 && height > 1) { 90 if (width > 1 && height > 1) {
34 cairo_rectangle(cairo, x, y, width, height); 91 cairo_rectangle(cairo, x, y, width, height);
35 cairo_fill(cairo); 92 cairo_fill(cairo);
@@ -39,13 +96,11 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color, double x, double y
39 height += y; 96 height += y;
40 width = x; 97 width = x;
41 } 98 }
42
43 if (height == 1) { 99 if (height == 1) {
44 y += 0.5; 100 y += 0.5;
45 width += x; 101 width += x;
46 height = y; 102 height = y;
47 } 103 }
48
49 cairo_move_to(cairo, x, y); 104 cairo_move_to(cairo, x, y);
50 cairo_set_line_width(cairo, 1.0); 105 cairo_set_line_width(cairo, 1.0);
51 cairo_line_to(cairo, width, height); 106 cairo_line_to(cairo, width, height);
@@ -53,176 +108,256 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color, double x, double y
53 } 108 }
54} 109}
55 110
56static void render_block(struct window *window, struct config *config, struct status_block *block, double *x, bool edge, bool is_focused) { 111static void block_hotspot_callback(struct swaybar_output *output,
57 int width, height, sep_width; 112 int x, int y, uint32_t button, void *data) {
58 get_text_size(window->cairo, window->font, &width, &height, 113 struct i3bar_block *block = data;
59 window->scale, block->markup, "%s", block->full_text); 114 struct status_line *status = output->bar->status;
115 i3bar_block_send_click(status, block, x, y, button);
116}
60 117
61 int textwidth = width; 118static uint32_t render_status_block(cairo_t *cairo,
62 double block_width = width; 119 struct swaybar_config *config, struct swaybar_output *output,
120 struct i3bar_block *block, double *x,
121 uint32_t surface_height, bool focused, bool edge) {
122 if (!block->full_text || !*block->full_text) {
123 return 0;
124 }
63 125
126 uint32_t height = surface_height * output->scale;
127
128 int text_width, text_height;
129 get_text_size(cairo, config->font, &text_width, &text_height,
130 output->scale, block->markup, "%s", block->full_text);
131
132 int margin = 3 * output->scale;
133 int ws_vertical_padding = WS_VERTICAL_PADDING * 2;
134
135 int width = text_width;
64 if (width < block->min_width) { 136 if (width < block->min_width) {
65 width = block->min_width; 137 width = block->min_width;
66 } 138 }
67 139
68 *x -= width; 140 double block_width = width;
141 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
142 uint32_t ideal_surface_height = ideal_height / output->scale;
143 if (surface_height < ideal_surface_height) {
144 return ideal_surface_height;
145 }
69 146
70 if (block->border != 0 && block->border_left > 0) { 147 *x -= width;
148 if (block->border && block->border_left > 0) {
71 *x -= (block->border_left + margin); 149 *x -= (block->border_left + margin);
72 block_width += block->border_left + margin; 150 block_width += block->border_left + margin;
73 } 151 }
74 152 if (block->border && block->border_right > 0) {
75 if (block->border != 0 && block->border_right > 0) {
76 *x -= (block->border_right + margin); 153 *x -= (block->border_right + margin);
77 block_width += block->border_right + margin; 154 block_width += block->border_right + margin;
78 } 155 }
79 156
80 // Add separator 157 int sep_width, sep_height;
81 if (!edge) { 158 if (!edge) {
82 if (config->sep_symbol) { 159 if (config->sep_symbol) {
83 get_text_size(window->cairo, window->font, &sep_width, &height, 160 get_text_size(cairo, config->font, &sep_width, &sep_height,
84 window->scale, false, "%s", config->sep_symbol); 161 output->scale, false, "%s", config->sep_symbol);
162 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2;
163 uint32_t _ideal_surface_height = _ideal_height / output->scale;
164 if (surface_height < _ideal_surface_height) {
165 return _ideal_surface_height;
166 }
85 if (sep_width > block->separator_block_width) { 167 if (sep_width > block->separator_block_width) {
86 block->separator_block_width = sep_width + margin * 2; 168 block->separator_block_width = sep_width + margin * 2;
87 } 169 }
88 } 170 }
89
90 *x -= block->separator_block_width; 171 *x -= block->separator_block_width;
91 } else { 172 } else {
92 *x -= margin; 173 *x -= margin;
93 } 174 }
94 175
95 double pos = *x; 176 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
96 177 hotspot->x = *x;
97 block->x = (int)pos; 178 hotspot->y = 0;
98 block->width = (int)block_width; 179 hotspot->width = width;
180 hotspot->height = height;
181 hotspot->callback = block_hotspot_callback;
182 hotspot->destroy = NULL;
183 hotspot->data = block;
184 wl_list_insert(&output->hotspots, &hotspot->link);
99 185
100 // render background 186 double pos = *x;
101 if (block->background != 0x0) { 187 if (block->background) {
102 cairo_set_source_u32(window->cairo, block->background); 188 cairo_set_source_u32(cairo, block->background);
103 cairo_rectangle(window->cairo, pos - 0.5, 1, block_width, (window->height * window->scale) - 2); 189 cairo_rectangle(cairo, pos - 0.5 * output->scale,
104 cairo_fill(window->cairo); 190 output->scale, block_width, height);
191 cairo_fill(cairo);
105 } 192 }
106 193
107 // render top border 194 if (block->border && block->border_top > 0) {
108 if (block->border != 0 && block->border_top > 0) { 195 render_sharp_line(cairo, block->border,
109 render_sharp_line(window->cairo, block->border, 196 pos - 0.5 * output->scale, output->scale,
110 pos - 0.5, 197 block_width, block->border_top);
111 1,
112 block_width,
113 block->border_top);
114 } 198 }
115 199 if (block->border && block->border_bottom > 0) {
116 // render bottom border 200 render_sharp_line(cairo, block->border,
117 if (block->border != 0 && block->border_bottom > 0) { 201 pos - 0.5 * output->scale,
118 render_sharp_line(window->cairo, block->border, 202 height - output->scale - block->border_bottom,
119 pos - 0.5, 203 block_width, block->border_bottom);
120 (window->height * window->scale) - 1 - block->border_bottom,
121 block_width,
122 block->border_bottom);
123 } 204 }
124
125 // render left border
126 if (block->border != 0 && block->border_left > 0) { 205 if (block->border != 0 && block->border_left > 0) {
127 render_sharp_line(window->cairo, block->border, 206 render_sharp_line(cairo, block->border,
128 pos - 0.5, 207 pos - 0.5 * output->scale, output->scale,
129 1, 208 block->border_left, height);
130 block->border_left,
131 (window->height * window->scale) - 2);
132
133 pos += block->border_left + margin; 209 pos += block->border_left + margin;
134 } 210 }
135 211
136 // render text
137 double offset = 0; 212 double offset = 0;
138
139 if (strncmp(block->align, "left", 5) == 0) { 213 if (strncmp(block->align, "left", 5) == 0) {
140 offset = pos; 214 offset = pos;
141 } else if (strncmp(block->align, "right", 5) == 0) { 215 } else if (strncmp(block->align, "right", 5) == 0) {
142 offset = pos + width - textwidth; 216 offset = pos + width - text_width;
143 } else if (strncmp(block->align, "center", 6) == 0) { 217 } else if (strncmp(block->align, "center", 6) == 0) {
144 offset = pos + (width - textwidth) / 2; 218 offset = pos + (width - text_width) / 2;
145 } 219 }
146 220 cairo_move_to(cairo, offset, height / 2.0 - text_height / 2.0);
147 cairo_move_to(window->cairo, offset, margin); 221 uint32_t color = block->color ? *block->color : config->colors.statusline;
148 cairo_set_source_u32(window->cairo, block->color); 222 cairo_set_source_u32(cairo, color);
149 pango_printf(window->cairo, window->font, window->scale, 223 pango_printf(cairo, config->font, output->scale,
150 block->markup, "%s", block->full_text); 224 block->markup, "%s", block->full_text);
151
152 pos += width; 225 pos += width;
153 226
154 // render right border 227 if (block->border && block->border_right > 0) {
155 if (block->border != 0 && block->border_right > 0) {
156 pos += margin; 228 pos += margin;
157 229 render_sharp_line(cairo, block->border,
158 render_sharp_line(window->cairo, block->border, 230 pos - 0.5 * output->scale, output->scale,
159 pos - 0.5, 231 block->border_right, height);
160 1,
161 block->border_right,
162 (window->height * window->scale) - 2);
163
164 pos += block->border_right; 232 pos += block->border_right;
165 } 233 }
166 234
167 // render separator
168 if (!edge && block->separator) { 235 if (!edge && block->separator) {
169 if (is_focused) { 236 if (focused) {
170 cairo_set_source_u32(window->cairo, config->colors.focused_separator); 237 cairo_set_source_u32(cairo, config->colors.focused_separator);
171 } else { 238 } else {
172 cairo_set_source_u32(window->cairo, config->colors.separator); 239 cairo_set_source_u32(cairo, config->colors.separator);
173 } 240 }
174 if (config->sep_symbol) { 241 if (config->sep_symbol) {
175 offset = pos + (block->separator_block_width - sep_width) / 2; 242 offset = pos + (block->separator_block_width - sep_width) / 2;
176 cairo_move_to(window->cairo, offset, margin); 243 cairo_move_to(cairo, offset, height / 2.0 - sep_height / 2.0);
177 pango_printf(window->cairo, window->font, window->scale, 244 pango_printf(cairo, config->font, output->scale, false,
178 false, "%s", config->sep_symbol); 245 "%s", config->sep_symbol);
179 } else { 246 } else {
180 cairo_set_line_width(window->cairo, 1); 247 cairo_set_line_width(cairo, 1);
181 cairo_move_to(window->cairo, pos + block->separator_block_width/2, 248 cairo_move_to(cairo,
182 margin); 249 pos + block->separator_block_width / 2, margin);
183 cairo_line_to(window->cairo, pos + block->separator_block_width/2, 250 cairo_line_to(cairo,
184 (window->height * window->scale) - margin); 251 pos + block->separator_block_width / 2, height - margin);
185 cairo_stroke(window->cairo); 252 cairo_stroke(cairo);
186 } 253 }
187 } 254 }
255 return surface_height;
256}
188 257
258static uint32_t render_status_line_i3bar(cairo_t *cairo,
259 struct swaybar_config *config, struct swaybar_output *output,
260 struct status_line *status, bool focused,
261 double *x, uint32_t surface_height) {
262 uint32_t max_height = 0;
263 bool edge = true;
264 struct i3bar_block *block;
265 wl_list_for_each(block, &status->blocks, link) {
266 uint32_t h = render_status_block(cairo, config, output,
267 block, x, surface_height, focused, edge);
268 max_height = h > max_height ? h : max_height;
269 edge = false;
270 }
271 return max_height;
189} 272}
190 273
191static const char *strip_workspace_name(bool strip_num, const char *ws_name) { 274static uint32_t render_status_line(cairo_t *cairo,
192 bool strip = false; 275 struct swaybar_config *config, struct swaybar_output *output,
193 int i; 276 struct status_line *status, bool focused,
194 277 double *x, uint32_t surface_height) {
195 if (strip_num) { 278 switch (status->protocol) {
196 int len = strlen(ws_name); 279 case PROTOCOL_ERROR:
197 for (i = 0; i < len; ++i) { 280 return render_status_line_error(cairo, output, config,
198 if (!('0' <= ws_name[i] && ws_name[i] <= '9')) { 281 status->text, x, surface_height);
199 if (':' == ws_name[i] && i < len-1 && i > 0) { 282 case PROTOCOL_TEXT:
200 strip = true; 283 return render_status_line_text(cairo, output, config,
201 ++i; 284 status->text, focused, x, surface_height);
202 } 285 case PROTOCOL_I3BAR:
203 break; 286 return render_status_line_i3bar(cairo, config, output,
204 } 287 status, focused, x, surface_height);
205 } 288 case PROTOCOL_UNDEF:
289 return 0;
206 } 290 }
291 return 0;
292}
293
294static uint32_t render_binding_mode_indicator(cairo_t *cairo,
295 struct swaybar_output *output, struct swaybar_config *config,
296 const char *mode, double x, uint32_t surface_height) {
297 uint32_t height = surface_height * output->scale;
207 298
208 if (strip) { 299 int text_width, text_height;
209 return ws_name + i; 300 get_text_size(cairo, config->font, &text_width, &text_height,
301 output->scale, true, "%s", mode);
302
303 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
304 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale;
305 int border_width = BORDER_WIDTH * output->scale;
306
307 uint32_t ideal_height = text_height + ws_vertical_padding * 2
308 + border_width * 2;
309 uint32_t ideal_surface_height = ideal_height / output->scale;
310 if (surface_height < ideal_surface_height) {
311 return ideal_surface_height;
210 } 312 }
313 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
314
315 cairo_set_source_u32(cairo, config->colors.binding_mode.background);
316 cairo_rectangle(cairo, x, 0, width, height);
317 cairo_fill(cairo);
318
319 cairo_set_source_u32(cairo, config->colors.binding_mode.border);
320 cairo_rectangle(cairo, x, 0, width, border_width);
321 cairo_fill(cairo);
322 cairo_rectangle(cairo, x, 0, border_width, height);
323 cairo_fill(cairo);
324 cairo_rectangle(cairo, x + width - border_width, 0, border_width, height);
325 cairo_fill(cairo);
326 cairo_rectangle(cairo, x, height - border_width, width, border_width);
327 cairo_fill(cairo);
328
329 double text_y = height / 2.0 - text_height / 2.0;
330 cairo_set_source_u32(cairo, config->colors.binding_mode.text);
331 cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y));
332 pango_printf(cairo, config->font, output->scale, true, "%s", mode);
333 return surface_height;
334}
211 335
336static const char *strip_workspace_number(const char *ws_name) {
337 size_t len = strlen(ws_name);
338 for (size_t i = 0; i < len; ++i) {
339 if (ws_name[i] < '0' || ws_name[i] > '9') {
340 if (':' == ws_name[i] && i < len - 1 && i > 0) {
341 return ws_name + i + 1;
342 }
343 return ws_name;
344 }
345 }
212 return ws_name; 346 return ws_name;
213} 347}
214 348
215void workspace_button_size(struct window *window, const char *workspace_name, int *width, int *height) { 349static void workspace_hotspot_callback(struct swaybar_output *output,
216 const char *stripped_name = strip_workspace_name(swaybar.config->strip_workspace_numbers, workspace_name); 350 int x, int y, uint32_t button, void *data) {
217 351 ipc_send_workspace_command(output->bar, (const char *)data);
218 get_text_size(window->cairo, window->font, width, height,
219 window->scale, true, "%s", stripped_name);
220 *width += 2 * ws_horizontal_padding;
221 *height += 2 * ws_vertical_padding;
222} 352}
223 353
224static void render_workspace_button(struct window *window, struct config *config, struct workspace *ws, double *x) { 354static uint32_t render_workspace_button(cairo_t *cairo,
225 const char *stripped_name = strip_workspace_name(config->strip_workspace_numbers, ws->name); 355 struct swaybar_output *output, struct swaybar_config *config,
356 struct swaybar_workspace *ws, double *x, uint32_t surface_height) {
357 const char *name = ws->name;
358 if (config->strip_workspace_numbers) {
359 name = strip_workspace_number(ws->name);
360 }
226 361
227 struct box_colors box_colors; 362 struct box_colors box_colors;
228 if (ws->urgent) { 363 if (ws->urgent) {
@@ -235,133 +370,154 @@ static void render_workspace_button(struct window *window, struct config *config
235 box_colors = config->colors.inactive_workspace; 370 box_colors = config->colors.inactive_workspace;
236 } 371 }
237 372
238 int width, height; 373 uint32_t height = surface_height * output->scale;
239 workspace_button_size(window, stripped_name, &width, &height);
240 374
241 // background 375 int text_width, text_height;
242 cairo_set_source_u32(window->cairo, box_colors.background); 376 get_text_size(cairo, config->font, &text_width, &text_height,
243 cairo_rectangle(window->cairo, *x, 1.5, width - 1, height); 377 output->scale, true, "%s", name);
244 cairo_fill(window->cairo); 378
245 379 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
246 // border 380 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale;
247 cairo_set_source_u32(window->cairo, box_colors.border); 381 int border_width = BORDER_WIDTH * output->scale;
248 cairo_rectangle(window->cairo, *x, 1.5, width - 1, height); 382
249 cairo_stroke(window->cairo); 383 uint32_t ideal_height = ws_vertical_padding * 2 + text_height
250 384 + border_width * 2;
251 // text 385 uint32_t ideal_surface_height = ideal_height / output->scale;
252 cairo_set_source_u32(window->cairo, box_colors.text); 386 if (surface_height < ideal_surface_height) {
253 cairo_move_to(window->cairo, (int)*x + ws_horizontal_padding, margin); 387 return ideal_surface_height;
254 pango_printf(window->cairo, window->font, window->scale, 388 }
255 true, "%s", stripped_name);
256
257 *x += width + ws_spacing;
258}
259 389
260static void render_binding_mode_indicator(struct window *window, struct config *config, double pos) { 390 uint32_t width = ws_horizontal_padding * 2 + text_width + border_width * 2;
261 int width, height; 391
262 get_text_size(window->cairo, window->font, &width, &height, 392 cairo_set_source_u32(cairo, box_colors.background);
263 window->scale, false, "%s", config->mode); 393 cairo_rectangle(cairo, *x, 0, width, height);
264 394 cairo_fill(cairo);
265 // background 395
266 cairo_set_source_u32(window->cairo, config->colors.binding_mode.background); 396 cairo_set_source_u32(cairo, box_colors.border);
267 cairo_rectangle(window->cairo, pos, 1.5, width + ws_horizontal_padding * 2 - 1, 397 cairo_rectangle(cairo, *x, 0, width, border_width);
268 height + ws_vertical_padding * 2); 398 cairo_fill(cairo);
269 cairo_fill(window->cairo); 399 cairo_rectangle(cairo, *x, 0, border_width, height);
270 400 cairo_fill(cairo);
271 // border 401 cairo_rectangle(cairo, *x + width - border_width, 0, border_width, height);
272 cairo_set_source_u32(window->cairo, config->colors.binding_mode.border); 402 cairo_fill(cairo);
273 cairo_rectangle(window->cairo, pos, 1.5, width + ws_horizontal_padding * 2 - 1, 403 cairo_rectangle(cairo, *x, height - border_width, width, border_width);
274 height + ws_vertical_padding * 2); 404 cairo_fill(cairo);
275 cairo_stroke(window->cairo); 405
276 406 double text_y = height / 2.0 - text_height / 2.0;
277 // text 407 cairo_set_source_u32(cairo, box_colors.text);
278 cairo_set_source_u32(window->cairo, config->colors.binding_mode.text); 408 cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y));
279 cairo_move_to(window->cairo, (int)pos + ws_horizontal_padding, margin); 409 pango_printf(cairo, config->font, output->scale, true, "%s", name);
280 pango_printf(window->cairo, window->font, window->scale, 410
281 false, "%s", config->mode); 411 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
412 hotspot->x = *x;
413 hotspot->y = 0;
414 hotspot->width = width;
415 hotspot->height = height;
416 hotspot->callback = workspace_hotspot_callback;
417 hotspot->destroy = free;
418 hotspot->data = strdup(ws->name);
419 wl_list_insert(&output->hotspots, &hotspot->link);
420
421 *x += width;
422 return surface_height;
282} 423}
283 424
284void render(struct output *output, struct config *config, struct status_line *line) { 425static uint32_t render_to_cairo(cairo_t *cairo,
285 int i; 426 struct swaybar *bar, struct swaybar_output *output) {
286 427 struct swaybar_config *config = bar->config;
287 struct window *window = output->window;
288 cairo_t *cairo = window->cairo;
289 bool is_focused = output->focused;
290
291 // Clear
292 cairo_save(cairo);
293 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
294 cairo_paint(cairo);
295 cairo_restore(cairo);
296
297 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); 428 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
298 429 if (output->focused) {
299 // Background
300 if (is_focused) {
301 cairo_set_source_u32(cairo, config->colors.focused_background); 430 cairo_set_source_u32(cairo, config->colors.focused_background);
302 } else { 431 } else {
303 cairo_set_source_u32(cairo, config->colors.background); 432 cairo_set_source_u32(cairo, config->colors.background);
304 } 433 }
305 cairo_paint(cairo); 434 cairo_paint(cairo);
306 435
307#ifdef ENABLE_TRAY 436 uint32_t max_height = 0;
308 uint32_t tray_width = tray_render(output, config); 437 /*
309#else 438 * Each render_* function takes the actual height of the bar, and returns
310 const uint32_t tray_width = window->width * window->scale; 439 * the ideal height. If the actual height is too short, the render function
311#endif 440 * can do whatever it wants - the buffer won't be committed. If the actual
312 441 * height is too tall, the render function should adapt its drawing to
313 // Command output 442 * utilize the available space.
314 if (is_focused) { 443 */
315 cairo_set_source_u32(cairo, config->colors.focused_statusline); 444 double x = output->width * output->scale;
316 } else { 445 if (bar->status) {
317 cairo_set_source_u32(cairo, config->colors.statusline); 446 uint32_t h = render_status_line(cairo, config, output,
318 } 447 bar->status, output->focused, &x, output->height);
319 448 max_height = h > max_height ? h : max_height;
320 int width, height; 449 }
321 450 x = 0;
322 if (line->protocol == TEXT) { 451 if (config->workspace_buttons) {
323 get_text_size(window->cairo, window->font, &width, &height, 452 struct swaybar_workspace *ws;
324 window->scale, config->pango_markup, "%s", line->text_line); 453 wl_list_for_each_reverse(ws, &output->workspaces, link) {
325 cairo_move_to(cairo, tray_width - margin - width, margin); 454 uint32_t h = render_workspace_button(cairo,
326 pango_printf(window->cairo, window->font, window->scale, 455 output, config, ws, &x, output->height);
327 config->pango_markup, "%s", line->text_line); 456 max_height = h > max_height ? h : max_height;
328 } else if (line->protocol == I3BAR && line->block_line) {
329 double pos = tray_width - 0.5;
330 bool edge = true;
331 for (i = line->block_line->length - 1; i >= 0; --i) {
332 struct status_block *block = line->block_line->items[i];
333 if (block->full_text && block->full_text[0]) {
334 render_block(window, config, block, &pos, edge, is_focused);
335 edge = false;
336 }
337 } 457 }
338 } 458 }
459 if (config->binding_mode_indicator && config->mode) {
460 uint32_t h = render_binding_mode_indicator(cairo,
461 output, config, config->mode, x, output->height);
462 max_height = h > max_height ? h : max_height;
463 }
339 464
340 cairo_set_line_width(cairo, 1.0); 465 return max_height > output->height ? max_height : output->height;
341 double x = 0.5; 466}
342 467
343 // Workspaces 468void render_frame(struct swaybar *bar, struct swaybar_output *output) {
344 if (config->workspace_buttons) { 469 struct swaybar_hotspot *hotspot, *tmp;
345 for (i = 0; i < output->workspaces->length; ++i) { 470 wl_list_for_each_safe(hotspot, tmp, &output->hotspots, link) {
346 struct workspace *ws = output->workspaces->items[i]; 471 if (hotspot->destroy) {
347 render_workspace_button(window, config, ws, &x); 472 hotspot->destroy(hotspot->data);
348 } 473 }
474 wl_list_remove(&hotspot->link);
475 free(hotspot);
349 } 476 }
350 477
351 // binding mode indicator 478 cairo_surface_t *recorder = cairo_recording_surface_create(
352 if (config->mode && config->binding_mode_indicator) { 479 CAIRO_CONTENT_COLOR_ALPHA, NULL);
353 render_binding_mode_indicator(window, config, x); 480 cairo_t *cairo = cairo_create(recorder);
481 cairo_save(cairo);
482 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
483 cairo_paint(cairo);
484 cairo_restore(cairo);
485 uint32_t height = render_to_cairo(cairo, bar, output);
486 if (bar->config->height >= 0 && height < (uint32_t)bar->config->height) {
487 height = bar->config->height;
354 } 488 }
355} 489 if (height != output->height) {
356 490 // Reconfigure surface
357void set_window_height(struct window *window, int height) { 491 zwlr_layer_surface_v1_set_size(output->layer_surface, 0, height);
358 int text_width, text_height; 492 zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, height);
359 get_text_size(window->cairo, window->font, 493 // TODO: this could infinite loop if the compositor assigns us a
360 &text_width, &text_height, window->scale, false, 494 // different height than what we asked for
361 "Test string for measuring purposes"); 495 wl_surface_commit(output->surface);
362 if (height > 0) { 496 wl_display_roundtrip(bar->display);
363 margin = (height - text_height) / 2; 497 } else {
364 ws_vertical_padding = margin - 1.5; 498 // Replay recording into shm and send it off
365 } 499 output->current_buffer = get_next_buffer(bar->shm,
366 window->height = (text_height + margin * 2) / window->scale; 500 output->buffers,
501 output->width * output->scale,
502 output->height * output->scale);
503 cairo_t *shm = output->current_buffer->cairo;
504
505 cairo_save(shm);
506 cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR);
507 cairo_paint(shm);
508 cairo_restore(shm);
509
510 cairo_set_source_surface(shm, recorder, 0.0, 0.0);
511 cairo_paint(shm);
512
513 wl_surface_set_buffer_scale(output->surface, output->scale);
514 wl_surface_attach(output->surface,
515 output->current_buffer->buffer, 0, 0);
516 wl_surface_damage(output->surface, 0, 0,
517 output->width, output->height);
518 wl_surface_commit(output->surface);
519 wl_display_roundtrip(bar->display);
520 }
521 cairo_surface_destroy(recorder);
522 cairo_destroy(cairo);
367} 523}
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
index 87e90caf..8afe4707 100644
--- a/swaybar/status_line.c
+++ b/swaybar/status_line.c
@@ -1,530 +1,130 @@
1#define _XOPEN_SOURCE 700 1#define _POSIX_C_SOURCE
2#include <fcntl.h>
3#include <json-c/json.h>
2#include <stdlib.h> 4#include <stdlib.h>
3#include <string.h> 5#include <string.h>
6#include <stdio.h>
4#include <unistd.h> 7#include <unistd.h>
5#include <json-c/json.h> 8#include <wlr/util/log.h>
6
7#include "swaybar/config.h" 9#include "swaybar/config.h"
8#include "swaybar/status_line.h" 10#include "swaybar/status_line.h"
9#include "log.h" 11#include "readline.h"
10#include "util.h"
11
12#define I3JSON_MAXDEPTH 4
13#define I3JSON_UNKNOWN 0
14#define I3JSON_ARRAY 1
15#define I3JSON_STRING 2
16
17struct {
18 int bufsize;
19 char *buffer;
20 char *line_start;
21 char *parserpos;
22 bool escape;
23 int depth;
24 int bar[I3JSON_MAXDEPTH+1];
25} i3json_state = { 0, NULL, NULL, NULL, false, 0, { I3JSON_UNKNOWN } };
26
27static char line[1024];
28static char line_rest[1024];
29
30static char event_buff[1024];
31
32static void free_status_block(void *item) {
33 if (!item) {
34 return;
35 }
36 struct status_block *sb = (struct status_block*)item;
37 if (sb->full_text) {
38 free(sb->full_text);
39 }
40 if (sb->short_text) {
41 free(sb->short_text);
42 }
43 if (sb->align) {
44 free(sb->align);
45 }
46 if (sb->name) {
47 free(sb->name);
48 }
49 if (sb->instance) {
50 free(sb->instance);
51 }
52 free(sb);
53}
54
55static void parse_json(struct bar *bar, const char *text) {
56 json_object *results = json_tokener_parse(text);
57 if (!results) {
58 sway_log(L_DEBUG, "Failed to parse json");
59 return;
60 }
61
62 if (json_object_array_length(results) < 1) {
63 return;
64 }
65
66 if (bar->status->block_line) {
67 list_foreach(bar->status->block_line, free_status_block);
68 list_free(bar->status->block_line);
69 }
70
71 bar->status->block_line = create_list();
72
73 int i;
74 for (i = 0; i < json_object_array_length(results); ++i) {
75 json_object *full_text, *short_text, *color, *min_width, *align, *urgent;
76 json_object *name, *instance, *separator, *separator_block_width;
77 json_object *background, *border, *border_top, *border_bottom;
78 json_object *border_left, *border_right, *markup;
79
80 json_object *json = json_object_array_get_idx(results, i);
81 if (!json) {
82 continue;
83 }
84
85 json_object_object_get_ex(json, "full_text", &full_text);
86 json_object_object_get_ex(json, "short_text", &short_text);
87 json_object_object_get_ex(json, "color", &color);
88 json_object_object_get_ex(json, "min_width", &min_width);
89 json_object_object_get_ex(json, "align", &align);
90 json_object_object_get_ex(json, "urgent", &urgent);
91 json_object_object_get_ex(json, "name", &name);
92 json_object_object_get_ex(json, "instance", &instance);
93 json_object_object_get_ex(json, "markup", &markup);
94 json_object_object_get_ex(json, "separator", &separator);
95 json_object_object_get_ex(json, "separator_block_width", &separator_block_width);
96 json_object_object_get_ex(json, "background", &background);
97 json_object_object_get_ex(json, "border", &border);
98 json_object_object_get_ex(json, "border_top", &border_top);
99 json_object_object_get_ex(json, "border_bottom", &border_bottom);
100 json_object_object_get_ex(json, "border_left", &border_left);
101 json_object_object_get_ex(json, "border_right", &border_right);
102
103 struct status_block *new = calloc(1, sizeof(struct status_block));
104
105 if (full_text) {
106 new->full_text = strdup(json_object_get_string(full_text));
107 }
108
109 if (short_text) {
110 new->short_text = strdup(json_object_get_string(short_text));
111 }
112
113 if (color) {
114 new->color = parse_color(json_object_get_string(color));
115 } else {
116 new->color = bar->config->colors.statusline;
117 }
118
119 if (min_width) {
120 json_type type = json_object_get_type(min_width);
121 if (type == json_type_int) {
122 new->min_width = json_object_get_int(min_width);
123 } else if (type == json_type_string) {
124 /* the width will be calculated when rendering */
125 new->min_width = 0;
126 }
127 }
128
129 if (align) {
130 new->align = strdup(json_object_get_string(align));
131 } else {
132 new->align = strdup("left");
133 }
134
135 if (urgent) {
136 new->urgent = json_object_get_int(urgent);
137 }
138 12
139 if (name) { 13void status_error(struct status_line *status, const char *text) {
140 new->name = strdup(json_object_get_string(name)); 14 close(status->read_fd);
141 } 15 close(status->write_fd);
142 16 status->protocol = PROTOCOL_ERROR;
143 if (instance) { 17 status->text = text;
144 new->instance = strdup(json_object_get_string(instance));
145 }
146
147 if (markup) {
148 new->markup = false;
149 const char *markup_str = json_object_get_string(markup);
150 if (strcmp(markup_str, "pango") == 0) {
151 new->markup = true;
152 }
153 }
154
155 if (separator) {
156 new->separator = json_object_get_int(separator);
157 } else {
158 new->separator = true; // i3bar spec
159 }
160
161 if (separator_block_width) {
162 new->separator_block_width = json_object_get_int(separator_block_width);
163 } else {
164 new->separator_block_width = 9; // i3bar spec
165 }
166
167 // Airblader features
168 if (background) {
169 new->background = parse_color(json_object_get_string(background));
170 } else {
171 new->background = 0x0; // transparent
172 }
173
174 if (border) {
175 new->border = parse_color(json_object_get_string(border));
176 } else {
177 new->border = 0x0; // transparent
178 }
179
180 if (border_top) {
181 new->border_top = json_object_get_int(border_top);
182 } else {
183 new->border_top = 1;
184 }
185
186 if (border_bottom) {
187 new->border_bottom = json_object_get_int(border_bottom);
188 } else {
189 new->border_bottom = 1;
190 }
191
192 if (border_left) {
193 new->border_left = json_object_get_int(border_left);
194 } else {
195 new->border_left = 1;
196 }
197
198 if (border_right) {
199 new->border_right = json_object_get_int(border_right);
200 } else {
201 new->border_right = 1;
202 }
203
204 list_add(bar->status->block_line, new);
205 }
206
207 json_object_put(results);
208} 18}
209 19
210// continue parsing from last parserpos 20bool status_handle_readable(struct status_line *status) {
211static int i3json_parse(struct bar *bar) { 21 char *line;
212 char *c = i3json_state.parserpos; 22 switch (status->protocol) {
213 int handled = 0; 23 case PROTOCOL_ERROR:
214 while (*c) {
215 if (i3json_state.bar[i3json_state.depth] == I3JSON_STRING) {
216 if (!i3json_state.escape && *c == '"') {
217 --i3json_state.depth;
218 }
219 i3json_state.escape = !i3json_state.escape && *c == '\\';
220 } else {
221 switch (*c) {
222 case '[':
223 ++i3json_state.depth;
224 if (i3json_state.depth > I3JSON_MAXDEPTH) {
225 sway_abort("JSON too deep");
226 }
227 i3json_state.bar[i3json_state.depth] = I3JSON_ARRAY;
228 if (i3json_state.depth == 2) {
229 i3json_state.line_start = c;
230 }
231 break;
232 case ']':
233 if (i3json_state.bar[i3json_state.depth] != I3JSON_ARRAY) {
234 sway_abort("JSON malformed");
235 }
236 --i3json_state.depth;
237 if (i3json_state.depth == 1) {
238 // c[1] is valid since c[0] != '\0'
239 char p = c[1];
240 c[1] = '\0';
241 parse_json(bar, i3json_state.line_start);
242 c[1] = p;
243 ++handled;
244 i3json_state.line_start = c+1;
245 }
246 break;
247 case '"':
248 ++i3json_state.depth;
249 if (i3json_state.depth > I3JSON_MAXDEPTH) {
250 sway_abort("JSON too deep");
251 }
252 i3json_state.bar[i3json_state.depth] = I3JSON_STRING;
253 break;
254 }
255 }
256 ++c;
257 }
258 i3json_state.parserpos = c;
259 return handled;
260}
261
262// Read line from file descriptor, only show the line tail if it is too long.
263// In non-blocking mode treat "no more data" as a linebreak.
264// If data after a line break has been read, return it in rest.
265// If rest is non-empty, then use that as the start of the next line.
266static int read_line_tail(int fd, char *buf, int nbyte, char *rest) {
267 if (fd < 0 || !buf || !nbyte) {
268 return -1;
269 }
270 int l;
271 char *buffer = malloc(nbyte*2+1);
272 char *readpos = buffer;
273 char *lf;
274 // prepend old data to new line if necessary
275 if (rest) {
276 l = strlen(rest);
277 if (l > nbyte) {
278 strcpy(buffer, rest + l - nbyte);
279 readpos += nbyte;
280 } else if (l) {
281 strcpy(buffer, rest);
282 readpos += l;
283 }
284 }
285 // read until a linefeed is found or no more data is available
286 while ((l = read(fd, readpos, nbyte)) > 0) {
287 readpos[l] = '\0';
288 lf = strchr(readpos, '\n');
289 if (lf) {
290 // linefeed found, replace with \0
291 *lf = '\0';
292 // give data from the end of the line, try to fill the buffer
293 if (lf-buffer > nbyte) {
294 strcpy(buf, lf - nbyte + 1);
295 } else {
296 strcpy(buf, buffer);
297 }
298 // we may have read data from the next line, save it to rest
299 if (rest) {
300 rest[0] = '\0';
301 strcpy(rest, lf + 1);
302 }
303 free(buffer);
304 return strlen(buf);
305 } else {
306 // no linefeed found, slide data back.
307 int overflow = readpos - buffer + l - nbyte;
308 if (overflow > 0) {
309 memmove(buffer, buffer + overflow , nbyte + 1);
310 }
311 }
312 }
313 if (l < 0) {
314 free(buffer);
315 return l;
316 }
317 readpos[l]='\0';
318 if (rest) {
319 rest[0] = '\0';
320 }
321 if (nbyte < readpos - buffer + l - 1) {
322 memcpy(buf, readpos - nbyte + l + 1, nbyte);
323 } else {
324 strncpy(buf, buffer, nbyte);
325 }
326 buf[nbyte-1] = '\0';
327 free(buffer);
328 return strlen(buf);
329}
330
331// make sure that enough buffer space is available starting from parserpos
332static void i3json_ensure_free(int min_free) {
333 int _step = 10240;
334 int r = min_free % _step;
335 if (r) {
336 min_free += _step - r;
337 }
338 if (!i3json_state.buffer) {
339 i3json_state.buffer = malloc(min_free);
340 i3json_state.bufsize = min_free;
341 i3json_state.parserpos = i3json_state.buffer;
342 } else {
343 int len = 0;
344 int pos = 0;
345 if (i3json_state.line_start) {
346 len = strlen(i3json_state.line_start);
347 pos = i3json_state.parserpos - i3json_state.line_start;
348 if (i3json_state.line_start != i3json_state.buffer) {
349 memmove(i3json_state.buffer, i3json_state.line_start, len+1);
350 }
351 } else {
352 len = strlen(i3json_state.buffer);
353 }
354 if (i3json_state.bufsize < len+min_free) {
355 i3json_state.bufsize += min_free;
356 if (i3json_state.bufsize > 1024000) {
357 sway_abort("Status line json too long or malformed.");
358 }
359 i3json_state.buffer = realloc(i3json_state.buffer, i3json_state.bufsize);
360 if (!i3json_state.buffer) {
361 sway_abort("Could not allocate json buffer");
362 }
363 }
364 if (i3json_state.line_start) {
365 i3json_state.line_start = i3json_state.buffer;
366 i3json_state.parserpos = i3json_state.buffer + pos;
367 } else {
368 i3json_state.parserpos = i3json_state.buffer;
369 }
370 }
371 if (!i3json_state.buffer) {
372 sway_abort("Could not allocate buffer.");
373 }
374}
375
376// append data and parse it.
377static int i3json_handle_data(struct bar *bar, char *data) {
378 int len = strlen(data);
379 i3json_ensure_free(len);
380 strcpy(i3json_state.parserpos, data);
381 return i3json_parse(bar);
382}
383
384// read data from fd and parse it.
385static int i3json_handle_fd(struct bar *bar) {
386 i3json_ensure_free(10240);
387 // get fresh data at the end of the buffer
388 int readlen = read(bar->status_read_fd, i3json_state.parserpos, 10239);
389 if (readlen < 0) {
390 return readlen;
391 }
392 i3json_state.parserpos[readlen] = '\0';
393 return i3json_parse(bar);
394}
395
396bool status_line_mouse_event(struct bar *bar, int x, int y, uint32_t button) {
397 sway_log(L_DEBUG, "status_line_mouse_event.");
398 if (!bar->status->click_events) {
399 sway_log(L_DEBUG, "click_events are not enabled.");
400 return false; 24 return false;
401 } 25 case PROTOCOL_I3BAR:
402 26 if (i3bar_handle_readable(status) > 0) {
403 if (bar->status->protocol == I3BAR) {
404 sway_log(L_DEBUG, "Sending click event.");
405
406 // find clicked block
407 struct status_block *clicked_block = NULL;
408 struct status_block *current_block = NULL;
409 int num_blocks = bar->status->block_line->length;
410
411 if (num_blocks == 0) {
412 return false;
413 } else {
414 current_block = bar->status->block_line->items[0];
415 if (x < current_block->x) {
416 return false;
417 }
418 }
419
420 for (int i = 0; i < num_blocks; i++) {
421 current_block = bar->status->block_line->items[i];
422 if (x < (current_block->x + current_block->width)) {
423 clicked_block = current_block;
424 break;
425 }
426 }
427
428 if (!clicked_block || !clicked_block->name) {
429 return false;
430 }
431
432 // event example {"name":"capture","instance":"label","button":1,"x":3431,"y":18}
433
434 struct json_object *event_json = json_object_new_object();
435 json_object_object_add(event_json, "name", json_object_new_string(clicked_block->name));
436 if (clicked_block->instance) {
437 json_object_object_add(event_json, "instance", json_object_new_string(clicked_block->instance));
438 }
439 json_object_object_add(event_json, "button", json_object_new_int(button));
440 json_object_object_add(event_json, "x", json_object_new_int(x));
441 json_object_object_add(event_json, "y", json_object_new_int(y));
442
443 int len = snprintf(event_buff, sizeof(event_buff), "%s\n", json_object_to_json_string(event_json));
444
445 json_object_put(event_json);
446
447 if (len <= (int)sizeof(event_buff)) { // if not truncated
448 write(bar->status_write_fd, event_buff, len);
449 return true; 27 return true;
450 } 28 }
451 }
452
453 return false;
454}
455
456bool handle_status_line(struct bar *bar) {
457 bool dirty = false;
458
459 switch (bar->status->protocol) {
460 case I3BAR:
461 sway_log(L_DEBUG, "Got i3bar protocol.");
462 if (i3json_handle_fd(bar) > 0) {
463 dirty = true;
464 }
465 break; 29 break;
466 case TEXT: 30 case PROTOCOL_TEXT:
467 sway_log(L_DEBUG, "Got text protocol."); 31 line = read_line_buffer(status->read,
468 read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest); 32 status->text_state.buffer, status->text_state.buffer_size);
469 dirty = true; 33 if (!line) {
470 bar->status->text_line = line; 34 status_error(status, "[error reading from status command]");
471 break; 35 } else {
472 case UNDEF: 36 status->text = line;
473 sway_log(L_DEBUG, "Detecting protocol..."); 37 }
474 if (read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest) < 0) { 38 return true;
475 break; 39 case PROTOCOL_UNDEF:
40 line = read_line_buffer(status->read,
41 status->text_state.buffer, status->text_state.buffer_size);
42 if (!line) {
43 status_error(status, "[error reading from status command]");
44 return false;
476 } 45 }
477 dirty = true;
478 bar->status->text_line = line;
479 bar->status->protocol = TEXT;
480 if (line[0] == '{') { 46 if (line[0] == '{') {
481 // detect i3bar json protocol
482 json_object *proto = json_tokener_parse(line); 47 json_object *proto = json_tokener_parse(line);
483 if (proto) { 48 if (proto) {
484
485 json_object *version; 49 json_object *version;
486 if (json_object_object_get_ex(proto, "version", &version) 50 if (json_object_object_get_ex(proto, "version", &version)
487 && json_object_get_int(version) == 1 51 && json_object_get_int(version) == 1) {
488 ) { 52 wlr_log(L_DEBUG, "Switched to i3bar protocol.");
489 sway_log(L_DEBUG, "Switched to i3bar protocol."); 53 status->protocol = PROTOCOL_I3BAR;
490 bar->status->protocol = I3BAR;
491 } 54 }
492
493 json_object *click_events; 55 json_object *click_events;
494 if (json_object_object_get_ex(proto, "click_events", &click_events) 56 if (json_object_object_get_ex(
57 proto, "click_events", &click_events)
495 && json_object_get_boolean(click_events)) { 58 && json_object_get_boolean(click_events)) {
496 59 wlr_log(L_DEBUG, "Enabled click events.");
497 sway_log(L_DEBUG, "Enabling click events."); 60 status->i3bar_state.click_events = true;
498 bar->status->click_events = true;
499
500 const char *events_array = "[\n"; 61 const char *events_array = "[\n";
501 write(bar->status_write_fd, events_array, strlen(events_array)); 62 ssize_t len = strlen(events_array);
63 if (write(status->write_fd, events_array, len) != len) {
64 status_error(status,
65 "[failed to write to status command]");
66 }
502 } 67 }
503
504 i3json_handle_data(bar, line_rest);
505
506 json_object_put(proto); 68 json_object_put(proto);
507 } 69 }
70
71 status->protocol = PROTOCOL_I3BAR;
72 free(status->text_state.buffer);
73 wl_list_init(&status->blocks);
74 status->i3bar_state.buffer_size = 4096;
75 status->i3bar_state.buffer =
76 malloc(status->i3bar_state.buffer_size);
77 } else {
78 status->protocol = PROTOCOL_TEXT;
79 status->text = line;
508 } 80 }
509 break; 81 return true;
510 } 82 }
511 83 return false;
512 return dirty;
513} 84}
514 85
515struct status_line *init_status_line() { 86struct status_line *status_line_init(char *cmd) {
516 struct status_line *line = malloc(sizeof(struct status_line)); 87 struct status_line *status = calloc(1, sizeof(struct status_line));
517 line->block_line = create_list(); 88 status->text_state.buffer_size = 8192;
518 line->text_line = NULL; 89 status->text_state.buffer = malloc(status->text_state.buffer_size);
519 line->protocol = UNDEF;
520 line->click_events = false;
521 90
522 return line; 91 int pipe_read_fd[2];
523} 92 int pipe_write_fd[2];
93 if (pipe(pipe_read_fd) != 0 || pipe(pipe_write_fd) != 0) {
94 wlr_log(L_ERROR, "Unable to create pipes for status_command fork");
95 exit(1);
96 }
97
98 status->pid = fork();
99 if (status->pid == 0) {
100 dup2(pipe_read_fd[1], STDOUT_FILENO);
101 close(pipe_read_fd[0]);
102 close(pipe_read_fd[1]);
524 103
525void free_status_line(struct status_line *line) { 104 dup2(pipe_write_fd[0], STDIN_FILENO);
526 if (line->block_line) { 105 close(pipe_write_fd[0]);
527 list_foreach(line->block_line, free_status_block); 106 close(pipe_write_fd[1]);
528 list_free(line->block_line); 107
108 char *const _cmd[] = { "sh", "-c", cmd, NULL, };
109 execvp(_cmd[0], _cmd);
110 exit(1);
529 } 111 }
112
113 close(pipe_read_fd[1]);
114 status->read_fd = pipe_read_fd[0];
115 fcntl(status->read_fd, F_SETFL, O_NONBLOCK);
116 close(pipe_write_fd[0]);
117 status->write_fd = pipe_write_fd[1];
118 fcntl(status->write_fd, F_SETFL, O_NONBLOCK);
119
120 status->read = fdopen(status->read_fd, "r");
121 status->write = fdopen(status->write_fd, "w");
122 return status;
123}
124
125void status_line_free(struct status_line *status) {
126 close(status->read_fd);
127 close(status->write_fd);
128 kill(status->pid, SIGTERM);
129 free(status);
530} 130}
diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c
deleted file mode 100644
index 8e719fd9..00000000
--- a/swaybar/tray/dbus.c
+++ /dev/null
@@ -1,197 +0,0 @@
1#define _XOPEN_SOURCE 700
2#include <stdio.h>
3#include <stdlib.h>
4#include <stdint.h>
5#include <stdbool.h>
6#include <poll.h>
7#include <signal.h>
8#include <time.h>
9#include <dbus/dbus.h>
10#include "swaybar/tray/dbus.h"
11#include "swaybar/event_loop.h"
12#include "log.h"
13
14DBusConnection *conn = NULL;
15
16static void dispatch_watch(int fd, short mask, void *data) {
17 sway_log(L_DEBUG, "Dispatching watch");
18 DBusWatch *watch = data;
19
20 if (!dbus_watch_get_enabled(watch)) {
21 return;
22 }
23
24 uint32_t flags = 0;
25
26 if (mask & POLLIN) {
27 flags |= DBUS_WATCH_READABLE;
28 } if (mask & POLLOUT) {
29 flags |= DBUS_WATCH_WRITABLE;
30 } if (mask & POLLHUP) {
31 flags |= DBUS_WATCH_HANGUP;
32 } if (mask & POLLERR) {
33 flags |= DBUS_WATCH_ERROR;
34 }
35
36 dbus_watch_handle(watch, flags);
37}
38
39static dbus_bool_t add_watch(DBusWatch *watch, void *_data) {
40 if (!dbus_watch_get_enabled(watch)) {
41 // Watch should not be polled
42 return TRUE;
43 }
44
45 short mask = 0;
46 uint32_t flags = dbus_watch_get_flags(watch);
47
48 if (flags & DBUS_WATCH_READABLE) {
49 mask |= POLLIN;
50 } if (flags & DBUS_WATCH_WRITABLE) {
51 mask |= POLLOUT;
52 }
53
54 int fd = dbus_watch_get_unix_fd(watch);
55
56 sway_log(L_DEBUG, "Adding DBus watch fd: %d", fd);
57 add_event(fd, mask, dispatch_watch, watch);
58
59 return TRUE;
60}
61
62static void remove_watch(DBusWatch *watch, void *_data) {
63 int fd = dbus_watch_get_unix_fd(watch);
64
65 remove_event(fd);
66}
67
68static void dispatch_timeout(timer_t timer, void *data) {
69 sway_log(L_DEBUG, "Dispatching DBus timeout");
70 DBusTimeout *timeout = data;
71
72 if (dbus_timeout_get_enabled(timeout)) {
73 dbus_timeout_handle(timeout);
74 }
75}
76
77static dbus_bool_t add_timeout(DBusTimeout *timeout, void *_data) {
78 if (!dbus_timeout_get_enabled(timeout)) {
79 return TRUE;
80 }
81
82 timer_t *timer = malloc(sizeof(timer_t));
83 if (!timer) {
84 sway_log(L_ERROR, "Cannot allocate memory");
85 return FALSE;
86 }
87 struct sigevent ev = {
88 .sigev_notify = SIGEV_NONE,
89 };
90
91 if (timer_create(CLOCK_MONOTONIC, &ev, timer)) {
92 sway_log(L_ERROR, "Could not create DBus timer");
93 return FALSE;
94 }
95
96 int interval = dbus_timeout_get_interval(timeout);
97 int interval_sec = interval / 1000;
98 int interval_msec = (interval_sec * 1000) - interval;
99
100 struct timespec period = {
101 (time_t) interval_sec,
102 ((long) interval_msec) * 1000 * 1000,
103 };
104 struct itimerspec time = {
105 period,
106 period,
107 };
108
109 timer_settime(*timer, 0, &time, NULL);
110
111 dbus_timeout_set_data(timeout, timer, NULL);
112
113 sway_log(L_DEBUG, "Adding DBus timeout. Interval: %ds %dms", interval_sec, interval_msec);
114 add_timer(*timer, dispatch_timeout, timeout);
115
116 return TRUE;
117}
118static void remove_timeout(DBusTimeout *timeout, void *_data) {
119 timer_t *timer = (timer_t *) dbus_timeout_get_data(timeout);
120 sway_log(L_DEBUG, "Removing DBus timeout.");
121
122 if (timer) {
123 remove_timer(*timer);
124 timer_delete(*timer);
125 free(timer);
126 }
127}
128
129static bool should_dispatch = true;
130
131static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status,
132 void *_data) {
133 if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
134 should_dispatch = true;
135 }
136}
137
138/* Public functions below */
139
140void dispatch_dbus() {
141 if (!should_dispatch || !conn) {
142 return;
143 }
144
145 DBusDispatchStatus status;
146
147 do {
148 status = dbus_connection_dispatch(conn);
149 } while (status == DBUS_DISPATCH_DATA_REMAINS);
150
151 if (status != DBUS_DISPATCH_COMPLETE) {
152 sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status);
153 }
154
155 should_dispatch = false;
156}
157
158int dbus_init() {
159 DBusError error;
160 dbus_error_init(&error);
161
162 conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
163 if (conn == NULL) {
164 sway_log(L_INFO, "Compiled with dbus support, but unable to connect to dbus");
165 sway_log(L_INFO, "swaybar will be unable to display tray icons.");
166 return -1;
167 }
168
169 dbus_connection_set_exit_on_disconnect(conn, FALSE);
170 if (dbus_error_is_set(&error)) {
171 sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message);
172 conn = NULL;
173 return -1;
174 }
175
176 sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn));
177
178 // Will be called if dispatch status changes
179 dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL);
180
181 if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch,
182 NULL, NULL, NULL)) {
183 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
184 sway_log(L_ERROR, "Failed to activate DBUS watch functions");
185 return -1;
186 }
187
188 if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout,
189 NULL, NULL, NULL)) {
190 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
191 dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL);
192 sway_log(L_ERROR, "Failed to activate DBUS timeout functions");
193 return -1;
194 }
195
196 return 0;
197}
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
deleted file mode 100644
index c146bf32..00000000
--- a/swaybar/tray/icon.c
+++ /dev/null
@@ -1,400 +0,0 @@
1#define _XOPEN_SOURCE 700
2#define _POSIX_C_SOURCE 200809L
3#include <stdio.h>
4#include <stdlib.h>
5#include <unistd.h>
6#include <string.h>
7#include <dirent.h>
8#include <sys/stat.h>
9#include <stdbool.h>
10#include <stdint.h>
11#include <limits.h>
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "swaybar/config.h"
15#include "stringop.h"
16#include "log.h"
17
18/**
19 * REVIEW:
20 * This file repeats lots of "costly" operations that are the same for every
21 * icon. It's possible to create a dictionary or some other structure to cache
22 * these, though it may complicate things somewhat.
23 *
24 * Also parsing (index.theme) is currently pretty messy, so that could be made
25 * much better as well. Over all, things work, but are not optimal.
26 */
27
28/* Finds all themes that the given theme inherits */
29static list_t *find_inherits(const char *theme_dir) {
30 const char inherits[] = "Inherits";
31 const char index_name[] = "index.theme";
32 list_t *themes = create_list();
33 FILE *index = NULL;
34 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
35 if (!path) {
36 goto fail;
37 }
38 if (!themes) {
39 goto fail;
40 }
41
42 strcpy(path, theme_dir);
43 strcat(path, index_name);
44
45 index = fopen(path, "r");
46 if (!index) {
47 goto fail;
48 }
49
50 char *buf = NULL;
51 size_t n = 0;
52 while (!feof(index) && getline(&buf, &n, index) != -1) {
53 if (n <= sizeof(inherits) + 1) {
54 continue;
55 }
56 if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) {
57 char *themestr = buf + sizeof(inherits);
58 themes = split_string(themestr, ",");
59 break;
60 }
61 }
62 free(buf);
63
64fail:
65 free(path);
66 if (index) {
67 fclose(index);
68 }
69 return themes;
70}
71
72static bool isdir(const char *path) {
73 struct stat statbuf;
74 if (stat(path, &statbuf) != -1) {
75 if (S_ISDIR(statbuf.st_mode)) {
76 return true;
77 }
78 }
79 return false;
80
81}
82
83/**
84 * Returns the directory of a given theme if it exists.
85 * The returned pointer must be freed.
86 */
87static char *find_theme_dir(const char *theme) {
88 char *basedir;
89 char *icon_dir;
90
91 if (!theme) {
92 return NULL;
93 }
94
95 if (!(icon_dir = malloc(1024))) {
96 sway_log(L_ERROR, "Out of memory!");
97 goto fail;
98 }
99
100 if ((basedir = getenv("HOME"))) {
101 if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) {
102 sway_log(L_ERROR, "Path too long to render");
103 // XXX perhaps just goto trying in /usr/share? This
104 // shouldn't happen anyway, but might with a long global
105 goto fail;
106 }
107
108 if (isdir(icon_dir)) {
109 return icon_dir;
110 }
111 }
112
113 if ((basedir = getenv("XDG_DATA_DIRS"))) {
114 if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) {
115 sway_log(L_ERROR, "Path too long to render");
116 // ditto
117 goto fail;
118 }
119
120 if (isdir(icon_dir)) {
121 return icon_dir;
122 }
123 }
124
125 // Spec says use "/usr/share/pixmaps/", but I see everything in
126 // "/usr/share/icons/" look it both, I suppose.
127 if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) {
128 sway_log(L_ERROR, "Path too long to render");
129 goto fail;
130 }
131 if (isdir(icon_dir)) {
132 return icon_dir;
133 }
134
135 if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) {
136 sway_log(L_ERROR, "Path too long to render");
137 goto fail;
138 }
139 if (isdir(icon_dir)) {
140 return icon_dir;
141 }
142
143fail:
144 free(icon_dir);
145 sway_log(L_ERROR, "Could not find dir for theme: %s", theme);
146 return NULL;
147}
148
149/**
150 * Returns all theme dirs needed to be looked in for an icon.
151 * Does not check for duplicates
152 */
153static list_t *find_all_theme_dirs(const char *theme) {
154 list_t *dirs = create_list();
155 if (!dirs) {
156 return NULL;
157 }
158 char *dir = find_theme_dir(theme);
159 if (dir) {
160 list_add(dirs, dir);
161 list_t *inherits = find_inherits(dir);
162 list_cat(dirs, inherits);
163 list_free(inherits);
164 }
165 dir = find_theme_dir("hicolor");
166 if (dir) {
167 list_add(dirs, dir);
168 }
169
170 return dirs;
171}
172
173struct subdir {
174 int size;
175 char name[];
176};
177
178static int subdir_str_cmp(const void *_subdir, const void *_str) {
179 const struct subdir *subdir = _subdir;
180 const char *str = _str;
181 return strcmp(subdir->name, str);
182}
183/**
184 * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but
185 * generates a list of struct subdirs
186 */
187static list_t *split_subdirs(char *subdir_str) {
188 list_t *subdir_list = create_list();
189 char *copy = strdup(subdir_str);
190 if (!subdir_list || !copy) {
191 list_free(subdir_list);
192 free(copy);
193 return NULL;
194 }
195
196 char *token;
197 token = strtok(copy, ",");
198 while(token) {
199 int len = strlen(token) + 1;
200 struct subdir *subdir =
201 malloc(sizeof(struct subdir) + sizeof(char [len]));
202 if (!subdir) {
203 // Return what we have
204 return subdir_list;
205 }
206 subdir->size = 0;
207 strcpy(subdir->name, token);
208
209 list_add(subdir_list, subdir);
210
211 token = strtok(NULL, ",");
212 }
213 free(copy);
214
215 return subdir_list;
216}
217/**
218 * Returns a list of all subdirectories of a theme.
219 * Take note: the subdir names are all relative to `theme_dir` and must be
220 * combined with it to form a valid directory.
221 *
222 * Each member of the list is of type (struct subdir *) this struct contains
223 * the name of the subdir, along with size information. These must be freed
224 * bye the caller.
225 *
226 * This currently ignores min and max sizes of icons.
227 */
228static list_t* find_theme_subdirs(const char *theme_dir) {
229 const char index_name[] = "/index.theme";
230 list_t *dirs = NULL;
231 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
232 FILE *index = NULL;
233 if (!path) {
234 sway_log(L_ERROR, "Failed to allocate memory");
235 goto fail;
236 }
237
238 strcpy(path, theme_dir);
239 strcat(path, index_name);
240
241 index = fopen(path, "r");
242 if (!index) {
243 sway_log(L_ERROR, "Could not open file: %s", path);
244 goto fail;
245 }
246
247 char *buf = NULL;
248 size_t n = 0;
249 const char directories[] = "Directories";
250 while (!feof(index) && getline(&buf, &n, index) != -1) {
251 if (n <= sizeof(directories) + 1) {
252 continue;
253 }
254 if (strncmp(directories, buf, sizeof(directories) - 1) == 0) {
255 char *dirstr = buf + sizeof(directories);
256 dirs = split_subdirs(dirstr);
257 break;
258 }
259 }
260 // Now, find the size of each dir
261 struct subdir *current_subdir = NULL;
262 const char size[] = "Size";
263 while (!feof(index) && getline(&buf, &n, index) != -1) {
264 if (buf[0] == '[') {
265 int len = strlen(buf);
266 if (buf[len-1] == '\n') {
267 len--;
268 }
269 // replace ']'
270 buf[len-1] = '\0';
271
272 int index;
273 if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) {
274 current_subdir = (dirs->items[index]);
275 }
276 }
277
278 if (strncmp(size, buf, sizeof(size) - 1) == 0) {
279 if (current_subdir) {
280 current_subdir->size = atoi(buf + sizeof(size));
281 }
282 }
283 }
284 free(buf);
285fail:
286 free(path);
287 if (index) {
288 fclose(index);
289 }
290 return dirs;
291}
292
293/* Returns the file of an icon given its name and size */
294static char *find_icon_file(const char *name, int size) {
295 int namelen = strlen(name);
296 list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme);
297 if (!dirs) {
298 return NULL;
299 }
300 int min_size_diff = INT_MAX;
301 char *current_file = NULL;
302
303 for (int i = 0; i < dirs->length; ++i) {
304 char *dir = dirs->items[i];
305 list_t *subdirs = find_theme_subdirs(dir);
306
307 if (!subdirs) {
308 continue;
309 }
310
311 for (int i = 0; i < subdirs->length; ++i) {
312 struct subdir *subdir = subdirs->items[i];
313
314 // Only use an unsized if we don't already have a
315 // canidate this should probably change to allow svgs
316 if (!subdir->size && current_file) {
317 continue;
318 }
319
320 int size_diff = abs(size - subdir->size);
321
322 if (size_diff >= min_size_diff) {
323 continue;
324 }
325
326 char *path = malloc(strlen(subdir->name) + strlen(dir) + 2);
327
328 strcpy(path, dir);
329 path[strlen(dir)] = '/';
330 strcpy(path + strlen(dir) + 1, subdir->name);
331
332 DIR *icons = opendir(path);
333 if (!icons) {
334 free(path);
335 continue;
336 }
337
338 struct dirent *direntry;
339 while ((direntry = readdir(icons)) != NULL) {
340 int len = strlen(direntry->d_name);
341 if (len <= namelen + 2) { //must have some ext
342 continue;
343 }
344 if (strncmp(direntry->d_name, name, namelen) == 0) {
345 char *ext = direntry->d_name + namelen + 1;
346#ifdef WITH_GDK_PIXBUF
347 if (strcmp(ext, "png") == 0 ||
348 strcmp(ext, "xpm") == 0 ||
349 strcmp(ext, "svg") == 0) {
350#else
351 if (strcmp(ext, "png") == 0) {
352#endif
353 free(current_file);
354 char *icon_path = malloc(strlen(path) + len + 2);
355
356 strcpy(icon_path, path);
357 icon_path[strlen(path)] = '/';
358 strcpy(icon_path + strlen(path) + 1, direntry->d_name);
359 current_file = icon_path;
360 min_size_diff = size_diff;
361 }
362 }
363 }
364 free(path);
365 closedir(icons);
366 }
367 free_flat_list(subdirs);
368 }
369 free_flat_list(dirs);
370
371 return current_file;
372}
373
374cairo_surface_t *find_icon(const char *name, int size) {
375 char *image_path = find_icon_file(name, size);
376 if (image_path == NULL) {
377 return NULL;
378 }
379
380 cairo_surface_t *image = NULL;
381#ifdef WITH_GDK_PIXBUF
382 GError *err = NULL;
383 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err);
384 if (!pixbuf) {
385 sway_log(L_ERROR, "Failed to load icon image: %s", err->message);
386 }
387 image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
388 g_object_unref(pixbuf);
389#else
390 // TODO make svg work? cairo supports it. maybe remove gdk alltogether
391 image = cairo_image_surface_create_from_png(image_path);
392#endif //WITH_GDK_PIXBUF
393 if (!image) {
394 sway_log(L_ERROR, "Could not read icon image");
395 return NULL;
396 }
397
398 free(image_path);
399 return image;
400}
diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c
deleted file mode 100644
index c9d00657..00000000
--- a/swaybar/tray/sni.c
+++ /dev/null
@@ -1,481 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdio.h>
4#include <string.h>
5#include <stdint.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include <arpa/inet.h>
9#include <netinet/in.h>
10#include "swaybar/tray/dbus.h"
11#include "swaybar/tray/sni.h"
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "client/cairo.h"
15#include "log.h"
16
17// Not sure what this is but cairo needs it.
18static const cairo_user_data_key_t cairo_user_data_key;
19
20struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
21 int height) {
22 struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref));
23 if (!sni_ref) {
24 return NULL;
25 }
26 sni_ref->icon = cairo_image_surface_scale(item->image, height, height);
27 sni_ref->ref = item;
28
29 return sni_ref;
30}
31
32void sni_icon_ref_free(struct sni_icon_ref *sni_ref) {
33 if (!sni_ref) {
34 return;
35 }
36 cairo_surface_destroy(sni_ref->icon);
37 free(sni_ref);
38}
39
40/* Gets the pixmap of an icon */
41static void reply_icon(DBusPendingCall *pending, void *_data) {
42 struct StatusNotifierItem *item = _data;
43
44 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
45
46 if (!reply) {
47 sway_log(L_ERROR, "Did not get reply");
48 goto bail;
49 }
50
51 int message_type = dbus_message_get_type(reply);
52
53 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
54 char *msg;
55
56 dbus_message_get_args(reply, NULL,
57 DBUS_TYPE_STRING, &msg,
58 DBUS_TYPE_INVALID);
59
60 sway_log(L_ERROR, "Message is error: %s", msg);
61 goto bail;
62 }
63
64 DBusMessageIter iter;
65 DBusMessageIter variant; /* v[a(iiay)] */
66 DBusMessageIter array; /* a(iiay) */
67 DBusMessageIter d_struct; /* (iiay) */
68 DBusMessageIter icon; /* ay */
69
70 dbus_message_iter_init(reply, &iter);
71
72 // Each if here checks the types above before recursing
73 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
74 sway_log(L_ERROR, "Relpy type incorrect");
75 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
76 dbus_message_iter_get_signature(&iter));
77 goto bail;
78 }
79 dbus_message_iter_recurse(&iter, &variant);
80
81 if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) {
82 sway_log(L_ERROR, "Relpy type incorrect");
83 sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"",
84 dbus_message_iter_get_signature(&variant));
85 goto bail;
86 }
87
88 if (dbus_message_iter_get_element_count(&variant) == 0) {
89 // Can't recurse if there are no items
90 sway_log(L_INFO, "Item has no icon");
91 goto bail;
92 }
93 dbus_message_iter_recurse(&variant, &array);
94
95 dbus_message_iter_recurse(&array, &d_struct);
96
97 int width;
98 dbus_message_iter_get_basic(&d_struct, &width);
99 dbus_message_iter_next(&d_struct);
100
101 int height;
102 dbus_message_iter_get_basic(&d_struct, &height);
103 dbus_message_iter_next(&d_struct);
104
105 int len = dbus_message_iter_get_element_count(&d_struct);
106
107 if (!len) {
108 sway_log(L_ERROR, "No icon data");
109 goto bail;
110 }
111
112 // Also implies len % 4 == 0, useful below
113 if (len != width * height * 4) {
114 sway_log(L_ERROR, "Incorrect array size passed");
115 goto bail;
116 }
117
118 dbus_message_iter_recurse(&d_struct, &icon);
119
120 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
121 // FIXME support a variable stride
122 // (works on my machine though for all tested widths)
123 if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) {
124 goto bail;
125 }
126
127 // Data is by reference, no need to free
128 uint8_t *message_data;
129 dbus_message_iter_get_fixed_array(&icon, &message_data, &len);
130
131 uint8_t *image_data = malloc(stride * height);
132 if (!image_data) {
133 sway_log(L_ERROR, "Could not allocate memory for icon");
134 goto bail;
135 }
136
137 // Transform from network byte order to host byte order
138 // Assumptions are safe because the equality above
139 uint32_t *network = (uint32_t *) message_data;
140 uint32_t *host = (uint32_t *)image_data;
141 for (int i = 0; i < width * height; ++i) {
142 host[i] = ntohl(network[i]);
143 }
144
145 cairo_surface_t *image = cairo_image_surface_create_for_data(
146 image_data, CAIRO_FORMAT_ARGB32,
147 width, height, stride);
148
149 if (image) {
150 if (item->image) {
151 cairo_surface_destroy(item->image);
152 }
153 item->image = image;
154 // Free the image data on surface destruction
155 cairo_surface_set_user_data(image,
156 &cairo_user_data_key,
157 image_data,
158 free);
159 item->dirty = true;
160 dirty = true;
161
162 dbus_message_unref(reply);
163 dbus_pending_call_unref(pending);
164 return;
165 } else {
166 sway_log(L_ERROR, "Could not create image surface");
167 free(image_data);
168 }
169
170bail:
171 if (reply) {
172 dbus_message_unref(reply);
173 }
174 dbus_pending_call_unref(pending);
175 sway_log(L_ERROR, "Could not get icon from item");
176 return;
177}
178static void send_icon_msg(struct StatusNotifierItem *item) {
179 DBusPendingCall *pending;
180 DBusMessage *message = dbus_message_new_method_call(
181 item->name,
182 "/StatusNotifierItem",
183 "org.freedesktop.DBus.Properties",
184 "Get");
185 const char *iface;
186 if (item->kde_special_snowflake) {
187 iface = "org.kde.StatusNotifierItem";
188 } else {
189 iface = "org.freedesktop.StatusNotifierItem";
190 }
191 const char *prop = "IconPixmap";
192
193 dbus_message_append_args(message,
194 DBUS_TYPE_STRING, &iface,
195 DBUS_TYPE_STRING, &prop,
196 DBUS_TYPE_INVALID);
197
198 bool status =
199 dbus_connection_send_with_reply(conn, message, &pending, -1);
200
201 dbus_message_unref(message);
202
203 if (!(pending || status)) {
204 sway_log(L_ERROR, "Could not get item icon");
205 return;
206 }
207
208 dbus_pending_call_set_notify(pending, reply_icon, item, NULL);
209}
210
211/* Get an icon by its name */
212static void reply_icon_name(DBusPendingCall *pending, void *_data) {
213 struct StatusNotifierItem *item = _data;
214
215 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
216
217 if (!reply) {
218 sway_log(L_INFO, "Got no icon name reply from item");
219 goto bail;
220 }
221
222 int message_type = dbus_message_get_type(reply);
223
224 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
225 char *msg;
226
227 dbus_message_get_args(reply, NULL,
228 DBUS_TYPE_STRING, &msg,
229 DBUS_TYPE_INVALID);
230
231 sway_log(L_INFO, "Could not get icon name: %s", msg);
232 goto bail;
233 }
234
235 DBusMessageIter iter; /* v[s] */
236 DBusMessageIter variant; /* s */
237
238 dbus_message_iter_init(reply, &iter);
239 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
240 sway_log(L_ERROR, "Relpy type incorrect");
241 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
242 dbus_message_iter_get_signature(&iter));
243 goto bail;
244 }
245 dbus_message_iter_recurse(&iter, &variant);
246
247
248 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) {
249 sway_log(L_ERROR, "Relpy type incorrect");
250 sway_log(L_ERROR, "Should be \"s\", is \"%s\"",
251 dbus_message_iter_get_signature(&iter));
252 goto bail;
253 }
254
255 char *icon_name;
256 dbus_message_iter_get_basic(&variant, &icon_name);
257
258 cairo_surface_t *image = find_icon(icon_name, 256);
259
260 if (image) {
261 sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name,
262 cairo_image_surface_get_width(image));
263 if (item->image) {
264 cairo_surface_destroy(item->image);
265 }
266 item->image = image;
267 item->dirty = true;
268 dirty = true;
269
270 dbus_message_unref(reply);
271 dbus_pending_call_unref(pending);
272 return;
273 }
274
275bail:
276 if (reply) {
277 dbus_message_unref(reply);
278 }
279 dbus_pending_call_unref(pending);
280 // Now try the pixmap
281 send_icon_msg(item);
282 return;
283}
284static void send_icon_name_msg(struct StatusNotifierItem *item) {
285 DBusPendingCall *pending;
286 DBusMessage *message = dbus_message_new_method_call(
287 item->name,
288 "/StatusNotifierItem",
289 "org.freedesktop.DBus.Properties",
290 "Get");
291 const char *iface;
292 if (item->kde_special_snowflake) {
293 iface = "org.kde.StatusNotifierItem";
294 } else {
295 iface = "org.freedesktop.StatusNotifierItem";
296 }
297 const char *prop = "IconName";
298
299 dbus_message_append_args(message,
300 DBUS_TYPE_STRING, &iface,
301 DBUS_TYPE_STRING, &prop,
302 DBUS_TYPE_INVALID);
303
304 bool status =
305 dbus_connection_send_with_reply(conn, message, &pending, -1);
306
307 dbus_message_unref(message);
308
309 if (!(pending || status)) {
310 sway_log(L_ERROR, "Could not get item icon name");
311 return;
312 }
313
314 dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL);
315}
316
317void get_icon(struct StatusNotifierItem *item) {
318 send_icon_name_msg(item);
319}
320
321void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
322 const char *iface =
323 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
324 : "org.freedesktop.StatusNotifierItem");
325 DBusMessage *message = dbus_message_new_method_call(
326 item->name,
327 "/StatusNotifierItem",
328 iface,
329 "Activate");
330
331 dbus_message_append_args(message,
332 DBUS_TYPE_INT32, &x,
333 DBUS_TYPE_INT32, &y,
334 DBUS_TYPE_INVALID);
335
336 dbus_connection_send(conn, message, NULL);
337
338 dbus_message_unref(message);
339}
340
341void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
342 const char *iface =
343 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
344 : "org.freedesktop.StatusNotifierItem");
345 DBusMessage *message = dbus_message_new_method_call(
346 item->name,
347 "/StatusNotifierItem",
348 iface,
349 "ContextMenu");
350
351 dbus_message_append_args(message,
352 DBUS_TYPE_INT32, &x,
353 DBUS_TYPE_INT32, &y,
354 DBUS_TYPE_INVALID);
355
356 dbus_connection_send(conn, message, NULL);
357
358 dbus_message_unref(message);
359}
360void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
361 const char *iface =
362 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
363 : "org.freedesktop.StatusNotifierItem");
364 DBusMessage *message = dbus_message_new_method_call(
365 item->name,
366 "/StatusNotifierItem",
367 iface,
368 "SecondaryActivate");
369
370 dbus_message_append_args(message,
371 DBUS_TYPE_INT32, &x,
372 DBUS_TYPE_INT32, &y,
373 DBUS_TYPE_INVALID);
374
375 dbus_connection_send(conn, message, NULL);
376
377 dbus_message_unref(message);
378}
379
380static void get_unique_name(struct StatusNotifierItem *item) {
381 // I think that we're fine being sync here becaues the message is
382 // directly to the message bus. Could be async though.
383 DBusMessage *message = dbus_message_new_method_call(
384 "org.freedesktop.DBus",
385 "/org/freedesktop/DBus",
386 "org.freedesktop.DBus",
387 "GetNameOwner");
388
389 dbus_message_append_args(message,
390 DBUS_TYPE_STRING, &item->name,
391 DBUS_TYPE_INVALID);
392
393 DBusMessage *reply = dbus_connection_send_with_reply_and_block(
394 conn, message, -1, NULL);
395
396 dbus_message_unref(message);
397
398 if (!reply) {
399 sway_log(L_ERROR, "Could not get unique name for item: %s",
400 item->name);
401 return;
402 }
403
404 char *unique_name;
405 if (!dbus_message_get_args(reply, NULL,
406 DBUS_TYPE_STRING, &unique_name,
407 DBUS_TYPE_INVALID)) {
408 sway_log(L_ERROR, "Error parsing method args");
409 } else {
410 if (item->unique_name) {
411 free(item->unique_name);
412 }
413 item->unique_name = strdup(unique_name);
414 }
415
416 dbus_message_unref(reply);
417}
418
419struct StatusNotifierItem *sni_create(const char *name) {
420 // Make sure `name` is well formed
421 if (!dbus_validate_bus_name(name, NULL)) {
422 sway_log(L_INFO, "Name (%s) is not a bus name. We cannot create an item.", name);
423 return NULL;
424 }
425
426 struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
427 item->name = strdup(name);
428 item->unique_name = NULL;
429 item->image = NULL;
430 item->dirty = false;
431
432 // If it doesn't use this name then assume that it uses the KDE spec
433 // This is because xembed-sni-proxy uses neither "org.freedesktop" nor
434 // "org.kde" and just gives us the items "unique name"
435 //
436 // We could use this to our advantage and fill out the "unique name"
437 // field with the given name if it is neither freedesktop or kde, but
438 // that's makes us rely on KDE hackyness which is bad practice
439 const char freedesktop_name[] = "org.freedesktop";
440 if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) {
441 item->kde_special_snowflake = true;
442 } else {
443 item->kde_special_snowflake = false;
444 }
445
446 get_icon(item);
447
448 get_unique_name(item);
449
450 return item;
451}
452/* Return 0 if `item` has a name of `str` */
453int sni_str_cmp(const void *_item, const void *_str) {
454 const struct StatusNotifierItem *item = _item;
455 const char *str = _str;
456
457 return strcmp(item->name, str);
458}
459/* Returns 0 if `item` has a unique name of `str` */
460int sni_uniq_cmp(const void *_item, const void *_str) {
461 const struct StatusNotifierItem *item = _item;
462 const char *str = _str;
463
464 if (!item->unique_name) {
465 return false;
466 }
467 return strcmp(item->unique_name, str);
468}
469void sni_free(struct StatusNotifierItem *item) {
470 if (!item) {
471 return;
472 }
473 free(item->name);
474 if (item->unique_name) {
475 free(item->unique_name);
476 }
477 if (item->image) {
478 cairo_surface_destroy(item->image);
479 }
480 free(item);
481}
diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c
deleted file mode 100644
index 86453e70..00000000
--- a/swaybar/tray/sni_watcher.c
+++ /dev/null
@@ -1,497 +0,0 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include "swaybar/tray/dbus.h"
9#include "list.h"
10#include "log.h"
11
12static list_t *items = NULL;
13static list_t *hosts = NULL;
14
15/**
16 * Describes the function of the StatusNotifierWatcher
17 * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/
18 *
19 * We also implement KDE's special snowflake protocol, it's like this but with
20 * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect.
21 */
22static const char *interface_xml =
23 "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'"
24 "'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>"
25 "<node>"
26 " <interface name='org.freedesktop.DBus.Introspectable'>"
27 " <method name='Introspect'>"
28 " <arg name='xml_data' direction='out' type='s'/>"
29 " </method>"
30 " </interface>"
31 " <interface name='org.freedesktop.DBus.Properties'>"
32 " <method name='Get'>"
33 " <arg name='interface' direction='in' type='s'/>"
34 " <arg name='propname' direction='in' type='s'/>"
35 " <arg name='value' direction='out' type='v'/>"
36 " </method>"
37 " <method name='Set'>"
38 " <arg name='interface' direction='in' type='s'/>"
39 " <arg name='propname' direction='in' type='s'/>"
40 " <arg name='value' direction='in' type='v'/>"
41 " </method>"
42 " <method name='GetAll'>"
43 " <arg name='interface' direction='in' type='s'/>"
44 " <arg name='props' direction='out' type='a{sv}'/>"
45 " </method>"
46 " </interface>"
47 " <interface name='org.freedesktop.StatusNotifierWatcher'>"
48 " <method name='RegisterStatusNotifierItem'>"
49 " <arg type='s' name='service' direction='in'/>"
50 " </method>"
51 " <method name='RegisterStatusNotifierHost'>"
52 " <arg type='s' name='service' direction='in'/>"
53 " </method>"
54 " <property name='RegisteredStatusNotifierItems' type='as' access='read'/>"
55 " <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>"
56 " <property name='ProtocolVersion' type='i' access='read'/>"
57 " <signal name='StatusNotifierItemRegistered'>"
58 " <arg type='s' name='service' direction='out'/>"
59 " </signal>"
60 " <signal name='StatusNotifierItemUnregistered'>"
61 " <arg type='s' name='service' direction='out'/>"
62 " </signal>"
63 " <signal name='StatusNotifierHostRegistered'>"
64 " <arg type='' name='service' direction='out'/>"
65 " </signal>"
66 " </interface>"
67 "</node>";
68
69static void host_registered_signal(DBusConnection *connection) {
70 // Send one signal for each protocol
71 DBusMessage *signal = dbus_message_new_signal(
72 "/StatusNotifierWatcher",
73 "org.freedesktop.StatusNotifierWatcher",
74 "StatusNotifierHostRegistered");
75
76 dbus_connection_send(connection, signal, NULL);
77 dbus_message_unref(signal);
78
79
80 signal = dbus_message_new_signal(
81 "/StatusNotifierWatcher",
82 "org.kde.StatusNotifierWatcher",
83 "StatusNotifierHostRegistered");
84
85 dbus_connection_send(connection, signal, NULL);
86 dbus_message_unref(signal);
87}
88static void item_registered_signal(DBusConnection *connection, const char *name) {
89 DBusMessage *signal = dbus_message_new_signal(
90 "/StatusNotifierWatcher",
91 "org.freedesktop.StatusNotifierWatcher",
92 "StatusNotifierItemRegistered");
93 dbus_message_append_args(signal,
94 DBUS_TYPE_STRING, &name,
95 DBUS_TYPE_INVALID);
96 dbus_connection_send(connection, signal, NULL);
97 dbus_message_unref(signal);
98
99 signal = dbus_message_new_signal(
100 "/StatusNotifierWatcher",
101 "org.kde.StatusNotifierWatcher",
102 "StatusNotifierItemRegistered");
103 dbus_message_append_args(signal,
104 DBUS_TYPE_STRING, &name,
105 DBUS_TYPE_INVALID);
106 dbus_connection_send(connection, signal, NULL);
107 dbus_message_unref(signal);
108}
109static void item_unregistered_signal(DBusConnection *connection, const char *name) {
110 DBusMessage *signal = dbus_message_new_signal(
111 "/StatusNotifierWatcher",
112 "org.freedesktop.StatusNotifierWatcher",
113 "StatusNotifierItemUnregistered");
114 dbus_message_append_args(signal,
115 DBUS_TYPE_STRING, &name,
116 DBUS_TYPE_INVALID);
117 dbus_connection_send(connection, signal, NULL);
118 dbus_message_unref(signal);
119
120 signal = dbus_message_new_signal(
121 "/StatusNotifierWatcher",
122 "org.kde.StatusNotifierWatcher",
123 "StatusNotifierItemUnregistered");
124 dbus_message_append_args(signal,
125 DBUS_TYPE_STRING, &name,
126 DBUS_TYPE_INVALID);
127 dbus_connection_send(connection, signal, NULL);
128 dbus_message_unref(signal);
129}
130
131static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) {
132 DBusMessage *reply;
133
134 reply = dbus_message_new_method_return(request);
135 dbus_message_append_args(reply,
136 DBUS_TYPE_STRING, &interface_xml,
137 DBUS_TYPE_INVALID);
138 dbus_connection_send(connection, reply, NULL);
139 dbus_message_unref(reply);
140}
141
142static void register_item(DBusConnection *connection, DBusMessage *message) {
143 DBusError error;
144 char *name;
145
146 dbus_error_init(&error);
147 if (!dbus_message_get_args(message, &error,
148 DBUS_TYPE_STRING, &name,
149 DBUS_TYPE_INVALID)) {
150 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
151 }
152
153 sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name);
154
155 // Don't add duplicate or not real item
156 if (!dbus_validate_bus_name(name, NULL)) {
157 sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
158 return;
159 }
160
161 if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
162 return;
163 }
164 if (!dbus_bus_name_has_owner(connection, name, &error)) {
165 return;
166 }
167
168 list_add(items, strdup(name));
169 item_registered_signal(connection, name);
170
171 // It's silly, but xembedsniproxy wants a reply for this function
172 DBusMessage *reply = dbus_message_new_method_return(message);
173 dbus_connection_send(connection, reply, NULL);
174 dbus_message_unref(reply);
175}
176
177static void register_host(DBusConnection *connection, DBusMessage *message) {
178 DBusError error;
179 char *name;
180
181 dbus_error_init(&error);
182 if (!dbus_message_get_args(message, &error,
183 DBUS_TYPE_STRING, &name,
184 DBUS_TYPE_INVALID)) {
185 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
186 }
187
188 sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name);
189
190 // Don't add duplicate or not real host
191 if (!dbus_validate_bus_name(name, NULL)) {
192 sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
193 return;
194 }
195
196
197 if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) {
198 return;
199 }
200 if (!dbus_bus_name_has_owner(connection, name, &error)) {
201 return;
202 }
203
204 list_add(hosts, strdup(name));
205 host_registered_signal(connection);
206}
207
208static void get_property(DBusConnection *connection, DBusMessage *message) {
209 DBusError error;
210 char *interface;
211 char *property;
212
213 dbus_error_init(&error);
214 if (!dbus_message_get_args(message, &error,
215 DBUS_TYPE_STRING, &interface,
216 DBUS_TYPE_STRING, &property,
217 DBUS_TYPE_INVALID)) {
218 sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message);
219 return;
220 }
221
222 if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
223 sway_log(L_INFO, "Replying with items\n");
224 DBusMessage *reply;
225 reply = dbus_message_new_method_return(message);
226 DBusMessageIter iter;
227 DBusMessageIter sub;
228 DBusMessageIter subsub;
229
230 dbus_message_iter_init_append(reply, &iter);
231
232 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
233 "as", &sub);
234 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
235 "s", &subsub);
236
237 for (int i = 0; i < items->length; ++i) {
238 dbus_message_iter_append_basic(&subsub,
239 DBUS_TYPE_STRING, &items->items[i]);
240 }
241
242 dbus_message_iter_close_container(&sub, &subsub);
243 dbus_message_iter_close_container(&iter, &sub);
244
245 dbus_connection_send(connection, reply, NULL);
246 dbus_message_unref(reply);
247 } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) {
248 DBusMessage *reply;
249 DBusMessageIter iter;
250 DBusMessageIter sub;
251 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
252
253 reply = dbus_message_new_method_return(message);
254
255 dbus_message_iter_init_append(reply, &iter);
256
257 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
258 "b", &sub);
259 dbus_message_iter_append_basic(&sub,
260 DBUS_TYPE_BOOLEAN, &registered);
261
262 dbus_message_iter_close_container(&iter, &sub);
263
264 dbus_connection_send(connection, reply, NULL);
265 dbus_message_unref(reply);
266 } else if (strcmp(property, "ProtocolVersion") == 0) {
267 DBusMessage *reply;
268 DBusMessageIter iter;
269 DBusMessageIter sub;
270 const int version = 0;
271
272 reply = dbus_message_new_method_return(message);
273
274 dbus_message_iter_init_append(reply, &iter);
275
276 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
277 "i", &sub);
278 dbus_message_iter_append_basic(&sub,
279 DBUS_TYPE_INT32, &version);
280
281 dbus_message_iter_close_container(&iter, &sub);
282 dbus_connection_send(connection, reply, NULL);
283 dbus_message_unref(reply);
284 }
285}
286
287static void set_property(DBusConnection *connection, DBusMessage *message) {
288 // All properties are read only and we don't allow new properties
289 return;
290}
291
292static void get_all(DBusConnection *connection, DBusMessage *message) {
293 DBusMessage *reply;
294 reply = dbus_message_new_method_return(message);
295 DBusMessageIter iter; /* a{v} */
296 DBusMessageIter arr;
297 DBusMessageIter dict;
298 DBusMessageIter sub;
299 DBusMessageIter subsub;
300 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
301 const int version = 0;
302 const char *prop;
303
304 // Could clean this up with a function for each prop
305 dbus_message_iter_init_append(reply, &iter);
306 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
307 "{sv}", &arr);
308
309 prop = "RegisteredStatusNotifierItems";
310 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
311 NULL, &dict);
312 dbus_message_iter_append_basic(&dict,
313 DBUS_TYPE_STRING, &prop);
314 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
315 "as", &sub);
316 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
317 "s", &subsub);
318 for (int i = 0; i < items->length; ++i) {
319 dbus_message_iter_append_basic(&subsub,
320 DBUS_TYPE_STRING, &items->items[i]);
321 }
322 dbus_message_iter_close_container(&sub, &subsub);
323 dbus_message_iter_close_container(&dict, &sub);
324 dbus_message_iter_close_container(&arr, &dict);
325
326 prop = "IsStatusNotifierHostRegistered";
327 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
328 NULL, &dict);
329 dbus_message_iter_append_basic(&dict,
330 DBUS_TYPE_STRING, &prop);
331 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
332 "b", &sub);
333 dbus_message_iter_append_basic(&sub,
334 DBUS_TYPE_BOOLEAN, &registered);
335 dbus_message_iter_close_container(&dict, &sub);
336 dbus_message_iter_close_container(&arr, &dict);
337
338 prop = "ProtocolVersion";
339 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
340 NULL, &dict);
341 dbus_message_iter_append_basic(&dict,
342 DBUS_TYPE_STRING, &prop);
343 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
344 "i", &sub);
345 dbus_message_iter_append_basic(&sub,
346 DBUS_TYPE_INT32, &version);
347 dbus_message_iter_close_container(&dict, &sub);
348 dbus_message_iter_close_container(&arr, &dict);
349
350 dbus_message_iter_close_container(&iter, &arr);
351
352 dbus_connection_send(connection, reply, NULL);
353 dbus_message_unref(reply);
354}
355
356static DBusHandlerResult message_handler(DBusConnection *connection,
357 DBusMessage *message, void *data) {
358 const char *interface_name = dbus_message_get_interface(message);
359 const char *member_name = dbus_message_get_member(message);
360
361 // In order of the xml above
362 if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
363 strcmp(member_name, "Introspect") == 0) {
364 // We don't have an introspect for KDE
365 respond_to_introspect(connection, message);
366 return DBUS_HANDLER_RESULT_HANDLED;
367 } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) {
368 if (strcmp(member_name, "Get") == 0) {
369 get_property(connection, message);
370 return DBUS_HANDLER_RESULT_HANDLED;
371 } else if (strcmp(member_name, "Set") == 0) {
372 set_property(connection, message);
373 return DBUS_HANDLER_RESULT_HANDLED;
374 } else if (strcmp(member_name, "GetAll") == 0) {
375 get_all(connection, message);
376 return DBUS_HANDLER_RESULT_HANDLED;
377 } else {
378 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
379 }
380 } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 ||
381 strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) {
382 if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) {
383 register_item(connection, message);
384 return DBUS_HANDLER_RESULT_HANDLED;
385 } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) {
386 register_host(connection, message);
387 return DBUS_HANDLER_RESULT_HANDLED;
388 } else {
389 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
390 }
391 }
392 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
393}
394
395static DBusHandlerResult signal_handler(DBusConnection *connection,
396 DBusMessage *message, void *_data) {
397 if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) {
398 // Only eat the message if it is name that we are watching
399 const char *name;
400 const char *old_owner;
401 const char *new_owner;
402 int index;
403 if (!dbus_message_get_args(message, NULL,
404 DBUS_TYPE_STRING, &name,
405 DBUS_TYPE_STRING, &old_owner,
406 DBUS_TYPE_STRING, &new_owner,
407 DBUS_TYPE_INVALID)) {
408 sway_log(L_ERROR, "Error getting LostName args");
409 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
410 }
411 if (strcmp(new_owner, "") != 0) {
412 // Name is not lost
413 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
414 }
415 if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) {
416 sway_log(L_INFO, "Status Notifier Item lost %s", name);
417 free(items->items[index]);
418 list_del(items, index);
419 item_unregistered_signal(connection, name);
420
421 return DBUS_HANDLER_RESULT_HANDLED;
422 }
423 if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) {
424 sway_log(L_INFO, "Status Notifier Host lost %s", name);
425 free(hosts->items[index]);
426 list_del(hosts, index);
427
428 return DBUS_HANDLER_RESULT_HANDLED;
429 }
430 }
431 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
432}
433
434static const DBusObjectPathVTable vtable = {
435 .message_function = message_handler,
436 .unregister_function = NULL,
437};
438
439int init_sni_watcher() {
440 DBusError error;
441 dbus_error_init(&error);
442 if (!conn) {
443 sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher");
444 return -1;
445 }
446
447 items = create_list();
448 hosts = create_list();
449
450 int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
451 DBUS_NAME_FLAG_REPLACE_EXISTING,
452 &error);
453 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
454 sway_log(L_DEBUG, "Got watcher name");
455 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
456 sway_log(L_INFO, "Could not get watcher name, it may start later");
457 }
458 if (dbus_error_is_set(&error)) {
459 sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message);
460 return -1;
461 }
462
463 status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher",
464 DBUS_NAME_FLAG_REPLACE_EXISTING,
465 &error);
466 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
467 sway_log(L_DEBUG, "Got kde watcher name");
468 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
469 sway_log(L_INFO, "Could not get kde watcher name, it may start later");
470 }
471 if (dbus_error_is_set(&error)) {
472 sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message);
473 return -1;
474 }
475
476 dbus_connection_try_register_object_path(conn,
477 "/StatusNotifierWatcher",
478 &vtable, NULL, &error);
479 if (dbus_error_is_set(&error)) {
480 sway_log(L_ERROR, "dbus_err: %s\n", error.message);
481 return -1;
482 }
483
484 dbus_bus_add_match(conn,
485 "type='signal',\
486 sender='org.freedesktop.DBus',\
487 interface='org.freedesktop.DBus',\
488 member='NameOwnerChanged'",
489 &error);
490
491 if (dbus_error_is_set(&error)) {
492 sway_log(L_ERROR, "DBus error getting match args: %s", error.message);
493 }
494
495 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
496 return 0;
497}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
deleted file mode 100644
index 91c3af06..00000000
--- a/swaybar/tray/tray.c
+++ /dev/null
@@ -1,398 +0,0 @@
1#define _XOPEN_SOURCE 700
2#include <unistd.h>
3#include <stdlib.h>
4#include <string.h>
5#include <sys/wait.h>
6#include <dbus/dbus.h>
7#include "swaybar/bar.h"
8#include "swaybar/tray/tray.h"
9#include "swaybar/tray/dbus.h"
10#include "swaybar/tray/sni.h"
11#include "swaybar/tray/sni_watcher.h"
12#include "swaybar/bar.h"
13#include "swaybar/config.h"
14#include "list.h"
15#include "log.h"
16
17struct tray *tray;
18
19static void register_host(char *name) {
20 DBusMessage *message;
21
22 message = dbus_message_new_method_call(
23 "org.freedesktop.StatusNotifierWatcher",
24 "/StatusNotifierWatcher",
25 "org.freedesktop.StatusNotifierWatcher",
26 "RegisterStatusNotifierHost");
27 if (!message) {
28 sway_log(L_ERROR, "Cannot allocate dbus method call");
29 return;
30 }
31
32 dbus_message_append_args(message,
33 DBUS_TYPE_STRING, &name,
34 DBUS_TYPE_INVALID);
35
36 dbus_connection_send(conn, message, NULL);
37
38 dbus_message_unref(message);
39}
40
41static void get_items_reply(DBusPendingCall *pending, void *_data) {
42 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
43
44 if (!reply) {
45 sway_log(L_ERROR, "Got no items reply from sni watcher");
46 goto bail;
47 }
48
49 int message_type = dbus_message_get_type(reply);
50
51 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
52 char *msg;
53
54 dbus_message_get_args(reply, NULL,
55 DBUS_TYPE_STRING, &msg,
56 DBUS_TYPE_INVALID);
57
58 sway_log(L_ERROR, "Message is error: %s", msg);
59 goto bail;
60 }
61
62 DBusMessageIter iter;
63 DBusMessageIter variant;
64 DBusMessageIter array;
65
66 dbus_message_iter_init(reply, &iter);
67 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
68 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
69 goto bail;
70 }
71 dbus_message_iter_recurse(&iter, &variant);
72 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
73 dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
74 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
75 goto bail;
76 }
77
78 // Clear list
79 list_foreach(tray->items, (void (*)(void *))sni_free);
80 list_free(tray->items);
81 tray->items = create_list();
82
83 // O(n) function, could be faster dynamically reading values
84 int len = dbus_message_iter_get_element_count(&variant);
85
86 dbus_message_iter_recurse(&variant, &array);
87 for (int i = 0; i < len; i++) {
88 const char *name;
89 dbus_message_iter_get_basic(&array, &name);
90
91 struct StatusNotifierItem *item = sni_create(name);
92
93 if (item) {
94 sway_log(L_DEBUG, "Item registered with host: %s", name);
95 list_add(tray->items, item);
96 dirty = true;
97 }
98 }
99
100bail:
101 dbus_message_unref(reply);
102 dbus_pending_call_unref(pending);
103 return;
104}
105static void get_items() {
106 DBusPendingCall *pending;
107 DBusMessage *message = dbus_message_new_method_call(
108 "org.freedesktop.StatusNotifierWatcher",
109 "/StatusNotifierWatcher",
110 "org.freedesktop.DBus.Properties",
111 "Get");
112
113 const char *iface = "org.freedesktop.StatusNotifierWatcher";
114 const char *prop = "RegisteredStatusNotifierItems";
115 dbus_message_append_args(message,
116 DBUS_TYPE_STRING, &iface,
117 DBUS_TYPE_STRING, &prop,
118 DBUS_TYPE_INVALID);
119
120 bool status =
121 dbus_connection_send_with_reply(conn, message, &pending, -1);
122 dbus_message_unref(message);
123
124 if (!(pending || status)) {
125 sway_log(L_ERROR, "Could not get items");
126 return;
127 }
128
129 dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
130}
131
132static DBusHandlerResult signal_handler(DBusConnection *connection,
133 DBusMessage *message, void *_data) {
134 if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
135 "StatusNotifierItemRegistered")) {
136 const char *name;
137 if (!dbus_message_get_args(message, NULL,
138 DBUS_TYPE_STRING, &name,
139 DBUS_TYPE_INVALID)) {
140 sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
141 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
142 }
143
144 if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
145 struct StatusNotifierItem *item = sni_create(name);
146
147 if (item) {
148 list_add(tray->items, item);
149 dirty = true;
150 }
151 }
152
153 return DBUS_HANDLER_RESULT_HANDLED;
154 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
155 "StatusNotifierItemUnregistered")) {
156 const char *name;
157 if (!dbus_message_get_args(message, NULL,
158 DBUS_TYPE_STRING, &name,
159 DBUS_TYPE_INVALID)) {
160 sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
161 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
162 }
163
164 int index;
165 if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
166 sni_free(tray->items->items[index]);
167 list_del(tray->items, index);
168 dirty = true;
169 } else {
170 // If it's not in our list, then our list is incorrect.
171 // Fetch all items again
172 sway_log(L_INFO, "Host item list incorrect, refreshing");
173 get_items();
174 }
175
176 return DBUS_HANDLER_RESULT_HANDLED;
177 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
178 "NewIcon") || dbus_message_is_signal(message,
179 "org.kde.StatusNotifierItem", "NewIcon")) {
180 const char *name;
181 int index;
182 struct StatusNotifierItem *item;
183
184 name = dbus_message_get_sender(message);
185 if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
186 item = tray->items->items[index];
187 sway_log(L_INFO, "NewIcon signal from item %s", item->name);
188 get_icon(item);
189 }
190
191 return DBUS_HANDLER_RESULT_HANDLED;
192 }
193 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
194}
195
196static int init_host() {
197 tray = (struct tray *)malloc(sizeof(tray));
198
199 tray->items = create_list();
200
201 DBusError error;
202 dbus_error_init(&error);
203 char *name = NULL;
204 if (!conn) {
205 sway_log(L_ERROR, "Connection is null, cannot init SNI host");
206 goto err;
207 }
208 name = calloc(sizeof(char), 256);
209
210 if (!name) {
211 sway_log(L_ERROR, "Cannot allocate name");
212 goto err;
213 }
214
215 pid_t pid = getpid();
216 if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
217 >= 256) {
218 sway_log(L_ERROR, "Cannot get host name because string is too short."
219 "This should not happen");
220 goto err;
221 }
222
223 // We want to be the sole owner of this name
224 if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
225 &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
226 sway_log(L_ERROR, "Cannot get host name and start the tray");
227 goto err;
228 }
229 if (dbus_error_is_set(&error)) {
230 sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
231 goto err;
232 }
233 sway_log(L_DEBUG, "Got host name");
234
235 register_host(name);
236
237 get_items();
238
239 // Perhaps use addmatch helper functions like wlc does?
240 dbus_bus_add_match(conn,
241 "type='signal',\
242 sender='org.freedesktop.StatusNotifierWatcher',\
243 member='StatusNotifierItemRegistered'",
244 &error);
245 if (dbus_error_is_set(&error)) {
246 sway_log(L_ERROR, "dbus_err: %s", error.message);
247 goto err;
248 }
249 dbus_bus_add_match(conn,
250 "type='signal',\
251 sender='org.freedesktop.StatusNotifierWatcher',\
252 member='StatusNotifierItemUnregistered'",
253 &error);
254 if (dbus_error_is_set(&error)) {
255 sway_log(L_ERROR, "dbus_err: %s", error.message);
256 return -1;
257 }
258
259 // SNI matches
260 dbus_bus_add_match(conn,
261 "type='signal',\
262 interface='org.freedesktop.StatusNotifierItem',\
263 member='NewIcon'",
264 &error);
265 if (dbus_error_is_set(&error)) {
266 sway_log(L_ERROR, "dbus_err %s", error.message);
267 goto err;
268 }
269 dbus_bus_add_match(conn,
270 "type='signal',\
271 interface='org.kde.StatusNotifierItem',\
272 member='NewIcon'",
273 &error);
274 if (dbus_error_is_set(&error)) {
275 sway_log(L_ERROR, "dbus_err %s", error.message);
276 goto err;
277 }
278
279 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
280
281 free(name);
282 return 0;
283
284err:
285 // TODO better handle errors
286 free(name);
287 return -1;
288}
289
290void tray_mouse_event(struct output *output, int x, int y,
291 uint32_t button, uint32_t state) {
292
293 struct window *window = output->window;
294 uint32_t tray_padding = swaybar.config->tray_padding;
295 int tray_width = window->width * window->scale;
296
297 for (int i = 0; i < output->items->length; ++i) {
298 struct sni_icon_ref *item =
299 output->items->items[i];
300 int icon_width = cairo_image_surface_get_width(item->icon);
301
302 tray_width -= tray_padding;
303 if (x <= tray_width && x >= tray_width - icon_width) {
304 if (button == swaybar.config->activate_button) {
305 sni_activate(item->ref, x, y);
306 } else if (button == swaybar.config->context_button) {
307 sni_context_menu(item->ref, x, y);
308 } else if (button == swaybar.config->secondary_button) {
309 sni_secondary(item->ref, x, y);
310 }
311 break;
312 }
313 tray_width -= icon_width;
314 }
315}
316
317uint32_t tray_render(struct output *output, struct config *config) {
318 struct window *window = output->window;
319 cairo_t *cairo = window->cairo;
320
321 // Tray icons
322 uint32_t tray_padding = config->tray_padding;
323 uint32_t tray_width = window->width * window->scale;
324 const int item_size = (window->height * window->scale) - (2 * tray_padding);
325
326 if (item_size < 0) {
327 // Can't render items if the padding is too large
328 return tray_width;
329 }
330
331 if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
332 return tray_width;
333 }
334
335 for (int i = 0; i < tray->items->length; ++i) {
336 struct StatusNotifierItem *item =
337 tray->items->items[i];
338 if (!item->image) {
339 continue;
340 }
341
342 struct sni_icon_ref *render_item = NULL;
343 int j;
344 for (j = i; j < output->items->length; ++j) {
345 struct sni_icon_ref *ref =
346 output->items->items[j];
347 if (ref->ref == item) {
348 render_item = ref;
349 break;
350 } else {
351 sni_icon_ref_free(ref);
352 list_del(output->items, j);
353 }
354 }
355
356 if (!render_item) {
357 render_item = sni_icon_ref_create(item, item_size);
358 list_add(output->items, render_item);
359 } else if (item->dirty) {
360 // item needs re-render
361 sni_icon_ref_free(render_item);
362 output->items->items[j] = render_item =
363 sni_icon_ref_create(item, item_size);
364 }
365
366 tray_width -= tray_padding;
367 tray_width -= item_size;
368
369 cairo_operator_t op = cairo_get_operator(cairo);
370 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
371 cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
372 cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
373 cairo_fill(cairo);
374 cairo_set_operator(cairo, op);
375
376 item->dirty = false;
377 }
378
379
380 if (tray_width != window->width * window->scale) {
381 tray_width -= tray_padding;
382 }
383
384 return tray_width;
385}
386
387void init_tray(struct bar *bar) {
388 if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) {
389 /* Connect to the D-Bus */
390 dbus_init();
391
392 /* Start the SNI watcher */
393 init_sni_watcher();
394
395 /* Start the SNI host */
396 init_host();
397 }
398}