master
wawa.c
1/* See LICENSE file for copyright and license details. */
2#include <byteswap.h>
3#include <fcntl.h>
4#include <stdbool.h>
5#include <sys/mman.h>
6#include <unistd.h>
7#include <wayland-client-protocol.h>
8#include <wayland-client.h>
9#ifdef __linux__
10#include <linux/memfd.h>
11#endif
12
13#include "stbi_alloc.h"
14#define STB_IMAGE_IMPLEMENTATION
15#include "stb_image.h"
16#define STB_IMAGE_RESIZE_IMPLEMENTATION
17#include "stb_image_resize2.h"
18
19#include "wlr-layer-shell-unstable-v1-protocol.h"
20
21#define MAX(A, B) ((A) > (B) ? (A) : (B))
22#define MIN(A, B) ((A) < (B) ? (A) : (B))
23
24struct output {
25 struct wl_output *wl;
26 struct wl_surface *surface;
27 struct zwlr_layer_surface_v1 *layer_surface;
28
29 int32_t x, y;
30 uint32_t width, height;
31 uint32_t size, stride;
32
33 bool configured;
34
35 struct wl_list link;
36};
37
38struct rect {
39 int width, height;
40 int x, y;
41};
42
43static struct wl_display *display;
44static struct wl_registry *registry;
45static struct wl_compositor *compositor;
46static struct wl_shm *shm;
47static struct zwlr_layer_shell_v1 *layer_shell;
48static struct wl_list outputs;
49
50struct {
51 FILE *fp;
52 int width, height;
53 unsigned char *data;
54} image;
55
56static uint32_t color;
57
58static void image_fill(unsigned char *dst, struct output *output);
59static void (*image_modify)(unsigned char *, struct output *) = image_fill;
60
61static void
62noop() {}
63
64static void
65die(const char *fmt, ...)
66{
67 va_list ap;
68
69 va_start(ap, fmt);
70 vfprintf(stderr, fmt, ap);
71 va_end(ap);
72
73 if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
74 fputc(' ', stderr);
75 perror(NULL);
76 } else {
77 fputc('\n', stderr);
78 }
79
80 exit(EXIT_FAILURE);
81}
82
83static bool
84parse_color(const char *src)
85{
86 int len;
87
88 if (src[0] == '#')
89 src++;
90 len = strlen(src);
91 if (len != 6 && len != 8) return false;
92
93 color = strtoul(src, NULL, 16);
94 if (len == 6)
95 color = (color << 8) | 0xFF;
96
97 /* for colorspace conversion in output_image_load */
98 color = bswap_32(color);
99 return true;
100}
101
102static void
103image_color(unsigned char *dst, struct output *output)
104{
105 for (size_t i = 0; i < output->size; i += 4)
106 memcpy(dst + i, &color, 4);
107}
108
109static void
110image_stretch(unsigned char *dst, struct output *output)
111{
112 stbir_resize_uint8_linear(
113 image.data, image.width, image.height, image.width * 4,
114 dst, output->width, output->height, output->stride, 4);
115}
116
117static void
118image_fit(unsigned char *dst, struct output *output)
119{
120 struct rect crop;
121 double factor;
122
123 factor = fmin((double)output->width/image.width, (double)output->height/image.height);
124 crop.width = image.width * factor;
125 crop.height = image.height * factor;
126 crop.x = (output->width - crop.width) / 2;
127 crop.y = (output->height - crop.height) / 2;
128 stbir_resize_uint8_linear(
129 image.data, image.width, image.height, image.width * 4,
130 dst + crop.y * output->stride + crop.x * 4,
131 crop.width, crop.height, output->stride, 4);
132}
133
134static void
135image_fill(unsigned char *dst, struct output *output)
136{
137 struct rect crop;
138 double factor;
139
140 factor = fmin((double)image.width/output->width, (double)image.height/output->height);
141 crop.width = output->width * factor;
142 crop.height = output->height * factor;
143 crop.x = (image.width - crop.width) / 2;
144 crop.y = (image.height - crop.height) / 2;
145 stbir_resize_uint8_linear(
146 image.data + crop.y * image.width * 4 + crop.x * 4,
147 crop.width, crop.height, image.width * 4,
148 dst, output->width, output->height, output->stride, 4);
149}
150
151static void
152image_tile(unsigned char *dst, struct output *o)
153{
154 unsigned char *to, *src;
155 uint16_t off_x, off_y, w, h;
156
157 /* implementation shamelessly stolen from xwallpaper, MIT:
158 * 2025 Tobias Stoeckmann <tobias@stoeckmann.org> */
159 for (off_y = 0; off_y < o->height; off_y += image.height) {
160 h = (off_y + image.height > o->height) ? o->height - off_y : image.height;
161 for (off_x = 0; off_x < o->width; off_x += image.width) {
162 w = (off_x + image.width > o->width) ? o->width - off_x : image.width;
163 for (int y = 0; y < h; y++) {
164 to = dst + ((off_y + y) * o->stride);
165 src = image.data + (y * image.width * 4);
166 memcpy(to + (off_x * 4), src, w * 4);
167 }
168 }
169 }
170}
171
172static void
173image_spread(unsigned char *dst, struct output *output)
174{
175 /* i can only claim that i designated the exact ways to perform
176 * the calculations. */
177 struct output *o;
178 struct rect crop;
179 int min_x, min_y, max_x, max_y;
180 double scale;
181
182 /* set initial value to account for incoming negative outputs */
183 o = wl_container_of(outputs.next, o, link);
184 max_x = (min_x = o->x) + (o->width);
185 max_y = (min_y = o->y) + (o->height);
186
187 wl_list_for_each(o, &outputs, link) {
188 /* avoids some obscure compiler optimization ?? */
189 int32_t ow = o->width, oh = o->height;
190 min_x = MIN(min_x, o->x);
191 min_y = MIN(min_y, o->y);
192 max_x = MAX(o->x + ow, max_x);
193 max_y = MAX(o->y + oh, max_y);
194 }
195
196 crop.width = max_x - min_x;
197 crop.height = max_y - min_y;
198 scale = 1.0 / fmax((double)crop.width / image.width, (double)crop.height / image.height);
199 /* offset total bounding to center */
200 crop.x = (output->x - min_x) * scale + (image.width - crop.width * scale) / 2;
201 crop.y = (output->y - min_y) * scale + (image.height - crop.height * scale) / 2;
202 crop.width = MIN(ceil(output->width * scale), image.width - crop.x);
203 crop.height = MIN(ceil(output->height * scale), image.height - crop.y);
204
205 stbir_resize_uint8_linear(
206 image.data + (crop.y * image.width + crop.x) * 4,
207 crop.width, crop.height, image.width * 4,
208 dst, output->width, output->height, output->stride, 4);
209}
210
211static struct wl_buffer *
212output_load_image(struct output *output)
213{
214 int fd = -1;
215 struct wl_shm_pool *shm_pool;
216 struct wl_buffer *buffer;
217 unsigned char *data;
218
219 fd = memfd_create("drwbuf-shm-buffer-pool",
220 MFD_CLOEXEC | MFD_ALLOW_SEALING
221 #ifdef __linux__
222 | MFD_NOEXEC_SEAL
223 #endif
224 );
225 if (fd < 0) die("memfd_create:");
226
227 if ((ftruncate(fd, output->size)) < 0) die("ftruncate:");
228
229 data = mmap(NULL, output->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
230 if (data == MAP_FAILED) die("mmap:");
231
232 fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL);
233
234 shm_pool = wl_shm_create_pool(shm, fd, output->size);
235 buffer = wl_shm_pool_create_buffer(shm_pool, 0,
236 output->width, output->height, output->stride, WL_SHM_FORMAT_ARGB8888);
237 wl_shm_pool_destroy(shm_pool);
238 close(fd);
239
240 image_modify(data, output);
241
242 /* RGBA->BGRA */
243 for (int i = 0; i < output->size; i += 4) {
244 data[i] ^= data[i+2];
245 data[i+2] ^= data[i];
246 data[i] ^= data[i+2];
247 }
248
249 munmap(data, output->size);
250 return buffer;
251}
252
253static void
254layer_surface_handle_configure(void *data, struct zwlr_layer_surface_v1 *layer_surface,
255 uint32_t serial, uint32_t width, uint32_t height)
256{
257 struct wl_buffer *buffer;
258 struct output *output = data;
259
260 zwlr_layer_surface_v1_ack_configure(layer_surface, serial);
261
262 if (output->configured && width == output->width && height == output->height)
263 return;
264
265 if (!image.data && image.fp) {
266 if (fseek(image.fp, 0, SEEK_SET) < 0) die("fseek:");
267 image.data = stbi_load_from_file(image.fp, &image.width, &image.height, NULL, 4);
268 if (!image.data) die("failed to load image: %s", stbi_failure_reason());
269 }
270
271 output->width = width;
272 output->height = height;
273 output->stride = width * 4;
274 output->size = width * height * 4;
275
276 buffer = output_load_image(output);
277 wl_surface_attach(output->surface, buffer, 0, 0);
278 wl_surface_commit(output->surface);
279 wl_buffer_destroy(buffer);
280
281 output->configured = true;
282
283 if (!image.fp) return;
284 wl_list_for_each(output, &outputs, link)
285 if (!output->configured) return;
286
287 stbi_image_free(image.data);
288 image.data = NULL;
289}
290
291static void
292layer_surface_handle_closed(void *data, struct zwlr_layer_surface_v1 *surface)
293{
294 struct output *output = data;
295
296 zwlr_layer_surface_v1_destroy(output->layer_surface);
297 wl_surface_destroy(output->surface);
298 wl_output_destroy(output->wl);
299 wl_list_remove(&output->link);
300 free(output);
301}
302
303static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
304 .configure = layer_surface_handle_configure,
305 .closed = layer_surface_handle_closed,
306};
307
308static void
309output_handle_geometry(void *data, struct wl_output *wl_output,
310 int32_t x, int32_t y, int32_t physical_width, int32_t physical_height,
311 int32_t subpixel, const char *make, const char *model, int32_t transform)
312{
313 struct output *output = data;
314 output->x = x;
315 output->y = y;
316
317 /* A output's geometry has changed, mark all outputs as misconfigured */
318 wl_list_for_each(output, &outputs, link)
319 output->configured = false;
320}
321
322static const struct wl_output_listener output_listener = {
323 .geometry = output_handle_geometry,
324 .mode = noop,
325 .done = noop,
326 .scale = noop,
327 .name = noop,
328 .description = noop,
329};
330
331static void
332output_setup_callback(void *data, struct wl_callback *callback,
333 uint32_t time)
334{
335 struct output *output = data;
336
337 output->surface = wl_compositor_create_surface(compositor);
338
339 output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell,
340 output->surface, output->wl, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "wallpaper");
341 zwlr_layer_surface_v1_set_anchor(output->layer_surface,
342 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
343 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
344 zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1);
345 zwlr_layer_surface_v1_set_keyboard_interactivity(output->layer_surface, 0);
346 zwlr_layer_surface_v1_add_listener(output->layer_surface, &layer_surface_listener, output);
347
348 wl_surface_commit(output->surface);
349 wl_callback_destroy(callback);
350}
351
352static const struct wl_callback_listener output_setup_listener = {
353 .done = output_setup_callback,
354};
355
356static void
357registry_handle_global(void *data, struct wl_registry *registry,
358 uint32_t name, const char *interface, uint32_t version)
359{
360 struct wl_callback *callback;
361
362 if (!strcmp(interface, wl_compositor_interface.name))
363 compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
364 else if (!strcmp(interface, wl_shm_interface.name))
365 shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
366 else if (!strcmp(interface, zwlr_layer_shell_v1_interface.name))
367 layer_shell = wl_registry_bind(registry, name,
368 &zwlr_layer_shell_v1_interface, 1);
369 else if (!strcmp(interface, wl_output_interface.name)) {
370 struct output *output = calloc(1, sizeof(struct output));
371 if (!output) die("calloc:");
372 output->wl = wl_registry_bind(registry, name,
373 &wl_output_interface, 1);
374 wl_output_add_listener(output->wl, &output_listener, output);
375 wl_list_insert(&outputs, &output->link);
376 /*
377 * There is no gurantee of the registry order, ensure a callback is used
378 * to setup the output, which is going to be called in the next event
379 * iteration, after the checks for registry objects is completed.
380 */
381 callback = wl_display_sync(display);
382 wl_callback_add_listener(callback, &output_setup_listener, output);
383 }
384}
385
386static void
387registry_handle_remove(void *data, struct wl_registry *registry, uint32_t name)
388{
389}
390
391static const struct wl_registry_listener registry_listener = {
392 .global = registry_handle_global,
393 .global_remove = registry_handle_remove,
394};
395
396int
397main(int argc, char *argv[])
398{
399 switch (argc) {
400 case 2:
401 if (!(parse_color(argv[1]))) goto usage;
402 image_modify = image_color;
403 break;
404 case 3:
405 if (!strcmp(argv[1], "stretch")) image_modify = image_stretch;
406 else if (!strcmp(argv[1], "fit")) image_modify = image_fit;
407 else if (!strcmp(argv[1], "fill")) image_modify = image_fill;
408 else if (!strcmp(argv[1], "tile")) image_modify = image_tile;
409 else if (!strcmp(argv[1], "spread")) image_modify = image_spread;
410 else goto usage;
411
412 if (!(image.fp = fopen(argv[2], "rb"))) die("fopen %s:", argv[2]);
413 break;
414 default:
415 goto usage;
416 }
417
418 if (!(display = wl_display_connect(NULL)))
419 die("failed to connect to wayland");
420
421 wl_list_init(&outputs);
422
423 registry = wl_display_get_registry(display);
424 wl_registry_add_listener(registry, ®istry_listener, NULL);
425 wl_display_roundtrip(display);
426 wl_display_roundtrip(display); /* output handlers */
427
428 if (!compositor || !layer_shell || !shm)
429 die("bad compositor available");
430
431 while (wl_display_dispatch(display) != -1)
432 ;
433
434 return EXIT_SUCCESS;
435
436usage:
437 fprintf(stderr, "usage: %s fill|fit|spread|stretch|tile filename\n"
438 " %s RRGGBBAA\n",
439 argv[0], argv[0]);
440 return EXIT_FAILURE;
441}