From b02b4f0901b06866ef8cb868eb3986fb2e7b9018 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 1 Feb 2023 10:32:15 +1000 Subject: [PATCH] Drop protobuf in favour of a custom protocol This protocol is wayland-like though it uses a slightly different message format. The XML file uses the same structure, except for the "fixed" type which is "float" here. The scanner uses a jinja template to generate source and header files for ei and eis which are now used instead of the protobuf-generated objects. Note that the scanner is a minimal working version, some features like enum value checks are not yet implemented. Unlike wayland we do not need to generate the libwayland-like library, we only need the wire protocol parser - some shortcuts can thus be taken. To keep the changes simple, the protocol currently is a flat protocol with only one interface and all messages copied over from the previous ei.proto file. In future commits, this will be moved to the respective interfaces instead. --- .editorconfig | 4 +- .gitlab-ci.yml | 4 +- .gitlab-ci/config.yml | 4 +- meson.build | 13 +- proto/ei-proto.c.tmpl | 137 ++++++++++++++ proto/ei-proto.h.tmpl | 88 +++++++++ proto/ei.proto | 292 ----------------------------- proto/meson.build | 30 ++- proto/protocol.xml | 315 +++++++++++++++++++++++++++++++ proto/scanner.py | 405 ++++++++++++++++++++++++++++++++++++++++ src/brei-shared.c | 418 +++++++++++++++++++++++++++++++++++------- src/brei-shared.h | 54 +++++- src/libei-private.h | 15 +- src/libei.c | 149 +++++++++------ src/libeis-client.c | 140 ++++++++------ src/libeis-client.h | 13 ++ src/libeis-device.c | 29 +-- src/libeis-private.h | 1 + src/libeis.c | 4 +- test/meson.build | 2 +- 20 files changed, 1602 insertions(+), 515 deletions(-) create mode 100644 proto/ei-proto.c.tmpl create mode 100644 proto/ei-proto.h.tmpl delete mode 100644 proto/ei.proto create mode 100644 proto/protocol.xml create mode 100755 proto/scanner.py diff --git a/.editorconfig b/.editorconfig index 326e25f..e653696 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,7 +15,7 @@ tab_width = 4 indent_style = space tab_width = 4 -[*.{c,h}] +[*.{c,h,tmpl}] indent_style = tab tab_width = 8 @@ -28,7 +28,7 @@ indent_style = space indent_size = 4 charset = utf-8 -[*.{yaml,yml,tmpl}] +[*.{yaml,yml}] indent_style = space indent_size = 2 charset = utf-8 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f0594a7..259808f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,14 +32,14 @@ variables: # See the documentation here: # # https://wayland.freedesktop.org/libinput/doc/latest/building_libinput.html # ############################################################################### - FEDORA_PACKAGES: 'git diffutils gcc gcc-c++ pkgconf-pkg-config meson systemd-devel protobuf-c-devel libxkbcommon-devel doxygen python3-attrs python3-pytest python3-dbusmock ' + FEDORA_PACKAGES: 'git diffutils gcc gcc-c++ pkgconf-pkg-config meson systemd-devel libxkbcommon-devel doxygen python3-attrs python3-pytest python3-dbusmock python3-jinja2 ' ############################ end of package lists ############################# # these tags should be updated each time the list of packages is updated # 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: '2022-12-05.1' + FEDORA_TAG: '2023-02-02.0' FDO_UPSTREAM_REPO: libinput/libei diff --git a/.gitlab-ci/config.yml b/.gitlab-ci/config.yml index 5a8c632..555fb2f 100644 --- a/.gitlab-ci/config.yml +++ b/.gitlab-ci/config.yml @@ -3,7 +3,7 @@ # # We're happy to rebuild all containers when one changes. -.default_tag: &default_tag '2022-12-05.1' +.default_tag: &default_tag '2023-02-02.0' distributions: - name: fedora @@ -19,12 +19,12 @@ distributions: - pkgconf-pkg-config - meson - systemd-devel - - protobuf-c-devel - libxkbcommon-devel - doxygen - python3-attrs - python3-pytest - python3-dbusmock + - python3-jinja2 pages: distro: fedora diff --git a/meson.build b/meson.build index cbc625b..43cce74 100644 --- a/meson.build +++ b/meson.build @@ -79,15 +79,14 @@ src_libei = files( 'src/libei-event.c', 'src/libei-fd.c', 'src/libei-log.c', - 'src/libei-proto.c', + 'src/libei-region.c', 'src/libei-region.c', 'src/libei-seat.c', 'src/libei-socket.c', -) + [proto_headers] +) + [ei_proto_headers, ei_proto_sources] deps_libei = [ dep_libutil, - dep_protobuf, ] lib_libei = shared_library('ei', @@ -95,6 +94,7 @@ lib_libei = shared_library('ei', dependencies: deps_libei, include_directories: [inc_proto], gnu_symbol_visibility: 'hidden', + include_directories: ['src'], install: true ) install_headers('src/libei.h') @@ -122,16 +122,15 @@ src_libeis = files( 'src/libeis-event.c', 'src/libeis-fd.c', 'src/libeis-log.c', - 'src/libeis-proto.c', 'src/libeis-region.c', 'src/libeis-seat.c', 'src/libeis-socket.c', -) + [proto_headers] +) + [eis_proto_headers, eis_proto_sources] lib_libeis = shared_library('eis', src_libeis, - dependencies: [dep_libutil, dep_protobuf], - include_directories: [inc_proto], + dependencies: [dep_libutil], + include_directories: [inc_proto, inc_src], gnu_symbol_visibility: 'hidden', install: true ) diff --git a/proto/ei-proto.c.tmpl b/proto/ei-proto.c.tmpl new file mode 100644 index 0000000..c916155 --- /dev/null +++ b/proto/ei-proto.c.tmpl @@ -0,0 +1,137 @@ +/** + * GENERATED FILE, DO NOT EDIT + * + * SPDX-License-Identifier: MIT + */ + +{#- this is a jinja template, warning above is for the generated file + + Non-obvious variables set by the scanner that are used in this template: + - target: because eis is actually eis_client in the code, the target points to + either "ei" or "eis_client". The various attributes on target resolve + accordingly. + - request.fqdn/event.fqdn - the full name of a request/event with the + interface name prefixed, "ei_foo_request_bar" or "ei_foo_event_bar" + - incoming/outgoing: points to the list of requests or events, depending + which one is the outgoing one from the perspective of the file we're + generating (ei or eis) +#} + + +#include +#include +#include +#include "brei-shared.h" + +{% if headerfile %} +#include "{{headerfile}}" +{% endif %} + +/** + * Forward declarations. This file is intended to be compile-able without including + * any of the actual implementation files. + */ +struct {{target.name}}; + +/** + * The function that converts the given arguments into a wire format and sends it. + * This function must be provided by the implementation, it is called by all + * message sending functions (requests for libei, events for libeis). + */ +extern int {{target.name}}_send_message( + {{target.as_arg}}, uint32_t object_id, uint32_t opcode, + const char *signature, size_t nargs, ... +); + +{% for interface in interfaces %} +/*************************************************************************** + * Interface: {{interface.name.ljust(60)}} * + ***************************************************************************/ + +/* returns the protocol interface from the given struct, see the dispatcher */ +const struct {{interface.name}}_interface *{{target.name}}_get_interface({{target.as_arg}}); + +/* returns the message sending context from the given struct */ +{{target.ctype}} {{target.name}}_get_{{target.context}}({{target.as_arg}}); + +/* Returns the protocol object id from the given struct */ +const struct brei_object * +{{target.name}}_get_proto_object({{target.as_arg}}); + +/* request opcodes */ +{% for request in interface.requests %} +#define {{request.fqdn.upper().ljust(45)}} {{request.opcode}} +{% endfor %} + +/* event opcodes */ +{% for event in interface.events %} +#define {{event.fqdn.upper().ljust(45)}} {{event.opcode}} +{% endfor %} + +/* Message sender functions */ +{% for outgoing in interface.outgoing %} +int +{{outgoing.fqdn}}({{target.as_arg}}{%- for arg in outgoing.arguments %}, {{arg.as_arg}}{% endfor %}) +{ + const struct brei_object *obj = {{target.name}}_get_proto_object({{target.name}}); + {{target.ctype}} ctx = {{target.name}}_get_{{target.context}}({{target.name}}); + return {{target.name}}_send_message( + ctx, obj->id, {{outgoing.fqdn.upper()}}, "{{outgoing.signature}}", + {{outgoing.arguments|length}}{%- for arg in outgoing.arguments %}, {{arg.name}}{% endfor -%} + ); +} + +{% endfor %} + +/** + * The dispatcher for the {{interface.name}} interface is the function called by + * brei with the messages parsed from the wire. + */ +static int +{{interface.name}}_dispatcher( + {{target.as_arg}}, + uint32_t opcode, + size_t nargs, + union brei_arg *args +) { +{% if interface.incoming|length %} + const struct {{interface.name}}_interface *interface = {{target.name}}_get_interface({{target.name}}); + switch (opcode) { + {% for incoming in interface.incoming %} + case {{incoming.fqdn.upper()}}: + assert(interface->{{incoming.name}} != NULL); + return interface->{{incoming.name}}({{target.name}}{% for arg in incoming.arguments %}, (args + {{loop.index - 1}})->{{arg.argtype}}{% endfor %}); + {% endfor %} + } +{% endif %} + return -EINVAL; +} + +static const struct brei_message {{interface.name}}_requests[] = { + {% for request in interface.requests %} + { "{{request.name}}", "{{request.signature}}" }, + {% endfor %} +}; + +static const struct brei_message {{interface.name}}_events[] = { + {% for event in interface.events %} + { "{{event.name}}", "{{event.signature}}" }, + {% endfor %} +}; + +static const struct brei_message {{interface.name}}_incoming[] = { + {% for msg in interface.incoming %} + { "{{msg.name}}", "{{msg.signature}}" }, + {% endfor %} +}; + +const struct brei_interface {{interface.name}}_proto_interface = { + .name = "{{interface.name}}", .version = {{interface.version}}, + .nrequests = {{interface.requests|length}}, .requests = {{interface.name}}_requests, + .nevents = {{interface.events|length}}, .events = {{interface.name}}_events, + .nincoming = {{interface.incoming|length}}, .incoming = {{interface.name}}_incoming, + .dispatcher = (brei_event_dispatcher){{interface.name}}_dispatcher, +}; + +{% endfor %} + diff --git a/proto/ei-proto.h.tmpl b/proto/ei-proto.h.tmpl new file mode 100644 index 0000000..f3c2747 --- /dev/null +++ b/proto/ei-proto.h.tmpl @@ -0,0 +1,88 @@ +/** + * GENERATED FILE, DO NOT EDIT + * + * SPDX-License-Identifier: MIT + */ + +{# this is a jinja template, warning above is for the generated file + + Non-obvious variables set by the scanner that are used in this template: + - target: because eis is actually eis_client in the code, the target points to + either "ei" or "eis_client" + - request.fqdn/event.fqdn - the full name of a request/event with the + interface name prefixed, "ei_foo_request_bar" or "ei_foo_event_bar" + - incoming/outgoing: points to the list of requests or events, depending + which one is the outgoing one from the perspective of the file we're + generating (ei or eis) +#} + +#pragma once + +#ifdef _cplusplus +extern "C" { +#endif + +#include + +/** + * Forward declarations. This file is intended to be compile-able without including + * any of the actual sources files. + */ + +typedef uint32_t new_id_t; +typedef uint32_t object_id_t; + +struct {{target.name}}; + +/* Interface declarations */ +{% for interface in interfaces %} +struct {{interface.name}}; +{% endfor %} + +{% for interface in interfaces %} +extern const struct brei_interface {{interface.name}}_proto_interface; +{% endfor %} + +{% for interface in interfaces %} + +/** {{interface.name}} **/ + +{% for request in interface.requests %} +#define {{request.fqdn.upper()}}_SINCE_VERSION {{request.since}} +{% endfor %} + +{% for event in interface.events %} +#define {{event.fqdn.upper()}}_SINCE_VERSION {{event.since}} +{% endfor %} + +{% for enum in interface.enums %} +enum {{enum.fqdn}} { + {% for entry in enum.entries %} + {{enum.fqdn.upper()}}_{{entry.name.upper()}} = {{entry.value}}, + {% endfor %} +}; +{% endfor %} + +/* Message sender functions */ +{% for outgoing in interface.outgoing %} +extern int +{{outgoing.fqdn}}({{target.as_arg}}{%- for arg in outgoing.arguments %}, {{arg.as_arg}}{% endfor %}); + +{% endfor %} + +/** + * Interface to handle incoming messages for objects of type {{interface.name}}. + * + * After parsing the wire message, the data is dispatched into the functions below. + */ +struct {{interface.name}}_interface { + {% for incoming in interface.incoming %} + int (*{{incoming.name}})({{target.as_arg}}{%- for arg in incoming.arguments %}, {{arg.as_arg}}{% endfor %}); + {% endfor %} +}; +{% endfor %} + +#ifdef _cplusplus +} +#endif + diff --git a/proto/ei.proto b/proto/ei.proto deleted file mode 100644 index 8bf679c..0000000 --- a/proto/ei.proto +++ /dev/null @@ -1,292 +0,0 @@ -syntax = "proto3"; - -/** - * EI Protocol Specification - * - * This protocol is an internal implementation detail and subject to change - * at any time. This specification is for developers of libei only. - * - * ClientMessage → sent from the client to the server - * ServerMessage → sent from the server to the client - * - * A normal sequence consists of: - * [1. - client establishes connection to server - * 2. - client sends "Connect" - * 2.a - server replies with "Connected" or - * 2.b - server replies with "Disconnected" and closes its end of the socket - * 3. - server sends "AddSeat" (once or multiple times) - * 4. - client sends "BindSeat" for each seat - * 5. - server sends "DeviceAdded" for any device on this seat - * 6. - server sends "DeviceResumed" - * 7. - client sends "StartEmulating" to notify the server emulation starts - * 8. - client sends "PointerRelative" or any other event - * 9. - client sends "StopEmulating" to notify the server emulation starts - * 10. - client sends "CloseDevice" - * 11. - client sends "Disconnect" and closes its end of the socket - * - * The server may send Disconnect at any time. - * The server may send SeatRemoved for a device at any time. - * The server may send DeviceSuspended for any currently resumed device at any time. - * The server may send DeviceRemoved for a device at any time. - * - * Where a connection error occurs, the library (libei or libeis) will - * unroll the state as seen from the API. - */ - -/* Request the server version. This request MAY be sent at any time and/or - * multiple times and the server always replies with the server's highest - * supported version, see Version. - * - * To avoid roundtrips during the initial connection, an EIS implementation - * *SHOULD* send a Version message immediately. A client thus may not need to - * request the version. - * - * The "ei" field is the constant string "EI" and helps identifying - * this connection in debugging tools. - */ -message GetVersion { - string ei = 1; /* always "EI" */ -} - -message Connect { - fixed32 version = 1; /* Must be equal or less to the server's GetVersion response */ - string name = 2; - bool is_sender = 3; -} - -message ConnectDone { -} - -message Disconnect { -} - -message BindSeat { - fixed32 seatid = 1; - fixed32 capabilities = 2; -} - -message CloseDevice { - fixed32 deviceid = 1; -} - -message PointerRelative { - fixed32 deviceid = 1; - double x = 2; - double y = 3; -} - -message PointerAbsolute { - fixed32 deviceid = 1; - double x = 2; - double y = 3; -} - -message PointerScroll { - fixed32 deviceid = 1; - double x = 2; - double y = 3; -} - -message PointerScrollDiscrete { - fixed32 deviceid = 1; - double x = 2; - double y = 3; -} - -message PointerScrollStop { - fixed32 deviceid = 1; - bool x = 2; - bool y = 3; - bool is_cancel = 4; -} - -message PointerButton { - fixed32 deviceid = 1; - fixed32 button = 2; - bool state = 3; -} - -message KeyboardKey { - fixed32 deviceid = 1; - fixed32 key = 2; - bool state = 3; -} - -message TouchDown { - fixed32 deviceid = 1; - fixed32 touchid = 2; - double x = 5; - double y = 6; -} - -message TouchMotion { - fixed32 deviceid = 1; - fixed32 touchid = 2; - double x = 5; - double y = 6; -} - -message TouchUp { - fixed32 deviceid = 1; - fixed32 touchid = 2; -} - -message StartEmulating { - fixed32 deviceid = 1; - fixed32 sequence = 2; -} - -message StopEmulating { - fixed32 deviceid = 1; -} - -message Frame { - fixed32 deviceid = 1; - uint64 timestamp = 2; -} - -message ClientMessage { - oneof msg { - /* Client setup and configuration */ - Connect connect = 1; - ConnectDone connect_done = 2; - Disconnect disconnect = 3; - BindSeat bind_seat = 4; - CloseDevice close_device = 6; - GetVersion get_version = 10; - - /* Events */ - StartEmulating start_emulating = 20; - StopEmulating stop_emulating = 21; - PointerRelative pointer_relative = 22; - PointerAbsolute pointer_absolute = 23; - PointerScroll pointer_scroll = 24; - PointerScrollDiscrete pointer_scroll_discrete = 25; - PointerScrollStop pointer_scroll_stop = 26; - PointerButton pointer_button = 27; - KeyboardKey keyboard_key = 28; - TouchDown touch_down = 29; - TouchMotion touch_motion = 30; - TouchUp touch_up = 31; - Frame frame = 32; - } -} - -/** - * The highest version number supported by the server. This message SHOULD be - * sent by the EIS implementation immediately after a socket is - * created/obtained. This avoids roundtrips between the server and the client - * on connection. - * - * This message MAY be sent at any other time. - * This message MUST be sent in response to a GetVersion request. - * - * The "eis" field is the constant string "EIS" and helps identifying - * this connection in debugging tools. - * - * The content of this message never changes. - */ -message Version { - fixed32 version = 1; - string eis = 2; /* always "EIS" */ -} - -message Connected { -} - -message Disconnected { -} - -message SeatAdded { - fixed32 seatid = 1; - fixed32 capabilities = 2; - string name = 3; -} - -message SeatRemoved { - fixed32 seatid = 1; -} - -message DeviceAdded { - fixed32 deviceid = 1; - fixed32 capabilities = 2; - string name = 6; - fixed32 seatid = 7; - fixed32 type = 8; - fixed32 width = 9; - fixed32 height = 10; -} - -message DeviceKeymap { - fixed32 deviceid = 1; - fixed32 keymap_type = 2; - fixed32 keymap_size = 3; - /* keymap itself is passed as fd */ -} - -message DeviceRegion { - fixed32 deviceid = 1; - fixed32 offset_x = 2; - fixed32 offset_y = 3; - fixed32 width = 4; - fixed32 height = 5; - double scale = 6; -} - -message DeviceDone { - fixed32 deviceid = 1; -} - -message DeviceRemoved { - fixed32 deviceid = 1; -} - -message DeviceResumed { - fixed32 deviceid = 1; -} - -message DevicePaused { - fixed32 deviceid = 1; -} - -message KeyboardModifiers { - fixed32 deviceid = 1; - fixed32 depressed = 2; - fixed32 locked = 3; - fixed32 latched = 4; - fixed32 group = 5; -} - -message ServerMessage { - oneof msg { - /* Server setup and configuration */ - Connected connected = 2; - Disconnected disconnected = 3; - SeatAdded seat_added = 4; - SeatRemoved seat_removed = 5; - DeviceAdded device_added = 6; - DeviceRegion device_region = 7; - DeviceKeymap device_keymap = 8; - DeviceDone device_done = 9; - DeviceRemoved device_removed = 10; - DeviceResumed device_resumed = 11; - DevicePaused device_paused = 12; - KeyboardModifiers keyboard_modifiers = 13; - Version version = 15; - - /* Events */ - StartEmulating start_emulating = 20; - StopEmulating stop_emulating = 21; - PointerRelative pointer_relative = 22; - PointerAbsolute pointer_absolute = 23; - PointerScroll pointer_scroll = 24; - PointerScrollDiscrete pointer_scroll_discrete = 25; - PointerScrollStop pointer_scroll_stop = 26; - PointerButton pointer_button = 27; - KeyboardKey keyboard_key = 28; - TouchDown touch_down = 29; - TouchMotion touch_motion = 30; - TouchUp touch_up = 31; - Frame frame = 32; - } -} diff --git a/proto/meson.build b/proto/meson.build index b9d8097..a449bad 100644 --- a/proto/meson.build +++ b/proto/meson.build @@ -1,9 +1,21 @@ -dep_protobuf = dependency('libprotobuf-c') -protoc = find_program('protoc') -proto_headers = custom_target('proto-headers', - input: 'ei.proto', - output: ['ei.pb-c.c', 'ei.pb-c.h'], - command: [protoc, - '--proto_path=@0@'.format(meson.current_source_dir()), - '--c_out=@0@'.format(meson.current_build_dir()), - 'ei.proto']) +proto_c_template = files('ei-proto.c.tmpl') +proto_h_template = files('ei-proto.h.tmpl') + +scanner = find_program('scanner.py') +protocol_xml = files('protocol.xml') +ei_proto_headers = custom_target('ei-proto-headers', + input: protocol_xml, + output: ['ei-proto.h'], + command: [scanner, '--component=ei', '--output=@OUTPUT@', '@INPUT@', proto_h_template]) +ei_proto_sources = custom_target('ei-proto-sources', + input: protocol_xml, + output: ['ei-proto.c'], + command: [scanner, '--component=ei', '--output=@OUTPUT@', '@INPUT@', proto_c_template]) +eis_proto_headers = custom_target('eis-proto-headers', + input: protocol_xml, + output: ['eis-proto.h'], + command: [scanner, '--component=eis', '--output=@OUTPUT@', '@INPUT@', proto_h_template]) +eis_proto_sources = custom_target('eis-proto-sources', + input: protocol_xml, + output: ['eis-proto.c'], + command: [scanner, '--component=eis', '--output=@OUTPUT@', '@INPUT@', proto_c_template]) diff --git a/proto/protocol.xml b/proto/protocol.xml new file mode 100644 index 0000000..85642c7 --- /dev/null +++ b/proto/protocol.xml @@ -0,0 +1,315 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2023 Red Hat, Inc. + + 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: + + The above copyright notice and this permission notice (including the + next paragraph) 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. + + + + + + + The core global object. This is a special singleton object. It + is used for internal ei protocol features. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/proto/scanner.py b/proto/scanner.py new file mode 100755 index 0000000..620d392 --- /dev/null +++ b/proto/scanner.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: MIT + +from typing import Optional, Union +from pathlib import Path + +import argparse +import attr +import jinja2 +import jinja2.environment +import os +import sys +import xml.sax +import xml.sax.handler +import xml.sax._exceptions + + +def proto_to_type(proto: str) -> Optional[str]: + """ + Conversion of protocol types to the signatures we use in the code + """ + + return { + "uint": "u", + "int": "i", + "float": "f", + "fd": "h", + "new_id": "n", + "object": "o", + "string": "s", + }.get(proto) + + +@attr.s +class Target: + """ + Defines the target struct for the base "ei" interface. + + In libei we have a `struct ei` but in libeis the equivalent + level is `struct eis_client`. This target type maps those two. + """ + + name: str = attr.ib() + context: str = attr.ib() + + @property + def ctype(self) -> str: + return f"struct {self.name} *" + + @property + def as_param(self) -> str: + return f"struct {self.name}* {self.name}" + + @property + def as_arg(self) -> str: + return self.as_param + + @classmethod + def create(cls, name: str, context: str) -> "Target": + return cls(name=name, context=context) + + +@attr.s +class Argument: + """ + Argument to a request or a reply + """ + + name: str = attr.ib() + signature: str = attr.ib(converter=proto_to_type) + summary: str = attr.ib() + + @property + def as_arg(self) -> str: + return f"{self.ctype} {self.name}" + + @property + def ctype(self) -> str: + return { + "u": "uint32_t", + "i": "int32_t", + "s": "const char *", + "h": "int", + "f": "float", + "o": "object_id_t", + "n": "new_id_t", + }[self.signature] + + @property + def argtype(self) -> str: + return { + "u": "u32", + "i": "i32", + "s": "str", + "h": "fd", + "f": "f32", + "o": "obj", + "n": "obj", + }[self.signature] + + @signature.validator # type: ignore + def _validate_signature(self, attribute, value): + types = "iufhnos" + assert value in types, f"Failed to parse signature {value}" + + @classmethod + def create(cls, name: str, signature: str, summary: str = "") -> "Argument": + # FIXME: enum value checks + return cls(name, signature, summary) + + +@attr.s +class Message: + """ + Parent class for the wire message (Request or Event). + """ + + name: str = attr.ib() + since: int = attr.ib() + opcode: int = attr.ib() + interface: "Interface" = attr.ib() + + arguments: list[Argument] = attr.ib(init=False, factory=list) + + def add_argument(self, arg: Argument) -> None: + self.arguments.append(arg) + + @property + def num_arguments(self) -> int: + return len(self.arguments) + + @property + def signature(self) -> str: + return "".join([a.signature for a in self.arguments]) + + +@attr.s +class Request(Message): + @classmethod + def create( + cls, name: str, opcode: int, interface: "Interface", since: int = 1 + ) -> "Request": + return cls(name=name, opcode=opcode, since=since, interface=interface) + + @property + def fqdn(self): + return f"{self.interface.name}_request_{self.name}" + + +@attr.s +class Event(Message): + @classmethod + def create( + cls, name: str, opcode: int, interface: "Interface", since: int = 1 + ) -> "Event": + return cls(name=name, opcode=opcode, since=since, interface=interface) + + @property + def fqdn(self): + return f"{self.interface.name}_event_{self.name}" + + +@attr.s +class Entry: + """ + An enum entry + """ + + name: str = attr.ib() + value: int = attr.ib() + summary: str = attr.ib() + + @classmethod + def create(cls, name: str, value: int, summary: str = "") -> "Entry": + return cls(name=name, value=value, summary=summary) + + +@attr.s +class Enum: + name: str = attr.ib() + since: int = attr.ib() + interface: "Interface" = attr.ib() + + entries: list[Entry] = attr.ib(init=False, factory=list) + + @classmethod + def create(cls, name: str, interface: "Interface", since: int = 1) -> "Enum": + return cls(name=name, since=since, interface=interface) + + def add_entry(self, entry: Entry) -> None: + self.entries.append(entry) + + @property + def fqdn(self): + return f"{self.interface.name}_{self.name}" + + +@attr.s +class Interface: + name: str = attr.ib() + version: int = attr.ib() + + requests: list[Request] = attr.ib(init=False, factory=list) + events: list[Event] = attr.ib(init=False, factory=list) + enums: list[Enum] = attr.ib(init=False, factory=list) + + mode: str = attr.ib() # "ei" or "eis" + + def add_request(self, request: Request) -> None: + self.requests.append(request) + + def add_event(self, event: Event) -> None: + self.events.append(event) + + def add_enum(self, enum: Enum) -> None: + self.enums.append(enum) + + @property + def outgoing(self) -> list[Message]: + """ + Returns the list of messages outgoing from this implementation. + + We use the same class for both ei and eis. To make the + template simpler, the class maps requests/events to + incoming/outgoing as correct relative to the implementation. + """ + if self.mode == "ei": + return self.requests # type: ignore + else: + return self.events # type: ignore + + @property + def incoming(self) -> list[Message]: + """ + Returns the list of messages incoming to this implementation. + + We use the same class for both ei and eis. To make the + template simpler, the class maps requests/events to + incoming/outgoing as correct relative to the implementation. + """ + if self.mode == "ei": + return self.events # type: ignore + else: + return self.requests # type: ignore + + @classmethod + def create(cls, name: str, version: int, mode: str = "ei") -> "Interface": + assert mode in ["ei", "eis"] + return cls(name=name, version=version, mode=mode) + + +@attr.s +class Protocol(xml.sax.handler.ContentHandler): + component: str = attr.ib() + interfaces: list[Interface] = attr.ib(factory=list) + + current_interface: Optional[Interface] = attr.ib(init=False, default=None) + current_message: Optional[Union[Message, Enum]] = attr.ib(init=False, default=None) + + def startElement(self, element: str, attrs: dict): + if element == "interface": + assert self.current_interface is None + name = attrs["name"] + if name.startswith("ei"): + name = f"{self.component}{name[2:]}" + version = attrs["version"] + intf = Interface.create(name=name, version=version, mode=self.component) + self.current_interface = intf + self.interfaces.append(intf) + elif element == "request": + assert self.current_interface is not None + assert self.current_message is None + name = attrs["name"] + since = attrs.get("since", 1) + opcode = len(self.current_interface.requests) + request = Request.create( + name=name, since=since, opcode=opcode, interface=self.current_interface + ) + self.current_interface.add_request(request) + self.current_message = request + elif element == "event": + assert self.current_interface is not None + assert self.current_message is None + + name = attrs["name"] + since = attrs.get("since", 1) + opcode = len(self.current_interface.events) + event = Event.create( + name=name, since=since, opcode=opcode, interface=self.current_interface + ) + self.current_interface.add_event(event) + self.current_message = event + elif element == "enum": + assert self.current_interface is not None + assert self.current_message is None + name = attrs["name"] + since = attrs.get("since", 1) + enum = Enum.create(name=name, since=since, interface=self.current_interface) + self.current_interface.add_enum(enum) + self.current_message = enum + elif element == "arg": + assert self.current_interface is not None + assert isinstance(self.current_message, Message) + name = attrs["name"] + sig = attrs["type"] + summary = attrs.get("summary", "") + arg = Argument.create(name=name, signature=sig, summary=summary) + self.current_message.add_argument(arg) + elif element == "entry": + assert self.current_interface is not None + assert isinstance(self.current_message, Enum) + name = attrs["name"] + value = attrs["value"] + summary = attrs.get("summary", "") + entry = Entry.create(name=name, value=value, summary=summary) + self.current_message.add_entry(entry) + + def endElement(self, name): + if name == "interface": + assert self.current_interface is not None + self.current_interface = None + elif name == "request": + assert isinstance(self.current_message, Request) + self.current_message = None + elif name == "event": + assert isinstance(self.current_message, Event) + self.current_message = None + elif name == "enum": + assert isinstance(self.current_message, Enum) + self.current_message = None + + def characters(self, content): + pass + + @classmethod + def create(cls, component: str) -> "Protocol": + h = cls(component=component) + return h + + +def parse(protofile: Path, component: str) -> Protocol: + proto = Protocol.create(component=component) + xml.sax.parse(os.fspath(protofile), proto) + return proto + + +def generate_source( + proto: Protocol, headerfile: Optional[str], template: Path, component: str +) -> jinja2.environment.TemplateStream: + assert component in ["ei", "eis"] + target = { + "ei": Target.create("ei", context="context"), + "eis": Target.create("eis_client", context="client"), + }[component] + + data = {} + data["target"] = target + data["interfaces"] = proto.interfaces + if headerfile: + data["headerfile"] = headerfile + + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.fspath(template.parent)), + trim_blocks=True, + lstrip_blocks=True, + ) + jtemplate = env.get_template(template.name) + return jtemplate.stream(data) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--component", type=str, choices=["ei", "eis"], default="ei") + parser.add_argument( + "--output", type=str, default="-", help="Output file to write to" + ) + parser.add_argument("protocol", type=Path, help="The protocol XML file") + parser.add_argument("template", type=Path, help="The template file") + + ns = parser.parse_args() + assert ns.template.exists() + assert ns.protocol.exists() + + try: + proto = parse( + protofile=ns.protocol, + component=ns.component, + ) + except xml.sax._exceptions.SAXParseException as e: + print(f"Parser error: {e}", file=sys.stderr) + sys.exit(1) + + headerfile = f"{Path(ns.output).stem}.h" if ns.output != "-" else None + + stream = generate_source( + proto=proto, headerfile=headerfile, template=ns.template, component=ns.component + ) + + file = sys.stdout if ns.output == "-" else open(ns.output, "w") + stream.dump(file) + + +if __name__ == "__main__": + main() diff --git a/src/brei-shared.c b/src/brei-shared.c index 344f303..1ef80ed 100644 --- a/src/brei-shared.c +++ b/src/brei-shared.c @@ -29,7 +29,6 @@ #include "util-mem.h" #include "util-io.h" -#include "ei.pb-c.h" #include "brei-shared.h" struct brei_message_private { @@ -47,9 +46,155 @@ brei_message_take_fd(struct brei_message *m) return fd; } +/** + * Return the number of int32s required to store count bytes. + */ +static inline uint32_t +bytes_to_int32(uint32_t count) +{ + return (uint32_t)(((uint64_t)count + 3)/4); +} + +static int +brei_demarshal(struct iobuf *buf, const char *signature, union brei_arg **args_out) +{ + size_t nargs = strlen(signature); + if (nargs > 256) + return -EPROTO; + + /* This over-allocates if we have more than one char per type but meh */ + _cleanup_free_ union brei_arg *args = xalloc(nargs * sizeof(*args)); + + const char *s = signature; + union brei_arg *arg = args; + uint32_t *p = (uint32_t*)iobuf_data(buf); + uint32_t *end = (uint32_t*)iobuf_data_end(buf); + + nargs = 0; + while (*s) { + switch (*s) { + case 'i': + case 'u': + case 'f': + case 'o': + case 'n': + arg->u32 = *p++; + break; + case 'h': + arg->fd = iobuf_take_fd(buf); + break; + case 's': { + size_t slen = *p++; /* string length includes \0 */ + if (slen == 0) { + arg->str = NULL; + break; + } + + uint32_t slen32 = bytes_to_int32(slen); + if (end - p < slen32) { + return -EINVAL; + } + + const char *str = (char*)p; + + /* strings must be null-terminated */ + if (slen && str[slen - 1] != '\0') + return -EINVAL; + + arg->str = str; + p += slen32; + break; + } + } + arg++; + s++; + nargs++; + } + + *args_out = steal(&args); + + return nargs; +} + +static int +brei_marshal(struct iobuf *buf, const char *signature, size_t nargs, va_list args) +{ + const char *s = signature; + int32_t i; + uint32_t u; + float f; + int fd; + + while (*s) { + switch (*s) { + case 'i': + i = va_arg(args, int32_t); + iobuf_append(buf, (const char*)(&i), 4); + break; + case 'u': + case 'o': + case 'n': + u = va_arg(args, uint32_t); + iobuf_append(buf, (const char*)(&u), 4); + break; + case 'f': + f = va_arg(args, double); + iobuf_append(buf, (const char*)(&f), 4); + break; + case 'h': + fd = va_arg(args, int); + iobuf_append_fd(buf, fd); + break; + case 's': { + static const char zeroes[4] = {0}; + const char *str = va_arg(args, const char*); + + /* FIXME: nullable strings */ + size_t slen = str ? strlen(str) + 1 : 0; + iobuf_append(buf, (const char*)&slen, 4); + if (slen > 0) { + iobuf_append(buf, str, slen); + if (slen % 4) + iobuf_append(buf, zeroes, 4 - slen % 4); + } + break; + } + } + s++; + } + + return 0; +} + +int +brei_send_message(int fd, + uint32_t id, + uint32_t opcode, + const char *signature, + size_t nargs, + va_list args) +{ + uint32_t message_len = 0; /* we'll overwrite this later */ + + _cleanup_iobuf_ struct iobuf *buf = iobuf_new(128); + iobuf_append(buf, (const char *)&message_len, 4); + iobuf_append(buf, (const char *)&id, 4); + iobuf_append(buf, (const char *)&opcode, 4); + + int rc = brei_marshal(buf, signature, nargs, args); + + if (rc == 0) { + /* now write the actual message length, including header */ + *(uint32_t*)iobuf_data(buf) = iobuf_len(buf); + rc = iobuf_send(buf, fd); + } + + return rc; +} + int brei_dispatch(int fd, - int (*callback)(struct brei_message *m, void *user_data), + int (*lookup_object)(uint32_t object_id, struct brei_object **object, void *user_data), void *user_data) { _cleanup_iobuf_ struct iobuf *buf = iobuf_new(64); @@ -62,44 +207,58 @@ brei_dispatch(int fd, return rc; } - _cleanup_close_ int recvfd = -1; - size_t idx = 0; while (true) { - const char *data = iobuf_data(buf) + idx; - size_t len = iobuf_len(buf) - idx; - int consumed = 0; + uint32_t *data = (uint32_t*)iobuf_data(buf); + size_t len = iobuf_len(buf); - if (len == 0) + const size_t headersize = 12; /* length, object id, opcode */ + + if (len < headersize) break; - uint32_t msglen = *(uint32_t*)data; - const char *msgdata = data + sizeof(msglen); + size_t msglen = data[0]; /* 4 bytes message length */ + if (len < msglen) + break; - assert(len >= msglen); + uint32_t object_id = data[1]; /* 4 bytes for object id */ + uint32_t opcode = data[2]; /* 4 bytes for opcode */ - /* This is a bit messy because it's just blu tacked on. - * Our protocol passes maximum of one fd per message. We - * take whatever next fd is and pass it along. Where the - * parser takes it (brei_message_take_fd()) it gets set to - * -1 and we take the next fd for the next message. - */ - if (recvfd == -1) - recvfd = iobuf_take_fd(buf); + /* Find the object, it is stored in the ei/eis context */ + struct brei_object *object = NULL; + rc = lookup_object(object_id, &object, user_data); + if (rc < 0) + goto error; - struct brei_message_private msg = { - .base.data = msgdata, - .base.len = msglen, - .fd = &recvfd, - }; + assert(object); - /* Actual message parsing is done by the caller */ - consumed = callback(&msg.base, user_data); - assert(consumed != 0); - if (consumed < 0) { - rc = consumed; + /* We know the object's interface, now find the event we + * need to parse */ + const struct brei_interface *interface = object->interface; + assert(interface); + + if (opcode >= interface->nincoming) { + rc = -EINVAL; goto error; } - idx += consumed + sizeof(msglen); + + iobuf_pop(buf, headersize); + + /* Demarshal the protocol into a set of arguments */ + _cleanup_free_ union brei_arg * args = NULL; + const char *signature = interface->incoming[opcode].signature; + int nargs = brei_demarshal(buf, signature, &args); + if (nargs < 0) { + rc = nargs; + goto error; + } + + /* Success! Let's pass this on to the + * context to process */ + rc = interface->dispatcher(object->implementation, opcode, nargs, args); + if (rc < 0) + goto error; + + iobuf_pop(buf, msglen - headersize); } rc = 0; @@ -122,30 +281,50 @@ brei_drain_fd(int fd) #include "util-munit.h" static int -brei_dispatch_cb(struct brei_message *msg, - void *user_data) +brei_marshal_va(struct iobuf *buf, const char *signature, size_t nargs, ...) { - char *buf = user_data; + va_list args; - memcpy(buf, msg->data, msg->len); - - return msg->len; + va_start(args, nargs); + int rc = brei_marshal(buf, signature, nargs, args); + va_end(args); + return rc; } -static inline void -send_data(int fd, const char *data, size_t data_size) +MUNIT_TEST(test_brei_marshal) { - /* note: data is null-terminated, we copy all of it but only use - datalen to check truncation works */ - unsigned char buf[1024] = {0}; - *(uint32_t*)buf = data_size; - memcpy(buf + 4, data, strlen(data)); /* intentionally strlen */ + _cleanup_iobuf_ struct iobuf *buf = iobuf_new(64); + const char *str = "eierspeise"; - int rc = xsend(fd, buf, 4 + data_size); - munit_assert_int(rc, ==, 4 + data_size); + int rc = brei_marshal_va(buf, "noiusf", 5, 0xab, 0xcd, -13, 0xfffd, str, 1.45); + munit_assert_int(rc, ==, 0); + + _cleanup_free_ union brei_arg *args; + rc = brei_demarshal(buf, "noiusf", &args); + munit_assert_int(rc, ==, 6); + + munit_assert_int(args[0].obj, ==, 0xab); + munit_assert_int(args[1].obj, ==, 0xcd); + munit_assert_int(args[2].i32, ==, -13); + munit_assert_int(args[3].u32, ==, 0xfffd); + munit_assert_string_equal(args[4].str, str); + munit_assert_double_equal(args[5].f32, 1.45, 3 /* precision */); + + return MUNIT_OK; } -MUNIT_TEST(test_brei_dispatch) +static int +brei_send_message_va(int fd, uint32_t id, uint32_t opcode, + const char *signature, size_t nargs, ...) +{ + va_list args; + va_start(args, nargs); + int rc = brei_send_message(fd, id, opcode, signature, nargs, args); + va_end(args); + return rc; +} + +MUNIT_TEST(test_brei_send_message) { int sv[2]; int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, sv); @@ -155,32 +334,145 @@ MUNIT_TEST(test_brei_dispatch) int sock_write = sv[1]; { - /* Packet one: just an 'x' */ - char return_buffer[1024] = {0}; - send_data(sock_write, "x", 1); - int rc = brei_dispatch(sock_read, brei_dispatch_cb, return_buffer); - munit_assert_int(rc, ==, 0); - munit_assert_string_equal(return_buffer, "x"); + const int msglen = 20; /* 12 header + 2 * 4 bytes */ + uint32_t id = 1; + uint32_t opcode = 2; + const char *signature = "uu"; + int rc = brei_send_message_va(sock_write, id, opcode, signature, 2, 0xff, 0xdddd); + munit_assert_int(rc, ==, msglen); + + uint32_t buf[64]; + int len = read(sock_read, buf, sizeof(buf)); + munit_assert_int(len, ==, msglen); + munit_assert_int(buf[0], ==, msglen); + munit_assert_int(buf[1], ==, id); + munit_assert_int(buf[2], ==, opcode); + munit_assert_int(buf[3], ==, 0xff); + munit_assert_int(buf[4], ==, 0xdddd); + } + { + const int msglen = 20; /* 12 header + 2 * 4 bytes */ + uint32_t id = 1; + uint32_t opcode = 2; + const char *signature = "fi"; + int rc = brei_send_message_va(sock_write, id, opcode, signature, 2, 1.234, -12); + munit_assert_int(rc, ==, msglen); + + uint32_t buf[64]; + int len = read(sock_read, buf, sizeof(buf)); + union { + uint32_t bytes; + float f; + } ufloat; + munit_assert_int(len, ==, msglen); + munit_assert_int(buf[0], ==, msglen); + munit_assert_int(buf[1], ==, id); + munit_assert_int(buf[2], ==, opcode); + ufloat.bytes = buf[3]; + munit_assert_double_equal(ufloat.f, 1.234, 4/* precision */); + munit_assert_int(buf[4], ==, -12); } { - /* Packet two: 'foobar' */ - char return_buffer[1024] = {0}; - send_data(sock_write, "foobar", 6); - int rc = brei_dispatch(sock_read, brei_dispatch_cb, return_buffer); - munit_assert_int(rc, ==, 0); - munit_assert_string_equal(return_buffer, "foobar"); + const char string[12] = "hello wor"; /* tests padding too */ + int slen = bytes_to_int32(strlen(string) + 1) * 4; + munit_assert_int(slen, ==, sizeof(string)); + + const int msglen = 28 + slen; /* 12 header + 3 * 4 bytes + 4 bytes slen + string length */ + uint32_t id = 2; + uint32_t opcode = 3; + const char *signature = "ison"; + int rc = brei_send_message_va(sock_write, id, opcode, signature, 4, + -42, string, 0xab, 0xcdef); + munit_assert_int(rc, ==, msglen); + + uint32_t buf[64]; + int len = read(sock_read, buf, sizeof(buf)); + munit_assert_int(len, ==, msglen); + munit_assert_int(buf[0], ==, msglen); + munit_assert_int(buf[1], ==, id); + munit_assert_int(buf[2], ==, opcode); + munit_assert_int(buf[3], ==, -42); + munit_assert_int(buf[4], ==, strlen(string) + 1); + munit_assert_string_equal((const char*)&buf[5], string); + munit_assert_int(memcmp(&buf[5], string, slen), ==, 0); + + munit_assert_int(buf[5 + slen/4], ==, 0xab); + munit_assert_int(buf[6 + slen/4], ==, 0xcdef); } { - /* Packet three: 'foobar' but last char truncated */ - char return_buffer[1024] = {0}; - send_data(sock_write, "foobar", 5); /* truncated */ - int rc = brei_dispatch(sock_read, brei_dispatch_cb, return_buffer); + const char string1[12] = "hello wor"; /* tests padding too */ + const char string2[8] = "barba"; /* tests padding too */ + + const int msglen = 20 + sizeof(string1) + sizeof(string2); /* 12 header + 2 * 4 bytes slen + string lengths */ + uint32_t id = 2; + uint32_t opcode = 3; + const char *signature = "ss"; + int rc = brei_send_message_va(sock_write, id, opcode, signature, 2, string1, string2); + munit_assert_int(rc, ==, msglen); + + uint32_t buf[64]; + int len = read(sock_read, buf, sizeof(buf)); + munit_assert_int(len, ==, msglen); + munit_assert_int(buf[0], ==, msglen); + munit_assert_int(buf[1], ==, id); + munit_assert_int(buf[2], ==, opcode); + munit_assert_int(buf[3], ==, strlen(string1) + 1); + munit_assert_string_equal((const char*)&buf[4], string1); + munit_assert_int(buf[7], ==, strlen(string2) + 1); + munit_assert_string_equal((const char*)&buf[8], string2); + } + + { + int fds[2]; + int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, fds); munit_assert_int(rc, ==, 0); - munit_assert_string_equal(return_buffer, "fooba"); + _cleanup_close_ int left = fds[0]; + _cleanup_close_ int right = fds[1]; + + /* actual message data to be sent */ + char data[] = "some data\n"; + + const int msglen = 20; /* 12 header, 2 unsigned, fd is not in data */ + uint32_t id = 2; + uint32_t opcode = 3; + const char *signature = "uhu"; + rc = brei_send_message_va(sock_write, id, opcode, signature, 3, 0xab, right, 0xcd); + munit_assert_int(rc, ==, msglen); + + /* We passed it down, can close it now */ + close(right); + right = -1; + + /* receive the brei message */ + _cleanup_iobuf_ struct iobuf *recv = iobuf_new(64); + int len = iobuf_recv_from_fd(recv, sock_read); + + uint32_t *buf = (uint32_t*)iobuf_data(recv); + munit_assert_int(len, ==, msglen); + munit_assert_int(buf[0], ==, msglen); + munit_assert_int(buf[1], ==, id); + munit_assert_int(buf[2], ==, opcode); + munit_assert_int(buf[3], ==, 0xab); + /* fd is not in data */ + munit_assert_int(buf[4], ==, 0xcd); + + _cleanup_close_ int fd = iobuf_take_fd(recv); + munit_assert_int(fd, !=, -1); + munit_assert_int(iobuf_take_fd(recv), ==, -1); /* only one fd */ + + /* send some data down the dup'd fd */ + rc = xsend(fd, data, sizeof(data)); + munit_assert_int(rc, ==, sizeof(data)); + + char recvbuf[64] = {0}; + rc = xread(left, recvbuf, sizeof(recvbuf)); + munit_assert_int(rc, ==, sizeof(data)); + munit_assert_string_equal(recvbuf, data); } return MUNIT_OK; } + #endif diff --git a/src/brei-shared.h b/src/brei-shared.h index d157dbf..fee6f60 100644 --- a/src/brei-shared.h +++ b/src/brei-shared.h @@ -27,12 +27,60 @@ #include "config.h" #include +#include +#include + +struct brei_interface; +struct brei_message; +struct brei_object; + +union brei_arg { + uint32_t u32; + int32_t i32; + float f32; + int fd; + const char *str; + uint32_t obj; +}; struct brei_message { - const char *data; - size_t len; + const char *name; /* request/event name */ + const char *signature; + const struct brei_interface **types; }; +typedef int (*brei_event_dispatcher)(void *object, uint32_t opcode, + size_t nargs, union brei_arg *args); + +struct brei_interface { + const char *name; + uint32_t version; + uint32_t nrequests; + const struct brei_message *requests; + uint32_t nevents; + const struct brei_message *events; + + uint32_t nincoming; + const struct brei_message *incoming; + + brei_event_dispatcher dispatcher; +}; + +struct brei_object { + const struct brei_interface *interface; + void *implementation; /* pointer to the actual object */ + uint32_t id; /* protocol object id */ + uint32_t version; /* protocol object interface version */ +}; + +int +brei_send_message(int fd, + uint32_t id, + uint32_t opcode, + const char *signature, + size_t nargs, + va_list args); + /** * Return the first file descriptor passed along with this message, or -1. * You must only call this when a message is supposed to have an fd. @@ -56,7 +104,7 @@ brei_message_take_fd(struct brei_message *b); */ int brei_dispatch(int fd, - int (*callback)(struct brei_message *msg, void *userdata), + int (*lookup_object)(uint32_t object_id, struct brei_object **object, void *user_data), void *user_data); /** diff --git a/src/libei-private.h b/src/libei-private.h index 6fe4739..50cf8dc 100644 --- a/src/libei-private.h +++ b/src/libei-private.h @@ -30,6 +30,7 @@ #include "util-object.h" #include "libei.h" +#include "brei-shared.h" #include "util-list.h" #include "util-sources.h" #include "util-structs.h" @@ -55,6 +56,8 @@ enum ei_state { struct ei { struct object object; + struct brei_object proto_object; + void *user_data; struct sink *sink; struct source *source; @@ -70,22 +73,30 @@ struct ei { enum ei_log_priority priority; } log; - const struct ei_proto_requests *requests; - bool is_sender; uint32_t server_version; uint32_t client_version; }; +const struct ei_interface * +ei_get_interface(struct ei *ei); + int ei_set_socket(struct ei *ei, int fd); void ei_disconnect(struct ei *ei); +const struct brei_object * +ei_get_proto_object(struct ei *ei); + struct ei * ei_get_context(struct ei *ei); +int +ei_send_message(struct ei *ei, uint32_t object_id, + uint32_t opcode, const char *signature, size_t nargs, ...); + int ei_send_seat_bind(struct ei_seat *seat, uint32_t capabilities); diff --git a/src/libei.c b/src/libei.c index 9c14cab..0d5adb0 100644 --- a/src/libei.c +++ b/src/libei.c @@ -42,8 +42,8 @@ #include "libei.h" #include "libei-private.h" -#include "libei-proto.h" #include "brei-shared.h" +#include "ei-proto.h" _Static_assert(sizeof(enum ei_device_capability) == sizeof(int), "Invalid enum size"); _Static_assert(sizeof(enum ei_keymap_type) == sizeof(int), "Invalid enum size"); @@ -110,6 +110,13 @@ OBJECT_IMPLEMENT_GETTER(ei, user_data, void *); DEFINE_UNREF_CLEANUP_FUNC(ei_device); DEFINE_UNREF_CLEANUP_FUNC(ei_region); + +const struct brei_object * +ei_get_proto_object(struct ei *ei) +{ + return &ei->proto_object; +} + struct ei * ei_get_context(struct ei *ei) { @@ -122,7 +129,9 @@ ei_create_context(bool is_sender, void *user_data) _unref_(ei) *ei = ei_create(NULL); ei->state = EI_STATE_NEW; - ei->requests = ei_proto_get_requests(); + ei->proto_object.id = 0; + ei->proto_object.implementation = ei; + ei->proto_object.interface = &ei_proto_interface; list_init(&ei->event_queue); list_init(&ei->seats); @@ -571,7 +580,7 @@ ei_disconnect(struct ei *ei) } if (state != EI_STATE_NEW) { - ei->requests->disconnect(ei); + ei_request_disconnect(ei); } queue_disconnect_event(ei); ei->state = EI_STATE_DISCONNECTED; @@ -664,7 +673,7 @@ handle_msg_device_added(struct ei *ei, uint32_t deviceid, uint32_t capabilities, static int handle_msg_device_keymap(struct ei *ei, uint32_t deviceid, enum ei_keymap_type keymap_type, - size_t keymap_sz, int keymap_fd) + uint32_t keymap_sz, int keymap_fd) { log_debug(ei, "Adding keymap for %#x", deviceid); @@ -708,7 +717,7 @@ static int handle_msg_device_region(struct ei *ei, uint32_t deviceid, uint32_t x, uint32_t y, uint32_t w, uint32_t h, - double scale) + float scale) { log_debug(ei, "Adding device region for %#x", deviceid); @@ -803,7 +812,7 @@ ei_send_close_device(struct ei_device *device) if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; - int rc = ei->requests->close_device(device); + int rc = ei_request_close_device(ei, device->id); if (rc) ei_disconnect(ei); return rc; @@ -817,7 +826,7 @@ ei_send_start_emulating(struct ei_device *device, uint32_t sequence) if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; - int rc = ei->requests->start_emulating(device, sequence); + int rc = ei_request_start_emulating(ei, device->id, sequence); if (rc) ei_disconnect(ei); return rc; @@ -831,7 +840,7 @@ ei_send_stop_emulating(struct ei_device *device) if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; - int rc = ei->requests->stop_emulating(device); + int rc = ei_request_stop_emulating(ei, device->id); if (rc) ei_disconnect(ei); return rc; @@ -845,7 +854,7 @@ ei_send_seat_bind(struct ei_seat *seat, uint32_t capabilities) if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; - int rc = ei->requests->bind_seat(seat, capabilities); + int rc = ei_request_bind_seat(ei, seat->id, capabilities); if (rc) ei_disconnect(ei); return rc; @@ -864,7 +873,7 @@ ei_send_frame(struct ei_device *device, uint64_t time) device->send_frame_event = false; - int rc = ei->requests->frame(device, time); + int rc = ei_request_frame(ei, device->id, us2ms(time), time % 1000); if (rc) ei_disconnect(ei); return rc; @@ -880,7 +889,7 @@ ei_send_pointer_rel(struct ei_device *device, double x, double y) device->send_frame_event = true; - int rc = ei->requests->rel(device, x, y); + int rc = ei_request_pointer_relative(ei, device->id, x, y); if (rc) ei_disconnect(ei); return rc; @@ -896,7 +905,7 @@ ei_send_pointer_abs(struct ei_device *device, double x, double y) device->send_frame_event = true; - int rc = ei->requests->abs(device, x, y); + int rc = ei_request_pointer_absolute(ei, device->id, x, y); if (rc) ei_disconnect(ei); return rc; @@ -912,7 +921,7 @@ ei_send_pointer_button(struct ei_device *device, uint32_t button, bool is_press) device->send_frame_event = true; - int rc = ei->requests->button(device, button, is_press); + int rc = ei_request_pointer_button(ei, device->id, button, is_press); if (rc) ei_disconnect(ei); return rc; @@ -927,7 +936,7 @@ int ei_send_pointer_scroll(struct ei_device *device, double x, double y) device->send_frame_event = true; - int rc = ei->requests->scroll(device, x, y); + int rc = ei_request_pointer_scroll(ei, device->id, x, y); if (rc) ei_disconnect(ei); return rc; @@ -942,7 +951,7 @@ int ei_send_pointer_scroll_stop(struct ei_device *device, double x, double y) device->send_frame_event = true; - int rc = ei->requests->scroll_stop(device, x, y); + int rc = ei_request_pointer_scroll_stop(ei, device->id, x, y, false); if (rc) ei_disconnect(ei); return rc; @@ -957,7 +966,7 @@ int ei_send_pointer_scroll_cancel(struct ei_device *device, double x, double y) device->send_frame_event = true; - int rc = ei->requests->scroll_cancel(device, x, y); + int rc = ei_request_pointer_scroll_stop(ei, device->id, x, y, true); if (rc) ei_disconnect(ei); return rc; @@ -973,7 +982,7 @@ int ei_send_pointer_scroll_discrete(struct ei_device *device, int32_t x, int32_t device->send_frame_event = true; - int rc = ei->requests->scroll_discrete(device, x, y); + int rc = ei_request_pointer_scroll_discrete(ei, device->id, x, y); if (rc) ei_disconnect(ei); return rc; @@ -989,7 +998,7 @@ ei_send_keyboard_key(struct ei_device *device, uint32_t key, bool is_press) device->send_frame_event = true; - int rc = ei->requests->key(device, key, is_press); + int rc = ei_request_keyboard_key(ei, device->id, key, is_press); if (rc) ei_disconnect(ei); return rc; @@ -1006,7 +1015,7 @@ ei_send_touch_down(struct ei_device *device, uint32_t tid, device->send_frame_event = true; - int rc = ei->requests->touch_down(device, tid, x, y); + int rc = ei_request_touch_down(ei, device->id, tid, x, y); if (rc) ei_disconnect(ei); return rc; @@ -1023,7 +1032,7 @@ ei_send_touch_motion(struct ei_device *device, uint32_t tid, device->send_frame_event = true; - int rc = ei->requests->touch_motion(device, tid, x, y); + int rc = ei_request_touch_motion(ei, device->id, tid, x, y); if (rc) ei_disconnect(ei); return rc; @@ -1039,7 +1048,7 @@ ei_send_touch_up(struct ei_device *device, uint32_t tid) device->send_frame_event = true; - int rc = ei->requests->touch_up(device, tid); + int rc = ei_request_touch_up(ei, device->id, tid); if (rc) ei_disconnect(ei); return rc; @@ -1111,21 +1120,20 @@ handle_msg_stop_emulating(struct ei *ei, uint32_t deviceid) } static int -handle_msg_frame(struct ei *ei, uint32_t deviceid, uint64_t time) +handle_msg_frame(struct ei *ei, uint32_t deviceid, uint32_t time, uint32_t micros) { DISCONNECT_IF_SENDER_CONTEXT(ei); struct ei_device *device = ei_find_device(ei, deviceid); if (device) - return ei_device_event_frame(device, time); + return ei_device_event_frame(device, ms2us(time) + micros); return 0; } static int -handle_msg_pointer_rel(struct ei *ei, uint32_t deviceid, - double x, double y) +handle_msg_pointer_rel(struct ei *ei, uint32_t deviceid, float x, float y) { DISCONNECT_IF_SENDER_CONTEXT(ei); @@ -1138,8 +1146,7 @@ handle_msg_pointer_rel(struct ei *ei, uint32_t deviceid, } static int -handle_msg_pointer_abs(struct ei *ei, uint32_t deviceid, - double x, double y) +handle_msg_pointer_abs(struct ei *ei, uint32_t deviceid, float x, float y) { DISCONNECT_IF_SENDER_CONTEXT(ei); @@ -1153,21 +1160,20 @@ handle_msg_pointer_abs(struct ei *ei, uint32_t deviceid, static int handle_msg_pointer_button(struct ei *ei, uint32_t deviceid, - uint32_t button, bool state) + uint32_t button, uint32_t state) { DISCONNECT_IF_SENDER_CONTEXT(ei); struct ei_device *device = ei_find_device(ei, deviceid); if (device) - return ei_device_event_pointer_button(device, button, state); + return ei_device_event_pointer_button(device, button, !!state); return -EINVAL; } static int -handle_msg_pointer_scroll(struct ei *ei, uint32_t deviceid, - double x, double y) +handle_msg_pointer_scroll(struct ei *ei, uint32_t deviceid, float x, float y) { DISCONNECT_IF_SENDER_CONTEXT(ei); @@ -1195,7 +1201,7 @@ handle_msg_pointer_scroll_discrete(struct ei *ei, uint32_t deviceid, static int handle_msg_pointer_scroll_stop(struct ei *ei, uint32_t deviceid, - bool x, bool y, bool is_cancel) + uint32_t x, uint32_t y, uint32_t is_cancel) { DISCONNECT_IF_SENDER_CONTEXT(ei); @@ -1203,9 +1209,9 @@ handle_msg_pointer_scroll_stop(struct ei *ei, uint32_t deviceid, if (device) { if (is_cancel) - return ei_device_event_pointer_scroll_cancel(device, x, y); + return ei_device_event_pointer_scroll_cancel(device, !!x, !!y); else - return ei_device_event_pointer_scroll_stop(device, x, y); + return ei_device_event_pointer_scroll_stop(device, !!x, !!y); } return -EINVAL; @@ -1213,21 +1219,21 @@ handle_msg_pointer_scroll_stop(struct ei *ei, uint32_t deviceid, static int handle_msg_keyboard_key(struct ei *ei, uint32_t deviceid, - uint32_t key, bool state) + uint32_t key, uint32_t state) { DISCONNECT_IF_SENDER_CONTEXT(ei); struct ei_device *device = ei_find_device(ei, deviceid); if (device) - return ei_device_event_keyboard_key(device, key, state); + return ei_device_event_keyboard_key(device, key, !!state); return -EINVAL; } static int handle_msg_touch_down(struct ei *ei, uint32_t deviceid, - uint32_t touchid, double x, double y) + uint32_t touchid, float x, float y) { DISCONNECT_IF_SENDER_CONTEXT(ei); @@ -1241,7 +1247,7 @@ handle_msg_touch_down(struct ei *ei, uint32_t deviceid, static int handle_msg_touch_motion(struct ei *ei, uint32_t deviceid, - uint32_t touchid, double x, double y) + uint32_t touchid, float x, float y) { DISCONNECT_IF_SENDER_CONTEXT(ei); @@ -1298,25 +1304,25 @@ handle_msg_version_during_connection(struct ei *ei, uint32_t version) return ei_finish_set_socket(ei, ei->client_version); } -static const struct ei_proto_interface intf_state_backend = { +static const struct ei_interface intf_state_backend = { .version = handle_msg_version_during_connection, /* Everything triggers -EPROTO */ .connected = NULL, }; -static const struct ei_proto_interface intf_state_version_query = { +static const struct ei_interface intf_state_version_query = { .version = handle_msg_version_during_connection, /* Everything else triggers -EPROTO */ .connected = NULL, }; -static const struct ei_proto_interface intf_state_connecting = { +static const struct ei_interface intf_state_connecting = { .connected = handle_msg_connected, .disconnected = handle_msg_disconnected, .version = handle_msg_version, }; -static const struct ei_proto_interface intf_state_connected = { +static const struct ei_interface intf_state_connected = { .disconnected = handle_msg_disconnected, .seat_added = handle_msg_seat_added, .seat_removed = handle_msg_seat_removed, @@ -1333,20 +1339,20 @@ static const struct ei_proto_interface intf_state_connected = { /* events */ .start_emulating = handle_msg_start_emulating, .stop_emulating = handle_msg_stop_emulating, - .rel = handle_msg_pointer_rel, - .abs = handle_msg_pointer_abs, - .button = handle_msg_pointer_button, - .scroll = handle_msg_pointer_scroll, - .scroll_stop = handle_msg_pointer_scroll_stop, - .scroll_discrete = handle_msg_pointer_scroll_discrete, - .key = handle_msg_keyboard_key, + .pointer_relative = handle_msg_pointer_rel, + .pointer_absolute = handle_msg_pointer_abs, + .pointer_button = handle_msg_pointer_button, + .pointer_scroll = handle_msg_pointer_scroll, + .pointer_scroll_stop = handle_msg_pointer_scroll_stop, + .pointer_scroll_discrete = handle_msg_pointer_scroll_discrete, + .keyboard_key = handle_msg_keyboard_key, .touch_down = handle_msg_touch_down, .touch_motion = handle_msg_touch_motion, .touch_up = handle_msg_touch_up, .frame = handle_msg_frame, }; -static const struct ei_proto_interface *interfaces[] = { +static const struct ei_interface *interfaces[] = { [EI_STATE_NEW] = NULL, [EI_STATE_BACKEND] = &intf_state_backend, [EI_STATE_VERSION_QUERY] = &intf_state_version_query, @@ -1356,15 +1362,23 @@ static const struct ei_proto_interface *interfaces[] = { [EI_STATE_DISCONNECTED] = NULL, }; + +const struct ei_interface * +ei_get_interface(struct ei *ei) +{ + assert(ei->state < ARRAY_LENGTH(interfaces)); + return interfaces[ei->state]; +} + static int -connection_message_callback(struct brei_message *bmsg, void *userdata) +lookup_object(uint32_t object_id, struct brei_object **object, void *userdata) { struct ei *ei = userdata; - assert(ei->state < ARRAY_LENGTH(interfaces)); - const struct ei_proto_interface *intf = interfaces[ei->state]; + assert(object_id == 0); /* We only have one object atm */ - return ei_proto_handle_message(ei, intf, bmsg); + *object = &ei->proto_object; + return 0; } static void @@ -1373,7 +1387,7 @@ connection_dispatch(struct source *source, void *userdata) struct ei *ei = userdata; enum ei_state old_state = ei->state; - int rc = brei_dispatch(source_get_fd(source), connection_message_callback, ei); + int rc = brei_dispatch(source_get_fd(source), lookup_object, ei); if (rc < 0) { brei_drain_fd(source_get_fd(source)); ei_disconnect(ei); @@ -1398,6 +1412,21 @@ connection_dispatch(struct source *source, void *userdata) states[ei->state]); } +int +ei_send_message(struct ei *ei, uint32_t object_id, + uint32_t opcode, const char *signature, size_t nargs, ...) +{ + int fd = source_get_fd(ei->source); + + log_debug(ei, "sending: %#x:%u signature '%s'", object_id, opcode, signature); + + va_list args; + va_start(args, nargs); + int rc = brei_send_message(fd, object_id, opcode, signature, nargs, args); + va_end(args); + return rc < 0 ? rc : 0; +} + int ei_set_socket(struct ei *ei, int fd) { @@ -1416,23 +1445,23 @@ ei_set_socket(struct ei *ei, int fd) if (ei->state == EI_STATE_BACKEND) { /* The server didn't send the version number, so request it. */ - rc = ei->requests->get_version(ei); + rc = ei_request_get_version(ei); ei->state = EI_STATE_VERSION_QUERY; } } source_unref(source); - return rc; + return rc < 0 ? rc : 0; } static int ei_finish_set_socket(struct ei *ei, uint32_t version) { - int rc = ei->requests->connect(ei, version); + int rc = ei_request_connect(ei, version, ei->name, ei->is_sender); if (rc == 0) { - rc = ei->requests->connect_done(ei); + rc = ei_request_connect_done(ei); } if (rc == 0) { ei->state = EI_STATE_CONNECTING; @@ -1442,7 +1471,7 @@ ei_finish_set_socket(struct ei *ei, uint32_t version) ei_disconnect(ei); } - return rc; + return rc < 0 ? rc : 0; } _public_ void diff --git a/src/libeis-client.c b/src/libeis-client.c index 43184b0..a6ad42f 100644 --- a/src/libeis-client.c +++ b/src/libeis-client.c @@ -37,10 +37,12 @@ #include "util-strings.h" #include "util-structs.h" #include "util-tristate.h" +#include "util-time.h" #include "libeis-private.h" #include "libeis-proto.h" #include "brei-shared.h" +#include "eis-proto.h" DEFINE_TRISTATE(started, finished, connected); @@ -77,6 +79,12 @@ eis_client_get_context(struct eis_client *client) return eis_client_parent(client); } +const struct brei_object * +eis_client_get_proto_object(struct eis_client *client) +{ + return &client->proto_object; +} + struct eis_client * eis_client_get_client(struct eis_client *client) { @@ -136,89 +144,100 @@ eis_client_find_seat(struct eis_client *client, uint32_t seatid) return NULL; } +int +eis_client_send_message(struct eis_client *client, uint32_t object_id, + uint32_t opcode, const char *signature, size_t nargs, ...) +{ + struct eis *eis = eis_client_get_context(client); + int fd = source_get_fd(client->source); + + log_debug(eis, "sending: %#x:%u signature '%s'", object_id, opcode, signature); + + va_list args; + va_start(args, nargs); + int rc = brei_send_message(fd, object_id, opcode, signature, nargs, args); + va_end(args); + return rc < 0 ? rc : 0; +} + static int client_send_version(struct eis_client *client, uint32_t version) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->version(client, version); + return eis_event_version(client, version); } static int client_send_disconnect(struct eis_client *client) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->disconnected(client); + return eis_event_disconnected(client); } static int client_send_connect(struct eis_client *client) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->connected(client); + return eis_event_connected(client); } static int client_send_seat_added(struct eis_client *client, struct eis_seat *seat) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->seat_added(seat); + return eis_event_seat_added(client, seat->id, seat->capabilities_mask, seat->name); } static int client_send_seat_removed(struct eis_client *client, struct eis_seat *seat) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->seat_removed(seat); + return eis_event_seat_removed(client, seat->id); } static int client_send_device_added(struct eis_client *client, struct eis_device *device) { - struct eis *eis = eis_client_get_context(client); - int rc = eis->requests->device_added(device); + struct eis_seat *seat = eis_device_get_seat(device); + int rc = eis_event_device_added(client, device->id, device->capabilities, + device->name, seat->id, + device->type, device->width, device->height); if (rc >= 0 && device->keymap) - rc = eis->requests->device_keymap(device); + rc = eis_event_device_keymap(client, device->id, device->keymap->type, device->keymap->size, device->keymap->fd); if (rc >= 0 && device->type == EIS_DEVICE_TYPE_VIRTUAL) { struct eis_region *r; list_for_each(r, &device->regions, link) { - rc = eis->requests->device_region(device, r); + rc = eis_event_device_region(client, device->id, r->x, r->y, r->width, r->height, r->physical_scale); } } if (rc >= 0) - rc = eis->requests->device_done(device); + rc = eis_event_device_done(client, device->id); return rc; } static int client_send_device_removed(struct eis_client *client, struct eis_device *device) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->device_removed(device); + return eis_event_device_removed(client, device->id); } static int client_send_device_paused(struct eis_client *client, struct eis_device *device) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->device_paused(device); + return eis_event_device_paused(client, device->id); } static int client_send_device_resumed(struct eis_client *client, struct eis_device *device) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->device_resumed(device); + return eis_event_device_resumed(client, device->id); } static int client_send_keyboard_modifiers(struct eis_client *client, struct eis_device *device, const struct eis_xkb_modifiers *mods) { - struct eis *eis = eis_client_get_context(client); - return eis->requests->keyboard_modifiers(device, mods); + return eis_event_keyboard_modifiers(client, device->id, + mods->depressed, mods->locked, + mods->latched, mods->group); } _public_ void @@ -329,21 +348,21 @@ client_msg_stop_emulating(struct eis_client *client, uint32_t deviceid) } static int -client_msg_frame(struct eis_client *client, uint32_t deviceid, uint64_t time) +client_msg_frame(struct eis_client *client, uint32_t deviceid, uint32_t time, uint32_t micros) { DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) - return eis_device_event_frame(device, time); + return eis_device_event_frame(device, ms2us(time) + micros); return 0; } static int client_msg_pointer_rel(struct eis_client *client, uint32_t deviceid, - double x, double y) + float x, float y) { DISCONNECT_IF_RECEIVER_CONTEXT(client); @@ -357,7 +376,7 @@ client_msg_pointer_rel(struct eis_client *client, uint32_t deviceid, static int client_msg_pointer_abs(struct eis_client *client, uint32_t deviceid, - double x, double y) + float x, float y) { DISCONNECT_IF_RECEIVER_CONTEXT(client); @@ -371,21 +390,21 @@ client_msg_pointer_abs(struct eis_client *client, uint32_t deviceid, static int client_msg_pointer_button(struct eis_client *client, uint32_t deviceid, - uint32_t button, bool state) + uint32_t button, uint32_t state) { DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) - return eis_device_event_pointer_button(device, button, state); + return eis_device_event_pointer_button(device, button, !!state); return -EINVAL; } static int client_msg_pointer_scroll(struct eis_client *client, uint32_t deviceid, - double x, double y) + float x, float y) { DISCONNECT_IF_RECEIVER_CONTEXT(client); @@ -413,7 +432,7 @@ client_msg_pointer_scroll_discrete(struct eis_client *client, uint32_t deviceid, static int client_msg_pointer_scroll_stop(struct eis_client *client, uint32_t deviceid, - bool x, bool y, bool is_cancel) + uint32_t x, uint32_t y, uint32_t is_cancel) { DISCONNECT_IF_RECEIVER_CONTEXT(client); @@ -421,9 +440,9 @@ client_msg_pointer_scroll_stop(struct eis_client *client, uint32_t deviceid, if (device) { if (is_cancel) - return eis_device_event_pointer_scroll_cancel(device, x, y); + return eis_device_event_pointer_scroll_cancel(device, !!x, !!y); else - return eis_device_event_pointer_scroll_stop(device, x, y); + return eis_device_event_pointer_scroll_stop(device, !!x, !!y); } return -EINVAL; @@ -431,21 +450,21 @@ client_msg_pointer_scroll_stop(struct eis_client *client, uint32_t deviceid, static int client_msg_keyboard_key(struct eis_client *client, uint32_t deviceid, - uint32_t key, bool state) + uint32_t key, uint32_t state) { DISCONNECT_IF_RECEIVER_CONTEXT(client); struct eis_device *device = eis_client_find_device(client, deviceid); if (device) - return eis_device_event_keyboard_key(device, key, state); + return eis_device_event_keyboard_key(device, key, !!state); return -EINVAL; } static int client_msg_touch_down(struct eis_client *client, uint32_t deviceid, - uint32_t touchid, double x, double y) + uint32_t touchid, float x, float y) { DISCONNECT_IF_RECEIVER_CONTEXT(client); @@ -459,7 +478,7 @@ client_msg_touch_down(struct eis_client *client, uint32_t deviceid, static int client_msg_touch_motion(struct eis_client *client, uint32_t deviceid, - uint32_t touchid, double x, double y) + uint32_t touchid, float x, float y) { DISCONNECT_IF_RECEIVER_CONTEXT(client); @@ -486,7 +505,7 @@ client_msg_touch_up(struct eis_client *client, uint32_t deviceid, uint32_t touch static int client_msg_connect(struct eis_client *client, uint32_t version, - const char *name, bool is_sender) + const char *name, uint32_t is_sender) { if (client->version > EIS_PROTOCOL_VERSION) return -EPROTO; @@ -496,7 +515,7 @@ client_msg_connect(struct eis_client *client, uint32_t version, if (client->name == NULL) client->name = xstrdup(name); - client->is_sender = is_sender; + client->is_sender = !!is_sender; return 0; } @@ -522,7 +541,7 @@ client_msg_get_version(struct eis_client *client) return client_send_version(client, EI_PROTOCOL_VERSION); } -static const struct eis_proto_interface intf_state_new = { +static const struct eis_interface intf_state_new = { .connect = client_msg_connect, .connect_done = client_msg_connect_done, .disconnect = client_msg_disconnect, @@ -530,12 +549,12 @@ static const struct eis_proto_interface intf_state_new = { }; /* Client is waiting for us, shouldn't send anything except disconnect */ -static const struct eis_proto_interface intf_state_connecting = { +static const struct eis_interface intf_state_connecting = { .disconnect = client_msg_disconnect, .get_version = client_msg_get_version, }; -static const struct eis_proto_interface intf_state_connected = { +static const struct eis_interface intf_state_connected = { .disconnect = client_msg_disconnect, .bind_seat = client_msg_bind_seat, .close_device = client_msg_close_device, @@ -544,35 +563,42 @@ static const struct eis_proto_interface intf_state_connected = { /* events */ .start_emulating = client_msg_start_emulating, .stop_emulating = client_msg_stop_emulating, - .rel = client_msg_pointer_rel, - .abs = client_msg_pointer_abs, - .button = client_msg_pointer_button, - .scroll = client_msg_pointer_scroll, - .scroll_stop = client_msg_pointer_scroll_stop, - .scroll_discrete = client_msg_pointer_scroll_discrete, - .key = client_msg_keyboard_key, + .pointer_relative = client_msg_pointer_rel, + .pointer_absolute = client_msg_pointer_abs, + .pointer_button = client_msg_pointer_button, + .pointer_scroll = client_msg_pointer_scroll, + .pointer_scroll_stop = client_msg_pointer_scroll_stop, + .pointer_scroll_discrete = client_msg_pointer_scroll_discrete, + .keyboard_key = client_msg_keyboard_key, .touch_down = client_msg_touch_down, .touch_motion = client_msg_touch_motion, .touch_up = client_msg_touch_up, .frame = client_msg_frame, }; -static const struct eis_proto_interface *interfaces[] = { +static const struct eis_interface *interfaces[] = { [EIS_CLIENT_STATE_NEW] = &intf_state_new, [EIS_CLIENT_STATE_CONNECTING] = &intf_state_connecting, [EIS_CLIENT_STATE_CONNECTED] = &intf_state_connected, [EIS_CLIENT_STATE_DISCONNECTED] = NULL, }; +const struct eis_interface * +eis_client_get_interface(struct eis_client *client) +{ + assert(client->state < ARRAY_LENGTH(interfaces)); + return interfaces[client->state]; +} + static int -client_message_callback(struct brei_message *bmsg, void *userdata) +lookup_object(uint32_t object_id, struct brei_object **object, void *userdata) { struct eis_client *client = userdata; - assert(client->state < ARRAY_LENGTH(interfaces)); - const struct eis_proto_interface *intf = interfaces[client->state]; + assert(object_id == 0); /* We only have one object atm */ - return eis_proto_handle_message(client, intf, bmsg); + *object = &client->proto_object; + return 0; } static void @@ -581,7 +607,7 @@ client_dispatch(struct source *source, void *userdata) _unref_(eis_client) *client = eis_client_ref(userdata); enum eis_client_state old_state = client->state; - int rc = brei_dispatch(source_get_fd(source), client_message_callback, client); + int rc = brei_dispatch(source_get_fd(source), lookup_object, client); if (rc < 0) { brei_drain_fd(source_get_fd(source)); eis_client_disconnect(client); @@ -616,6 +642,10 @@ eis_client_new(struct eis *eis, int fd) static uint32_t client_id; struct eis_client *client = eis_client_create(&eis->object); + client->proto_object.id = 0; + client->proto_object.implementation = client; + client->proto_object.interface = &eis_proto_interface; + client->is_sender = true; client->id = ++client_id; list_init(&client->seats); diff --git a/src/libeis-client.h b/src/libeis-client.h index ae30694..a4208d9 100644 --- a/src/libeis-client.h +++ b/src/libeis-client.h @@ -25,6 +25,7 @@ #pragma once #include "libeis.h" +#include "brei-shared.h" #include "util-object.h" #include "util-list.h" @@ -38,6 +39,8 @@ enum eis_client_state { struct eis_client { struct object object; + struct brei_object proto_object; + void *user_data; struct list link; struct source *source; @@ -62,9 +65,19 @@ eis_client_new(struct eis *eis, int fd); struct eis_client * eis_client_get_client(struct eis_client *client); +const struct eis_interface * +eis_client_get_interface(struct eis_client *client); + +const struct brei_object * +eis_client_get_proto_object(struct eis_client *client); + void eis_add_client(struct eis *eis, struct eis_client *client); +int +eis_client_send_message(struct eis_client *client, uint32_t object_id, + uint32_t opcode, const char *signature, size_t nargs, ...); + void eis_client_add_seat(struct eis_client *client, struct eis_seat *seat); void diff --git a/src/libeis-device.c b/src/libeis-device.c index 2893dfd..d5ec597 100644 --- a/src/libeis-device.c +++ b/src/libeis-device.c @@ -29,9 +29,10 @@ #include "util-macros.h" #include "util-bits.h" #include "util-io.h" +#include "util-time.h" #include "libeis-private.h" -#include "libeis-proto.h" +#include "eis-proto.h" _public_ OBJECT_IMPLEMENT_REF(eis_keymap); @@ -309,13 +310,13 @@ eis_device_has_capability(struct eis_device *device, } #define handle_request_noargs(device_, func_) { \ - struct eis *eis = eis_device_get_context(device); \ - eis->requests->func_(device_, device->id); \ + struct eis_client *client = eis_device_get_client(device); \ + eis_event_##func_(client, device->id); \ } #define handle_request(device_, func_, ...) { \ - struct eis *eis = eis_device_get_context(device); \ - eis->requests->func_(device_, device->id, __VA_ARGS__); \ + struct eis_client *client = eis_device_get_client(device); \ + eis_event_##func_(client, device->id, __VA_ARGS__); \ } static void @@ -378,7 +379,7 @@ eis_device_pointer_motion(struct eis_device *device, device->send_frame_event = true; - handle_request(device, rel, x, y); + handle_request(device, pointer_relative, x, y); } _public_ void @@ -403,7 +404,7 @@ eis_device_pointer_motion_absolute(struct eis_device *device, device->send_frame_event = true; - handle_request(device, abs, x, y); + handle_request(device, pointer_absolute, x, y); } _public_ void @@ -429,7 +430,7 @@ eis_device_pointer_button(struct eis_device *device, device->send_frame_event = true; - handle_request(device, button, button, is_press); + handle_request(device, pointer_button, button, is_press); } static inline void @@ -462,7 +463,7 @@ eis_device_pointer_scroll(struct eis_device *device, device->send_frame_event = true; - handle_request(device, scroll, x, y); + handle_request(device, pointer_scroll, x, y); } _public_ void @@ -489,7 +490,7 @@ eis_device_pointer_scroll_stop(struct eis_device *device, bool x, bool y) if (x || y) { device->send_frame_event = true; - handle_request(device, scroll_stop, x, y, false); + handle_request(device, pointer_scroll_stop, x, y, false); } } @@ -521,7 +522,7 @@ eis_device_pointer_scroll_cancel(struct eis_device *device, bool x, bool y) if (x || y) { device->send_frame_event = true; - handle_request(device, scroll_stop, x, y, true); + handle_request(device, pointer_scroll_stop, x, y, true); } } @@ -542,7 +543,7 @@ eis_device_pointer_scroll_discrete(struct eis_device *device, device->send_frame_event = true; - handle_request(device, scroll_discrete, x, y); + handle_request(device, pointer_scroll_discrete, x, y); } _public_ void @@ -560,7 +561,7 @@ eis_device_keyboard_key(struct eis_device *device, device->send_frame_event = true; - handle_request(device, key, key, is_press); + handle_request(device, keyboard_key, key, is_press); } @@ -683,7 +684,7 @@ eis_device_frame(struct eis_device *device, uint64_t time) device->send_frame_event = false; - handle_request(device, frame, time); + handle_request(device, frame, us2ms(time), time % 1000); } int diff --git a/src/libeis-private.h b/src/libeis-private.h index af7ef63..3ba3a99 100644 --- a/src/libeis-private.h +++ b/src/libeis-private.h @@ -29,6 +29,7 @@ #include "util-object.h" #include "libeis.h" +#include "brei-shared.h" #include "util-macros.h" #include "util-list.h" #include "util-sources.h" diff --git a/src/libeis.c b/src/libeis.c index 93d7341..a080ab1 100644 --- a/src/libeis.c +++ b/src/libeis.c @@ -36,8 +36,8 @@ #include "util-time.h" #include "libeis.h" -#include "libeis-proto.h" #include "libeis-private.h" +#include "eis-proto.h" _Static_assert(sizeof(enum eis_device_capability) == sizeof(int), "Invalid enum size"); _Static_assert(sizeof(enum eis_keymap_type) == sizeof(int), "Invalid enum size"); @@ -78,8 +78,6 @@ eis_new(void *user_data) { _unref_(eis) *eis = eis_create(NULL); - eis->requests = eis_proto_get_requests(); - list_init(&eis->clients); list_init(&eis->event_queue); diff --git a/test/meson.build b/test/meson.build index 3611f11..b0dc15d 100644 --- a/test/meson.build +++ b/test/meson.build @@ -35,7 +35,7 @@ test('unit-tests-eis', src_libeis, include_directories: [inc_src, inc_proto, inc_builddir], c_args: ['-D_enable_tests_'], - dependencies: [dep_unittest, dep_libutil, dep_protobuf])) + dependencies: [dep_unittest, dep_libutil])) if build_oeffis test('unit-tests-oeffis',