sway-patched-tray-menu/swaybg/main.c
Brian Ashworth 75e7bd24cc 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.
2019-04-04 20:08:21 +03:00

542 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, &registry_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;
}