]> git.ipfire.org Git - thirdparty/systemd.git/blame - hwdb/parse_hwdb.py
Merge pull request #12207 from poettering/portable-bus-policy-fix
[thirdparty/systemd.git] / hwdb / parse_hwdb.py
CommitLineData
3e67e5c9 1#!/usr/bin/env python3
d8a0bcfd 2# SPDX-License-Identifier: MIT
0c9836c0 3#
818bf546 4# This file is distributed under the MIT license, see below.
0c9836c0 5#
acb98601 6# The MIT License (MIT)
0c9836c0 7#
acb98601
ZJS
8# Permission is hereby granted, free of charge, to any person obtaining a copy
9# of this software and associated documentation files (the "Software"), to deal
10# in the Software without restriction, including without limitation the rights
11# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12# copies of the Software, and to permit persons to whom the Software is
13# furnished to do so, subject to the following conditions:
0c9836c0 14#
acb98601
ZJS
15# The above copyright notice and this permission notice shall be included in
16# all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24# SOFTWARE.
0c9836c0 25
c3f6a561
ZJS
26import glob
27import string
28import sys
29import os
0c9836c0
ZJS
30
31try:
af182e78 32 from pyparsing import (Word, White, Literal, ParserElement, Regex, LineEnd,
8d9d1e3a 33 OneOrMore, Combine, Or, Optional, Suppress, Group,
0c9836c0 34 nums, alphanums, printables,
086c001e 35 stringEnd, pythonStyleComment, QuotedString,
0c9836c0
ZJS
36 ParseBaseException)
37except ImportError:
c3f6a561
ZJS
38 print('pyparsing is not available')
39 sys.exit(77)
0c9836c0
ZJS
40
41try:
42 from evdev.ecodes import ecodes
43except ImportError:
44 ecodes = None
45 print('WARNING: evdev is not available')
46
1258f088
ZJS
47try:
48 from functools import lru_cache
49except ImportError:
50 # don't do caching on old python
51 lru_cache = lambda: (lambda f: f)
52
0c9836c0 53EOL = LineEnd().suppress()
f644a6da 54EMPTYLINE = LineEnd()
0c9836c0
ZJS
55COMMENTLINE = pythonStyleComment + EOL
56INTEGER = Word(nums)
086c001e 57STRING = QuotedString('"')
0c9836c0 58REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
7fdc73af 59SIGNED_REAL = Combine(Optional(Word('-+')) + REAL)
0c9836c0
ZJS
60UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
61
62TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
63 'evdev': ('name', 'atkbd', 'input'),
ffac3034 64 'id-input': ('modalias'),
0c9836c0 65 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
c8ec393b 66 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'),
0c9836c0 67 'keyboard': ('name', ),
7fdc73af 68 'sensor': ('modalias', ),
8d9d1e3a 69 }
0c9836c0 70
1258f088 71@lru_cache()
0c9836c0
ZJS
72def hwdb_grammar():
73 ParserElement.setDefaultWhitespaceChars('')
74
75 prefix = Or(category + ':' + Or(conn) + ':'
76 for category, conn in TYPES.items())
77 matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
78 propertyline = (White(' ', exact=1).suppress() +
7fdc73af 79 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.!-;, "') - Optional(pythonStyleComment)) +
0c9836c0
ZJS
80 EOL)
81 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
82
83 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
84 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
8d9d1e3a 85 (EMPTYLINE ^ stringEnd()).suppress())
0c9836c0
ZJS
86 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
87
88 grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
89
90 return grammar
91
1258f088 92@lru_cache()
0c9836c0
ZJS
93def property_grammar():
94 ParserElement.setDefaultWhitespaceChars(' ')
95
7fdc73af
ZJS
96 dpi_setting = (Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ'))('SETTINGS*')
97 mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL
98 mount_matrix = (mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX')
99
100 props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))),
0c9836c0 101 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
a6a8e60b 102 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
9107a337
PH
103 ('MOUSE_WHEEL_CLICK_COUNT', INTEGER),
104 ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER),
ffac3034
PH
105 ('ID_INPUT', Literal('1')),
106 ('ID_INPUT_ACCELEROMETER', Literal('1')),
107 ('ID_INPUT_JOYSTICK', Literal('1')),
108 ('ID_INPUT_KEY', Literal('1')),
109 ('ID_INPUT_KEYBOARD', Literal('1')),
110 ('ID_INPUT_MOUSE', Literal('1')),
111 ('ID_INPUT_POINTINGSTICK', Literal('1')),
112 ('ID_INPUT_SWITCH', Literal('1')),
113 ('ID_INPUT_TABLET', Literal('1')),
114 ('ID_INPUT_TABLET_PAD', Literal('1')),
115 ('ID_INPUT_TOUCHPAD', Literal('1')),
116 ('ID_INPUT_TOUCHSCREEN', Literal('1')),
0c9836c0 117 ('ID_INPUT_TRACKBALL', Literal('1')),
1a4132ae
PH
118 ('MOUSE_WHEEL_TILT_HORIZONTAL', Literal('1')),
119 ('MOUSE_WHEEL_TILT_VERTICAL', Literal('1')),
0c9836c0
ZJS
120 ('POINTINGSTICK_SENSITIVITY', INTEGER),
121 ('POINTINGSTICK_CONST_ACCEL', REAL),
c8ec393b 122 ('ID_INPUT_JOYSTICK_INTEGRATION', Or(('internal', 'external'))),
0c9836c0 123 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
086c001e 124 ('XKB_FIXED_LAYOUT', STRING),
8d9d1e3a 125 ('XKB_FIXED_VARIANT', STRING),
b698b5cf
BN
126 ('KEYBOARD_LED_NUMLOCK', Literal('0')),
127 ('KEYBOARD_LED_CAPSLOCK', Literal('0')),
7fdc73af 128 ('ACCEL_MOUNT_MATRIX', mount_matrix),
8d9d1e3a 129 )
0c9836c0
ZJS
130 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
131 for name, val in props]
132 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
133 - Suppress('=') -
134 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
8d9d1e3a 135 ]
0c9836c0
ZJS
136 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
137 - Suppress('=') -
138 Word(nums + ':')('VALUE')
8d9d1e3a 139 ]
0c9836c0 140
fe2a2a4f 141 grammar = Or(fixed_props + kbd_props + abs_props) + EOL
0c9836c0
ZJS
142
143 return grammar
144
145ERROR = False
146def error(fmt, *args, **kwargs):
147 global ERROR
148 ERROR = True
149 print(fmt.format(*args, **kwargs))
150
151def convert_properties(group):
152 matches = [m[0] for m in group.MATCHES]
153 props = [p[0] for p in group.PROPERTIES]
154 return matches, props
155
156def parse(fname):
157 grammar = hwdb_grammar()
158 try:
aeceb390
MP
159 with open(fname, 'r', encoding='UTF-8') as f:
160 parsed = grammar.parseFile(f)
0c9836c0
ZJS
161 except ParseBaseException as e:
162 error('Cannot parse {}: {}', fname, e)
163 return []
164 return [convert_properties(g) for g in parsed.GROUPS]
165
166def check_match_uniqueness(groups):
167 matches = sum((group[0] for group in groups), [])
168 matches.sort()
169 prev = None
170 for match in matches:
171 if match == prev:
172 error('Match {!r} is duplicated', match)
173 prev = match
174
175def check_one_default(prop, settings):
176 defaults = [s for s in settings if s.DEFAULT]
177 if len(defaults) > 1:
178 error('More than one star entry: {!r}', prop)
179
494d16aa
ZJS
180def check_one_mount_matrix(prop, value):
181 numbers = [s for s in value if s not in {';', ','}]
182 if len(numbers) != 9:
183 error('Wrong accel matrix: {!r}', prop)
184 try:
185 numbers = [abs(float(number)) for number in numbers]
186 except ValueError:
187 error('Wrong accel matrix: {!r}', prop)
188 bad_x, bad_y, bad_z = max(numbers[0:3]) == 0, max(numbers[3:6]) == 0, max(numbers[6:9]) == 0
189 if bad_x or bad_y or bad_z:
190 error('Mount matrix is all zero in {} row: {!r}',
191 'x' if bad_x else ('y' if bad_y else 'z'),
192 prop)
193
0c9836c0
ZJS
194def check_one_keycode(prop, value):
195 if value != '!' and ecodes is not None:
196 key = 'KEY_' + value.upper()
197 if key not in ecodes:
c6dce245
MS
198 key = value.upper()
199 if key not in ecodes:
200 error('Keycode {} unknown', key)
0c9836c0
ZJS
201
202def check_properties(groups):
203 grammar = property_grammar()
204 for matches, props in groups:
205 prop_names = set()
206 for prop in props:
207 # print('--', prop)
208 prop = prop.partition('#')[0].rstrip()
209 try:
210 parsed = grammar.parseString(prop)
211 except ParseBaseException as e:
212 error('Failed to parse: {!r}', prop)
213 continue
214 # print('{!r}'.format(parsed))
215 if parsed.NAME in prop_names:
216 error('Property {} is duplicated', parsed.NAME)
217 prop_names.add(parsed.NAME)
218 if parsed.NAME == 'MOUSE_DPI':
219 check_one_default(prop, parsed.VALUE.SETTINGS)
494d16aa
ZJS
220 elif parsed.NAME == 'ACCEL_MOUNT_MATRIX':
221 check_one_mount_matrix(prop, parsed.VALUE)
0c9836c0
ZJS
222 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
223 check_one_keycode(prop, parsed.VALUE)
224
225def print_summary(fname, groups):
226 print('{}: {} match groups, {} matches, {} properties'
227 .format(fname,
228 len(groups),
229 sum(len(matches) for matches, props in groups),
8d9d1e3a 230 sum(len(props) for matches, props in groups)))
0c9836c0
ZJS
231
232if __name__ == '__main__':
c3f6a561
ZJS
233 args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
234
235 for fname in args:
0c9836c0
ZJS
236 groups = parse(fname)
237 print_summary(fname, groups)
238 check_match_uniqueness(groups)
239 check_properties(groups)
240
241 sys.exit(ERROR)