diff --git a/include/xkbcommon/xkbcommon-compose.h b/include/xkbcommon/xkbcommon-compose.h index b28e4f843..100c83f28 100644 --- a/include/xkbcommon/xkbcommon-compose.h +++ b/include/xkbcommon/xkbcommon-compose.h @@ -83,14 +83,23 @@ extern "C" { * @page compose-conflicting Conflicting Sequences * @parblock * - * To avoid ambiguity, a sequence is not allowed to be a prefix of another. + * Sequences of length 1 are allowed. + * + * Without `XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES` + * + * To avoid ambiguity, a sequence is *not* allowed to be a prefix of another. * In such a case, the conflict is resolved thus: * * 1. A longer sequence overrides a shorter one. * 2. An equal sequence overrides an existing one. * 3. A shorter sequence does not override a longer one. * - * Sequences of length 1 are allowed. + * With `XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES` + * + * Overlapping sequences of different lengths are *allowed* to co-exist. + * Conflicts are resolved with the following rule: + * + * 1. An equal sequence overrides an existing one. * * @endparblock */ @@ -141,7 +150,11 @@ struct xkb_compose_state; /** Flags affecting Compose file compilation. */ enum xkb_compose_compile_flags { /** Do not apply any flags. */ - XKB_COMPOSE_COMPILE_NO_FLAGS = 0 + XKB_COMPOSE_COMPILE_NO_FLAGS = 0, + /** Allow overlapping sequences + * @since 1.7.0 + */ + XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES }; /** The recognized Compose file formats. */ @@ -450,7 +463,7 @@ enum xkb_compose_state_flags { * @param flags * Optional flags for the compose state, or 0. * - * @returns A new compose state, or NULL on failure. + * @returns A new compose state, or `NULL` on failure. * * @memberof xkb_compose_state */ @@ -471,7 +484,7 @@ xkb_compose_state_ref(struct xkb_compose_state *state); /** * Release a reference on a compose state object, and possibly free it. * - * @param state The object. If NULL, do nothing. + * @param state The object. If `NULL`, do nothing. * * @memberof xkb_compose_state */ @@ -502,7 +515,17 @@ enum xkb_compose_status { /** A complete sequence has been matched. */ XKB_COMPOSE_COMPOSED, /** The last sequence was cancelled due to an unmatched keysym. */ - XKB_COMPOSE_CANCELLED + XKB_COMPOSE_CANCELLED, + /** A complete sequence has been matched, but a longer sequence also exists. + * + * @since 1.7.0 + */ + XKB_COMPOSE_CANDIDATE, + /** The last sequence was accepted due to an unmatched keysym. + * + * @since 1.7.0 + */ + XKB_COMPOSE_CANDIDATE_ACCEPTED }; /** The effect of a keysym fed to xkb_compose_state_feed(). */ @@ -524,27 +547,45 @@ enum xkb_compose_feed_result { * have no effect on the status or otherwise. * * The following is a description of the possible status transitions, in - * the format CURRENT STATUS => NEXT STATUS, given a non-ignored input - * keysym `keysym`: + * the format `CURRENT STATUS` => `NEXT STATUS`, given a non-ignored + * input keysym `keysym`: * @verbatim - NOTHING or CANCELLED or COMPOSED => + NOTHING or CANCELLED or COMPOSED or CANDIDATE_ACCEPTED => NOTHING if keysym does not start a sequence. COMPOSING if keysym starts a sequence. + CANDIDATE if keysym starts and terminates a single-keysym sequence, + but a longer sequence also exists. COMPOSED if keysym starts and terminates a single-keysym sequence. COMPOSING => COMPOSING if keysym advances any of the currently possible sequences but does not terminate any of them. + CANDIDATE if keysym terminates one of the currently possible + sequences, but a longer sequence also exists. COMPOSED if keysym terminates one of the currently possible sequences. CANCELLED if keysym does not advance any of the currently possible sequences. + + CANDIDATE => + COMPOSING if keysym advances any of the currently possible + sequences but does not terminate any of them. + CANDIDATE if keysym terminates one of the currently possible + sequences, but a longer sequence also exists. + COMPOSED if keysym terminates one of the currently possible + sequences. + CANDIDATE_ACCEPTED + if keysym does not advance any of the currently + possible sequences, but a candidate was proposed previously. @endverbatim * + * @note `CANDIDATE` and `CANDIDATE_ACCEPTED` are only possible when compiling + * using `XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES`. + * * The current Compose formats do not support multiple-keysyms. * Therefore, if you are using a function such as xkb_state_key_get_syms() - * and it returns more than one keysym, consider feeding XKB_KEY_NoSymbol + * and it returns more than one keysym, consider feeding `XKB_KEY_NoSymbol` * instead. * * @param state @@ -565,7 +606,7 @@ xkb_compose_state_feed(struct xkb_compose_state *state, /** * Reset the Compose sequence state machine. * - * The status is set to XKB_COMPOSE_NOTHING, and the current sequence + * The status is set to `XKB_COMPOSE_NOTHING`, and the current sequence * is discarded. * * @memberof xkb_compose_state @@ -586,7 +627,7 @@ xkb_compose_state_get_status(struct xkb_compose_state *state); * Get the result Unicode/UTF-8 string for a composed sequence. * * See @ref compose-overview for more details. This function is only - * useful when the status is XKB_COMPOSE_COMPOSED. + * useful when the status is `XKB_COMPOSE_COMPOSED` or `XKB_COMPOSE_CANDIDATE`. * * @param[in] state * The compose state. @@ -618,10 +659,10 @@ xkb_compose_state_get_utf8(struct xkb_compose_state *state, * Get the result keysym for a composed sequence. * * See @ref compose-overview for more details. This function is only - * useful when the status is XKB_COMPOSE_COMPOSED. + * useful when the status is `XKB_COMPOSE_COMPOSED` or `XKB_COMPOSE_CANDIDATE`. * * @returns The result keysym. If the sequence is not complete, or does - * not specify a result keysym, returns XKB_KEY_NoSymbol. + * not specify a result keysym, returns `XKB_KEY_NoSymbol`. * * @memberof xkb_compose_state **/ diff --git a/src/compose/parser.c b/src/compose/parser.c index ac11446a3..a7742aa96 100644 --- a/src/compose/parser.c +++ b/src/compose/parser.c @@ -346,13 +346,23 @@ add_production(struct xkb_compose_table *table, struct scanner *s, uint32_t curr = darray_size(table->nodes) == 1 ? 0 : 1; uint32_t *pptr = NULL; struct compose_node *node = NULL; + bool allow_overlapping; - /* Warn before potentially going over the limit, discard silently after. */ - if (darray_size(table->nodes) + production->len + MAX_LHS_LEN > MAX_COMPOSE_NODES) + // TODO: adapt limit if overlapping is disallowed? + /* + * Warn before potentially going over the limit, discard silently after. + * + * We may add up to production->len * 2 - 1 nodes: + * • one node per keysym in the sequence + * • plus one node per keysym for overlap, except for the last node. + */ + if (darray_size(table->nodes) + production->len * 2 - 1 + MAX_LHS_LEN > MAX_COMPOSE_NODES) scanner_warn(s, "too many sequences for one Compose file; will ignore further lines"); - if (darray_size(table->nodes) + production->len >= MAX_COMPOSE_NODES) + if (darray_size(table->nodes) + production->len * 2 - 1 >= MAX_COMPOSE_NODES) return; + allow_overlapping = !!(table->flags & XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES); + /* * Insert the sequence to the ternary search tree, creating new nodes as * needed. @@ -375,8 +385,9 @@ add_production(struct xkb_compose_table *table, struct scanner *s, .lokid = 0, .hikid = 0, .internal = { - .eqkid = 0, + .resid = 0, .is_leaf = false, + .eqkid = 0, }, }; curr = darray_size(table->nodes); @@ -396,28 +407,86 @@ add_production(struct xkb_compose_table *table, struct scanner *s, pptr = &node->hikid; curr = node->hikid; } else if (!last) { + /* Adding intermediate node */ if (node->is_leaf) { - scanner_warn(s, "a sequence already exists which is a prefix of this sequence; overriding"); - node->internal.eqkid = 0; + /* Existing leaf */ + if (allow_overlapping) { + /* Backup overlapping sequence result */ + struct compose_node overlapping = { + .keysym = node->keysym, + .lokid = 0, + .hikid = 0, + .leaf = node->leaf + }; + darray_append(table->nodes, overlapping); + node = &darray_item(table->nodes, curr); + node->internal.resid = darray_size(table->nodes) - 1; + } else { + scanner_warn(s, "a sequence already exists which is a prefix of this sequence; overriding"); + node->internal.resid = 0; + } + /* Reset node */ node->internal.is_leaf = false; + node->internal.eqkid = 0; } lhs_pos++; pptr = &node->internal.eqkid; curr = node->internal.eqkid; } else { + /* Adding the last node of the sequence and the result */ + struct compose_node *result = NULL; + bool has_previous_leaf; if (node->is_leaf) { + /* Existing leaf */ + has_previous_leaf = true; + result = node; + } else if (node->internal.eqkid != 0) { + /* Existing non-leaf */ + if (!allow_overlapping) { + scanner_warn(s, "this compose sequence is a prefix of another; skipping line"); + return; + } else if (node->internal.resid) { + /* Reuse existing overlapping sequence result */ + result = &darray_item(table->nodes, node->internal.resid); + has_previous_leaf = true; + } else { + /* Create a new overlapping sequence result */ + node->internal.resid = darray_size(table->nodes); + struct compose_node overlapping = { + .keysym = node->keysym, + .lokid = 0, + .hikid = 0, + .leaf = { + .utf8 = 0, + .is_leaf = true, + .keysym = XKB_KEY_NoSymbol + } + }; + darray_append(table->nodes, overlapping); + node = &darray_item(table->nodes, curr); + result = &darray_item(table->nodes, + node->internal.resid); + has_previous_leaf = false; + } + } else { + /* New leaf */ + has_previous_leaf = false; + node->is_leaf = true; + result = node; + } + if (has_previous_leaf) { bool same_string = - (node->leaf.utf8 == 0 && !production->has_string) || + (result->leaf.utf8 == 0 && !production->has_string) || ( - node->leaf.utf8 != 0 && production->has_string && - streq(&darray_item(table->utf8, node->leaf.utf8), + result->leaf.utf8 != 0 && production->has_string && + streq(&darray_item(table->utf8, result->leaf.utf8), production->string) ); bool same_keysym = - (node->leaf.keysym == XKB_KEY_NoSymbol && !production->has_keysym) || + (result->leaf.keysym == XKB_KEY_NoSymbol && !production->has_keysym) || ( - node->leaf.keysym != XKB_KEY_NoSymbol && production->has_keysym && - node->leaf.keysym == production->keysym + result->leaf.keysym != XKB_KEY_NoSymbol && production->has_keysym && + result->leaf.keysym == production->keysym ); if (same_string && same_keysym) { scanner_warn(s, "this compose sequence is a duplicate of another; skipping line"); @@ -425,18 +494,15 @@ add_production(struct xkb_compose_table *table, struct scanner *s, } else { scanner_warn(s, "this compose sequence already exists; overriding"); } - } else if (node->internal.eqkid != 0) { - scanner_warn(s, "this compose sequence is a prefix of another; skipping line"); - return; } - node->is_leaf = true; + result->is_leaf = true; if (production->has_string) { - node->leaf.utf8 = darray_size(table->utf8); + result->leaf.utf8 = darray_size(table->utf8); darray_append_items(table->utf8, production->string, strlen(production->string) + 1); } if (production->has_keysym) { - node->leaf.keysym = production->keysym; + result->leaf.keysym = production->keysym; } return; } diff --git a/src/compose/state.c b/src/compose/state.c index 7a788914e..f8051152e 100644 --- a/src/compose/state.c +++ b/src/compose/state.c @@ -143,14 +143,18 @@ xkb_compose_state_get_status(struct xkb_compose_state *state) prev_node = &darray_item(state->table->nodes, state->prev_context); node = &darray_item(state->table->nodes, state->context); - if (state->context == 0 && !prev_node->is_leaf) - return XKB_COMPOSE_CANCELLED; - - if (state->context == 0) + if (state->context == 0) { + if (!prev_node->is_leaf) + return prev_node->internal.resid + ? XKB_COMPOSE_CANDIDATE_ACCEPTED + : XKB_COMPOSE_CANCELLED; return XKB_COMPOSE_NOTHING; + } if (!node->is_leaf) - return XKB_COMPOSE_COMPOSING; + return node->internal.resid + ? XKB_COMPOSE_CANDIDATE + : XKB_COMPOSE_COMPOSING; return XKB_COMPOSE_COMPOSED; } @@ -162,8 +166,14 @@ xkb_compose_state_get_utf8(struct xkb_compose_state *state, const struct compose_node *node = &darray_item(state->table->nodes, state->context); - if (!node->is_leaf) - goto fail; + if (!node->is_leaf) { + if (node->internal.resid) { + node = &darray_item(state->table->nodes, + node->internal.resid); + } else { + goto fail; + } + } /* If there's no string specified, but only a keysym, try to do the * most helpful thing. */ @@ -195,7 +205,12 @@ xkb_compose_state_get_one_sym(struct xkb_compose_state *state) { const struct compose_node *node = &darray_item(state->table->nodes, state->context); - if (!node->is_leaf) + if (node->is_leaf) { + return node->leaf.keysym; + } else if (node->internal.resid) { + return darray_item(state->table->nodes, + node->internal.resid).leaf.keysym; + } else { return XKB_KEY_NoSymbol; - return node->leaf.keysym; + } } diff --git a/src/compose/table.c b/src/compose/table.c index 04fa8cb74..522858570 100644 --- a/src/compose/table.c +++ b/src/compose/table.c @@ -60,8 +60,8 @@ xkb_compose_table_new(struct xkb_context *ctx, darray_init(table->utf8); dummy.keysym = XKB_KEY_NoSymbol; - dummy.leaf.is_leaf = true; dummy.leaf.utf8 = 0; + dummy.leaf.is_leaf = true; dummy.leaf.keysym = XKB_KEY_NoSymbol; darray_append(table->nodes, dummy); @@ -89,6 +89,8 @@ xkb_compose_table_unref(struct xkb_compose_table *table) free(table); } +#define XKB_COMPOSE_COMPILE_MASK XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES + XKB_EXPORT struct xkb_compose_table * xkb_compose_table_new_from_file(struct xkb_context *ctx, FILE *file, @@ -99,7 +101,7 @@ xkb_compose_table_new_from_file(struct xkb_context *ctx, struct xkb_compose_table *table; bool ok; - if (flags & ~(XKB_COMPOSE_COMPILE_NO_FLAGS)) { + if (flags & ~(XKB_COMPOSE_COMPILE_MASK)) { log_err_func(ctx, "unrecognized flags: %#x\n", flags); return NULL; } @@ -132,7 +134,7 @@ xkb_compose_table_new_from_buffer(struct xkb_context *ctx, struct xkb_compose_table *table; bool ok; - if (flags & ~(XKB_COMPOSE_COMPILE_NO_FLAGS)) { + if (flags & ~(XKB_COMPOSE_COMPILE_MASK)) { log_err_func(ctx, "unrecognized flags: %#x\n", flags); return NULL; } @@ -165,7 +167,7 @@ xkb_compose_table_new_from_locale(struct xkb_context *ctx, FILE *file; bool ok; - if (flags & ~(XKB_COMPOSE_COMPILE_NO_FLAGS)) { + if (flags & ~(XKB_COMPOSE_COMPILE_MASK)) { log_err_func(ctx, "unrecognized flags: %#x\n", flags); return NULL; } @@ -253,13 +255,18 @@ xkb_compose_table_entry_utf8(struct xkb_compose_table_entry *entry) enum node_direction { NODE_LEFT = 0, NODE_DOWN, + NODE_DOWN_NON_OVERLAPPING, NODE_RIGHT, NODE_UP }; +#define NODE_OFFSET_BIT_FIELD_SIZE 29 +_Static_assert(MAX_COMPOSE_NODES_LOG2 + 1 <= NODE_OFFSET_BIT_FIELD_SIZE, + "xkb_compose_table_iterator_cursor::node_offset cannot hold MAX_COMPOSE_NODES"); + 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 + uint32_t node_offset:NODE_OFFSET_BIT_FIELD_SIZE; + uint8_t direction:3; /* enum node_direction: current direction * traversing the tree */ }; @@ -347,26 +354,45 @@ xkb_compose_table_iterator_next(struct xkb_compose_table_iterator *iter) case NODE_LEFT: cursor->direction = NODE_DOWN; if (node->lokid) { - struct xkb_compose_table_iterator_cursor new_cursor = {node->lokid, NODE_LEFT}; + struct xkb_compose_table_iterator_cursor new_cursor = { + .node_offset = node->lokid, + .direction = 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) { + cursor->direction = NODE_RIGHT; + iter->entry.keysym = node->leaf.keysym; + iter->entry.utf8 = &darray_item(iter->table->utf8, node->leaf.utf8); + return &iter->entry; + } else if (node->internal.resid) { + cursor->direction = NODE_DOWN_NON_OVERLAPPING; + node = &darray_item(iter->table->nodes, node->internal.resid); 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}; +down: + cursor->direction = NODE_RIGHT; + struct xkb_compose_table_iterator_cursor new_cursor = { + .node_offset = node->internal.eqkid, + .direction = NODE_LEFT + }; darray_append(iter->cursors, new_cursor); } break; + case NODE_DOWN_NON_OVERLAPPING: + assert (iter->entry.sequence_length <= MAX_LHS_LEN); + iter->entry.sequence[iter->entry.sequence_length] = node->keysym; + goto down; + case NODE_RIGHT: cursor->direction = NODE_UP; iter->entry.sequence_length--; diff --git a/src/compose/table.h b/src/compose/table.h index f6904a1c2..72ba88da0 100644 --- a/src/compose/table.h +++ b/src/compose/table.h @@ -77,23 +77,26 @@ /* 7 nodes for every potential Unicode character and then some should be * enough for all purposes. */ -#define MAX_COMPOSE_NODES (1 << 23) +#define MAX_COMPOSE_NODES_LOG2 23 +#define MAX_COMPOSE_NODES (1 << MAX_COMPOSE_NODES_LOG2) struct compose_node { xkb_keysym_t keysym; - /* Offset into xkb_compose_table::nodes or 0. */ + /* Low sibling keysym. Offset into xkb_compose_table::nodes or 0. */ uint32_t lokid; - /* Offset into xkb_compose_table::nodes or 0. */ + /* High sibling keysym. Offset into xkb_compose_table::nodes or 0. */ uint32_t hikid; union { struct { - uint32_t _pad:31; + uint32_t _pad1:31; bool is_leaf:1; + uint32_t _pad2; }; struct { - uint32_t _pad:31; + /* Overlapping result. Offset into xkb_compose_table::nodes or 0. */ + uint32_t resid:31; bool is_leaf:1; /* Offset into xkb_compose_table::nodes or 0. */ uint32_t eqkid; diff --git a/test/compose.c b/test/compose.c index 56bd889c0..9fd2094c2 100644 --- a/test/compose.c +++ b/test/compose.c @@ -35,6 +35,10 @@ compose_status_string(enum xkb_compose_status status) return "nothing"; case XKB_COMPOSE_COMPOSING: return "composing"; + case XKB_COMPOSE_CANDIDATE: + return "candidate"; + case XKB_COMPOSE_CANDIDATE_ACCEPTED: + return "candidate accepted"; case XKB_COMPOSE_COMPOSED: return "composed"; case XKB_COMPOSE_CANCELLED: @@ -156,14 +160,15 @@ test_compose_seq(struct xkb_compose_table *table, ...) } static bool -test_compose_seq_buffer(struct xkb_context *ctx, const char *buffer, ...) +test_compose_seq_buffer(struct xkb_context *ctx, + enum xkb_compose_compile_flags flags, + const char *buffer, ...) { va_list ap; bool ok; struct xkb_compose_table *table; table = xkb_compose_table_new_from_buffer(ctx, buffer, strlen(buffer), "", - XKB_COMPOSE_FORMAT_TEXT_V1, - XKB_COMPOSE_COMPILE_NO_FLAGS); + XKB_COMPOSE_FORMAT_TEXT_V1, flags); assert(table); va_start(ap, buffer); ok = test_compose_seq_va(table, ap); @@ -176,7 +181,7 @@ static void test_compose_utf8_bom(struct xkb_context *ctx) { const char buffer[] = "\xef\xbb\xbf : X"; - assert(test_compose_seq_buffer(ctx, buffer, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, buffer, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "X", XKB_KEY_X, XKB_KEY_NoSymbol)); } @@ -188,7 +193,7 @@ test_invalid_encodings(struct xkb_context *ctx) /* ISO 8859-1 (latin1) */ const char iso_8859_1[] = " : \"\xe1\" acute"; - assert(!test_compose_seq_buffer(ctx, iso_8859_1, + assert(!test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, iso_8859_1, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "\xc3\xa1", XKB_KEY_acute, XKB_KEY_NoSymbol)); @@ -328,7 +333,7 @@ test_seqs(struct xkb_context *ctx) xkb_compose_table_unref(table); /* Make sure one-keysym sequences work. */ - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, " : \"foo\" X \n" " : \"baz\" Y \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_X, @@ -339,7 +344,7 @@ test_seqs(struct xkb_context *ctx) XKB_KEY_NoSymbol)); /* No sequences at all. */ - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, "", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, @@ -349,7 +354,7 @@ test_seqs(struct xkb_context *ctx) XKB_KEY_NoSymbol)); /* Only keysym - string derived from keysym. */ - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, " : X \n" " : dollar \n" " : dead_acute \n", @@ -361,7 +366,7 @@ test_seqs(struct xkb_context *ctx) XKB_KEY_NoSymbol)); /* Make sure a cancelling keysym doesn't start a new sequence. */ - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, " : X \n" " : Y \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, @@ -375,10 +380,10 @@ test_seqs(struct xkb_context *ctx) } static void -test_conflicting(struct xkb_context *ctx) +test_conflicting_without_overlapping(struct xkb_context *ctx) { // new is prefix of old - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, " : \"foo\" A \n" " : \"bar\" B \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, @@ -387,7 +392,7 @@ test_conflicting(struct xkb_context *ctx) XKB_KEY_NoSymbol)); // old is a prefix of new - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, " : \"bar\" B \n" " : \"foo\" A \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, @@ -396,7 +401,77 @@ test_conflicting(struct xkb_context *ctx) XKB_KEY_NoSymbol)); // new duplicate of old - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, + " : \"bar\" B \n" + " : \"bar\" B \n", + XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_B, + XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, + XKB_KEY_NoSymbol)); + + // new same length as old #1 + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, + " : \"foo\" A \n" + " : \"bar\" B \n", + XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_B, + XKB_KEY_NoSymbol)); + + // new same length as old #2 + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, + " : \"foo\" A \n" + " : \"foo\" B \n", + XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_B, + XKB_KEY_NoSymbol)); + + // new same length as old #3 + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, + " : \"foo\" A \n" + " : \"bar\" A \n", + XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_A, + XKB_KEY_NoSymbol)); + + // overlapping tests + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, + " : \"1\" X \n" + " : \"foo\" A \n" + " : \"bar\" B \n" + " : \"2\" Y \n" + " : \"baz\" C \n" + " : \"foo\" D \n", + XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_D, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_E, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_D, + XKB_KEY_NoSymbol)); +} + +static void +test_conflicting_with_overlapping(struct xkb_context *ctx) +{ + // new is prefix of old + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES, + " : \"foo\" A \n" + " : \"bar\" B \n", + XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE, "bar", XKB_KEY_B, + XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_A, + XKB_KEY_NoSymbol)); + + // old is a prefix of new + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES, + " : \"bar\" B \n" + " : \"foo\" A \n", + XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE, "bar", XKB_KEY_B, + XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_A, + XKB_KEY_NoSymbol)); + + // new duplicate of old + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES, " : \"bar\" B \n" " : \"bar\" B \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, @@ -405,7 +480,7 @@ test_conflicting(struct xkb_context *ctx) XKB_KEY_NoSymbol)); // new same length as old #1 - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES, " : \"foo\" A \n" " : \"bar\" B \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, @@ -413,7 +488,7 @@ test_conflicting(struct xkb_context *ctx) XKB_KEY_NoSymbol)); // new same length as old #2 - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES, " : \"foo\" A \n" " : \"foo\" B \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, @@ -421,12 +496,27 @@ test_conflicting(struct xkb_context *ctx) XKB_KEY_NoSymbol)); // new same length as old #3 - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES, " : \"foo\" A \n" " : \"bar\" A \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_A, XKB_KEY_NoSymbol)); + + // overlapping tests + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES, + " : \"1\" X \n" // #1: overriden by #4 + " : \"foo\" A \n" // #2: adds overlapping #1 + " : \"bar\" B \n" // #3: adds overlapping; overriden by #5 + " : \"2\" Y \n" // #4: overrides #1 + " : \"baz\" C \n" // #5: overrides #3 + " : \"foo\" D \n", // #6: adds overlapping #2 + XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE, "2", XKB_KEY_Y, + XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE, "baz", XKB_KEY_C, + XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE, "foo", XKB_KEY_A, + XKB_KEY_D, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_E, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_D, + XKB_KEY_NoSymbol)); } static void @@ -563,7 +653,7 @@ test_modifier_syntax(struct xkb_context *ctx) /* We don't do anything with the modifiers, but make sure we can parse * them. */ - assert(test_compose_seq_buffer(ctx, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, "None : X \n" "Shift : Y \n" "Ctrl : Y \n" @@ -625,7 +715,7 @@ test_include(struct xkb_context *ctx) " : \"bar\" Y\n", path); assert(table_string); - assert(test_compose_seq_buffer(ctx, table_string, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, table_string, /* No conflict. */ XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "´", XKB_KEY_acute, @@ -649,17 +739,55 @@ test_override(struct xkb_context *ctx) { const char *table_string = " : \"foo\" X\n" " : \"bar\" Y\n" - " : \"baz\" Z\n"; + " : \"baz\" Z\n" + " : \"foo\" A\n" + " : \"bar\" B\n"; + + /* Without overlapping */ + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, table_string, + /* Comes after - does override. */ + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "baz", XKB_KEY_Z, + + /* Override does not affect sibling nodes */ + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_Y, - assert(test_compose_seq_buffer(ctx, table_string, /* Comes after - does override. */ - XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, - XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, - XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "baz", XKB_KEY_Z, + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_g, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_A, + + /* Can cancel after candidate. */ + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANCELLED, "", XKB_KEY_NoSymbol, + XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, + + XKB_KEY_NoSymbol)); + + /* With overlapping */ + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES, table_string, + /* Comes after - does override. */ + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE, "foo", XKB_KEY_X, + XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "baz", XKB_KEY_Z, /* Override does not affect sibling nodes */ - XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, - XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_Y, + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_Y, + + /* Comes after - does override. */ + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE, "bar", XKB_KEY_B, + XKB_KEY_g, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_A, + + /* Can cancel after candidate. */ + XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, + XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE, "bar", XKB_KEY_B, + XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANDIDATE_ACCEPTED, "", XKB_KEY_NoSymbol, + XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_NoSymbol)); } @@ -777,7 +905,7 @@ test_escape_sequences(struct xkb_context *ctx) */ const char *table_string = " : \"\\401f\\x0o\\0o\" X\n"; - assert(test_compose_seq_buffer(ctx, table_string, + assert(test_compose_seq_buffer(ctx, XKB_COMPOSE_COMPILE_NO_FLAGS, table_string, XKB_KEY_o, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_X, XKB_KEY_NoSymbol)); @@ -810,7 +938,8 @@ main(int argc, char *argv[]) test_compose_utf8_bom(ctx); test_invalid_encodings(ctx); test_seqs(ctx); - test_conflicting(ctx); + test_conflicting_without_overlapping(ctx); + test_conflicting_with_overlapping(ctx); test_XCOMPOSEFILE(ctx); test_from_locale(ctx); test_state(ctx); diff --git a/tools/interactive-evdev.c b/tools/interactive-evdev.c index 66bc18cfb..d7d2c0db2 100644 --- a/tools/interactive-evdev.c +++ b/tools/interactive-evdev.c @@ -57,6 +57,7 @@ static bool terminate; static int evdev_offset = 8; static bool report_state_changes; static bool with_compose; +static bool compose_overlapping; static enum xkb_consumed_mode consumed_mode = XKB_CONSUMED_MODE_XKB; #ifdef ENABLE_PRIVATE_APIS @@ -287,7 +288,9 @@ process_event(struct keyboard *kbd, uint16_t type, uint16_t code, int32_t value) if (with_compose) { status = xkb_compose_state_get_status(kbd->compose_state); - if (status == XKB_COMPOSE_CANCELLED || status == XKB_COMPOSE_COMPOSED) + if (status == XKB_COMPOSE_CANCELLED || + status == XKB_COMPOSE_COMPOSED || + status == XKB_COMPOSE_CANDIDATE_ACCEPTED) xkb_compose_state_reset(kbd->compose_state); } @@ -389,6 +392,7 @@ usage(FILE *fp, char *progname) " --short (do not print layout nor Unicode keysym translation)\n" " --report-state-changes (report changes to the state)\n" " --enable-compose (enable Compose)\n" + " --enable-compose-overlapping (enable Compose overlapping sequences)\n" " --consumed-mode={xkb|gtk} (select the consumed modifiers mode, default: xkb)\n" " --without-x11-offset (don't add X11 keycode offset)\n" "Other:\n" @@ -426,6 +430,7 @@ main(int argc, char *argv[]) OPT_WITHOUT_X11_OFFSET, OPT_CONSUMED_MODE, OPT_COMPOSE, + OPT_COMPOSE_OVERLAPPING, OPT_SHORT, OPT_REPORT_STATE, #ifdef ENABLE_PRIVATE_APIS @@ -444,6 +449,7 @@ main(int argc, char *argv[]) {"keymap", required_argument, 0, OPT_KEYMAP}, {"consumed-mode", required_argument, 0, OPT_CONSUMED_MODE}, {"enable-compose", no_argument, 0, OPT_COMPOSE}, + {"enable-compose-overlapping", no_argument, 0, OPT_COMPOSE_OVERLAPPING}, {"short", no_argument, 0, OPT_SHORT}, {"report-state-changes", no_argument, 0, OPT_REPORT_STATE}, {"without-x11-offset", no_argument, 0, OPT_WITHOUT_X11_OFFSET}, @@ -505,6 +511,9 @@ main(int argc, char *argv[]) case OPT_COMPOSE: with_compose = true; break; + case OPT_COMPOSE_OVERLAPPING: + compose_overlapping = true; + break; case OPT_SHORT: print_fields &= ~PRINT_VERBOSE_FIELDS; break; @@ -591,9 +600,13 @@ main(int argc, char *argv[]) if (with_compose) { locale = setlocale(LC_CTYPE, NULL); + enum xkb_compose_compile_flags compose_compile_flags = + compose_overlapping + ? XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES + : XKB_COMPOSE_COMPILE_NO_FLAGS; compose_table = xkb_compose_table_new_from_locale(ctx, locale, - XKB_COMPOSE_COMPILE_NO_FLAGS); + compose_compile_flags); if (!compose_table) { fprintf(stderr, "Couldn't create compose from locale\n"); goto out; diff --git a/tools/interactive-wayland.c b/tools/interactive-wayland.c index bca33f86b..5282045cf 100644 --- a/tools/interactive-wayland.c +++ b/tools/interactive-wayland.c @@ -416,7 +416,9 @@ kbd_key(void *data, struct wl_keyboard *wl_kbd, uint32_t serial, uint32_t time, if (seat->compose_state) { enum xkb_compose_status status = xkb_compose_state_get_status(seat->compose_state); - if (status == XKB_COMPOSE_CANCELLED || status == XKB_COMPOSE_COMPOSED) + if (status == XKB_COMPOSE_CANCELLED || + status == XKB_COMPOSE_COMPOSED || + status == XKB_COMPOSE_CANDIDATE_ACCEPTED) xkb_compose_state_reset(seat->compose_state); } @@ -702,11 +704,12 @@ static void usage(FILE *fp, char *progname) { fprintf(fp, - "Usage: %s [--help] [--enable-compose]\n", + "Usage: %s [--help] [--enable-compose] [--enable-compose-overlapping]\n", progname); fprintf(fp, - " --enable-compose enable Compose\n" - " --help display this help and exit\n" + " --enable-compose enable Compose\n" + " --enable-compose-overlapping enable Compose overlapping sequences\n" + " --help display this help and exit\n" ); } @@ -720,12 +723,15 @@ main(int argc, char *argv[]) struct xkb_compose_table *compose_table = NULL; bool with_compose = false; + bool compose_overlapping = false; enum options { OPT_COMPOSE, + OPT_COMPOSE_OVERLAPPING, }; static struct option opts[] = { - {"help", no_argument, 0, 'h'}, - {"enable-compose", no_argument, 0, OPT_COMPOSE}, + {"help", no_argument, 0, 'h'}, + {"enable-compose", no_argument, 0, OPT_COMPOSE}, + {"enable-compose-overlapping", no_argument, 0, OPT_COMPOSE_OVERLAPPING}, {0, 0, 0, 0}, }; @@ -741,6 +747,9 @@ main(int argc, char *argv[]) case OPT_COMPOSE: with_compose = true; break; + case OPT_COMPOSE_OVERLAPPING: + compose_overlapping = true; + break; case 'h': usage(stdout, argv[0]); return EXIT_SUCCESS; @@ -771,9 +780,13 @@ main(int argc, char *argv[]) if (with_compose) { locale = setlocale(LC_CTYPE, NULL); + enum xkb_compose_compile_flags compose_compile_flags = + compose_overlapping + ? XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES + : XKB_COMPOSE_COMPILE_NO_FLAGS; compose_table = xkb_compose_table_new_from_locale(inter.ctx, locale, - XKB_COMPOSE_COMPILE_NO_FLAGS); + compose_compile_flags); if (!compose_table) { fprintf(stderr, "Couldn't create compose from locale\n"); goto err_compose; diff --git a/tools/interactive-x11.c b/tools/interactive-x11.c index 0ab1898db..81f186a54 100644 --- a/tools/interactive-x11.c +++ b/tools/interactive-x11.c @@ -264,7 +264,8 @@ process_event(xcb_generic_event_t *gevent, struct keyboard *kbd) if (kbd->compose_state) { enum xkb_compose_status status = xkb_compose_state_get_status(kbd->compose_state); if (status == XKB_COMPOSE_CANCELLED || - status == XKB_COMPOSE_COMPOSED) + status == XKB_COMPOSE_COMPOSED || + status == XKB_COMPOSE_CANDIDATE_ACCEPTED) xkb_compose_state_reset(kbd->compose_state); } @@ -354,10 +355,11 @@ static void usage(FILE *fp, char *progname) { fprintf(fp, - "Usage: %s [--help] [--enable-compose]\n", + "Usage: %s [--help] [--enable-compose] [--enable-compose-overlapping]\n", progname); fprintf(fp, " --enable-compose enable Compose\n" + " --enable-compose-overlapping enable Compose overlapping sequences\n" " --help display this help and exit\n" ); } @@ -375,12 +377,15 @@ main(int argc, char *argv[]) struct xkb_compose_table *compose_table = NULL; bool with_compose = false; + bool compose_overlapping = false; enum options { OPT_COMPOSE, + OPT_COMPOSE_OVERLAPPING, }; static struct option opts[] = { {"help", no_argument, 0, 'h'}, {"enable-compose", no_argument, 0, OPT_COMPOSE}, + {"enable-compose-overlapping", no_argument, 0, OPT_COMPOSE_OVERLAPPING}, {0, 0, 0, 0}, }; @@ -396,6 +401,9 @@ main(int argc, char *argv[]) case OPT_COMPOSE: with_compose = true; break; + case OPT_COMPOSE_OVERLAPPING: + compose_overlapping = true; + break; case 'h': usage(stdout, argv[0]); return EXIT_SUCCESS; @@ -434,9 +442,12 @@ main(int argc, char *argv[]) if (with_compose) { locale = setlocale(LC_CTYPE, NULL); + enum xkb_compose_compile_flags compose_flags = + compose_overlapping + ? XKB_COMPOSE_COMPILE_OVERLAPPING_SEQUENCES + : XKB_COMPOSE_COMPILE_NO_FLAGS; compose_table = - xkb_compose_table_new_from_locale(ctx, locale, - XKB_COMPOSE_COMPILE_NO_FLAGS); + xkb_compose_table_new_from_locale(ctx, locale, compose_flags); if (!compose_table) { fprintf(stderr, "Couldn't create compose from locale\n"); goto err_compose; diff --git a/tools/tools-common.c b/tools/tools-common.c index b163438fe..3dab32e3c 100644 --- a/tools/tools-common.c +++ b/tools/tools-common.c @@ -173,7 +173,7 @@ tools_print_keycode_state(char *prefix, if (status == XKB_COMPOSE_COMPOSING || status == XKB_COMPOSE_CANCELLED) return; - if (status == XKB_COMPOSE_COMPOSED) { + if (status == XKB_COMPOSE_COMPOSED || status == XKB_COMPOSE_CANDIDATE) { sym = xkb_compose_state_get_one_sym(compose_state); syms = &sym; nsyms = 1; @@ -202,7 +202,7 @@ tools_print_keycode_state(char *prefix, printf("] "); if (fields & PRINT_UNICODE) { - if (status == XKB_COMPOSE_COMPOSED) + if (status == XKB_COMPOSE_COMPOSED || status == XKB_COMPOSE_CANDIDATE) xkb_compose_state_get_utf8(compose_state, s, sizeof(s)); else xkb_state_key_get_utf8(state, keycode, s, sizeof(s)); diff --git a/tools/xkbcli-interactive-evdev.1 b/tools/xkbcli-interactive-evdev.1 index 167e3ec65..aede3a752 100644 --- a/tools/xkbcli-interactive-evdev.1 +++ b/tools/xkbcli-interactive-evdev.1 @@ -77,6 +77,9 @@ Report changes to the keyboard state .It Fl \-enable\-compose Enable Compose functionality . +.It Fl \-enable\-compose\-overlapping +Allow Compose to handle overlapping sequences +. .It Fl \-consumed\-mode Brq xkb|gtk Set the consumed modifiers mode (default: xkb) . diff --git a/tools/xkbcli-interactive-wayland.1 b/tools/xkbcli-interactive-wayland.1 index 0542e07d8..80bfffc17 100644 --- a/tools/xkbcli-interactive-wayland.1 +++ b/tools/xkbcli-interactive-wayland.1 @@ -31,6 +31,9 @@ Print help and exit . .It Fl \-enable\-compose Enable Compose functionality +. +.It Fl \-enable\-compose\-overlapping +Allow Compose to handle overlapping sequences .El . .Sh SEE ALSO diff --git a/tools/xkbcli-interactive-x11.1 b/tools/xkbcli-interactive-x11.1 index dbbd25b34..ff740007e 100644 --- a/tools/xkbcli-interactive-x11.1 +++ b/tools/xkbcli-interactive-x11.1 @@ -31,6 +31,9 @@ Print help and exit . .It Fl \-enable\-compose Enable Compose functionality +. +.It Fl \-enable\-compose\-overlapping +Allow Compose to handle overlapping sequences .El . .Sh SEE ALSO