aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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}