diff --git a/src/libeis-client.c b/src/libeis-client.c index 6264f58..b55f690 100644 --- a/src/libeis-client.c +++ b/src/libeis-client.c @@ -247,6 +247,7 @@ client_disconnect(struct eis_client *client, case EIS_CLIENT_STATE_CONNECTING: case EIS_CLIENT_STATE_CONNECTED: client_drop_seats(client); + eis_connection_remove_pending_callbacks(client->connection); eis_queue_disconnect_event(client); eis_connection_event_disconnected(client->connection, client->last_client_serial, diff --git a/src/libeis-connection.c b/src/libeis-connection.c index 0226856..d71402e 100644 --- a/src/libeis-connection.c +++ b/src/libeis-connection.c @@ -43,12 +43,8 @@ eis_connection_destroy(struct eis_connection *connection) struct eis_client *client = eis_connection_get_client(connection); eis_client_unregister_object(client, &connection->proto_object); - struct eis_pingpong *cb; - list_for_each_safe(cb, &connection->pending_pingpongs, link) { - list_remove(&cb->link); - eis_connection_ping_callback_unref(cb->user_data); - eis_pingpong_unref(cb); - } + /* Should be a noop */ + eis_connection_remove_pending_callbacks(connection); } OBJECT_IMPLEMENT_REF(eis_connection); @@ -108,6 +104,17 @@ eis_connection_new(struct eis_client *client) return connection; /* ref owned by caller */ } +void +eis_connection_remove_pending_callbacks(struct eis_connection *connection) +{ + struct eis_callback *cb; + list_for_each_safe(cb, &connection->pending_pingpongs, link) { + list_remove(&cb->link); + eis_connection_ping_callback_unref(cb->user_data); + eis_callback_unref(cb); + } +} + static void eis_connection_ping_callback_destroy(struct eis_connection_ping_callback *callback) { @@ -136,6 +143,13 @@ eis_connection_ping_callback_get_client(struct eis_connection_ping_callback *cal return eis_connection_get_client(connection); } +struct eis * +eis_connection_ping_callback_get_context(struct eis_connection_ping_callback *callback) +{ + struct eis_connection *connection = eis_connection_ping_callback_get_connection(callback); + return eis_connection_get_context(connection); +} + struct eis_connection_ping_callback * eis_connection_ping_callback_new(struct eis_connection *connection, eis_connection_ping_callback_done_t done, diff --git a/src/libeis-connection.h b/src/libeis-connection.h index f4223c2..b705fad 100644 --- a/src/libeis-connection.h +++ b/src/libeis-connection.h @@ -53,6 +53,8 @@ OBJECT_DECLARE_UNREF(eis_connection); struct eis_connection * eis_connection_new(struct eis_client *client); +void +eis_connection_remove_pending_callbacks(struct eis_connection *connection); /** * Called when the ei_callback.done request is received after @@ -92,6 +94,7 @@ OBJECT_DECLARE_REF(eis_connection_ping_callback); OBJECT_DECLARE_UNREF(eis_connection_ping_callback); OBJECT_DECLARE_GETTER(eis_connection_ping_callback, connection, struct eis_connection *); OBJECT_DECLARE_GETTER(eis_connection_ping_callback, client, struct eis_client *); +OBJECT_DECLARE_GETTER(eis_connection_ping_callback, context, struct eis *); OBJECT_DECLARE_GETTER(eis_connection_ping_callback, user_data, void*); DEFINE_UNREF_CLEANUP_FUNC(eis_connection_ping_callback); diff --git a/src/libeis-event.c b/src/libeis-event.c index 4878547..c59e0c4 100644 --- a/src/libeis-event.c +++ b/src/libeis-event.c @@ -58,6 +58,10 @@ eis_event_destroy(struct eis_event *event) case EIS_EVENT_FRAME: handled = true; break; + case EIS_EVENT_PONG: + eis_ping_unref(event->pong.ping); + handled = true; + break; } if (!handled) @@ -188,6 +192,14 @@ eis_event_get_time(struct eis_event *event) return event->timestamp; } +_public_ struct eis_ping * +eis_event_pong_get_ping(struct eis_event *event) +{ + require_event_type(event, NULL, EIS_EVENT_PONG); + + return event->pong.ping; +} + _public_ bool eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap) { diff --git a/src/libeis-event.h b/src/libeis-event.h index 36bd423..2856b1d 100644 --- a/src/libeis-event.h +++ b/src/libeis-event.h @@ -63,6 +63,9 @@ struct eis_event { struct { uint32_t sequence; } start_emulating; + struct { + struct eis_ping *ping; + } pong; }; }; diff --git a/src/libeis-ping.c b/src/libeis-ping.c new file mode 100644 index 0000000..3163f1c --- /dev/null +++ b/src/libeis-ping.c @@ -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 + +#include "util-mem.h" +#include "util-macros.h" +#include "util-object.h" +#include "util-list.h" +#include "brei-shared.h" + +#include "libeis-private.h" +#include "libeis-connection.h" + +struct eis_ping { + struct object object; + uint64_t id; + void *user_data; + + struct eis_client *client; + bool is_pending; + bool is_done; +}; + +static void +eis_ping_destroy(struct eis_ping *ping) +{ + if (!ping->is_pending) + eis_client_unref(ping->client); +} + +static +OBJECT_IMPLEMENT_CREATE(eis_ping); + +_public_ +OBJECT_IMPLEMENT_REF(eis_ping); +_public_ +OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_ping); +_public_ +OBJECT_IMPLEMENT_GETTER(eis_ping, id, uint64_t); +_public_ +OBJECT_IMPLEMENT_GETTER(eis_ping, user_data, void*); +_public_ +OBJECT_IMPLEMENT_SETTER(eis_ping, user_data, void*); +static +OBJECT_IMPLEMENT_GETTER(eis_ping, client, struct eis_client*); + +_public_ struct eis_ping * +eis_client_new_ping(struct eis_client *client) +{ + static uint64_t id = 0; + + struct eis_ping *ping = eis_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->client = eis_client_ref(client); + ping->is_pending = false; + ping->is_done = false; + + return ping; +} + +static void +on_pong(struct eis_connection_ping_callback *callback) +{ + struct eis_ping *ping = eis_connection_ping_callback_get_user_data(callback); + ping->is_done = true; + + struct eis_client *client = eis_connection_ping_callback_get_client(callback); + eis_queue_pong_event(client, ping); + /* eis_ping ref is removed in on_destroy */ +} + +static void +on_destroy(struct eis_connection_ping_callback *callback) +{ + /* This is only called if we never receisved a pong */ + _unref_(eis_ping) *ping = eis_connection_ping_callback_get_user_data(callback); + + /* We never got a pong because we got disconnected. Queue a fake pong event */ + if (!ping->is_done) { + struct eis_client *client = eis_connection_ping_callback_get_client(callback); + eis_queue_pong_event(client, ping); + } +} + +_public_ void +eis_ping(struct eis_ping *ping) +{ + struct eis_client *client = eis_ping_get_client(ping); + + eis_client_unref(client); + ping->client = client; + ping->is_pending = true; + + _unref_(eis_connection_ping_callback) *cb = eis_connection_ping_callback_new(client->connection, + on_pong, + on_destroy, + eis_ping_ref(ping)); + eis_connection_ping(client->connection, cb); +} diff --git a/src/libeis-private.h b/src/libeis-private.h index 7b4288e..57f390a 100644 --- a/src/libeis-private.h +++ b/src/libeis-private.h @@ -92,6 +92,9 @@ eis_queue_connect_event(struct eis_client *client); void eis_queue_disconnect_event(struct eis_client *client); +void +eis_queue_pong_event(struct eis_client *client, struct eis_ping *ping); + void eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities); diff --git a/src/libeis.c b/src/libeis.c index 9761174..284c7df 100644 --- a/src/libeis.c +++ b/src/libeis.c @@ -118,6 +118,7 @@ eis_event_type_to_string(enum eis_event_type type) CASE_RETURN_STRING(EIS_EVENT_CLIENT_DISCONNECT); CASE_RETURN_STRING(EIS_EVENT_SEAT_BIND); CASE_RETURN_STRING(EIS_EVENT_DEVICE_CLOSED); + CASE_RETURN_STRING(EIS_EVENT_PONG); CASE_RETURN_STRING(EIS_EVENT_DEVICE_START_EMULATING); CASE_RETURN_STRING(EIS_EVENT_DEVICE_STOP_EMULATING); CASE_RETURN_STRING(EIS_EVENT_POINTER_MOTION); @@ -251,6 +252,15 @@ eis_queue_device_closed_event(struct eis_device *device) eis_queue_event(e); } +void +eis_queue_pong_event(struct eis_client *client, struct eis_ping *ping) +{ + struct eis_event *e = eis_event_new_for_client(client); + e->type = EIS_EVENT_PONG; + e->pong.ping = eis_ping_ref(ping); + eis_queue_event(e); +} + void eis_queue_frame_event(struct eis_device *device, uint64_t time) { diff --git a/src/libeis.h b/src/libeis.h index 093a52e..88fcc0a 100644 --- a/src/libeis.h +++ b/src/libeis.h @@ -147,6 +147,16 @@ struct eis_keymap; */ struct eis_touch; +/** + * @struct eis_ping + * + * A callback struct returned by eis_new_ping() to handle + * roundtrips to the client. + * + * @see eis_new_ping + */ +struct eis_ping; + /** * @struct eis_region * @ingroup libeis-region @@ -261,6 +271,11 @@ enum eis_event_type { */ EIS_EVENT_DEVICE_CLOSED, + /** + * Returned in response to eis_ping(). + */ + EIS_EVENT_PONG = 90, + /** * "Hardware" frame event. This event **must** be sent by the client * and notifies the server that the previous set of events belong to @@ -527,6 +542,78 @@ eis_get_fd(struct eis *eis); void eis_dispatch(struct eis *eis); +/** + * 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 +eis_ping_get_id(struct eis_ping *ping); + +/** + * Increase the refcount of this struct by one. Use eis_ping_unref() to decrease + * the refcount. + * + * @return the argument passed into the function + * + * @since 1.4 + */ +struct eis_ping * +eis_ping_ref(struct eis_ping *eis_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 eis_ping * +eis_ping_unref(struct eis_ping *eis_ping); + +/** + * Set a custom data pointer for this struct. libeis will not look at or + * modify the pointer. Use eis_ping_get_user_data() to retrieve a previously set + * user data. + * + * @since 1.4 + */ +void +eis_ping_set_user_data(struct eis_ping *eis_ping, void *user_data); + +/** + * Return the custom data pointer for this struct. libeis will not look at or + * modify the pointer. Use eis_ping_set_user_data() to change the user data. + * + * @since 1.4 + */ +void * +eis_ping_get_user_data(struct eis_ping *eis_ping); + +/** + * Issue a roundtrip request to the client, resulting in + * an @ref EIS_EVENT_PONG event when this roundtrip has been processed. Events + * are processed in-order by the client implementation so this + * function can be used as synchronization point between events. + * + * If the client is disconnected before the roundtrip is complete, + * libeis will emulate a @ref EIS_EVENT_PONG event before @ref + * EIS_EVENT_DISCONNECT. + * + * @since 1.4 + */ +void +eis_ping(struct eis_ping *ping); + /** * Returns the next event in the internal event queue (or NULL) and removes * it from the queue. @@ -580,6 +667,18 @@ eis_event_get_client(struct eis_event *event); struct eis_seat * eis_event_get_seat(struct eis_event *event); +/** + * Returns the associated @ref eis_ping struct with this event. + * + * For events of type other than @ref EIS_EVENT_PONG this function + * returns NULL. + * + * This does not increase the refcount of the eis_pong. Use eis_pong_ref() + * to ekeep a reference beyond the immediate scope. + */ +struct eis_ping * +eis_event_pong_get_ping(struct eis_event *event); + /** * For an event of type @ref EIS_EVENT_SEAT_BIND, return the capabilities * requested by the client. @@ -611,7 +710,17 @@ eis_event_get_device(struct eis_event *event); uint64_t eis_event_get_time(struct eis_event *event); - +/** + * Create a new eis_ping object to trigger a round trip to the client. + * See eis_ping() for details. + * + * The returned @ref eis_ping is refcounted, use eis_ping_unref() to + * drop the reference. + * + * @since 1.4 + */ +struct eis_ping * +eis_client_new_ping(struct eis_client *client); struct eis_client * eis_client_ref(struct eis_client *client); diff --git a/src/meson.build b/src/meson.build index 4881a29..3e97c11 100644 --- a/src/meson.build +++ b/src/meson.build @@ -114,6 +114,7 @@ if build_libeis 'libeis-keyboard.c', 'libeis-log.c', 'libeis-pingpong.c', + 'libeis-ping.c', 'libeis-pointer-absolute.c', 'libeis-pointer.c', 'libeis-region.c', diff --git a/test/eierpecken.c b/test/eierpecken.c index 67302dc..02df5f5 100644 --- a/test/eierpecken.c +++ b/test/eierpecken.c @@ -909,6 +909,8 @@ _peck_dispatch_eis(struct peck *peck, int lineno) else process_event = tristate_no; break; + case EIS_EVENT_PONG: + break; case EIS_EVENT_FRAME: /* Ensure we only expect frames when we expect them */ munit_assert_true(need_frame); @@ -1420,6 +1422,7 @@ peck_eis_event_type_name(enum eis_event_type type) CASE_STRING(CLIENT_DISCONNECT); CASE_STRING(SEAT_BIND); CASE_STRING(DEVICE_CLOSED); + CASE_STRING(PONG); CASE_STRING(DEVICE_START_EMULATING); CASE_STRING(DEVICE_STOP_EMULATING); CASE_STRING(POINTER_MOTION); diff --git a/test/eierpecken.h b/test/eierpecken.h index 60c0aeb..74c09c8 100644 --- a/test/eierpecken.h +++ b/test/eierpecken.h @@ -369,6 +369,7 @@ DEFINE_UNREF_CLEANUP_FUNC(eis_touch); DEFINE_UNREF_CLEANUP_FUNC(eis_keymap); DEFINE_UNREF_CLEANUP_FUNC(eis_seat); DEFINE_UNREF_CLEANUP_FUNC(eis_region); +DEFINE_UNREF_CLEANUP_FUNC(eis_ping); /* Macros intended just for readability to make it more obvious which part of a test handles server vs client */ diff --git a/test/test-eis.c b/test/test-eis.c index 87dcb5c..ca763e1 100644 --- a/test/test-eis.c +++ b/test/test-eis.c @@ -352,3 +352,105 @@ MUNIT_TEST(eistest_regions) return MUNIT_OK; } + +MUNIT_TEST(test_eis_ping) +{ + _unref_(peck) *peck = peck_new(); + _unref_(eis_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_server(peck) { + peck_drain_eis(eis); + } + + /* Create a ping object without having our own ref, object + * is kept alive until the returned pong event is destroyed */ + with_server(peck) { + struct eis_client *client = peck_eis_get_default_client(peck); + _unref_(eis_ping) *ping = eis_client_new_ping(client); + eis_ping_set_user_data(ping, &userdata); + eis_ping(ping); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *e = peck_eis_next_event(eis, EIS_EVENT_PONG); + struct eis_ping *pong = eis_event_pong_get_ping(e); + munit_assert_not_null(pong); + munit_assert_ptr_equal(eis_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_server(peck) { + struct eis_client *client = peck_eis_get_default_client(peck); + ping = eis_client_new_ping(client); + eis_ping_set_user_data(ping, &userdata); + eis_ping(ping); + /* Keep the ref */ + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *e = peck_eis_next_event(eis, EIS_EVENT_PONG); + struct eis_ping *pong = eis_event_pong_get_ping(e); + munit_assert_ptr_equal(pong, ping); + munit_assert_int64(eis_ping_get_id(pong), ==, eis_ping_get_id(ping)); + munit_assert_ptr_equal(eis_ping_get_user_data(pong), &userdata); + } + + /* unref after the event above, in case that blows things up */ + ping = eis_ping_unref(ping); + + peck_mark(peck); + + /* Send two pings, one we keep the ref to, one floating, then disconnect + * immediately */ + with_server(peck) { + struct eis_client *client = peck_eis_get_default_client(peck); + ping = eis_client_new_ping(client); + eis_ping(ping); + + _unref_(eis_ping) *floating = eis_client_new_ping(client); + eis_ping(floating); + + eis_client_disconnect(client); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + struct eis_event *e; + while ((e = eis_peek_event(eis))) { + bool found = eis_event_get_type(e) == EIS_EVENT_PONG; + eis_event_unref(e); + + if (found) + break; + _unref_(eis_event) *ev = eis_get_event(eis); + } + + _unref_(eis_event) *e1 = peck_eis_next_event(eis, EIS_EVENT_PONG); + struct eis_ping *pong = eis_event_pong_get_ping(e1); + munit_assert_ptr_equal(pong, ping); + + _unref_(eis_event) *e2 = peck_eis_next_event(eis, EIS_EVENT_PONG); + pong = eis_event_pong_get_ping(e2); + munit_assert_ptr_not_equal(pong, ping); + } + + return MUNIT_OK; +} diff --git a/tools/eis-demo-server.c b/tools/eis-demo-server.c index 2293894..77fd80d 100644 --- a/tools/eis-demo-server.c +++ b/tools/eis-demo-server.c @@ -546,6 +546,11 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server, eis_event_get_time(e)); } break; + case EIS_EVENT_PONG: + { + colorprint("pong\n"); + } + break; default: /* This is a demo server and we abort to make it easy to debug * a missing implementation of some new event.