#!/usr/bin/python # Convert hal keymap FDIs into udev rules and key map files # Please note that this is far from perfect, since the mapping between fdi and # udev rules is not straightforward (and impossible in some cases). # # (C) 2009 Canonical Ltd. # Author: Martin Pitt # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # keymap is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with keymap; if not, write to the Free Software Foundation, # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. import sys, os.path, xml.dom.minidom def string_op2glob(node): '''Convert FDI string match operator to a glob. Return pair (glob, identifier) with the actual glob, and a string containing no glob characters or spaces, which is suitable as a file name. ''' if node.attributes.has_key('string'): v = node.attributes['string'].nodeValue return (v, v.replace(' ', '_').replace('/', '_').lower()) if node.attributes.has_key('prefix'): v = node.attributes['prefix'].nodeValue return (v + '*', v.replace(' ', '_').replace('/', '_').lower()) if node.attributes.has_key('suffix'): v = node.attributes['suffix'].nodeValue return ('*' + v, v.replace(' ', '_').replace('/', '_').lower()) if node.attributes.has_key('contains'): v = node.attributes['contains'].nodeValue return ('*' + v + '*', v.replace(' ', '_').replace('/', '_').lower()) if node.attributes.has_key('contains_outof'): alternatives = node.attributes['contains_outof'].nodeValue.split(';') gl = '|'.join(['*%s*' % v for v in alternatives]) id = '_'.join([v.replace(' ', '_').replace('/', '_').lower() for v in alternatives]) return (gl, id) if node.attributes.has_key('string_outof'): alternatives = node.attributes['string_outof'].nodeValue.split(';') gl = '|'.join(alternatives) id = '_'.join([v.replace(' ', '_').lower() for v in alternatives]) return (gl, id) if node.attributes.has_key('contains_ncase'): v = node.attributes['contains_ncase'].nodeValue nocase_glob = ''.join(['[%s%s]' % (c.lower(), c.upper()) for c in v]) return ('*' + nocase_glob + '*', v.replace(' ', '_').lower()) if node.attributes.has_key('prefix_ncase'): v = node.attributes['prefix_ncase'].nodeValue nocase_glob = ''.join(['[%s%s]' % (c.lower(), c.upper()) for c in v]) return (nocase_glob + '*', v.replace(' ', '_').lower()) raise NotImplementedError, 'unknown string operator ' + str(node.attributes.keys()) def get_node_comment(node): '''Find the next comment node after node''' while node: node = node.nextSibling if node and node.nodeType == xml.dom.Node.COMMENT_NODE: return node.nodeValue.strip() return None def normalize_code(code): code = int(code, 16) if code >= 0xE000: return code - 0xE000 + 128 return code def create_keylist(node, filename): '''Parse key code assignmends from notes and create map file.''' if not os.path.isdir('keymaps'): os.mkdir('keymaps') f = open(os.path.join('keymaps', filename), 'w') #f = sys.stdout #print '-------------- %s -------------' % filename for c in node.childNodes: if c.nodeName == 'append' and c.attributes.get('key').nodeValue == 'input.keymap.data': content_node = c.childNodes[0] assert content_node.nodeType == xml.dom.Node.TEXT_NODE (code, name) = content_node.nodeValue.split(':') comment = get_node_comment(c) if comment: print >> f, '0x%X %s # %s' % (normalize_code(code), name, comment) else: print >> f, '0x%X %s' % (normalize_code(code), name) #print '---------------------------' f.close() def get_vendor_node(node): '''Find the node's parent which matches the system vendor.''' while True: node = node.parentNode if not node: raise SystemError, 'no vendor parent node found' if node.nodeName == 'match' and node.attributes['key'].nodeValue == \ '/org/freedesktop/Hal/devices/computer:system.hardware.vendor': return node def parse_fdi_vendor(node): (vendor_glob, fname) = string_op2glob(node) print 'ATTR{[dmi/id]sys_vendor}=="%s", RUN+="keymap $name %s"' % (vendor_glob, fname) create_keylist(node, fname) def parse_fdi_product(node): (vendor_glob, vendor_fname) = string_op2glob(get_vendor_node(node)) (product_glob, product_fname) = string_op2glob(node) fname = '%s-%s' % (vendor_fname, product_fname) print 'ATTR{[dmi/id]sys_vendor}=="%s", ATTR{[dmi/id]product_name}=="%s", RUN+="keymap $name %s"' % (vendor_glob, product_glob, fname) create_keylist(node, fname) def parse_fdi_version(node): (vendor_glob, vendor_fname) = string_op2glob(get_vendor_node(node)) (product_glob, product_fname) = string_op2glob(node) fname = '%s-%s' % (vendor_fname, product_fname) print 'ATTR{[dmi/id]sys_vendor}=="%s", ATTR{[dmi/id]product_version}=="%s", RUN+="keymap $name %s"' % (vendor_glob, product_glob, fname) create_keylist(node, fname) def parse_fdi(fdi): '''Parse keymaps from a fdi node.''' for match_node in fdi.getElementsByTagName('match'): key = match_node.attributes['key'].nodeValue if key == '/org/freedesktop/Hal/devices/computer:system.hardware.vendor': # vendor list without model specific quirks parse_fdi_vendor(match_node) elif key == '/org/freedesktop/Hal/devices/computer:system.hardware.product': # product specific list parse_fdi_product(match_node) elif key == '/org/freedesktop/Hal/devices/computer:system.hardware.version': # product version specific list parse_fdi_version(match_node) elif key == '/org/freedesktop/Hal/devices/computer:system.formfactor': # this assumes that a formfactor does not have product submatches try: (vendor_glob, fname) = string_op2glob(get_vendor_node(match_node)) create_keylist(match_node, fname) except SystemError: # formfactor match is at toplevel pass elif key == '@input.originating_device:info.linux.driver': # already covered in udev rules header pass else: print >> sys.stderr, 'WARNING: I do not understand key type', key # udev rules header print '''ACTION!="add", GOTO="keyboard_end" SUBSYSTEM!="input", GOTO="keyboard_end" DRIVERS=="atkbd", GOTO="keyboard_vendorcheck" GOTO="keyboard_end" LABEL="keyboard_vendorcheck"''' # parse FDI files for f in sys.argv[1:]: parse_fdi(xml.dom.minidom.parse(f)) # udev rules footer print '\nLABEL="keyboard_end"'