Compose: add iterator API
Allow users to iterate the entries in a compose table. This is useful for other projects which want programmable access to the sequences, without having to write their own parser. - New API: - `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`. - Add tests in `test/compose.c`. - Add benchmark for compose traversal. - `tools/compose.c`: - Print entries instead of just validating them. - Add `--file` option. - TODO: make this tool part of the xkbcli commands. Co-authored-by: Pierre Le Marre <dev@wismill.eu> Co-authored-by: Ran Benita <ran@unusedvar.com> Signed-off-by: Ran Benita <ran@unusedvar.com>master
parent
c0065c95a4
commit
a17701327e
|
@ -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 <time.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
|
@ -299,6 +299,143 @@ xkb_compose_table_ref(struct xkb_compose_table *table);
|
||||||
void
|
void
|
||||||
xkb_compose_table_unref(struct xkb_compose_table *table);
|
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:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <dead_tilde> <space> : "~" 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:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <dead_tilde> <space> : "~" 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:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <dead_tilde> <space> : "~" 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. */
|
/** Flags for compose state creation. */
|
||||||
enum xkb_compose_state_flags {
|
enum xkb_compose_state_flags {
|
||||||
/** Do not apply any flags. */
|
/** Do not apply any flags. */
|
||||||
|
|
|
@ -804,6 +804,11 @@ benchmark(
|
||||||
executable('bench-compose', 'bench/compose.c', dependencies: test_dep),
|
executable('bench-compose', 'bench/compose.c', dependencies: test_dep),
|
||||||
env: bench_env,
|
env: bench_env,
|
||||||
)
|
)
|
||||||
|
benchmark(
|
||||||
|
'compose-traversal',
|
||||||
|
executable('bench-compose-traversal', 'bench/compose-traversal.c', dependencies: test_dep),
|
||||||
|
env: bench_env,
|
||||||
|
)
|
||||||
benchmark(
|
benchmark(
|
||||||
'atom',
|
'atom',
|
||||||
executable('bench-atom', 'bench/atom.c', dependencies: test_dep),
|
executable('bench-atom', 'bench/atom.c', dependencies: test_dep),
|
||||||
|
|
|
@ -63,9 +63,6 @@ OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
|
|
||||||
#define MAX_LHS_LEN 10
|
|
||||||
#define MAX_INCLUDE_DEPTH 5
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Grammar adapted from libX11/modules/im/ximcp/imLcPrs.c.
|
* Grammar adapted from libX11/modules/im/ximcp/imLcPrs.c.
|
||||||
* See also the XCompose(5) manpage.
|
* See also the XCompose(5) manpage.
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
#ifndef COMPOSE_PARSER_H
|
#ifndef COMPOSE_PARSER_H
|
||||||
#define COMPOSE_PARSER_H
|
#define COMPOSE_PARSER_H
|
||||||
|
|
||||||
|
#define MAX_LHS_LEN 10
|
||||||
|
#define MAX_INCLUDE_DEPTH 5
|
||||||
|
|
||||||
bool
|
bool
|
||||||
parse_string(struct xkb_compose_table *table,
|
parse_string(struct xkb_compose_table *table,
|
||||||
const char *string, size_t len,
|
const char *string, size_t len,
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "table.h"
|
#include "table.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include "paths.h"
|
#include "paths.h"
|
||||||
|
#include "xkbcommon/xkbcommon.h"
|
||||||
|
|
||||||
static struct xkb_compose_table *
|
static struct xkb_compose_table *
|
||||||
xkb_compose_table_new(struct xkb_context *ctx,
|
xkb_compose_table_new(struct xkb_context *ctx,
|
||||||
|
@ -228,3 +229,158 @@ found_path:
|
||||||
free(path);
|
free(path);
|
||||||
return table;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -119,4 +119,11 @@ struct xkb_compose_table {
|
||||||
darray(struct compose_node) nodes;
|
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
|
#endif
|
||||||
|
|
|
@ -114,6 +114,11 @@ typedef darray (unsigned long) darray_ulong;
|
||||||
#define darray_concat(arr_to, arr_from) \
|
#define darray_concat(arr_to, arr_from) \
|
||||||
darray_append_items((arr_to), (arr_from).item, (arr_from).size)
|
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 ***/
|
/*** String buffer ***/
|
||||||
|
|
||||||
#define darray_append_string(arr, str) do { \
|
#define darray_append_string(arr, str) do { \
|
||||||
|
|
105
test/compose.c
105
test/compose.c
|
@ -580,6 +580,110 @@ test_override(struct xkb_context *ctx)
|
||||||
XKB_KEY_NoSymbol));
|
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 = "<dead_circumflex> <dead_circumflex> : \"foo\" X\n"
|
||||||
|
"<Ahook> <x> : \"foobar\"\n"
|
||||||
|
"<Multi_key> <o> <e> : oe\n"
|
||||||
|
"<dead_circumflex> <e> : \"bar\" Y\n"
|
||||||
|
"<Multi_key> <a> <e> : \"æ\" ae\n"
|
||||||
|
"<dead_circumflex> <a> : \"baz\" Z\n"
|
||||||
|
"<dead_acute> <e> : \"é\" eacute\n"
|
||||||
|
"<Multi_key> <a> <a> <c>: \"aac\"\n"
|
||||||
|
"<Multi_key> <a> <a> <b>: \"aab\"\n"
|
||||||
|
"<Multi_key> <a> <a> <a>: \"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
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
@ -612,6 +716,7 @@ main(int argc, char *argv[])
|
||||||
test_modifier_syntax(ctx);
|
test_modifier_syntax(ctx);
|
||||||
test_include(ctx);
|
test_include(ctx);
|
||||||
test_override(ctx);
|
test_override(ctx);
|
||||||
|
test_traverse(ctx);
|
||||||
|
|
||||||
xkb_context_unref(ctx);
|
xkb_context_unref(ctx);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -29,21 +29,49 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "xkbcommon/xkbcommon.h"
|
#include "xkbcommon/xkbcommon.h"
|
||||||
|
#include "xkbcommon/xkbcommon-keysyms.h"
|
||||||
#include "xkbcommon/xkbcommon-compose.h"
|
#include "xkbcommon/xkbcommon-compose.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
usage(FILE *fp, char *progname)
|
usage(FILE *fp, char *progname)
|
||||||
{
|
{
|
||||||
fprintf(fp,
|
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);
|
progname);
|
||||||
fprintf(fp,
|
fprintf(fp,
|
||||||
|
" --file - specify a file to load\n"
|
||||||
" --locale - specify the locale directly\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-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"
|
" --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
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
@ -51,12 +79,16 @@ main(int argc, char *argv[])
|
||||||
struct xkb_context *ctx = NULL;
|
struct xkb_context *ctx = NULL;
|
||||||
struct xkb_compose_table *compose_table = NULL;
|
struct xkb_compose_table *compose_table = NULL;
|
||||||
const char *locale = NULL;
|
const char *locale = NULL;
|
||||||
|
const char *path = NULL;
|
||||||
|
enum xkb_compose_format format = XKB_COMPOSE_FORMAT_TEXT_V1;
|
||||||
enum options {
|
enum options {
|
||||||
|
OPT_FILE,
|
||||||
OPT_LOCALE,
|
OPT_LOCALE,
|
||||||
OPT_LOCALE_FROM_ENV,
|
OPT_LOCALE_FROM_ENV,
|
||||||
OPT_LOCALE_FROM_SETLOCALE,
|
OPT_LOCALE_FROM_SETLOCALE,
|
||||||
};
|
};
|
||||||
static struct option opts[] = {
|
static struct option opts[] = {
|
||||||
|
{"file", required_argument, 0, OPT_FILE},
|
||||||
{"locale", required_argument, 0, OPT_LOCALE},
|
{"locale", required_argument, 0, OPT_LOCALE},
|
||||||
{"locale-from-env", no_argument, 0, OPT_LOCALE_FROM_ENV},
|
{"locale-from-env", no_argument, 0, OPT_LOCALE_FROM_ENV},
|
||||||
{"locale-from-setlocale", no_argument, 0, OPT_LOCALE_FROM_SETLOCALE},
|
{"locale-from-setlocale", no_argument, 0, OPT_LOCALE_FROM_SETLOCALE},
|
||||||
|
@ -74,6 +106,9 @@ main(int argc, char *argv[])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
|
case OPT_FILE:
|
||||||
|
path = optarg;
|
||||||
|
break;
|
||||||
case OPT_LOCALE:
|
case OPT_LOCALE:
|
||||||
locale = optarg;
|
locale = optarg;
|
||||||
break;
|
break;
|
||||||
|
@ -108,18 +143,40 @@ main(int argc, char *argv[])
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
compose_table =
|
if (path != NULL) {
|
||||||
xkb_compose_table_new_from_locale(ctx, locale,
|
FILE *file = fopen(path, "rb");
|
||||||
XKB_COMPOSE_COMPILE_NO_FLAGS);
|
if (file == NULL) {
|
||||||
if (!compose_table) {
|
perror(path);
|
||||||
fprintf(stderr, "Couldn't create compose from locale\n");
|
goto file_error;
|
||||||
goto out;
|
}
|
||||||
|
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:
|
out:
|
||||||
xkb_compose_table_unref(compose_table);
|
xkb_compose_table_unref(compose_table);
|
||||||
|
file_error:
|
||||||
xkb_context_unref(ctx);
|
xkb_context_unref(ctx);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -109,3 +109,13 @@ global:
|
||||||
xkb_utf32_to_keysym;
|
xkb_utf32_to_keysym;
|
||||||
xkb_keymap_key_get_mods_for_level;
|
xkb_keymap_key_get_mods_for_level;
|
||||||
} V_0.8.0;
|
} 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;
|
||||||
|
|
Loading…
Reference in New Issue