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
45 from optparse import OptionParser
47 class KeyMapping(object):
48 def __init__(self, scancode, normal, shifted, altgr, shiftedaltgr):
49 self.scancode = scancode
50 self.normal = self._cleanup(normal)
51 self.shifted = self._cleanup(shifted)
52 self.altgr = self._cleanup(altgr)
53 self.shiftedaltgr = self._cleanup(shiftedaltgr)
56 def _cleanup(cls, charstr):
59 noat, at, junk = charstr.partition('@')
60 match = re.match('^[0-9A-Fa-f]{4}$', noat)
62 return unichr(int(match.group(), 16))
67 def scancode_to_xkbname(cls, scancode):
68 special = {0x29: "TLDE", 0x2b: "BKSL", 0x39: "SPCE", 0x53: "KPDL"}
69 if scancode in special:
70 return special[scancode]
72 elif scancode <= 0x0d:
73 return "AE%02d" % (scancode - 1)
74 elif scancode <= 0x1b:
75 return "AD%02d" % (scancode - 0xf)
76 elif scancode <= 0x28:
77 return "AC%02d" % (scancode - 0x1d)
78 elif scancode <= 0x35:
79 return "AB%02d" % (scancode - 0x2b)
82 def to_xkb_def_str(self):
83 xkbname = self.scancode_to_xkbname(self.scancode)
87 return ("key <%s> { [ %s, %s, %s, %s ] };"
89 + [keysymdata.name(sym)
90 for sym in [self.normal, self.shifted,
91 self.altgr, self.shiftedaltgr]]))
93 def DDDD_to_name(DDDD):
94 "'0046' -> 'FULL STOP'"
95 return unicodedata.name(unichr(int(DDDD, 16)))
97 class DeadKey(object):
98 def __init__(self, deadkey):
99 self.deadkey = deadkey
102 def add_entry(self, key, value):
103 self.maps[key] = value
105 def to_xcompose_str(self):
106 return '# %s' % DDDD_to_name(self.deadkey) + "\n" \
107 + "\n".join("<%s> <%s>:\t%s\t# '%s' -> %s" % (keysymdata.name(self.deadkey),
108 keysymdata.name(k), keysymdata.name(v),
111 for k, v in self.maps.items()) + "\n"
113 class MSKLCFile(object):
114 def __init__(self, filename):
115 self.filename = filename
123 with codecs.open(self.filename, 'r', encoding='utf-16') as file:
127 line, sep, comment = line.partition('//')
129 comment = comment.strip()
134 if state == 'shiftstate':
137 self.shiftmap.append([shift, shift in [0, 1, 6, 7]])
141 elif state == 'layout':
142 pieces = line.split()
143 if len(pieces) == len(self.shiftmap)+3:
144 self.parse_layout_line(pieces)
148 elif state in ['keyname', 'keyname_ext', 'keyname_dead', 'descriptions', 'languagenames']:
150 hex, key = line.split("\t", 1)
155 elif state == 'deadkey':
156 match = re.match(r"""^([0-9a-fA-F]{4})\s+
157 ([0-9a-fA-F]{4})$""",
164 self.deadkeys[-1].add_entry(match.group(1),
171 key, sep, arg = line.partition("\t")
172 if not key.isupper():
177 self.metadata['layout'], sep, quoted = arg.partition("\t")
178 self.metadata['name'] = quoted[1:-1]
179 elif key in ['copyright', 'company', 'version']:
180 self.metadata['key'] = arg[1:-1]
181 elif key in ['localename', 'localeid']:
183 elif key in ['shiftstate', 'layout', 'keyname', 'keyname_ext', 'keyname_dead', 'descriptions', 'languagenames']:
185 elif key == 'deadkey':
187 self.deadkeys.append(DeadKey(arg))
188 elif key == 'endkbd':
194 def parse_layout_line(self, pieces):
195 scancode = int(pieces[0], 16)
198 valid = ['-1','-1','-1','-1']
199 for i in range(3, len(pieces)):
200 shift, good = self.shiftmap[i-3]
202 valid[shift % 4] = pieces[i]
203 elif pieces[i] != '-1':
204 print >> sys.stderr, "skipping %(pc)s at position %(pos)u (shiftstate %(ss)u) for %(key)s" % {
210 mapping = KeyMapping(scancode, *valid)
211 self.mappings.append(mapping)
217 optp = OptionParser(usage=__doc__)
218 optp.add_option('--compose', dest='compose_file', metavar="FILE",
219 help="Write dead key definitions to compose file FILE")
220 options, args = optp.parse_args(args)
228 layout = MSKLCFile(filename)
230 print "xkb_symbols {"
231 for mapping in layout.mappings:
232 print mapping.to_xkb_def_str()
235 if options.compose_file is not None:
236 with open(options.compose_file, 'w') as compose_file:
237 for key in layout.deadkeys:
238 print >> compose_file, key.to_xcompose_str().encode('utf-8')
240 if __name__ == '__main__':