joeylib2/tests/gate/netWmIcon.c

257 lines
8.2 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// netWmIcon.c - an X window's icon against the picture it should be.
//
// netWmIcon XPROP WANT SIZE LABEL
//
// A window's icon on X is its _NET_WM_ICON property: one or more pictures,
// each its width, its height and then its pixels as 32-bit ARGB, row after
// row. SDL3 sets it from SDL_SetWindowIcon; Wine from the window class's
// icon. xprop prints it as numbers with
//
// xprop -id WINDOW -f _NET_WM_ICON 32c ' = $0+' _NET_WM_ICON > XPROP
//
// and this compares the picture of the wanted size in that against WANT (a
// PNG; an .ico's 32-bit picture of that size). A pixel at least half
// opaque must be so in both, and then of the same colour, give or take
// TOLERANCE a gun (a system may round its own way).
//
// The numbers are those after the first '=', separated by commas and white
// space, up to anything else. Prints LABEL: PASS or LABEL: FAIL, and exits
// 0 or 1 (2 on a bad command line).
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gateArg.h"
#include "gateImage.h"
#include "toolFile.h"
#define TOOL_NAME "netWmIcon"
#define FAIL_EXIT 1
#define TOLERANCE 2u
#define ALPHA_OPAQUE_MIN 128u
#define ICO_BPP 32u
#define ALPHA_SHIFT 24u
#define RED_SHIFT 16u
#define GREEN_SHIFT 8u
#define BYTE_MASK 0xFFu
#define DECIMAL 10
#define VALUES_INITIAL 1024u
// One picture in xprop's numbers: its size and where its pixels are among
// the numbers (fewer than width x height when the numbers run out).
typedef struct PictureT {
uint64_t width;
uint64_t height;
size_t first;
size_t count;
} PictureT;
static size_t parseValues(const char *text, uint64_t **values);
static uint32_t pictures(const uint64_t *values, size_t count, PictureT **out);
static void printSizes(const PictureT *found, uint32_t count);
static const char *const kUsage[] = {
"netWmIcon - an X window's icon against the picture it should be.",
"",
" xprop -id WINDOW -f _NET_WM_ICON 32c ' = $0+' _NET_WM_ICON > XPROP",
" netWmIcon XPROP WANT SIZE LABEL",
"",
"compares the window's SIZE x SIZE icon against WANT (a PNG; an .ico's",
"32-bit picture of that size). Prints LABEL: PASS or LABEL: FAIL, and exits",
"0 or 1."
};
// The numbers after the first '=' in xprop's text, separated by commas and
// white space, up to anything else.
static size_t parseValues(const char *text, uint64_t **values) {
const char *at;
char *end;
size_t count;
size_t cap;
uint64_t *grown;
*values = NULL;
count = 0u;
cap = 0u;
at = strchr(text, '=');
if (at == NULL) {
return 0u;
}
at++;
for (;;) {
while (*at == ',' || isspace((unsigned char)*at)) {
at++;
}
if (!isdigit((unsigned char)*at)) {
return count;
}
if (count == cap) {
cap = (cap == 0u) ? VALUES_INITIAL : cap * 2u;
grown = (uint64_t *)realloc(*values, cap * sizeof(uint64_t));
if (grown == NULL) {
toolFail("out of memory");
}
*values = grown;
}
(*values)[count++] = (uint64_t)strtoull(at, &end, DECIMAL);
at = end;
}
}
// Every picture in the numbers: width, height, then up to width x height
// pixels (all that are left when there are fewer).
static uint32_t pictures(const uint64_t *values, size_t count, PictureT **out) {
PictureT *list;
PictureT *grown;
uint32_t n;
uint32_t cap;
size_t at;
size_t avail;
list = NULL;
n = 0u;
cap = 0u;
at = 0u;
while (at + 2u <= count) {
if (n == cap) {
cap = (cap == 0u) ? VALUES_INITIAL : cap * 2u;
grown = (PictureT *)realloc(list, (size_t)cap * sizeof(PictureT));
if (grown == NULL) {
toolFail("out of memory");
}
list = grown;
}
list[n].width = values[at];
list[n].height = values[at + 1u];
at += 2u;
avail = count - at;
list[n].first = at;
if (list[n].width != 0u && list[n].height > avail / list[n].width) {
list[n].count = avail;
at = count;
} else {
list[n].count = (size_t)(list[n].width * list[n].height);
at += list[n].count;
}
n++;
}
*out = list;
return n;
}
// The pictures' sizes, "WxH, WxH", or "none".
static void printSizes(const PictureT *found, uint32_t count) {
uint32_t f;
for (f = 0; f < count; f++) {
printf("%s%" PRIu64 "x%" PRIu64, (f == 0u) ? "" : ", ", found[f].width, found[f].height);
}
if (count == 0u) {
printf("none");
}
}
int main(int argc, char **argv) {
GateImageT want;
PictureT *found;
const PictureT *shown;
uint64_t *values;
uint8_t *text;
const uint8_t *w;
const char *label;
size_t textLen;
size_t valueCount;
size_t compared;
size_t i;
uint32_t foundCount;
uint32_t f;
uint32_t g;
uint32_t wrong;
uint32_t opaqueCount;
uint64_t argb;
uint8_t rgb[GATE_RGB_BYTES];
long size;
bool shownOpaque;
bool close;
toolFileName(TOOL_NAME);
if (argc != 5) {
return gateUsage(kUsage, GATE_COUNT_OF(kUsage));
}
size = gateArgLong("size", argv[3]);
if (size < 1) {
gateArgBad("size", argv[3]);
}
label = argv[4];
if (!toolReadFile(argv[1], &text, &textLen)) {
toolFail("cannot read %s", argv[1]);
}
valueCount = parseValues((const char *)text, &values);
foundCount = pictures(values, valueCount, &found);
shown = NULL;
for (f = 0; f < foundCount && shown == NULL; f++) {
if (found[f].width == (uint64_t)size && found[f].height == (uint64_t)size) {
shown = &found[f];
}
}
if (shown == NULL) {
printf("%s: FAIL - the window has no %ldx%ld icon (it has: ", label, size, size);
printSizes(found, foundCount);
printf(")\n");
return FAIL_EXIT;
}
gateImageLoadIcon(argv[2], (uint32_t)size, ICO_BPP, &want);
if ((long)want.width != size || (long)want.height != size) {
printf("%s: FAIL - %s is %ux%u, not %ldx%ld\n", label, argv[2], (unsigned)want.width, (unsigned)want.height, size, size);
return FAIL_EXIT;
}
wrong = 0u;
opaqueCount = 0u;
compared = (shown->count < (size_t)size * (size_t)size) ? shown->count : (size_t)size * (size_t)size;
for (i = 0; i < compared; i++) {
argb = values[shown->first + i];
w = want.rgba + i * GATE_RGBA_BYTES;
shownOpaque = (argb >> ALPHA_SHIFT) >= ALPHA_OPAQUE_MIN;
if (shownOpaque != (w[GATE_ALPHA] >= ALPHA_OPAQUE_MIN)) {
wrong++;
continue;
}
if (!shownOpaque) {
continue;
}
opaqueCount++;
rgb[0] = (uint8_t)((argb >> RED_SHIFT) & BYTE_MASK);
rgb[1] = (uint8_t)((argb >> GREEN_SHIFT) & BYTE_MASK);
rgb[2] = (uint8_t)(argb & BYTE_MASK);
close = true;
for (g = 0; g < GATE_RGB_BYTES; g++) {
close = close && (uint32_t)abs((int32_t)rgb[g] - (int32_t)w[g]) <= TOLERANCE;
}
if (!close) {
wrong++;
}
}
if (wrong != 0u || opaqueCount == 0u) {
printf("%s: FAIL - %u of the %ldx%ld icon's pixels differ from %s (%u opaque)\n", label, (unsigned)wrong, size, size, argv[2], (unsigned)opaqueCount);
return FAIL_EXIT;
}
printf("%s: PASS (the window's %ldx%ld icon is the baked one: %u opaque pixels and the transparency round them; icons on the window: ", label, size, size, (unsigned)opaqueCount);
printSizes(found, foundCount);
printf(")\n");
gateImageFree(&want);
free(found);
free(values);
free(text);
return 0;
}