4 """%(prog) [options] KLCFILE
6 Read a .KLC file saved by Microsoft Keyboard Layout Creator, and print
7 an equivalent xkb_symbol description.
9 The resulting output can be included in the xkb_symbol section of a keymap
10 description. This may work for you:
12 $ %(prog) foo.klc > foo.xkbsym
14 > | sed '/xkb_symbols/s/};/\n\t\t\tinclude "foo.xkbsym" };/' \
15 > | xkbcomp - "${DISPLAY}"
16 $ setxkbmap -option lv3:ralt_switch # For AltGr
18 To use the optional dead-key compose file, written out to say
19 ~/.XCompose.dead_keys, create a file called ~/.XCompose containing
22 include "%H/.XCompose.dead_keys"
24 See §4.19 “Xlib Compose file support and extensions” of the X11R7.1 release
25 notes (http://xorg.freedesktop.org/releases/X11R7.1/doc/RELNOTES4.html#30) for
28 X applications read the compose file on startup, so you will need to restart
29 applications to pick up changes in dead keys, whereas other changes will take
30 effect immediately on running setxkbmap.
32 There are fundamental difference in the way that X and Windows handle dead
33 keys. For the key sequence 〈dead_key〉〈other_key〉, where 〈other_key〉 has no
34 mapping for this particular dead key, windows treats both keypresses normally,
38 from __future__ import with_statement
44 from optparse import OptionParser
46 class KeyMapping(object):
47 def __init__(self, scancode, normal, shifted, altgr, shiftedaltgr):
48 self.scancode = scancode
49 self.normal = self._cleanup(normal)
50 self.shifted = self._cleanup(shifted)
51 self.altgr = self._cleanup(altgr)
52 self.shiftedaltgr = self._cleanup(shiftedaltgr)
55 def _cleanup(cls, charstr):
58 match = re.match('[0-9A-Fa-f]{4}', charstr)
60 return unichr(int(match.group(), 16))
61 assert len(charstr) == 1
65 def scancode_to_xkbname(cls, scancode):
66 special = {0x29: "TLDE", 0x2b: "BKSL", 0x39: "SPCE", 0x53: "KPDL"}
67 if scancode in special:
68 return special[scancode]
70 elif scancode <= 0x0d:
71 return "AE%02d" % (scancode - 1)
72 elif scancode <= 0x1b:
73 return "AD%02d" % (scancode - 0xf)
74 elif scancode <= 0x28:
75 return "AC%02d" % (scancode - 0x1d)
76 elif scancode <= 0x35:
77 return "AB%02d" % (scancode - 0x2b)
80 def to_xkb_def_str(self):
81 xkbname = self.scancode_to_xkbname(self.scancode)
84 def format_symbol(sym):
87 return "U%04x" % ord(sym)
89 return ("key <%s> { [ %s, %s, %s, %s ] };"
92 for sym in [self.normal, self.shifted,
93 self.altgr, self.shiftedaltgr]]))
95 def DDDD_to_name(DDDD):
96 "'0046' -> 'FULL STOP'"
97 return unicodedata.name(unichr(int(DDDD, 16)))
99 class DeadKey(object):
100 def __init__(self, deadkey):
101 self.deadkey = deadkey
104 def add_entry(self, key, value):
105 self.maps[key] = value
107 def to_xcompose_str(self):
108 return '# %s' % DDDD_to_name(self.deadkey) + "\n" \
109 + "\n".join("<U%s> <U%s>: U%s # '%s' -> %s" % (self.deadkey,
113 for k, v in self.maps.items()) + "\n"
115 class MSKLCFile(object):
116 def __init__(self, filename):
117 self.filename = filename
123 with codecs.open(self.filename, 'r', encoding='utf-16') as file:
124 in_deadkey_def = False
130 match = re.match(r"""^([0-9a-fA-F]{4})\s+
137 in_deadkey_def = False
140 self.deadkeys[-1].add_entry(match.group(1),
144 match = re.match(r'^DEADKEY\s+([0-9a-fA-F]{4})$', line)
146 in_deadkey_def = True
147 self.deadkeys.append(DeadKey(match.group(1)))
149 match = re.match('(.*?)//', line)
151 pieces = match.group().split()
153 self.parse_layout_line(pieces)
155 def parse_layout_line(self, pieces):
156 scancode, vk, cap, normal, shift, ctrl, altgr, shiftaltgr, junk = \
158 scancode = int(scancode, 16)
159 mapping = KeyMapping(scancode, normal, shift, altgr, shiftaltgr)
160 self.mappings.append(mapping)
166 optp = OptionParser(usage=__doc__)
167 optp.add_option('--compose', dest='compose_file', metavar="FILE",
168 help="Write dead key definitions to compose file FILE")
169 options, args = optp.parse_args(args)
177 layout = MSKLCFile(filename)
179 print "xkb_symbols {"
180 for mapping in layout.mappings:
181 print mapping.to_xkb_def_str()
184 if options.compose_file is not None:
185 with open(options.compose_file, 'w') as compose_file:
186 for key in layout.deadkeys:
187 print >> compose_file, key.to_xcompose_str()
189 if __name__ == '__main__':