Commit | Line | Data |
---|---|---|
a09e091a JB |
1 | #!/usr/bin/python |
2 | # | |
3 | # Convert xorg keys from hal FDIs files to xorg.conf InputClass sections. | |
4 | # Modified from Martin Pitt's original fdi2mpi.py script: | |
5 | # http://cgit.freedesktop.org/media-player-info/tree/tools/fdi2mpi.py | |
6 | # | |
7 | # (C) 2010 Dan Nicholson | |
8 | # (C) 2009 Canonical Ltd. | |
9 | # Author: Dan Nicholson <dbn.lists@gmail.com> | |
10 | # Author: Martin Pitt <martin.pitt@ubuntu.com> | |
11 | # | |
12 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
13 | # of this software and associated documentation files (the "Software"), to | |
14 | # deal in the Software without restriction, including without limitation the | |
15 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
16 | # sell copies of the Software, and to permit persons to whom the Software is | |
17 | # fur- nished to do so, subject to the following conditions: | |
18 | # | |
19 | # The above copyright notice and this permission notice shall be included in | |
20 | # all copies or substantial portions of the Software. | |
21 | # | |
22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
23 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
24 | # FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
25 | # THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN | |
26 | # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- | |
27 | # NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
28 | ||
29 | import sys, xml.dom.minidom | |
30 | ||
31 | # dict converting <match> tags to Match* entries | |
32 | match_table = { | |
33 | 'info.product': 'MatchProduct', | |
34 | 'input.product': 'MatchProduct', | |
35 | 'info.vendor': 'MatchVendor', | |
36 | 'input.vendor': 'MatchVendor', | |
37 | 'info.device': 'MatchDevicePath', | |
38 | 'linux.device_file': 'MatchDevicePath', | |
39 | '/org/freedesktop/Hal/devices/computer:system.kernel.name': 'MatchOS', | |
40 | '@info.parent:pnp.id': 'MatchPnPID', | |
41 | } | |
42 | ||
43 | # dict converting info.capabilities list to Match* entries | |
44 | cap_match_table = { | |
45 | 'input.keys': 'MatchIsKeyboard', | |
46 | 'input.keyboard': 'MatchIsKeyboard', | |
47 | 'input.keypad': 'MatchIsKeyboard', | |
48 | 'input.mouse': 'MatchIsPointer', | |
49 | 'input.joystick': 'MatchIsJoystick', | |
50 | 'input.tablet': 'MatchIsTablet', | |
51 | 'input.touchpad': 'MatchIsTouchpad', | |
52 | 'input.touchscreen': 'MatchIsTouchscreen', | |
53 | } | |
54 | ||
55 | def device_glob(path): | |
56 | '''Convert a contains device path to a glob entry''' | |
57 | if path[0] != '/': | |
58 | path = '*' + path | |
59 | return path + '*' | |
60 | ||
61 | def parse_match(node): | |
62 | '''Parse a <match> tag to a tuple with InputClass values''' | |
63 | match = None | |
64 | value = None | |
65 | booltype = False | |
66 | ||
67 | # see what type of key we have | |
68 | if node.attributes.has_key('key'): | |
69 | key = node.attributes['key'].nodeValue | |
70 | if key in match_table: | |
71 | match = match_table[key] | |
72 | elif key == 'info.capabilities': | |
73 | booltype = True | |
74 | ||
75 | # bail out now if it's unrecognized | |
76 | if not match and not booltype: | |
77 | return (match, value) | |
78 | ||
79 | if node.attributes.has_key('string'): | |
80 | value = node.attributes['string'].nodeValue | |
81 | elif node.attributes.has_key('contains'): | |
82 | value = node.attributes['contains'].nodeValue | |
83 | if match == 'MatchDevicePath': | |
84 | value = device_glob(value) | |
85 | elif booltype and value in cap_match_table: | |
86 | match = cap_match_table[value] | |
87 | value = 'yes' | |
88 | elif node.attributes.has_key('string_outof'): | |
89 | value = node.attributes['string_outof'].nodeValue.replace(';','|') | |
90 | elif node.attributes.has_key('contains_outof'): | |
91 | all_values = node.attributes['contains_outof'].nodeValue.split(';') | |
92 | for v in all_values: | |
93 | if match == 'MatchDevicePath': | |
94 | v = device_glob(v) | |
95 | elif match == 'MatchPnPID' and len(v) < 7: | |
96 | v += '*' | |
97 | if value: | |
98 | value += '|' + v | |
99 | else: | |
100 | value = v | |
101 | ||
102 | return (match, value) | |
103 | ||
104 | def parse_options(node): | |
105 | '''Parse the x11_* options and return InputClass entries''' | |
106 | driver = '' | |
107 | ignore = False | |
108 | options = [] | |
109 | for n in node.childNodes: | |
110 | if n.nodeType != xml.dom.minidom.Node.ELEMENT_NODE: | |
111 | continue | |
112 | ||
113 | tag = n.tagName | |
114 | key = n.attributes['key'].nodeValue | |
115 | value = '' | |
116 | ||
117 | if n.hasChildNodes(): | |
118 | content_node = n.childNodes[0] | |
119 | assert content_node.nodeType == xml.dom.Node.TEXT_NODE | |
120 | value = content_node.nodeValue | |
121 | ||
122 | if tag == 'match': | |
123 | continue | |
124 | assert tag in ('addset', 'merge', 'append', 'remove') | |
125 | ||
126 | if tag == 'remove' and key == 'input.x11_driver': | |
127 | ignore = True | |
128 | elif key == 'input.x11_driver': | |
129 | driver = value | |
130 | elif key.startswith('input.x11_options.'): | |
131 | option = key.split('.', 2)[2] | |
132 | options.append((option, value)) | |
133 | ||
134 | return (driver, ignore, options) | |
135 | ||
136 | def is_match_node(node): | |
137 | '''Check if a node is a <match> element''' | |
138 | return node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE and \ | |
139 | node.tagName == 'match' | |
140 | ||
141 | def parse_all_matches(node): | |
142 | '''Parse a x11 match tag and any parents that don't supply their | |
143 | own options''' | |
144 | matches = [] | |
145 | ||
146 | while True: | |
147 | (key, value) = parse_match(node) | |
148 | if key and value: | |
149 | matches.append((key, value)) | |
150 | ||
151 | # walk up to a parent match node | |
152 | node = node.parentNode | |
153 | if node == None or not is_match_node(node): | |
154 | break | |
155 | ||
156 | # leave if there other options at this level | |
157 | children = set([n.tagName for n in node.childNodes | |
158 | if n.nodeType == xml.dom.minidom.Node.ELEMENT_NODE]) | |
159 | if children & set(['addset', 'merge', 'append']): | |
160 | break | |
161 | ||
162 | return matches | |
163 | ||
164 | # stupid counter to give "unique" rule names | |
165 | num_sections = 1 | |
166 | def print_section(matches, driver, ignore, options): | |
167 | '''Print a valid InputClass section to stdout''' | |
168 | global num_sections | |
169 | print 'Section "InputClass"' | |
170 | print '\tIdentifier "Converted Class %d"' % num_sections | |
171 | num_sections += 1 | |
172 | for m, v in matches: | |
173 | print '\t%s "%s"' % (m, v) | |
174 | if driver: | |
175 | print '\tDriver "%s"' % driver | |
176 | if ignore: | |
177 | print '\tOption "Ignore" "yes"' | |
178 | for o, v in options: | |
179 | print '\tOption "%s" "%s"' % (o, v) | |
180 | print 'EndSection' | |
181 | ||
182 | def parse_fdi(fdi): | |
183 | '''Parse x11 matches from fdi''' | |
184 | # find all <match> leaf nodes | |
185 | num = 0 | |
186 | for match_node in fdi.getElementsByTagName('match'): | |
187 | children = set([n.tagName for n in match_node.childNodes | |
188 | if n.nodeType == xml.dom.minidom.Node.ELEMENT_NODE]) | |
189 | ||
190 | # see if there are any options at this level | |
191 | (driver, ignore, options) = parse_options(match_node) | |
192 | if not driver and not ignore and not options: | |
193 | continue | |
194 | ||
195 | matches = parse_all_matches(match_node) | |
196 | if num > 0: | |
197 | ||
198 | print_section(matches, driver, ignore, options) | |
199 | num += 1 | |
200 | ||
201 | for f in sys.argv[1:]: | |
202 | parse_fdi(xml.dom.minidom.parse(f)) |