mirror of
https://github.com/swaywm/sway
synced 2024-11-23 17:32:40 +01:00
75e7bd24cc
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.
543 lines
16 KiB
C
543 lines
16 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <getopt.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <wayland-client.h>
|
|
#include "background-image.h"
|
|
#include "cairo.h"
|
|
#include "log.h"
|
|
#include "pool-buffer.h"
|
|
#include "util.h"
|
|
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
|
#include "xdg-output-unstable-v1-client-protocol.h"
|
|
|
|
struct swaybg_state {
|
|
struct wl_display *display;
|
|
struct wl_compositor *compositor;
|
|
struct wl_shm *shm;
|
|
struct zwlr_layer_shell_v1 *layer_shell;
|
|
struct zxdg_output_manager_v1 *xdg_output_manager;
|
|
struct wl_list configs; // struct swaybg_output_config::link
|
|
struct wl_list outputs; // struct swaybg_output::link
|
|
bool run_display;
|
|
};
|
|
|
|
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) {
|
|
int len = strlen(color);
|
|
if (len != 7 || color[0] != '#') {
|
|
sway_log(SWAY_ERROR, "%s is not a valid color for swaybg. "
|
|
"Color should be specified as #rrggbb (no alpha).", color);
|
|
return false;
|
|
}
|
|
|
|
int i;
|
|
for (i = 1; i < len; ++i) {
|
|
if (!isxdigit(color[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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 = output->current_buffer->cairo;
|
|
cairo_save(cairo);
|
|
cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
|
|
cairo_paint(cairo);
|
|
cairo_restore(cairo);
|
|
if (output->config->mode == BACKGROUND_MODE_SOLID_COLOR) {
|
|
cairo_set_source_u32(cairo, output->config->color);
|
|
cairo_paint(cairo);
|
|
} else {
|
|
if (output->config->color) {
|
|
cairo_set_source_u32(cairo, output->config->color);
|
|
cairo_paint(cairo);
|
|
}
|
|
render_background_image(cairo, output->config->image,
|
|
output->config->mode, buffer_width, buffer_height);
|
|
}
|
|
|
|
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 void destroy_swaybg_output_config(struct swaybg_output_config *config) {
|
|
if (!config) {
|
|
return;
|
|
}
|
|
wl_list_remove(&config->link);
|
|
free(config->output);
|
|
free(config);
|
|
}
|
|
|
|
static void destroy_swaybg_output(struct swaybg_output *output) {
|
|
if (!output) {
|
|
return;
|
|
}
|
|
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,
|
|
struct zwlr_layer_surface_v1 *surface,
|
|
uint32_t serial, uint32_t width, uint32_t height) {
|
|
struct swaybg_output *output = data;
|
|
output->width = width;
|
|
output->height = height;
|
|
zwlr_layer_surface_v1_ack_configure(surface, serial);
|
|
render_frame(output);
|
|
}
|
|
|
|
static void layer_surface_closed(void *data,
|
|
struct zwlr_layer_surface_v1 *surface) {
|
|
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 = {
|
|
.configure = layer_surface_configure,
|
|
.closed = layer_surface_closed,
|
|
};
|
|
|
|
static void output_geometry(void *data, struct wl_output *output, int32_t x,
|
|
int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel,
|
|
const char *make, const char *model, int32_t transform) {
|
|
// Who cares
|
|
}
|
|
|
|
static void output_mode(void *data, struct wl_output *output, uint32_t flags,
|
|
int32_t width, int32_t height, int32_t refresh) {
|
|
// Who cares
|
|
}
|
|
|
|
static void output_done(void *data, struct wl_output *output) {
|
|
// Who cares
|
|
}
|
|
|
|
static void output_scale(void *data, struct wl_output *wl_output,
|
|
int32_t scale) {
|
|
struct swaybg_output *output = data;
|
|
output->scale = scale;
|
|
if (output->state->run_display && output->width > 0 && output->height > 0) {
|
|
render_frame(output);
|
|
}
|
|
}
|
|
|
|
static const struct wl_output_listener output_listener = {
|
|
.geometry = output_geometry,
|
|
.mode = output_mode,
|
|
.done = output_done,
|
|
.scale = output_scale,
|
|
};
|
|
|
|
static void xdg_output_handle_logical_position(void *data,
|
|
struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
|
|
// Who cares
|
|
}
|
|
|
|
static void xdg_output_handle_logical_size(void *data,
|
|
struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
|
|
// 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;
|
|
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) {
|
|
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) {
|
|
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 = {
|
|
.logical_position = xdg_output_handle_logical_position,
|
|
.logical_size = xdg_output_handle_logical_size,
|
|
.name = xdg_output_handle_name,
|
|
.description = xdg_output_handle_description,
|
|
.done = xdg_output_handle_done,
|
|
};
|
|
|
|
static void handle_global(void *data, struct wl_registry *registry,
|
|
uint32_t name, const char *interface, uint32_t version) {
|
|
struct swaybg_state *state = data;
|
|
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
|
state->compositor =
|
|
wl_registry_bind(registry, name, &wl_compositor_interface, 4);
|
|
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
|
state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
|
} 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);
|
|
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
|
state->xdg_output_manager = wl_registry_bind(registry, name,
|
|
&zxdg_output_manager_v1_interface, 2);
|
|
}
|
|
}
|
|
|
|
static void handle_global_remove(void *data, struct wl_registry *registry,
|
|
uint32_t name) {
|
|
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 = {
|
|
.global = handle_global,
|
|
.global_remove = handle_global_remove,
|
|
};
|
|
|
|
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);
|
|
|
|
struct swaybg_state state = {0};
|
|
wl_list_init(&state.configs);
|
|
wl_list_init(&state.outputs);
|
|
|
|
parse_command_line(argc, argv, &state);
|
|
|
|
state.display = wl_display_connect(NULL);
|
|
if (!state.display) {
|
|
sway_log(SWAY_ERROR, "Unable to connect to the compositor. "
|
|
"If your compositor is running, check or set the "
|
|
"WAYLAND_DISPLAY environment variable.");
|
|
return 1;
|
|
}
|
|
|
|
struct wl_registry *registry = wl_display_get_registry(state.display);
|
|
wl_registry_add_listener(registry, ®istry_listener, &state);
|
|
wl_display_roundtrip(state.display);
|
|
if (state.compositor == NULL || state.shm == NULL ||
|
|
state.layer_shell == NULL || state.xdg_output_manager == NULL) {
|
|
sway_log(SWAY_ERROR, "Missing a required Wayland interface");
|
|
return 1;
|
|
}
|
|
|
|
struct swaybg_output *output;
|
|
wl_list_for_each(output, &state.outputs, link) {
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|