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
125 with codecs.open(self.filename, 'r', encoding='utf-16') as file:
129 line, sep, comment = line.partition('//')
131 comment = comment.strip()
136 if state == 'shiftstate':
139 self.shiftmap.append([shift, shift in [0, 1, 6, 7]])
143 elif state == 'layout':
144 pieces = line.split()
145 if len(pieces) == len(self.shiftmap)+3:
146 self.parse_layout_line(pieces)
150 elif state in ['keyname', 'keyname_ext', 'keyname_dead', 'descriptions', 'languagenames']:
152 hex, key = line.split("\t", 1)
157 elif state == 'deadkey':
158 match = re.match(r"""^([0-9a-fA-F]{4})\s+
159 ([0-9a-fA-F]{4})$""",
166 self.deadkeys[-1].add_entry(match.group(1),
173 key, sep, arg = line.partition("\t")
174 if not key.isupper():
179 self.metadata['layout'], sep, quoted = arg.partition("\t")
180 self.metadata['name'] = quoted[1:-1]
181 elif key in ['copyright', 'company', 'version']:
182 self.metadata['key'] = arg[1:-1]
183 elif key in ['localename', 'localeid']:
185 elif key in ['shiftstate', 'layout', 'keyname', 'keyname_ext', 'keyname_dead', 'descriptions', 'languagenames']:
187 elif key == 'deadkey':
189 self.deadkeys.append(DeadKey(arg))
190 elif key == 'endkbd':
196 def parse_layout_line(self, pieces):
197 scancode = int(pieces[0], 16)
200 valid = ['-1','-1','-1','-1']
201 for i in range(3, len(pieces)):
202 shift, good = self.shiftmap[i-3]
204 valid[shift % 4] = pieces[i]
205 elif pieces[i] != '-1':
206 print >> sys.stderr, "skipping %(pc)s at position %(pos)u (shiftstate %(ss)u) for %(key)s" % {
212 mapping = KeyMapping(scancode, *valid)
213 self.mappings.append(mapping)
219 optp = OptionParser(usage=__doc__)
220 optp.add_option('--compose', dest='compose_file', metavar="FILE",
221 help="Write dead key definitions to compose file FILE")
222 options, args = optp.parse_args(args)
230 layout = MSKLCFile(filename)
232 print "xkb_symbols {"
233 for mapping in layout.mappings:
234 print mapping.to_xkb_def_str()
237 if options.compose_file is not None:
238 with open(options.compose_file, 'w') as compose_file:
239 for key in layout.deadkeys:
240 print >> compose_file, key.to_xcompose_str()
242 if __name__ == '__main__':