]> git.ipfire.org Git - thirdparty/systemd.git/blame - hwdb/parse_hwdb.py
hwdb: add Lenovo X1 Tablet pointing stick speed fix (#4128)
[thirdparty/systemd.git] / hwdb / parse_hwdb.py
CommitLineData
c3f6a561 1#!/usr/bin/python3
0c9836c0
ZJS
2# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
3#
4# This file is part of systemd.
5#
6# Copyright 2016 Zbigniew Jędrzejewski-Szmek
7#
8# systemd is free software; you can redistribute it and/or modify it
9# under the terms of the GNU Lesser General Public License as published by
10# the Free Software Foundation; either version 2.1 of the License, or
11# (at your option) any later version.
12#
13# systemd is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16# Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with systemd; If not, see <http://www.gnu.org/licenses/>.
20
0c9836c0 21import functools
c3f6a561
ZJS
22import glob
23import string
24import sys
25import os
0c9836c0
ZJS
26
27try:
28 from pyparsing import (Word, White, Literal, ParserElement, Regex,
29 LineStart, LineEnd,
30 ZeroOrMore, OneOrMore, Combine, Or, Optional, Suppress, Group,
31 nums, alphanums, printables,
32 stringEnd, pythonStyleComment,
33 ParseBaseException)
34except ImportError:
c3f6a561
ZJS
35 print('pyparsing is not available')
36 sys.exit(77)
0c9836c0
ZJS
37
38try:
39 from evdev.ecodes import ecodes
40except ImportError:
41 ecodes = None
42 print('WARNING: evdev is not available')
43
44EOL = LineEnd().suppress()
45EMPTYLINE = LineStart() + LineEnd()
46COMMENTLINE = pythonStyleComment + EOL
47INTEGER = Word(nums)
48REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
49UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
50
51TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
52 'evdev': ('name', 'atkbd', 'input'),
53 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
54 'keyboard': ('name', ),
55 }
56
57@functools.lru_cache()
58def hwdb_grammar():
59 ParserElement.setDefaultWhitespaceChars('')
60
61 prefix = Or(category + ':' + Or(conn) + ':'
62 for category, conn in TYPES.items())
63 matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
64 propertyline = (White(' ', exact=1).suppress() +
65 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! ') - Optional(pythonStyleComment)) +
66 EOL)
67 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
68
69 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
70 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
71 (EMPTYLINE ^ stringEnd()).suppress() )
72 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
73
74 grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
75
76 return grammar
77
78@functools.lru_cache()
79def property_grammar():
80 ParserElement.setDefaultWhitespaceChars(' ')
81
82 setting = Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ')
83 props = (('MOUSE_DPI', Group(OneOrMore(setting('SETTINGS*')))),
84 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
a6a8e60b 85 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
0c9836c0
ZJS
86 ('ID_INPUT_TRACKBALL', Literal('1')),
87 ('POINTINGSTICK_SENSITIVITY', INTEGER),
88 ('POINTINGSTICK_CONST_ACCEL', REAL),
89 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
90 )
91 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
92 for name, val in props]
93 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
94 - Suppress('=') -
95 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
96 ]
97 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
98 - Suppress('=') -
99 Word(nums + ':')('VALUE')
100 ]
101
102 grammar = Or(fixed_props + kbd_props + abs_props)
103
104 return grammar
105
106ERROR = False
107def error(fmt, *args, **kwargs):
108 global ERROR
109 ERROR = True
110 print(fmt.format(*args, **kwargs))
111
112def convert_properties(group):
113 matches = [m[0] for m in group.MATCHES]
114 props = [p[0] for p in group.PROPERTIES]
115 return matches, props
116
117def parse(fname):
118 grammar = hwdb_grammar()
119 try:
120 parsed = grammar.parseFile(fname)
121 except ParseBaseException as e:
122 error('Cannot parse {}: {}', fname, e)
123 return []
124 return [convert_properties(g) for g in parsed.GROUPS]
125
126def check_match_uniqueness(groups):
127 matches = sum((group[0] for group in groups), [])
128 matches.sort()
129 prev = None
130 for match in matches:
131 if match == prev:
132 error('Match {!r} is duplicated', match)
133 prev = match
134
135def check_one_default(prop, settings):
136 defaults = [s for s in settings if s.DEFAULT]
137 if len(defaults) > 1:
138 error('More than one star entry: {!r}', prop)
139
140def check_one_keycode(prop, value):
141 if value != '!' and ecodes is not None:
142 key = 'KEY_' + value.upper()
143 if key not in ecodes:
144 error('Keycode {} unknown', key)
145
146def check_properties(groups):
147 grammar = property_grammar()
148 for matches, props in groups:
149 prop_names = set()
150 for prop in props:
151 # print('--', prop)
152 prop = prop.partition('#')[0].rstrip()
153 try:
154 parsed = grammar.parseString(prop)
155 except ParseBaseException as e:
156 error('Failed to parse: {!r}', prop)
157 continue
158 # print('{!r}'.format(parsed))
159 if parsed.NAME in prop_names:
160 error('Property {} is duplicated', parsed.NAME)
161 prop_names.add(parsed.NAME)
162 if parsed.NAME == 'MOUSE_DPI':
163 check_one_default(prop, parsed.VALUE.SETTINGS)
164 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
165 check_one_keycode(prop, parsed.VALUE)
166
167def print_summary(fname, groups):
168 print('{}: {} match groups, {} matches, {} properties'
169 .format(fname,
170 len(groups),
171 sum(len(matches) for matches, props in groups),
172 sum(len(props) for matches, props in groups),
173 ))
174
175if __name__ == '__main__':
c3f6a561
ZJS
176 args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
177
178 for fname in args:
0c9836c0
ZJS
179 groups = parse(fname)
180 print_summary(fname, groups)
181 check_match_uniqueness(groups)
182 check_properties(groups)
183
184 sys.exit(ERROR)