]> git.ipfire.org Git - thirdparty/systemd.git/blame - hwdb/parse_hwdb.py
hwdb_parser: add support for ACCEL_MOUNT_MATRIX
[thirdparty/systemd.git] / hwdb / parse_hwdb.py
CommitLineData
c3f6a561 1#!/usr/bin/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'),
69 'keyboard': ('name', ),
7fdc73af 70 'sensor': ('modalias', ),
8d9d1e3a 71 }
0c9836c0 72
1258f088 73@lru_cache()
0c9836c0
ZJS
74def hwdb_grammar():
75 ParserElement.setDefaultWhitespaceChars('')
76
77 prefix = Or(category + ':' + Or(conn) + ':'
78 for category, conn in TYPES.items())
79 matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
80 propertyline = (White(' ', exact=1).suppress() +
7fdc73af 81 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.!-;, "') - Optional(pythonStyleComment)) +
0c9836c0
ZJS
82 EOL)
83 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
84
85 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
86 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
8d9d1e3a 87 (EMPTYLINE ^ stringEnd()).suppress())
0c9836c0
ZJS
88 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
89
90 grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
91
92 return grammar
93
1258f088 94@lru_cache()
0c9836c0
ZJS
95def property_grammar():
96 ParserElement.setDefaultWhitespaceChars(' ')
97
7fdc73af
ZJS
98 dpi_setting = (Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ'))('SETTINGS*')
99 mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL
100 mount_matrix = (mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX')
101
102 props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))),
0c9836c0 103 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
a6a8e60b 104 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
9107a337
PH
105 ('MOUSE_WHEEL_CLICK_COUNT', INTEGER),
106 ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER),
0c9836c0
ZJS
107 ('ID_INPUT_TRACKBALL', Literal('1')),
108 ('POINTINGSTICK_SENSITIVITY', INTEGER),
109 ('POINTINGSTICK_CONST_ACCEL', REAL),
110 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
086c001e 111 ('XKB_FIXED_LAYOUT', STRING),
8d9d1e3a 112 ('XKB_FIXED_VARIANT', STRING),
7fdc73af 113 ('ACCEL_MOUNT_MATRIX', mount_matrix),
8d9d1e3a 114 )
0c9836c0
ZJS
115 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
116 for name, val in props]
117 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
118 - Suppress('=') -
119 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
8d9d1e3a 120 ]
0c9836c0
ZJS
121 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
122 - Suppress('=') -
123 Word(nums + ':')('VALUE')
8d9d1e3a 124 ]
0c9836c0
ZJS
125
126 grammar = Or(fixed_props + kbd_props + abs_props)
127
128 return grammar
129
130ERROR = False
131def error(fmt, *args, **kwargs):
132 global ERROR
133 ERROR = True
134 print(fmt.format(*args, **kwargs))
135
136def convert_properties(group):
137 matches = [m[0] for m in group.MATCHES]
138 props = [p[0] for p in group.PROPERTIES]
139 return matches, props
140
141def parse(fname):
142 grammar = hwdb_grammar()
143 try:
aeceb390
MP
144 with open(fname, 'r', encoding='UTF-8') as f:
145 parsed = grammar.parseFile(f)
0c9836c0
ZJS
146 except ParseBaseException as e:
147 error('Cannot parse {}: {}', fname, e)
148 return []
149 return [convert_properties(g) for g in parsed.GROUPS]
150
151def check_match_uniqueness(groups):
152 matches = sum((group[0] for group in groups), [])
153 matches.sort()
154 prev = None
155 for match in matches:
156 if match == prev:
157 error('Match {!r} is duplicated', match)
158 prev = match
159
160def check_one_default(prop, settings):
161 defaults = [s for s in settings if s.DEFAULT]
162 if len(defaults) > 1:
163 error('More than one star entry: {!r}', prop)
164
165def check_one_keycode(prop, value):
166 if value != '!' and ecodes is not None:
167 key = 'KEY_' + value.upper()
168 if key not in ecodes:
169 error('Keycode {} unknown', key)
170
171def check_properties(groups):
172 grammar = property_grammar()
173 for matches, props in groups:
174 prop_names = set()
175 for prop in props:
176 # print('--', prop)
177 prop = prop.partition('#')[0].rstrip()
178 try:
179 parsed = grammar.parseString(prop)
180 except ParseBaseException as e:
181 error('Failed to parse: {!r}', prop)
182 continue
183 # print('{!r}'.format(parsed))
184 if parsed.NAME in prop_names:
185 error('Property {} is duplicated', parsed.NAME)
186 prop_names.add(parsed.NAME)
187 if parsed.NAME == 'MOUSE_DPI':
188 check_one_default(prop, parsed.VALUE.SETTINGS)
189 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
190 check_one_keycode(prop, parsed.VALUE)
191
192def print_summary(fname, groups):
193 print('{}: {} match groups, {} matches, {} properties'
194 .format(fname,
195 len(groups),
196 sum(len(matches) for matches, props in groups),
8d9d1e3a 197 sum(len(props) for matches, props in groups)))
0c9836c0
ZJS
198
199if __name__ == '__main__':
c3f6a561
ZJS
200 args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
201
202 for fname in args:
0c9836c0
ZJS
203 groups = parse(fname)
204 print_summary(fname, groups)
205 check_match_uniqueness(groups)
206 check_properties(groups)
207
208 sys.exit(ERROR)