0.1.0 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, &registry_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}