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