ei: add ei_ping() and the matching EI_EVENT_PONG

This wraps around ei_connection.sync to allow a C API user to add
synchronization points.

Closes #69

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/316>
This commit is contained in:
Peter Hutterer 2024-12-04 11:17:56 +10:00 committed by Marge Bot
parent 2cc5e56e28
commit a278c7b371
13 changed files with 408 additions and 5 deletions

View file

@ -44,11 +44,8 @@ ei_connection_destroy(struct ei_connection *connection)
struct ei *ei = ei_connection_get_context(connection);
ei_unregister_object(ei, &connection->proto_object);
struct ei_callback *cb;
list_for_each_safe(cb, &connection->pending_callbacks, link) {
list_remove(&cb->link);
ei_callback_unref(cb);
}
/* should be a noop */
ei_connection_remove_pending_callbacks(connection);
}
OBJECT_IMPLEMENT_REF(ei_connection);
@ -101,6 +98,17 @@ ei_connection_new(struct ei *ei, object_id_t id, uint32_t version)
return connection; /* ref owned by caller */
}
void
ei_connection_remove_pending_callbacks(struct ei_connection *connection)
{
struct ei_callback *cb;
list_for_each_safe(cb, &connection->pending_callbacks, link) {
list_remove(&cb->link);
ei_connection_sync_callback_unref(cb->user_data);
ei_callback_unref(cb);
}
}
static void
ei_connection_sync_callback_destroy(struct ei_connection_sync_callback *callback)
{

View file

@ -54,6 +54,9 @@ OBJECT_DECLARE_UNREF(ei_connection);
struct ei_connection *
ei_connection_new(struct ei *ei, object_id_t id, uint32_t version);
void
ei_connection_remove_pending_callbacks(struct ei_connection *connection);
/**
* Called when the ei_callback.done event is received after
* an ei_connection_sync() request.

View file

@ -49,6 +49,7 @@ ei_event_type_to_string(enum ei_event_type type)
CASE_RETURN_STRING(EI_EVENT_DEVICE_PAUSED);
CASE_RETURN_STRING(EI_EVENT_DEVICE_RESUMED);
CASE_RETURN_STRING(EI_EVENT_KEYBOARD_MODIFIERS);
CASE_RETURN_STRING(EI_EVENT_PONG);
CASE_RETURN_STRING(EI_EVENT_FRAME);
CASE_RETURN_STRING(EI_EVENT_DEVICE_START_EMULATING);
CASE_RETURN_STRING(EI_EVENT_DEVICE_STOP_EMULATING);
@ -96,6 +97,9 @@ ei_event_destroy(struct ei_event *event)
case EI_EVENT_TOUCH_UP:
case EI_EVENT_TOUCH_MOTION:
break;
case EI_EVENT_PONG:
ei_ping_unref(event->pong.ping);
break;
}
ei_device_unref(event->device);
ei_seat_unref(event->seat);
@ -204,6 +208,14 @@ ei_event_keyboard_get_xkb_group(struct ei_event *event)
return event->modifiers.group;
}
_public_ struct ei_ping *
ei_event_pong_get_ping(struct ei_event *event)
{
require_event_type(event, NULL, EI_EVENT_PONG);
return event->pong.ping;
}
_public_ double
ei_event_pointer_get_dx(struct ei_event *event)
{

View file

@ -69,6 +69,9 @@ struct ei_event {
struct {
uint32_t sequence;
} start_emulating;
struct {
struct ei_ping *ping;
} pong;
};
};

124
src/libei-ping.c Normal file
View file

@ -0,0 +1,124 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2024 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.
*/
#include <stdio.h>
#include "util-mem.h"
#include "util-macros.h"
#include "util-object.h"
#include "util-list.h"
#include "brei-shared.h"
#include "libei-private.h"
#include "libei-connection.h"
struct ei_ping {
struct object object;
uint64_t id;
void *user_data;
struct ei *context;
bool is_pending;
bool is_done;
};
static void
ei_ping_destroy(struct ei_ping *ping)
{
if (!ping->is_pending)
ei_unref(ping->context);
}
static
OBJECT_IMPLEMENT_CREATE(ei_ping);
_public_
OBJECT_IMPLEMENT_REF(ei_ping);
_public_
OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_ping);
_public_
OBJECT_IMPLEMENT_GETTER(ei_ping, id, uint64_t);
_public_
OBJECT_IMPLEMENT_GETTER(ei_ping, user_data, void*);
_public_
OBJECT_IMPLEMENT_SETTER(ei_ping, user_data, void*);
static
OBJECT_IMPLEMENT_GETTER(ei_ping, context, struct ei*);
_public_ struct ei_ping *
ei_new_ping(struct ei *ei)
{
static uint64_t id = 0;
struct ei_ping *ping = ei_ping_create(NULL);
ping->id = ++id;
/* Ref our context while it's pending (i.e. only the caller has the ref).
* Once it's pending we no longer need the ref.
*/
ping->context = ei_ref(ei);
ping->is_pending = false;
ping->is_done = false;
return ping;
}
static void
on_pong(struct ei_connection_sync_callback *callback)
{
struct ei_ping *ping = ei_connection_sync_callback_get_user_data(callback);
ping->is_done = true;
struct ei *ei = ei_connection_sync_callback_get_context(callback);
ei_queue_pong_event(ei, ping);
/* ei_ping ref is removed in on_destroy */
}
static void
on_destroy(struct ei_connection_sync_callback *callback)
{
/* This is only called if we never received a pong */
_unref_(ei_ping) *ping = ei_connection_sync_callback_get_user_data(callback);
/* We never got a pong because we got disconnected. Queue a fake pong event */
if (!ping->is_done) {
struct ei *ei = ei_connection_sync_callback_get_context(callback);
ei_queue_pong_event(ei, ping);
}
}
_public_ void
ei_ping(struct ei_ping *ping)
{
struct ei *ei = ei_ping_get_context(ping);
ei_unref(ping->context);
ping->context = ei;
ping->is_pending = true;
_unref_(ei_connection_sync_callback) *cb = ei_connection_sync_callback_new(ei,
on_pong,
on_destroy,
ei_ping_ref(ping));
ei_connection_sync(ei->connection, cb);
}

