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