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	struct wl_region *empty = wl_compositor_create_region(compositor);
339	struct zxdg_output_v1 *xdg;
340
341	output->surface = wl_compositor_create_surface(compositor);
342
343	/* fallthrough clicks to desktop or other surfaces (eg. root menu) */
344	wl_surface_set_input_region(output->surface, empty);
345	/*
346	 * the image must be scanned or probed prior to figuring out
347	 * transparency and it is such a niche edge case. if a specific
348	 * color with alpha is chosen however, this call seems to change
349	 * nothing for modern compositors as the alpha is rendered anyways.
350	 */
351	wl_surface_set_opaque_region(output->surface, empty);
352	wl_region_destroy(empty);
353
354	if (output_manager) {
355		xdg = zxdg_output_manager_v1_get_xdg_output(output_manager, output->wl);
356		zxdg_output_v1_add_listener(xdg, &xdg_output_listener, output);
357		/* ensure that the XDG handlers are reached before surface commit */
358		wl_display_roundtrip(display);
359	}
360	
361	output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell,
362				output->surface, output->wl, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "wallpaper");
363	zwlr_layer_surface_v1_set_anchor(output->layer_surface, 
364		ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
365		ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
366	zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1);
367	zwlr_layer_surface_v1_set_keyboard_interactivity(output->layer_surface, 0);
368	zwlr_layer_surface_v1_add_listener(output->layer_surface, &layer_surface_listener, output);
369			
370	wl_surface_commit(output->surface);
371}
372
373static const struct wl_callback_listener output_setup_listener = {
374	.done = output_setup_callback,
375};
376
377static void
378registry_handle_global(void *data, struct wl_registry *registry,
379		uint32_t name, const char *interface, uint32_t version)
380{
381	struct wl_callback *callback;
382
383	if (!strcmp(interface, wl_compositor_interface.name))
384		compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
385	else if (!strcmp(interface, wl_shm_interface.name))
386		shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
387	else if (!strcmp(interface, zwlr_layer_shell_v1_interface.name))
388		layer_shell = wl_registry_bind(registry, name,
389			&zwlr_layer_shell_v1_interface, 1);
390	else if (!strcmp(interface, zxdg_output_manager_v1_interface.name))
391		output_manager = wl_registry_bind(registry, name,
392		  &zxdg_output_manager_v1_interface, 1);
393	else if (!strcmp(interface, wl_output_interface.name)) {
394		struct output *output = calloc(1, sizeof(struct output));
395		if (!output) die("calloc:");
396		output->wl = wl_registry_bind(registry, name,
397			&wl_output_interface, 1);
398		wl_output_add_listener(output->wl, &output_listener, output);
399		wl_list_insert(&outputs, &output->link);
400		/*
401		 * There is no gurantee of the registry order, ensure a callback is used
402		 * to setup the output, which is going to be called in the next event
403		 * iteration, after the checks for registry objects is completed.
404		 */
405		callback = wl_display_sync(display);
406		wl_callback_add_listener(callback, &output_setup_listener, output);
407	}
408}
409
410static const struct wl_registry_listener registry_listener = {
411	.global = registry_handle_global,
412	.global_remove = noop, /* layer_surface_handle_closed */
413};
414
415int
416main(int argc, char *argv[])
417{
418	int len, dirn = 0;
419	char *mode = NULL, *path = NULL;
420	struct stat st;
421
422	switch (argc) {
423	case 2:
424		path = argv[1];
425		if (path[0] == '#')
426			path++;
427		len = strlen(path);
428		if (len != 6 && len != 8) goto file;
429
430		color = strtoul(path, NULL, 16);
431		if (len == 6) color = (color << 8) | 0xFF;
432		/* for colorspace conversion in output_image_load */
433		color = bswap_32(color);
434
435		image_modify = image_color;
436		break;
437	case 3:;
438		struct dirent **namelist = NULL, *name;
439
440		mode = argv[1]; path = argv[2];
441mode:
442		if (!strcmp(mode, "stretch")) image_modify = image_stretch;
443		else if (!strcmp(mode, "fit")) image_modify = image_fit;
444		else if (!strcmp(mode, "fill")) image_modify = image_fill;
445		else if (!strcmp(mode, "tile")) image_modify = image_tile;
446		else if (!strcmp(mode, "spread")) image_modify = image_spread;
447		else if (!dirn) goto usage;
448		else goto prescan;
449
450file:
451		if (stat(path, &st) < 0) die("stat %s:", path);
452
453		if (S_ISDIR(st.st_mode)) {
454			srand((intptr_t)path | (unsigned int)time(NULL));
455scan:
456			if (chdir(path) < 0) die("chdir:"); // slower & easier than strncpy PATH_MAX
457			if ((dirn = scandir(".", &namelist, NULL, alphasort)) < 0) die("scandir:");
458			if (dirn < 3) die("no images");
459			name = namelist[2 + rand() % (dirn - 2)];
460			while (dirn--) if (namelist[dirn] != name) free(namelist[dirn]);
461			free(namelist);
462			mode = basename((path = name->d_name));
463			if (argc == 2) goto mode;
464prescan:
465			if (name->d_type == DT_DIR) goto scan;
466		}
467
468		if (!(image.fp = fopen(path, "rb"))) die("fopen %s:", path);
469		if (dirn) free(name);
470		break;
471	default:
472		goto usage;
473	}
474
475	if (!(display = wl_display_connect(NULL)))
476		die("failed to connect to wayland");
477
478	wl_list_init(&outputs);
479
480	registry = wl_display_get_registry(display);
481	wl_registry_add_listener(registry, &registry_listener, NULL);
482	wl_display_roundtrip(display);
483	wl_display_roundtrip(display); /* output handlers */
484
485	if (!compositor || !layer_shell || !shm)
486		die("bad compositor available");
487
488	while (wl_display_dispatch(display) != -1)
489		;
490
491	return EXIT_SUCCESS;
492
493usage:
494	fprintf(stderr, "usage: %s [fill|fit|spread|stretch|tile] filename|dir\n"
495	                "       %s filename|dir|RRGGBB[AA]\n",
496	        argv[0], argv[0]);
497	return EXIT_FAILURE;
498}