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 { list_t *active_bar_modifiers; struct sway_mode *current_mode; struct bar_config *current_bar; - char *swaybg_command; uint32_t floating_mod; bool floating_mod_inverse; uint32_t dragging_key; @@ -447,6 +446,11 @@ struct sway_config { enum sway_popup_during_fullscreen popup_during_fullscreen; bool xwayland; + // swaybg + char *swaybg_command; + struct wl_client *swaybg_client; + struct wl_listener swaybg_client_destroy; + // Flags enum focus_follows_mouse_mode focus_follows_mouse; enum mouse_warping_mode mouse_warping; @@ -607,6 +611,8 @@ void reset_outputs(void); void free_output_config(struct output_config *oc); +bool spawn_swaybg(void); + int workspace_output_cmp_workspace(const void *a, const void *b); int sway_binding_cmp(const void *a, const void *b); @@ -625,8 +631,6 @@ void load_swaybar(struct bar_config *bar); void load_swaybars(void); -void terminate_swaybg(pid_t pid); - struct bar_config *default_bar_config(void); void 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 { struct sway_output_state current; - struct wl_client *swaybg_client; - struct wl_listener destroy; struct wl_listener mode; struct wl_listener transform; @@ -47,7 +45,6 @@ struct sway_output { struct wl_listener present; struct wl_listener damage_destroy; struct wl_listener damage_frame; - struct wl_listener swaybg_client_destroy; struct { 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) { config->handler_context.leftovers.argc = 0; config->handler_context.leftovers.argv = NULL; + bool background = output->background; + output = store_output_config(output); // If reloading, the output configs will be applied after reading the @@ -75,6 +77,9 @@ struct cmd_results *cmd_output(int argc, char **argv) { // workspace name is not given to re-enabled outputs. if (!config->reloading) { apply_output_config_to_outputs(output); + if (background) { + spawn_swaybg(); + } } 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) { } list_free(config->output_configs); } + if (config->swaybg_client != NULL) { + wl_client_destroy(config->swaybg_client); + } if (config->input_configs) { for (int i = 0; i < config->input_configs->length; i++) { free_input_config(config->input_configs->items[i]); @@ -480,6 +483,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) { if (is_active) { reset_outputs(); + spawn_swaybg(); config->reloading = false; 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, return wlr_output_set_mode(output, best); } -static void handle_swaybg_client_destroy(struct wl_listener *listener, - void *data) { - struct sway_output *output = - wl_container_of(listener, output, swaybg_client_destroy); - wl_list_remove(&output->swaybg_client_destroy.link); - wl_list_init(&output->swaybg_client_destroy.link); - output->swaybg_client = NULL; -} - -static bool set_cloexec(int fd, bool cloexec) { - int flags = fcntl(fd, F_GETFD); - if (flags == -1) { - sway_log_errno(SWAY_ERROR, "fcntl failed"); - return false; - } - if (cloexec) { - flags = flags | FD_CLOEXEC; - } else { - flags = flags & ~FD_CLOEXEC; - } - if (fcntl(fd, F_SETFD, flags) == -1) { - sway_log_errno(SWAY_ERROR, "fcntl failed"); - return false; - } - return true; -} - -static bool spawn_swaybg(struct sway_output *output, char *const cmd[]) { - int sockets[2]; - if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) { - sway_log_errno(SWAY_ERROR, "socketpair failed"); - return false; - } - if (!set_cloexec(sockets[0], true) || !set_cloexec(sockets[1], true)) { - return false; - } - - output->swaybg_client = wl_client_create(server.wl_display, sockets[0]); - if (output->swaybg_client == NULL) { - sway_log_errno(SWAY_ERROR, "wl_client_create failed"); - return false; - } - - output->swaybg_client_destroy.notify = handle_swaybg_client_destroy; - wl_client_add_destroy_listener(output->swaybg_client, - &output->swaybg_client_destroy); - - pid_t pid = fork(); - if (pid < 0) { - sway_log_errno(SWAY_ERROR, "fork failed"); - return false; - } else if (pid == 0) { - pid = fork(); - if (pid < 0) { - sway_log_errno(SWAY_ERROR, "fork failed"); - _exit(EXIT_FAILURE); - } else if (pid == 0) { - if (!set_cloexec(sockets[1], false)) { - _exit(EXIT_FAILURE); - } - - char wayland_socket_str[16]; - snprintf(wayland_socket_str, sizeof(wayland_socket_str), - "%d", sockets[1]); - setenv("WAYLAND_SOCKET", wayland_socket_str, true); - - execvp(cmd[0], cmd); - sway_log_errno(SWAY_ERROR, "execvp failed"); - _exit(EXIT_FAILURE); - } - _exit(EXIT_SUCCESS); - } - - if (close(sockets[1]) != 0) { - sway_log_errno(SWAY_ERROR, "close failed"); - return false; - } - if (waitpid(pid, NULL, 0) < 0) { - sway_log_errno(SWAY_ERROR, "waitpid failed"); - return false; - } - - return true; -} - bool apply_output_config(struct output_config *oc, struct sway_output *output) { if (output == root->noop_output) { return false; @@ -397,25 +312,6 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { wlr_output_transformed_resolution(wlr_output, &output->width, &output->height); - if (output->swaybg_client != NULL) { - wl_client_destroy(output->swaybg_client); - } - if (oc && oc->background && config->swaybg_command) { - sway_log(SWAY_DEBUG, "Setting background for output %s to %s", - wlr_output->name, oc->background); - - char *const cmd[] = { - config->swaybg_command, - wlr_output->name, - oc->background, - oc->background_option, - oc->background_fallback ? oc->background_fallback : NULL, - NULL, - }; - if (!spawn_swaybg(output, cmd)) { - return false; - } - } if (oc && oc->dpms_state == DPMS_OFF) { sway_log(SWAY_DEBUG, "Turning off screen"); @@ -584,3 +480,151 @@ void free_output_config(struct output_config *oc) { free(oc->background_option); free(oc); } + +static void handle_swaybg_client_destroy(struct wl_listener *listener, + void *data) { + wl_list_remove(&config->swaybg_client_destroy.link); + wl_list_init(&config->swaybg_client_destroy.link); + config->swaybg_client = NULL; +} + +static bool set_cloexec(int fd, bool cloexec) { + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + sway_log_errno(SWAY_ERROR, "fcntl failed"); + return false; + } + if (cloexec) { + flags = flags | FD_CLOEXEC; + } else { + flags = flags & ~FD_CLOEXEC; + } + if (fcntl(fd, F_SETFD, flags) == -1) { + sway_log_errno(SWAY_ERROR, "fcntl failed"); + return false; + } + return true; +} + +static bool _spawn_swaybg(char **command) { + if (config->swaybg_client != NULL) { + wl_client_destroy(config->swaybg_client); + } + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) { + sway_log_errno(SWAY_ERROR, "socketpair failed"); + return false; + } + if (!set_cloexec(sockets[0], true) || !set_cloexec(sockets[1], true)) { + return false; + } + + config->swaybg_client = wl_client_create(server.wl_display, sockets[0]); + if (config->swaybg_client == NULL) { + sway_log_errno(SWAY_ERROR, "wl_client_create failed"); + return false; + } + + config->swaybg_client_destroy.notify = handle_swaybg_client_destroy; + wl_client_add_destroy_listener(config->swaybg_client, + &config->swaybg_client_destroy); + + pid_t pid = fork(); + if (pid < 0) { + sway_log_errno(SWAY_ERROR, "fork failed"); + return false; + } else if (pid == 0) { + pid = fork(); + if (pid < 0) { + sway_log_errno(SWAY_ERROR, "fork failed"); + _exit(EXIT_FAILURE); + } else if (pid == 0) { + if (!set_cloexec(sockets[1], false)) { + _exit(EXIT_FAILURE); + } + + char wayland_socket_str[16]; + snprintf(wayland_socket_str, sizeof(wayland_socket_str), + "%d", sockets[1]); + setenv("WAYLAND_SOCKET", wayland_socket_str, true); + + execvp(command[0], command); + sway_log_errno(SWAY_ERROR, "execvp failed"); + _exit(EXIT_FAILURE); + } + _exit(EXIT_SUCCESS); + } + + if (close(sockets[1]) != 0) { + sway_log_errno(SWAY_ERROR, "close failed"); + return false; + } + if (waitpid(pid, NULL, 0) < 0) { + sway_log_errno(SWAY_ERROR, "waitpid failed"); + return false; + } + + return true; +} + +bool spawn_swaybg(void) { + if (!config->swaybg_command) { + return true; + } + + size_t length = 2; + for (int i = 0; i < config->output_configs->length; i++) { + struct output_config *oc = config->output_configs->items[i]; + if (!oc->background) { + continue; + } + if (strcmp(oc->background_option, "solid_color") == 0) { + length += 4; + } else if (oc->background_fallback) { + length += 8; + } else { + length += 6; + } + } + + char **cmd = calloc(1, sizeof(char **) * length); + if (!cmd) { + sway_log(SWAY_ERROR, "Failed to allocate spawn_swaybg command"); + return false; + } + + size_t i = 0; + cmd[i++] = config->swaybg_command; + for (int j = 0; j < config->output_configs->length; j++) { + struct output_config *oc = config->output_configs->items[j]; + if (!oc->background) { + continue; + } + if (strcmp(oc->background_option, "solid_color") == 0) { + cmd[i++] = "-o"; + cmd[i++] = oc->name; + cmd[i++] = "-c"; + cmd[i++] = oc->background; + } else { + cmd[i++] = "-o"; + cmd[i++] = oc->name; + cmd[i++] = "-i"; + cmd[i++] = oc->background; + cmd[i++] = "-m"; + cmd[i++] = oc->background_option; + if (oc->background_fallback) { + cmd[i++] = "-c"; + cmd[i++] = oc->background_fallback; + } + } + assert(i <= length); + } + + for (size_t k = 0; k < i; k++) { + sway_log(SWAY_DEBUG, "spawn_swaybg cmd[%ld] = %s", k, cmd[k]); + } + + bool result = _spawn_swaybg(cmd); + free(cmd); + return result; +} 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) { wl_list_remove(&output->present.link); wl_list_remove(&output->damage_destroy.link); wl_list_remove(&output->damage_frame.link); - wl_list_remove(&output->swaybg_client_destroy.link); transaction_commit_dirty(); } @@ -632,7 +631,6 @@ void handle_new_output(struct wl_listener *listener, void *data) { output->damage_frame.notify = damage_handle_frame; wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); output->damage_destroy.notify = damage_handle_destroy; - wl_list_init(&output->swaybg_client_destroy.link); struct output_config *oc = find_output_config(output); 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) { root_for_each_container(untrack_output, output); - if (output->swaybg_client != NULL) { - wl_client_destroy(output->swaybg_client); - } - int index = list_find(root->outputs, output); list_del(root->outputs, index); 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 @@ +#define _POSIX_C_SOURCE 200809L #include #include +#include #include #include #include #include -#include +#include #include #include "background-image.h" #include "cairo.h" @@ -14,49 +16,44 @@ #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" -struct swaybg_state; - -struct swaybg_args { - const char *output; - const char *path; - enum background_mode mode; - const char *fallback; -}; - -struct swaybg_context { - uint32_t color; - cairo_surface_t *image; -}; - -struct swaybg_output { - struct wl_output *wl_output; - struct zxdg_output_v1 *xdg_output; - struct swaybg_state *state; - struct wl_list link; - - int32_t scale; -}; - struct swaybg_state { - const struct swaybg_args *args; - struct swaybg_context context; - struct wl_display *display; struct wl_compositor *compositor; struct wl_shm *shm; - struct wl_list outputs; struct zwlr_layer_shell_v1 *layer_shell; struct zxdg_output_manager_v1 *xdg_output_manager; - - struct swaybg_output *output; - struct wl_surface *surface; - struct wl_region *input_region; - struct zwlr_layer_surface_v1 *layer_surface; - + struct wl_list configs; // struct swaybg_output_config::link + struct wl_list outputs; // struct swaybg_output::link bool run_display; - uint32_t width, height; +}; + +struct swaybg_output_config { + char *output; + cairo_surface_t *image; + enum background_mode mode; + uint32_t color; + struct wl_list link; +}; + +struct swaybg_output { + uint32_t wl_name; + struct wl_output *wl_output; + struct zxdg_output_v1 *xdg_output; + char *name; + char *identifier; + + struct swaybg_state *state; + struct swaybg_output_config *config; + + struct wl_surface *surface; + struct zwlr_layer_surface_v1 *layer_surface; struct pool_buffer buffers[2]; struct pool_buffer *current_buffer; + + uint32_t width, height; + int32_t scale; + + struct wl_list link; }; bool is_valid_color(const char *color) { @@ -77,68 +74,82 @@ bool is_valid_color(const char *color) { return true; } -static void render_frame(struct swaybg_state *state) { - int buffer_width = state->width * state->output->scale, - buffer_height = state->height * state->output->scale; - state->current_buffer = get_next_buffer(state->shm, - state->buffers, buffer_width, buffer_height); - if (!state->current_buffer) { +static void render_frame(struct swaybg_output *output) { + int buffer_width = output->width * output->scale, + buffer_height = output->height * output->scale; + output->current_buffer = get_next_buffer(output->state->shm, + output->buffers, buffer_width, buffer_height); + if (!output->current_buffer) { return; } - cairo_t *cairo = state->current_buffer->cairo; + cairo_t *cairo = output->current_buffer->cairo; cairo_save(cairo); cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); cairo_paint(cairo); cairo_restore(cairo); - if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { - cairo_set_source_u32(cairo, state->context.color); + if (output->config->mode == BACKGROUND_MODE_SOLID_COLOR) { + cairo_set_source_u32(cairo, output->config->color); cairo_paint(cairo); } else { - if (state->args->fallback && state->context.color) { - cairo_set_source_u32(cairo, state->context.color); + if (output->config->color) { + cairo_set_source_u32(cairo, output->config->color); cairo_paint(cairo); } - render_background_image(cairo, state->context.image, - state->args->mode, buffer_width, buffer_height); + render_background_image(cairo, output->config->image, + output->config->mode, buffer_width, buffer_height); } - wl_surface_set_buffer_scale(state->surface, state->output->scale); - wl_surface_attach(state->surface, state->current_buffer->buffer, 0, 0); - wl_surface_damage_buffer(state->surface, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_commit(state->surface); + wl_surface_set_buffer_scale(output->surface, output->scale); + wl_surface_attach(output->surface, output->current_buffer->buffer, 0, 0); + wl_surface_damage_buffer(output->surface, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_commit(output->surface); } -static bool prepare_context(struct swaybg_state *state) { - if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { - state->context.color = parse_color(state->args->path); - return is_valid_color(state->args->path); +static void destroy_swaybg_output_config(struct swaybg_output_config *config) { + if (!config) { + return; } - if (state->args->fallback && is_valid_color(state->args->fallback)) { - state->context.color = parse_color(state->args->fallback); + wl_list_remove(&config->link); + free(config->output); + free(config); +} + +static void destroy_swaybg_output(struct swaybg_output *output) { + if (!output) { + return; } - if (!(state->context.image = load_background_image(state->args->path))) { - return false; + wl_list_remove(&output->link); + if (output->layer_surface != NULL) { + zwlr_layer_surface_v1_destroy(output->layer_surface); } - return true; + if (output->surface != NULL) { + wl_surface_destroy(output->surface); + } + zxdg_output_v1_destroy(output->xdg_output); + wl_output_destroy(output->wl_output); + destroy_buffer(&output->buffers[0]); + destroy_buffer(&output->buffers[1]); + free(output->name); + free(output->identifier); + free(output); } static void layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) { - struct swaybg_state *state = data; - state->width = width; - state->height = height; + struct swaybg_output *output = data; + output->width = width; + output->height = height; zwlr_layer_surface_v1_ack_configure(surface, serial); - render_frame(state); + render_frame(output); } static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { - struct swaybg_state *state = data; - zwlr_layer_surface_v1_destroy(state->layer_surface); - wl_surface_destroy(state->surface); - wl_region_destroy(state->input_region); - state->run_display = false; + struct swaybg_output *output = data; + sway_log(SWAY_DEBUG, "Destroying output %s (%s)", + output->name, output->identifier); + destroy_swaybg_output(output); } static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { @@ -164,12 +175,9 @@ static void output_done(void *data, struct wl_output *output) { static void output_scale(void *data, struct wl_output *wl_output, int32_t scale) { struct swaybg_output *output = data; - struct swaybg_state *state = output->state; - output->scale = scale; - - if (state->output == output && state->run_display) { - render_frame(state); + if (output->state->run_display && output->width > 0 && output->height > 0) { + render_frame(output); } } @@ -190,24 +198,91 @@ static void xdg_output_handle_logical_size(void *data, // Who cares } +static void find_config(struct swaybg_output *output, const char *name) { + struct swaybg_output_config *config = NULL; + wl_list_for_each(config, &output->state->configs, link) { + if (strcmp(config->output, name) == 0) { + output->config = config; + return; + } else if (!output->config && strcmp(config->output, "*") == 0) { + output->config = config; + } + } +} + static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct swaybg_output *output = data; - struct swaybg_state *state = output->state; - if (strcmp(name, state->args->output) == 0) { - assert(state->output == NULL); - state->output = output; + output->name = strdup(name); + + // If description was sent first, the config may already be populated. If + // there is an identifier config set, keep it. + if (!output->config || strcmp(output->config->output, "*") == 0) { + find_config(output, name); } } static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { - // Who cares + struct swaybg_output *output = data; + + // wlroots currently sets the description to `make model serial (name)` + // If this changes in the future, this will need to be modified. + char *paren = strrchr(description, '('); + if (paren) { + size_t length = paren - description; + output->identifier = malloc(length); + if (!output->identifier) { + sway_log(SWAY_ERROR, "Failed to allocate output identifier"); + return; + } + strncpy(output->identifier, description, length); + output->identifier[length - 1] = '\0'; + + find_config(output, output->identifier); + } +} + +static void create_layer_surface(struct swaybg_output *output) { + output->surface = wl_compositor_create_surface(output->state->compositor); + assert(output->surface); + + // Empty input region + struct wl_region *input_region = + wl_compositor_create_region(output->state->compositor); + assert(input_region); + wl_surface_set_input_region(output->surface, input_region); + wl_region_destroy(input_region); + + output->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + output->state->layer_shell, output->surface, output->wl_output, + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "wallpaper"); + assert(output->layer_surface); + + zwlr_layer_surface_v1_set_size(output->layer_surface, 0, 0); + zwlr_layer_surface_v1_set_anchor(output->layer_surface, + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); + zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1); + zwlr_layer_surface_v1_add_listener(output->layer_surface, + &layer_surface_listener, output); + wl_surface_commit(output->surface); } static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { - // Who cares + struct swaybg_output *output = data; + if (!output->config) { + sway_log(SWAY_DEBUG, "Could not find config for output %s (%s)", + output->name, output->identifier); + destroy_swaybg_output(output); + } else if (!output->layer_surface) { + sway_log(SWAY_DEBUG, "Found config %s for output %s (%s)", + output->config->output, output->name, output->identifier); + create_layer_surface(output); + } } static const struct zxdg_output_v1_listener xdg_output_listener = { @@ -229,10 +304,18 @@ static void handle_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, wl_output_interface.name) == 0) { struct swaybg_output *output = calloc(1, sizeof(struct swaybg_output)); output->state = state; + output->wl_name = name; output->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 3); wl_output_add_listener(output->wl_output, &output_listener, output); wl_list_insert(&state->outputs, &output->link); + + if (state->run_display) { + output->xdg_output = zxdg_output_manager_v1_get_xdg_output( + state->xdg_output_manager, output->wl_output); + zxdg_output_v1_add_listener(output->xdg_output, + &xdg_output_listener, output); + } } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { state->layer_shell = 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, static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { - // who cares + struct swaybg_state *state = data; + struct swaybg_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &state->outputs, link) { + if (output->wl_name == name) { + sway_log(SWAY_DEBUG, "Destroying output %s (%s)", + output->name, output->identifier); + destroy_swaybg_output(output); + break; + } + } } static const struct wl_registry_listener registry_listener = { @@ -252,32 +344,159 @@ static const struct wl_registry_listener registry_listener = { .global_remove = handle_global_remove, }; -int main(int argc, const char **argv) { +static bool store_swaybg_output_config(struct swaybg_state *state, + struct swaybg_output_config *config) { + struct swaybg_output_config *oc = NULL; + wl_list_for_each(oc, &state->configs, link) { + if (strcmp(config->output, oc->output) == 0) { + // Merge on top + if (config->image) { + free(oc->image); + oc->image = config->image; + config->image = NULL; + } + if (config->color) { + oc->color = config->color; + } + if (config->mode != BACKGROUND_MODE_INVALID) { + oc->mode = config->mode; + } + return false; + } + } + // New config, just add it + wl_list_insert(&state->configs, &config->link); + return true; +} + +static void parse_command_line(int argc, char **argv, + struct swaybg_state *state) { + static struct option long_options[] = { + {"color", required_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {"image", required_argument, NULL, 'i'}, + {"mode", required_argument, NULL, 'm'}, + {"output", required_argument, NULL, 'o'}, + {"version", no_argument, NULL, 'v'}, + {0, 0, 0, 0} + }; + + const char *usage = + "Usage: swaybg \n" + "\n" + " -c, --color Set the background color.\n" + " -h, --help Show help message and quit.\n" + " -i, --image Set the image to display.\n" + " -m, --mode Set the mode to use for the image.\n" + " -o, --output Set the output to operate on or * for all.\n" + " -v, --version Show the version number and quit.\n" + "\n" + "Background Modes:\n" + " stretch, fit, fill, center, tile, or solid_color\n"; + + struct swaybg_output_config *config = NULL; + + int c; + while (1) { + int option_index = 0; + c = getopt_long(argc, argv, "c:hi:m:o:v", long_options, &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'c': // color + if (!config) { + goto no_output; + } + if (!is_valid_color(optarg)) { + sway_log(SWAY_ERROR, "Invalid color: %s", optarg); + continue; + } + config->color = parse_color(optarg); + break; + case 'i': // image + if (!config) { + goto no_output; + } + free(config->image); + config->image = load_background_image(optarg); + if (!config->image) { + sway_log(SWAY_ERROR, "Failed to load image: %s", optarg); + } + break; + case 'm': // mode + if (!config) { + goto no_output; + } + config->mode = parse_background_mode(optarg); + if (config->mode == BACKGROUND_MODE_INVALID) { + sway_log(SWAY_ERROR, "Invalid mode: %s", optarg); + } + break; + case 'o': // output + if (config && !store_swaybg_output_config(state, config)) { + // Empty config or merged on top of an existing one + destroy_swaybg_output_config(config); + } + config = calloc(sizeof(struct swaybg_output_config), 1); + config->output = strdup(optarg); + config->mode = BACKGROUND_MODE_INVALID; + wl_list_init(&config->link); // init for safe removal + break; + case 'v': // version + fprintf(stdout, "swaybg version " SWAY_VERSION "\n"); + exit(EXIT_SUCCESS); + break; + default: + fprintf(c == 'h' ? stdout : stderr, "%s", usage); + exit(c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE); + } + } + if (config && !store_swaybg_output_config(state, config)) { + // Empty config or merged on top of an existing one + destroy_swaybg_output_config(config); + } + + // Check for invalid options + if (optind < argc) { + config = NULL; + struct swaybg_output_config *tmp = NULL; + wl_list_for_each_safe(config, tmp, &state->configs, link) { + destroy_swaybg_output_config(config); + } + // continue into empty list + } + if (wl_list_empty(&state->configs)) { + fprintf(stderr, "%s", usage); + exit(EXIT_FAILURE); + } + + // Set default mode and remove empties + config = NULL; + struct swaybg_output_config *tmp = NULL; + wl_list_for_each_safe(config, tmp, &state->configs, link) { + if (!config->image && !config->color) { + destroy_swaybg_output_config(config); + } else if (config->mode == BACKGROUND_MODE_INVALID) { + config->mode = config->image + ? BACKGROUND_MODE_STRETCH + : BACKGROUND_MODE_SOLID_COLOR; + } + } + return; +no_output: + fprintf(stderr, "Cannot operate on NULL output config\n"); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) { sway_log_init(SWAY_DEBUG, NULL); - struct swaybg_args args = {0}; - struct swaybg_state state = { .args = &args }; + struct swaybg_state state = {0}; + wl_list_init(&state.configs); wl_list_init(&state.outputs); - if (argc < 4 || argc > 5) { - sway_log(SWAY_ERROR, "Do not run this program manually. " - "See `man 5 sway-output` and look for background options."); - return 1; - } - - args.output = argv[1]; - args.path = argv[2]; - - args.mode = parse_background_mode(argv[3]); - if (args.mode == BACKGROUND_MODE_INVALID) { - return 1; - } - - args.fallback = argc == 5 ? argv[4] : NULL; - - if (!prepare_context(&state)) { - return 1; - } + parse_command_line(argc, argv, &state); state.display = wl_display_connect(NULL); if (!state.display) { @@ -303,42 +522,21 @@ int main(int argc, const char **argv) { zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } - // Second roundtrip to get xdg_output properties - wl_display_roundtrip(state.display); - if (state.output == NULL) { - sway_log(SWAY_ERROR, "Cannot find output '%s'", args.output); - return 1; - } - - state.surface = wl_compositor_create_surface(state.compositor); - assert(state.surface); - - // Empty input region - state.input_region = wl_compositor_create_region(state.compositor); - assert(state.input_region); - wl_surface_set_input_region(state.surface, state.input_region); - - state.layer_surface = zwlr_layer_shell_v1_get_layer_surface( - state.layer_shell, state.surface, state.output->wl_output, - ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "wallpaper"); - assert(state.layer_surface); - - zwlr_layer_surface_v1_set_size(state.layer_surface, 0, 0); - zwlr_layer_surface_v1_set_anchor(state.layer_surface, - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); - zwlr_layer_surface_v1_set_exclusive_zone(state.layer_surface, -1); - zwlr_layer_surface_v1_add_listener(state.layer_surface, - &layer_surface_listener, &state); - wl_surface_commit(state.surface); - wl_display_roundtrip(state.display); state.run_display = true; while (wl_display_dispatch(state.display) != -1 && state.run_display) { // This space intentionally left blank } + struct swaybg_output *tmp_output; + wl_list_for_each_safe(output, tmp_output, &state.outputs, link) { + destroy_swaybg_output(output); + } + + struct swaybg_output_config *config = NULL, *tmp_config = NULL; + wl_list_for_each_safe(config, tmp_config, &state.configs, link) { + destroy_swaybg_output_config(config); + } + return 0; }