From b1c1c5d579dc6349c976287381921fbe93d54cec Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 26 Nov 2024 13:05:49 +1000 Subject: [PATCH] Add the ei_touchscreen.cancel event and ei_touch_cancel() In the protocol it's a new request/event that is sent instead of the touch up event. In the library this is implemented as ei_touch_cancel() which transparently sends cancel() or up depending on the EIS implementation support. This is mirrored for the EIS implementation. Where touch cancel is received as an event it is presented as EI_EVENT_TOUCH_UP with an ei_event_touch_is_cancel() flag to check if it was a cancel. This is required for backwards compatbility, we cannot replace the TOUCH_UP event with a TOUCH_CANCEL event without breaking existing callers. To add a new event type we would need clients announcing support for those event types but that's an effort that's better postponed until we have a stronger need for it (see #68). Closes #60 Part-of: --- proto/protocol.xml | 51 +++++++++++ src/libei-device.c | 67 ++++++++++++++ src/libei-event.c | 8 ++ src/libei-event.h | 1 + src/libei-private.h | 3 + src/libei.c | 15 +++- src/libei.h | 27 ++++++ src/libeis-client.c | 2 +- src/libeis-device.c | 48 ++++++++++ src/libeis-event.c | 8 ++ src/libeis-event.h | 1 + src/libeis-private.h | 3 + src/libeis.c | 11 +++ src/libeis.h | 21 +++++ test/test-ei-device.c | 197 ++++++++++++++++++++++++++++++++++++++++++ test/test_protocol.py | 100 +++++++++++++++++++++ 16 files changed, 561 insertions(+), 2 deletions(-) diff --git a/proto/protocol.xml b/proto/protocol.xml index a2e465c..bcae78d 100644 --- a/proto/protocol.xml +++ b/proto/protocol.xml @@ -1449,6 +1449,10 @@ up. The touchid is the unique id for this touch previously sent with ei_touchscreen.down. + If a touch is cancelled via ei_touchscreen.cancel, the ei_touchscreen.up + request must not be sent for this same touch. Likewise, a touch released + with ei_touchscreen.up must not be cancelled. + The touchid may be re-used after this request. It is a protocol violation to send a touch up in the same @@ -1457,6 +1461,30 @@ + + + + + Notifies the EIS implementation about an existing touch being cancelled. + This typically means that any effects the touch may have had on the + user interface should be reverted or otherwise made inconsequential. + + This request replaces ei_touchscreen.up for the same touch. + If a touch is cancelled via ei_touchscreen.cancel, the ei_touchscreen.up + request must not be sent for this same touch. Likewise, a touch released + with ei_touchscreen.up must not be cancelled. + + The touchid is the unique id for this touch previously + sent with ei_touchscreen.down. + + The touchid may be re-used after this request. + + It is a protocol violation to send a touch cancel + in the same frame as a touch motion or touch down. + + + + @@ -1508,10 +1536,33 @@ It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. + If a touch is released via ei_touchscreen.up, no ei_touchscreen.cancel + event is sent for this same touch. Likewise, a touch released + with ei_touchscreen.cancel must not be released via ei_touchscreen.up. + It is a protocol violation to send a touch up in the same frame as a touch motion or touch down. + + + + + + See the ei_touchscreen.cancel request for details. + + It is a protocol violation to send this event for a client + of an ei_handshake.context_type other than receiver. + + If a touch is cancelled via ei_touchscreen.cancel, no ei_touchscreen.up + event is sent for this same touch. Likewise, a touch released + with ei_touchscreen.up must not be cancelled via ei_touchscreen.cancel. + + It is a protocol violation to send a touch cancel event in the same + frame as a touch motion or touch down. + + + diff --git a/src/libei-device.c b/src/libei-device.c index 98a701b..49534bc 100644 --- a/src/libei-device.c +++ b/src/libei-device.c @@ -858,6 +858,31 @@ handle_msg_touch_up(struct ei_touchscreen *touchscreen, uint32_t touchid) return maybe_error_on_device_state(device, "touch up"); } +static struct brei_result * +handle_msg_touch_cancel(struct ei_touchscreen *touchscreen, uint32_t touchid) +{ + struct ei_device *device = ei_touchscreen_get_device(touchscreen); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Touch cancel event for non-touch device"); + } + + struct ei *ei = ei_device_get_context(device); + if (ei->interface_versions.ei_touchscreen < EI_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Touch cancel event for touchscreen version v1"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + ei_queue_touch_cancel_event(device, touchid); + return NULL; + } + return maybe_error_on_device_state(device, "touch cancel"); +} + static struct brei_result * handle_msg_touchscreen_destroy(struct ei_touchscreen *touchscreen, uint32_t serial) { @@ -875,6 +900,7 @@ static const struct ei_touchscreen_interface touchscreen_interface = { .down = handle_msg_touch_down, .motion = handle_msg_touch_motion, .up = handle_msg_touch_up, + .cancel = handle_msg_touch_cancel, }; const struct ei_touchscreen_interface * @@ -1648,6 +1674,21 @@ ei_send_touch_up(struct ei_device *device, uint32_t tid) return rc; } +static int +ei_send_touch_cancel(struct ei_device *device, uint32_t tid) +{ + struct ei *ei = ei_device_get_context(device); + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return 0; + + device->send_frame_event = true; + + int rc = ei_touchscreen_request_cancel(device->touchscreen, tid); + if (rc) + ei_disconnect(ei); + return rc; +} _public_ struct ei_touch * ei_device_touch_new(struct ei_device *device) @@ -1741,6 +1782,32 @@ ei_touch_up(struct ei_touch *touch) ei_send_touch_up(touch->device, touch->tracking_id); } +_public_ void +ei_touch_cancel(struct ei_touch *touch) +{ + struct ei_device *device = ei_touch_get_device(touch); + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", __func__); + return; + } + + if (touch->state != TOUCH_IS_DOWN) { + log_bug_client(ei_device_get_context(device), + "%s: touch %u is not currently down", __func__, touch->tracking_id); + return; + } + + touch->state = TOUCH_IS_UP; + + struct ei *ei = ei_device_get_context(device); + + if (ei->interface_versions.ei_touchscreen >= EI_TOUCHSCREEN_REQUEST_CANCEL_SINCE_VERSION) + ei_send_touch_cancel(touch->device, touch->tracking_id); + else + ei_send_touch_up(touch->device, touch->tracking_id); +} + _public_ void ei_device_frame(struct ei_device *device, uint64_t time) { diff --git a/src/libei-event.c b/src/libei-event.c index 4f56a26..deaaa05 100644 --- a/src/libei-event.c +++ b/src/libei-event.c @@ -347,6 +347,14 @@ ei_event_touch_get_y(struct ei_event *event) return event->touch.y; } +_public_ bool +ei_event_touch_get_is_cancel(struct ei_event *event) +{ + require_event_type(event,false, EI_EVENT_TOUCH_UP); + + return event->touch.is_cancel; +} + _public_ uint64_t ei_event_get_time(struct ei_event *event) { diff --git a/src/libei-event.h b/src/libei-event.h index fecefe6..006b6bf 100644 --- a/src/libei-event.h +++ b/src/libei-event.h @@ -64,6 +64,7 @@ struct ei_event { struct { uint32_t touchid; double x, y; + bool is_cancel; } touch; struct { uint32_t sequence; diff --git a/src/libei-private.h b/src/libei-private.h index 02791a8..ae8e347 100644 --- a/src/libei-private.h +++ b/src/libei-private.h @@ -234,6 +234,9 @@ ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid, void ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid); +void +ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid); + _printf_(6, 7) void ei_log_msg(struct ei *ei, diff --git a/src/libei.c b/src/libei.c index edccc09..60d4969 100644 --- a/src/libei.c +++ b/src/libei.c @@ -133,7 +133,7 @@ ei_create_context(bool is_sender, void *user_data) .ei_scroll = VERSION_V(1), .ei_button = VERSION_V(1), .ei_keyboard = VERSION_V(1), - .ei_touchscreen = VERSION_V(1), + .ei_touchscreen = VERSION_V(2), }; /* This must be v1 until the server tells us otherwise */ ei->handshake = ei_handshake_new(ei, VERSION_V(1)); @@ -576,6 +576,19 @@ ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid) e->type = EI_EVENT_TOUCH_UP; e->touch.touchid = touchid, + e->touch.is_cancel = false; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_TOUCH_UP; + e->touch.touchid = touchid, + e->touch.is_cancel = true; queue_event(ei_device_get_context(device), e); } diff --git a/src/libei.h b/src/libei.h index ba1c451..beb96a6 100644 --- a/src/libei.h +++ b/src/libei.h @@ -1735,6 +1735,20 @@ ei_touch_motion(struct ei_touch *touch, double x, double y); void ei_touch_up(struct ei_touch *touch); +/** + * @ingroup libei-sender + * + * Cancel this touch. After this call, the touch event becomes inert and + * no longer responds to either ei_touch_down(), ei_touch_motion() or + * ei_touch_up() and the caller should call ei_touch_unref(). + * + * This is only available if the EIS implementation supports version 2 + * or later of the ei_touchscreen protocol interface. Otherwise, + * this function is equivalent to calling ei_touch_up(). + */ +void +ei_touch_cancel(struct ei_touch *touch); + /** * @ingroup libei-sender * @@ -2010,6 +2024,19 @@ ei_event_touch_get_x(struct ei_event *event); double ei_event_touch_get_y(struct ei_event *event); +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_TOUCH_UP + * return true if the event was cancelled instead of + * logically released. + * + * Support for touch cancellation requires the EIS implementation and client to + * support version 2 or later of the ei_touchscreen protocol interface. + */ +bool +ei_event_touch_get_is_cancel(struct ei_event *event); + /** * @} */ diff --git a/src/libeis-client.c b/src/libeis-client.c index 9343f2d..6264f58 100644 --- a/src/libeis-client.c +++ b/src/libeis-client.c @@ -450,7 +450,7 @@ eis_client_new(struct eis *eis, int fd) .ei_scroll = VERSION_V(1), .ei_button = VERSION_V(1), .ei_keyboard = VERSION_V(1), - .ei_touchscreen = VERSION_V(1), + .ei_touchscreen = VERSION_V(2), }; struct source *s = source_new(fd, client_dispatch, client); int rc = sink_add_source(eis->sink, s); diff --git a/src/libeis-device.c b/src/libeis-device.c index 70209a2..2c3b97a 100644 --- a/src/libeis-device.c +++ b/src/libeis-device.c @@ -673,6 +673,32 @@ client_msg_touch_up(struct eis_touchscreen *touchscreen, uint32_t touchid) return maybe_error_on_device_state(device, "touch up"); } +static struct brei_result * +client_msg_touch_cancel(struct eis_touchscreen *touchscreen, uint32_t touchid) +{ + struct eis_device *device = eis_touchscreen_get_device(touchscreen); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Touch cancel event for non-touch device"); + } + + struct eis_client *client = eis_device_get_client(device); + if (client->interface_versions.ei_touchscreen < EIS_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Touch cancel event for touchscreen version v1"); + } + + if (device->state == EIS_DEVICE_STATE_EMULATING) { + eis_queue_touch_cancel_event(device, touchid); + return NULL; + } + + return maybe_error_on_device_state(device, "touch cancel"); +} + static struct brei_result * client_msg_touchscreen_release(struct eis_touchscreen *touchscreen) { @@ -688,6 +714,7 @@ static const struct eis_touchscreen_interface touchscreen_interface = { .down = client_msg_touch_down, .motion = client_msg_touch_motion, .up = client_msg_touch_up, + .cancel = client_msg_touch_cancel, }; const struct eis_touchscreen_interface * @@ -1317,6 +1344,27 @@ eis_touch_up(struct eis_touch *touch) eis_touchscreen_event_up(device->touchscreen, touch->tracking_id); } +_public_ void +eis_touch_cancel(struct eis_touch *touch) +{ + struct eis_device *device = eis_touch_get_device(touch); + + if (touch->state != TOUCH_IS_DOWN) { + log_bug_client(eis_device_get_context(device), + "%s: touch %u is not currently down", __func__, touch->tracking_id); + return; + } + + touch->state = TOUCH_IS_UP; + device->send_frame_event = true; + + struct eis_client *client = eis_device_get_client(device); + if (client->interface_versions.ei_touchscreen >= EIS_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) + eis_touchscreen_event_cancel(device->touchscreen, touch->tracking_id); + else + eis_touchscreen_event_up(device->touchscreen, touch->tracking_id); +} + _public_ void eis_device_frame(struct eis_device *device, uint64_t time) { diff --git a/src/libeis-event.c b/src/libeis-event.c index 382a2c9..4878547 100644 --- a/src/libeis-event.c +++ b/src/libeis-event.c @@ -405,3 +405,11 @@ eis_event_touch_get_y(struct eis_event *event) return event->touch.y; } + +_public_ bool +eis_event_touch_get_is_cancel(struct eis_event *event) +{ + require_event_type(event, false, EIS_EVENT_TOUCH_UP); + + return event->touch.is_cancel; +} diff --git a/src/libeis-event.h b/src/libeis-event.h index 40cc2b0..36bd423 100644 --- a/src/libeis-event.h +++ b/src/libeis-event.h @@ -58,6 +58,7 @@ struct eis_event { struct { uint32_t touchid; double x, y; + bool is_cancel; } touch; struct { uint32_t sequence; diff --git a/src/libeis-private.h b/src/libeis-private.h index b873633..7b4288e 100644 --- a/src/libeis-private.h +++ b/src/libeis-private.h @@ -146,6 +146,9 @@ eis_queue_touch_motion_event(struct eis_device *device, uint32_t touchid, void eis_queue_touch_up_event(struct eis_device *device, uint32_t touchid); +void +eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid); + _printf_(6, 7) void eis_log_msg(struct eis *eis, enum eis_log_priority priority, diff --git a/src/libeis.c b/src/libeis.c index 315d820..9761174 100644 --- a/src/libeis.c +++ b/src/libeis.c @@ -389,10 +389,21 @@ eis_queue_touch_motion_event(struct eis_device *device, uint32_t touchid, void eis_queue_touch_up_event(struct eis_device *device, uint32_t touchid) +{ + struct eis_event *e = eis_event_new_for_device(device); + e->type = EIS_EVENT_TOUCH_UP; + e->touch.touchid = touchid; + e->touch.is_cancel = false; + eis_queue_event(e); +} + +void +eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_TOUCH_UP; e->touch.touchid = touchid, + e->touch.is_cancel = true; eis_queue_event(e); } diff --git a/src/libeis.h b/src/libeis.h index 0527585..341795a 100644 --- a/src/libeis.h +++ b/src/libeis.h @@ -1400,6 +1400,14 @@ eis_touch_motion(struct eis_touch *touch, double x, double y); void eis_touch_up(struct eis_touch *touch); +/** + * @ingroup libeis-receiver + * + * see @ref ei_touch_cancel + */ +void +eis_touch_cancel(struct eis_touch *touch); + /** * @ingroup libeis-receiver * @@ -1618,6 +1626,19 @@ eis_event_touch_get_x(struct eis_event *event); double eis_event_touch_get_y(struct eis_event *event); +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_TOUCH_UP + * return true if the touch was cancelled instead + * of logically released. + * + * Support for touch cancellation requires the EIS implementation and client to + * support version 2 or later of the ei_touchscreen protocol interface. + */ +bool +eis_event_touch_get_is_cancel(struct eis_event *event); + /** * @returns a timestamp for the current time to pass into * eis_device_frame(). diff --git a/test/test-ei-device.c b/test/test-ei-device.c index d623737..f9b155b 100644 --- a/test/test-ei-device.c +++ b/test/test-ei-device.c @@ -1355,6 +1355,95 @@ MUNIT_TEST(test_ei_device_touch) return MUNIT_OK; } +MUNIT_TEST(test_ei_device_touch_cancel) +{ + _unref_(peck) *peck = peck_new(); + struct ei_device *device = NULL; + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + with_client(peck) { + device = peck_ei_get_default_touch(peck); + /* We know our default device has one region */ + _unref_(ei_touch) *t = ei_device_touch_new(device); + ei_touch_down(t, 1, 2); + ei_device_frame(device, peck_ei_now(peck)); + ei_touch_motion(t, 200, 500); + ei_device_frame(device, peck_ei_now(peck)); + ei_touch_cancel(t); + ei_device_frame(device, peck_ei_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *down = peck_eis_touch_down(eis, 1, 2); + uint32_t tid = eis_event_touch_get_id(down); + + _unref_(eis_event) *motion = peck_eis_touch_motion(eis, 200, 500); + munit_assert_uint32(eis_event_touch_get_id(motion), ==, tid); + + _unref_(eis_event) *up = peck_eis_touch_up(eis); + munit_assert_uint32(eis_event_touch_get_id(up), ==, tid); + munit_assert_true(eis_event_touch_get_is_cancel(up)); + + peck_assert_no_eis_events(eis); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + /* up + cancel, latter is ignored */ + _unref_(ei_touch) *t = ei_device_touch_new(device); + ei_touch_down(t, 100, 200); + ei_device_frame(device, peck_ei_now(peck)); + ei_touch_motion(t, 300, 400); + ei_device_frame(device, peck_ei_now(peck)); + ei_touch_up(t); + with_nonfatal_ei_bug(peck) + ei_touch_cancel(t); /* ignored */ + ei_device_frame(device, peck_ei_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200); + _unref_(eis_event) *motion = peck_eis_touch_motion(eis, 300, 400); + _unref_(eis_event) *up = peck_eis_touch_up(eis); + munit_assert_false(eis_event_touch_get_is_cancel(up)); + peck_assert_no_eis_events(eis); + } + + with_client(peck) { + /* cancel + up, latter is ignored */ + _unref_(ei_touch) *t = ei_device_touch_new(device); + ei_touch_down(t, 100, 200); + ei_device_frame(device, peck_ei_now(peck)); + ei_touch_motion(t, 300, 400); + ei_device_frame(device, peck_ei_now(peck)); + ei_touch_cancel(t); + with_nonfatal_ei_bug(peck) + ei_touch_up(t); /* ignored */ + ei_device_frame(device, peck_ei_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200); + _unref_(eis_event) *motion = peck_eis_touch_motion(eis, 300, 400); + _unref_(eis_event) *up = peck_eis_touch_up(eis); + munit_assert_true(eis_event_touch_get_is_cancel(up)); + peck_assert_no_eis_events(eis); + } + + return MUNIT_OK; +} + MUNIT_TEST(test_ei_device_multitouch) { _unref_(peck) *peck = peck_new(); @@ -2611,6 +2700,114 @@ MUNIT_TEST(test_passive_ei_device_touch) return MUNIT_OK; } +/* same as test_ei_device_touch_cancel but for a passive context */ +MUNIT_TEST(test_passive_ei_device_touch_cancel) +{ + _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); + struct eis_device *device = NULL; + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + uint32_t sequence = 123; + + with_server(peck) { + device = peck_eis_get_default_touch(peck); + eis_device_start_emulating(device, sequence); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + _unref_(ei_event) *start = + peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); + munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); + } + + with_server(peck) { + device = peck_eis_get_default_touch(peck); + + _unref_(eis_touch) *t = eis_device_touch_new(device); + eis_touch_down(t, 1, 2); + eis_device_frame(device, peck_eis_now(peck)); + eis_touch_motion(t, 200, 500); + eis_device_frame(device, peck_eis_now(peck)); + eis_touch_cancel(t); + eis_device_frame(device, peck_eis_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + _unref_(ei_event) *down = peck_ei_touch_down(ei, 1, 2); + uint32_t tid = ei_event_touch_get_id(down); + + _unref_(ei_event) *motion = peck_ei_touch_motion(ei, 200, 500); + munit_assert_uint32(ei_event_touch_get_id(motion), ==, tid); + + _unref_(ei_event) *up = peck_ei_touch_up(ei); + munit_assert_uint32(ei_event_touch_get_id(up), ==, tid); + munit_assert_true(ei_event_touch_get_is_cancel(up)); + peck_assert_no_ei_events(ei); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + /* up + cancel, latter is ignored */ + _unref_(eis_touch) *t = eis_device_touch_new(device); + eis_touch_down(t, 100, 200); + eis_device_frame(device, peck_eis_now(peck)); + eis_device_frame(device, peck_eis_now(peck)); + eis_touch_motion(t, 300, 400); + eis_device_frame(device, peck_eis_now(peck)); + eis_touch_up(t); + eis_device_frame(device, peck_eis_now(peck)); + with_nonfatal_eis_bug(peck) + eis_touch_cancel(t); /* ignored */ + eis_device_frame(device, peck_eis_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + _unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200); + _unref_(ei_event) *motion = peck_ei_touch_motion(ei, 300, 400); + _unref_(ei_event) *up = peck_ei_touch_up(ei); + munit_assert_false(ei_event_touch_get_is_cancel(up)); + peck_assert_no_ei_events(ei); + } + + with_server(peck) { + /* cancel + up, latter is ignored */ + _unref_(eis_touch) *t = eis_device_touch_new(device); + eis_touch_down(t, 100, 200); + eis_device_frame(device, peck_eis_now(peck)); + eis_device_frame(device, peck_eis_now(peck)); + eis_touch_motion(t, 300, 400); + eis_device_frame(device, peck_eis_now(peck)); + eis_touch_cancel(t); + eis_device_frame(device, peck_eis_now(peck)); + with_nonfatal_eis_bug(peck) + eis_touch_up(t); /* ignored */ + eis_device_frame(device, peck_eis_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + _unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200); + _unref_(ei_event) *motion = peck_ei_touch_motion(ei, 300, 400); + _unref_(ei_event) *up = peck_ei_touch_up(ei); + munit_assert_true(ei_event_touch_get_is_cancel(up)); + peck_assert_no_ei_events(ei); + } + + return MUNIT_OK; +} + /* same as test_ei_device_multitouch but for a passive context */ MUNIT_TEST(test_passive_ei_device_multitouch) { diff --git a/test/test_protocol.py b/test/test_protocol.py index 71f3598..2d32396 100644 --- a/test/test_protocol.py +++ b/test/test_protocol.py @@ -44,8 +44,10 @@ try: MessageHeader, EiCallback, EiConnection, + EiDevice, EiHandshake, EiSeat, + EiTouchscreen, ) except ModuleNotFoundError as e: # This file needs to be processed by meson, so let's skip when this fails in the source dir @@ -1141,3 +1143,101 @@ class TestEiProtocol: assert status.pointers[wanted_pointer] is not None assert len(status.pointers) == 1 + + @pytest.mark.parametrize("ei_touchscreen_version", (1, 2)) + def test_touch_cancel_check_version(self, eis, ei_touchscreen_version): + """ + Ensure EIS disconnects us (or not) if we send a touch cancel event, + depending whether it's supported. + """ + + ei = eis.ei + + @dataclass + class Status: + device: EiDevice = None + touchscreen: Optional[EiTouchscreen] = None + disconnected: bool = False + resumed: bool = False + serial: int = 0 + + status = Status() + + def on_interface(device, object, name, version, new_objects): + logger.debug( + "new capability", + device=device, + object=object, + name=name, + version=version, + ) + if name == InterfaceName.EI_TOUCHSCREEN: + assert status.touchscreen is None + status.touchscreen = new_objects["object"] + + def on_device_resumed(device, serial): + status.resumed = True + status.serial = serial + + def on_new_device(seat, device, version, new_objects): + logger.debug("new device", object=new_objects["device"]) + status.device = new_objects["device"] + status.device.connect("Interface", on_interface) + status.device.connect("Resumed", on_device_resumed) + + def on_new_object(o: Interface): + logger.debug("new object", object=o) + if o.name == InterfaceName.EI_SEAT: + ei.seat_fill_capability_masks(o) + o.connect("Device", on_new_device) + + ei.context.connect("register", on_new_object) + ei.dispatch() + + def on_disconnected(connection, last_serial, reason, explanation): + status.disconnected = True + + def on_connection(setup, serial, id, version, new_objects={}): + connection = new_objects["connection"] + connection.connect("Disconnected", on_disconnected) + + setup = ei.handshake + setup.connect("Connection", on_connection) + ei.init_default_sender_connection( + interface_versions={"ei_touchscreen": ei_touchscreen_version} + ) + + assert ei.interface_versions[InterfaceName.EI_TOUCHSCREEN] == VERSION_V( + ei_touchscreen_version + ) + + ei.wait_for_seat() + seat = ei.seats[0] + ei.send(seat.Bind(seat.bind_mask([InterfaceName.EI_TOUCHSCREEN]))) + ei.wait_for(lambda: status.touchscreen and status.resumed) + + assert status.touchscreen is not None + + ei.send(status.device.StartEmulating(status.serial, 123)) + logger.debug("Sending touch events") + touchid = 1 + touchscreen = status.touchscreen + device = status.device + ei.send(touchscreen.Down(touchid, 10, 20)) + ei.send(device.Frame(status.serial, int(time.time()))) + ei.send(touchscreen.Motion(touchid, 10, 25)) + ei.send(device.Frame(status.serial, int(time.time()))) + ei.send(touchscreen.Cancel(touchid)) + try: + ei.send(device.Frame(status.serial, int(time.time()))) + except BrokenPipeError: + pass + + ei.dispatch() + ei.wait_for(lambda: status.disconnected) + + if ei_touchscreen_version == 1: + assert status.disconnected is True + else: + ei.callback_roundtrip() + assert status.disconnected is False