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>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
diff --git a/makekeys.py b/makekeys.py
index 94885c0..996ff3d 100644
--- a/makekeys.py
+++ b/makekeys.py
@@ -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')
diff --git a/src/keysym.c b/src/keysym.c
index cff0b15..4f6285b 100644
--- a/src/keysym.c
+++ b/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;
}
diff --git a/src/xkbcomp/expr.c b/src/xkbcomp/expr.c
index eb043e1..dc64d78 100644
--- a/src/xkbcomp/expr.c
+++ b/src/xkbcomp/expr.c
@@ -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;
}
diff --git a/src/xkbcomp/symbols.c b/src/xkbcomp/symbols.c
index 10efef0..9900469 100644
--- a/src/xkbcomp/symbols.c
+++ b/src/xkbcomp/symbols.c
@@ -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;
diff --git a/test/keyseq.c b/test/keyseq.c
index d5993ec..cb53739 100644
--- a/test/keyseq.c
+++ b/test/keyseq.c
@@ -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,
diff --git a/test/keysym.c b/test/keysym.c
index ef934ab..595f829 100644
--- a/test/keysym.c
+++ b/test/keysym.c
@@ -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);
@@ -38,6 +38,19 @@ test_string(const char *string, xkb_keysym_t 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)
{
char s[16];
@@ -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));
diff --git a/xkbcommon/xkbcommon.h b/xkbcommon/xkbcommon.h
index 5d6a295..ddc715c 100644
--- a/xkbcommon/xkbcommon.h
+++ b/xkbcommon/xkbcommon.h
@@ -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.