Skip to content

Commit 7aa73be

Browse files
committed
Implement compose table iterator
Added in libxkbcommon-1.6.0
1 parent 1392163 commit 7aa73be

File tree

3 files changed

+108
-5
lines changed

3 files changed

+108
-5
lines changed

tests/test_xkb.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,9 +598,25 @@ class TestCompose(TestCase):
598598
@classmethod
599599
def setUpClass(cls):
600600
ctx = xkb.Context()
601-
ct = ctx.compose_table_new_from_buffer(
601+
cls.ct = ctx.compose_table_new_from_buffer(
602602
sample_compose_bytes, "en_US.UTF-8")
603-
cls.compose = ct.compose_state_new()
603+
cls.compose = cls.ct.compose_state_new()
604+
605+
def test_compose_table_iterator(self):
606+
# Our sample compose table has 5125 entries
607+
self.assertEqual(len(list(self.ct)), 5125)
608+
609+
# Check that we can find the compose sequence DEAD_TILDE,
610+
# DEAD_HORN, O and its results
611+
seq = (self.XKB_KEYSYM_DEAD_TILDE, self.XKB_KEYSYM_DEAD_HORN,
612+
self.XKB_KEYSYM_O)
613+
for te in self.ct:
614+
if te.sequence == seq:
615+
self.assertEqual(te.keysym, self.XKB_KEYSYM_OHORNTILDE)
616+
self.assertEqual(te.utf8, self.UTF8_OHORNTILDE)
617+
break
618+
else:
619+
self.fail("Did not find test sequence in compose table")
604620

605621
def test_compose_initial_status(self):
606622
self.compose.reset()

xkbcommon/ffi_build.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from cffi import FFI
22
ffibuilder = FFI()
33

4-
# Currently implemented with reference to libxkbcommon-1.5.0
4+
# Currently implemented with reference to libxkbcommon-1.6.0
55

66
ffibuilder.set_source("xkbcommon._ffi", """
77
#include <stdarg.h>
@@ -443,6 +443,29 @@
443443
void
444444
xkb_compose_table_unref(struct xkb_compose_table *table);
445445
446+
struct xkb_compose_table_entry;
447+
448+
const xkb_keysym_t *
449+
xkb_compose_table_entry_sequence(struct xkb_compose_table_entry *entry,
450+
size_t *sequence_length);
451+
452+
xkb_keysym_t
453+
xkb_compose_table_entry_keysym(struct xkb_compose_table_entry *entry);
454+
455+
const char *
456+
xkb_compose_table_entry_utf8(struct xkb_compose_table_entry *entry);
457+
458+
struct xkb_compose_table_iterator;
459+
460+
struct xkb_compose_table_iterator *
461+
xkb_compose_table_iterator_new(struct xkb_compose_table *table);
462+
463+
void
464+
xkb_compose_table_iterator_free(struct xkb_compose_table_iterator *iter);
465+
466+
struct xkb_compose_table_entry *
467+
xkb_compose_table_iterator_next(struct xkb_compose_table_iterator *iter);
468+
446469
enum xkb_compose_state_flags {
447470
XKB_COMPOSE_STATE_NO_FLAGS = ...
448471
};

xkbcommon/xkb.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import enum
22
import mmap
33
import sys
4+
from dataclasses import dataclass
5+
from typing import Tuple
46

57
from xkbcommon._ffi import ffi, lib
68

@@ -115,6 +117,11 @@ class XKBComposeTableCreationFailure(XKBError):
115117
pass
116118

117119

120+
class XKBComposeTableIteratorCreationFailure(XKBError):
121+
"""Unable to create a compose table iterator."""
122+
pass
123+
124+
118125
class XKBComposeStateCreationFailure(XKBError):
119126
"""Unable to create a compose state."""
120127
pass
@@ -1266,8 +1273,29 @@ def __init__(self, context, pointer, load_method):
12661273
self._table = ffi.gc(
12671274
pointer, _keepref(lib, lib.xkb_compose_table_unref))
12681275

1269-
# Methods to access and iterate over the compose table will be
1270-
# added for release 1.6
1276+
def __iter__(self):
1277+
local_lib = lib
1278+
iterator = local_lib.xkb_compose_table_iterator_new(self._table)
1279+
if not iterator:
1280+
raise XKBComposeTableIteratorCreationFailure()
1281+
# This allocates a single size_t
1282+
sequence_length_ptr = ffi.new("size_t *")
1283+
try:
1284+
while True:
1285+
entry = local_lib.xkb_compose_table_iterator_next(iterator)
1286+
if entry == ffi.NULL:
1287+
return
1288+
sequence = local_lib.xkb_compose_table_entry_sequence(
1289+
entry, sequence_length_ptr)
1290+
keysym = local_lib.xkb_compose_table_entry_keysym(entry)
1291+
utf8 = local_lib.xkb_compose_table_entry_utf8(entry)
1292+
yield ComposeTableEntry(
1293+
sequence=tuple(sequence[0:sequence_length_ptr[0]]),
1294+
keysym=keysym,
1295+
string=ffi.string(utf8).decode('utf8'))
1296+
finally:
1297+
del sequence_length_ptr
1298+
local_lib.xkb_compose_table_iterator_free(iterator)
12711299

12721300
def compose_state_new(self, flags=None):
12731301
pointer = lib.xkb_compose_state_new(self._table, flags if flags else 0)
@@ -1277,6 +1305,29 @@ def compose_state_new(self, flags=None):
12771305
return ComposeState(self, pointer)
12781306

12791307

1308+
@dataclass
1309+
class ComposeTableEntry:
1310+
"""A Compose table entry
1311+
1312+
Enables access to the left-hand keysym sequence, right-hand result
1313+
keysym and right-hand result string of a compose table entry.
1314+
1315+
Do not instantiate this object directly. Instead, obtain a compose
1316+
table iterator by calling iter() on a ComposeTable; the iterator
1317+
will yield ComposeTableEntry instances
1318+
"""
1319+
sequence: Tuple[int]
1320+
keysym: int
1321+
string: str
1322+
1323+
# Someone reading the libxkbcommon documentation may expect the
1324+
# right hand result string to be called "utf8". This is just an
1325+
# alias.
1326+
@property
1327+
def utf8(self):
1328+
return self.string
1329+
1330+
12801331
class ComposeState:
12811332
"""A Compose state object.
12821333
@@ -1366,6 +1417,19 @@ def get_utf8(self):
13661417
lib.xkb_compose_state_get_utf8(self._state, buffer, buffer_size)
13671418
return ffi.string(buffer).decode("utf8")
13681419

1420+
# This is an alias for get_utf8() — the name is more logical but
1421+
# may be unexpected to somebody just reading the libxkbcommon
1422+
# documentation
1423+
def get_string(self):
1424+
"""Get the result string for a compose sequence.
1425+
1426+
This function is only useful when the status is
1427+
ComposeStatus.XKB_COMPOSE_COMPOSED.
1428+
1429+
Returns string for composed sequence or empty string if not viable.
1430+
"""
1431+
return self.get_utf8()
1432+
13691433
def get_one_sym(self):
13701434
"""Get the result keysym for a composed sequence.
13711435

0 commit comments

Comments
 (0)