scanner: add the protocol name so we can compile some #defines

The protocol name on an interface is a fixed string that is part of
the ABI since it's used in a few messages (e.g.
ei_handshake.interface_version). To avoid typos, let's expose that
string in the scanner and #define it in the generated sources.
This commit is contained in:
Peter Hutterer 2023-04-06 09:25:05 +10:00
parent fa07430683
commit 5aad9fd777
12 changed files with 135 additions and 74 deletions

View file

@ -46,8 +46,8 @@ variables:
# changing these will force rebuilding the associated image
# Note: these tags have no meaning and are not tied to a particular
# libinput version
FEDORA_TAG: '2023-03-17.0'
DEBIAN_TAG: '2023-03-17.0'
FEDORA_TAG: '2023-04-06.0'
DEBIAN_TAG: '2023-04-06.0'
FDO_UPSTREAM_REPO: libinput/libei
@ -174,7 +174,7 @@ fedora:37@container-prep:
FDO_DISTRIBUTION_VERSION: '37'
FDO_DISTRIBUTION_PACKAGES: $FEDORA_PACKAGES
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
FDO_DISTRIBUTION_EXEC: 'pip install structlog'
FDO_DISTRIBUTION_EXEC: 'pip install structlog strenum'
debian:bullseye@container-prep:
extends:
@ -186,7 +186,7 @@ debian:bullseye@container-prep:
FDO_DISTRIBUTION_VERSION: 'bullseye'
FDO_DISTRIBUTION_PACKAGES: $DEBIAN_PACKAGES
FDO_DISTRIBUTION_TAG: $DEBIAN_TAG
FDO_DISTRIBUTION_EXEC: 'pip install structlog'
FDO_DISTRIBUTION_EXEC: 'pip install structlog strenum'

View file

@ -183,7 +183,7 @@ python-ruff:
FDO_DISTRIBUTION_VERSION: '{{version}}'
FDO_DISTRIBUTION_PACKAGES: ${{distro.name.upper()}}_PACKAGES
FDO_DISTRIBUTION_TAG: ${{distro.name.upper()}}_TAG
FDO_DISTRIBUTION_EXEC: 'pip install structlog'
FDO_DISTRIBUTION_EXEC: 'pip install structlog strenum'
{% endfor %}
{% endfor %}

View file

@ -3,7 +3,7 @@
#
# We're happy to rebuild all containers when one changes.
.default_tag: &default_tag '2023-03-17.0'
.default_tag: &default_tag '2023-04-06.0'
distributions:
- name: fedora

View file

@ -299,7 +299,8 @@ class Enum:
@attr.s
class Interface:
name: str = attr.ib()
name: str = attr.ib() # mangled name, e.g. ei_pointer or eis_pointer
protocol_name: str = attr.ib() # name as in the XML, e.g. ei_pointer
version: int = attr.ib()
requests: List[Request] = attr.ib(init=False, factory=list)
events: List[Event] = attr.ib(init=False, factory=list)
@ -388,9 +389,11 @@ class Interface:
return snake2camel(self.name)
@classmethod
def create(cls, name: str, version: int, mode: str = "ei") -> "Interface":
def create(
cls, name: str, protocol_name: str, version: int, mode: str = "ei"
) -> "Interface":
assert mode in ["ei", "eis", "brei"]
return cls(name=name, version=version, mode=mode)
return cls(name=name, protocol_name=protocol_name, version=version, mode=mode)
@attr.s
@ -463,13 +466,19 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
self.location,
)
name = Interface.mangle_name(name, self.component)
protocol_name = name
interface_name = Interface.mangle_name(name, self.component)
# We only create the interface on the first run, in subsequent runs we
# re-use them so we can cross reference correctly
if self._run_counter > 1:
intf = self.interface_by_name(name)
intf = self.interface_by_name(interface_name)
else:
intf = Interface.create(name=name, version=version, mode=self.component)
intf = Interface.create(
name=interface_name,
protocol_name=protocol_name,
version=version,
mode=self.component,
)
self.interfaces.append(intf)
self.current_interface = intf

View file

@ -51,6 +51,10 @@ struct {{interface.name}};
extern const struct brei_interface {{interface.name}}_proto_interface;
{% endfor %}
{% for interface in interfaces %}
#define {{interface.name.upper()}}_INTERFACE_NAME "{{interface.protocol_name}}"
{% endfor %}
{% for interface in interfaces %}
/** {{interface.name}} **/

