Skip to content

Commit

Permalink
Implement compose table iterator
Browse files Browse the repository at this point in the history
Added in libxkbcommon-1.6.0
  • Loading branch information
sde1000 committed Jul 5, 2024
1 parent 1392163 commit dee5cce
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 5 deletions.
20 changes: 18 additions & 2 deletions tests/test_xkb.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,9 +598,25 @@ class TestCompose(TestCase):
@classmethod
def setUpClass(cls):
ctx = xkb.Context()
ct = ctx.compose_table_new_from_buffer(
cls.ct = ctx.compose_table_new_from_buffer(
sample_compose_bytes, "en_US.UTF-8")
cls.compose = ct.compose_state_new()
cls.compose = cls.ct.compose_state_new()

def test_compose_table_iterator(self):
# Our sample compose table has 5125 entries
self.assertEqual(len(list(self.ct)), 5125)

# Check that we can find the compose sequence DEAD_TILDE,
# DEAD_HORN, O and its results
seq = (self.XKB_KEYSYM_DEAD_TILDE, self.XKB_KEYSYM_DEAD_HORN,
self.XKB_KEYSYM_O)
for te in self.ct:
if te.sequence == seq:
self.assertEqual(te.keysym, self.XKB_KEYSYM_OHORNTILDE)
self.assertEqual(te.utf8, self.UTF8_OHORNTILDE)
break
else:
self.fail("Did not find test sequence in compose table")

def test_compose_initial_status(self):
self.compose.reset()
Expand Down
25 changes: 24 additions & 1 deletion xkbcommon/ffi_build.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from cffi import FFI
ffibuilder = FFI()

# Currently implemented with reference to libxkbcommon-1.5.0
# Currently implemented with reference to libxkbcommon-1.6.0

ffibuilder.set_source("xkbcommon._ffi", """
#include <stdarg.h>
Expand Down Expand Up @@ -443,6 +443,29 @@
void
xkb_compose_table_unref(struct xkb_compose_table *table);
struct xkb_compose_table_entry;
const xkb_keysym_t *
xkb_compose_table_entry_sequence(struct xkb_compose_table_entry *entry,
size_t *sequence_length);
xkb_keysym_t
xkb_compose_table_entry_keysym(struct xkb_compose_table_entry *entry);
const char *
xkb_compose_table_entry_utf8(struct xkb_compose_table_entry *entry);
struct xkb_compose_table_iterator;
struct xkb_compose_table_iterator *
xkb_compose_table_iterator_new(struct xkb_compose_table *table);
void
xkb_compose_table_iterator_free(struct xkb_compose_table_iterator *iter);
struct xkb_compose_table_entry *
xkb_compose_table_iterator_next(struct xkb_compose_table_iterator *iter);
enum xkb_compose_state_flags {
XKB_COMPOSE_STATE_NO_FLAGS = ...
};
Expand Down
67 changes: 65 additions & 2 deletions xkbcommon/xkb.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import enum
import mmap
import sys
from dataclasses import dataclass

from xkbcommon._ffi import ffi, lib

Expand Down Expand Up @@ -115,6 +116,11 @@ class XKBComposeTableCreationFailure(XKBError):
pass


class XKBComposeTableIteratorCreationFailure(XKBError):
"""Unable to create a compose table iterator."""
pass


class XKBComposeStateCreationFailure(XKBError):
"""Unable to create a compose state."""
pass
Expand Down Expand Up @@ -1266,8 +1272,29 @@ def __init__(self, context, pointer, load_method):
self._table = ffi.gc(
pointer, _keepref(lib, lib.xkb_compose_table_unref))

# Methods to access and iterate over the compose table will be
# added for release 1.6
def __iter__(self):
local_lib = lib
iterator = local_lib.xkb_compose_table_iterator_new(self._table)
if not iterator:
raise XKBComposeTableIteratorCreationFailure()
# This allocates a single size_t
sequence_length_ptr = ffi.new("size_t *")
try:
while True:
entry = local_lib.xkb_compose_table_iterator_next(iterator)
if entry == ffi.NULL:
return
sequence = local_lib.xkb_compose_table_entry_sequence(
entry, sequence_length_ptr)
keysym = local_lib.xkb_compose_table_entry_keysym(entry)
utf8 = local_lib.xkb_compose_table_entry_utf8(entry)
yield ComposeTableEntry(
sequence=tuple(sequence[0:sequence_length_ptr[0]]),
keysym=keysym,
string=ffi.string(utf8).decode('utf8'))
finally:
del sequence_length_ptr
local_lib.xkb_compose_table_iterator_free(iterator)

def compose_state_new(self, flags=None):
pointer = lib.xkb_compose_state_new(self._table, flags if flags else 0)
Expand All @@ -1277,6 +1304,29 @@ def compose_state_new(self, flags=None):
return ComposeState(self, pointer)


@dataclass
class ComposeTableEntry:
"""A Compose table entry
Enables access to the left-hand keysym sequence, right-hand result
keysym and right-hand result string of a compose table entry.
Do not instantiate this object directly. Instead, obtain a compose
table iterator by calling iter() on a ComposeTable; the iterator
will yield ComposeTableEntry instances
"""
sequence: tuple[int]
keysym: int
string: str

# Someone reading the libxkbcommon documentation may expect the
# right hand result string to be called "utf8". This is just an
# alias.
@property
def utf8(self):
return self.string


class ComposeState:
"""A Compose state object.
Expand Down Expand Up @@ -1366,6 +1416,19 @@ def get_utf8(self):
lib.xkb_compose_state_get_utf8(self._state, buffer, buffer_size)
return ffi.string(buffer).decode("utf8")

# This is an alias for get_utf8() — the name is more logical but
# may be unexpected to somebody just reading the libxkbcommon
# documentation
def get_string(self):
"""Get the result string for a compose sequence.
This function is only useful when the status is
ComposeStatus.XKB_COMPOSE_COMPOSED.
Returns string for composed sequence or empty string if not viable.
"""
return self.get_utf8()

def get_one_sym(self):
"""Get the result keysym for a composed sequence.
Expand Down

0 comments on commit dee5cce

Please sign in to comment.