]> git.draconx.ca Git - rrace.git/blob - t/ewmhicon.c
Make EWMH icon generation more abstract.
[rrace.git] / t / ewmhicon.c
1 /*
2  * Test app for _NET_WM_ICON formatting.
3  * Copyright © 2022 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
36 static const char *progname = "ewmhicon";
37 static const char sopts[] = "c:VH";
38 static const struct option lopts[] = {
39         { "colourseq", 1, NULL, 'c' },
40         { "version",   0, NULL, 'V' },
41         { "help",      0, NULL, 'H' },
42         { 0 }
43 };
44
45 #define PASTE(a, b) a ## b
46 #define INDIR(a) a
47
48 #define S8TO16(x) ((x) * 0xfffful / 0xff)
49
50 #define RGB8_(r, g, b) { \
51         0xff000000 | (r << 16) | (g << 8) | b, \
52         S8TO16(r), S8TO16(g), S8TO16(b) }
53
54 #define RGB8(r, g, b) RGB8_(0x ## r ## ul, 0x ## g ## ul, 0x ## b ## ul)
55
56 #define COLOURTAB(n) { \
57         INDIR(RGB8 PASTE(COLOUR ## n, _PRIMARY_RGB_)), \
58         INDIR(RGB8 PASTE(COLOUR ## n, _DARK_RGB_)), \
59         INDIR(RGB8 PASTE(COLOUR ## n, _LIGHT_RGB_)) }
60
61 static const XColor colours[7][3] = {
62         {
63                 { 0xffff0000, 0xffff },
64                 { 0xff00ff00, 0, 0xffff },
65                 { 0xff0000ff, 0, 0, 0xffff }
66         },
67         COLOURTAB(0), COLOURTAB(1), COLOURTAB(2),
68         COLOURTAB(3), COLOURTAB(4), COLOURTAB(5)
69 };
70
71 static void print_usage(FILE *f)
72 {
73         fprintf(f, "Usage: %s size\n", progname);
74         if (f != stdout)
75                 fprintf(f, "Try %s --help for more information.\n", progname);
76 }
77
78 static void print_help(void)
79 {
80         const struct option *opt;
81
82         print_usage(stdout);
83         putchar('\n');
84         puts("Options:");
85         for (opt = lopts; opt->name; opt++) {
86                 if (help_print_optstring(opt, "ARG", 20))
87                         putchar('\n');
88         }
89         putchar('\n');
90
91         printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
92 }
93
94 static void print_version(void)
95 {
96         printf("ewmhicon (%s) %s\n", PACKAGE, PACKAGE_VERSION);
97         printf("Copyright (C) 2022 Nick Bowler\n");
98         puts("License GPLv3+: GNU GPL version 3 or any later version");
99         puts("This is free software: you are free to change and redistribute it.");
100         puts("There is NO WARRANTY, to the extent permitted by law.");
101 }
102
103 static void
104 print_xpm(const char *colourseq, unsigned long size, unsigned long *icon_buf)
105 {
106         const char colourchars[21] = ".%+"",Rr""-Oo""'Yy"":Gg"";Bb"" Ww";
107         unsigned w = size & 0xffff, h = (size >> 16) & 0xffff;
108         unsigned x, y, i, j, n;
109
110         n = 7;
111         for (i = 0; i < 7; i++)
112                 n -= !strchr(colourseq, '0'+i);
113
114         puts("/* XPM */");
115         puts("static char *icon[] = {");
116         printf("\"%u %u %u 1\",\n", w, h, 3*n);
117         for (i = 0; i < 7; i++) {
118                 if (!strchr(colourseq, '0'+i))
119                         continue;
120
121                 for (j = 0; j < 3; j++) {
122                         printf("\"%c c #%.6lx\",\n", colourchars[3*i+j],
123                                        colours[i][j].pixel & 0xffffff);
124                 }
125         }
126
127         for (y = 0; y < h; y++) {
128                 putchar('"');
129                 for (x = 0; x < w; x++) {
130                         unsigned long val = icon_buf[y*h+x];
131                         int c = '#';
132
133                         for (i = 0; i < sizeof colourchars; i++) {
134                                 if (colours[i/3][i%3].pixel == val) {
135                                         c = colourchars[i];
136                                         break;
137                                 }
138                         }
139
140                         putchar(c);
141                 }
142                 printf("\"%.*s\n", y+1<h, ",");
143         }
144         printf("};\n");
145 }
146
147 static int decode_colourseq(char *out, const char *arg)
148 {
149         int i;
150
151         for (i = 0; i < 9; i++) {
152                 switch (arg[i]) {
153                 case 0: return 0;
154                 case 'R': case 'r': out[i] = '1'; break;
155                 case 'O': case 'o': out[i] = '2'; break;
156                 case 'Y': case 'y': out[i] = '3'; break;
157                 case 'G': case 'g': out[i] = '4'; break;
158                 case 'B': case 'b': out[i] = '5'; break;
159                 case 'W': case 'w': out[i] = '6'; break;
160                 case '1': case '2': case '3': case '4': case '5': case '6':
161                         case '0': out[i] = arg[i]; break;
162                 default: goto err;
163                 }
164         }
165
166         if (arg[i]) {
167 err:
168                 fprintf(stderr, "%s: error: invalid colour sequence '%s'\n",
169                                 progname, arg);
170                 return -1;
171         }
172
173         return 0;
174 }
175
176 /* Convert the user sequence into a list of colour index specifiers */
177 static unsigned long *expand_seq(unsigned long *out, const char *seq)
178 {
179         int i;
180
181         for (i = 0; i < 9; i++) {
182                 out[i] = 0x30303 * (seq[i]-'0') + 0x20100;
183         }
184
185         return out;
186 }
187
188 static unsigned long decode_size(char *size_spec)
189 {
190         unsigned long w, h;
191         char c = 0;
192         size_t n;
193
194         n = strspn(size_spec, "0123456789");
195         switch (size_spec[n]) {
196         default:
197                 goto err_invalid;
198         case 0: case 'x': case 'X':
199                 c = size_spec[n];
200                 size_spec[n] = 0;
201         }
202
203         w = strtoul(size_spec, NULL, 10);
204         size_spec[n] = c;
205         if (w > 0xffff)
206                 goto err_range;
207
208         if (!c) {
209                 h = w;
210         } else {
211                 n = strspn(size_spec += n+1, "0123456789");
212                 if (size_spec[n])
213                         goto err_invalid;
214
215                 h = strtoul(size_spec, NULL, 10);
216                 if (h > 0xffff)
217                         goto err_range;
218         }
219
220         return (h << 16) | w;
221 err_range:
222         fprintf(stderr, "%s: %s: %s\n", progname, size_spec, strerror(ERANGE));
223         return 0;
224 err_invalid:
225         fprintf(stderr, "%s: %s: %s\n", progname, size_spec,
226                                         "invalid size specification");
227         return 0;
228 }
229
230 static unsigned long *find_icon(unsigned long size, unsigned long *ewmhicon)
231 {
232         unsigned long w = size & 0xffff, h = (size >> 16) & 0xffff;
233         unsigned long i;
234
235         if (!size)
236                 return NULL;
237
238         for (i = 0; i < EWMH_ICON_NELEM-2;) {
239                 unsigned long icon_w = ewmhicon[i];
240                 unsigned long icon_h = ewmhicon[i+1];
241
242                 if (w == icon_w && h == icon_h) {
243                         i += 2;
244                         break;
245                 }
246
247                 assert(icon_w < ULONG_MAX / icon_h);
248                 assert(i < ULONG_MAX - icon_w*icon_h);
249                 i += 2 + icon_w*icon_h;
250         }
251
252         if (i < EWMH_ICON_NELEM)
253                 return &ewmhicon[i];
254
255         fprintf(stderr, "%s: error: no %lux%lu icon found\n", progname, w, h);
256         return NULL;
257 }
258
259 int main(int argc, char **argv)
260 {
261         unsigned long *ewmhicon, *icon, size, buf[9];
262         char colourseq[10] = "000000000";
263         int opt;
264
265         if (argc > 0)
266                 progname = argv[0];
267
268         while ((opt = getopt_long(argc, argv, sopts, lopts, 0)) != -1) {
269                 switch (opt) {
270                 case 'c':
271                         if (decode_colourseq(colourseq, optarg) != 0)
272                                 return EXIT_FAILURE;
273                         break;
274                 case 'H':
275                         print_help();
276                         return EXIT_SUCCESS;
277                 case 'V':
278                         print_version();
279                         return EXIT_SUCCESS;
280                 default:
281                         print_usage(stderr);
282                         return EXIT_FAILURE;
283                 }
284         }
285
286         if (argc != optind+1) {
287                 printf("%s: error: size not specified\n", progname);
288                 print_usage(stderr);
289                 return EXIT_FAILURE;
290         }
291
292         size = decode_size(argv[optind]);
293         if (!size)
294                 return EXIT_FAILURE;
295
296         ewmhicon = ewmh_icon_generate(expand_seq(buf, colourseq), colours[0]);
297         if (!ewmhicon) {
298                 fprintf(stderr, "%s: failed to allocate memory\n", progname);
299                 return EXIT_FAILURE;
300         }
301
302         icon = find_icon(size, ewmhicon);
303         if (!icon)
304                 return EXIT_FAILURE;
305
306         print_xpm(colourseq, size, icon);
307
308         free(ewmhicon);
309         return 0;
310 }