View file

@ -168,6 +168,9 @@ ei_queue_connect_event(struct ei *ei);
void
ei_queue_disconnect_event(struct ei *ei);
void
ei_queue_pong_event(struct ei *ei, struct ei_ping *ping);
void
ei_queue_device_removed_event(struct ei_device *device);

View file

@ -326,6 +326,16 @@ ei_queue_disconnect_event(struct ei *ei)
queue_event(ei, e);
}
void
ei_queue_pong_event(struct ei *ei, struct ei_ping *ping)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_PONG;
e->pong.ping = ei_ping_ref(ping);
queue_event(ei, e);
}
void
ei_queue_seat_added_event(struct ei_seat *seat)
{
@ -613,6 +623,7 @@ ei_disconnect(struct ei *ei)
if (state != EI_STATE_NEW) {
ei_connection_request_disconnect(ei->connection);
ei_connection_remove_pending_callbacks(ei->connection);
}
ei_queue_disconnect_event(ei);
ei->state = EI_STATE_DISCONNECTED;

View file

@ -215,6 +215,16 @@ struct ei_region;
*/
struct ei_touch;
/**
* @struct ei_ping
*
* A callback struct returned by ei_new_ping() to handle
* roundtrips to the EIS implementation.
*
* @see ei_new_ping
*/
struct ei_ping;
/**
* @enum ei_device_type
* @ingroup libei-device
@ -412,6 +422,15 @@ enum ei_event_type {
*/
EI_EVENT_KEYBOARD_MODIFIERS,
/**
* Returned in response to ei_ping().
*
* Note that in the ei protocol, the matching client request is called `sync`
* but for consistency in the libeis/libei API the C API uses
* ei_ping() with `EI_EVENT_PONG` as return.
*/
EI_EVENT_PONG = 90,
/**
* "Hardware" frame event. This event **must** be sent by the server
* and notifies the client that the previous set of events belong to
@ -807,6 +826,95 @@ ei_setup_backend_socket(struct ei *ei, const char *socketpath);
int
ei_get_fd(struct ei *ei);
/**
* Create a new ei_ping object to trigger a round trip to the EIS implementation.
* See ei_ping() for details.
*
* The returned @ref ei_ping is refcounted, use ei_ping_unref() to
* drop the reference.
*
* @since 1.4
*/
struct ei_ping *
ei_new_ping(struct ei *ei);
/**
* Return a unique, increasing id for this struct. This ID is assigned at
* struct creation and constant for the lifetime of the struct. The ID
* does not exist at the protocol level.
*
* This is a convenience API to make it easier to put this struct into
* e.g. a hashtable without having to use the pointer value itself.
*
* The ID increases by an unspecified amount.
*
* @since 1.4
*/
uint64_t
ei_ping_get_id(struct ei_ping *ping);
/**
* Increase the refcount of this struct by one. Use ei_ping_unref() to decrease
* the refcount.
*
* @return the argument passed into the function
*
* @since 1.4
*/
struct ei_ping *
ei_ping_ref(struct ei_ping *ei_ping);
/**
* Decrease the refcount of this struct by one. When the refcount reaches
* zero, the context disconnects from the server and all allocated resources
* are released.
*
* @return always NULL
*
* @since 1.4
*/
struct ei_ping *
ei_ping_unref(struct ei_ping *ei_ping);
/**
* Set a custom data pointer for this struct. libei will not look at or
* modify the pointer. Use ei_ping_get_user_data() to retrieve a previously set
* user data.
*
* @since 1.4
*/
void
ei_ping_set_user_data(struct ei_ping *ei_ping, void *user_data);
/**
* Return the custom data pointer for this struct. libei will not look at or
* modify the pointer. Use ei_ping_set_user_data() to change the user data.
*
* @since 1.4
*/
void *
ei_ping_get_user_data(struct ei_ping *ei_ping);
/**
* Issue a roundtrip request to the EIS implementation, resulting in
* an @ref EI_EVENT_PONG event when this roundtrip has been processed. Client
* requests are processed in-order by the EIS implementation so this
* function can be used as synchronization point between requests.
*
* If the client is disconnected before the roundtrip is complete,
* libei will emulate a @ref EI_EVENT_PONG event before @ref
* EI_EVENT_DISCONNECT.
*
* Note that in the ei protocol, the client request is `ei_connection.sync`
* followed by `ei_callback.done`. For consistency with the
* libeis API the C API uses ei_ping() with and @ref EI_EVENT_PONG as return
* event.
*
* @since 1.4
*/
void
ei_ping(struct ei_ping *ping);
/**
* Main event dispatching function. Reads events of the file descriptors
* and processes them internally. Use ei_get_event() to retrieve the
@ -1819,6 +1927,18 @@ ei_touch_get_device(struct ei_touch *touch);
struct ei_seat *
ei_event_get_seat(struct ei_event *event);
/**
* Returns the associated @ref ei_ping struct with this event.
*
* For events of type other than @ref EI_EVENT_PONG this function
* returns NULL.
*
* This does not increase the refcount of the ei_pong. Use ei_pong_ref()
* to keep a reference beyond the immediate scope.
*/
struct ei_ping *
ei_event_pong_get_ping(struct ei_event *event);
/**
* @ingroup libei-receiver
*

View file

@ -50,6 +50,7 @@ if build_libei
'libei-keyboard.c',
'libei-log.c',
'libei-pingpong.c',
'libei-ping.c',
'libei-pointer-absolute.c',
'libei-pointer.c',
'libei-region.c',

View file

@ -1067,6 +1067,8 @@ _peck_dispatch_ei(struct peck *peck, int lineno)
if (flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_PAUSED))
process_event = tristate_yes;
break;
case EI_EVENT_PONG:
break;
case EI_EVENT_FRAME:
/* Ensure we only expect frames when we expect them */
munit_assert_true(need_frame);
@ -1381,6 +1383,7 @@ peck_ei_event_type_name(enum ei_event_type type)
CASE_STRING(DEVICE_PAUSED);
CASE_STRING(DEVICE_RESUMED);
CASE_STRING(KEYBOARD_MODIFIERS);
CASE_STRING(PONG);
CASE_STRING(FRAME);
CASE_STRING(DEVICE_START_EMULATING);
CASE_STRING(DEVICE_STOP_EMULATING);

View file

@ -359,6 +359,7 @@ DEFINE_UNREF_CLEANUP_FUNC(ei_touch);
DEFINE_UNREF_CLEANUP_FUNC(ei_keymap);
DEFINE_UNREF_CLEANUP_FUNC(ei_seat);
DEFINE_UNREF_CLEANUP_FUNC(ei_region);
DEFINE_UNREF_CLEANUP_FUNC(ei_ping);
DEFINE_UNREF_CLEANUP_FUNC(eis);
DEFINE_UNREF_CLEANUP_FUNC(eis_client);

View file

@ -797,3 +797,105 @@ MUNIT_TEST(test_ei_invalid_object_ids)
return MUNIT_OK;
}
MUNIT_TEST(test_ei_ping)
{
_unref_(peck) *peck = peck_new();
_unref_(ei_ping) *ping = NULL;
int userdata = 123;
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);
peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSTART);
peck_dispatch_until_stable(peck);
with_client(peck) {
peck_drain_ei(ei);
}
/* Create a ping object without having our own ref, object
* is kept alive until the returned pong event is destroyed */
with_client(peck) {
_unref_(ei_ping) *ping = ei_new_ping(ei);
ei_ping_set_user_data(ping, &userdata);
ei_ping(ping);
}
peck_dispatch_until_stable(peck);
with_client(peck) {
_unref_(ei_event) *e = peck_ei_next_event(ei, EI_EVENT_PONG);
struct ei_ping *pong = ei_event_pong_get_ping(e);
munit_assert_not_null(pong);
munit_assert_ptr_equal(ei_ping_get_user_data(pong), &userdata);
}
peck_dispatch_until_stable(peck);
/* Create a ping object this time keeping our own ref, object
* is kept alive until the returned pong event is destroyed */
with_client(peck) {
ping = ei_new_ping(ei);
ei_ping_set_user_data(ping, &userdata);
ei_ping(ping);
/* Keep the ref */
}
peck_dispatch_until_stable(peck);
with_client(peck) {
_unref_(ei_event) *e = peck_ei_next_event(ei, EI_EVENT_PONG);
struct ei_ping *pong = ei_event_pong_get_ping(e);
munit_assert_ptr_equal(pong, ping);
munit_assert_int64(ei_ping_get_id(pong), ==, ei_ping_get_id(ping));
munit_assert_ptr_equal(ei_ping_get_user_data(pong), &userdata);
}
/* unref after the event above, in case that blows things up */
ping = ei_ping_unref(ping);
peck_mark(peck);
/* Send two pings, one we keep the ref to, one floating, then disconnect
* immediately */
with_client(peck) {
ping = ei_new_ping(ei);
ei_ping(ping);
_unref_(ei_ping) *floating = ei_new_ping(ei);
ei_ping(floating);
}
with_server(peck) {
struct eis_client *client = peck_eis_get_default_client(peck);
eis_client_disconnect(client);
}
peck_dispatch_until_stable(peck);
with_client(peck) {
struct ei_event *e;
while ((e = ei_peek_event(ei))) {
bool found = ei_event_get_type(e) == EI_EVENT_PONG;
ei_event_unref(e);
if (found)
break;
_unref_(ei_event) *ev = ei_get_event(ei);
}
_unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_PONG);
struct ei_ping *pong = ei_event_pong_get_ping(e1);
munit_assert_ptr_equal(pong, ping);
_unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_PONG);
pong = ei_event_pong_get_ping(e2);
munit_assert_ptr_not_equal(pong, ping);
}
return MUNIT_OK;
}

View file

@ -39,6 +39,7 @@
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <poll.h>
#include <stdio.h>
#include <signal.h>
@ -286,6 +287,14 @@ print_modifiers_event(struct ei_event *event)
printf(" locked: \"0x%x\"\n", ei_event_keyboard_get_xkb_mods_locked(event));
}
static void
print_pong_event(struct ei_event *event)
{
struct ei_ping *ping = ei_event_pong_get_ping(event);
printf(" id: %#" PRIx64 "\n", ei_ping_get_id(ping));
}
int main(int argc, char **argv)
{
enum {
@ -443,6 +452,9 @@ int main(int argc, char **argv)
case EI_EVENT_KEYBOARD_MODIFIERS:
print_modifiers_event(e);
break;
case EI_EVENT_PONG:
print_pong_event(e);
break;
}
}
}