pax_global_header00006660000000000000000000000064146134021060014507gustar00rootroot0000000000000052 comment=dcb5ebc76adcfef81c1a275ae7ed21a7936cbd7c swaybg-1.2.1/000077500000000000000000000000001461340210600130045ustar00rootroot00000000000000swaybg-1.2.1/.build.yml000066400000000000000000000004771461340210600147140ustar00rootroot00000000000000image: alpine/edge packages: - cairo-dev - gdk-pixbuf-dev - meson - scdoc - wayland-dev - wayland-protocols sources: - https://github.com/swaywm/swaybg tasks: - setup: | cd swaybg meson build/ --fatal-meson-warnings -Dauto_features=enabled - build: | cd swaybg ninja -C build/ swaybg-1.2.1/.editorconfig000066400000000000000000000005021461340210600154560ustar00rootroot00000000000000# For the full list of code style requirements, see sway's CONTRIBUTING.md root = true [*] charset = utf-8 end_of_line = lf [*.{c,h,cmake,txt}] indent_style = tab indent_size = 4 [*.{xml,yml}] indent_style = space indent_size = 2 [config] indent_style = space indent_size = 4 [*.md] trim_trailing_whitespace = false swaybg-1.2.1/.gitignore000066400000000000000000000000061461340210600147700ustar00rootroot00000000000000build swaybg-1.2.1/LICENSE000066400000000000000000000020451461340210600140120ustar00rootroot00000000000000Copyright (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. swaybg-1.2.1/README.md000066400000000000000000000022121461340210600142600ustar00rootroot00000000000000# swaybg swaybg is a wallpaper utility for Wayland compositors. It is compatible with any Wayland compositor which implements the wlr-layer-shell protocol and `wl_output` version 4. See the man page, `swaybg(1)`, for instructions on using swaybg. ## Release Signatures Releases are signed with [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) and published [on GitHub](https://github.com/swaywm/swaybg/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 (optional: image formats other than PNG) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* * git (optional: version information) \* _\* Compile-time dep_ Run these commands: meson build/ ninja -C build/ sudo ninja -C build/ install swaybg-1.2.1/background-image.c000066400000000000000000000076311461340210600163560ustar00rootroot00000000000000#include #include "background-image.h" #include "cairo_util.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; } // Correct for embedded image orientation; typical images are not // rotated and will be handled efficiently GdkPixbuf *oriented = gdk_pixbuf_apply_embedded_orientation(pixbuf); g_object_unref(pixbuf); image = gdk_cairo_image_surface_create_from_pixbuf(oriented); g_object_unref(oriented); #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); cairo_pattern_destroy(pattern); break; } case BACKGROUND_MODE_SOLID_COLOR: case BACKGROUND_MODE_INVALID: assert(0); break; } cairo_paint(cairo); cairo_restore(cairo); } swaybg-1.2.1/cairo.c000066400000000000000000000065741461340210600142610ustar00rootroot00000000000000#include #include #include "cairo_util.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 swaybg-1.2.1/include/000077500000000000000000000000001461340210600144275ustar00rootroot00000000000000swaybg-1.2.1/include/background-image.h000066400000000000000000000010551461340210600200000ustar00rootroot00000000000000#ifndef _SWAY_BACKGROUND_IMAGE_H #define _SWAY_BACKGROUND_IMAGE_H #include "cairo_util.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 swaybg-1.2.1/include/cairo_util.h000066400000000000000000000010661461340210600167350ustar00rootroot00000000000000#ifndef _SWAY_CAIRO_UTIL_H #define _SWAY_CAIRO_UTIL_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 swaybg-1.2.1/include/log.h000066400000000000000000000014551461340210600153660ustar00rootroot00000000000000#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 swaybg-1.2.1/include/pool-buffer.h000066400000000000000000000006631461340210600170250ustar00rootroot00000000000000#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; void *data; size_t size; }; bool create_buffer(struct pool_buffer *buffer, struct wl_shm *shm, int32_t width, int32_t height, uint32_t format); void destroy_buffer(struct pool_buffer *buffer); #endif swaybg-1.2.1/log.c000066400000000000000000000026241461340210600137350ustar00rootroot00000000000000#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; } swaybg-1.2.1/main.c000066400000000000000000000463151461340210600141050ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "background-image.h" #include "cairo_util.h" #include "log.h" #include "pool-buffer.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" #include "single-pixel-buffer-v1-client-protocol.h" /* * If `color` is a hexadecimal string of the form 'rrggbb' or '#rrggbb', * `*result` will be set to the uint32_t version of the color. Otherwise, * return false and leave `*result` unmodified. */ static bool parse_color(const char *color, uint32_t *result) { if (color[0] == '#') { ++color; } int len = strlen(color); if (len != 6) { return false; } for (int i = 0; i < len; ++i) { if (!isxdigit(color[i])) { return false; } } uint32_t val = (uint32_t)strtoul(color, NULL, 16); *result = (val << 8) | 0xFF; return true; } struct swaybg_state { struct wl_display *display; struct wl_compositor *compositor; struct wl_shm *shm; struct zwlr_layer_shell_v1 *layer_shell; struct wp_viewporter *viewporter; struct wp_single_pixel_buffer_manager_v1 *single_pixel_buffer_manager; struct wl_list configs; // struct swaybg_output_config::link struct wl_list outputs; // struct swaybg_output::link struct wl_list images; // struct swaybg_image::link bool run_display; }; struct swaybg_image { struct wl_list link; const char *path; bool load_required; }; struct swaybg_output_config { char *output; const char *image_path; struct swaybg_image *image; enum background_mode mode; uint32_t color; struct wl_list link; }; struct swaybg_output { uint32_t wl_name; struct wl_output *wl_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; uint32_t width, height; int32_t scale; uint32_t configure_serial; bool dirty, needs_ack; int32_t committed_width, committed_height, committed_scale; struct wl_list link; }; static void render_frame(struct swaybg_output *output, cairo_surface_t *surface) { int buffer_width = output->width * output->scale, buffer_height = output->height * output->scale; // If the last committed buffer has the same size as this one would, do // not render a new buffer, because it will be identical to the old one if (output->committed_width == buffer_width && output->committed_height == buffer_height) { if (output->committed_scale != output->scale) { wl_surface_set_buffer_scale(output->surface, output->scale); wl_surface_commit(output->surface); output->committed_scale = output->scale; } return; } if (output->config->mode == BACKGROUND_MODE_SOLID_COLOR && output->state->viewporter && output->state->single_pixel_buffer_manager) { uint8_t r8 = (output->config->color >> 24) & 0xFF; uint8_t g8 = (output->config->color >> 16) & 0xFF; uint8_t b8 = (output->config->color >> 8) & 0xFF; uint8_t a8 = (output->config->color >> 0) & 0xFF; uint32_t f = 0xFFFFFFFF / 0xFF; // division result is an integer uint32_t r32 = r8 * f; uint32_t g32 = g8 * f; uint32_t b32 = b8 * f; uint32_t a32 = a8 * f; struct wl_buffer *buffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( output->state->single_pixel_buffer_manager, r32, g32, b32, a32); wl_surface_attach(output->surface, buffer, 0, 0); wl_surface_damage_buffer(output->surface, 0, 0, INT32_MAX, INT32_MAX); struct wp_viewport *viewport = wp_viewporter_get_viewport( output->state->viewporter, output->surface); wp_viewport_set_destination(viewport, output->width, output->height); wl_surface_commit(output->surface); wp_viewport_destroy(viewport); wl_buffer_destroy(buffer); return; } struct pool_buffer buffer; if (!create_buffer(&buffer, output->state->shm, buffer_width, buffer_height, WL_SHM_FORMAT_ARGB8888)) { return; } cairo_t *cairo = 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); } if (surface) { render_background_image(cairo, surface, output->config->mode, buffer_width, buffer_height); } } wl_surface_set_buffer_scale(output->surface, output->scale); wl_surface_attach(output->surface, buffer.buffer, 0, 0); wl_surface_damage_buffer(output->surface, 0, 0, INT32_MAX, INT32_MAX); wl_surface_commit(output->surface); output->committed_width = buffer_width; output->committed_height = buffer_height; output->committed_scale = output->scale; // we will not reuse the buffer, so destroy it immediately destroy_buffer(&buffer); } static void destroy_swaybg_image(struct swaybg_image *image) { if (!image) { return; } wl_list_remove(&image->link); free(image); } 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); } wl_output_destroy(output->wl_output); 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; output->dirty = true; output->configure_serial = serial; output->needs_ack = true; } 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 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 output_done(void *data, struct wl_output *wl_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 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) { output->dirty = true; } } 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 output_name(void *data, struct wl_output *wl_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 output_description(void *data, struct wl_output *wl_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 const struct wl_output_listener output_listener = { .geometry = output_geometry, .mode = output_mode, .done = output_done, .scale = output_scale, .name = output_name, .description = output_description, }; 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, 4); wl_output_add_listener(output->wl_output, &output_listener, output); wl_list_insert(&state->outputs, &output->link); } 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, wp_viewporter_interface.name) == 0) { state->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); } else if (strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) == 0) { state->single_pixel_buffer_manager = wl_registry_bind(registry, name, &wp_single_pixel_buffer_manager_v1_interface, 1); } } 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_path) { oc->image_path = config->image_path; } 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 RRGGBB 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(1, sizeof(struct swaybg_output_config)); 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 (!parse_color(optarg, &config->color)) { swaybg_log(LOG_ERROR, "%s is not a valid color for swaybg. " "Color should be specified as rrggbb or #rrggbb (no alpha).", optarg); continue; } break; case 'i': // image config->image_path = 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(1, sizeof(struct swaybg_output_config)); 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_path && !config->color) { destroy_swaybg_output_config(config); } else if (config->mode == BACKGROUND_MODE_INVALID) { config->mode = config->image_path ? 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); wl_list_init(&state.images); parse_command_line(argc, argv, &state); // Identify distinct image paths which will need to be loaded struct swaybg_image *image; struct swaybg_output_config *config; wl_list_for_each(config, &state.configs, link) { if (!config->image_path) { continue; } wl_list_for_each(image, &state.images, link) { if (strcmp(image->path, config->image_path) == 0) { config->image = image; break; } } if (config->image) { continue; } image = calloc(1, sizeof(struct swaybg_image)); image->path = config->image_path; wl_list_insert(&state.images, &image->link); config->image = image; } 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); if (wl_display_roundtrip(state.display) < 0) { swaybg_log(LOG_ERROR, "wl_display_roundtrip failed"); return 1; } if (state.compositor == NULL || state.shm == NULL || state.layer_shell == NULL) { swaybg_log(LOG_ERROR, "Missing a required Wayland interface"); return 1; } state.run_display = true; while (wl_display_dispatch(state.display) != -1 && state.run_display) { // Send acks, and determine which images need to be loaded struct swaybg_output *output; wl_list_for_each(output, &state.outputs, link) { if (output->needs_ack) { output->needs_ack = false; zwlr_layer_surface_v1_ack_configure( output->layer_surface, output->configure_serial); } int buffer_width = output->width * output->scale, buffer_height = output->height * output->scale; bool buffer_change = output->committed_height != buffer_height || output->committed_width != buffer_width; if (output->dirty && output->config->image && buffer_change) { output->config->image->load_required = true; } } // Load images, render associated frames, and unload wl_list_for_each(image, &state.images, link) { if (!image->load_required) { continue; } cairo_surface_t *surface = load_background_image(image->path); if (!surface) { swaybg_log(LOG_ERROR, "Failed to load image: %s", image->path); continue; } wl_list_for_each(output, &state.outputs, link) { if (output->dirty && output->config->image == image) { output->dirty = false; render_frame(output, surface); } } image->load_required = false; cairo_surface_destroy(surface); } // Redraw outputs without associated image wl_list_for_each(output, &state.outputs, link) { if (output->dirty) { output->dirty = false; render_frame(output, NULL); } } } struct swaybg_output *output, *tmp_output; wl_list_for_each_safe(output, tmp_output, &state.outputs, link) { destroy_swaybg_output(output); } struct swaybg_output_config *tmp_config = NULL; wl_list_for_each_safe(config, tmp_config, &state.configs, link) { destroy_swaybg_output_config(config); } struct swaybg_image *tmp_image; wl_list_for_each_safe(image, tmp_image, &state.images, link) { destroy_swaybg_image(image); } return 0; } swaybg-1.2.1/meson.build000066400000000000000000000060301461340210600151450ustar00rootroot00000000000000project( 'swaybg', 'c', version: '1.2.1', license: 'MIT', meson_version: '>=0.59.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 cc = meson.get_compiler('c') rt = cc.find_library('rt') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.26') wayland_scanner = dependency('wayland-scanner', version: '>=1.14.91', native: true) cairo = dependency('cairo') gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf')) git = find_program('git', required: false, native: true) scdoc = find_program('scdoc', required: get_option('man-pages'), native: true) version = '"@0@"'.format(meson.project_version()) if git.found() git_commit_hash = run_command([git, 'describe', '--always', '--tags'], check: false) git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false) 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), '-DHAVE_GDK_PIXBUF=@0@'.format(gdk_pixbuf.found().to_int()), ], language: 'c') wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') wayland_scanner_prog = find_program(wayland_scanner.get_variable('wayland_scanner'), native: true) wayland_scanner_code = generator( wayland_scanner_prog, output: '@BASENAME@-protocol.c', arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner_prog, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) protos_src = [] client_protocols = [ wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', wl_protocol_dir / 'stable/viewporter/viewporter.xml', wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', 'wlr-layer-shell-unstable-v1.xml', ] foreach filename : client_protocols protos_src += wayland_scanner_code.process(filename) protos_src += wayland_scanner_client.process(filename) endforeach executable( 'swaybg', [ 'background-image.c', 'cairo.c', 'log.c', 'main.c', 'pool-buffer.c', protos_src, ], include_directories: 'include', dependencies: [ cairo, rt, gdk_pixbuf, wayland_client, ], install: true ) if scdoc.found() 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: scdoc, feed: true, capture: true, install: true, install_dir: '@0@/man@1@'.format(mandir, section) ) endforeach endif swaybg-1.2.1/meson_options.txt000066400000000000000000000003161461340210600164410ustar00rootroot00000000000000option('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') swaybg-1.2.1/pool-buffer.c000066400000000000000000000035301461340210600153710ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809 #include #include #include #include #include #include #include #include #include #include #include #include "pool-buffer.h" static int anonymous_shm_open(void) { int retries = 100; do { // try a probably-unique name struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); pid_t pid = getpid(); char name[50]; snprintf(name, sizeof(name), "/swaybg-%x-%x", (unsigned int)pid, (unsigned int)ts.tv_nsec); // shm_open guarantees that O_CLOEXEC is set int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { shm_unlink(name); return fd; } --retries; } while (retries > 0 && errno == EEXIST); return -1; } bool create_buffer(struct pool_buffer *buf, struct wl_shm *shm, int32_t width, int32_t height, uint32_t format) { uint32_t stride = width * 4; size_t size = stride * height; int fd = anonymous_shm_open(); assert(fd != -1); if (ftruncate(fd, size) < 0) { close(fd); return false; } 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); buf->size = size; buf->data = data; buf->surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, stride); buf->cairo = cairo_create(buf->surface); return true; } 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); } } swaybg-1.2.1/swaybg.1.scd000066400000000000000000000021111461340210600151250ustar00rootroot00000000000000swaybg(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* <[#]rrggbb> 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/swaybg. swaybg-1.2.1/wlr-layer-shell-unstable-v1.xml000066400000000000000000000320741461340210600207160ustar00rootroot00000000000000 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 accommodate 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.