From 9ac131e494a8c310d824abcdb4a9e0cd5b1aac89 Mon Sep 17 00:00:00 2001 From: Birger Schacht Date: Sat, 18 May 2019 21:08:50 +0100 Subject: Import swaybg_1.0.orig.tar.gz [dgit import orig swaybg_1.0.orig.tar.gz] --- .gitignore | 1 + LICENSE | 19 ++ README.md | 48 ++++ background-image.c | 120 +++++++++ cairo.c | 126 +++++++++ include/background-image.h | 20 ++ include/cairo.h | 25 ++ include/log.h | 36 +++ include/meson.build | 1 + include/pool-buffer.h | 22 ++ log.c | 68 +++++ main.c | 549 ++++++++++++++++++++++++++++++++++++++++ meson.build | 145 +++++++++++ meson_options.txt | 2 + pool-buffer.c | 143 +++++++++++ swaybg.1.scd | 44 ++++ wlr-layer-shell-unstable-v1.xml | 285 +++++++++++++++++++++ 17 files changed, 1654 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 background-image.c create mode 100644 cairo.c create mode 100644 include/background-image.h create mode 100644 include/cairo.h create mode 100644 include/log.h create mode 100644 include/meson.build create mode 100644 include/pool-buffer.h create mode 100644 log.c create mode 100644 main.c create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 pool-buffer.c create mode 100644 swaybg.1.scd create mode 100644 wlr-layer-shell-unstable-v1.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c6e6356 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2019 Drew DeVault + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..de7dac3 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# swaybg + +swaybg is a wallpaper utility for Wayland compositors. It is compatible with any +Wayland compositor which implements the following Wayland protocols: + +- wlr-layer-shell +- xdg-output +- xdg-shell + +See the man page, `swaybg(1)`, for instructions on using swaybg. + +## Release Signatures + +Releases are signed with [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) +and published [on GitHub](https://github.com/swaywm/swaylock/releases). swaybg +releases are managed independently of sway releases. + +## Installation + +### From Packages + +swaybg is available in many distributions. Try installing the "swaybg" +package for yours. + +If you're interested in packaging swaybg for your distribution, stop by the +IRC channel or shoot an email to sir@cmpwn.com for advice. + +### Compiling from Source + +Install dependencies: + +* meson \* +* wayland +* wayland-protocols \* +* cairo +* gdk-pixbuf2 \*\* +* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* +* git \* + +_\*Compile-time dep_ + +_\*\*optional: required for background images other than PNG_ + +Run these commands: + + meson build + ninja -C build + sudo ninja -C build install diff --git a/background-image.c b/background-image.c new file mode 100644 index 0000000..63c9810 --- /dev/null +++ b/background-image.c @@ -0,0 +1,120 @@ +#include +#include "background-image.h" +#include "cairo.h" +#include "log.h" + +enum background_mode parse_background_mode(const char *mode) { + if (strcmp(mode, "stretch") == 0) { + return BACKGROUND_MODE_STRETCH; + } else if (strcmp(mode, "fill") == 0) { + return BACKGROUND_MODE_FILL; + } else if (strcmp(mode, "fit") == 0) { + return BACKGROUND_MODE_FIT; + } else if (strcmp(mode, "center") == 0) { + return BACKGROUND_MODE_CENTER; + } else if (strcmp(mode, "tile") == 0) { + return BACKGROUND_MODE_TILE; + } else if (strcmp(mode, "solid_color") == 0) { + return BACKGROUND_MODE_SOLID_COLOR; + } + swaybg_log(LOG_ERROR, "Unsupported background mode: %s", mode); + return BACKGROUND_MODE_INVALID; +} + +cairo_surface_t *load_background_image(const char *path) { + cairo_surface_t *image; +#if HAVE_GDK_PIXBUF + GError *err = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err); + if (!pixbuf) { + swaybg_log(LOG_ERROR, "Failed to load background image (%s).", + err->message); + return NULL; + } + image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); + g_object_unref(pixbuf); +#else + image = cairo_image_surface_create_from_png(path); +#endif // HAVE_GDK_PIXBUF + if (!image) { + swaybg_log(LOG_ERROR, "Failed to read background image."); + return NULL; + } + if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) { + swaybg_log(LOG_ERROR, "Failed to read background image: %s." +#if !HAVE_GDK_PIXBUF + "\nSway was compiled without gdk_pixbuf support, so only" + "\nPNG images can be loaded. This is the likely cause." +#endif // !HAVE_GDK_PIXBUF + , cairo_status_to_string(cairo_surface_status(image))); + return NULL; + } + return image; +} + +void render_background_image(cairo_t *cairo, cairo_surface_t *image, + enum background_mode mode, int buffer_width, int buffer_height) { + double width = cairo_image_surface_get_width(image); + double height = cairo_image_surface_get_height(image); + + cairo_save(cairo); + switch (mode) { + case BACKGROUND_MODE_STRETCH: + cairo_scale(cairo, + (double)buffer_width / width, + (double)buffer_height / height); + cairo_set_source_surface(cairo, image, 0, 0); + break; + case BACKGROUND_MODE_FILL: { + double window_ratio = (double)buffer_width / buffer_height; + double bg_ratio = width / height; + + if (window_ratio > bg_ratio) { + double scale = (double)buffer_width / width; + cairo_scale(cairo, scale, scale); + cairo_set_source_surface(cairo, image, + 0, (double)buffer_height / 2 / scale - height / 2); + } else { + double scale = (double)buffer_height / height; + cairo_scale(cairo, scale, scale); + cairo_set_source_surface(cairo, image, + (double)buffer_width / 2 / scale - width / 2, 0); + } + break; + } + case BACKGROUND_MODE_FIT: { + double window_ratio = (double)buffer_width / buffer_height; + double bg_ratio = width / height; + + if (window_ratio > bg_ratio) { + double scale = (double)buffer_height / height; + cairo_scale(cairo, scale, scale); + cairo_set_source_surface(cairo, image, + (double)buffer_width / 2 / scale - width / 2, 0); + } else { + double scale = (double)buffer_width / width; + cairo_scale(cairo, scale, scale); + cairo_set_source_surface(cairo, image, + 0, (double)buffer_height / 2 / scale - height / 2); + } + break; + } + case BACKGROUND_MODE_CENTER: + cairo_set_source_surface(cairo, image, + (double)buffer_width / 2 - width / 2, + (double)buffer_height / 2 - height / 2); + break; + case BACKGROUND_MODE_TILE: { + cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image); + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); + cairo_set_source(cairo, pattern); + break; + } + case BACKGROUND_MODE_SOLID_COLOR: + case BACKGROUND_MODE_INVALID: + assert(0); + break; + } + cairo_paint(cairo); + cairo_restore(cairo); +} diff --git a/cairo.c b/cairo.c new file mode 100644 index 0000000..75314bd --- /dev/null +++ b/cairo.c @@ -0,0 +1,126 @@ +#include +#include +#include "cairo.h" +#if HAVE_GDK_PIXBUF +#include +#endif + +void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { + cairo_set_source_rgba(cairo, + (color >> (3*8) & 0xFF) / 255.0, + (color >> (2*8) & 0xFF) / 255.0, + (color >> (1*8) & 0xFF) / 255.0, + (color >> (0*8) & 0xFF) / 255.0); +} + +cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel) { + switch (subpixel) { + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: + return CAIRO_SUBPIXEL_ORDER_RGB; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: + return CAIRO_SUBPIXEL_ORDER_BGR; + case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: + return CAIRO_SUBPIXEL_ORDER_VRGB; + case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: + return CAIRO_SUBPIXEL_ORDER_VBGR; + default: + return CAIRO_SUBPIXEL_ORDER_DEFAULT; + } + return CAIRO_SUBPIXEL_ORDER_DEFAULT; +} + +#if HAVE_GDK_PIXBUF +cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf(const GdkPixbuf *gdkbuf) { + int chan = gdk_pixbuf_get_n_channels(gdkbuf); + if (chan < 3) { + return NULL; + } + + const guint8* gdkpix = gdk_pixbuf_read_pixels(gdkbuf); + if (!gdkpix) { + return NULL; + } + gint w = gdk_pixbuf_get_width(gdkbuf); + gint h = gdk_pixbuf_get_height(gdkbuf); + int stride = gdk_pixbuf_get_rowstride(gdkbuf); + + cairo_format_t fmt = (chan == 3) ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32; + cairo_surface_t * cs = cairo_image_surface_create (fmt, w, h); + cairo_surface_flush (cs); + if ( !cs || cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS) { + return NULL; + } + + int cstride = cairo_image_surface_get_stride(cs); + unsigned char * cpix = cairo_image_surface_get_data(cs); + + if (chan == 3) { + int i; + for (i = h; i; --i) { + const guint8 *gp = gdkpix; + unsigned char *cp = cpix; + const guint8* end = gp + 3*w; + while (gp < end) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + cp[0] = gp[2]; + cp[1] = gp[1]; + cp[2] = gp[0]; +#else + cp[1] = gp[0]; + cp[2] = gp[1]; + cp[3] = gp[2]; +#endif + gp += 3; + cp += 4; + } + gdkpix += stride; + cpix += cstride; + } + } else { + /* premul-color = alpha/255 * color/255 * 255 = (alpha*color)/255 + * (z/255) = z/256 * 256/255 = z/256 (1 + 1/255) + * = z/256 + (z/256)/255 = (z + z/255)/256 + * # recurse once + * = (z + (z + z/255)/256)/256 + * = (z + z/256 + z/256/255) / 256 + * # only use 16bit uint operations, loose some precision, + * # result is floored. + * -> (z + z>>8)>>8 + * # add 0x80/255 = 0.5 to convert floor to round + * => (z+0x80 + (z+0x80)>>8 ) >> 8 + * ------ + * tested as equal to lround(z/255.0) for uint z in [0..0xfe02] + */ +#define PREMUL_ALPHA(x,a,b,z) \ + G_STMT_START { z = a * b + 0x80; x = (z + (z >> 8)) >> 8; } \ + G_STMT_END + int i; + for (i = h; i; --i) { + const guint8 *gp = gdkpix; + unsigned char *cp = cpix; + const guint8* end = gp + 4*w; + guint z1, z2, z3; + while (gp < end) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + PREMUL_ALPHA(cp[0], gp[2], gp[3], z1); + PREMUL_ALPHA(cp[1], gp[1], gp[3], z2); + PREMUL_ALPHA(cp[2], gp[0], gp[3], z3); + cp[3] = gp[3]; +#else + PREMUL_ALPHA(cp[1], gp[0], gp[3], z1); + PREMUL_ALPHA(cp[2], gp[1], gp[3], z2); + PREMUL_ALPHA(cp[3], gp[2], gp[3], z3); + cp[0] = gp[3]; +#endif + gp += 4; + cp += 4; + } + gdkpix += stride; + cpix += cstride; + } +#undef PREMUL_ALPHA + } + cairo_surface_mark_dirty(cs); + return cs; +} +#endif // HAVE_GDK_PIXBUF diff --git a/include/background-image.h b/include/background-image.h new file mode 100644 index 0000000..15935ff --- /dev/null +++ b/include/background-image.h @@ -0,0 +1,20 @@ +#ifndef _SWAY_BACKGROUND_IMAGE_H +#define _SWAY_BACKGROUND_IMAGE_H +#include "cairo.h" + +enum background_mode { + BACKGROUND_MODE_STRETCH, + BACKGROUND_MODE_FILL, + BACKGROUND_MODE_FIT, + BACKGROUND_MODE_CENTER, + BACKGROUND_MODE_TILE, + BACKGROUND_MODE_SOLID_COLOR, + BACKGROUND_MODE_INVALID, +}; + +enum background_mode parse_background_mode(const char *mode); +cairo_surface_t *load_background_image(const char *path); +void render_background_image(cairo_t *cairo, cairo_surface_t *image, + enum background_mode mode, int buffer_width, int buffer_height); + +#endif diff --git a/include/cairo.h b/include/cairo.h new file mode 100644 index 0000000..15664cd --- /dev/null +++ b/include/cairo.h @@ -0,0 +1,25 @@ +#ifndef _SWAY_CAIRO_H +#define _SWAY_CAIRO_H + +#include "config.h" +#include +#include +#include +#if HAVE_GDK_PIXBUF +#include +#endif + +void cairo_set_source_u32(cairo_t *cairo, uint32_t color); +cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel); + +cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, + int width, int height); + +#if HAVE_GDK_PIXBUF + +cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf( + const GdkPixbuf *gdkbuf); + +#endif // HAVE_GDK_PIXBUF + +#endif diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..eaa265a --- /dev/null +++ b/include/log.h @@ -0,0 +1,36 @@ +#ifndef _SWAYBG_LOG_H +#define _SWAYBG_LOG_H + +#include +#include +#include + +enum log_importance { + LOG_SILENT = 0, + LOG_ERROR = 1, + LOG_INFO = 2, + LOG_DEBUG = 3, + LOG_IMPORTANCE_LAST, +}; + +void swaybg_log_init(enum log_importance verbosity); + +#ifdef __GNUC__ +#define _ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) +#else +#define _ATTRIB_PRINTF(start, end) +#endif + +void _swaybg_log(enum log_importance verbosity, const char *format, ...) + _ATTRIB_PRINTF(2, 3); + +const char *_swaybg_strip_path(const char *filepath); + +#define swaybg_log(verb, fmt, ...) \ + _swaybg_log(verb, "[%s:%d] " fmt, _swaybg_strip_path(__FILE__), \ + __LINE__, ##__VA_ARGS__) + +#define swaybg_log_errno(verb, fmt, ...) \ + swaybg_log(verb, fmt ": %s", ##__VA_ARGS__, strerror(errno)) + +#endif diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..65ed027 --- /dev/null +++ b/include/meson.build @@ -0,0 +1 @@ +configure_file(output: 'config.h', configuration: conf_data) diff --git a/include/pool-buffer.h b/include/pool-buffer.h new file mode 100644 index 0000000..0ebf787 --- /dev/null +++ b/include/pool-buffer.h @@ -0,0 +1,22 @@ +#ifndef _SWAY_BUFFERS_H +#define _SWAY_BUFFERS_H +#include +#include +#include +#include + +struct pool_buffer { + struct wl_buffer *buffer; + cairo_surface_t *surface; + cairo_t *cairo; + uint32_t width, height; + void *data; + size_t size; + bool busy; +}; + +struct pool_buffer *get_next_buffer(struct wl_shm *shm, + struct pool_buffer pool[static 2], uint32_t width, uint32_t height); +void destroy_buffer(struct pool_buffer *buffer); + +#endif diff --git a/log.c b/log.c new file mode 100644 index 0000000..fc7b7d9 --- /dev/null +++ b/log.c @@ -0,0 +1,68 @@ +#define _POSIX_C_SOURCE 199506L +#include +#include +#include +#include +#include +#include +#include +#include "log.h" + +static enum log_importance log_importance = LOG_ERROR; + +static const char *verbosity_colors[] = { + [LOG_SILENT] = "", + [LOG_ERROR ] = "\x1B[1;31m", + [LOG_INFO ] = "\x1B[1;34m", + [LOG_DEBUG ] = "\x1B[1;30m", +}; + +void swaybg_log_init(enum log_importance verbosity) { + if (verbosity < LOG_IMPORTANCE_LAST) { + log_importance = verbosity; + } +} + +void _swaybg_log(enum log_importance verbosity, const char *fmt, ...) { + if (verbosity > log_importance) { + return; + } + + va_list args; + va_start(args, fmt); + + // prefix the time to the log message + struct tm result; + time_t t = time(NULL); + struct tm *tm_info = localtime_r(&t, &result); + char buffer[26]; + + // generate time prefix + strftime(buffer, sizeof(buffer), "%F %T - ", tm_info); + fprintf(stderr, "%s", buffer); + + unsigned c = (verbosity < LOG_IMPORTANCE_LAST) + ? verbosity : LOG_IMPORTANCE_LAST - 1; + + if (isatty(STDERR_FILENO)) { + fprintf(stderr, "%s", verbosity_colors[c]); + } + + vfprintf(stderr, fmt, args); + + if (isatty(STDERR_FILENO)) { + fprintf(stderr, "\x1B[0m"); + } + fprintf(stderr, "\n"); + + va_end(args); +} + +const char *_swaybg_strip_path(const char *filepath) { + if (*filepath == '.') { + while (*filepath == '.' || *filepath == '/') { + ++filepath; + } + } + return filepath; +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..f0519b7 --- /dev/null +++ b/main.c @@ -0,0 +1,549 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "background-image.h" +#include "cairo.h" +#include "log.h" +#include "pool-buffer.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include "xdg-output-unstable-v1-client-protocol.h" + +static uint32_t parse_color(const char *color) { + if (color[0] == '#') { + ++color; + } + + int len = strlen(color); + if (len != 6 && len != 8) { + swaybg_log(LOG_DEBUG, "Invalid color %s, defaulting to 0xFFFFFFFF", + color); + return 0xFFFFFFFF; + } + uint32_t res = (uint32_t)strtoul(color, NULL, 16); + if (strlen(color) == 6) { + res = (res << 8) | 0xFF; + } + return res; +} + +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] != '#') { + swaybg_log(LOG_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; + swaybg_log(LOG_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) { + swaybg_log(LOG_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) { + swaybg_log(LOG_DEBUG, "Could not find config for output %s (%s)", + output->name, output->identifier); + destroy_swaybg_output(output); + } else if (!output->layer_surface) { + swaybg_log(LOG_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) { + swaybg_log(LOG_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 \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 = calloc(sizeof(struct swaybg_output_config), 1); + config->output = strdup("*"); + config->mode = BACKGROUND_MODE_INVALID; + wl_list_init(&config->link); // init for safe removal + + 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 (!is_valid_color(optarg)) { + swaybg_log(LOG_ERROR, "Invalid color: %s", optarg); + continue; + } + config->color = parse_color(optarg); + break; + case 'i': // image + free(config->image); + config->image = load_background_image(optarg); + if (!config->image) { + swaybg_log(LOG_ERROR, "Failed to load image: %s", optarg); + } + break; + case 'm': // mode + config->mode = parse_background_mode(optarg); + if (config->mode == BACKGROUND_MODE_INVALID) { + swaybg_log(LOG_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 " SWAYBG_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; + } + } +} + +int main(int argc, char **argv) { + swaybg_log_init(LOG_DEBUG); + + 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) { + swaybg_log(LOG_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) { + swaybg_log(LOG_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; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..abdf039 --- /dev/null +++ b/meson.build @@ -0,0 +1,145 @@ +project( + 'swaybg', + 'c', + version: '1.0', + license: 'MIT', + meson_version: '>=0.48.0', + default_options: [ + 'c_std=c11', + 'warning_level=2', + 'werror=true', + ], +) + +add_project_arguments( + [ + '-Wno-unused-parameter', + '-Wno-unused-result', + '-Wundef', + '-Wvla', + ], + language: 'c', +) + +is_freebsd = host_machine.system().startswith('freebsd') +if is_freebsd + add_project_arguments('-D_C11_SOURCE', language: 'c') +endif + +wayland_client = dependency('wayland-client') +wayland_protos = dependency('wayland-protocols', version: '>=1.14') +cairo = dependency('cairo') +gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf')) + +git = find_program('git', required: false) +scdoc = find_program('scdoc', required: get_option('man-pages')) +wayland_scanner = find_program('wayland-scanner') + +version = '"@0@"'.format(meson.project_version()) +if git.found() + git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']) + git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']) + if git_commit_hash.returncode() == 0 and git_branch.returncode() == 0 + version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash.stdout().strip(), git_branch.stdout().strip()) + endif +endif +add_project_arguments('-DSWAYBG_VERSION=@0@'.format(version), language: 'c') + +wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') + +if wayland_client.version().version_compare('>=1.14.91') + code_type = 'private-code' +else + code_type = 'code' +endif + +wayland_scanner_code = generator( + wayland_scanner, + output: '@BASENAME@-protocol.c', + arguments: [code_type, '@INPUT@', '@OUTPUT@'], +) + +wayland_scanner_client = generator( + wayland_scanner, + output: '@BASENAME@-client-protocol.h', + arguments: ['client-header', '@INPUT@', '@OUTPUT@'], +) + +client_protos_src = [] +client_protos_headers = [] + +client_protocols = [ + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], + ['wlr-layer-shell-unstable-v1.xml'], +] + +foreach p : client_protocols + xml = join_paths(p) + client_protos_src += wayland_scanner_code.process(xml) + client_protos_headers += wayland_scanner_client.process(xml) +endforeach + +lib_client_protos = static_library( + 'client_protos', + client_protos_src + client_protos_headers, + dependencies: [wayland_client] +) # for the include directory + +client_protos = declare_dependency( + link_with: lib_client_protos, + sources: client_protos_headers, +) + +conf_data = configuration_data() +conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) + +subdir('include') + +dependencies = [ + cairo, + client_protos, + gdk_pixbuf, + wayland_client, +] + +sources = [ + 'background-image.c', + 'cairo.c', + 'log.c', + 'main.c', + 'pool-buffer.c', +] + +swaybg_inc = include_directories('include') + +executable('swaybg', + sources, + include_directories: [swaybg_inc], + dependencies: dependencies, + install: true +) + +if scdoc.found() + sh = find_program('sh') + mandir = get_option('mandir') + man_files = [ + 'swaybg.1.scd', + ] + foreach filename : man_files + topic = filename.split('.')[-3].split('/')[-1] + section = filename.split('.')[-2] + output = '@0@.@1@'.format(topic, section) + + custom_target( + output, + input: filename, + output: output, + command: [ + sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.path(), output) + ], + install: true, + install_dir: '@0@/man@1@'.format(mandir, section) + ) + endforeach +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..cf13bd5 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,2 @@ +option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats') +option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') diff --git a/pool-buffer.c b/pool-buffer.c new file mode 100644 index 0000000..b3f1ef2 --- /dev/null +++ b/pool-buffer.c @@ -0,0 +1,143 @@ +#define _POSIX_C_SOURCE 200809 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pool-buffer.h" + +static bool set_cloexec(int fd) { + long flags = fcntl(fd, F_GETFD); + if (flags == -1) { + return false; + } + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + return false; + } + + return true; +} + +static int create_pool_file(size_t size, char **name) { + static const char template[] = "sway-client-XXXXXX"; + const char *path = getenv("XDG_RUNTIME_DIR"); + if (path == NULL) { + fprintf(stderr, "XDG_RUNTIME_DIR is not set\n"); + return -1; + } + + size_t name_size = strlen(template) + 1 + strlen(path) + 1; + *name = malloc(name_size); + if (*name == NULL) { + fprintf(stderr, "allocation failed\n"); + return -1; + } + snprintf(*name, name_size, "%s/%s", path, template); + + int fd = mkstemp(*name); + if (fd < 0) { + return -1; + } + + if (!set_cloexec(fd)) { + close(fd); + return -1; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static void buffer_release(void *data, struct wl_buffer *wl_buffer) { + struct pool_buffer *buffer = data; + buffer->busy = false; +} + +static const struct wl_buffer_listener buffer_listener = { + .release = buffer_release +}; + +static struct pool_buffer *create_buffer(struct wl_shm *shm, + struct pool_buffer *buf, int32_t width, int32_t height, + uint32_t format) { + uint32_t stride = width * 4; + size_t size = stride * height; + + char *name; + int fd = create_pool_file(size, &name); + assert(fd != -1); + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + buf->buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, stride, format); + wl_shm_pool_destroy(pool); + close(fd); + unlink(name); + free(name); + fd = -1; + + buf->size = size; + buf->width = width; + buf->height = height; + buf->data = data; + buf->surface = cairo_image_surface_create_for_data(data, + CAIRO_FORMAT_ARGB32, width, height, stride); + buf->cairo = cairo_create(buf->surface); + + wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); + return buf; +} + +void destroy_buffer(struct pool_buffer *buffer) { + if (buffer->buffer) { + wl_buffer_destroy(buffer->buffer); + } + if (buffer->cairo) { + cairo_destroy(buffer->cairo); + } + if (buffer->surface) { + cairo_surface_destroy(buffer->surface); + } + if (buffer->data) { + munmap(buffer->data, buffer->size); + } + memset(buffer, 0, sizeof(struct pool_buffer)); +} + +struct pool_buffer *get_next_buffer(struct wl_shm *shm, + struct pool_buffer pool[static 2], uint32_t width, uint32_t height) { + struct pool_buffer *buffer = NULL; + + for (size_t i = 0; i < 2; ++i) { + if (pool[i].busy) { + continue; + } + buffer = &pool[i]; + } + + if (!buffer) { + return NULL; + } + + if (buffer->width != width || buffer->height != height) { + destroy_buffer(buffer); + } + + if (!buffer->buffer) { + if (!create_buffer(shm, buffer, width, height, + WL_SHM_FORMAT_ARGB8888)) { + return NULL; + } + } + buffer->busy = true; + return buffer; +} diff --git a/swaybg.1.scd b/swaybg.1.scd new file mode 100644 index 0000000..027e5d6 --- /dev/null +++ b/swaybg.1.scd @@ -0,0 +1,44 @@ +swaybg(1) + +# NAME + +swaybg - Background for Wayland + +# SYNOPSIS + +*swaybg* [options...] + +Displays a background image on all outputs of your Wayland session. + +Without an output specified, appearance options apply to all outputs. +Per-output appearance options can be set by passing _-o, --output_ followed by +these options. + +# OPTIONS + +*-c, --color* + Set the background color. + +*-h, --help* + Show help message and quit. + +*-i, --image* + Set the background image. + +*-m, --mode* + Scaling mode for images: _stretch_, _fill_, _fit_, _center_, or _tile_. Use + the additional mode _solid\_color_ to display only the background color, + even if a background image is specified. + +*-o, --output* + Select an output to configure. Subsequent appearance options will only + apply to this output. The special value _\*_ selects all outputs. + +*-v, --version* + Show the version number and quit. + +# AUTHORS + +Maintained by Drew DeVault , who is assisted by other open +source contributors. For more information about swaybg development, see +https://github.com/swaywm/sway. diff --git a/wlr-layer-shell-unstable-v1.xml b/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..2bb72ed --- /dev/null +++ b/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,285 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (size, anchor, exclusive zone, margin, interactivity) + is double-buffered, and will be applied at the time wl_surface.commit of + the corresponding wl_surface is called. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthoginal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area of the surface + with other surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to an + edge, rather than a corner. The zone is the number of surface-local + coordinates from the edge that are considered exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive excluzive zone. If set to -1, the surface + indicates that it would not like to be moved to accomodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + -- cgit v1.2.3