aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Brian Ashworth <bosrsf04@gmail.com>2019-04-03 21:53:43 -0400
committerLibravatar Simon Ser <contact@emersion.fr>2019-04-04 20:08:21 +0300
commit75e7bd24ccb9731065bb7f8313aa53ba11ddc420 (patch)
tree2522f31626a21b42c2496a32266eafd3b7d66f50
parentidle_inhibit: fix crash during view destruction (diff)
downloadsway-75e7bd24ccb9731065bb7f8313aa53ba11ddc420.tar.gz
sway-75e7bd24ccb9731065bb7f8313aa53ba11ddc420.tar.zst
sway-75e7bd24ccb9731065bb7f8313aa53ba11ddc420.zip
swaybg: one instance for all outputs
This makes it so there will only be one swaybg instance running instead of one per output. swaybg's cli has been changed to a xrandr like interface, where you select an output and then change properties for that output and then select another output and repeat. This also makes it so swaybg is only killed and respawned when a background changes or when reloading.
-rw-r--r--include/sway/config.h10
-rw-r--r--include/sway/output.h3
-rw-r--r--sway/commands/output.c5
-rw-r--r--sway/config.c4
-rw-r--r--sway/config/output.c252
-rw-r--r--sway/desktop/output.c2
-rw-r--r--sway/tree/output.c4
-rw-r--r--swaybg/main.c452
8 files changed, 489 insertions, 243 deletions
diff --git a/include/sway/config.h b/include/sway/config.h
index d49120a0..fe06fb9d 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -424,7 +424,6 @@ struct sway_config {
424 list_t *active_bar_modifiers; 424 list_t *active_bar_modifiers;
425 struct sway_mode *current_mode; 425 struct sway_mode *current_mode;
426 struct bar_config *current_bar; 426 struct bar_config *current_bar;
427 char *swaybg_command;
428 uint32_t floating_mod; 427 uint32_t floating_mod;
429 bool floating_mod_inverse; 428 bool floating_mod_inverse;
430 uint32_t dragging_key; 429 uint32_t dragging_key;
@@ -447,6 +446,11 @@ struct sway_config {
447 enum sway_popup_during_fullscreen popup_during_fullscreen; 446 enum sway_popup_during_fullscreen popup_during_fullscreen;
448 bool xwayland; 447 bool xwayland;
449 448
449 // swaybg
450 char *swaybg_command;
451 struct wl_client *swaybg_client;
452 struct wl_listener swaybg_client_destroy;
453
450 // Flags 454 // Flags
451 enum focus_follows_mouse_mode focus_follows_mouse; 455 enum focus_follows_mouse_mode focus_follows_mouse;
452 enum mouse_warping_mode mouse_warping; 456 enum mouse_warping_mode mouse_warping;
@@ -607,6 +611,8 @@ void reset_outputs(void);
607 611
608void free_output_config(struct output_config *oc); 612void free_output_config(struct output_config *oc);
609 613
614bool spawn_swaybg(void);
615
610int workspace_output_cmp_workspace(const void *a, const void *b); 616int workspace_output_cmp_workspace(const void *a, const void *b);
611 617
612int sway_binding_cmp(const void *a, const void *b); 618int sway_binding_cmp(const void *a, const void *b);
@@ -625,8 +631,6 @@ void load_swaybar(struct bar_config *bar);
625 631
626void load_swaybars(void); 632void load_swaybars(void);
627 633
628void terminate_swaybg(pid_t pid);
629
630struct bar_config *default_bar_config(void); 634struct bar_config *default_bar_config(void);
631 635
632void free_bar_config(struct bar_config *bar); 636void free_bar_config(struct bar_config *bar);
diff --git a/include/sway/output.h b/include/sway/output.h
index c336c559..cae77e2e 100644
--- a/include/sway/output.h
+++ b/include/sway/output.h
@@ -38,8 +38,6 @@ struct sway_output {
38 38
39 struct sway_output_state current; 39 struct sway_output_state current;
40 40
41 struct wl_client *swaybg_client;
42
43 struct wl_listener destroy; 41 struct wl_listener destroy;
44 struct wl_listener mode; 42 struct wl_listener mode;
45 struct wl_listener transform; 43 struct wl_listener transform;
@@ -47,7 +45,6 @@ struct sway_output {
47 struct wl_listener present; 45 struct wl_listener present;
48 struct wl_listener damage_destroy; 46 struct wl_listener damage_destroy;
49 struct wl_listener damage_frame; 47 struct wl_listener damage_frame;
50 struct wl_listener swaybg_client_destroy;
51 48
52 struct { 49 struct {
53 struct wl_signal destroy; 50 struct wl_signal destroy;
diff --git a/sway/commands/output.c b/sway/commands/output.c
index 44e28512..6b9eafdb 100644
--- a/sway/commands/output.c
+++ b/sway/commands/output.c
@@ -68,6 +68,8 @@ struct cmd_results *cmd_output(int argc, char **argv) {
68 config->handler_context.leftovers.argc = 0; 68 config->handler_context.leftovers.argc = 0;
69 config->handler_context.leftovers.argv = NULL; 69 config->handler_context.leftovers.argv = NULL;
70 70
71 bool background = output->background;
72
71 output = store_output_config(output); 73 output = store_output_config(output);
72 74
73 // If reloading, the output configs will be applied after reading the 75 // If reloading, the output configs will be applied after reading the
@@ -75,6 +77,9 @@ struct cmd_results *cmd_output(int argc, char **argv) {
75 // workspace name is not given to re-enabled outputs. 77 // workspace name is not given to re-enabled outputs.
76 if (!config->reloading) { 78 if (!config->reloading) {
77 apply_output_config_to_outputs(output); 79 apply_output_config_to_outputs(output);
80 if (background) {
81 spawn_swaybg();
82 }
78 } 83 }
79 84
80 return cmd_results_new(CMD_SUCCESS, NULL); 85 return cmd_results_new(CMD_SUCCESS, NULL);
diff --git a/sway/config.c b/sway/config.c
index 7104f55d..d5bfe105 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -104,6 +104,9 @@ void free_config(struct sway_config *config) {
104 } 104 }
105 list_free(config->output_configs); 105 list_free(config->output_configs);
106 } 106 }
107 if (config->swaybg_client != NULL) {
108 wl_client_destroy(config->swaybg_client);
109 }
107 if (config->input_configs) { 110 if (config->input_configs) {
108 for (int i = 0; i < config->input_configs->length; i++) { 111 for (int i = 0; i < config->input_configs->length; i++) {
109 free_input_config(config->input_configs->items[i]); 112 free_input_config(config->input_configs->items[i]);
@@ -480,6 +483,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) {
480 483
481 if (is_active) { 484 if (is_active) {
482 reset_outputs(); 485 reset_outputs();
486 spawn_swaybg();
483 487
484 config->reloading = false; 488 config->reloading = false;
485 if (config->swaynag_config_errors.pid > 0) { 489 if (config->swaynag_config_errors.pid > 0) {
diff --git a/sway/config/output.c b/sway/config/output.c
index d06051b3..0473d0ad 100644
--- a/sway/config/output.c
+++ b/sway/config/output.c
@@ -228,91 +228,6 @@ static bool set_mode(struct wlr_output *output, int width, int height,
228 return wlr_output_set_mode(output, best); 228 return wlr_output_set_mode(output, best);
229} 229}
230 230
231static void handle_swaybg_client_destroy(struct wl_listener *listener,
232 void *data) {
233 struct sway_output *output =
234 wl_container_of(listener, output, swaybg_client_destroy);
235 wl_list_remove(&output->swaybg_client_destroy.link);
236 wl_list_init(&output->swaybg_client_destroy.link);
237 output->swaybg_client = NULL;
238}
239
240static bool set_cloexec(int fd, bool cloexec) {
241 int flags = fcntl(fd, F_GETFD);
242 if (flags == -1) {
243 sway_log_errno(SWAY_ERROR, "fcntl failed");
244 return false;
245 }
246 if (cloexec) {
247 flags = flags | FD_CLOEXEC;
248 } else {
249 flags = flags & ~FD_CLOEXEC;
250 }
251 if (fcntl(fd, F_SETFD, flags) == -1) {
252 sway_log_errno(SWAY_ERROR, "fcntl failed");
253 return false;
254 }
255 return true;
256}
257
258static bool spawn_swaybg(struct sway_output *output, char *const cmd[]) {
259 int sockets[2];
260 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) {
261 sway_log_errno(SWAY_ERROR, "socketpair failed");
262 return false;
263 }
264 if (!set_cloexec(sockets[0], true) || !set_cloexec(sockets[1], true)) {
265 return false;
266 }
267
268 output->swaybg_client = wl_client_create(server.wl_display, sockets[0]);
269 if (output->swaybg_client == NULL) {
270 sway_log_errno(SWAY_ERROR, "wl_client_create failed");
271 return false;
272 }
273
274 output->swaybg_client_destroy.notify = handle_swaybg_client_destroy;
275 wl_client_add_destroy_listener(output->swaybg_client,
276 &output->swaybg_client_destroy);
277
278 pid_t pid = fork();
279 if (pid < 0) {
280 sway_log_errno(SWAY_ERROR, "fork failed");
281 return false;
282 } else if (pid == 0) {
283 pid = fork();
284 if (pid < 0) {
285 sway_log_errno(SWAY_ERROR, "fork failed");
286 _exit(EXIT_FAILURE);
287 } else if (pid == 0) {
288 if (!set_cloexec(sockets[1], false)) {
289 _exit(EXIT_FAILURE);
290 }
291
292 char wayland_socket_str[16];
293 snprintf(wayland_socket_str, sizeof(wayland_socket_str),
294 "%d", sockets[1]);
295 setenv("WAYLAND_SOCKET", wayland_socket_str, true);
296
297 execvp(cmd[0], cmd);
298 sway_log_errno(SWAY_ERROR, "execvp failed");
299 _exit(EXIT_FAILURE);
300 }
301 _exit(EXIT_SUCCESS);
302 }
303
304 if (close(sockets[1]) != 0) {
305 sway_log_errno(SWAY_ERROR, "close failed");
306 return false;
307 }
308 if (waitpid(pid, NULL, 0) < 0) {
309 sway_log_errno(SWAY_ERROR, "waitpid failed");
310 return false;
311 }
312
313 return true;
314}
315
316bool apply_output_config(struct output_config *oc, struct sway_output *output) { 231bool apply_output_config(struct output_config *oc, struct sway_output *output) {
317 if (output == root->noop_output) { 232 if (output == root->noop_output) {
318 return false; 233 return false;
@@ -397,25 +312,6 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
397 wlr_output_transformed_resolution(wlr_output, 312 wlr_output_transformed_resolution(wlr_output,
398 &output->width, &output->height); 313 &output->width, &output->height);
399 314
400 if (output->swaybg_client != NULL) {
401 wl_client_destroy(output->swaybg_client);
402 }
403 if (oc && oc->background && config->swaybg_command) {
404 sway_log(SWAY_DEBUG, "Setting background for output %s to %s",
405 wlr_output->name, oc->background);
406
407 char *const cmd[] = {
408 config->swaybg_command,
409 wlr_output->name,
410 oc->background,
411 oc->background_option,
412 oc->background_fallback ? oc->background_fallback : NULL,
413 NULL,
414 };
415 if (!spawn_swaybg(output, cmd)) {
416 return false;
417 }
418 }
419 315
420 if (oc && oc->dpms_state == DPMS_OFF) { 316 if (oc && oc->dpms_state == DPMS_OFF) {
421 sway_log(SWAY_DEBUG, "Turning off screen"); 317 sway_log(SWAY_DEBUG, "Turning off screen");
@@ -584,3 +480,151 @@ void free_output_config(struct output_config *oc) {
584 free(oc->background_option); 480 free(oc->background_option);
585 free(oc); 481 free(oc);
586} 482}
483
484static void handle_swaybg_client_destroy(struct wl_listener *listener,
485 void *data) {
486 wl_list_remove(&config->swaybg_client_destroy.link);
487 wl_list_init(&config->swaybg_client_destroy.link);
488 config->swaybg_client = NULL;
489}
490
491static bool set_cloexec(int fd, bool cloexec) {
492 int flags = fcntl(fd, F_GETFD);
493 if (flags == -1) {
494 sway_log_errno(SWAY_ERROR, "fcntl failed");
495 return false;
496 }
497 if (cloexec) {
498 flags = flags | FD_CLOEXEC;
499 } else {
500 flags = flags & ~FD_CLOEXEC;
501 }
502 if (fcntl(fd, F_SETFD, flags) == -1) {
503 sway_log_errno(SWAY_ERROR, "fcntl failed");
504 return false;
505 }
506 return true;
507}
508
509static bool _spawn_swaybg(char **command) {
510 if (config->swaybg_client != NULL) {
511 wl_client_destroy(config->swaybg_client);
512 }
513 int sockets[2];
514 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) {
515 sway_log_errno(SWAY_ERROR, "socketpair failed");
516 return false;
517 }
518 if (!set_cloexec(sockets[0], true) || !set_cloexec(sockets[1], true)) {
519 return false;
520 }
521
522 config->swaybg_client = wl_client_create(server.wl_display, sockets[0]);
523 if (config->swaybg_client == NULL) {
524 sway_log_errno(SWAY_ERROR, "wl_client_create failed");
525 return false;
526 }
527
528 config->swaybg_client_destroy.notify = handle_swaybg_client_destroy;
529 wl_client_add_destroy_listener(config->swaybg_client,
530 &config->swaybg_client_destroy);
531
532 pid_t pid = fork();
533 if (pid < 0) {
534 sway_log_errno(SWAY_ERROR, "fork failed");
535 return false;
536 } else if (pid == 0) {
537 pid = fork();
538 if (pid < 0) {
539 sway_log_errno(SWAY_ERROR, "fork failed");
540 _exit(EXIT_FAILURE);
541 } else if (pid == 0) {
542 if (!set_cloexec(sockets[1], false)) {
543 _exit(EXIT_FAILURE);
544 }
545
546 char wayland_socket_str[16];
547 snprintf(wayland_socket_str, sizeof(wayland_socket_str),
548 "%d", sockets[1]);
549 setenv("WAYLAND_SOCKET", wayland_socket_str, true);
550
551 execvp(command[0], command);
552 sway_log_errno(SWAY_ERROR, "execvp failed");
553 _exit(EXIT_FAILURE);
554 }
555 _exit(EXIT_SUCCESS);
556 }
557
558 if (close(sockets[1]) != 0) {
559 sway_log_errno(SWAY_ERROR, "close failed");
560 return false;
561 }
562 if (waitpid(pid, NULL, 0) < 0) {
563 sway_log_errno(SWAY_ERROR, "waitpid failed");
564 return false;
565 }
566
567 return true;
568}
569
570bool spawn_swaybg(void) {
571 if (!config->swaybg_command) {
572 return true;
573 }
574
575 size_t length = 2;
576 for (int i = 0; i < config->output_configs->length; i++) {
577 struct output_config *oc = config->output_configs->items[i];
578 if (!oc->background) {
579 continue;
580 }
581 if (strcmp(oc->background_option, "solid_color") == 0) {
582 length += 4;
583 } else if (oc->background_fallback) {
584 length += 8;
585 } else {
586 length += 6;
587 }
588 }
589
590 char **cmd = calloc(1, sizeof(char **) * length);
591 if (!cmd) {
592 sway_log(SWAY_ERROR, "Failed to allocate spawn_swaybg command");
593 return false;
594 }
595
596 size_t i = 0;
597 cmd[i++] = config->swaybg_command;
598 for (int j = 0; j < config->output_configs->length; j++) {
599 struct output_config *oc = config->output_configs->items[j];
600 if (!oc->background) {
601 continue;
602 }
603 if (strcmp(oc->background_option, "solid_color") == 0) {
604 cmd[i++] = "-o";
605 cmd[i++] = oc->name;
606 cmd[i++] = "-c";
607 cmd[i++] = oc->background;
608 } else {
609 cmd[i++] = "-o";
610 cmd[i++] = oc->name;
611 cmd[i++] = "-i";
612 cmd[i++] = oc->background;
613 cmd[i++] = "-m";
614 cmd[i++] = oc->background_option;
615 if (oc->background_fallback) {
616 cmd[i++] = "-c";
617 cmd[i++] = oc->background_fallback;
618 }
619 }
620 assert(i <= length);
621 }
622
623 for (size_t k = 0; k < i; k++) {
624 sway_log(SWAY_DEBUG, "spawn_swaybg cmd[%ld] = %s", k, cmd[k]);
625 }
626
627 bool result = _spawn_swaybg(cmd);
628 free(cmd);
629 return result;
630}
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 9d0c0ef5..0b3e1edb 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -525,7 +525,6 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
525 wl_list_remove(&output->present.link); 525 wl_list_remove(&output->present.link);
526 wl_list_remove(&output->damage_destroy.link); 526 wl_list_remove(&output->damage_destroy.link);
527 wl_list_remove(&output->damage_frame.link); 527 wl_list_remove(&output->damage_frame.link);
528 wl_list_remove(&output->swaybg_client_destroy.link);
529 528
530 transaction_commit_dirty(); 529 transaction_commit_dirty();
531} 530}
@@ -632,7 +631,6 @@ void handle_new_output(struct wl_listener *listener, void *data) {
632 output->damage_frame.notify = damage_handle_frame; 631 output->damage_frame.notify = damage_handle_frame;
633 wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); 632 wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
634 output->damage_destroy.notify = damage_handle_destroy; 633 output->damage_destroy.notify = damage_handle_destroy;
635 wl_list_init(&output->swaybg_client_destroy.link);
636 634
637 struct output_config *oc = find_output_config(output); 635 struct output_config *oc = find_output_config(output);
638 if (!oc || oc->enabled) { 636 if (!oc || oc->enabled) {
diff --git a/sway/tree/output.c b/sway/tree/output.c
index b3589be5..24adc08d 100644
--- a/sway/tree/output.c
+++ b/sway/tree/output.c
@@ -262,10 +262,6 @@ void output_disable(struct sway_output *output) {
262 262
263 root_for_each_container(untrack_output, output); 263 root_for_each_container(untrack_output, output);
264 264
265 if (output->swaybg_client != NULL) {
266 wl_client_destroy(output->swaybg_client);
267 }
268
269 int index = list_find(root->outputs, output); 265 int index = list_find(root->outputs, output);
270 list_del(root->outputs, index); 266 list_del(root->outputs, index);
271 267
diff --git a/swaybg/main.c b/swaybg/main.c
index e66221f0..b983dd6a 100644
--- a/swaybg/main.c
+++ b/swaybg/main.c
@@ -1,10 +1,12 @@
1#define _POSIX_C_SOURCE 200809L
1#include <assert.h> 2#include <assert.h>
2#include <ctype.h> 3#include <ctype.h>
4#include <getopt.h>
3#include <stdbool.h> 5#include <stdbool.h>
4#include <stdio.h> 6#include <stdio.h>
5#include <stdlib.h> 7#include <stdlib.h>
6#include <string.h> 8#include <string.h>
7#include <time.h> 9#include <strings.h>
8#include <wayland-client.h> 10#include <wayland-client.h>
9#include "background-image.h" 11#include "background-image.h"
10#include "cairo.h" 12#include "cairo.h"
@@ -14,49 +16,44 @@
14#include "wlr-layer-shell-unstable-v1-client-protocol.h" 16#include "wlr-layer-shell-unstable-v1-client-protocol.h"
15#include "xdg-output-unstable-v1-client-protocol.h" 17#include "xdg-output-unstable-v1-client-protocol.h"
16 18
17struct swaybg_state; 19struct swaybg_state {
18 20 struct wl_display *display;
19struct swaybg_args { 21 struct wl_compositor *compositor;
20 const char *output; 22 struct wl_shm *shm;
21 const char *path; 23 struct zwlr_layer_shell_v1 *layer_shell;
22 enum background_mode mode; 24 struct zxdg_output_manager_v1 *xdg_output_manager;
23 const char *fallback; 25 struct wl_list configs; // struct swaybg_output_config::link
26 struct wl_list outputs; // struct swaybg_output::link
27 bool run_display;
24}; 28};
25 29
26struct swaybg_context { 30struct swaybg_output_config {
27 uint32_t color; 31 char *output;
28 cairo_surface_t *image; 32 cairo_surface_t *image;
33 enum background_mode mode;
34 uint32_t color;
35 struct wl_list link;
29}; 36};
30 37
31struct swaybg_output { 38struct swaybg_output {
39 uint32_t wl_name;
32 struct wl_output *wl_output; 40 struct wl_output *wl_output;
33 struct zxdg_output_v1 *xdg_output; 41 struct zxdg_output_v1 *xdg_output;
34 struct swaybg_state *state; 42 char *name;
35 struct wl_list link; 43 char *identifier;
36 44
37 int32_t scale; 45 struct swaybg_state *state;
38}; 46 struct swaybg_output_config *config;
39
40struct swaybg_state {
41 const struct swaybg_args *args;
42 struct swaybg_context context;
43
44 struct wl_display *display;
45 struct wl_compositor *compositor;
46 struct wl_shm *shm;
47 struct wl_list outputs;
48 struct zwlr_layer_shell_v1 *layer_shell;
49 struct zxdg_output_manager_v1 *xdg_output_manager;
50 47
51 struct swaybg_output *output;
52 struct wl_surface *surface; 48 struct wl_surface *surface;
53 struct wl_region *input_region;
54 struct zwlr_layer_surface_v1 *layer_surface; 49 struct zwlr_layer_surface_v1 *layer_surface;
55
56 bool run_display;
57 uint32_t width, height;
58 struct pool_buffer buffers[2]; 50 struct pool_buffer buffers[2];
59 struct pool_buffer *current_buffer; 51 struct pool_buffer *current_buffer;
52
53 uint32_t width, height;
54 int32_t scale;
55
56 struct wl_list link;
60}; 57};
61 58
62bool is_valid_color(const char *color) { 59bool is_valid_color(const char *color) {
@@ -77,68 +74,82 @@ bool is_valid_color(const char *color) {
77 return true; 74 return true;
78} 75}
79 76
80static void render_frame(struct swaybg_state *state) { 77static void render_frame(struct swaybg_output *output) {
81 int buffer_width = state->width * state->output->scale, 78 int buffer_width = output->width * output->scale,
82 buffer_height = state->height * state->output->scale; 79 buffer_height = output->height * output->scale;
83 state->current_buffer = get_next_buffer(state->shm, 80 output->current_buffer = get_next_buffer(output->state->shm,
84 state->buffers, buffer_width, buffer_height); 81 output->buffers, buffer_width, buffer_height);
85 if (!state->current_buffer) { 82 if (!output->current_buffer) {
86 return; 83 return;
87 } 84 }
88 cairo_t *cairo = state->current_buffer->cairo; 85 cairo_t *cairo = output->current_buffer->cairo;
89 cairo_save(cairo); 86 cairo_save(cairo);
90 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); 87 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
91 cairo_paint(cairo); 88 cairo_paint(cairo);
92 cairo_restore(cairo); 89 cairo_restore(cairo);
93 if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { 90 if (output->config->mode == BACKGROUND_MODE_SOLID_COLOR) {
94 cairo_set_source_u32(cairo, state->context.color); 91 cairo_set_source_u32(cairo, output->config->color);
95 cairo_paint(cairo); 92 cairo_paint(cairo);
96 } else { 93 } else {
97 if (state->args->fallback && state->context.color) { 94 if (output->config->color) {
98 cairo_set_source_u32(cairo, state->context.color); 95 cairo_set_source_u32(cairo, output->config->color);
99 cairo_paint(cairo); 96 cairo_paint(cairo);
100 } 97 }
101 render_background_image(cairo, state->context.image, 98 render_background_image(cairo, output->config->image,
102 state->args->mode, buffer_width, buffer_height); 99 output->config->mode, buffer_width, buffer_height);
103 } 100 }
104 101
105 wl_surface_set_buffer_scale(state->surface, state->output->scale); 102 wl_surface_set_buffer_scale(output->surface, output->scale);
106 wl_surface_attach(state->surface, state->current_buffer->buffer, 0, 0); 103 wl_surface_attach(output->surface, output->current_buffer->buffer, 0, 0);
107 wl_surface_damage_buffer(state->surface, 0, 0, INT32_MAX, INT32_MAX); 104 wl_surface_damage_buffer(output->surface, 0, 0, INT32_MAX, INT32_MAX);
108 wl_surface_commit(state->surface); 105 wl_surface_commit(output->surface);
109} 106}
110 107
111static bool prepare_context(struct swaybg_state *state) { 108static void destroy_swaybg_output_config(struct swaybg_output_config *config) {
112 if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { 109 if (!config) {
113 state->context.color = parse_color(state->args->path); 110 return;
114 return is_valid_color(state->args->path);
115 } 111 }
116 if (state->args->fallback && is_valid_color(state->args->fallback)) { 112 wl_list_remove(&config->link);
117 state->context.color = parse_color(state->args->fallback); 113 free(config->output);
114 free(config);
115}
116
117static void destroy_swaybg_output(struct swaybg_output *output) {
118 if (!output) {
119 return;
118 } 120 }
119 if (!(state->context.image = load_background_image(state->args->path))) { 121 wl_list_remove(&output->link);
120 return false; 122 if (output->layer_surface != NULL) {
123 zwlr_layer_surface_v1_destroy(output->layer_surface);
121 } 124 }
122 return true; 125 if (output->surface != NULL) {
126 wl_surface_destroy(output->surface);
127 }
128 zxdg_output_v1_destroy(output->xdg_output);
129 wl_output_destroy(output->wl_output);
130 destroy_buffer(&output->buffers[0]);
131 destroy_buffer(&output->buffers[1]);
132 free(output->name);
133 free(output->identifier);
134 free(output);
123} 135}
124 136
125static void layer_surface_configure(void *data, 137static void layer_surface_configure(void *data,
126 struct zwlr_layer_surface_v1 *surface, 138 struct zwlr_layer_surface_v1 *surface,
127 uint32_t serial, uint32_t width, uint32_t height) { 139 uint32_t serial, uint32_t width, uint32_t height) {
128 struct swaybg_state *state = data; 140 struct swaybg_output *output = data;
129 state->width = width; 141 output->width = width;
130 state->height = height; 142 output->height = height;
131 zwlr_layer_surface_v1_ack_configure(surface, serial); 143 zwlr_layer_surface_v1_ack_configure(surface, serial);
132 render_frame(state); 144 render_frame(output);
133} 145}
134 146
135static void layer_surface_closed(void *data, 147static void layer_surface_closed(void *data,
136 struct zwlr_layer_surface_v1 *surface) { 148 struct zwlr_layer_surface_v1 *surface) {
137 struct swaybg_state *state = data; 149 struct swaybg_output *output = data;
138 zwlr_layer_surface_v1_destroy(state->layer_surface); 150 sway_log(SWAY_DEBUG, "Destroying output %s (%s)",
139 wl_surface_destroy(state->surface); 151 output->name, output->identifier);
140 wl_region_destroy(state->input_region); 152 destroy_swaybg_output(output);
141 state->run_display = false;
142} 153}
143 154
144static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { 155static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
@@ -164,12 +175,9 @@ static void output_done(void *data, struct wl_output *output) {
164static void output_scale(void *data, struct wl_output *wl_output, 175static void output_scale(void *data, struct wl_output *wl_output,
165 int32_t scale) { 176 int32_t scale) {
166 struct swaybg_output *output = data; 177 struct swaybg_output *output = data;
167 struct swaybg_state *state = output->state;
168
169 output->scale = scale; 178 output->scale = scale;
170 179 if (output->state->run_display && output->width > 0 && output->height > 0) {
171 if (state->output == output && state->run_display) { 180 render_frame(output);
172 render_frame(state);
173 } 181 }
174} 182}
175 183
@@ -190,24 +198,91 @@ static void xdg_output_handle_logical_size(void *data,
190 // Who cares 198 // Who cares
191} 199}
192 200
201static void find_config(struct swaybg_output *output, const char *name) {
202 struct swaybg_output_config *config = NULL;
203 wl_list_for_each(config, &output->state->configs, link) {
204 if (strcmp(config->output, name) == 0) {
205 output->config = config;
206 return;
207 } else if (!output->config && strcmp(config->output, "*") == 0) {
208 output->config = config;
209 }
210 }
211}
212
193static void xdg_output_handle_name(void *data, 213static void xdg_output_handle_name(void *data,
194 struct zxdg_output_v1 *xdg_output, const char *name) { 214 struct zxdg_output_v1 *xdg_output, const char *name) {
195 struct swaybg_output *output = data; 215 struct swaybg_output *output = data;
196 struct swaybg_state *state = output->state; 216 output->name = strdup(name);
197 if (strcmp(name, state->args->output) == 0) { 217
198 assert(state->output == NULL); 218 // If description was sent first, the config may already be populated. If
199 state->output = output; 219 // there is an identifier config set, keep it.
220 if (!output->config || strcmp(output->config->output, "*") == 0) {
221 find_config(output, name);
200 } 222 }
201} 223}
202 224
203static void xdg_output_handle_description(void *data, 225static void xdg_output_handle_description(void *data,
204 struct zxdg_output_v1 *xdg_output, const char *description) { 226 struct zxdg_output_v1 *xdg_output, const char *description) {
205 // Who cares 227 struct swaybg_output *output = data;
228
229 // wlroots currently sets the description to `make model serial (name)`
230 // If this changes in the future, this will need to be modified.
231 char *paren = strrchr(description, '(');
232 if (paren) {
233 size_t length = paren - description;
234 output->identifier = malloc(length);
235 if (!output->identifier) {
236 sway_log(SWAY_ERROR, "Failed to allocate output identifier");
237 return;
238 }
239 strncpy(output->identifier, description, length);
240 output->identifier[length - 1] = '\0';
241
242 find_config(output, output->identifier);
243 }
244}
245
246static void create_layer_surface(struct swaybg_output *output) {
247 output->surface = wl_compositor_create_surface(output->state->compositor);
248 assert(output->surface);
249
250 // Empty input region
251 struct wl_region *input_region =
252 wl_compositor_create_region(output->state->compositor);
253 assert(input_region);
254 wl_surface_set_input_region(output->surface, input_region);
255 wl_region_destroy(input_region);
256
257 output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
258 output->state->layer_shell, output->surface, output->wl_output,
259 ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "wallpaper");
260 assert(output->layer_surface);
261
262 zwlr_layer_surface_v1_set_size(output->layer_surface, 0, 0);
263 zwlr_layer_surface_v1_set_anchor(output->layer_surface,
264 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
265 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
266 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
267 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
268 zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1);
269 zwlr_layer_surface_v1_add_listener(output->layer_surface,
270 &layer_surface_listener, output);
271 wl_surface_commit(output->surface);
206} 272}
207 273
208static void xdg_output_handle_done(void *data, 274static void xdg_output_handle_done(void *data,
209 struct zxdg_output_v1 *xdg_output) { 275 struct zxdg_output_v1 *xdg_output) {
210 // Who cares 276 struct swaybg_output *output = data;
277 if (!output->config) {
278 sway_log(SWAY_DEBUG, "Could not find config for output %s (%s)",
279 output->name, output->identifier);
280 destroy_swaybg_output(output);
281 } else if (!output->layer_surface) {
282 sway_log(SWAY_DEBUG, "Found config %s for output %s (%s)",
283 output->config->output, output->name, output->identifier);
284 create_layer_surface(output);
285 }
211} 286}
212 287
213static const struct zxdg_output_v1_listener xdg_output_listener = { 288static const struct zxdg_output_v1_listener xdg_output_listener = {
@@ -229,10 +304,18 @@ static void handle_global(void *data, struct wl_registry *registry,
229 } else if (strcmp(interface, wl_output_interface.name) == 0) { 304 } else if (strcmp(interface, wl_output_interface.name) == 0) {
230 struct swaybg_output *output = calloc(1, sizeof(struct swaybg_output)); 305 struct swaybg_output *output = calloc(1, sizeof(struct swaybg_output));
231 output->state = state; 306 output->state = state;
307 output->wl_name = name;
232 output->wl_output = 308 output->wl_output =
233 wl_registry_bind(registry, name, &wl_output_interface, 3); 309 wl_registry_bind(registry, name, &wl_output_interface, 3);
234 wl_output_add_listener(output->wl_output, &output_listener, output); 310 wl_output_add_listener(output->wl_output, &output_listener, output);
235 wl_list_insert(&state->outputs, &output->link); 311 wl_list_insert(&state->outputs, &output->link);
312
313 if (state->run_display) {
314 output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
315 state->xdg_output_manager, output->wl_output);
316 zxdg_output_v1_add_listener(output->xdg_output,
317 &xdg_output_listener, output);
318 }
236 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { 319 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
237 state->layer_shell = 320 state->layer_shell =
238 wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); 321 wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1);
@@ -244,7 +327,16 @@ static void handle_global(void *data, struct wl_registry *registry,
244 327
245static void handle_global_remove(void *data, struct wl_registry *registry, 328static void handle_global_remove(void *data, struct wl_registry *registry,
246 uint32_t name) { 329 uint32_t name) {
247 // who cares 330 struct swaybg_state *state = data;
331 struct swaybg_output *output, *tmp;
332 wl_list_for_each_safe(output, tmp, &state->outputs, link) {
333 if (output->wl_name == name) {
334 sway_log(SWAY_DEBUG, "Destroying output %s (%s)",
335 output->name, output->identifier);
336 destroy_swaybg_output(output);
337 break;
338 }
339 }
248} 340}
249 341
250static const struct wl_registry_listener registry_listener = { 342static const struct wl_registry_listener registry_listener = {
@@ -252,32 +344,159 @@ static const struct wl_registry_listener registry_listener = {
252 .global_remove = handle_global_remove, 344 .global_remove = handle_global_remove,
253}; 345};
254 346
255int main(int argc, const char **argv) { 347static bool store_swaybg_output_config(struct swaybg_state *state,
256 sway_log_init(SWAY_DEBUG, NULL); 348 struct swaybg_output_config *config) {
257 349 struct swaybg_output_config *oc = NULL;
258 struct swaybg_args args = {0}; 350 wl_list_for_each(oc, &state->configs, link) {
259 struct swaybg_state state = { .args = &args }; 351 if (strcmp(config->output, oc->output) == 0) {
260 wl_list_init(&state.outputs); 352 // Merge on top
353 if (config->image) {
354 free(oc->image);
355 oc->image = config->image;
356 config->image = NULL;
357 }
358 if (config->color) {
359 oc->color = config->color;
360 }
361 if (config->mode != BACKGROUND_MODE_INVALID) {
362 oc->mode = config->mode;
363 }
364 return false;
365 }
366 }
367 // New config, just add it
368 wl_list_insert(&state->configs, &config->link);
369 return true;
370}
261 371
262 if (argc < 4 || argc > 5) { 372static void parse_command_line(int argc, char **argv,
263 sway_log(SWAY_ERROR, "Do not run this program manually. " 373 struct swaybg_state *state) {
264 "See `man 5 sway-output` and look for background options."); 374 static struct option long_options[] = {
265 return 1; 375 {"color", required_argument, NULL, 'c'},
376 {"help", no_argument, NULL, 'h'},
377 {"image", required_argument, NULL, 'i'},
378 {"mode", required_argument, NULL, 'm'},
379 {"output", required_argument, NULL, 'o'},
380 {"version", no_argument, NULL, 'v'},
381 {0, 0, 0, 0}
382 };
383
384 const char *usage =
385 "Usage: swaybg <options...>\n"
386 "\n"
387 " -c, --color Set the background color.\n"
388 " -h, --help Show help message and quit.\n"
389 " -i, --image Set the image to display.\n"
390 " -m, --mode Set the mode to use for the image.\n"
391 " -o, --output Set the output to operate on or * for all.\n"
392 " -v, --version Show the version number and quit.\n"
393 "\n"
394 "Background Modes:\n"
395 " stretch, fit, fill, center, tile, or solid_color\n";
396
397 struct swaybg_output_config *config = NULL;
398
399 int c;
400 while (1) {
401 int option_index = 0;
402 c = getopt_long(argc, argv, "c:hi:m:o:v", long_options, &option_index);
403 if (c == -1) {
404 break;
405 }
406 switch (c) {
407 case 'c': // color
408 if (!config) {
409 goto no_output;
410 }
411 if (!is_valid_color(optarg)) {
412 sway_log(SWAY_ERROR, "Invalid color: %s", optarg);
413 continue;
414 }
415 config->color = parse_color(optarg);
416 break;
417 case 'i': // image
418 if (!config) {
419 goto no_output;
420 }
421 free(config->image);
422 config->image = load_background_image(optarg);
423 if (!config->image) {
424 sway_log(SWAY_ERROR, "Failed to load image: %s", optarg);
425 }
426 break;
427 case 'm': // mode
428 if (!config) {
429 goto no_output;
430 }
431 config->mode = parse_background_mode(optarg);
432 if (config->mode == BACKGROUND_MODE_INVALID) {
433 sway_log(SWAY_ERROR, "Invalid mode: %s", optarg);
434 }
435 break;
436 case 'o': // output
437 if (config && !store_swaybg_output_config(state, config)) {
438 // Empty config or merged on top of an existing one
439 destroy_swaybg_output_config(config);
440 }
441 config = calloc(sizeof(struct swaybg_output_config), 1);
442 config->output = strdup(optarg);
443 config->mode = BACKGROUND_MODE_INVALID;
444 wl_list_init(&config->link); // init for safe removal
445 break;
446 case 'v': // version
447 fprintf(stdout, "swaybg version " SWAY_VERSION "\n");
448 exit(EXIT_SUCCESS);
449 break;
450 default:
451 fprintf(c == 'h' ? stdout : stderr, "%s", usage);
452 exit(c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE);
453 }
454 }
455 if (config && !store_swaybg_output_config(state, config)) {
456 // Empty config or merged on top of an existing one
457 destroy_swaybg_output_config(config);
266 } 458 }
267 459
268 args.output = argv[1]; 460 // Check for invalid options
269 args.path = argv[2]; 461 if (optind < argc) {
462 config = NULL;
463 struct swaybg_output_config *tmp = NULL;
464 wl_list_for_each_safe(config, tmp, &state->configs, link) {
465 destroy_swaybg_output_config(config);
466 }
467 // continue into empty list
468 }
469 if (wl_list_empty(&state->configs)) {
470 fprintf(stderr, "%s", usage);
471 exit(EXIT_FAILURE);
472 }
270 473
271 args.mode = parse_background_mode(argv[3]); 474 // Set default mode and remove empties
272 if (args.mode == BACKGROUND_MODE_INVALID) { 475 config = NULL;
273 return 1; 476 struct swaybg_output_config *tmp = NULL;
477 wl_list_for_each_safe(config, tmp, &state->configs, link) {
478 if (!config->image && !config->color) {
479 destroy_swaybg_output_config(config);
480 } else if (config->mode == BACKGROUND_MODE_INVALID) {
481 config->mode = config->image
482 ? BACKGROUND_MODE_STRETCH
483 : BACKGROUND_MODE_SOLID_COLOR;
484 }
274 } 485 }
486 return;
487no_output:
488 fprintf(stderr, "Cannot operate on NULL output config\n");
489 exit(EXIT_FAILURE);
490}
275 491
276 args.fallback = argc == 5 ? argv[4] : NULL; 492int main(int argc, char **argv) {
493 sway_log_init(SWAY_DEBUG, NULL);
277 494
278 if (!prepare_context(&state)) { 495 struct swaybg_state state = {0};
279 return 1; 496 wl_list_init(&state.configs);
280 } 497 wl_list_init(&state.outputs);
498
499 parse_command_line(argc, argv, &state);
281 500
282 state.display = wl_display_connect(NULL); 501 state.display = wl_display_connect(NULL);
283 if (!state.display) { 502 if (!state.display) {
@@ -303,42 +522,21 @@ int main(int argc, const char **argv) {
303 zxdg_output_v1_add_listener(output->xdg_output, 522 zxdg_output_v1_add_listener(output->xdg_output,
304 &xdg_output_listener, output); 523 &xdg_output_listener, output);
305 } 524 }
306 // Second roundtrip to get xdg_output properties
307 wl_display_roundtrip(state.display);
308 if (state.output == NULL) {
309 sway_log(SWAY_ERROR, "Cannot find output '%s'", args.output);
310 return 1;
311 }
312
313 state.surface = wl_compositor_create_surface(state.compositor);
314 assert(state.surface);
315
316 // Empty input region
317 state.input_region = wl_compositor_create_region(state.compositor);
318 assert(state.input_region);
319 wl_surface_set_input_region(state.surface, state.input_region);
320
321 state.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
322 state.layer_shell, state.surface, state.output->wl_output,
323 ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "wallpaper");
324 assert(state.layer_surface);
325
326 zwlr_layer_surface_v1_set_size(state.layer_surface, 0, 0);
327 zwlr_layer_surface_v1_set_anchor(state.layer_surface,
328 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
329 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
330 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
331 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
332 zwlr_layer_surface_v1_set_exclusive_zone(state.layer_surface, -1);
333 zwlr_layer_surface_v1_add_listener(state.layer_surface,
334 &layer_surface_listener, &state);
335 wl_surface_commit(state.surface);
336 wl_display_roundtrip(state.display);
337 525
338 state.run_display = true; 526 state.run_display = true;
339 while (wl_display_dispatch(state.display) != -1 && state.run_display) { 527 while (wl_display_dispatch(state.display) != -1 && state.run_display) {
340 // This space intentionally left blank 528 // This space intentionally left blank
341 } 529 }
342 530
531 struct swaybg_output *tmp_output;
532 wl_list_for_each_safe(output, tmp_output, &state.outputs, link) {
533 destroy_swaybg_output(output);
534 }
535
536 struct swaybg_output_config *config = NULL, *tmp_config = NULL;
537 wl_list_for_each_safe(config, tmp_config, &state.configs, link) {
538 destroy_swaybg_output_config(config);
539 }
540
343 return 0; 541 return 0;
344} 542}