diff --git a/bench/compose-traversal.c b/bench/compose-traversal.c new file mode 100644 index 000000000..be24beba7 --- /dev/null +++ b/bench/compose-traversal.c @@ -0,0 +1,88 @@ +/* + * Copyright © 2023 Pierre Le Marre + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include "xkbcommon/xkbcommon-compose.h" + +#include "../test/test.h" +#include "bench.h" + +#define BENCHMARK_ITERATIONS 1000 + +int +main(void) +{ + struct xkb_context *ctx; + char *path; + FILE *file; + struct xkb_compose_table *table; + struct xkb_compose_table_iterator *iter; + struct xkb_compose_table_entry *entry; + struct bench bench; + char *elapsed; + + ctx = test_get_context(CONTEXT_NO_FLAG); + assert(ctx); + + path = test_get_path("locale/en_US.UTF-8/Compose"); + file = fopen(path, "rb"); + if (file == NULL) { + perror(path); + free(path); + xkb_context_unref(ctx); + return -1; + } + free(path); + + xkb_context_set_log_level(ctx, XKB_LOG_LEVEL_CRITICAL); + xkb_context_set_log_verbosity(ctx, 0); + + table = xkb_compose_table_new_from_file(ctx, file, "", + XKB_COMPOSE_FORMAT_TEXT_V1, + XKB_COMPOSE_COMPILE_NO_FLAGS); + fclose(file); + assert(table); + + bench_start(&bench); + for (int i = 0; i < BENCHMARK_ITERATIONS; i++) { + iter = xkb_compose_table_iterator_new(table); + while ((entry = xkb_compose_table_iterator_next(iter))) { + assert (entry); + } + xkb_compose_table_iterator_free(iter); + } + bench_stop(&bench); + + xkb_compose_table_unref(table); + + elapsed = bench_elapsed_str(&bench); + fprintf(stderr, "traversed %d compose tables in %ss\n", + BENCHMARK_ITERATIONS, elapsed); + free(elapsed); + + xkb_context_unref(ctx); + return 0; +} diff --git a/include/xkbcommon/xkbcommon-compose.h b/include/xkbcommon/xkbcommon-compose.h index 8b41b987a..b28e4f843 100644 --- a/include/xkbcommon/xkbcommon-compose.h +++ b/include/xkbcommon/xkbcommon-compose.h @@ -299,6 +299,143 @@ xkb_compose_table_ref(struct xkb_compose_table *table); void xkb_compose_table_unref(struct xkb_compose_table *table); +/** + * @struct xkb_compose_table_entry + * Opaque Compose table entry object. + * + * Represents a single entry in a Compose file in the iteration API. + * It is immutable. + * + * @sa xkb_compose_table_iterator_new + * @since 1.6.0 + */ +struct xkb_compose_table_entry; + +/** + * Get the left-hand keysym sequence of a Compose table entry. + * + * For example, given the following entry: + * + * ``` + * : "~" asciitilde # TILDE + * ``` + * + * it will return `{XKB_KEY_dead_tilde, XKB_KEY_space}`. + * + * @param[in] entry The compose table entry object to process. + * + * @param[out] sequence_length Number of keysyms in the sequence. + * + * @returns The array of left-hand side keysyms. The number of keysyms + * is returned in the @p sequence_length out-parameter. + * + * @memberof xkb_compose_table_entry + * @since 1.6.0 + */ +const xkb_keysym_t * +xkb_compose_table_entry_sequence(struct xkb_compose_table_entry *entry, + size_t *sequence_length); + +/** + * Get the right-hand result keysym of a Compose table entry. + * + * For example, given the following entry: + * + * ``` + * : "~" asciitilde # TILDE + * ``` + * + * it will return `XKB_KEY_asciitilde`. + * + * The keysym is optional; if the entry does not specify a keysym, + * returns `XKB_KEY_NoSymbol`. + * + * @memberof xkb_compose_table_entry + * @since 1.6.0 + */ +xkb_keysym_t +xkb_compose_table_entry_keysym(struct xkb_compose_table_entry *entry); + +/** + * Get the right-hand result string of a Compose table entry. + * + * The string is UTF-8 encoded and NULL-terminated. + * + * For example, given the following entry: + * + * ``` + * : "~" asciitilde # TILDE + * ``` + * + * it will return `"~"`. + * + * The string is optional; if the entry does not specify a string, + * returns the empty string. + * + * @memberof xkb_compose_table_entry + * @since 1.6.0 + */ +const char * +xkb_compose_table_entry_utf8(struct xkb_compose_table_entry *entry); + +/** + * @struct xkb_compose_table_iterator + * Iterator over a compose table’s entries. + * + * @sa xkb_compose_table_iterator_new() + * @since 1.6.0 + */ +struct xkb_compose_table_iterator; + +/** + * Create a new iterator for a compose table. + * + * Intended use: + * + * ```c + * struct xkb_compose_table_iterator *iter = xkb_compose_table_iterator_new(compose_table); + * struct xkb_compose_table_entry *entry; + * while ((entry = xkb_compose_table_iterator_next(iter))) { + * // ... + * } + * xkb_compose_table_iterator_free(iter); + * ``` + * + * @returns A new compose table iterator, or `NULL` on failure. + * + * @memberof xkb_compose_table_iterator + * @sa xkb_compose_table_iterator_free() + * @since 1.6.0 + */ +struct xkb_compose_table_iterator * +xkb_compose_table_iterator_new(struct xkb_compose_table *table); + +/** + * Free a compose iterator. + * + * @memberof xkb_compose_table_iterator + * @since 1.6.0 + */ +void +xkb_compose_table_iterator_free(struct xkb_compose_table_iterator *iter); + +/** + * Get the next compose entry from a compose table iterator. + * + * The entries are returned in lexicographic order of the left-hand + * side of entries. This does not correspond to the order in which + * the entries appear in the Compose file. + * + * @attention The return value is valid until the next call to this function. + * + * Returns `NULL` in case there is no more entries. + * + * @memberof xkb_compose_table_iterator + * @since 1.6.0 + */ +struct xkb_compose_table_entry * +xkb_compose_table_iterator_next(struct xkb_compose_table_iterator *iter); + /** Flags for compose state creation. */ enum xkb_compose_state_flags { /** Do not apply any flags. */ diff --git a/meson.build b/meson.build index ce17b1d69..91d0b36b1 100644 --- a/meson.build +++ b/meson.build @@ -804,6 +804,11 @@ benchmark( executable('bench-compose', 'bench/compose.c', dependencies: test_dep), env: bench_env, ) +benchmark( + 'compose-traversal', + executable('bench-compose-traversal', 'bench/compose-traversal.c', dependencies: test_dep), + env: bench_env, +) benchmark( 'atom', executable('bench-atom', 'bench/atom.c', dependencies: test_dep), diff --git a/src/compose/parser.c b/src/compose/parser.c index 88105fa21..e1b81dea2 100644 --- a/src/compose/parser.c +++ b/src/compose/parser.c @@ -63,9 +63,6 @@ OR PERFORMANCE OF THIS SOFTWARE. #include "utf8.h" #include "parser.h" -#define MAX_LHS_LEN 10 -#define MAX_INCLUDE_DEPTH 5 - /* * Grammar adapted from libX11/modules/im/ximcp/imLcPrs.c. * See also the XCompose(5) manpage. diff --git a/src/compose/parser.h b/src/compose/parser.h index 3f64a0740..487f1a9f8 100644 --- a/src/compose/parser.h +++ b/src/compose/parser.h @@ -24,6 +24,9 @@ #ifndef COMPOSE_PARSER_H #define COMPOSE_PARSER_H +#define MAX_LHS_LEN 10 +#define MAX_INCLUDE_DEPTH 5 + bool parse_string(struct xkb_compose_table *table, const char *string, size_t len, diff --git a/src/compose/table.c b/src/compose/table.c index 3abc64940..04fa8cb74 100644 --- a/src/compose/table.c +++ b/src/compose/table.c @@ -27,6 +27,7 @@ #include "table.h" #include "parser.h" #include "paths.h" +#include "xkbcommon/xkbcommon.h" static struct xkb_compose_table * xkb_compose_table_new(struct xkb_context *ctx, @@ -228,3 +229,158 @@ xkb_compose_table_new_from_locale(struct xkb_context *ctx, free(path); return table; } + +XKB_EXPORT const xkb_keysym_t * +xkb_compose_table_entry_sequence(struct xkb_compose_table_entry *entry, + size_t *sequence_length) +{ + *sequence_length = entry->sequence_length; + return entry->sequence; +} + +XKB_EXPORT xkb_keysym_t +xkb_compose_table_entry_keysym(struct xkb_compose_table_entry *entry) +{ + return entry->keysym; +} + +XKB_EXPORT const char * +xkb_compose_table_entry_utf8(struct xkb_compose_table_entry *entry) +{ + return entry->utf8; +} + +enum node_direction { + NODE_LEFT = 0, + NODE_DOWN, + NODE_RIGHT, + NODE_UP +}; + +struct xkb_compose_table_iterator_cursor { + uint32_t node_offset:30; /* WARNING: ensure it fits MAX_COMPOSE_NODES */ + uint8_t direction:2; /* enum node_direction: current direction + * traversing the tree */ +}; + +struct xkb_compose_table_iterator { + struct xkb_compose_table *table; + /* Current entry */ + struct xkb_compose_table_entry entry; + /* Stack of pending nodes to process */ + darray(struct xkb_compose_table_iterator_cursor) cursors; +}; + +XKB_EXPORT struct xkb_compose_table_iterator * +xkb_compose_table_iterator_new(struct xkb_compose_table *table) +{ + struct xkb_compose_table_iterator *iter; + struct xkb_compose_table_iterator_cursor cursor; + xkb_keysym_t *sequence; + + iter = calloc(1, sizeof(*iter)); + if (!iter) { + return NULL; + } + iter->table = xkb_compose_table_ref(table); + sequence = calloc(MAX_LHS_LEN, sizeof(xkb_keysym_t)); + if (!sequence) { + free(iter); + return NULL; + } + iter->entry.sequence = sequence; + iter->entry.sequence_length = 0; + + darray_init(iter->cursors); + cursor.direction = NODE_LEFT; + /* Offset 0 is a dummy null entry, skip it. */ + cursor.node_offset = 1; + darray_append(iter->cursors, cursor); + + return iter; +} + +XKB_EXPORT void +xkb_compose_table_iterator_free(struct xkb_compose_table_iterator *iter) +{ + xkb_compose_table_unref(iter->table); + darray_free(iter->cursors); + free(iter->entry.sequence); + free(iter); +} + +XKB_EXPORT struct xkb_compose_table_entry * +xkb_compose_table_iterator_next(struct xkb_compose_table_iterator *iter) +{ + /* + * This function takes the following recursive traversal function, + * and makes it non-recursive and resumable. The iter->cursors stack + * is analogous to the call stack, and cursor->direction to the + * instruction pointer of a stack frame. + * + * traverse(xkb_keysym_t *sequence, size_t sequence_length, uint16_t p) { + * if (!p) return + * // cursor->direction == NODE_LEFT + * node = &darray_item(table->nodes, p) + * traverse(sequence, sequence_length, node->lokid) + * // cursor->direction == NODE_DOWN + * sequence[sequence_length++] = node->keysym + * if (node->is_leaf) + * emit(sequence, sequence_length, node->leaf.keysym, table->utf[node->leaf.utf8]) + * else + * traverse(sequence, sequence_length, node->internal.eqkid) + * sequence_length-- + * // cursor->direction == NODE_RIGHT + * traverse(sequence, sequence_length, node->hikid) + * // cursor->direction == NODE_UP + * } + */ + + struct xkb_compose_table_iterator_cursor *cursor; + const struct compose_node *node; + + while (!darray_empty(iter->cursors)) { + cursor = &darray_item(iter->cursors, darray_size(iter->cursors) - 1); + node = &darray_item(iter->table->nodes, cursor->node_offset); + + switch (cursor->direction) { + case NODE_LEFT: + cursor->direction = NODE_DOWN; + if (node->lokid) { + struct xkb_compose_table_iterator_cursor new_cursor = {node->lokid, NODE_LEFT}; + darray_append(iter->cursors, new_cursor); + } + break; + + case NODE_DOWN: + cursor->direction = NODE_RIGHT; + assert (iter->entry.sequence_length <= MAX_LHS_LEN); + iter->entry.sequence[iter->entry.sequence_length] = node->keysym; + iter->entry.sequence_length++; + if (node->is_leaf) { + iter->entry.keysym = node->leaf.keysym; + iter->entry.utf8 = &darray_item(iter->table->utf8, node->leaf.utf8); + return &iter->entry; + } else { + struct xkb_compose_table_iterator_cursor new_cursor = {node->internal.eqkid, NODE_LEFT}; + darray_append(iter->cursors, new_cursor); + } + break; + + case NODE_RIGHT: + cursor->direction = NODE_UP; + iter->entry.sequence_length--; + if (node->hikid) { + struct xkb_compose_table_iterator_cursor new_cursor = {node->hikid, NODE_LEFT}; + darray_append(iter->cursors, new_cursor); + } + break; + + case NODE_UP: + darray_remove_last(iter->cursors); + break; + } + } + + return NULL; +} diff --git a/src/compose/table.h b/src/compose/table.h index 825a8e2e8..f6904a1c2 100644 --- a/src/compose/table.h +++ b/src/compose/table.h @@ -119,4 +119,11 @@ struct xkb_compose_table { darray(struct compose_node) nodes; }; +struct xkb_compose_table_entry { + xkb_keysym_t *sequence; + size_t sequence_length; + xkb_keysym_t keysym; + const char *utf8; +}; + #endif diff --git a/src/darray.h b/src/darray.h index de659ccad..b75d85f98 100644 --- a/src/darray.h +++ b/src/darray.h @@ -114,6 +114,11 @@ typedef darray (unsigned long) darray_ulong; #define darray_concat(arr_to, arr_from) \ darray_append_items((arr_to), (arr_from).item, (arr_from).size) +/*** Removal ***/ + +/* Warning: Do not call darray_remove_last on an empty darray. */ +#define darray_remove_last(arr) (--(arr).size) + /*** String buffer ***/ #define darray_append_string(arr, str) do { \ diff --git a/test/compose.c b/test/compose.c index 1e85cbd47..5e7cba0f3 100644 --- a/test/compose.c +++ b/test/compose.c @@ -580,6 +580,110 @@ test_override(struct xkb_context *ctx) XKB_KEY_NoSymbol)); } +static bool +test_eq_entry_va(struct xkb_compose_table_entry *entry, xkb_keysym_t keysym_ref, const char *utf8_ref, va_list ap) +{ + assert (entry != NULL); + + assert (xkb_compose_table_entry_keysym(entry) == keysym_ref); + + const char *utf8 = xkb_compose_table_entry_utf8(entry); + assert (utf8 && utf8_ref && strcmp(utf8, utf8_ref) == 0); + + size_t nsyms; + const xkb_keysym_t *sequence = xkb_compose_table_entry_sequence(entry, &nsyms); + + xkb_keysym_t keysym; + for (unsigned k = 0; ; k++) { + keysym = va_arg(ap, xkb_keysym_t); + if (keysym == XKB_KEY_NoSymbol) { + return (k == nsyms - 1); + } + assert (k < nsyms); + assert (keysym == sequence[k]); + } +} + +static bool +test_eq_entry(struct xkb_compose_table_entry *entry, xkb_keysym_t keysym, const char *utf8, ...) +{ + va_list ap; + bool ok; + va_start(ap, utf8); + ok = test_eq_entry_va(entry, keysym, utf8, ap); + va_end(ap); + return ok; +} + +static void +test_traverse(struct xkb_context *ctx) +{ + struct xkb_compose_table *table; + + const char *buffer = " : \"foo\" X\n" + " : \"foobar\"\n" + " : oe\n" + " : \"bar\" Y\n" + " : \"æ\" ae\n" + " : \"baz\" Z\n" + " : \"é\" eacute\n" + " : \"aac\"\n" + " : \"aab\"\n" + " : \"aaa\"\n"; + + table = xkb_compose_table_new_from_buffer(ctx, buffer, strlen(buffer), "", + XKB_COMPOSE_FORMAT_TEXT_V1, + XKB_COMPOSE_COMPILE_NO_FLAGS); + assert(table); + + struct xkb_compose_table_iterator *iter = xkb_compose_table_iterator_new(table); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_eacute, "é", + XKB_KEY_dead_acute, XKB_KEY_e, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_Z, "baz", + XKB_KEY_dead_circumflex, XKB_KEY_a, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_Y, "bar", + XKB_KEY_dead_circumflex, XKB_KEY_e, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_X, "foo", + XKB_KEY_dead_circumflex, XKB_KEY_dead_circumflex, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_NoSymbol, "aaa", + XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_a, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_NoSymbol, "aab", + XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_b, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_NoSymbol, "aac", + XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_c, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_ae, "æ", + XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_e, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_oe, "", + XKB_KEY_Multi_key, XKB_KEY_o, XKB_KEY_e, XKB_KEY_NoSymbol); + + test_eq_entry(xkb_compose_table_iterator_next(iter), + XKB_KEY_NoSymbol, "foobar", + XKB_KEY_Ahook, XKB_KEY_x, XKB_KEY_NoSymbol); + + assert (xkb_compose_table_iterator_next(iter) == NULL); + + xkb_compose_table_iterator_free(iter); + xkb_compose_table_unref(table); +} + int main(int argc, char *argv[]) { @@ -612,6 +716,7 @@ main(int argc, char *argv[]) test_modifier_syntax(ctx); test_include(ctx); test_override(ctx); + test_traverse(ctx); xkb_context_unref(ctx); return 0; diff --git a/tools/compose.c b/tools/compose.c index 2b3ba643c..3a77347b8 100644 --- a/tools/compose.c +++ b/tools/compose.c @@ -29,21 +29,49 @@ #include #include "xkbcommon/xkbcommon.h" +#include "xkbcommon/xkbcommon-keysyms.h" #include "xkbcommon/xkbcommon-compose.h" static void usage(FILE *fp, char *progname) { fprintf(fp, - "Usage: %s [--locale LOCALE | --locale-from-env | --locale-from-setlocale]\n", + "Usage: %s [--file FILE] [--locale LOCALE | --locale-from-env | --locale-from-setlocale]\n", progname); fprintf(fp, + " --file - specify a file to load\n" " --locale - specify the locale directly\n" " --locale-from-env - get the locale from the LC_ALL/LC_CTYPE/LANG environment variables (falling back to C)\n" " --locale-from-setlocale - get the locale using setlocale(3)\n" ); } +static void +print_compose_table_entry(struct xkb_compose_table_entry *entry) +{ + size_t nsyms; + const xkb_keysym_t *syms = xkb_compose_table_entry_sequence(entry, &nsyms); + char buf[128]; + for (size_t i = 0; i < nsyms; i++) { + xkb_keysym_get_name(syms[i], buf, sizeof(buf)); + printf("<%s>", buf); + if (i + 1 < nsyms) { + printf(" "); + } + } + printf(":"); + const char *utf8 = xkb_compose_table_entry_utf8(entry); + if (*utf8 != '\0') { + printf(" \"%s\"", utf8); + } + const xkb_keysym_t keysym = xkb_compose_table_entry_keysym(entry); + if (keysym != XKB_KEY_NoSymbol) { + xkb_keysym_get_name(keysym, buf, sizeof(buf)); + printf(" %s", buf); + } + printf("\n"); +} + int main(int argc, char *argv[]) { @@ -51,12 +79,16 @@ main(int argc, char *argv[]) struct xkb_context *ctx = NULL; struct xkb_compose_table *compose_table = NULL; const char *locale = NULL; + const char *path = NULL; + enum xkb_compose_format format = XKB_COMPOSE_FORMAT_TEXT_V1; enum options { + OPT_FILE, OPT_LOCALE, OPT_LOCALE_FROM_ENV, OPT_LOCALE_FROM_SETLOCALE, }; static struct option opts[] = { + {"file", required_argument, 0, OPT_FILE}, {"locale", required_argument, 0, OPT_LOCALE}, {"locale-from-env", no_argument, 0, OPT_LOCALE_FROM_ENV}, {"locale-from-setlocale", no_argument, 0, OPT_LOCALE_FROM_SETLOCALE}, @@ -74,6 +106,9 @@ main(int argc, char *argv[]) break; switch (opt) { + case OPT_FILE: + path = optarg; + break; case OPT_LOCALE: locale = optarg; break; @@ -108,18 +143,40 @@ main(int argc, char *argv[]) goto out; } - compose_table = - xkb_compose_table_new_from_locale(ctx, locale, - XKB_COMPOSE_COMPILE_NO_FLAGS); - if (!compose_table) { - fprintf(stderr, "Couldn't create compose from locale\n"); - goto out; + if (path != NULL) { + FILE *file = fopen(path, "rb"); + if (file == NULL) { + perror(path); + goto file_error; + } + compose_table = + xkb_compose_table_new_from_file(ctx, file, locale, format, + XKB_COMPOSE_COMPILE_NO_FLAGS); + fclose(file); + if (!compose_table) { + fprintf(stderr, "Couldn't create compose from file: %s\n", path); + goto out; + } + } else { + compose_table = + xkb_compose_table_new_from_locale(ctx, locale, + XKB_COMPOSE_COMPILE_NO_FLAGS); + if (!compose_table) { + fprintf(stderr, "Couldn't create compose from locale\n"); + goto out; + } } - printf("Compiled successfully from locale %s\n", locale); + struct xkb_compose_table_iterator *iter = xkb_compose_table_iterator_new(compose_table); + struct xkb_compose_table_entry *entry; + while ((entry = xkb_compose_table_iterator_next(iter))) { + print_compose_table_entry(entry); + } + xkb_compose_table_iterator_free(iter); out: xkb_compose_table_unref(compose_table); +file_error: xkb_context_unref(ctx); return ret; diff --git a/xkbcommon.map b/xkbcommon.map index 59ef97182..b2507272e 100644 --- a/xkbcommon.map +++ b/xkbcommon.map @@ -109,3 +109,13 @@ global: xkb_utf32_to_keysym; xkb_keymap_key_get_mods_for_level; } V_0.8.0; + +V_1.6.0 { +global: + xkb_compose_table_entry_sequence; + xkb_compose_table_entry_keysym; + xkb_compose_table_entry_utf8; + xkb_compose_table_iterator_new; + xkb_compose_table_iterator_free; + xkb_compose_table_iterator_next; +} V_1.0.0;