#!/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(" 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(" 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)