]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - hwdb/parse_hwdb.py
build-sys: use #if Y instead of #ifdef Y everywhere
[thirdparty/systemd.git] / hwdb / parse_hwdb.py
old mode 100644 (file)
new mode 100755 (executable)
index 773513e..a25ac8d
@@ -1,35 +1,46 @@
-#  -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
+#!/usr/bin/env python3
+# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
 #
-#  This file is part of systemd.
+# This file is part of systemd. It is distrubuted under the MIT license, see
+# below.
 #
-#  Copyright 2016 Zbigniew Jędrzejewski-Szmek
+# Copyright 2016 Zbigniew Jędrzejewski-Szmek
 #
-#  systemd is free software; you can redistribute it and/or modify it
-#  under the terms of the GNU Lesser General Public License as published by
-#  the Free Software Foundation; either version 2.1 of the License, or
-#  (at your option) any later version.
+# The MIT License (MIT)
 #
-#  systemd is distributed in the hope that it will be useful, but
-#  WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#  Lesser General Public License for more details.
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
 #
-#  You should have received a copy of the GNU Lesser General Public License
-#  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-
-import sys
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import glob
 import string
-import functools
+import sys
+import os
 
 try:
     from pyparsing import (Word, White, Literal, ParserElement, Regex,
                            LineStart, LineEnd,
-                           ZeroOrMore, OneOrMore, Combine, Or, Optional, Suppress, Group,
+                           OneOrMore, Combine, Or, Optional, Suppress, Group,
                            nums, alphanums, printables,
-                           stringEnd, pythonStyleComment,
+                           stringEnd, pythonStyleComment, QuotedString,
                            ParseBaseException)
 except ImportError:
-    sys.exit('pyparsing is not available')
+    print('pyparsing is not available')
+    sys.exit(77)
 
 try:
     from evdev.ecodes import ecodes
@@ -37,20 +48,30 @@ except ImportError:
     ecodes = None
     print('WARNING: evdev is not available')
 
+try:
+    from functools import lru_cache
+except ImportError:
+    # don't do caching on old python
+    lru_cache = lambda: (lambda f: f)
+
 EOL = LineEnd().suppress()
-EMPTYLINE = LineStart() + LineEnd()
+EMPTYLINE = LineEnd()
 COMMENTLINE = pythonStyleComment + EOL
 INTEGER = Word(nums)
+STRING =  QuotedString('"')
 REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
+SIGNED_REAL = Combine(Optional(Word('-+')) + REAL)
 UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
 
 TYPES = {'mouse':    ('usb', 'bluetooth', 'ps2', '*'),
          'evdev':    ('name', 'atkbd', 'input'),
          'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
+         'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'),
          'keyboard': ('name', ),
-         }
+         'sensor':   ('modalias', ),
+        }
 
-@functools.lru_cache()
+@lru_cache()
 def hwdb_grammar():
     ParserElement.setDefaultWhitespaceChars('')
 
@@ -58,43 +79,57 @@ def hwdb_grammar():
                 for category, conn in TYPES.items())
     matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
     propertyline = (White(' ', exact=1).suppress() +
-                    Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! ') - Optional(pythonStyleComment)) +
+                    Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.!-;, "') - Optional(pythonStyleComment)) +
                     EOL)
     propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
 
     group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
              OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
-             (EMPTYLINE ^ stringEnd()).suppress() )
+             (EMPTYLINE ^ stringEnd()).suppress())
     commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
 
     grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
 
     return grammar
 
-@functools.lru_cache()
+@lru_cache()
 def property_grammar():
     ParserElement.setDefaultWhitespaceChars(' ')
 
-    setting = Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ')
-    props = (('MOUSE_DPI', Group(OneOrMore(setting('SETTINGS*')))),
+    dpi_setting = (Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ'))('SETTINGS*')
+    mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL
+    mount_matrix = (mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX')
+
+    props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))),
              ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
