Add xkb_keysym_from_name() flags argument for case-insensitive search
This adds a flags argument to xkb_keysym_from_name() so we can perform a case-insensitive search. This should really be supported as many keysyms have really weird capitalization-rules. However, as this may produce conflicts, users must be warned to only use this for fallback paths or error-recovery. This is also the reason why the internal XKB parsers still use the case-sensitive search. This also adds some test-cases so the expected results are really produced. The binary-size does _not_ change with this patch. However, case-sensitive search may be slightly slower with this patch. But this is barely measurable. [ran: use bool instead of int for icase, add a recommendation to the doc, and test a couple "thorny" cases.] Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>master
parent
5fff637e07
commit
7b3bd11f92
|
@ -12,7 +12,7 @@ print('''struct name_keysym {
|
|||
};\n''')
|
||||
|
||||
print('static const struct name_keysym name_to_keysym[] = {');
|
||||
for (name, _) in sorted(entries, key=lambda e: e[0]):
|
||||
for (name, _) in sorted(entries, key=lambda e: e[0].lower()):
|
||||
print(' {{ "{name}", XKB_KEY_{name} }},'.format(name=name))
|
||||
print('};\n')
|
||||
|
||||
|
|
71
src/keysym.c
71
src/keysym.c
|
@ -64,7 +64,7 @@ static int
|
|||
compare_by_name(const void *a, const void *b)
|
||||
{
|
||||
const struct name_keysym *key = a, *entry = b;
|
||||
return strcmp(key->name, entry->name);
|
||||
return strcasecmp(key->name, entry->name);
|
||||
}
|
||||
|
||||
XKB_EXPORT int
|
||||
|
@ -93,22 +93,80 @@ xkb_keysym_get_name(xkb_keysym_t ks, char *buffer, size_t size)
|
|||
return snprintf(buffer, size, "0x%08x", ks);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the correct keysym if one case-insensitive match is given.
|
||||
*
|
||||
* The name_to_keysym table is sorted by strcasecmp(). So bsearch() may return
|
||||
* _any_ of all possible case-insensitive duplicates. This function searches the
|
||||
* returned entry @entry, all previous and all next entries that match by
|
||||
* case-insensitive comparison and returns the exact match to @name. If @icase
|
||||
* is true, then this returns the best case-insensitive match instead of a
|
||||
* correct match.
|
||||
* The "best" case-insensitive match is the lower-case keysym which we find with
|
||||
* the help of xkb_keysym_is_lower().
|
||||
* The only keysyms that only differ by letter-case are keysyms that are
|
||||
* available as lower-case and upper-case variant (like KEY_a and KEY_A). So
|
||||
* returning the first lower-case match is enough in this case.
|
||||
*/
|
||||
static const struct name_keysym *
|
||||
find_sym(const struct name_keysym *entry, const char *name, bool icase)
|
||||
{
|
||||
const struct name_keysym *iter, *last;
|
||||
size_t len = sizeof(name_to_keysym) / sizeof(*name_to_keysym);
|
||||
|
||||
if (!entry)
|
||||
return NULL;
|
||||
|
||||
if (!icase && strcmp(entry->name, name) == 0)
|
||||
return entry;
|
||||
if (icase && xkb_keysym_is_lower(entry->keysym))
|
||||
return entry;
|
||||
|
||||
for (iter = entry - 1; iter >= name_to_keysym; --iter) {
|
||||
if (!icase && strcmp(iter->name, name) == 0)
|
||||
return iter;
|
||||
if (strcasecmp(iter->name, entry->name) != 0)
|
||||
break;
|
||||
if (icase && xkb_keysym_is_lower(iter->keysym))
|
||||
return iter;
|
||||
}
|
||||
|
||||
last = name_to_keysym + len;
|
||||
for (iter = entry + 1; iter < last; --iter) {
|
||||
if (!icase && strcmp(iter->name, name) == 0)
|
||||
return iter;
|
||||
if (strcasecmp(iter->name, entry->name) != 0)
|
||||
break;
|
||||
if (icase && xkb_keysym_is_lower(iter->keysym))
|
||||
return iter;
|
||||
}
|
||||
|
||||
if (icase)
|
||||
return entry;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
XKB_EXPORT xkb_keysym_t
|
||||
xkb_keysym_from_name(const char *s)
|
||||
xkb_keysym_from_name(const char *s, enum xkb_keysym_flags flags)
|
||||
{
|
||||
const struct name_keysym search = { .name = s, .keysym = 0 };
|
||||
const struct name_keysym *entry;
|
||||
char *tmp;
|
||||
xkb_keysym_t val;
|
||||
bool icase = !!(flags & XKB_KEYSYM_CASE_INSENSITIVE);
|
||||
|
||||
if (flags & ~XKB_KEYSYM_CASE_INSENSITIVE)
|
||||
return XKB_KEY_NoSymbol;
|
||||
|
||||
entry = bsearch(&search, name_to_keysym,
|
||||
sizeof(name_to_keysym) / sizeof(*name_to_keysym),
|
||||
sizeof(*name_to_keysym),
|
||||
compare_by_name);
|
||||
entry = find_sym(entry, s, icase);
|
||||
if (entry)
|
||||
return entry->keysym;
|
||||
|
||||
if (*s == 'U') {
|
||||
if (*s == 'U' || (icase && *s == 'u')) {
|
||||
val = strtoul(&s[1], &tmp, 16);
|
||||
if (tmp && *tmp != '\0')
|
||||
return XKB_KEY_NoSymbol;
|
||||
|
@ -121,7 +179,7 @@ xkb_keysym_from_name(const char *s)
|
|||
return XKB_KEY_NoSymbol;
|
||||
return val | 0x01000000;
|
||||
}
|
||||
else if (s[0] == '0' && s[1] == 'x') {
|
||||
else if (s[0] == '0' && (s[1] == 'x' || (icase && s[1] == 'X'))) {
|
||||
val = strtoul(&s[2], &tmp, 16);
|
||||
if (tmp && *tmp != '\0')
|
||||
return XKB_KEY_NoSymbol;
|
||||
|
@ -132,13 +190,14 @@ xkb_keysym_from_name(const char *s)
|
|||
/* Stupid inconsistency between the headers and XKeysymDB: the former has
|
||||
* no separating underscore, while some XF86* syms in the latter did.
|
||||
* As a last ditch effort, try without. */
|
||||
if (strncmp(s, "XF86_", 5) == 0) {
|
||||
if (strncmp(s, "XF86_", 5) == 0 ||
|
||||
(icase && strncasecmp(s, "XF86_", 5) == 0)) {
|
||||
xkb_keysym_t ret;
|
||||
tmp = strdup(s);
|
||||
if (!tmp)
|
||||
return XKB_KEY_NoSymbol;
|
||||
memmove(&tmp[4], &tmp[5], strlen(s) - 5 + 1);
|
||||
ret = xkb_keysym_from_name(tmp);
|
||||
ret = xkb_keysym_from_name(tmp, flags);
|
||||
free(tmp);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -641,7 +641,7 @@ ExprResolveKeySym(struct xkb_context *ctx, const ExprDef *expr,
|
|||
if (expr->op == EXPR_IDENT) {
|
||||
const char *str;
|
||||
str = xkb_atom_text(ctx, expr->value.str);
|
||||
*sym_rtrn = xkb_keysym_from_name(str);
|
||||
*sym_rtrn = xkb_keysym_from_name(str, 0);
|
||||
if (*sym_rtrn != XKB_KEY_NoSymbol)
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -652,7 +652,7 @@ LookupKeysym(const char *str, xkb_keysym_t *sym_rtrn)
|
|||
return 1;
|
||||
}
|
||||
|
||||
sym = xkb_keysym_from_name(str);
|
||||
sym = xkb_keysym_from_name(str, 0);
|
||||
if (sym != XKB_KEY_NoSymbol) {
|
||||
*sym_rtrn = sym;
|
||||
return 1;
|
||||
|
|
|
@ -305,7 +305,7 @@ main(void)
|
|||
KEY_RIGHTSHIFT, UP, XKB_KEY_Shift_R, NEXT,
|
||||
KEY_V, BOTH, XKB_KEY_Cyrillic_ZHE, FINISH));
|
||||
|
||||
#define KS(name) xkb_keysym_from_name(name)
|
||||
#define KS(name) xkb_keysym_from_name(name, 0)
|
||||
|
||||
/* Test that levels (1-5) in de(neo) symbols map work. */
|
||||
assert(test_key_seq(keymap,
|
||||
|
|
|
@ -29,7 +29,7 @@ test_string(const char *string, xkb_keysym_t expected)
|
|||
{
|
||||
xkb_keysym_t keysym;
|
||||
|
||||
keysym = xkb_keysym_from_name(string);
|
||||
keysym = xkb_keysym_from_name(string, 0);
|
||||
|
||||
fprintf(stderr, "Expected string %s -> %x\n", string, expected);
|
||||
fprintf(stderr, "Received string %s -> %x\n\n", string, keysym);
|
||||
|
@ -37,6 +37,19 @@ test_string(const char *string, xkb_keysym_t expected)
|
|||
return keysym == expected;
|
||||
}
|
||||
|
||||
static int
|
||||
test_casestring(const char *string, xkb_keysym_t expected)
|
||||
{
|
||||
xkb_keysym_t keysym;
|
||||
|
||||
keysym = xkb_keysym_from_name(string, XKB_KEYSYM_CASE_INSENSITIVE);
|
||||
|
||||
fprintf(stderr, "Expected casestring %s -> %x\n", string, expected);
|
||||
fprintf(stderr, "Received casestring %s -> %x\n\n", string, keysym);
|
||||
|
||||
return keysym == expected;
|
||||
}
|
||||
|
||||
static int
|
||||
test_keysym(xkb_keysym_t keysym, const char *expected)
|
||||
{
|
||||
|
@ -75,12 +88,39 @@ main(void)
|
|||
assert(test_string("VoidSymbol", 0xFFFFFF));
|
||||
assert(test_string("U4567", 0x1004567));
|
||||
assert(test_string("0x10203040", 0x10203040));
|
||||
assert(test_string("a", 0x61));
|
||||
assert(test_string("A", 0x41));
|
||||
assert(test_string("ch", 0xfea0));
|
||||
assert(test_string("Ch", 0xfea1));
|
||||
assert(test_string("CH", 0xfea2));
|
||||
assert(test_string("THORN", 0x00de));
|
||||
assert(test_string("Thorn", 0x00de));
|
||||
assert(test_string("thorn", 0x00fe));
|
||||
|
||||
assert(test_keysym(0x1008FF56, "XF86Close"));
|
||||
assert(test_keysym(0x0, "NoSymbol"));
|
||||
assert(test_keysym(0x1008FE20, "XF86Ungrab"));
|
||||
assert(test_keysym(0x01001234, "U1234"));
|
||||
|
||||
assert(test_casestring("Undo", 0xFF65));
|
||||
assert(test_casestring("UNDO", 0xFF65));
|
||||
assert(test_casestring("A", 0x61));
|
||||
assert(test_casestring("a", 0x61));
|
||||
assert(test_casestring("ThisKeyShouldNotExist", XKB_KEY_NoSymbol));
|
||||
assert(test_casestring("XF86_Switch_vT_5", 0x1008FE05));
|
||||
assert(test_casestring("xF86_SwitcH_VT_5", 0x1008FE05));
|
||||
assert(test_casestring("xF86SwiTch_VT_5", 0x1008FE05));
|
||||
assert(test_casestring("xF86Switch_vt_5", 0x1008FE05));
|
||||
assert(test_casestring("VoidSymbol", 0xFFFFFF));
|
||||
assert(test_casestring("vOIDsymBol", 0xFFFFFF));
|
||||
assert(test_casestring("U4567", 0x1004567));
|
||||
assert(test_casestring("u4567", 0x1004567));
|
||||
assert(test_casestring("0x10203040", 0x10203040));
|
||||
assert(test_casestring("0X10203040", 0x10203040));
|
||||
assert(test_casestring("THORN", 0x00fe));
|
||||
assert(test_casestring("Thorn", 0x00fe));
|
||||
assert(test_casestring("thorn", 0x00fe));
|
||||
|
||||
assert(test_utf8(XKB_KEY_y, "y"));
|
||||
assert(test_utf8(XKB_KEY_u, "u"));
|
||||
assert(test_utf8(XKB_KEY_m, "m"));
|
||||
|
@ -100,13 +140,13 @@ main(void)
|
|||
|
||||
assert(xkb_keysym_is_lower(XKB_KEY_a));
|
||||
assert(xkb_keysym_is_lower(XKB_KEY_Greek_lambda));
|
||||
assert(xkb_keysym_is_lower(xkb_keysym_from_name("U03b1"))); /* GREEK SMALL LETTER ALPHA */
|
||||
assert(xkb_keysym_is_lower(xkb_keysym_from_name("U03af"))); /* GREEK SMALL LETTER IOTA WITH TONOS */
|
||||
assert(xkb_keysym_is_lower(xkb_keysym_from_name("U03b1", 0))); /* GREEK SMALL LETTER ALPHA */
|
||||
assert(xkb_keysym_is_lower(xkb_keysym_from_name("U03af", 0))); /* GREEK SMALL LETTER IOTA WITH TONOS */
|
||||
|
||||
assert(xkb_keysym_is_upper(XKB_KEY_A));
|
||||
assert(xkb_keysym_is_upper(XKB_KEY_Greek_LAMBDA));
|
||||
assert(xkb_keysym_is_upper(xkb_keysym_from_name("U0391"))); /* GREEK CAPITAL LETTER ALPHA */
|
||||
assert(xkb_keysym_is_upper(xkb_keysym_from_name("U0388"))); /* GREEK CAPITAL LETTER EPSILON WITH TONOS */
|
||||
assert(xkb_keysym_is_upper(xkb_keysym_from_name("U0391", 0))); /* GREEK CAPITAL LETTER ALPHA */
|
||||
assert(xkb_keysym_is_upper(xkb_keysym_from_name("U0388", 0))); /* GREEK CAPITAL LETTER EPSILON WITH TONOS */
|
||||
|
||||
assert(!xkb_keysym_is_upper(XKB_KEY_a));
|
||||
assert(!xkb_keysym_is_lower(XKB_KEY_A));
|
||||
|
@ -114,8 +154,8 @@ main(void)
|
|||
assert(!xkb_keysym_is_upper(XKB_KEY_Return));
|
||||
assert(!xkb_keysym_is_lower(XKB_KEY_hebrew_aleph));
|
||||
assert(!xkb_keysym_is_upper(XKB_KEY_hebrew_aleph));
|
||||
assert(!xkb_keysym_is_upper(xkb_keysym_from_name("U05D0"))); /* HEBREW LETTER ALEF */
|
||||
assert(!xkb_keysym_is_lower(xkb_keysym_from_name("U05D0"))); /* HEBREW LETTER ALEF */
|
||||
assert(!xkb_keysym_is_upper(xkb_keysym_from_name("U05D0", 0))); /* HEBREW LETTER ALEF */
|
||||
assert(!xkb_keysym_is_lower(xkb_keysym_from_name("U05D0", 0))); /* HEBREW LETTER ALEF */
|
||||
assert(!xkb_keysym_is_lower(XKB_KEY_8));
|
||||
assert(!xkb_keysym_is_upper(XKB_KEY_8));
|
||||
|
||||
|
|
|
@ -319,18 +319,34 @@ struct xkb_rule_names {
|
|||
int
|
||||
xkb_keysym_get_name(xkb_keysym_t keysym, char *buffer, size_t size);
|
||||
|
||||
/** Flags for xkb_keysym_from_name(). */
|
||||
enum xkb_keysym_flags {
|
||||
/** Find keysym by case-insensitive search. */
|
||||
XKB_KEYSYM_CASE_INSENSITIVE = (1 << 0),
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a keysym from its name.
|
||||
*
|
||||
* @param name The name of a keysym. See remarks in xkb_keysym_get_name();
|
||||
* this function will accept any name returned by that function.
|
||||
* @param flags A set of flags controlling how the search is done. If
|
||||
* invalid flags are passed, this will fail with XKB_KEY_NoSymbol.
|
||||
*
|
||||
* If you use the XKB_KEYSYM_CASE_INSENSITIVE flag and two keysym names
|
||||
* differ only by case, then the lower-case keysym is returned. For
|
||||
* instance, for KEY_a and KEY_A, this function would return KEY_a for the
|
||||
* case-insensitive search. If this functionality is needed, it is
|
||||
* recommended to first call this function without this flag; and if that
|
||||
* fails, only then to try with this flag, while possibly warning the user
|
||||
* he had misspelled the name, and might get wrong results.
|
||||
*
|
||||
* @returns The keysym. If the name is invalid, returns XKB_KEY_NoSymbol.
|
||||
*
|
||||
* @sa xkb_keysym_t
|
||||
*/
|
||||
xkb_keysym_t
|
||||
xkb_keysym_from_name(const char *name);
|
||||
xkb_keysym_from_name(const char *name, enum xkb_keysym_flags flags);
|
||||
|
||||
/**
|
||||
* Get the Unicode/UTF-8 representation of a keysym.
|
||||
|
|
Loading…
Reference in New Issue