Skip to content

Commit

Permalink
keysyms: Fix off-by-one XKB_KEYSYM_NAME_MAX_SIZE
Browse files Browse the repository at this point in the history
The constant did not account for the terminating `NULL` byte and this was
sadly not caught by the tests.

Fixed the invalid value, the corresponding script and the tests.
  • Loading branch information
wismill committed Sep 20, 2024
1 parent 05ba96d commit e426920
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 13 deletions.
1 change: 1 addition & 0 deletions changes/api/+fix-keysym-name-max-size.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `xkb_keymap_get_as_string` truncating the *4* longest keysyms names, such as `Greek_upsilonaccentdieresis`.
1 change: 1 addition & 0 deletions changes/tools/+fix-keysym-name-max-size.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed various tools truncating the *4* longest keysyms names, such as `Greek_upsilonaccentdieresis`.
29 changes: 21 additions & 8 deletions scripts/update-headers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env python3

import argparse
from collections import defaultdict
from pathlib import Path
import itertools
import re
import sys
from collections import defaultdict
from pathlib import Path
from typing import Any, TypeAlias

import jinja2
Expand All @@ -14,7 +15,7 @@
)
MAX_AMBIGUOUS_NAMES = 3

KeysymsBounds: TypeAlias = dict[str, int]
KeysymsBounds: TypeAlias = dict[str, int | str]
KeysymsCaseFoldedNames: TypeAlias = dict[str, list[str]]


Expand All @@ -40,6 +41,19 @@ def load_keysyms(path: Path) -> tuple[KeysymsBounds, KeysymsCaseFoldedNames]:
if value not in canonical_names:
canonical_names[value] = name

XKB_KEYSYM_LONGEST_CANONICAL_NAME = max(
max(canonical_names.values(), key=len),
max_unicode_name,
max_keysym_name,
key=len,
)
XKB_KEYSYM_LONGEST_NAME = max(
max(itertools.chain.from_iterable(casefolded_names.values()), key=len),
max_unicode_name,
max_keysym_name,
key=len,
)

