XkbLink
One connection's XKEYBOARD session: keysym placement and group control.
Opened through open_xkb_link; every method talks to the server on the
connection the link was opened on, so a group lock and the XTEST key that
follows it are processed in order without a round trip between them.
Attributes
attribute_d= xdisplayattribute_opcode= major_opcodeattributeevent_base= event_baseFirst event code of the extension on this connection.
attributecore_device= core_deviceXKB device id of the core keyboard, which the replacement notify (sent once per device) is filtered on.
attribute_placement= NoneKeysym to (keycode, group, level) at the lowest group,
then level, then keycode carrying it; None until a lookup needs
it.
Functions
func__init__(self, xdisplay, major_opcode, event_base, core_device) -> NoneSource Code
def __init__(self, xdisplay: Any, major_opcode: int, event_base: int,
core_device: int) -> None:
self._d = xdisplay
self._opcode = major_opcode
self.event_base = event_base
self.core_device = core_device
self._placement = NoneparamselfparamxdisplayAnyparammajor_opcodeintparamevent_baseintparamcore_deviceintReturns
Nonefuncinvalidate(self) -> NoneForget the map; the next lookup refetches it from the server.
Source Code
def invalidate(self) -> None:
"""Forget the map; the next lookup refetches it from the server."""
self._placement = NoneparamselfReturns
Nonefunclocate(self, keysym) -> Optional[Tuple[int, int, int]]Where the server's keymap puts a keysym.
Source Code
def locate(self, keysym: int) -> Optional[Tuple[int, int, int]]:
"""Where the server's keymap puts a keysym.
Returns:
`(keycode, group, level)` for the lowest group, then level, then
keycode carrying the keysym at an injectable level, or None when
the keymap does not carry it.
"""
if self._placement is None:
self._placement = self._fetch_placement()
return self._placement.get(keysym)paramselfparamkeysymintReturns
typing.Optional(keycode, group, level) for the lowest group, then level, then
func_fetch_placement(self) -> Dict[int, Tuple[int, int, int]]Fetch the XKB symbol map and index it by keysym.
Only the key symbol maps are requested: each is the key's group count
and width followed by width keysyms per group, which is all the
placement needs; the levels Shift and AltGr cannot reach are skipped.
Source Code
def _fetch_placement(self) -> Dict[int, Tuple[int, int, int]]:
"""Fetch the XKB symbol map and index it by keysym.
Only the key symbol maps are requested: each is the key's group count
and width followed by `width` keysyms per group, which is all the
placement needs; the levels Shift and AltGr cannot reach are skipped.
"""
info = self._d.display.info
lo, hi = info.min_keycode, info.max_keycode
reply = _XkbGetMap(
display=self._d.display, opcode=self._opcode, device_spec=XKB_USE_CORE_KBD,
full=0, partial=XKB_KEY_SYMS_MASK, first_type=0, n_types=0,
first_key_sym=lo, n_key_syms=hi - lo + 1, first_key_action=0,
n_key_actions=0, first_key_behavior=0, n_key_behaviors=0, virtual_mods=0,
first_key_explicit=0, n_key_explicit=0, first_mod_map_key=0,
n_mod_map_keys=0, first_vmod_map_key=0, n_vmod_map_keys=0)
blob = bytes(reply.map)
placement = {}
offset = 0
for keycode in range(reply.first_key_sym, reply.first_key_sym + reply.n_key_syms):
group_info, width, nsyms = struct.unpack_from('=xxxxBBH', blob, offset)
offset += 8
syms = struct.unpack_from('=%dI' % nsyms, blob, offset)
offset += 4 * nsyms
ngroups = group_info & 0x0F
for group in range(ngroups):
for level in range(min(width, XKB_INJECTABLE_LEVELS)):
index = group * width + level
sym = syms[index] if index < nsyms else 0
if not sym:
continue
have = placement.get(sym)
if have is None or (group, level, keycode) < (have[1], have[2], have[0]):
placement[sym] = (keycode, group, level)
return placementparamselfReturns
typing.Dict[int, typing.Tuple[int, int, int]]funclocked_group(self) -> intThe group the server currently has locked for the core keyboard.
Source Code
def locked_group(self) -> int:
"""The group the server currently has locked for the core keyboard."""
reply = _XkbGetState(display=self._d.display, opcode=self._opcode,
device_spec=XKB_USE_CORE_KBD)
return int(reply.locked_group)paramselfReturns
intfunclock_group(self, group) -> NoneLock the core keyboard's group; queued, not flushed, so the caller's next XTEST key follows it in the same write.
Source Code
def lock_group(self, group: int) -> None:
"""Lock the core keyboard's group; queued, not flushed, so the caller's
next XTEST key follows it in the same write."""
_XkbLatchLockState(
display=self._d.display, onerror=_log_x_error, opcode=self._opcode,
device_spec=XKB_USE_CORE_KBD, affect_mod_locks=0, mod_locks=0,
lock_group=1, group_lock=group, affect_mod_latches=0, mod_latches=0,
latch_group=0, group_latch=0)paramselfparamgroupintReturns
Nonefuncreplaced_keyboard(self, event) -> Optional[Tuple[int, int]]Recognise the XkbNewKeyboardNotify a whole-keyboard replacement sends.
The server emits one per device; only the core keyboard's counts, so a layout switch is handled once.
Source Code
def replaced_keyboard(self, event: Any) -> Optional[Tuple[int, int]]:
"""Recognise the XkbNewKeyboardNotify a whole-keyboard replacement sends.
The server emits one per device; only the core keyboard's counts, so a
layout switch is handled once.
Returns:
`(min_keycode, max_keycode)` of the new keyboard, or None when the
event is something else.
"""
if event.type != self.event_base or getattr(event, 'detail', None) != XKB_NEW_KEYBOARD_NOTIFY:
return None
data = bytes(event.data)
if len(data) < 8:
return None
_time, device, _old_device, lo, hi = struct.unpack_from('=IBBBB', data, 0)
if device != self.core_device:
return None
return lo, hiparamselfparameventAnyReturns
typing.Optional(min_keycode, max_keycode) of the new keyboard, or None when the