16747b715Smrg#!/usr/bin/python
26747b715Smrg#
36747b715Smrg# Convert xorg keys from hal FDIs files to xorg.conf InputClass sections.
46747b715Smrg# Modified from Martin Pitt's original fdi2mpi.py script:
56747b715Smrg# http://cgit.freedesktop.org/media-player-info/tree/tools/fdi2mpi.py
66747b715Smrg#
76747b715Smrg# (C) 2010 Dan Nicholson
86747b715Smrg# (C) 2009 Canonical Ltd.
96747b715Smrg# Author: Dan Nicholson <dbn.lists@gmail.com>
106747b715Smrg# Author: Martin Pitt <martin.pitt@ubuntu.com>
116747b715Smrg#
126747b715Smrg# Permission is hereby granted, free of charge, to any person obtaining a copy
136747b715Smrg# of this software and associated documentation files (the "Software"), to
146747b715Smrg# deal in the Software without restriction, including without limitation the
156747b715Smrg# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
166747b715Smrg# sell copies of the Software, and to permit persons to whom the Software is
176747b715Smrg# fur- nished to do so, subject to the following conditions:
186747b715Smrg#
196747b715Smrg#  The above copyright notice and this permission notice shall be included in
206747b715Smrg#  all copies or substantial portions of the Software.
216747b715Smrg#
226747b715Smrg#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
236747b715Smrg#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
246747b715Smrg#  FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
256747b715Smrg#  THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
266747b715Smrg#  AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON-
276747b715Smrg#  NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
286747b715Smrg
296747b715Smrgimport sys, xml.dom.minidom
306747b715Smrg
316747b715Smrg# dict converting <match> tags to Match* entries
326747b715Smrgmatch_table = {
336747b715Smrg    'info.product': 'MatchProduct',
346747b715Smrg    'input.product': 'MatchProduct',
356747b715Smrg    'info.vendor': 'MatchVendor',
366747b715Smrg    'input.vendor': 'MatchVendor',
376747b715Smrg    'info.device': 'MatchDevicePath',
386747b715Smrg    'linux.device_file': 'MatchDevicePath',
396747b715Smrg    '/org/freedesktop/Hal/devices/computer:system.kernel.name': 'MatchOS',
406747b715Smrg    '@info.parent:pnp.id': 'MatchPnPID',
416747b715Smrg}
426747b715Smrg
436747b715Smrg# dict converting info.capabilities list to Match* entries
446747b715Smrgcap_match_table = {
456747b715Smrg    'input.keys': 'MatchIsKeyboard',
466747b715Smrg    'input.keyboard': 'MatchIsKeyboard',
476747b715Smrg    'input.keypad': 'MatchIsKeyboard',
486747b715Smrg    'input.mouse': 'MatchIsPointer',
496747b715Smrg    'input.joystick': 'MatchIsJoystick',
506747b715Smrg    'input.tablet': 'MatchIsTablet',
516747b715Smrg    'input.touchpad': 'MatchIsTouchpad',
526747b715Smrg    'input.touchscreen': 'MatchIsTouchscreen',
536747b715Smrg}
546747b715Smrg
556747b715Smrgdef device_glob(path):
566747b715Smrg    '''Convert a contains device path to a glob entry'''
576747b715Smrg    if path[0] != '/':
586747b715Smrg        path = '*' + path
596747b715Smrg    return path + '*'
606747b715Smrg
616747b715Smrgdef parse_match(node):
626747b715Smrg    '''Parse a <match> tag to a tuple with InputClass values'''
636747b715Smrg    match = None
646747b715Smrg    value = None
656747b715Smrg    booltype = False
666747b715Smrg
676747b715Smrg    # see what type of key we have
686747b715Smrg    if node.attributes.has_key('key'):
696747b715Smrg        key = node.attributes['key'].nodeValue
706747b715Smrg        if key in match_table:
716747b715Smrg            match = match_table[key]
726747b715Smrg        elif key == 'info.capabilities':
736747b715Smrg            booltype = True
746747b715Smrg
756747b715Smrg    # bail out now if it's unrecognized
766747b715Smrg    if not match and not booltype:
776747b715Smrg        return (match, value)
786747b715Smrg
796747b715Smrg    if node.attributes.has_key('string'):
806747b715Smrg        value = node.attributes['string'].nodeValue
816747b715Smrg    elif node.attributes.has_key('contains'):
826747b715Smrg        value = node.attributes['contains'].nodeValue
836747b715Smrg        if match == 'MatchDevicePath':
846747b715Smrg            value = device_glob(value)
856747b715Smrg        elif booltype and value in cap_match_table:
866747b715Smrg            match = cap_match_table[value]
876747b715Smrg            value = 'yes'
886747b715Smrg    elif node.attributes.has_key('string_outof'):
896747b715Smrg        value = node.attributes['string_outof'].nodeValue.replace(';','|')
906747b715Smrg    elif node.attributes.has_key('contains_outof'):
916747b715Smrg        all_values = node.attributes['contains_outof'].nodeValue.split(';')
926747b715Smrg        for v in all_values:
936747b715Smrg            if match == 'MatchDevicePath':
946747b715Smrg                v = device_glob(v)
956747b715Smrg            elif match == 'MatchPnPID' and len(v) < 7:
966747b715Smrg                v += '*'
976747b715Smrg            if value:
986747b715Smrg                value += '|' + v
996747b715Smrg            else:
1006747b715Smrg                value = v
1016747b715Smrg
1026747b715Smrg    return (match, value)
1036747b715Smrg
1046747b715Smrgdef parse_options(node):
1056747b715Smrg    '''Parse the x11_* options and return InputClass entries'''
1066747b715Smrg    driver = ''
1076747b715Smrg    ignore = False
1086747b715Smrg    options = []
1096747b715Smrg    for n in node.childNodes:
1106747b715Smrg        if n.nodeType != xml.dom.minidom.Node.ELEMENT_NODE:
1116747b715Smrg            continue
1126747b715Smrg
1136747b715Smrg        tag = n.tagName
1146747b715Smrg        key = n.attributes['key'].nodeValue
1156747b715Smrg        value = ''
1166747b715Smrg
1176747b715Smrg        if n.hasChildNodes():
1186747b715Smrg            content_node = n.childNodes[0]
1196747b715Smrg            assert content_node.nodeType == xml.dom.Node.TEXT_NODE
1206747b715Smrg            value = content_node.nodeValue
1216747b715Smrg
1226747b715Smrg        if tag == 'match':
1236747b715Smrg            continue
1246747b715Smrg        assert tag in ('addset', 'merge', 'append', 'remove')
1256747b715Smrg
1266747b715Smrg        if tag == 'remove' and key == 'input.x11_driver':
1276747b715Smrg            ignore = True
1286747b715Smrg        elif key == 'input.x11_driver':
1296747b715Smrg            driver = value
1306747b715Smrg        elif key.startswith('input.x11_options.'):
1316747b715Smrg            option = key.split('.', 2)[2]
1326747b715Smrg            options.append((option, value))
1336747b715Smrg
1346747b715Smrg    return (driver, ignore, options)
1356747b715Smrg
1366747b715Smrgdef is_match_node(node):
1376747b715Smrg    '''Check if a node is a <match> element'''
1386747b715Smrg    return node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE and \
1396747b715Smrg        node.tagName == 'match'
1406747b715Smrg
1416747b715Smrgdef parse_all_matches(node):
1426747b715Smrg    '''Parse a x11 match tag and any parents that don't supply their
1436747b715Smrg    own options'''
1446747b715Smrg    matches = []
1456747b715Smrg
1466747b715Smrg    while True:
1476747b715Smrg        (key, value) = parse_match(node)
1486747b715Smrg        if key and value:
1496747b715Smrg            matches.append((key, value))
1506747b715Smrg
1516747b715Smrg        # walk up to a parent match node
1526747b715Smrg        node = node.parentNode
1536747b715Smrg        if node == None or not is_match_node(node):
1546747b715Smrg            break
1556747b715Smrg
1566747b715Smrg        # leave if there other options at this level
1576747b715Smrg        children = set([n.tagName for n in node.childNodes
1586747b715Smrg                        if n.nodeType == xml.dom.minidom.Node.ELEMENT_NODE])
1596747b715Smrg        if children & set(['addset', 'merge', 'append']):
1606747b715Smrg            break
1616747b715Smrg
1626747b715Smrg    return matches
1636747b715Smrg
1646747b715Smrg# stupid counter to give "unique" rule names
1656747b715Smrgnum_sections = 1
1666747b715Smrgdef print_section(matches, driver, ignore, options):
1676747b715Smrg    '''Print a valid InputClass section to stdout'''
1686747b715Smrg    global num_sections
1696747b715Smrg    print 'Section "InputClass"'
1706747b715Smrg    print '\tIdentifier "Converted Class %d"' % num_sections
1716747b715Smrg    num_sections += 1
1726747b715Smrg    for m, v in matches:
1736747b715Smrg        print '\t%s "%s"' % (m, v)
1746747b715Smrg    if driver:
1756747b715Smrg        print '\tDriver "%s"' % driver
1766747b715Smrg    if ignore:
1776747b715Smrg        print '\tOption "Ignore" "yes"'
1786747b715Smrg    for o, v in options:
1796747b715Smrg        print '\tOption "%s" "%s"' % (o, v)
1806747b715Smrg    print 'EndSection'
1816747b715Smrg
1826747b715Smrgdef parse_fdi(fdi):
1836747b715Smrg    '''Parse x11 matches from fdi'''
1846747b715Smrg    # find all <match> leaf nodes
1856747b715Smrg    num = 0
1866747b715Smrg    for match_node in fdi.getElementsByTagName('match'):
1876747b715Smrg        children = set([n.tagName for n in match_node.childNodes
1886747b715Smrg                if n.nodeType == xml.dom.minidom.Node.ELEMENT_NODE])
1896747b715Smrg
1906747b715Smrg        # see if there are any options at this level
1916747b715Smrg        (driver, ignore, options) = parse_options(match_node)
1926747b715Smrg        if not driver and not ignore and not options:
1936747b715Smrg            continue
1946747b715Smrg
1956747b715Smrg        matches = parse_all_matches(match_node)
1966747b715Smrg        if num > 0:
1976747b715Smrg            print
1986747b715Smrg        print_section(matches, driver, ignore, options)
1996747b715Smrg        num += 1
2006747b715Smrg
2016747b715Smrgfor f in sys.argv[1:]:
2026747b715Smrg    parse_fdi(xml.dom.minidom.parse(f))
203