Issue #947: Improved layout handling and Unicode -> X11 keysyms translation

This commit is contained in:
Maxim Devaev 2023-03-23 11:50:22 +02:00
parent 22db176ef0
commit 26238e241e
4 changed files with 64 additions and 20 deletions

View File

@ -62,6 +62,7 @@ depends=(
python-pam python-pam
"python-pillow>=8.3.1-1" "python-pillow>=8.3.1-1"
python-xlib python-xlib
libxkbcommon
python-hidapi python-hidapi
python-six python-six
python-pyrad python-pyrad

View File

@ -41,15 +41,19 @@ class SymmapModifiers:
CTRL: int = 0x4 CTRL: int = 0x4
def build_symmap(path: str) -> dict[int, dict[int, str]]: def build_symmap(path: str) -> dict[int, dict[int, str]]: # x11 keysym -> [(modifiers, webkey), ...]
# https://github.com/qemu/qemu/blob/95a9457fd44ad97c518858a4e1586a5498f9773c/ui/keymaps.c # https://github.com/qemu/qemu/blob/95a9457fd44ad97c518858a4e1586a5498f9773c/ui/keymaps.c
logger = get_logger() logger = get_logger()
symmap: dict[int, dict[int, str]] = {} symmap: dict[int, dict[int, str]] = {}
for (src, items) in [ for (src, items) in [
("<builtin>", list(X11_TO_AT1.items())),
(path, list(_read_keyboard_layout(path).items())), (path, list(_read_keyboard_layout(path).items())),
("<builtin>", list(X11_TO_AT1.items())),
]: ]:
# Пока лучшая логика - самые первые записи в файле раскладки
# должны иметь приоритет над следующими, а дефолтный маппинг
# только дополняет отсутствующие значения.
for (code, keys) in items: for (code, keys) in items:
for key in keys: for key in keys:
web_name = AT1_TO_WEB.get(key.code) web_name = AT1_TO_WEB.get(key.code)
@ -62,14 +66,15 @@ def build_symmap(path: str) -> dict[int, dict[int, str]]:
logger.error("Invalid modifier key at mapping %s: %s / %s", src, web_name, key) logger.error("Invalid modifier key at mapping %s: %s / %s", src, web_name, key)
continue continue
if code not in symmap: modifiers = (
symmap[code] = {}
symmap[code][
0 0
| (SymmapModifiers.SHIFT if key.shift else 0) | (SymmapModifiers.SHIFT if key.shift else 0)
| (SymmapModifiers.ALTGR if key.altgr else 0) | (SymmapModifiers.ALTGR if key.altgr else 0)
| (SymmapModifiers.CTRL if key.ctrl else 0) | (SymmapModifiers.CTRL if key.ctrl else 0)
] = web_name )
if code not in symmap:
symmap[code] = {}
symmap[code].setdefault(modifiers, web_name)
return symmap return symmap

View File

@ -20,22 +20,50 @@
# ========================================================================== # # ========================================================================== #
import ctypes
import ctypes.util
from typing import Generator from typing import Generator
from .keysym import SymmapModifiers from .keysym import SymmapModifiers
from .mappings import WebModifiers from .mappings import WebModifiers
# =====
def _load_libxkbcommon() -> ctypes.CDLL:
path = ctypes.util.find_library("xkbcommon")
if not path:
raise RuntimeError("Where is libxkbcommon?")
assert path
lib = ctypes.CDLL(path)
for (name, restype, argtypes) in [
("xkb_utf32_to_keysym", ctypes.c_uint32, [ctypes.c_uint32]),
]:
func = getattr(lib, name)
if not func:
raise RuntimeError(f"Where is libc.{name}?")
setattr(func, "restype", restype)
setattr(func, "argtypes", argtypes)
return lib
_libxkbcommon = _load_libxkbcommon()
def _ch_to_keysym(ch: str) -> int:
assert len(ch) == 1
return _libxkbcommon.xkb_utf32_to_keysym(ord(ch))
# ===== # =====
def text_to_web_keys( # pylint: disable=too-many-branches def text_to_web_keys( # pylint: disable=too-many-branches
text: str, text: str,
symmap: dict[int, dict[int, str]], symmap: dict[int, dict[int, str]],
shift_key: str=WebModifiers.SHIFT_LEFT,
) -> Generator[tuple[str, bool], None, None]: ) -> Generator[tuple[str, bool], None, None]:
assert shift_key in WebModifiers.SHIFTS shift = False
altgr = False
shifted = False
for ch in text: for ch in text:
# https://stackoverflow.com/questions/12343987/convert-ascii-character-to-x11-keycode # https://stackoverflow.com/questions/12343987/convert-ascii-character-to-x11-keycode
# https://www.ascii-code.com # https://www.ascii-code.com
@ -57,25 +85,34 @@ def text_to_web_keys( # pylint: disable=too-many-branches
if not ch.isprintable(): if not ch.isprintable():
continue continue
try: try:
keys = symmap[ord(ch)] keys = symmap[_ch_to_keysym(ch)]
except Exception: except Exception:
continue continue
for (modifiers, key) in reversed(keys.items()): for (modifiers, key) in keys.items():
if (modifiers & SymmapModifiers.ALTGR) or (modifiers & SymmapModifiers.CTRL): if modifiers & SymmapModifiers.CTRL:
# Not supported yet # Not supported yet
continue continue
if modifiers & SymmapModifiers.SHIFT and not shifted: if modifiers & SymmapModifiers.SHIFT and not shift:
yield (shift_key, True) yield (WebModifiers.SHIFT_LEFT, True)
shifted = True shift = True
elif not (modifiers & SymmapModifiers.SHIFT) and shifted: elif not (modifiers & SymmapModifiers.SHIFT) and shift:
yield (shift_key, False) yield (WebModifiers.SHIFT_LEFT, False)
shifted = False shift = False
if modifiers & SymmapModifiers.ALTGR and not altgr:
yield (WebModifiers.ALT_RIGHT, True)
altgr = True
elif not (modifiers & SymmapModifiers.ALTGR) and altgr:
yield (WebModifiers.ALT_RIGHT, False)
altgr = False
yield (key, True) yield (key, True)
yield (key, False) yield (key, False)
break break
if shifted: if shift:
yield (shift_key, False) yield (WebModifiers.SHIFT_LEFT, False)
if altgr:
yield (WebModifiers.ALT_RIGHT, False)

View File

@ -56,6 +56,7 @@ RUN pacman --noconfirm --ask=4 -Syy \
python-pam \ python-pam \
python-pillow \ python-pillow \
python-xlib \ python-xlib \
libxkbcommon \
python-hidapi \ python-hidapi \
python-zstandard \ python-zstandard \
freetype2 \ freetype2 \