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