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.
This commit is contained in:
Brian Ashworth 2019-04-03 21:53:43 -04:00 committed by Simon Ser
parent 0ad905f23c
commit 75e7bd24cc
8 changed files with 497 additions and 251 deletions

View file

@ -424,7 +424,6 @@ struct sway_config {
list_t *active_bar_modifiers; list_t *active_bar_modifiers;
struct sway_mode *current_mode; struct sway_mode *current_mode;
struct bar_config *current_bar; struct bar_config *current_bar;
char *swaybg_command;
uint32_t floating_mod; uint32_t floating_mod;
bool floating_mod_inverse; bool floating_mod_inverse;
uint32_t dragging_key; uint32_t dragging_key;
@ -447,6 +446,11 @@ struct sway_config {
enum sway_popup_during_fullscreen popup_during_fullscreen; enum sway_popup_during_fullscreen popup_during_fullscreen;
bool xwayland; bool xwayland;
// swaybg
char *swaybg_command;
struct wl_client *swaybg_client;
struct wl_listener swaybg_client_destroy;
// Flags // Flags
enum focus_follows_mouse_mode focus_follows_mouse; enum focus_follows_mouse_mode focus_follows_mouse;
enum mouse_warping_mode mouse_warping; enum mouse_warping_mode mouse_warping;
@ -607,6 +611,8 @@ void reset_outputs(void);
void free_output_config(struct output_config *oc); void free_output_config(struct output_config *oc);
bool spawn_swaybg(void);
int workspace_output_cmp_workspace(const void *a, const void *b); int workspace_output_cmp_workspace(const void *a, const void *b);
int sway_binding_cmp(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 load_swaybars(void);
void terminate_swaybg(pid_t pid);
struct bar_config *default_bar_config(void); struct bar_config *default_bar_config(void);
void free_bar_config(struct bar_config *bar); void free_bar_config(struct bar_config *bar);

View file

@ -38,8 +38,6 @@ struct sway_output {
struct sway_output_state current; struct sway_output_state current;
struct wl_client *swaybg_client;
struct wl_listener destroy; struct wl_listener destroy;
struct wl_listener mode; struct wl_listener mode;
struct wl_listener transform; struct wl_listener transform;
@ -47,7 +45,6 @@ struct sway_output {
struct wl_listener present; struct wl_listener present;
struct wl_listener damage_destroy; struct wl_listener damage_destroy;
struct wl_listener damage_frame; struct wl_listener damage_frame;
struct wl_listener swaybg_client_destroy;
struct { struct {
struct wl_signal destroy; struct wl_signal destroy;

View file

@ -68,6 +68,8 @@ struct cmd_results *cmd_output(int argc, char **argv) {
config->handler_context.leftovers.argc = 0; config->handler_context.leftovers.argc = 0;
config->handler_context.leftovers.argv = NULL; config->handler_context.leftovers.argv = NULL;
bool background = output->background;
output = store_output_config(output); output = store_output_config(output);
// If reloading, the output configs will be applied after reading the // 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. // workspace name is not given to re-enabled outputs.
if (!config->reloading) { if (!config->reloading) {
apply_output_config_to_outputs(output); apply_output_config_to_outputs(output);
if (background) {
spawn_swaybg();
}
} }
return cmd_results_new(CMD_SUCCESS, NULL); return cmd_results_new(CMD_SUCCESS, NULL);

View file

@ -104,6 +104,9 @@ void free_config(struct sway_config *config) {
} }
list_free(config->output_configs); list_free(config->output_configs);
} }
if (config->swaybg_client != NULL) {
wl_client_destroy(config->swaybg_client);
}
if (config->input_configs) { if (config->input_configs) {
for (int i = 0; i < config->input_configs->length; i++) { for (int i = 0; i < config->input_configs->length; i++) {
free_input_config(config->input_configs->items[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) { if (is_active) {
reset_outputs(); reset_outputs();
spawn_swaybg();
config->reloading = false; config->reloading = false;
if (config->swaynag_config_errors.pid > 0) { if (config->swaynag_config_errors.pid > 0) {

View file

@ -228,91 +228,6 @@ static bool set_mode(struct wlr_output *output, int width, int height,
return wlr_output_set_mode(output, best); 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) { bool apply_output_config(struct output_config *oc, struct sway_output *output) {
if (output == root->noop_output) { if (output == root->noop_output) {
return false; return false;
@ -397,25 +312,6 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
wlr_output_transformed_resolution(wlr_output, wlr_output_transformed_resolution(wlr_output,
&output->width, &output->height); &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) { if (oc && oc->dpms_state == DPMS_OFF) {
sway_log(SWAY_DEBUG, "Turning off screen"); 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->background_option);
free(oc); 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;
}

View file

@ -525,7 +525,6 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
wl_list_remove(&output->present.link); wl_list_remove(&output->present.link);
wl_list_remove(&output->damage_destroy.link); wl_list_remove(&output->damage_destroy.link);
wl_list_remove(&output->damage_frame.link); wl_list_remove(&output->damage_frame.link);
wl_list_remove(&output->swaybg_client_destroy.link);
transaction_commit_dirty(); transaction_commit_dirty();
} }
@ -632,7 +631,6 @@ void handle_new_output(struct wl_listener *listener, void *data) {
output->damage_frame.notify = damage_handle_frame; output->damage_frame.notify = damage_handle_frame;
wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
output->damage_destroy.notify = damage_handle_destroy; output->damage_destroy.notify = damage_handle_destroy;
wl_list_init(&output->swaybg_client_destroy.link);
struct output_config *oc = find_output_config(output); struct output_config *oc = find_output_config(output);
if (!oc || oc->enabled) { if (!oc || oc->enabled) {

View file

@ -262,10 +262,6 @@ void output_disable(struct sway_output *output) {
root_for_each_container(untrack_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); int index = list_find(root->outputs, output);
list_del(root->outputs, index); list_del(root->outputs, index);

View file

@ -1,10 +1,12 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <getopt.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <strings.h>
#include <wayland-client.h> #include <wayland-client.h>
#include "background-image.h" #include "background-image.h"
#include "cairo.h" #include "cairo.h"
@ -14,49 +16,44 @@
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-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 { struct swaybg_state {
const struct swaybg_args *args;
struct swaybg_context context;
struct wl_display *display; struct wl_display *display;
struct wl_compositor *compositor; struct wl_compositor *compositor;
struct wl_shm *shm; struct wl_shm *shm;
struct wl_list outputs;
struct zwlr_layer_shell_v1 *layer_shell; struct zwlr_layer_shell_v1 *layer_shell;
struct zxdg_output_manager_v1 *xdg_output_manager; struct zxdg_output_manager_v1 *xdg_output_manager;
struct wl_list configs; // struct swaybg_output_config::link
struct swaybg_output *output; struct wl_list outputs; // struct swaybg_output::link
struct wl_surface *surface;
struct wl_region *input_region;
struct zwlr_layer_surface_v1 *layer_surface;
bool run_display; 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 buffers[2];
struct pool_buffer *current_buffer; struct pool_buffer *current_buffer;
uint32_t width, height;
int32_t scale;
struct wl_list link;
}; };
bool is_valid_color(const char *color) { bool is_valid_color(const char *color) {
@ -77,68 +74,82 @@ bool is_valid_color(const char *color) {
return true; return true;
} }
static void render_frame(struct swaybg_state *state) { static void render_frame(struct swaybg_output *output) {
int buffer_width = state->width * state->output->scale, int buffer_width = output->width * output->scale,
buffer_height = state->height * state->output->scale; buffer_height = output->height * output->scale;
state->current_buffer = get_next_buffer(state->shm, output->current_buffer = get_next_buffer(output->state->shm,
state->buffers, buffer_width, buffer_height); output->buffers, buffer_width, buffer_height);
if (!state->current_buffer) { if (!output->current_buffer) {
return; return;
} }
cairo_t *cairo = state->current_buffer->cairo; cairo_t *cairo = output->current_buffer->cairo;
cairo_save(cairo); cairo_save(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
cairo_paint(cairo); cairo_paint(cairo);
cairo_restore(cairo); cairo_restore(cairo);
if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { if (output->config->mode == BACKGROUND_MODE_SOLID_COLOR) {
cairo_set_source_u32(cairo, state->context.color); cairo_set_source_u32(cairo, output->config->color);
cairo_paint(cairo); cairo_paint(cairo);
} else { } else {
if (state->args->fallback && state->context.color) { if (output->config->color) {
cairo_set_source_u32(cairo, state->context.color); cairo_set_source_u32(cairo, output->config->color);
cairo_paint(cairo); cairo_paint(cairo);
} }
render_background_image(cairo, state->context.image, render_background_image(cairo, output->config->image,
state->args->mode, buffer_width, buffer_height); output->config->mode, buffer_width, buffer_height);
} }
wl_surface_set_buffer_scale(state->surface, state->output->scale); wl_surface_set_buffer_scale(output->surface, output->scale);
wl_surface_attach(state->surface, state->current_buffer->buffer, 0, 0); wl_surface_attach(output->surface, output->current_buffer->buffer, 0, 0);
wl_surface_damage_buffer(state->surface, 0, 0, INT32_MAX, INT32_MAX); wl_surface_damage_buffer(output->surface, 0, 0, INT32_MAX, INT32_MAX);
wl_surface_commit(state->surface); wl_surface_commit(output->surface);
} }
static bool prepare_context(struct swaybg_state *state) { static void destroy_swaybg_output_config(struct swaybg_output_config *config) {
if (state->args->mode == BACKGROUND_MODE_SOLID_COLOR) { if (!config) {
state->context.color = parse_color(state->args->path); return;
return is_valid_color(state->args->path);
} }
if (state->args->fallback && is_valid_color(state->args->fallback)) { wl_list_remove(&config->link);
state->context.color = parse_color(state->args->fallback); free(config->output);
free(config);
} }
if (!(state->context.image = load_background_image(state->args->path))) {
return false; static void destroy_swaybg_output(struct swaybg_output *output) {
if (!output) {
return;
} }
return true; wl_list_remove(&output->link);
if (output->layer_surface != NULL) {
zwlr_layer_surface_v1_destroy(output->layer_surface);
}
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, static void layer_surface_configure(void *data,
struct zwlr_layer_surface_v1 *surface, struct zwlr_layer_surface_v1 *surface,
uint32_t serial, uint32_t width, uint32_t height) { uint32_t serial, uint32_t width, uint32_t height) {
struct swaybg_state *state = data; struct swaybg_output *output = data;
state->width = width; output->width = width;
state->height = height; output->height = height;
zwlr_layer_surface_v1_ack_configure(surface, serial); zwlr_layer_surface_v1_ack_configure(surface, serial);
render_frame(state); render_frame(output);
} }
static void layer_surface_closed(void *data, static void layer_surface_closed(void *data,
struct zwlr_layer_surface_v1 *surface) { struct zwlr_layer_surface_v1 *surface) {
struct swaybg_state *state = data; struct swaybg_output *output = data;
zwlr_layer_surface_v1_destroy(state->layer_surface); sway_log(SWAY_DEBUG, "Destroying output %s (%s)",
wl_surface_destroy(state->surface); output->name, output->identifier);
wl_region_destroy(state->input_region); destroy_swaybg_output(output);
state->run_display = false;
} }
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { 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, static void output_scale(void *data, struct wl_output *wl_output,
int32_t scale) { int32_t scale) {
struct swaybg_output *output = data; struct swaybg_output *output = data;
struct swaybg_state *state = output->state;
output->scale = scale; output->scale = scale;
if (output->state->run_display && output->width > 0 && output->height > 0) {
if (state->output == output && state->run_display) { render_frame(output);
render_frame(state);
} }
} }
@ -190,24 +198,91 @@ static void xdg_output_handle_logical_size(void *data,
// Who cares // 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, static void xdg_output_handle_name(void *data,
struct zxdg_output_v1 *xdg_output, const char *name) { struct zxdg_output_v1 *xdg_output, const char *name) {
struct swaybg_output *output = data; struct swaybg_output *output = data;
struct swaybg_state *state = output->state; output->name = strdup(name);
if (strcmp(name, state->args->output) == 0) {
assert(state->output == NULL); // If description was sent first, the config may already be populated. If
state->output = output; // 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, static void xdg_output_handle_description(void *data,
struct zxdg_output_v1 *xdg_output, const char *description) { 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, static void xdg_output_handle_done(void *data,
struct zxdg_output_v1 *xdg_output) { 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 = { 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) { } else if (strcmp(interface, wl_output_interface.name) == 0) {
struct swaybg_output *output = calloc(1, sizeof(struct swaybg_output)); struct swaybg_output *output = calloc(1, sizeof(struct swaybg_output));
output->state = state; output->state = state;
output->wl_name = name;
output->wl_output = output->wl_output =
wl_registry_bind(registry, name, &wl_output_interface, 3); wl_registry_bind(registry, name, &wl_output_interface, 3);
wl_output_add_listener(output->wl_output, &output_listener, output); wl_output_add_listener(output->wl_output, &output_listener, output);
wl_list_insert(&state->outputs, &output->link); 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) { } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
state->layer_shell = state->layer_shell =
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); 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, static void handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name) { 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 = { 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, .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 <options...>\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); sway_log_init(SWAY_DEBUG, NULL);
struct swaybg_args args = {0}; struct swaybg_state state = {0};
struct swaybg_state state = { .args = &args }; wl_list_init(&state.configs);
wl_list_init(&state.outputs); wl_list_init(&state.outputs);
if (argc < 4 || argc > 5) { parse_command_line(argc, argv, &state);
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;
}
state.display = wl_display_connect(NULL); state.display = wl_display_connect(NULL);
if (!state.display) { if (!state.display) {
@ -303,42 +522,21 @@ int main(int argc, const char **argv) {
zxdg_output_v1_add_listener(output->xdg_output, zxdg_output_v1_add_listener(output->xdg_output,
&xdg_output_listener, 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; state.run_display = true;
while (wl_display_dispatch(state.display) != -1 && state.run_display) { while (wl_display_dispatch(state.display) != -1 && state.run_display) {
// This space intentionally left blank // 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; return 0;
} }