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