Script converting hal fdi to InputClass sections

Dan Nicholson dbn.lists at gmail.com
Sat Apr 10 11:22:59 PDT 2010


Now that the server has moved to using udev on linux, we have many
configurations that were working using hal fdi files that will no longer
be applied. The proper solution is to convert these configurations to
using InputClass sections. I've taken a script that Martin Pitt wrote
for parsing media player info from fdi files and converted it to
generating InputClass sections. See the attached script. My python
skills suck, so it might be suboptimal in a lot of ways.

One of the things I noticed when working on this is that some of the hal
matches don't have corresponding InputClass Match* entries. Here are a
couple things I'd like to add to make this transition easier.

MatchOS - This affects the generic rules that the server installs on all
platforms. Our current x11-input.fdi allows evdev to be the driver only
on linux. It would be nice to add 'MatchOS "linux"' to 10-evdev.conf so
that it could be installed unconditionally.

PnP id matching - Since the serial devices don't export a name, most of
the matching is done on the PnP ID. Here's the wacom fdi file:

  http://cgit.freedesktop.org/~whot/xf86-input-wacom/tree/fdi/wacom.fdi

I'd like to add a MatchPnPID entry. Matching could be substring or glob.
By the same token, it would be nice to match USB or PCI IDs, too. Maybe
you could have three tags (MatchPnPID, MatchUSBID, MatchPCIID) or a
magic MatchBusID entry.

It would be nice to put this script in the server repo, but it's GPLv2+.
Martin, would you be opposed to relicensing your contributions to X11 or
dual licensing? The original script is here:

  http://cgit.freedesktop.org/~teuf/media-player-info/tree/tools/fdi2mpi.py

Let me know what you guys think.

--
Dan
-------------- next part --------------
#!/usr/bin/python
#
# Convert xorg keys from hal FDIs files to xorg.confInputClass sections.
#
# (C) 2010 Dan Nicholson
# (C) 2009 Canonical Ltd.
# Author: Dan Nicholson <dbn.lists at gmail.com>
# Author: Martin Pitt <martin.pitt at ubuntu.com>
#
# 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, xml.dom.minidom

# dict converting <match> tags to Match* entries
match_table = {
    'info.product': 'MatchProduct',
    'input.product': 'MatchProduct',
    'info.vendor': 'MatchVendor',
    'input.vendor': 'MatchVendor',
    'info.device': 'MatchDevicePath',
    'linux.device_file': 'MatchDevicePath',
    # Keys below aren't supported yet
    '/org/freedesktop/Hal/devices/computer:system.kernel.name': 'MatchOS',
    '@info.parent:pnp.id': 'MatchPnPID',
}

# dict converting info.capabilities list to Match* entries
cap_match_table = {
    'input.keys': 'MatchIsKeyboard',
    'input.keyboard': 'MatchIsKeyboard',
    'input.keypad': 'MatchIsKeyboard',
    'input.mouse': 'MatchIsPointer',
    'input.joystick': 'MatchIsJoystick',
    'input.tablet': 'MatchIsTablet',
    'input.touchpad': 'MatchIsTouchpad',
    'input.touchscreen': 'MatchIsTouchscreen',
}

def device_glob(path):
    '''Convert a contains device path to a glob entry'''
    if path[0] != '/':
        path = '*' + path
    return path + '*'

def parse_match(node):
    '''Parse a <match> tag to a tuple with InputClass values'''
    match = None
    value = None
    booltype = False

    # see what type of key we have
    if node.attributes.has_key('key'):
        key = node.attributes['key'].nodeValue
        if key in match_table:
            match = match_table[key]
        elif key == 'info.capabilities':
            booltype = True

    # bail out now if it's unrecognized
    if not match and not booltype:
        return (match, value)

    if node.attributes.has_key('string'):
        value = node.attributes['string'].nodeValue
    elif node.attributes.has_key('contains'):
        value = node.attributes['contains'].nodeValue
        if match == 'MatchDevicePath':
            value = device_glob(value)
        elif booltype and value in cap_match_table:
            match = cap_match_table[value]
            value = 'yes'
    elif node.attributes.has_key('string_outof'):
        value = node.attributes['string_outof'].nodeValue.replace(';','|')
    elif node.attributes.has_key('contains_outof'):
        all_values = node.attributes['contains_outof'].nodeValue.split(';')
        for v in all_values:
            if match == 'MatchDevicePath':
                v = device_glob(v)
            if value:
                value += '|' + v
            else:
                value = v

    if match == 'MatchOS':
        value = value.lower()

    return (match, value)

def parse_options(node):
    '''Parse the x11_* options and return InputClass entries'''
    driver = ''
    ignore = False
    options = []
    for n in node.childNodes:
        if n.nodeType != xml.dom.minidom.Node.ELEMENT_NODE:
            continue

        tag = n.tagName
        key = n.attributes['key'].nodeValue
        value = ''

        if n.hasChildNodes():
            content_node = n.childNodes[0]
            assert content_node.nodeType == xml.dom.Node.TEXT_NODE
            value = content_node.nodeValue

        if tag == 'match':
            continue
        assert tag in ('addset', 'merge', 'append', 'remove')

        if tag == 'remove' and key == 'input.x11_driver':
            ignore = True
        elif key == 'input.x11_driver':
            driver = value
        elif key.startswith('input.x11_options.'):
            option = key.split('.', 2)[2]
            options.append((option, value))

    return (driver, ignore, options)

def is_match_node(node):
    '''Check if a node is a <match> element'''
    return node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE and \
        node.tagName == 'match'

def parse_all_matches(node):
    '''Parse a x11 match tag and any parents that don't supply their
    own options'''
    matches = []

    while True:
        (key, value) = parse_match(node)
        if key and value:
            matches.append((key, value))

        # walk up to a parent match node
        node = node.parentNode
        if node == None or not is_match_node(node):
            break

        # leave if there other options at this level
        children = set([n.tagName for n in node.childNodes
                        if n.nodeType == xml.dom.minidom.Node.ELEMENT_NODE])
        if children & set(['addset', 'merge', 'append']):
            break

    return matches

def print_section(matches, driver, ignore, options):
    ''' Print a valid InputClass section to stdout'''
    print 'Section "InputClass"'
    print '\tIdentifier "xxx"'
    for m, v in matches:
        print '\t%s "%s"' % (m, v)
    if driver:
        print '\tDriver "%s"' % driver
    if ignore:
        print '\tOption "Ignore" "yes"'
    for o, v in options:
        print '\tOption "%s" "%s"' % (o, v)
    print 'EndSection'

def parse_fdi(fdi):
    '''Parse x11 matches from fdi'''
    # find all <match> leaf nodes
    num = 0
    for match_node in fdi.getElementsByTagName('match'):
        children = set([n.tagName for n in match_node.childNodes
                if n.nodeType == xml.dom.minidom.Node.ELEMENT_NODE])

        # see if there are any options at this level
        (driver, ignore, options) = parse_options(match_node)
        if not driver and not ignore and not options:
            continue

        matches = parse_all_matches(match_node)
        if num > 0:
            print
        print_section(matches, driver, ignore, options)
        num += 1

for f in sys.argv[1:]:
    parse_fdi(xml.dom.minidom.parse(f))


More information about the xorg-devel mailing list