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}