1 #define _POSIX_C_SOURCE 200809L
2 #include <assert.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <poll.h>
6 #include <signal.h>
7 #include <stdbool.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/wait.h>
11 #include <unistd.h>
12 #include <wayland-client.h>
13 #include <wayland-cursor.h>
14 #include "config.h"
15 #include "swaybar/bar.h"
16 #include "swaybar/config.h"
17 #include "swaybar/i3bar.h"
18 #include "swaybar/input.h"
19 #include "swaybar/ipc.h"
20 #include "swaybar/status_line.h"
21 #include "swaybar/render.h"
22 #if HAVE_TRAY
23 #include "swaybar/tray/tray.h"
24 #endif
25 #include "ipc-client.h"
26 #include "list.h"
27 #include "log.h"
28 #include "loop.h"
29 #include "pool-buffer.h"
30 #include "wlr-layer-shell-unstable-v1-client-protocol.h"
31 #include "xdg-output-unstable-v1-client-protocol.h"
32
free_workspaces(struct wl_list * list)33 void free_workspaces(struct wl_list *list) {
34 struct swaybar_workspace *ws, *tmp;
35 wl_list_for_each_safe(ws, tmp, list, link) {
36 wl_list_remove(&ws->link);
37 free(ws->name);
38 free(ws->label);
39 free(ws);
40 }
41 }
42
swaybar_output_free(struct swaybar_output * output)43 static void swaybar_output_free(struct swaybar_output *output) {
44 if (!output) {
45 return;
46 }
47 sway_log(SWAY_DEBUG, "Removing output %s", output->name);
48 if (output->layer_surface != NULL) {
49 zwlr_layer_surface_v1_destroy(output->layer_surface);
50 }
51 if (output->surface != NULL) {
52 wl_surface_destroy(output->surface);
53 }
54 if (output->input_region != NULL) {
55 wl_region_destroy(output->input_region);
56 }
57 zxdg_output_v1_destroy(output->xdg_output);
58 wl_output_destroy(output->output);
59 destroy_buffer(&output->buffers[0]);
60 destroy_buffer(&output->buffers[1]);
61 free_hotspots(&output->hotspots);
62 free_workspaces(&output->workspaces);
63 wl_list_remove(&output->link);
64 free(output->name);
65 free(output->identifier);
66 free(output);
67 }
68
set_output_dirty(struct swaybar_output * output)69 static void set_output_dirty(struct swaybar_output *output) {
70 if (output->frame_scheduled) {
71 output->dirty = true;
72 } else if (output->surface) {
73 render_frame(output);
74 }
75 }
76
layer_surface_configure(void * data,struct zwlr_layer_surface_v1 * surface,uint32_t serial,uint32_t width,uint32_t height)77 static void layer_surface_configure(void *data,
78 struct zwlr_layer_surface_v1 *surface,
79 uint32_t serial, uint32_t width, uint32_t height) {
80 struct swaybar_output *output = data;
81 output->width = width;
82 output->height = height;
83 zwlr_layer_surface_v1_ack_configure(surface, serial);
84 set_output_dirty(output);
85 }
86
layer_surface_closed(void * _output,struct zwlr_layer_surface_v1 * surface)87 static void layer_surface_closed(void *_output,
88 struct zwlr_layer_surface_v1 *surface) {
89 struct swaybar_output *output = _output;
90 swaybar_output_free(output);
91 }
92
93 struct zwlr_layer_surface_v1_listener layer_surface_listener = {
94 .configure = layer_surface_configure,
95 .closed = layer_surface_closed,
96 };
97
add_layer_surface(struct swaybar_output * output)98 static void add_layer_surface(struct swaybar_output *output) {
99 if (output->layer_surface) {
100 return;
101 }
102 struct swaybar *bar = output->bar;
103
104 struct swaybar_config *config = bar->config;
105 bool hidden = strcmp(config->mode, "hide") == 0;
106 bool overlay = !hidden && strcmp(config->mode, "overlay") == 0;
107 output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
108 bar->layer_shell, output->surface, output->output,
109 hidden || overlay ? ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY :
110 ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "panel");
111 assert(output->layer_surface);
112 zwlr_layer_surface_v1_add_listener(output->layer_surface,
113 &layer_surface_listener, output);
114
115 if (overlay) {
116 // Empty input region
117 output->input_region = wl_compositor_create_region(bar->compositor);
118 assert(output->input_region);
119
120 wl_surface_set_input_region(output->surface, output->input_region);
121 }
122
123 zwlr_layer_surface_v1_set_anchor(output->layer_surface, config->position);
124 if (hidden || overlay) {
125 zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1);
126 }
127 }
128
destroy_layer_surface(struct swaybar_output * output)129 void destroy_layer_surface(struct swaybar_output *output) {
130 if (!output->layer_surface) {
131 return;
132 }
133 zwlr_layer_surface_v1_destroy(output->layer_surface);
134 wl_surface_attach(output->surface, NULL, 0, 0); // detach buffer
135 output->layer_surface = NULL;
136 output->width = 0;
137 output->frame_scheduled = false;
138 }
139
set_bar_dirty(struct swaybar * bar)140 void set_bar_dirty(struct swaybar *bar) {
141 struct swaybar_output *output;
142 wl_list_for_each(output, &bar->outputs, link) {
143 set_output_dirty(output);
144 }
145 }
146
determine_bar_visibility(struct swaybar * bar,bool moving_layer)147 bool determine_bar_visibility(struct swaybar *bar, bool moving_layer) {
148 struct swaybar_config *config = bar->config;
149 bool visible = !(strcmp(config->mode, "invisible") == 0 ||
150 (strcmp(config->mode, config->hidden_state) == 0 // both "hide"
151 && !bar->visible_by_modifier && !bar->visible_by_urgency
152 && !bar->visible_by_mode));
153
154 // Create/destroy layer surfaces as needed
155 struct swaybar_output *output;
156 wl_list_for_each(output, &bar->outputs, link) {
157 // When moving to a different layer, we need to destroy and re-create
158 // the layer surface
159 if (!visible || moving_layer) {
160 destroy_layer_surface(output);
161 }
162
163 if (visible) {
164 add_layer_surface(output);
165 }
166 }
167 set_bar_dirty(bar);
168
169 if (visible != bar->visible) {
170 bar->visible = visible;
171
172 if (bar->status) {
173 sway_log(SWAY_DEBUG, "Sending %s signal to status command",
174 visible ? "cont" : "stop");
175 kill(bar->status->pid, visible ?
176 bar->status->cont_signal : bar->status->stop_signal);
177 }
178 }
179
180 return visible;
181 }
182
bar_uses_output(struct swaybar_output * output)183 static bool bar_uses_output(struct swaybar_output *output) {
184 if (wl_list_empty(&output->bar->config->outputs)) {
185 return true;
186 }
187 char *identifier = output->identifier;
188 struct config_output *coutput;
189 wl_list_for_each(coutput, &output->bar->config->outputs, link) {
190 if (strcmp(coutput->name, output->name) == 0 ||
191 (identifier && strcmp(coutput->name, identifier) == 0)) {
192 return true;
193 }
194 }
195 return false;
196 }
197
output_geometry(void * data,struct wl_output * wl_output,int32_t x,int32_t y,int32_t width_mm,int32_t height_mm,int32_t subpixel,const char * make,const char * model,int32_t transform)198 static void output_geometry(void *data, struct wl_output *wl_output, int32_t x,
199 int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel,
200 const char *make, const char *model, int32_t transform) {
201 struct swaybar_output *output = data;
202 output->subpixel = subpixel;
203 }
204
output_mode(void * data,struct wl_output * wl_output,uint32_t flags,int32_t width,int32_t height,int32_t refresh)205 static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags,
206 int32_t width, int32_t height, int32_t refresh) {
207 // Who cares
208 }
209
output_done(void * data,struct wl_output * wl_output)210 static void output_done(void *data, struct wl_output *wl_output) {
211 struct swaybar_output *output = data;
212 set_output_dirty(output);
213 }
214
output_scale(void * data,struct wl_output * wl_output,int32_t factor)215 static void output_scale(void *data, struct wl_output *wl_output,
216 int32_t factor) {
217 struct swaybar_output *output = data;
218 output->scale = factor;
219
220 bool render = false;
221 struct swaybar_seat *seat;
222 wl_list_for_each(seat, &output->bar->seats, link) {
223 if (output == seat->pointer.current) {
224 update_cursor(seat);
225 render = true;
226 }
227 };
228 if (render) {
229 render_frame(output);
230 }
231 }
232
233 struct wl_output_listener output_listener = {
234 .geometry = output_geometry,
235 .mode = output_mode,
236 .done = output_done,
237 .scale = output_scale,
238 };
239
xdg_output_handle_logical_position(void * data,struct zxdg_output_v1 * xdg_output,int32_t x,int32_t y)240 static void xdg_output_handle_logical_position(void *data,
241 struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
242 struct swaybar_output *output = data;
243 output->output_x = x;
244 output->output_y = y;
245 }
246
xdg_output_handle_logical_size(void * data,struct zxdg_output_v1 * xdg_output,int32_t width,int32_t height)247 static void xdg_output_handle_logical_size(void *data,
248 struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
249 struct swaybar_output *output = data;
250 output->output_height = height;
251 output->output_width = width;
252 }
253
xdg_output_handle_done(void * data,struct zxdg_output_v1 * xdg_output)254 static void xdg_output_handle_done(void *data,
255 struct zxdg_output_v1 *xdg_output) {
256 struct swaybar_output *output = data;
257 struct swaybar *bar = output->bar;
258
259 if (!wl_list_empty(&output->link)) {
260 return;
261 }
262
263 assert(output->name != NULL);
264 if (!bar_uses_output(output)) {
265 wl_list_remove(&output->link);
266 wl_list_insert(&bar->unused_outputs, &output->link);
267 return;
268 }
269
270 wl_list_remove(&output->link);
271 wl_list_insert(&bar->outputs, &output->link);
272
273 output->surface = wl_compositor_create_surface(bar->compositor);
274 assert(output->surface);
275
276 determine_bar_visibility(bar, false);
277
278 if (bar->running && bar->config->workspace_buttons) {
279 ipc_get_workspaces(bar);
280 }
281 }
282
xdg_output_handle_name(void * data,struct zxdg_output_v1 * xdg_output,const char * name)283 static void xdg_output_handle_name(void *data,
284 struct zxdg_output_v1 *xdg_output, const char *name) {
285 struct swaybar_output *output = data;
286 free(output->name);
287 output->name = strdup(name);
288 }
289
xdg_output_handle_description(void * data,struct zxdg_output_v1 * xdg_output,const char * description)290 static void xdg_output_handle_description(void *data,
291 struct zxdg_output_v1 *xdg_output, const char *description) {
292 // wlroots currently sets the description to `make model serial (name)`
293 // If this changes in the future, this will need to be modified.
294 struct swaybar_output *output = data;
295 free(output->identifier);
296 output->identifier = NULL;
297 char *paren = strrchr(description, '(');
298 if (paren) {
299 size_t length = paren - description;
300 output->identifier = malloc(length);
301 if (!output->identifier) {
302 sway_log(SWAY_ERROR, "Failed to allocate output identifier");
303 return;
304 }
305 strncpy(output->identifier, description, length);
306 output->identifier[length - 1] = '\0';
307 }
308 }
309
310 struct zxdg_output_v1_listener xdg_output_listener = {
311 .logical_position = xdg_output_handle_logical_position,
312 .logical_size = xdg_output_handle_logical_size,
313 .done = xdg_output_handle_done,
314 .name = xdg_output_handle_name,
315 .description = xdg_output_handle_description,
316 };
317
add_xdg_output(struct swaybar_output * output)318 static void add_xdg_output(struct swaybar_output *output) {
319 if (output->xdg_output != NULL) {
320 return;
321 }
322 assert(output->bar->xdg_output_manager != NULL);
323 output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
324 output->bar->xdg_output_manager, output->output);
325 zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener,
326 output);
327 }
328
handle_global(void * data,struct wl_registry * registry,uint32_t name,const char * interface,uint32_t version)329 static void handle_global(void *data, struct wl_registry *registry,
330 uint32_t name, const char *interface, uint32_t version) {
331 struct swaybar *bar = data;
332 if (strcmp(interface, wl_compositor_interface.name) == 0) {
333 bar->compositor = wl_registry_bind(registry, name,
334 &wl_compositor_interface, 4);
335 } else if (strcmp(interface, wl_seat_interface.name) == 0) {
336 struct swaybar_seat *seat = calloc(1, sizeof(struct swaybar_seat));
337 if (!seat) {
338 sway_abort("Failed to allocate swaybar_seat");
339 return;
340 }
341 seat->bar = bar;
342 seat->wl_name = name;
343 seat->wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 5);
344 wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
345 wl_list_insert(&bar->seats, &seat->link);
346 } else if (strcmp(interface, wl_shm_interface.name) == 0) {
347 bar->shm = wl_registry_bind(registry, name,
348 &wl_shm_interface, 1);
349 } else if (strcmp(interface, wl_output_interface.name) == 0) {
350 struct swaybar_output *output =
351 calloc(1, sizeof(struct swaybar_output));
352 output->bar = bar;
353 output->output = wl_registry_bind(registry, name,
354 &wl_output_interface, 3);
355 wl_output_add_listener(output->output, &output_listener, output);
356 output->scale = 1;
357 output->wl_name = name;
358 wl_list_init(&output->workspaces);
359 wl_list_init(&output->hotspots);
360 wl_list_init(&output->link);
361 if (bar->xdg_output_manager != NULL) {
362 add_xdg_output(output);
363 }
364 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
365 bar->layer_shell = wl_registry_bind(
366 registry, name, &zwlr_layer_shell_v1_interface, 1);
367 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
368 bar->xdg_output_manager = wl_registry_bind(registry, name,
369 &zxdg_output_manager_v1_interface, 2);
370 }
371 }
372
handle_global_remove(void * data,struct wl_registry * registry,uint32_t name)373 static void handle_global_remove(void *data, struct wl_registry *registry,
374 uint32_t name) {
375 struct swaybar *bar = data;
376 struct swaybar_output *output, *tmp;
377 wl_list_for_each_safe(output, tmp, &bar->outputs, link) {
378 if (output->wl_name == name) {
379 swaybar_output_free(output);
380 return;
381 }
382 }
383 wl_list_for_each_safe(output, tmp, &bar->unused_outputs, link) {
384 if (output->wl_name == name) {
385 swaybar_output_free(output);
386 return;
387 }
388 }
389 struct swaybar_seat *seat, *tmp_seat;
390 wl_list_for_each_safe(seat, tmp_seat, &bar->seats, link) {
391 if (seat->wl_name == name) {
392 swaybar_seat_free(seat);
393 return;
394 }
395 }
396 }
397
398 static const struct wl_registry_listener registry_listener = {
399 .global = handle_global,
400 .global_remove = handle_global_remove,
401 };
402
bar_setup(struct swaybar * bar,const char * socket_path)403 bool bar_setup(struct swaybar *bar, const char *socket_path) {
404 bar->visible = true;
405 bar->config = init_config();
406 wl_list_init(&bar->outputs);
407 wl_list_init(&bar->unused_outputs);
408 wl_list_init(&bar->seats);
409 bar->eventloop = loop_create();
410
411 bar->ipc_socketfd = ipc_open_socket(socket_path);
412 bar->ipc_event_socketfd = ipc_open_socket(socket_path);
413 if (!ipc_initialize(bar)) {
414 return false;
415 }
416
417 bar->display = wl_display_connect(NULL);
418 if (!bar->display) {
419 sway_abort("Unable to connect to the compositor. "
420 "If your compositor is running, check or set the "
421 "WAYLAND_DISPLAY environment variable.");
422 }
423
424 struct wl_registry *registry = wl_display_get_registry(bar->display);
425 wl_registry_add_listener(registry, ®istry_listener, bar);
426 wl_display_roundtrip(bar->display);
427 assert(bar->compositor && bar->layer_shell && bar->shm &&
428 bar->xdg_output_manager);
429
430 // Second roundtrip for xdg-output
431 wl_display_roundtrip(bar->display);
432
433 struct swaybar_seat *seat;
434 wl_list_for_each(seat, &bar->seats, link) {
435 struct swaybar_pointer *pointer = &seat->pointer;
436 if (!pointer) {
437 continue;
438 }
439 pointer->cursor_surface =
440 wl_compositor_create_surface(bar->compositor);
441 assert(pointer->cursor_surface);
442 }
443
444 if (bar->config->status_command) {
445 bar->status = status_line_init(bar->config->status_command);
446 bar->status->bar = bar;
447 }
448
449 #if HAVE_TRAY
450 if (!bar->config->tray_hidden) {
451 bar->tray = create_tray(bar);
452 }
453 #endif
454
455 if (bar->config->workspace_buttons) {
456 ipc_get_workspaces(bar);
457 }
458 determine_bar_visibility(bar, false);
459 return true;
460 }
461
display_in(int fd,short mask,void * data)462 static void display_in(int fd, short mask, void *data) {
463 struct swaybar *bar = data;
464 if (wl_display_dispatch(bar->display) == -1) {
465 bar->running = false;
466 }
467 }
468
ipc_in(int fd,short mask,void * data)469 static void ipc_in(int fd, short mask, void *data) {
470 struct swaybar *bar = data;
471 if (handle_ipc_readable(bar)) {
472 set_bar_dirty(bar);
473 }
474 }
475
status_in(int fd,short mask,void * data)476 void status_in(int fd, short mask, void *data) {
477 struct swaybar *bar = data;
478 if (mask & (POLLHUP | POLLERR)) {
479 status_error(bar->status, "[error reading from status command]");
480 set_bar_dirty(bar);
481 loop_remove_fd(bar->eventloop, fd);
482 } else if (status_handle_readable(bar->status)) {
483 set_bar_dirty(bar);
484 }
485 }
486
bar_run(struct swaybar * bar)487 void bar_run(struct swaybar *bar) {
488 loop_add_fd(bar->eventloop, wl_display_get_fd(bar->display), POLLIN,
489 display_in, bar);
490 loop_add_fd(bar->eventloop, bar->ipc_event_socketfd, POLLIN, ipc_in, bar);
491 if (bar->status) {
492 loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN,
493 status_in, bar);
494 }
495 #if HAVE_TRAY
496 if (bar->tray) {
497 loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus);
498 }
499 #endif
500 while (bar->running) {
501 errno = 0;
502 if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) {
503 break;
504 }
505 loop_poll(bar->eventloop);
506 }
507 }
508
free_outputs(struct wl_list * list)509 static void free_outputs(struct wl_list *list) {
510 struct swaybar_output *output, *tmp;
511 wl_list_for_each_safe(output, tmp, list, link) {
512 swaybar_output_free(output);
513 }
514 }
515
free_seats(struct wl_list * list)516 static void free_seats(struct wl_list *list) {
517 struct swaybar_seat *seat, *tmp;
518 wl_list_for_each_safe(seat, tmp, list, link) {
519 swaybar_seat_free(seat);
520 }
521 }
522
bar_teardown(struct swaybar * bar)523 void bar_teardown(struct swaybar *bar) {
524 #if HAVE_TRAY
525 destroy_tray(bar->tray);
526 #endif
527 free_outputs(&bar->outputs);
528 free_outputs(&bar->unused_outputs);
529 free_seats(&bar->seats);
530 if (bar->config) {
531 free_config(bar->config);
532 }
533 close(bar->ipc_event_socketfd);
534 close(bar->ipc_socketfd);
535 if (bar->status) {
536 status_line_free(bar->status);
537 }
538 free(bar->id);
539 free(bar->mode);
540 }
541