View file

@ -393,18 +393,18 @@ handle_msg_interface(struct ei_device *device, object_id_t id, const char *name,
{
DISCONNECT_IF_INVALID_ID(device, id);
if (streq(name, "ei_pointer")) {
if (streq(name, EI_POINTER_INTERFACE_NAME)) {
if (device->pointer)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_pointer interface object on device");
device->pointer = ei_pointer_new(device, id, version);
} else if (streq(name, "ei_keyboard")) {
} else if (streq(name, EI_KEYBOARD_INTERFACE_NAME)) {
if (device->keyboard)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_keyboard interface object on device");
device->keyboard = ei_keyboard_new(device, id, version);
} else if (streq(name, "ei_touchscreen")) {
} else if (streq(name, EI_TOUCHSCREEN_INTERFACE_NAME)) {
if (device->touchscreen)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_touchscreen interface object on device");

View file

@ -86,14 +86,14 @@ ei_handshake_initialize(struct ei_handshake *setup, uint32_t version)
ei_handshake_request_name(setup, ei->name);
if (version >= EI_HANDSHAKE_REQUEST_INTERFACE_VERSION_SINCE_VERSION) {
ei_handshake_request_interface_version(setup, "ei_connection", v->ei_connection);
ei_handshake_request_interface_version(setup, "ei_callback", v->ei_callback);
ei_handshake_request_interface_version(setup, "ei_pingpong", v->ei_pingpong);
ei_handshake_request_interface_version(setup, "ei_seat", v->ei_seat);
ei_handshake_request_interface_version(setup, "ei_device", v->ei_device);
ei_handshake_request_interface_version(setup, "ei_pointer", v->ei_pointer);
ei_handshake_request_interface_version(setup, "ei_keyboard", v->ei_keyboard);
ei_handshake_request_interface_version(setup, "ei_touchscreen", v->ei_touchscreen);
ei_handshake_request_interface_version(setup, EI_CONNECTION_INTERFACE_NAME, v->ei_connection);
ei_handshake_request_interface_version(setup, EI_CALLBACK_INTERFACE_NAME, v->ei_callback);
ei_handshake_request_interface_version(setup, EI_PINGPONG_INTERFACE_NAME, v->ei_pingpong);
ei_handshake_request_interface_version(setup, EI_SEAT_INTERFACE_NAME, v->ei_seat);
ei_handshake_request_interface_version(setup, EI_DEVICE_INTERFACE_NAME, v->ei_device);
ei_handshake_request_interface_version(setup, EI_POINTER_INTERFACE_NAME, v->ei_pointer);
ei_handshake_request_interface_version(setup, EI_KEYBOARD_INTERFACE_NAME, v->ei_keyboard);
ei_handshake_request_interface_version(setup, EI_TOUCHSCREEN_INTERFACE_NAME, v->ei_touchscreen);
}
ei_handshake_request_finish(setup);
@ -125,7 +125,7 @@ handle_msg_interface_version(struct ei_handshake *setup, const char *name, uint3
struct ei *ei = ei_handshake_get_context(setup);
struct ei_interface_versions *v = &ei->interface_versions;
if (streq(name, "ei_handshake")) {
if (streq(name, EI_HANDSHAKE_INTERFACE_NAME)) {
/* EIS shouldn't send this anyway, let's ignore this */
}
#define VERSION_UPDATE(iface_) if (streq(name, #iface_)) v->iface_ = min(version, v->iface_);

View file

@ -766,14 +766,16 @@ eis_device_add(struct eis_device *device)
eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) {
device->pointer = eis_pointer_new(device);
rc = eis_device_event_interface(device, eis_pointer_get_id(device->pointer),
"ei_pointer", eis_pointer_get_version(device->pointer));
EIS_POINTER_INTERFACE_NAME,
eis_pointer_get_version(device->pointer));
if (rc < 0)
goto out;
}
if (eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) {
device->keyboard = eis_keyboard_new(device);
rc = eis_device_event_interface(device, eis_keyboard_get_id(device->keyboard),
"ei_keyboard", eis_keyboard_get_version(device->keyboard));
EIS_KEYBOARD_INTERFACE_NAME,
eis_keyboard_get_version(device->keyboard));
if (rc < 0)
goto out;
@ -786,7 +788,8 @@ eis_device_add(struct eis_device *device)
if (eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) {
device->touchscreen = eis_touchscreen_new(device);
rc = eis_device_event_interface(device, eis_touchscreen_get_id(device->touchscreen),
"ei_touchscreen", eis_touchscreen_get_version(device->touchscreen));
EIS_TOUCHSCREEN_INTERFACE_NAME,
eis_touchscreen_get_version(device->touchscreen));
if (rc < 0)
goto out;
}

View file

@ -120,7 +120,7 @@ client_msg_finish(struct eis_handshake *setup)
/* ei_callback needs a client-created object, so tell the client
* about our version */
eis_handshake_event_interface_version(setup, "ei_callback",
eis_handshake_event_interface_version(setup, EIS_CALLBACK_INTERFACE_NAME,
setup->client_versions.ei_callback);
eis_client_setup_done(client, setup->name, setup->is_sender, &setup->client_versions);
@ -188,9 +188,9 @@ client_msg_interface_version(struct eis_handshake *setup, const char *name, uint
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Missing handshake versions");
if (streq(name, "ei_handshake"))
if (streq(name, EIS_HANDSHAKE_INTERFACE_NAME))
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"ei_handshake may not be used in interface_version");
"%s may not be used in interface_version", name);
struct eis_client *client = eis_handshake_get_client(setup);
struct eis *eis = eis_client_get_context(client);

View file

@ -20,6 +20,10 @@
from typing import Any, Callable, Generator, Tuple
from enum import IntEnum
try:
from enum import StrEnum
except ImportError:
from strenum import StrEnum
import attr
import binascii
@ -137,6 +141,12 @@ class Context:
return o
class InterfaceName(StrEnum):
{% for interface in interfaces %}
{{ interface.name.upper() }} = "{{interface.protocol_name}}"
{% endfor %}
@attr.s
class Interface:
object_id: int = attr.ib(repr=lambda id: f"{id:#x}")

View file

@ -46,7 +46,11 @@ required_python_modules = ['pytest', 'attr', 'structlog']
if build_oeffis
required_python_modules += ['dbusmock']
endif
pymod.find_installation('python3', modules: required_python_modules)
python = pymod.find_installation('python3', modules: required_python_modules)
if python.language_version().version_compare('< 3.11')
pymod.find_installation('python3', modules: ['strenum'])
endif
pytest = find_program('pytest-3', 'pytest')
pytest_args = ['--verbose', '--log-level=DEBUG']

View file

@ -39,6 +39,7 @@ try:
hexlify,
Context,
Interface,
InterfaceName,
MessageHeader,
EiCallback,
EiConnection,
@ -150,7 +151,7 @@ class Ei:
self.send(self.connection.Sync(cb.object_id))
return self.wait_for(
lambda: cb not in self.find_objects_by_interface("ei_callback")
lambda: cb not in self.find_objects_by_interface(InterfaceName.EI_CALLBACK)
)
@property
@ -165,15 +166,15 @@ class Ei:
self.send(setup.ContextType(EiHandshake.EiContextType.SENDER))
self.send(setup.Name("test client"))
self.send(
setup.InterfaceVersion("ei_connection", VERSION_V(1))
setup.InterfaceVersion(InterfaceName.EI_CONNECTION, VERSION_V(1))
) # this one is required
self.send(setup.InterfaceVersion("ei_callback", VERSION_V(1)))
self.send(setup.InterfaceVersion("ei_pingpong", VERSION_V(1)))
self.send(setup.InterfaceVersion("ei_seat", VERSION_V(1)))
self.send(setup.InterfaceVersion("ei_device", VERSION_V(1)))
self.send(setup.InterfaceVersion("ei_pointer", VERSION_V(1)))
self.send(setup.InterfaceVersion("ei_keyboard", VERSION_V(1)))
self.send(setup.InterfaceVersion("ei_touchscreen", VERSION_V(1)))
self.send(setup.InterfaceVersion(InterfaceName.EI_CALLBACK, VERSION_V(1)))
self.send(setup.InterfaceVersion(InterfaceName.EI_PINGPONG, VERSION_V(1)))
self.send(setup.InterfaceVersion(InterfaceName.EI_SEAT, VERSION_V(1)))
self.send(setup.InterfaceVersion(InterfaceName.EI_DEVICE, VERSION_V(1)))
self.send(setup.InterfaceVersion(InterfaceName.EI_POINTER, VERSION_V(1)))
self.send(setup.InterfaceVersion(InterfaceName.EI_KEYBOARD, VERSION_V(1)))
self.send(setup.InterfaceVersion(InterfaceName.EI_TOUCHSCREEN, VERSION_V(1)))
self.send(setup.Finish())
self.dispatch()
@ -388,7 +389,7 @@ class TestEiProtocol:
if call.name == "InterfaceVersion":
announced_interfaces.append((call.args["name"], call.args["version"]))
assert ("ei_callback", 1) in announced_interfaces
assert (InterfaceName.EI_CALLBACK, 1) in announced_interfaces
eis.terminate()
@ -417,7 +418,11 @@ class TestEiProtocol:
# or immediately disconnects us after the .done request
ei.dispatch()
for interface in ["ei_connection", "ei_callback", "ei_pingpong"]:
for interface in [
InterfaceName.EI_CONNECTION,
InterfaceName.EI_CALLBACK,
InterfaceName.EI_PINGPONG,
]:
ei.send(
ei.handshake.InterfaceVersion(interface, VERSION_V(1))
) # these are required
@ -501,13 +506,19 @@ class TestEiProtocol:
ei.send(setup.HandshakeVersion(VERSION_V(1)))
ei.send(setup.ContextType(EiHandshake.EiContextType.SENDER))
ei.send(setup.Name("test client"))
for interface in ["ei_connection", "ei_callback", "ei_pingpong"]:
for interface in [
InterfaceName.EI_CONNECTION,
InterfaceName.EI_CALLBACK,
InterfaceName.EI_PINGPONG,
]:
ei.send(
setup.InterfaceVersion(interface, VERSION_V(1))
) # these are required
ei.send(setup.InterfaceVersion("ei_seat", VERSION_V(100))) # excessive version
ei.send(
setup.InterfaceVersion("ei_device", VERSION_V(100))
setup.InterfaceVersion(InterfaceName.EI_SEAT, VERSION_V(100))
) # excessive version
ei.send(
setup.InterfaceVersion(InterfaceName.EI_DEVICE, VERSION_V(100))
) # excessive version
ei.send(setup.Finish())
ei.dispatch()
@ -562,7 +573,11 @@ class TestEiProtocol:
ei.send(setup.HandshakeVersion(VERSION_V(1)))
ei.send(setup.ContextType(EiHandshake.EiContextType.SENDER))
ei.send(setup.Name("test client"))
for interface in ["ei_connection", "ei_callback", "ei_pingpong"]:
for interface in [
InterfaceName.EI_CONNECTION,
InterfaceName.EI_CALLBACK,
InterfaceName.EI_PINGPONG,
]:
ei.send(
setup.InterfaceVersion(interface, VERSION_V(1))
) # these are required
@ -719,12 +734,12 @@ class TestEiProtocol:
@pytest.mark.parametrize(
"missing_interface",
(
"ei_callback",
"ei_connection",
"ei_pingpong",
"ei_seat",
"ei_device",
"ei_pointer",
InterfaceName.EI_CALLBACK,
InterfaceName.EI_CONNECTION,
InterfaceName.EI_PINGPONG,
InterfaceName.EI_SEAT,
InterfaceName.EI_DEVICE,
InterfaceName.EI_POINTER,
),
)
def test_connect_without_ei_interfaces(self, eis, missing_interface):
@ -737,14 +752,14 @@ class TestEiProtocol:
ei.send(setup.Name("test client"))
for interface in [
"ei_connection",
"ei_callback",
"ei_pingpong",
"ei_seat",
"ei_device",
"ei_pointer",
"ei_keyboard",
"ei_touchscreen",
InterfaceName.EI_CONNECTION,
InterfaceName.EI_CALLBACK,
InterfaceName.EI_PINGPONG,
InterfaceName.EI_SEAT,
InterfaceName.EI_DEVICE,
InterfaceName.EI_POINTER,
InterfaceName.EI_KEYBOARD,
InterfaceName.EI_TOUCHSCREEN,
]:
if interface != missing_interface:
ei.send(setup.InterfaceVersion(interface, 1))
@ -761,11 +776,11 @@ class TestEiProtocol:
try:
def on_device(seat, id, version, new_objects={}):
assert missing_interface not in ["ei_device"]
assert missing_interface not in [InterfaceName.EI_DEVICE]
status.devices = True
def on_seat(connection, id, version, new_objects={}):
assert missing_interface not in ["ei_seat"]
assert missing_interface not in [InterfaceName.EI_SEAT]
seat = new_objects["seat"]
assert seat is not None
seat.connect("Device", on_device)
@ -773,15 +788,18 @@ class TestEiProtocol:
status.seats = True
def on_disconnected(connection, last_serial, reason, explanation):
assert missing_interface in ["ei_seat", "ei_device"]
assert missing_interface in [
InterfaceName.EI_SEAT,
InterfaceName.EI_DEVICE,
]
status.disconnected = True
def on_connection(setup, serial, id, version, new_objects={}):
# these three must be present, otherwise we get disconnected
assert missing_interface not in [
"ei_connection",
"ei_callback",
"ei_pingpong",
InterfaceName.EI_CONNECTION,
InterfaceName.EI_CALLBACK,
InterfaceName.EI_PINGPONG,
]
status.connected = True
connection = new_objects["connection"]
@ -794,7 +812,11 @@ class TestEiProtocol:
ei.send(setup.Finish())
ei.dispatch()
if missing_interface in ["ei_connection", "ei_callback", "ei_pingpong"]:
if missing_interface in [
InterfaceName.EI_CONNECTION,
InterfaceName.EI_CALLBACK,
InterfaceName.EI_PINGPONG,
]:
# valgrind is slow, so let's wait for it to catch up
time.sleep(0.3)
ei.dispatch()
@ -804,14 +826,14 @@ class TestEiProtocol:
assert False, "We should've been disconnected by now"
ei.wait_for(lambda: status.connected)
if missing_interface in ["ei_device", "ei_seat"]:
if missing_interface in [InterfaceName.EI_DEVICE, InterfaceName.EI_SEAT]:
assert ei.wait_for(lambda: status.disconnected)
assert (
status.disconnected
), f"Expected to be disconnected for missing {missing_interface}"
else:
assert (
missing_interface == "ei_pointer"
missing_interface == InterfaceName.EI_POINTER
) # otherwise we shouldn't get here
assert ei.callback_roundtrip(), "Callback roundtrip failed"
assert status.connected
@ -821,7 +843,11 @@ class TestEiProtocol:
assert status.devices
except BrokenPipeError:
assert missing_interface in ["ei_connection", "ei_callback", "ei_pingpong"]
assert missing_interface in [
InterfaceName.EI_CONNECTION,
InterfaceName.EI_CALLBACK,
InterfaceName.EI_PINGPONG,
]
@pytest.mark.parametrize("test_for", ("repeat-id", "invalid-id", "decreasing-id"))
def test_invalid_object_id(self, eis, test_for):
@ -881,7 +907,12 @@ class TestEiProtocol:
assert status.explanation is not None
@pytest.mark.parametrize(
"wanted_interface", ("ei_pointer", "ei_keyboard", "ei_touchscreen")
"wanted_interface",
(
InterfaceName.EI_POINTER,
InterfaceName.EI_KEYBOARD,
InterfaceName.EI_TOUCHSCREEN,
),
)
def test_connect_receive_device(self, eis, wanted_interface):
"""
@ -912,7 +943,7 @@ class TestEiProtocol:
def on_new_object(o: Interface):
logger.debug("new object", object=o)
if o.name == "ei_seat":
if o.name == InterfaceName.EI_SEAT:
o.connect("Device", on_new_device)
ei.context.connect("register", on_new_object)
@ -963,7 +994,7 @@ class TestEiProtocol:
name=name,
version=version,
)
if name == "ei_pointer":
if name == InterfaceName.EI_POINTER:
status.pointer = new_objects["object"]
def on_new_device(seat, device, version, new_objects):
@ -973,7 +1004,7 @@ class TestEiProtocol:
def on_new_object(o: Interface):
logger.debug("new object", object=o)
if o.name == "ei_seat":
if o.name == InterfaceName.EI_SEAT:
o.connect("Device", on_new_device)
ei.context.connect("register", on_new_object)