// 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 #include #include #include #include #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; }