257 lines
8.2 KiB
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 color, 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;
|
|
}
|