# Keep only ambiguous case-insensitive names and sort them
for name in tuple(casefolded_names.keys()):
count = len(casefolded_names[name])
Expand All @@ -59,11 +73,10 @@ def load_keysyms(path: Path) -> tuple[KeysymsBounds, KeysymsCaseFoldedNames]:
"XKB_KEYSYM_MIN_EXPLICIT": keysym_min,
"XKB_KEYSYM_MAX_EXPLICIT": keysym_max,
"XKB_KEYSYM_COUNT_EXPLICIT": len(canonical_names),
"XKB_KEYSYM_NAME_MAX_SIZE": max(
max(len(name) for name in canonical_names.values()),
len(max_unicode_name),
len(max_keysym_name),
),
# Extra byte for terminating NULL
"XKB_KEYSYM_NAME_MAX_SIZE": len(XKB_KEYSYM_LONGEST_CANONICAL_NAME) + 1,
"XKB_KEYSYM_LONGEST_CANONICAL_NAME": XKB_KEYSYM_LONGEST_CANONICAL_NAME,
"XKB_KEYSYM_LONGEST_NAME": XKB_KEYSYM_LONGEST_NAME,
},
casefolded_names,
)
Expand Down
8 changes: 6 additions & 2 deletions src/keysym.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,12 @@
#define XKB_KEYSYM_UNICODE_MAX 0x0110ffff
/** Unicode version used for case mappings */
#define XKB_KEYSYM_UNICODE_VERSION { 15, 1, 0, 0 }
/** Maximum keysym name length */
#define XKB_KEYSYM_NAME_MAX_SIZE 27
/** Maximum keysym canonical name length, plus terminating NULL byte */
#define XKB_KEYSYM_NAME_MAX_SIZE 28
/** Longest keysym canonical name */
#define XKB_KEYSYM_LONGEST_CANONICAL_NAME ISO_Discontinuous_Underline
/** Longest keysym name */
#define XKB_KEYSYM_LONGEST_NAME ISO_Discontinuous_Underline
/** Maximum bytes to encode the Unicode representation of a keysym in UTF-8:
* 4 bytes + NULL-terminating byte */
#define XKB_KEYSYM_UTF8_MAX_SIZE 5
Expand Down
6 changes: 5 additions & 1 deletion src/keysym.h.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,12 @@
#define XKB_KEYSYM_UNICODE_MAX 0x0110ffff
/** Unicode version used for case mappings */
#define XKB_KEYSYM_UNICODE_VERSION { 15, 1, 0, 0 }
/** Maximum keysym name length */
/** Maximum keysym canonical name length, plus terminating NULL byte */
#define XKB_KEYSYM_NAME_MAX_SIZE {{ XKB_KEYSYM_NAME_MAX_SIZE }}
/** Longest keysym canonical name */
#define XKB_KEYSYM_LONGEST_CANONICAL_NAME {{ XKB_KEYSYM_LONGEST_CANONICAL_NAME }}
/** Longest keysym name */
#define XKB_KEYSYM_LONGEST_NAME {{ XKB_KEYSYM_LONGEST_NAME }}
/** Maximum bytes to encode the Unicode representation of a keysym in UTF-8:
* 4 bytes + NULL-terminating byte */
#define XKB_KEYSYM_UTF8_MAX_SIZE 5
Expand Down
13 changes: 11 additions & 2 deletions test/keysym.c
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,10 @@ main(void)
char utf8[7];
int needed = xkb_keysym_to_utf8(ks, utf8, sizeof(utf8));
assert(0 <= needed && needed <= XKB_KEYSYM_UTF8_MAX_SIZE);
/* Check maximum name length */
/* Check maximum name length (`needed` does not include the ending NULL) */
char name[XKB_KEYSYM_NAME_MAX_SIZE];
needed = xkb_keysym_iterator_get_name(iter, name, sizeof(name));
assert(0 < needed && (size_t)needed <= sizeof(name));
assert(0 < needed && (size_t)needed <= sizeof(name) - 1);
/* Test modifier keysyms */
bool expected = test_modifier(ks);
bool got = xkb_keysym_is_modifier(ks);
Expand Down Expand Up @@ -519,6 +519,12 @@ main(void)
assert(test_string("thorn", 0x00fe));
assert(test_string(" thorn", XKB_KEY_NoSymbol));
assert(test_string("thorn ", XKB_KEY_NoSymbol));
#define LONGEST_NAME STRINGIFY2(XKB_KEYSYM_LONGEST_NAME)
#define XKB_KEY_LONGEST_NAME CONCAT2(XKB_KEY_, XKB_KEYSYM_LONGEST_NAME)
assert(test_string(LONGEST_NAME, XKB_KEY_LONGEST_NAME));
#define LONGEST_CANONICAL_NAME STRINGIFY2(XKB_KEYSYM_LONGEST_CANONICAL_NAME)
#define XKB_KEY_LONGEST_CANONICAL_NAME CONCAT2(XKB_KEY_, XKB_KEYSYM_LONGEST_CANONICAL_NAME)
assert(test_string(LONGEST_CANONICAL_NAME, XKB_KEY_LONGEST_CANONICAL_NAME));

/* Decimal keysyms are not supported (digits are special cases) */
assert(test_string("-1", XKB_KEY_NoSymbol));
Expand Down Expand Up @@ -586,6 +592,9 @@ main(void)
assert(test_keysym(0x0, "NoSymbol"));
assert(test_keysym(0x1008FE20, "XF86Ungrab"));
assert(test_keysym(XKB_KEYSYM_UNICODE_OFFSET, "0x01000000"));
/* Longest names */
assert(test_keysym(XKB_KEY_LONGEST_NAME, LONGEST_NAME));
assert(test_keysym(XKB_KEY_LONGEST_CANONICAL_NAME, LONGEST_CANONICAL_NAME));
/* Canonical names */
assert(test_keysym(XKB_KEY_Henkan, "Henkan_Mode"));
assert(test_keysym(XKB_KEY_ISO_Group_Shift, "Mode_switch"));
Expand Down

0 comments on commit e426920

Please sign in to comment.