+             ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
+             ('MOUSE_WHEEL_CLICK_COUNT', INTEGER),
+             ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER),
              ('ID_INPUT_TRACKBALL', Literal('1')),
+             ('MOUSE_WHEEL_TILT_HORIZONTAL', Literal('1')),
+             ('MOUSE_WHEEL_TILT_VERTICAL', Literal('1')),
              ('POINTINGSTICK_SENSITIVITY', INTEGER),
              ('POINTINGSTICK_CONST_ACCEL', REAL),
+             ('ID_INPUT_JOYSTICK_INTEGRATION', Or(('internal', 'external'))),
              ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
-    )
+             ('XKB_FIXED_LAYOUT', STRING),
+             ('XKB_FIXED_VARIANT', STRING),
+             ('KEYBOARD_LED_NUMLOCK', Literal('0')),
+             ('KEYBOARD_LED_CAPSLOCK', Literal('0')),
+             ('ACCEL_MOUNT_MATRIX', mount_matrix),
+            )
     fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
                    for name, val in props]
     kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
                  - Suppress('=') -
                  ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
-                 ]
+                ]
     abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
                  - Suppress('=') -
                  Word(nums + ':')('VALUE')
-                 ]
+                ]
 
-    grammar = Or(fixed_props + kbd_props + abs_props)
+    grammar = Or(fixed_props + kbd_props + abs_props) + EOL
 
     return grammar
 
@@ -112,7 +147,8 @@ def convert_properties(group):
 def parse(fname):
     grammar = hwdb_grammar()
     try:
-        parsed = grammar.parseFile(fname)
+        with open(fname, 'r', encoding='UTF-8') as f:
+            parsed = grammar.parseFile(f)
     except ParseBaseException as e:
         error('Cannot parse {}: {}', fname, e)
         return []
@@ -132,11 +168,27 @@ def check_one_default(prop, settings):
     if len(defaults) > 1:
         error('More than one star entry: {!r}', prop)
 
+def check_one_mount_matrix(prop, value):
+    numbers = [s for s in value if s not in {';', ','}]
+    if len(numbers) != 9:
+        error('Wrong accel matrix: {!r}', prop)
+    try:
+        numbers = [abs(float(number)) for number in numbers]
+    except ValueError:
+        error('Wrong accel matrix: {!r}', prop)
+    bad_x, bad_y, bad_z = max(numbers[0:3]) == 0, max(numbers[3:6]) == 0, max(numbers[6:9]) == 0
+    if bad_x or bad_y or bad_z:
+        error('Mount matrix is all zero in {} row: {!r}',
+              'x' if bad_x else ('y' if bad_y else 'z'),
+              prop)
+
 def check_one_keycode(prop, value):
     if value != '!' and ecodes is not None:
         key = 'KEY_' + value.upper()
         if key not in ecodes:
-            error('Keycode {} unknown', key)
+            key = value.upper()
+            if key not in ecodes:
+                error('Keycode {} unknown', key)
 
 def check_properties(groups):
     grammar = property_grammar()
@@ -156,6 +208,8 @@ def check_properties(groups):
             prop_names.add(parsed.NAME)
             if parsed.NAME == 'MOUSE_DPI':
                 check_one_default(prop, parsed.VALUE.SETTINGS)
+            elif parsed.NAME == 'ACCEL_MOUNT_MATRIX':
+                check_one_mount_matrix(prop, parsed.VALUE)
             elif parsed.NAME.startswith('KEYBOARD_KEY_'):
                 check_one_keycode(prop, parsed.VALUE)
 
@@ -164,11 +218,12 @@ def print_summary(fname, groups):
           .format(fname,
                   len(groups),
                   sum(len(matches) for matches, props in groups),
-                  sum(len(props) for matches, props in groups),
-          ))
+                  sum(len(props) for matches, props in groups)))
 
 if __name__ == '__main__':
-    for fname in sys.argv[1:]:
+    args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
+
+    for fname in args:
         groups = parse(fname)
         print_summary(fname, groups)
         check_match_uniqueness(groups)