spiegel-keyman/linux/keyman-config/keyman_config/kvk2ldml.py
2022-07-18 15:19:50 +02:00

407 lines
13 KiB
Python
Executable file

#!/usr/bin/python3
import logging
import struct
import sys
from lxml import etree
# .kvk file format
# KVK files are variable length files with variable sized structures.
# Magic 4 bytes 'KVKF'
# Version 4 bytes 0x600
# Flags 1 byte bitmask: [102key?, DisplayUnderlying?, UseUnderlying?, AltGr?]
kvkk102key = b'\x01'
kvkkDisplayUnderlying = b'\x02'
kvkkUseUnderlying = b'\x04'
kvkkAltGr = b'\x08'
# AssociatedKeyboard NSTRING
# AnsiFont NFONT
# UnicodeFont NFONT
# KeyCount: DWORD
# Keys: NKEY[KeyCount]
class KVKData:
magic = ""
version = None
flags = 0
key102 = False
DisplayUnderlying = False
UseUnderlying = False
AltGr = False
AssociatedKeyboard = ""
AnsiFont = None
UnicodeFont = None
KeyCount = 0
Keys = []
# NSTRING = (Length: Word; Chars: WChar[Length])
# NFONT = (Name: NSTRING; Size: DWORD; Color: DWORD (RGBQuad))
class NFont:
name = ""
size = 0
red = 0
green = 0
blue = 0
resv = 0
# NKEY = (
# Flags: BYTE; // 1:kvkkBitmap, 2:kvkkUnicode
kvkkBitmap = b'\x01'
kvkkUnicode = b'\x02'
# Shift: WORD; // See KVKS_* below
# VKey: WORD;
# Text: NSTRING;
# Bitmap: NBITMAP
# )
# NBITMAP = (BitmapSize: DWORD; Bitmap: BYTE[BitmapSize])
class NKey:
number = 0
flags = 0
hasBitmap = False
hasUnicode = False
shiftflags = 0
Normal = False
Shift = False
Ctrl = False
Alt = False
LCtrl = False
RCtrl = False
LAlt = False
RAlt = False
VKey = 0
text = ""
bitmap = None
# // Note that these differ from the KMX modifier bitmasks
# KVKS_NORMAL = 0;
# KVKS_SHIFT = 1;
# KVKS_CTRL = 2;
# KVKS_ALT = 4;
# KVKS_LCTRL = 8;
# KVKS_RCTRL = 16;
# KVKS_LALT = 32;
# KVKS_RALT = 64;
KVKS_NORMAL = b'\x00'
KVKS_SHIFT = b'\x01'
KVKS_CTRL = b'\x02'
KVKS_ALT = b'\x04'
KVKS_LCTRL = b'\x08'
KVKS_RCTRL = b'\x10'
KVKS_LALT = b'\x20'
KVKS_RALT = b'\x40'
# from web/source/kmwosk.ts
VKey_to_Iso = {
90: {"code": "B01", "base": "z", "shift": "Z"}, # Z
88: {"code": "B02", "base": "x", "shift": "X"}, # X
67: {"code": "B03", "base": "c", "shift": "C"}, # C
86: {"code": "B04", "base": "v", "shift": "V"}, # V
66: {"code": "B05", "base": "b", "shift": "B"}, # B
78: {"code": "B06", "base": "n", "shift": "N"}, # N
77: {"code": "B07", "base": "m", "shift": "M"}, # M
188: {"code": "B08", "base": ",", "shift": "<"}, # ,
190: {"code": "B09", "base": ".", "shift": ">"}, # .
191: {"code": "B10", "base": "/", "shift": "?"}, # /
65: {"code": "C01", "base": "a", "shift": "A"}, # A
83: {"code": "C02", "base": "s", "shift": "S"}, # S
68: {"code": "C03", "base": "d", "shift": "D"}, # D
70: {"code": "C04", "base": "f", "shift": "F"}, # F
71: {"code": "C05", "base": "g", "shift": "G"}, # G
72: {"code": "C06", "base": "h", "shift": "H"}, # H
74: {"code": "C07", "base": "j", "shift": "J"}, # J
75: {"code": "C08", "base": "k", "shift": "K"}, # K
76: {"code": "C09", "base": "l", "shift": "L"}, # L
186: {"code": "C10", "base": ";", "shift": ":"}, # ;
222: {"code": "C11", "base": "'", "shift": '"'}, # '
81: {"code": "D01", "base": "q", "shift": "Q"}, # Q
87: {"code": "D02", "base": "w", "shift": "W"}, # W
69: {"code": "D03", "base": "e", "shift": "E"}, # E
82: {"code": "D04", "base": "r", "shift": "R"}, # R
84: {"code": "D05", "base": "t", "shift": "T"}, # T
89: {"code": "D06", "base": "y", "shift": "Y"}, # Y
85: {"code": "D07", "base": "u", "shift": "U"}, # U
73: {"code": "D08", "base": "i", "shift": "I"}, # I
79: {"code": "D09", "base": "o", "shift": "O"}, # O
80: {"code": "D10", "base": "p", "shift": "P"}, # P
219: {"code": "D11", "base": "[", "shift": "{"}, # [
221: {"code": "D12", "base": "]", "shift": "}"}, # ]
49: {"code": "E01", "base": "1", "shift": "!"}, # 1
50: {"code": "E02", "base": "2", "shift": "@"}, # 2
51: {"code": "E03", "base": "3", "shift": "#"}, # 3
52: {"code": "E04", "base": "4", "shift": "$"}, # 4
53: {"code": "E05", "base": "5", "shift": "%"}, # 5
54: {"code": "E06", "base": "6", "shift": "^"}, # 6
55: {"code": "E07", "base": "7", "shift": "&"}, # 7
56: {"code": "E08", "base": "8", "shift": "*"}, # 8
57: {"code": "E09", "base": "9", "shift": "("}, # 9
48: {"code": "E10", "base": "0", "shift": ")"}, # 0
189: {"code": "E11", "base": "-", "shift": "_"}, # -
187: {"code": "E12", "base": "=", "shift": "+"}, # =
192: {"code": "E00", "base": "`", "shift": "~"}, # `
220: {"code": "C12", "base": "\\", "shift": "|"}, # \
226: {"code": "B00", "base": "<", "shift": ">"}, # extra key on european keyboards
32: {"code": "A03", "base": " ", "shift": " "}, # space
97: {"code": "B51", "base": "1", "shift": "1"}, # "K_NP1"
98: {"code": "B52", "base": "2", "shift": "2"}, # "K_NP2"
99: {"code": "B53", "base": "3", "shift": "3"}, # "K_NP3"
100: {"code": "C51", "base": "4", "shift": "4"}, # "K_NP4"
101: {"code": "C52", "base": "5", "shift": "5"}, # "K_NP5"
102: {"code": "C53", "base": "6", "shift": "6"}, # "K_NP6"
103: {"code": "D51", "base": "7", "shift": "7"}, # "K_NP7"
104: {"code": "D52", "base": "8", "shift": "8"}, # "K_NP8"
105: {"code": "D53", "base": "9", "shift": "9"}, # "K_NP9"
}
def bytecheck(value, check):
if bytes([value & check[0]]) == check:
return True
else:
return False
def get_nkey(file, fileContent, offset):
nkey = NKey()
data = struct.unpack_from("<B2H", fileContent, offset)
nkey.flags = data[0]
nkey.hasBitmap = bytecheck(data[0], kvkkBitmap)
nkey.hasUnicode = bytecheck(data[0], kvkkUnicode)
nkey.shiftflags = data[1]
if data[1] == 0:
nkey.Normal = True
nkey.Shift = bytecheck(data[1], KVKS_SHIFT)
nkey.Ctrl = bytecheck(data[1], KVKS_CTRL)
nkey.Alt = bytecheck(data[1], KVKS_ALT)
nkey.LCtrl = bytecheck(data[1], KVKS_LCTRL)
nkey.RCtrl = bytecheck(data[1], KVKS_RCTRL)
nkey.LAlt = bytecheck(data[1], KVKS_LALT)
nkey.RAlt = bytecheck(data[1], KVKS_RALT)
nkey.VKey = data[2]
nkey.text, newoffset = get_nstring(file, fileContent, offset + struct.calcsize("<B2H"))
nkey.bitmap, newoffset = get_nbitmap(file, fileContent, newoffset)
return nkey, newoffset
def get_nfont(file, fileContent, offset):
nfont = NFont()
nfont.name, curoffset = get_nstring(file, fileContent, offset)
data = struct.unpack_from("<L4B", fileContent, curoffset)
nfont.resv = data[4]
nfont.blue = data[1]
nfont.green = data[2]
nfont.red = data[3]
nfont.size = data[0]
return nfont, curoffset + struct.calcsize("<L4B")
def get_nstring(file, fileContent, offset):
stringlength = struct.unpack_from("<H", fileContent, offset)
file.seek(offset + 2)
if stringlength[0] > 256:
logging.error("error: suspiciously long string. ABORT.")
sys.exit(5)
if stringlength[0]:
# don't read the null string terminator
stringdata = file.read((stringlength[0] - 1) * 2)
else:
stringdata = file.read(0)
return stringdata.decode('utf-16'), offset + 2 + (2 * stringlength[0])
def get_nbitmap(file, fileContent, offset):
bitmap = None
bitmaplength = struct.unpack_from("<I", fileContent, offset)
file.seek(offset + struct.calcsize("<I"))
bitmap = file.read(bitmaplength[0])
return bitmap, offset + struct.calcsize("<I") + bitmaplength[0]
def print_kvk(kvkData, allkeys=False):
print("keyboard:", kvkData.AssociatedKeyboard)
print("version", kvkData.version)
print("flags:", kvkData.flags)
if kvkData.key102:
print(" keyboard has 102 keys?")
if kvkData.DisplayUnderlying:
print(" keyboard displays underlying?")
if kvkData.UseUnderlying:
print(" keyboard uses underlying?")
if kvkData.AltGr:
print(" keyboard uses AltGr?")
print("AnsiFont:", kvkData.AnsiFont.name)
print(" size:", kvkData.AnsiFont.size)
print(" colour: r:%d g:%d b:%d a:%d" % (
kvkData.AnsiFont.red, kvkData.AnsiFont.green, kvkData.AnsiFont.blue,
kvkData.AnsiFont.resv))
print("UnicodeFont:", kvkData.UnicodeFont.name)
print(" size:", kvkData.UnicodeFont.size)
print(" colour: r:%d g:%d b:%d a:%d" % (
kvkData.UnicodeFont.red, kvkData.UnicodeFont.green, kvkData.UnicodeFont.blue,
kvkData.UnicodeFont.resv))
print("numkeys:", kvkData.KeyCount)
if allkeys:
for key in kvkData.Keys:
print("number:", key.number)
if key.hasBitmap:
print(" key has bitmap")
if key.hasUnicode:
print(" key has unicode text")
if key.Normal:
print(" normal key")
if key.Shift:
print(" shift key")
if key.Ctrl:
print(" ctrl key")
if key.Alt:
print(" alt key")
if key.LCtrl:
print(" left ctrl key")
if key.RCtrl:
print(" right ctrl key")
if key.LAlt:
print(" left alt key")
if key.RAlt:
print(" right alt key")
print(" vkey:", key.VKey)
print(" text:", key.text)
def plus_join(modifier, name):
if modifier:
modifier = modifier + "+"
modifier = modifier + name
return modifier
def get_modifer(key):
modifier = ""
if key.Normal:
return "None"
if key.Shift:
modifier = plus_join(modifier, "shift")
if key.Ctrl:
modifier = plus_join(modifier, "ctrl")
if key.Alt:
modifier = plus_join(modifier, "alt")
if key.LCtrl:
modifier = plus_join(modifier, "ctrlL")
if key.RCtrl:
modifier = plus_join(modifier, "ctrlR")
if key.LAlt:
modifier = plus_join(modifier, "altL")
if key.RAlt:
modifier = plus_join(modifier, "altR")
return modifier
def convert_ldml(kvkData):
keymaps = {}
for key in kvkData.Keys:
modifier = get_modifer(key)
if modifier in keymaps:
keymaps[modifier] = keymaps[modifier] + (key,)
else:
keymaps[modifier] = (key,)
for vkey in VKey_to_Iso:
alreadyused = False
if "None" in keymaps:
for key in keymaps["None"]:
if key.VKey == vkey:
alreadyused = True
if not alreadyused and vkey != 226:
uskey = NKey()
uskey.VKey = vkey
uskey.text = VKey_to_Iso[vkey]["base"]
if "None" in keymaps:
keymaps["None"] = keymaps["None"] + (uskey,)
else:
keymaps["None"] = (uskey,)
alreadyused = False
if "shift" in keymaps:
for key in keymaps["shift"]:
if key.VKey == vkey:
alreadyused = True
if not alreadyused and vkey != 226:
uskey = NKey()
uskey.VKey = vkey
uskey.text = VKey_to_Iso[vkey]["shift"]
if "shift" in keymaps:
keymaps["shift"] = keymaps["shift"] + (uskey,)
else:
keymaps["shift"] = (uskey,)
ldml = etree.Element("keyboard", locale="zzz-keyman")
etree.SubElement(ldml, "version", platform="11")
names = etree.SubElement(ldml, "names")
names.append(etree.Element("name", value="ZZZ"))
for modifier in keymaps:
if modifier == "None":
keymap = etree.SubElement(ldml, "keyMap")
else:
keymap = etree.SubElement(ldml, "keyMap", modifiers=modifier)
for key in keymaps[modifier]:
if key.VKey in VKey_to_Iso:
iso_key = VKey_to_Iso[key.VKey]["code"]
keymap.append(etree.Element("map", iso=iso_key, to=key.text))
else:
logging.warning("Unknown vkey: %s", key.VKey)
return ldml
def output_ldml(ldmlfile, ldml):
etree.ElementTree(ldml).write(ldmlfile, pretty_print=True)
def parse_kvk_file(kvkfile):
kvkData = KVKData()
try:
with open(kvkfile, mode='rb') as file: # b is important -> binary
fileContent = file.read()
kvkstart = struct.unpack_from("<4s4cc", fileContent, 0)
kvkData.version = (kvkstart[1], kvkstart[2], kvkstart[3], kvkstart[4])
kvkData.flags = kvkstart[5]
kvkData.key102 = bytecheck(kvkData.flags[0], kvkk102key)
kvkData.DisplayUnderlying = bytecheck(kvkData.flags[0], kvkkDisplayUnderlying)
kvkData.UseUnderlying = bytecheck(kvkData.flags[0], kvkkUseUnderlying)
kvkData.AltGr = bytecheck(kvkData.flags[0], kvkkAltGr)
kvkData.AssociatedKeyboard, newoffset = get_nstring(file, fileContent, struct.calcsize("<4s4cc"))
kvkData.AnsiFont, newoffset = get_nfont(file, fileContent, newoffset)
kvkData.UnicodeFont, newoffset = get_nfont(file, fileContent, newoffset)
numkeys = struct.unpack_from("I", fileContent, newoffset)
kvkData.KeyCount = numkeys[0]
newoffset = newoffset + struct.calcsize("I")
for num in range(numkeys[0]):
nkey, newoffset = get_nkey(file, fileContent, newoffset)
nkey.number = num
kvkData.Keys.append(nkey)
except Exception as e:
logging.warning('Exception %s parsing kvk file %s %s', type(e), kvkfile, e.args)
return kvkData
def convert_kvk_to_ldml(kvkfile):
kvkData = parse_kvk_file(kvkfile)
return convert_ldml(kvkData)