]> git.draconx.ca Git - rrace.git/blob - t/ewmhicon.c
ewmhicon: Fix colourmap generation in w32vga mode.
[rrace.git] / t / ewmhicon.c
1 /*
2  * Test app for _NET_WM_ICON formatting.
3  * Copyright © 2022-2024 Nick Bowler
4  *
5  * Use a fake colour scheme to generate an icon of the chosen size (16x16,
6  * 24x24, 32x32 or 48x48) and display the pixels as characters.
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20  */
21
22 #include <config.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <limits.h>
27 #include <errno.h>
28 #include <assert.h>
29 #include <getopt.h>
30
31 #include "ewmhicon.h"
32 #include "icon.h"
33 #include "colour.h"
34 #include "help.h"
35 #include "xtra.h"
36
37 enum {
38         SOPT_MAX = UCHAR_MAX,
39         LOPT_W32VGA,
40         LOPT_TEST_CMAP,
41         LOPT_MAX
42 };
43
44 static const char *progname = "ewmhicon";
45 static const char sopts[] = "c:VH";
46 static const struct option lopts[] = {
47         { "test-cmap", 0, NULL, LOPT_TEST_CMAP },
48         { "colourseq", 1, NULL, 'c' },
49         { "w32vga",    0, NULL, LOPT_W32VGA },
50         { "version",   0, NULL, 'V' },
51         { "help",      0, NULL, 'H' },
52         { 0 }
53 };
54
55 #define S8TO16(x) ((x) * 0xfffful / 0xff)
56
57 #define RGB8(r, g, b) { \
58         0xff000000 | (r << 16) | (g << 8) | b, \
59         S8TO16(r), S8TO16(g), S8TO16(b) }
60
61 #define COLOUR_SYS_XCOLOR(r, g, b) \
62         RGB8(0x ## r ## ul, 0x ## g ## ul, 0x ## b ## ul)
63
64 #undef COLOUR_SYSTEM
65 #define COLOUR_SYSTEM XCOLOR
66
67 #define COLOURTAB_(n) { n ## _PRIMARY, n ## _DARK, n ## _LIGHT }
68 #define COLOURTAB(n) COLOURTAB_(COLOUR ## n)
69
70 static const XColor colours[7][3] = {
71         {
72                 { 0xffff0000, 0xffff },
73                 { 0xff00ff00, 0, 0xffff },
74                 { 0xff0000ff, 0, 0, 0xffff }
75         },
76         COLOURTAB(0), COLOURTAB(1), COLOURTAB(2),
77         COLOURTAB(3), COLOURTAB(4), COLOURTAB(5)
78 };
79
80 static void print_usage(FILE *f)
81 {
82         fprintf(f, "Usage: %s size\n", progname);
83         if (f != stdout)
84                 fprintf(f, "Try %s --help for more information.\n", progname);
85 }
86
87 static void print_help(void)
88 {
89         const struct option *opt;
90
91         print_usage(stdout);
92         putchar('\n');
93         puts("Options:");
94         for (opt = lopts; opt->name; opt++) {
95                 if (help_print_optstring(opt, "ARG", 20))
96                         putchar('\n');
97         }
98         putchar('\n');
99
100         printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
101 }
102
103 static void print_version(void)
104 {
105         printf("ewmhicon (%s) %s\n", PACKAGE, PACKAGE_VERSION);
106         printf("Copyright (C) 2023 Nick Bowler\n");
107         puts("License GPLv3+: GNU GPL version 3 or any later version");
108         puts("This is free software: you are free to change and redistribute it.");
109         puts("There is NO WARRANTY, to the extent permitted by law.");
110 }
111
112
113 #define COLOUR_SYS_HEX(r, g, b) 0x ## r ## g ## b ## ul
114
115 #undef COLOUR_SYSTEM
116 #define COLOUR_SYSTEM HEX
117
118 /*
119  * Returns true iff this pixel is an "edge" -- that is, either there
120  * is no pixel to the right or below or those pixels are a different
121  * colour.
122  */
123 static int is_edge(unsigned x, unsigned y, unsigned w, unsigned h,
124                    unsigned long *p, unsigned long colour)
125 {
126         return y+1 >= h || x+1 >= w
127             || (p[1] & 0xffffff) != colour
128             || (p[w] & 0xffffff) != colour;
129 }
130
131 static void print_xpm_w32vga(unsigned long size, unsigned long *icon_buf)
132 {
133         unsigned w = size & 0xffff, h = (size >> 16) & 0xffff;
134         unsigned x, y, i, j, n;
135
136         const char colourchars[16] = "#rgybmc-+RGYBMC ";
137         unsigned long palette[16] = {
138                 0x000000, 0x800000, 0x008000, 0x808000,
139                 0x000080, 0x800080, 0x008080, 0xc0c0c0,
140                 0x808080, 0xff0000, 0x00ff00, 0xffff00,
141                 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff
142         };
143
144         unsigned long prev_colour = 0;
145         unsigned used_colours = 0, v;
146
147         size = (unsigned long) w * h;
148         for (i = 0; i < size; i++) {
149                 unsigned long *p = &icon_buf[i];
150                 char *c;
151
152                 x = i%w, y=i/w;
153
154                 switch (*p & 0xffffff) {
155                 /* red */
156                 case COLOUR0_PRIMARY:
157                         *p = y&1 ? ( x&1 ? 'r' : 'm' ) : ( x&1 ? 'y' : 'r' );
158                         break;
159                 case COLOUR0_LIGHT:
160                         *p = (x^y)&1 ? '+' : 'r';
161                         break;
162                 case COLOUR0_DARK:
163                         *p = 'r';
164                         break;
165                 /* orange */
166                 case COLOUR1_PRIMARY:
167                         *p = y&1 ? 'y' : x&1 ? '+' : 'R';
168                         break;
169                 case COLOUR1_LIGHT:
170                         *p = (x^y)&1 ? '+' : 'y';
171                         break;
172                 case COLOUR1_DARK:
173                         *p = (x^y)&1 ? 'r' : 'y';
174                         break;
175                 /* yellow */
176                 case COLOUR2_PRIMARY:
177                         *p = (x^y)&1 ? '+' : 'Y';
178                         break;
179                 case COLOUR2_LIGHT:
180                         *p = (x^y)&1 ? '-' : 'Y';
181                         break;
182                 case COLOUR2_DARK:
183                         *p = (x^y)&1 ? '+' : 'Y';
184                         if (is_edge(x, y, w, h, p, COLOUR2_DARK))
185                                 *p = (x^y)&1 ? '+' : 'y';
186                         break;
187                 /* green */
188                 case COLOUR3_PRIMARY:
189                         *p = y&1 ? 'g' : x&1 ? '+' : 'g';
190                         break;
191                 case COLOUR3_LIGHT:
192                         *p = (x^y)&1 ? '+' : 'g';
193                         break;
194                 case COLOUR3_DARK:
195                         *p = 'g';
196                         if (is_edge(x, y, w, h, p, COLOUR3_DARK))
197                                 *p = (x^y)&1 ? '#' : 'g';
198                         break;
199                 /* blue */
200                 case COLOUR4_PRIMARY:
201                         *p = (x^y)&1 ? 'b' : 'c';
202                         break;
203                 case COLOUR4_LIGHT:
204                         *p = 'c';
205                         if (is_edge(x, y, w, h, p, COLOUR4_LIGHT))
206                                 *p = (x^y)&1 ? 'b' : 'c';
207                         break;
208                 case COLOUR4_DARK:
209                         *p = 'b';
210                         if (is_edge(x, y, w, h, p, COLOUR4_DARK))
211                                 *p = (x^y)&1 ? '#' : 'b';
212                         break;
213                 /* white */
214                 case COLOUR5_PRIMARY:
215                         *p = (x^y)&1 ? '-' : ' ';
216                         break;
217                 case COLOUR5_LIGHT:
218                         *p = ' ';
219                         break;
220                 case COLOUR5_DARK:
221                         *p = (x^y)&1 ? '+' : ' ';
222                         break;
223                 /* dummy */
224                 case 0xff0000: *p = 'R'; break;
225                 case 0x00ff00: *p = 'G'; break;
226                 case 0x0000ff: *p = 'B'; break;
227                 }
228
229                 c = strchr(colourchars, *p);
230                 assert(c && c - colourchars < 16);
231                 used_colours |= 1u << (c-colourchars);
232         }
233
234         for (n = 0, v = used_colours; v; n++)
235                 v &= v-1;
236
237         puts("/* XPM */");
238         puts("static char *icon[] = {");
239         printf("\"%u %u %u 1\",\n", w, h, n);
240         for (i = 0; i < sizeof colourchars; i++) {
241                 if (!(used_colours & (1u << i)))
242                         continue;
243
244                 printf("\"%c c #%.6lx\",\n", colourchars[i], palette[i]);
245         }
246
247         for (y = 0; y < h; y++) {
248                 putchar('"');
249                 for (x = 0; x < w; x++) {
250                         putchar(icon_buf[y*h+x]);
251                 }
252                 printf("\"%.*s\n", y+1<h, ",");
253         }
254
255         puts("};");
256 }
257
258 static void
259 print_xpm(const char *colourseq, unsigned long size, unsigned long *icon_buf)
260 {
261         const char colourchars[21] = ".%+"",Rr""-Oo""'Yy"":Gg"";Bb"" Ww";
262         unsigned w = size & 0xffff, h = (size >> 16) & 0xffff;
263         unsigned x, y, i, j, n;
264
265         n = 7;
266         for (i = 0; i < 7; i++)
267                 n -= !strchr(colourseq, '0'+i);
268
269         puts("/* XPM */");
270         puts("static char *icon[] = {");
271         printf("\"%u %u %u 1\",\n", w, h, 3*n);
272         for (i = 0; i < 7; i++) {
273                 if (!strchr(colourseq, '0'+i))
274                         continue;
275
276                 for (j = 0; j < 3; j++) {
277                         printf("\"%c c #%.6lx\",\n", colourchars[3*i+j],
278                                        colours[i][j].pixel & 0xffffff);
279                 }
280         }
281
282         for (y = 0; y < h; y++) {
283                 putchar('"');
284                 for (x = 0; x < w; x++) {
285                         unsigned long val = icon_buf[y*h+x];
286                         int c = '#';
287
288                         for (i = 0; i < sizeof colourchars; i++) {
289                                 if (colours[i/3][i%3].pixel == val) {
290                                         c = colourchars[i];
291                                         break;
292                                 }
293                         }
294
295                         putchar(c);
296                 }
297                 printf("\"%.*s\n", y+1<h, ",");
298         }
299         puts("};");
300 }
301
302 static int decode_colourseq(char *out, const char *arg)
303 {
304         int i;
305
306         for (i = 0; i < 9; i++) {
307                 switch (arg[i]) {
308                 case 0: return 0;
309                 case 'R': case 'r': out[i] = '1'; break;
310                 case 'O': case 'o': out[i] = '2'; break;
311                 case 'Y': case 'y': out[i] = '3'; break;
312                 case 'G': case 'g': out[i] = '4'; break;
313                 case 'B': case 'b': out[i] = '5'; break;
314                 case 'W': case 'w': out[i] = '6'; break;
315                 case '1': case '2': case '3': case '4': case '5': case '6':
316                         case '0': out[i] = arg[i]; break;
317                 default: goto err;
318                 }
319         }
320
321         if (arg[i]) {
322 err:
323                 fprintf(stderr, "%s: error: invalid colour sequence '%s'\n",
324                                 progname, arg);
325                 return -1;
326         }
327
328         return 0;
329 }
330
331 /* Convert the user sequence into a list of colour index specifiers */
332 static unsigned long *expand_seq(unsigned long *out, const char *seq)
333 {
334         int i;
335
336         for (i = 0; i < 9; i++) {
337                 out[i] = 0x30303 * (seq[i]-'0') + 0x20100;
338         }
339
340         return out;
341 }
342
343 static unsigned long decode_size(char *size_spec)
344 {
345         unsigned long w, h;
346         char c = 0;
347         size_t n;
348
349         n = strspn(size_spec, "0123456789");
350         switch (size_spec[n]) {
351         default:
352                 goto err_invalid;
353         case 0: case 'x': case 'X':
354                 c = size_spec[n];
355                 size_spec[n] = 0;
356         }
357
358         w = strtoul(size_spec, NULL, 10);
359         size_spec[n] = c;
360         if (w > 0xffff)
361                 goto err_range;
362
363         if (!c) {
364                 h = w;
365         } else {
366                 n = strspn(size_spec += n+1, "0123456789");
367                 if (size_spec[n])
368                         goto err_invalid;
369
370                 h = strtoul(size_spec, NULL, 10);
371                 if (h > 0xffff)
372                         goto err_range;
373         }
374
375         return (h << 16) | w;
376 err_range:
377         fprintf(stderr, "%s: %s: %s\n", progname, size_spec, strerror(ERANGE));
378         return 0;
379 err_invalid:
380         fprintf(stderr, "%s: %s: %s\n", progname, size_spec,
381                                         "invalid size specification");
382         return 0;
383 }
384
385 static unsigned long *find_icon(unsigned long size, unsigned long *ewmhicon)
386 {
387         unsigned long w = size & 0xffff, h = (size >> 16) & 0xffff;
388         unsigned long i;
389
390         if (!size)
391                 return NULL;
392
393         for (i = 0; i < EWMH_ICON_NELEM-2;) {
394                 unsigned long icon_w = ewmhicon[i];
395                 unsigned long icon_h = ewmhicon[i+1];
396
397                 if (w == icon_w && h == icon_h) {
398                         i += 2;
399                         break;
400                 }
401
402                 assert(icon_w < ULONG_MAX / icon_h);
403                 assert(i < ULONG_MAX - icon_w*icon_h);
404                 i += 2 + icon_w*icon_h;
405         }
406
407         if (i < EWMH_ICON_NELEM)
408                 return &ewmhicon[i];
409
410         fprintf(stderr, "%s: error: no %lux%lu icon found\n", progname, w, h);
411         return NULL;
412 }
413
414 static int test_ewmh_icon_prepare(void)
415 {
416         int ret = EXIT_SUCCESS;
417         unsigned i;
418
419         XColor *template = (void *)colours;
420         XColor newmap[sizeof colours / sizeof colours[0][0]];
421
422         printf("1..%d\n", (int)XTRA_ARRAYSIZE(newmap));
423         for (i = 0; i < XTRA_ARRAYSIZE(newmap); i++) {
424                 newmap[i] = template[i];
425                 newmap[i].pixel = 0;
426         }
427
428         ewmh_icon_prepare_cmap(newmap, XTRA_ARRAYSIZE(newmap));
429         for (i = 0; i < XTRA_ARRAYSIZE(newmap); i++) {
430                 XColor *c = &newmap[i];
431
432                 if (c->pixel != template[i].pixel) {
433                         printf("not ");
434                         ret = EXIT_FAILURE;
435                 }
436
437                 printf("ok %u (%.4hx, %.4hx, %.4hx) -> %.8lx\n",
438                        i+1, c->red, c->green, c->blue, c->pixel);
439         }
440
441         return ret;
442 }
443
444 int main(int argc, char **argv)
445 {
446         unsigned long *ewmhicon, *icon, size, buf[9];
447         char colourseq[10] = "000000000";
448         int w32vga = 0, test_cmap = 0;
449         int opt;
450
451         if (argc > 0)
452                 progname = argv[0];
453
454         while ((opt = getopt_long(argc, argv, sopts, lopts, 0)) != -1) {
455                 switch (opt) {
456                 case 'c':
457                         if (decode_colourseq(colourseq, optarg) != 0)
458                                 return EXIT_FAILURE;
459                         break;
460                 case LOPT_W32VGA:
461                         w32vga = 1;
462                         break;
463                 case LOPT_TEST_CMAP:
464                         test_cmap = 1;
465                         break;
466                 case 'H':
467                         print_help();
468                         return EXIT_SUCCESS;
469                 case 'V':
470                         print_version();
471                         return EXIT_SUCCESS;
472                 default:
473                         print_usage(stderr);
474                         return EXIT_FAILURE;
475                 }
476         }
477
478         if (test_cmap) {
479                 return test_ewmh_icon_prepare();
480         }
481
482         if (argc != optind+1) {
483                 printf("%s: error: size not specified\n", progname);
484                 print_usage(stderr);
485                 return EXIT_FAILURE;
486         }
487
488         size = decode_size(argv[optind]);
489         if (!size)
490                 return EXIT_FAILURE;
491
492         ewmhicon = ewmh_icon_generate(expand_seq(buf, colourseq), colours[0]);
493         if (!ewmhicon) {
494                 fprintf(stderr, "%s: failed to allocate memory\n", progname);
495                 return EXIT_FAILURE;
496         }
497
498         icon = find_icon(size, ewmhicon);
499         if (!icon)
500                 return EXIT_FAILURE;
501
502         if (w32vga) {
503                 print_xpm_w32vga(size, icon);
504         } else {
505                 print_xpm(colourseq, size, icon);
506         }
507
508         free(ewmhicon);
509         return 0;
510 }