From f996808760f9bb869c60b7b8810725ff49b9b394 Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Mon, 12 Aug 2019 13:22:18 +1000
Subject: [PATCH] Prefix undefined event codes with an underscore

Previously, event codes without a kernel define would resolve into properly
named objects, e.g. libevdev.EV_REL.REL_0B

This breaks the API whenever the kernel introduces a new define because that
named object will just disappear. e.g. the above REL_0B is now
REL_WHEEL_HI_RES.

Since the undefined names aren't supposed to be used by callers directly
anyway, rename them to be underscored instead: libevdev.EV_REL._REL_0B
This means we can still return them from evbit() and events() but the
underscore signals a private API.

The actual name remains the same, so print(_REL_0B.name) will not have an
underscore. This is to avoid bugs/breakage where the caller uses the 3-lettter
prefix for other purposes.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
---
 libevdev/const.py  | 24 +++++++++++++++++++-----
 test/test_const.py |  4 ++++
 2 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/libevdev/const.py b/libevdev/const.py
index eeb19ee..b68c517 100644
--- a/libevdev/const.py
+++ b/libevdev/const.py
@@ -91,7 +91,17 @@ class EventCode(EvdevBit):
 
     .. attribute:: name
 
-        The string name of this event code
+        The string name of this event code. Where the name is not defined, a
+        fake name is generated by this module, consisting of the prefix and
+        the uppercase hexadecimal value, e.g. ``REL_0B``. These generated
+        names (see :func:`EventCode.is_defined()`) should never be used as
+        input.
+
+        Note that even defined names are not a stable API. From time to time
+        the kernel introduces a new name and aliases the old one. The same
+        numeric event code may then return a different name than before.
+        This does not affect the :func:`evbit()` function which continues to
+        return the correct event code.
 
     .. attribute:: type
 
@@ -356,15 +366,19 @@ def _load_consts():
         codes = []
         for c in range(cmax + 1):
             cname = Libevdev.event_to_name(t, c)
+            name = cname
             has_name = cname is not None
-            # For those without names, we just use the type name plus
-            # hexcode
+            # For those without names, we use the type name plus
+            # hexcode for the actual name, but a prefixing underscore for
+            # the class name (it's not stable API).
+            # i.e. libedev.EV_REL._REL_0B.name == 'REL_0B'
             if cname is None:
-                cname = "{}_{:02X}".format(tname[3:], c)
+                name = "{}_{:02X}".format(tname[3:], c)
+                cname = "_{}".format(name)
 
             new_class = type(cname, (EventCode, ),
                              {'type': type_object,
-                              'name': cname,
+                              'name': name,
                               'value': c,
                               'is_defined': has_name})
             code_object = new_class()
diff --git a/test/test_const.py b/test/test_const.py
index 6563d56..3c9ce28 100644
--- a/test/test_const.py
+++ b/test/test_const.py
@@ -97,6 +97,10 @@ class TestEventBits(unittest.TestCase):
                 else:
                     self.assertEqual(c.name, fake_name)
 
+    def test_evcode_undefined(self):
+        self.assertEqual(evbit('SYN_04'), libevdev.EV_SYN._SYN_04)
+        self.assertEqual(libevdev.EV_SYN._SYN_04.name, 'SYN_04')
+
     def test_propbit_string(self):
         self.assertEqual(propbit('INPUT_PROP_POINTER'), libevdev.INPUT_PROP_POINTER)
         self.assertEqual(propbit('INPUT_PROP_DIRECT'), libevdev.INPUT_PROP_DIRECT)
-- 
GitLab