/*
* Test app for _NET_WM_ICON formatting.
* Copyright © 2022-2024 Nick Bowler
*
* Use a fake colour scheme to generate an icon of the chosen size (16x16,
* 24x24, 32x32 or 48x48) and display the pixels as characters.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "ewmhicon.h"
#include "icon.h"
#include "colour.h"
#include "help.h"
#include "xtra.h"
enum {
SOPT_MAX = UCHAR_MAX,
LOPT_W32VGA,
LOPT_TEST_CMAP,
LOPT_MAX
};
static const char *progname = "ewmhicon";
static const char sopts[] = "c:VH";
static const struct option lopts[] = {
{ "test-cmap", 0, NULL, LOPT_TEST_CMAP },
{ "colourseq", 1, NULL, 'c' },
{ "w32vga", 0, NULL, LOPT_W32VGA },
{ "version", 0, NULL, 'V' },
{ "help", 0, NULL, 'H' },
{ 0 }
};
#define S8TO16(x) ((x) * 0xfffful / 0xff)
#define RGB8(r, g, b) { \
0xff000000 | (r << 16) | (g << 8) | b, \
S8TO16(r), S8TO16(g), S8TO16(b) }
#define COLOUR_SYS_XCOLOR(r, g, b) \
RGB8(0x ## r ## ul, 0x ## g ## ul, 0x ## b ## ul)
#undef COLOUR_SYSTEM
#define COLOUR_SYSTEM XCOLOR
#define COLOURTAB_(n) { n ## _PRIMARY, n ## _DARK, n ## _LIGHT }
#define COLOURTAB(n) COLOURTAB_(COLOUR ## n)
static const XColor colours[7][3] = {
{
{ 0xffff0000, 0xffff },
{ 0xff00ff00, 0, 0xffff },
{ 0xff0000ff, 0, 0, 0xffff }
},
COLOURTAB(0), COLOURTAB(1), COLOURTAB(2),
COLOURTAB(3), COLOURTAB(4), COLOURTAB(5)
};
static void print_usage(FILE *f)
{
fprintf(f, "Usage: %s size\n", progname);
if (f != stdout)
fprintf(f, "Try %s --help for more information.\n", progname);
}
static void print_help(void)
{
const struct option *opt;
print_usage(stdout);
putchar('\n');
puts("Options:");
for (opt = lopts; opt->name; opt++) {
if (help_print_optstring(opt, "ARG", 20))
putchar('\n');
}
putchar('\n');
printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
}
static void print_version(void)
{
printf("ewmhicon (%s) %s\n", PACKAGE, PACKAGE_VERSION);
printf("Copyright (C) 2023 Nick Bowler\n");
puts("License GPLv3+: GNU GPL version 3 or any later version");
puts("This is free software: you are free to change and redistribute it.");
puts("There is NO WARRANTY, to the extent permitted by law.");
}
#define COLOUR_SYS_HEX(r, g, b) 0x ## r ## g ## b ## ul
#undef COLOUR_SYSTEM
#define COLOUR_SYSTEM HEX
/*
* Returns true iff this pixel is an "edge" -- that is, either there
* is no pixel to the right or below or those pixels are a different
* colour.
*/
static int is_edge(unsigned x, unsigned y, unsigned w, unsigned h,
unsigned long *p, unsigned long colour)
{
return y+1 >= h || x+1 >= w
|| (p[1] & 0xffffff) != colour
|| (p[w] & 0xffffff) != colour;
}
static void print_xpm_w32vga(unsigned long size, unsigned long *icon_buf)
{
unsigned w = size & 0xffff, h = (size >> 16) & 0xffff;
unsigned x, y, i, j, n;
const char colourchars[16] = "#rgybmc-+RGYBMC ";
unsigned long palette[16] = {
0x000000, 0x800000, 0x008000, 0x808000,
0x000080, 0x800080, 0x008080, 0xc0c0c0,
0x808080, 0xff0000, 0x00ff00, 0xffff00,
0x0000ff, 0xff00ff, 0x00ffff, 0xffffff
};
unsigned long prev_colour = 0;
unsigned used_colours = 0, v;
size = (unsigned long) w * h;
for (i = 0; i < size; i++) {
unsigned long *p = &icon_buf[i];
char *c;
x = i%w, y=i/w;
switch (*p & 0xffffff) {
/* red */
case COLOUR0_PRIMARY:
*p = y&1 ? ( x&1 ? 'r' : 'm' ) : ( x&1 ? 'y' : 'r' );
break;
case COLOUR0_LIGHT:
*p = (x^y)&1 ? '+' : 'r';
break;
case COLOUR0_DARK:
*p = 'r';
break;
/* orange */
case COLOUR1_PRIMARY:
*p = y&1 ? 'y' : x&1 ? '+' : 'R';
break;
case COLOUR1_LIGHT:
*p = (x^y)&1 ? '+' : 'y';
break;
case COLOUR1_DARK:
*p = (x^y)&1 ? 'r' : 'y';
break;
/* yellow */
case COLOUR2_PRIMARY:
*p = (x^y)&1 ? '+' : 'Y';
break;
case COLOUR2_LIGHT:
*p = (x^y)&1 ? '-' : 'Y';
break;
case COLOUR2_DARK:
*p = (x^y)&1 ? '+' : 'Y';
if (is_edge(x, y, w, h, p, COLOUR2_DARK))
*p = (x^y)&1 ? '+' : 'y';
break;
/* green */
case COLOUR3_PRIMARY:
*p = y&1 ? 'g' : x&1 ? '+' : 'g';
break;
case COLOUR3_LIGHT:
*p = (x^y)&1 ? '+' : 'g';
break;
case COLOUR3_DARK:
*p = 'g';
if (is_edge(x, y, w, h, p, COLOUR3_DARK))
*p = (x^y)&1 ? '#' : 'g';
break;
/* blue */
case COLOUR4_PRIMARY:
*p = (x^y)&1 ? 'b' : 'c';
break;
case COLOUR4_LIGHT:
*p = 'c';
if (is_edge(x, y, w, h, p, COLOUR4_LIGHT))
*p = (x^y)&1 ? 'b' : 'c';
break;
case COLOUR4_DARK:
*p = 'b';
if (is_edge(x, y, w, h, p, COLOUR4_DARK))
*p = (x^y)&1 ? '#' : 'b';
break;
/* white */
case COLOUR5_PRIMARY:
*p = (x^y)&1 ? '-' : ' ';
break;
case COLOUR5_LIGHT:
*p = ' ';
break;
case COLOUR5_DARK:
*p = (x^y)&1 ? '+' : ' ';
break;
/* dummy */
case 0xff0000: *p = 'R'; break;
case 0x00ff00: *p = 'G'; break;
case 0x0000ff: *p = 'B'; break;
}
c = strchr(colourchars, *p);
assert(c && c - colourchars < 16);
used_colours |= 1u << (c-colourchars);
}
for (n = 0, v = used_colours; v; n++)
v &= v-1;
puts("/* XPM */");
puts("static char *icon[] = {");
printf("\"%u %u %u 1\",\n", w, h, n);
for (i = 0; i < sizeof colourchars; i++) {
if (!(used_colours & (1u << i)))
continue;
printf("\"%c c #%.6lx\",\n", colourchars[i], palette[i]);
}
for (y = 0; y < h; y++) {
putchar('"');
for (x = 0; x < w; x++) {
putchar(icon_buf[y*h+x]);
}
printf("\"%.*s\n", y+1> 16) & 0xffff;
unsigned x, y, i, j, n;
n = 7;
for (i = 0; i < 7; i++)
n -= !strchr(colourseq, '0'+i);
puts("/* XPM */");
puts("static char *icon[] = {");
printf("\"%u %u %u 1\",\n", w, h, 3*n);
for (i = 0; i < 7; i++) {
if (!strchr(colourseq, '0'+i))
continue;
for (j = 0; j < 3; j++) {
printf("\"%c c #%.6lx\",\n", colourchars[3*i+j],
colours[i][j].pixel & 0xffffff);
}
}
for (y = 0; y < h; y++) {
putchar('"');
for (x = 0; x < w; x++) {
unsigned long val = icon_buf[y*h+x];
int c = '#';
for (i = 0; i < sizeof colourchars; i++) {
if (colours[i/3][i%3].pixel == val) {
c = colourchars[i];
break;
}
}
putchar(c);
}
printf("\"%.*s\n", y+1 0xffff)
goto err_range;
if (!c) {
h = w;
} else {
n = strspn(size_spec += n+1, "0123456789");
if (size_spec[n])
goto err_invalid;
h = strtoul(size_spec, NULL, 10);
if (h > 0xffff)
goto err_range;
}
return (h << 16) | w;
err_range:
fprintf(stderr, "%s: %s: %s\n", progname, size_spec, strerror(ERANGE));
return 0;
err_invalid:
fprintf(stderr, "%s: %s: %s\n", progname, size_spec,
"invalid size specification");
return 0;
}
static unsigned long *find_icon(unsigned long size, unsigned long *ewmhicon)
{
unsigned long w = size & 0xffff, h = (size >> 16) & 0xffff;
unsigned long i;
if (!size)
return NULL;
for (i = 0; i < EWMH_ICON_NELEM-2;) {
unsigned long icon_w = ewmhicon[i];
unsigned long icon_h = ewmhicon[i+1];
if (w == icon_w && h == icon_h) {
i += 2;
break;
}
assert(icon_w < ULONG_MAX / icon_h);
assert(i < ULONG_MAX - icon_w*icon_h);
i += 2 + icon_w*icon_h;
}
if (i < EWMH_ICON_NELEM)
return &ewmhicon[i];
fprintf(stderr, "%s: error: no %lux%lu icon found\n", progname, w, h);
return NULL;
}
static int test_ewmh_icon_prepare(void)
{
int ret = EXIT_SUCCESS;
unsigned i;
XColor *template = (void *)colours;
XColor newmap[sizeof colours / sizeof colours[0][0]];
printf("1..%d\n", (int)XTRA_ARRAYSIZE(newmap));
for (i = 0; i < XTRA_ARRAYSIZE(newmap); i++) {
newmap[i] = template[i];
newmap[i].pixel = 0;
}
ewmh_icon_prepare_cmap(newmap, XTRA_ARRAYSIZE(newmap));
for (i = 0; i < XTRA_ARRAYSIZE(newmap); i++) {
XColor *c = &newmap[i];
if (c->pixel != template[i].pixel) {
printf("not ");
ret = EXIT_FAILURE;
}
printf("ok %u (%.4hx, %.4hx, %.4hx) -> %.8lx\n",
i+1, c->red, c->green, c->blue, c->pixel);
}
return ret;
}
int main(int argc, char **argv)
{
unsigned long *ewmhicon, *icon, size, buf[9];
char colourseq[10] = "000000000";
int w32vga = 0, test_cmap = 0;
int opt;
if (argc > 0)
progname = argv[0];
while ((opt = getopt_long(argc, argv, sopts, lopts, 0)) != -1) {
switch (opt) {
case 'c':
if (decode_colourseq(colourseq, optarg) != 0)
return EXIT_FAILURE;
break;
case LOPT_W32VGA:
w32vga = 1;
break;
case LOPT_TEST_CMAP:
test_cmap = 1;
break;
case 'H':
print_help();
return EXIT_SUCCESS;
case 'V':
print_version();
return EXIT_SUCCESS;
default:
print_usage(stderr);
return EXIT_FAILURE;
}
}
if (test_cmap) {
return test_ewmh_icon_prepare();
}
if (argc != optind+1) {
printf("%s: error: size not specified\n", progname);
print_usage(stderr);
return EXIT_FAILURE;
}
size = decode_size(argv[optind]);
if (!size)
return EXIT_FAILURE;
ewmhicon = ewmh_icon_generate(expand_seq(buf, colourseq), colours[0]);
if (!ewmhicon) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
return EXIT_FAILURE;
}
icon = find_icon(size, ewmhicon);
if (!icon)
return EXIT_FAILURE;
if (w32vga) {
print_xpm_w32vga(size, icon);
} else {
print_xpm(colourseq, size, icon);
}
free(ewmhicon);
return 0;
}