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.
This commit is contained in:
Peter Hutterer 2023-02-01 10:32:15 +10:00
parent 49c329e0de
commit b02b4f0901
20 changed files with 1602 additions and 515 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
)

137
proto/ei-proto.c.tmpl Normal file
View file

@ -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 <assert.h>
#include <errno.h>
#include <sys/types.h>
#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 %}

88
proto/ei-proto.h.tmpl Normal file
View file

@ -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 <stdint.h>
/**
* 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

View file

@ -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;
}
}

View file

@ -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])

315
proto/protocol.xml Normal file
View file

@ -0,0 +1,315 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ei">
<copyright>
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.
</copyright>
<!--
Protocol wire format: [length, sender-id, opcode, ...]
Where:
- length, sender-id and opcode are 32-bit integers in the
EIS implementation's native byte order.
- length is the length of the message in bytes, including the 12 header bytes
- sender-id is the id of the object sending the request/event. The sender-id
0 is reserved for the special "ei" object.
- opcode is the event or requeset-specific opcode, starting at 0
requests and events have overlapping opcode ranges, i.e. the first request
and the first event both have opcode 0.
- arguments is a variable number of arguments specific to the message.
Types:
- 'uint': a 32-bit unsigned integer
- 'uint': a 32-bit signed integer
- 'float': a 32-bit IEEE-754 float
- 'fd': a file descriptor. Zero bytes in the message itself, transmitted
in the overhead
- 'string': a length-prefix zero-terminated string. Encoded as
one 32-bit unsigned integer for the length followed by the string.
The string is padded to the nearest 4-byte units, for example the string
"hello" is of length 6 but is zero-padded to 8 bytes. Full (le) encoding:
[0x06, 0x00, 0x00, 0x00, 'h', 'e', 'l', 'l', 'o', '\0', '\0\, '\0']
-->
<interface name="ei" version="1">
<description summary="core global object">
The core global object. This is a special singleton object. It
is used for internal ei protocol features.
</description>
<request name="get_version">
</request>
<request name="connect">
<arg name="version" type="uint"/>
<arg name="name" type="string"/>
<arg name="is_sender" type="uint"/>
</request>
<request name="connect_done">
</request>
<request name="disconnect">
</request>
<request name="bind_seat">
<arg name="seat_id" type="uint"/>
<arg name="capabilities" type="uint"/>
</request>
<request name="close_device">
<arg name="device_id" type="uint"/>
</request>
<request name="pointer_relative">
<arg name="device_id" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</request>
<request name="pointer_absolute">
<arg name="device_id" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</request>
<request name="pointer_scroll">
<arg name="device_id" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</request>
<request name="pointer_scroll_discrete">
<arg name="device_id" type="uint"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
</request>
<request name="pointer_scroll_stop">
<arg name="device_id" type="uint"/>
<arg name="x" type="uint"/>
<arg name="y" type="uint"/>
<arg name="is_cancel" type="uint"/>
</request>
<request name="pointer_button">
<arg name="device_id" type="uint"/>
<arg name="button" type="uint"/>
<arg name="state" type="uint"/>
</request>
<request name="keyboard_key">
<arg name="device_id" type="uint"/>
<arg name="key" type="uint"/>
<arg name="state" type="uint"/>
</request>
<request name="touch_down">
<arg name="device_id" type="uint"/>
<arg name="touchid" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</request>
<request name="touch_motion">
<arg name="device_id" type="uint"/>
<arg name="touchid" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</request>
<request name="touch_up">
<arg name="device_id" type="uint"/>
<arg name="touchid" type="uint"/>
</request>
<request name="start_emulating">
<arg name="device_id" type="uint"/>
<arg name="sequence" type="uint"/>
</request>
<request name="stop_emulating">
<arg name="device_id" type="uint"/>
</request>
<request name="frame">
<arg name="device_id" type="uint"/>
<arg name="timestamp" type="uint"/>
<arg name="micros" type="uint"/>
</request>
<event name="version">
<arg name="version" type="uint"/>
</event>
<event name="connected">
</event>
<event name="disconnected">
</event>
<event name="seat_added">
<arg name="seat_id" type="uint"/>
<arg name="capabilities" type="uint"/>
<arg name="name" type="string"/>
</event>
<event name="seat_removed">
<arg name="seat_id" type="uint"/>
</event>
<event name="device_added">
<arg name="device_id" type="uint"/>
<arg name="capabilities" type="uint"/>
<arg name="name" type="string"/>
<arg name="seat_id" type="uint"/>
<arg name="type" type="uint"/>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</event>
<event name="device_keymap">
<arg name="device_id" type="uint"/>
<arg name="keymap_type" type="uint"/>
<arg name="keymap_size" type="uint"/>
<arg name="keymap" type="fd"/>
</event>
<event name="device_region">
<arg name="device_id" type="uint"/>
<arg name="offset_x" type="uint"/>
<arg name="offset_y" type="uint"/>
<arg name="width" type="uint"/>
<arg name="hight" type="uint"/>
<arg name="scale" type="float"/>
</event>
<event name="device_done">
<arg name="device_id" type="uint"/>
</event>
<event name="device_removed">
<arg name="device_id" type="uint"/>
</event>
<event name="device_resumed">
<arg name="device_id" type="uint"/>
</event>
<event name="device_paused">
<arg name="device_id" type="uint"/>
</event>
<event name="pointer_relative">
<arg name="device_id" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</event>
<event name="pointer_absolute">
<arg name="device_id" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</event>
<event name="pointer_scroll">
<arg name="device_id" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</event>
<event name="pointer_scroll_discrete">
<arg name="device_id" type="uint"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
</event>
<event name="pointer_scroll_stop">
<arg name="device_id" type="uint"/>
<arg name="x" type="uint"/>
<arg name="y" type="uint"/>
<arg name="is_cancel" type="uint"/>
</event>
<event name="pointer_button">
<arg name="device_id" type="uint"/>
<arg name="button" type="uint"/>
<arg name="state" type="uint"/>
</event>
<event name="keyboard_key">
<arg name="device_id" type="uint"/>
<arg name="key" type="uint"/>
<arg name="state" type="uint"/>
</event>
<event name="keyboard_modifiers">
<arg name="device_id" type="uint"/>
<arg name="depressed" type="uint"/>
<arg name="locked" type="uint"/>
<arg name="latched" type="uint"/>
<arg name="group" type="uint"/>
</event>
<event name="touch_down">
<arg name="device_id" type="uint"/>
<arg name="touchid" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</event>
<event name="touch_motion">
<arg name="device_id" type="uint"/>
<arg name="touchid" type="uint"/>
<arg name="x" type="float"/>
<arg name="y" type="float"/>
</event>
<event name="touch_up">
<arg name="device_id" type="uint"/>
<arg name="touchid" type="uint"/>
</event>
<event name="start_emulating">
<arg name="device_id" type="uint"/>
<arg name="sequence" type="uint"/>
</event>
<event name="stop_emulating">
<arg name="device_id" type="uint"/>
</event>
<event name="frame">
<arg name="device_id" type="uint"/>
<arg name="timestamp" type="uint"/>
<arg name="micros" type="uint"/>
</event>
</interface>
</protocol>

405
proto/scanner.py Executable file
View file

@ -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()

View file

@ -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

View file

@ -27,12 +27,60 @@
#include "config.h"
#include <stddef.h>
#include <stdint.h>
#include <stdarg.h>
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);
/**

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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);

View file

@ -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',