main wf/howl / src / decor.c
  1#include <stdlib.h>
  2#include <string.h>
  3#include <stdint.h>
  4#include <stdbool.h>
  5#include <ctype.h>
  6#include <errno.h>
  7#include <limits.h>
  8#include <libgen.h>
  9#include <dirent.h>
 10
 11#include <wayland-server.h>
 12#include <swc.h>
 13#include <spng.h>
 14
 15#include "howl.h"
 16#include "types.h"
 17
 18extern struct wm wm;
 19extern struct decor *decor;
 20
 21static bool
 22decode_part(FILE *fd, struct swc_decor_part *out) {
 23	spng_ctx *ctx = spng_ctx_new(0);
 24	int ret;
 25
 26	uint32_t limit = 256 * 256 * 64;
 27	spng_set_image_limits(ctx, limit, limit);
 28	if ((ret = spng_set_png_file(ctx, fd)) != 0) {
 29		_wrn("couldn't set PNG input: %s", spng_strerror(ret));
 30		return false;
 31	}
 32
 33	struct spng_ihdr header;
 34	if ((ret = spng_get_ihdr(ctx, &header)) != 0) {
 35		_wrn("couldn't get image header: %s", spng_strerror(ret));
 36		return false;
 37	}
 38
 39	out->width = header.width;
 40	out->height = header.height;
 41	out->stride = header.width * 4;
 42
 43	size_t imglen = 0;
 44	if ((ret = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &imglen)) != 0) {
 45		_wrn("couldn't get image size: %s", spng_strerror(ret));
 46		spng_ctx_free(ctx);
 47		return false;
 48	}
 49
 50	uint8_t *tmp = malloc(imglen);
 51	if (!tmp) {
 52		_wrn("couldn't allocate image buffer: %s", strerror(errno));
 53		spng_ctx_free(ctx);
 54		return false;
 55	}
 56
 57	if ((ret = spng_decode_image(ctx, tmp, imglen, SPNG_FMT_RGBA8, SPNG_DECODE_TRNS | SPNG_DECODE_GAMMA)) != 0) {
 58		_wrn("couldn't decode image: %s", spng_strerror(ret));
 59		free(tmp);
 60		spng_ctx_free(ctx);
 61		return false;
 62	}
 63
 64	uint32_t *argb = malloc(imglen / 4 * sizeof(uint32_t));
 65	if (!argb) {
 66		_wrn("couldn't allocate image ARGB: %s", strerror(errno));
 67		free(tmp);
 68		return false;
 69	}
 70
 71	for (size_t i = 0; i < imglen / 4; ++i) {
 72		uint8_t r = tmp[i * 4 + 0];
 73		uint8_t g = tmp[i * 4 + 1];
 74		uint8_t b = tmp[i * 4 + 2];
 75		uint8_t a = tmp[i * 4 + 3];
 76		argb[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
 77	}
 78	free(tmp);
 79
 80	if (out->data)
 81		free((void *)out->data);
 82	out->data = (const void *)argb;
 83
 84	spng_ctx_free(ctx);
 85	return true;
 86}
 87
 88bool
 89load_decor(char *path) {
 90	if (path == NULL)
 91		return false;
 92
 93	/**
 94	 * path is a path to a decoration folder. the structure of the folder is as follows:
 95	 *
 96	 *  info:      contains the decoration metadata
 97	 *  active/:   contains the PNG textures to draw on the focused window's decoration
 98	 *  inactive/: same as above, but for unfocused windows
 99	 *
100	 * anything else in the directory is ignored, meaning you can put stuff like previews and readmes in it.
101	 */
102
103	DIR *dir = NULL;
104	char real[PATH_MAX];
105	if (!(realpath(path, real))) {
106		_wrn("couldn't get decoration real path: %s", strerror(errno));
107		goto error;
108	}
109	if (!(dir = opendir(real))) {
110		_wrn("couldn't open decoration at %s", path);
111		goto error;
112	}
113
114	struct dirent *de;
115	while ((de = readdir(dir))) {
116		if ((strcmp(de->d_name, "info")) == 0) {
117			FILE *fp;
118			char line[256], fullpath[512];
119			snprintf(fullpath, 512 * sizeof(char), "%s/%s", real, de->d_name);
120
121			if (!(fp = fopen(fullpath, "r"))) {
122				_wrn("couldn't open %s: %s", fullpath, strerror(errno));
123				goto error;
124			}
125			while (fgets(line, 256, fp)) {
126				char *sep = strchr(line, '=');
127				if (!sep)
128					continue;
129				*sep = '\0';
130				char *key = line;
131				char *val = sep + 1;
132
133				if (key[strlen(key) - 1] == ',' || key[strlen(key) - 1] == '\n')
134					key[strlen(key) - 1] = '\0';
135				while (key[0] && isspace((unsigned char)key[strlen(key) - 1]))
136					key[strlen(key) - 1] = '\0';
137				while (*key && isspace((unsigned char)*key))
138					key++;
139
140				while (*val && (isspace((unsigned char)*val) || *val == '"'))
141					val++;
142				size_t vlen = strlen(val);
143				while (vlen > 0 && (isspace((unsigned char)val[vlen - 1]) || val[vlen - 1] == '"'))
144					val[--vlen] = '\0';
145
146				if (strcmp(key, "left") == 0)
147					decor->edge_left = strtoul(val, NULL, 0);
148				else if (strcmp(key, "right") == 0)
149					decor->edge_right = strtoul(val, NULL, 0);
150				else if (strcmp(key, "top") == 0)
151					decor->edge_top = strtoul(val, NULL, 0);
152				else if (strcmp(key, "bottom") == 0)
153					decor->edge_bottom = strtoul(val, NULL, 0);
154				else if (strcmp(key, "title.enabled") == 0)
155					decor->enabled = (strcmp(val, "true") == 0);
156				else if (strcmp(key, "title.edge") == 0) {
157					if (strcmp(val, "left") == 0) decor->edge = SWC_DECOR_EDGE_LEFT;
158					else if (strcmp(val, "right") == 0) decor->edge = SWC_DECOR_EDGE_RIGHT;
159					else if (strcmp(val, "top") == 0) decor->edge = SWC_DECOR_EDGE_TOP;
160					else if (strcmp(val, "bottom") == 0) decor->edge = SWC_DECOR_EDGE_BOTTOM;
161				}
162				else if (strcmp(key, "title.align") == 0) {
163					if (strcmp(val, "start") == 0) decor->align = SWC_DECOR_ALIGN_START;
164					else if (strcmp(val, "center") == 0) decor->align = SWC_DECOR_ALIGN_CENTER;
165					else if (strcmp(val, "end") == 0) decor->align = SWC_DECOR_ALIGN_END;
166				}
167				else if (strcmp(key, "title.foreground") == 0)
168					decor->foreground = strtoul(val, NULL, 16);
169				else if (strcmp(key, "title.background") == 0)
170					decor->background = strtoul(val, NULL, 16);
171				else if (strcmp(key, "title.padding") == 0)
172					decor->padding = strtoul(val, NULL, 0);
173				else if (strcmp(key, "title.offset_x") == 0)
174					decor->offset_x = atoi(val);
175				else if (strcmp(key, "title.offset_y") == 0)
176					decor->offset_y = atoi(val);
177				else if (strcmp(key, "title.font") == 0) {
178					char *fontname = strdup(val);
179					if (!fontname) {
180						_wrn("couldn't allocate decoration font name: %s", strerror(errno));
181					} else {
182						free(decor->fontname);
183						decor->fontname = fontname;
184					}
185				}
186
187			}
188			fclose(fp);
189		}
190
191		else if ((strcmp(de->d_name, "active")) == 0) {
192			DIR *active;
193			char fullpath[512];
194			snprintf(fullpath, 512 * sizeof(char), "%s/%s", real, de->d_name);
195
196			if (!(active = opendir(fullpath))) {
197				_wrn("couldn't opendir %s: %s", fullpath, strerror(errno));
198				goto error;
199			}
200
201			struct dirent *de2;
202			while ((de2 = readdir(active))) {
203				char full2[512];
204				snprintf(full2, 512 * sizeof(char), "%s/%s", fullpath, de2->d_name);
205
206				FILE *fp;
207				if (!(fp = fopen(full2, "rb"))) {
208					_wrn("couldn't open %s: %s", full2, strerror(errno));
209					closedir(active);
210					goto error;
211				}
212				fseek(fp, 0, SEEK_SET);
213
214				if ((strcmp(de2->d_name, "top_left.png")) == 0)
215					decode_part(fp, &decor->active.top_left);
216				else if ((strcmp(de2->d_name, "top.png")) == 0)
217					decode_part(fp, &decor->active.top);
218				else if ((strcmp(de2->d_name, "top_right.png")) == 0)
219					decode_part(fp, &decor->active.top_right);
220				else if ((strcmp(de2->d_name, "left.png")) == 0)
221					decode_part(fp, &decor->active.left);
222				else if ((strcmp(de2->d_name, "right.png")) == 0)
223					decode_part(fp, &decor->active.right);
224				else if ((strcmp(de2->d_name, "bottom_left.png")) == 0)
225					decode_part(fp, &decor->active.bottom_left);
226				else if ((strcmp(de2->d_name, "bottom.png")) == 0)
227					decode_part(fp, &decor->active.bottom);
228				else if ((strcmp(de2->d_name, "bottom_right.png")) == 0)
229					decode_part(fp, &decor->active.bottom_right);
230
231				fclose(fp);
232			}
233
234			closedir(active);
235		}
236
237		else if ((strcmp(de->d_name, "inactive")) == 0) {
238			DIR *inactive;
239			char fullpath[512];
240			snprintf(fullpath, 512 * sizeof(char), "%s/%s", real, de->d_name);
241
242			if (!(inactive = opendir(fullpath))) {
243				/* fallback to active textures instead */
244				decor->inactive.top_left = decor->active.top_left;
245				decor->inactive.top = decor->active.top;
246				decor->inactive.top_right = decor->active.top_right;
247				decor->inactive.left = decor->active.left;
248				decor->inactive.right = decor->active.right;
249				decor->inactive.bottom_left = decor->active.bottom_left;
250				decor->inactive.bottom = decor->active.bottom;
251				decor->inactive.bottom_right = decor->active.bottom_right;
252				closedir(inactive);
253				break;
254			}
255
256			struct dirent *de2;
257			while ((de2 = readdir(inactive))) {
258				char full2[512];
259				snprintf(full2, 512 * sizeof(char), "%s/%s", fullpath, de2->d_name);
260
261				FILE *fp;
262				if (!(fp = fopen(full2, "rb"))) {
263					_wrn("couldn't open %s: %s", full2, strerror(errno));
264					goto error;
265				}
266
267				if ((strcmp(de2->d_name, "top_left.png")) == 0)
268					decode_part(fp, &decor->inactive.top_left);
269				else if ((strcmp(de2->d_name, "top.png")) == 0)
270					decode_part(fp, &decor->inactive.top);
271				else if ((strcmp(de2->d_name, "top_right.png")) == 0)
272					decode_part(fp, &decor->inactive.top_right);
273				else if ((strcmp(de2->d_name, "left.png")) == 0)
274					decode_part(fp, &decor->inactive.left);
275				else if ((strcmp(de2->d_name, "right.png")) == 0)
276					decode_part(fp, &decor->inactive.right);
277				else if ((strcmp(de2->d_name, "bottom_left.png")) == 0)
278					decode_part(fp, &decor->inactive.bottom_left);
279				else if ((strcmp(de2->d_name, "bottom.png")) == 0)
280					decode_part(fp, &decor->inactive.bottom);
281				else if ((strcmp(de2->d_name, "bottom_right.png")) == 0)
282					decode_part(fp, &decor->inactive.bottom_right);
283
284				fclose(fp);
285			}
286
287			closedir(inactive);
288		}
289	}
290
291	return true;
292
293error:
294	if (dir)
295		closedir(dir);
296	return false;
297}