diff --git a/proto/protocol.xml b/proto/protocol.xml index 824b871..b3727e8 100644 --- a/proto/protocol.xml +++ b/proto/protocol.xml @@ -1640,6 +1640,344 @@ + + + Interface for swipe gesture requests and events. + + This interface is only provided once per device and where a client + requests ei_gesture_swipe.release the interface does not get re-initialized. An + EIS implementation may adjust the behavior of the device (including removing + the device) if the interface is released. + + Note that for a client to receive objects of this type, it must announce + support for this interface in ei_handshake.interface_version. + + + + + + + Notification that the client is no longer interested in swipe gestures. + The EIS implementation will release any resources related to this gesture and + send the ei_gesture_swipe.destroyed event once complete. + + + + + + Starts a new swipe gesture with the given number of fingers. + + The finger count must never be zero. While theoretically it is + possible to have single-finger swipe gestures, clients are advised + to avoid these as an EIS implementation is likely to immediately cancel + (or silently ignore) these. + + Only one swipe gesture may be active at any time, it is a protocol + violation to send a ei_gesture_swipe.begin request before + terminating the previous gesture (if any) with + ei_gesture_swipe.end. + + A swipe gesture may be terminated by the EIS implementation + with an ei_gesture_swipe.cancelled event. + + + + + + + Moves the gesture's logical center by the given delta. + + This request may be sent at most once per device frame and only + after a gesture has begun via ei_gesture_swipe.begin and only + before the gesture terminates with ei_gesture_swipe.end. + + This request must not be sent in the same frame as begin or end. + + + + + + + + Logically ends this gesture. + + + + + + + + + This gesture has been removed and a client should release all + associated resources. + + This ei_gesture_swipe object will be destroyed by the EIS + implementation immediately after this event is sent + and as such the client must not attempt to use + it after that point. + + + + + + + The current gesture has been cancelled by the EIS implementation. + The client must discard the current gesture and not send further + ei_gesture_swipe.update and ei_gesture_swipe.end events. Due to the asynchronous + nature of the protocol, an EIS implementation must allow + for ei_gesture_swipe.update and ei_gesture_swipe.end events to be received after + sending ei_gesture_swipe.cancelled. These events must be discarded. + + The client may begin a new gesture in the future using ei_gesture_swipe.begin. + + + + + + + See the ei_gesture_swipe.begin request for details. + + + + + + + See the ei_gesture_swipe.update request for details. + + + + + + + + See the ei_gesture_swipe.end request for details. + + + + + + + + Interface for pinch gesture requests and events. + + This interface is only provided once per device and where a client + requests ei_gesture_pinch.release the interface does not get re-initialized. An + EIS implementation may adjust the behavior of the device (including removing + the device) if the interface is released. + + Note that for a client to receive objects of this type, it must announce + support for this interface in ei_handshake.interface_version. + + + + + + + Notification that the client is no longer interested in pinch gestures. + The EIS implementation will release any resources related to this gesture and + send the ei_gesture_pinch.destroyed event once complete. + + + + + + Starts a new pinch gesture with the given number of fingers. + + The finger count for pinch gestures must be at least 2. + + Only one pinch gesture may be active at any time, it is a protocol + violation to send a ei_gesture_pinch.begin request before + terminating the previous gesture (if any) with + ei_gesture_pinch.end. + + A pinch gesture may be terminated by the EIS implementation + with an ei_gesture_pinch.cancelled event. + + + + + + + Moves the gesture's logical center by the given delta and/or + updates the logical scale of the gesture and/or logically + rotates the gesture. + + The gesture's scale is an absolute normalized + scale. Each pinch gesture starts at the scale of 1.0, + subsequent events increase or decrease that scale as the + pinch is widened or reduced. For example a scale of 2.0 + indicates the fingers are now twice as far apart relative + to the corresponding ei_gesture_pinch.begin. + + The rotation is the relative angle, in degrees, compared + to the previous ei_gesture_pinch.begin or + ei_gesture_pinch.update. A positive angle indicates CW rotation, + a negative angle CCW rotation. + + This request may be sent at most once per device frame and only + after a gesture has begun via ei_gesture_pinch.begin and only + before the gesture terminates with ei_gesture_pinch.end. + + This request must not be sent in the same frame as begin or end. + + + + + + + + + + Logically ends this gesture. + + + + + + + + + This gesture has been removed and a client should release all + associated resources. + + This ei_gesture_pinch object will be destroyed by the EIS + implementation immediately after this event is sent + and as such the client must not attempt to use + it after that point. + + + + + + + The current gesture has been cancelled by the EIS implementation. + The client must discard the current gesture and not send further + ei_gesture_pinch.update and ei_gesture_pinch.end events. Due to the asynchronous + nature of the protocol, an EIS implementation must allow + for ei_gesture_pinch.update and ei_gesture_pinch.end events to be received after + sending ei_gesture_pinch.cancelled. These events must be discarded. + + The client may begin a new gesture in the future using ei_gesture_pinch.begin. + + + + + + + See the ei_gesture_pinch.begin request for details. + + + + + + + See the ei_gesture_pinch.update request for details. + + + + + + + + + + See the ei_gesture_pinch.end request for details. + + + + + + + + Interface for hold gesture requests and events. + + This interface is only provided once per device and where a client + requests ei_gesture_hold.release the interface does not get re-initialized. An + EIS implementation may adjust the behavior of the device (including removing + the device) if the interface is released. + + Note that for a client to receive objects of this type, it must announce + support for this interface in ei_handshake.interface_version. + + + + + + + Notification that the client is no longer interested in hold gestures. + The EIS implementation will release any resources related to this gesture and + send the ei_gesture_hold.destroyed event once complete. + + + + + + Starts a new hold gesture with the given number of fingers. + + The finger count must never be zero. + + Only one hold gesture may be active at any time, it is a protocol + violation to send a ei_gesture_hold.begin request before + terminating the previous gesture (if any) with + ei_gesture_hold.end. + + A hold gesture may be terminated by the EIS implementation + with an ei_gesture_hold.cancelled event. + + + + + + + Logically ends this gesture. + + + + + + + + + This gesture has been removed and a client should release all + associated resources. + + This ei_gesture_hold object will be destroyed by the EIS + implementation immediately after this event is sent + and as such the client must not attempt to use + it after that point. + + + + + + + The current gesture has been cancelled by the EIS implementation. + The client must discard the current gesture and not send further + ei_gesture_hold.end events. Due to the asynchronous + nature of the protocol, an EIS implementation must allow for + ei_gesture_hold.end events to be received after + sending ei_gesture_hold.cancelled. These events must be discarded. + + The client may begin a new gesture in the future using ei_gesture_hold.begin. + + + + + + + See the ei_gesture_hold.begin request for details. + + + + + + + See the ei_gesture_hold.end request for details. + + + + + Interface for text-based requests and events. @@ -1755,6 +2093,7 @@ within the same frame is undefined. + diff --git a/src/libei-device.c b/src/libei-device.c index 8c0913e..a7d11db 100644 --- a/src/libei-device.c +++ b/src/libei-device.c @@ -91,6 +91,9 @@ ei_device_destroy(struct ei_device *device) ei_button_unref(device->button); ei_touchscreen_unref(device->touchscreen); ei_keyboard_unref(device->keyboard); + ei_gesture_swipe_unref(device->swipe); + ei_gesture_pinch_unref(device->pinch); + ei_gesture_hold_unref(device->hold); ei_text_unref(device->text); ei_seat_unref(seat); free(device->name); @@ -256,6 +259,8 @@ handle_msg_done(struct ei_device *device) mask_add(device->capabilities, EI_DEVICE_CAP_KEYBOARD); if (device->touchscreen) mask_add(device->capabilities, EI_DEVICE_CAP_TOUCH); + if (device->swipe || device->pinch || device->hold) + mask_add(device->capabilities, EI_DEVICE_CAP_GESTURES); if (device->text) mask_add(device->capabilities, EI_DEVICE_CAP_TEXT); @@ -265,6 +270,7 @@ handle_msg_done(struct ei_device *device) !ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH) && !ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) && !ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL) && + !ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES) && !ei_device_has_capability(device, EI_DEVICE_CAP_TEXT)) { log_debug(ei, "Rejecting device %#" PRIx64 " '%s' with no known capabilities", @@ -279,7 +285,7 @@ handle_msg_done(struct ei_device *device) ei_queue_device_added_event(device); ei_device_done(device); log_debug(ei, - "Added device %#" PRIx64 " '%s' caps: %s%s%s%s%s%s%s seat: %s", + "Added device %#" PRIx64 " '%s' caps: %s%s%s%s%s%s%s%s seat: %s", ei_device_get_id(device), ei_device_get_name(device), ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) ? "p" : "", @@ -288,6 +294,7 @@ handle_msg_done(struct ei_device *device) ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH) ? "t" : "", ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) ? "b" : "", ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL) ? "s" : "", + ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES) ? "g" : "", ei_device_has_capability(device, EI_DEVICE_CAP_TEXT) ? "x" : "", ei_seat_get_name(ei_device_get_seat(device))); return NULL; @@ -329,6 +336,14 @@ handle_msg_paused(struct ei_device *device, uint32_t serial) } \ } while(0) +#define DISCONNECT_IF_RECEIVER_CONTEXT(device_) do {\ + struct ei *ei_ = ei_device_get_context(device_); \ + if (!ei_is_sender(ei_)) { \ + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_MODE, \ + "Invalid event from sender EIS context. Disconnecting"); \ + } \ +} while(0) + static struct brei_result * handle_msg_start_emulating(struct ei_device *device, uint32_t serial, uint32_t sequence) { @@ -479,6 +494,24 @@ handle_msg_interface(struct ei_device *device, object_id_t id, const char *name, "Duplicate ei_touchscreen interface object on device"); device->touchscreen = ei_touchscreen_new(device, id, version); + } else if (streq(name, EI_GESTURE_SWIPE_INTERFACE_NAME)) { + DISCONNECT_IF_INVALID_VERSION(ei, ei_gesture_swipe, id, version); + if (device->swipe) + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Duplicate ei_gesture_swipe object on device"); + device->swipe = ei_gesture_swipe_new(device, id, version); + } else if (streq(name, EI_GESTURE_PINCH_INTERFACE_NAME)) { + DISCONNECT_IF_INVALID_VERSION(ei, ei_gesture_pinch, id, version); + if (device->pinch) + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Duplicate ei_gesture_pinch object on device"); + device->pinch = ei_gesture_pinch_new(device, id, version); + } else if (streq(name, EI_GESTURE_HOLD_INTERFACE_NAME)) { + DISCONNECT_IF_INVALID_VERSION(ei, ei_gesture_hold, id, version); + if (device->hold) + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Duplicate ei_gesture_hold object on device"); + device->hold = ei_gesture_hold_new(device, id, version); } else if (streq(name, EI_TEXT_INTERFACE_NAME)) { DISCONNECT_IF_INVALID_VERSION(ei, ei_text, id, version); if (device->text) @@ -935,6 +968,386 @@ ei_device_get_touchscreen_interface(struct ei_device *device) return &touchscreen_interface; } +static struct brei_result * +handle_msg_gesture_swipe_destroy(struct ei_gesture_swipe *swipe, uint32_t serial) +{ + struct ei *ei = ei_gesture_swipe_get_context(swipe); + ei_update_serial(ei, serial); + + struct ei_device *device = ei_gesture_swipe_get_device(swipe); + ei_gesture_swipe_unref(steal(&device->swipe)); + + return NULL; +} + +static struct brei_result * +handle_msg_gesture_swipe_begin(struct ei_gesture_swipe *swipe, uint32_t nfingers) +{ + struct ei_device *device = ei_gesture_swipe_get_device(swipe); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe begin event for non-gesture device"); + } + + if (nfingers == 0) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe begin event with zero fingers"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + if (device->gesture_state.swipe.is_active) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe begin event while swipe is active"); + } + device->gesture_state.swipe.is_active = true; + device->gesture_state.swipe.nfingers = nfingers; + ei_queue_swipe_begin_event(device, nfingers); + return NULL; + } + + return maybe_error_on_device_state(device, "swipe begin"); +} + +static struct brei_result * +handle_msg_gesture_swipe_update(struct ei_gesture_swipe *swipe, float dx, float dy) +{ + struct ei_device *device = ei_gesture_swipe_get_device(swipe); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe update event for non-gesture device"); + } + + if (!device->gesture_state.swipe.is_active) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe update event while no swipe is active"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + ei_queue_swipe_update_event(device, dx, dy); + return NULL; + } + + return maybe_error_on_device_state(device, "swipe update"); +} + +static struct brei_result * +handle_msg_gesture_swipe_end(struct ei_gesture_swipe *swipe, uint32_t is_cancelled) +{ + struct ei_device *device = ei_gesture_swipe_get_device(swipe); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe end event for non-gesture device"); + } + + if (!device->gesture_state.swipe.is_active) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe end event while no swipe is active"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + ei_queue_swipe_end_event(device, !!is_cancelled); + device->gesture_state.swipe.is_active = false; + return NULL; + } + + return maybe_error_on_device_state(device, "swipe end"); +} + +static struct brei_result * +handle_msg_gesture_swipe_cancelled(struct ei_gesture_swipe *swipe, uint32_t serial) +{ + struct ei *ei = ei_gesture_swipe_get_context(swipe); + struct ei_device *device = ei_gesture_swipe_get_device(swipe); + ei_update_serial(ei, serial); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe cancelled event for non-gesture device"); + } + + /* EIS may have cancelled us while we've ended the gesture, + * swallow the cancel if that happened */ + if (device->gesture_state.swipe.current_swipe) { + if (device->state == EI_DEVICE_STATE_EMULATING) + ei_queue_swipe_cancelled_event(device); + device->gesture_state.swipe.current_swipe = NULL; + return NULL; + } + + return maybe_error_on_device_state(device, "swipe cancel"); +} + +static const struct ei_gesture_swipe_interface swipe_interface = { + .destroyed = handle_msg_gesture_swipe_destroy, + .begin = handle_msg_gesture_swipe_begin, + .update = handle_msg_gesture_swipe_update, + .end = handle_msg_gesture_swipe_end, + .cancelled = handle_msg_gesture_swipe_cancelled, +}; + +const struct ei_gesture_swipe_interface * +ei_device_get_gesture_swipe_interface(struct ei_device *device) +{ + return &swipe_interface; +} + +static struct brei_result * +handle_msg_gesture_pinch_destroy(struct ei_gesture_pinch *pinch, uint32_t serial) +{ + struct ei *ei = ei_gesture_pinch_get_context(pinch); + ei_update_serial(ei, serial); + + struct ei_device *device = ei_gesture_pinch_get_device(pinch); + ei_gesture_pinch_unref(steal(&device->pinch)); + + return NULL; +} + +static struct brei_result * +handle_msg_gesture_pinch_begin(struct ei_gesture_pinch *pinch, uint32_t nfingers) +{ + struct ei_device *device = ei_gesture_pinch_get_device(pinch); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch begin event for non-gesture device"); + } + + if (nfingers < 2) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch begin event with fewer than 2 fingers"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + if (device->gesture_state.pinch.is_active) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch begin event while pinch is active"); + } + device->gesture_state.pinch.is_active = true; + device->gesture_state.pinch.nfingers = nfingers; + ei_queue_pinch_begin_event(device, nfingers); + return NULL; + } + + return maybe_error_on_device_state(device, "pinch begin"); +} + +static struct brei_result * +handle_msg_gesture_pinch_update(struct ei_gesture_pinch *pinch, + float dx, + float dy, + float scale, + float rotation) +{ + struct ei_device *device = ei_gesture_pinch_get_device(pinch); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch update event for non-gesture device"); + } + + if (!device->gesture_state.pinch.is_active) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch update event while no pinch is active"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + ei_queue_pinch_update_event(device, dx, dy, scale, rotation); + return NULL; + } + + return maybe_error_on_device_state(device, "pinch update"); +} + +static struct brei_result * +handle_msg_gesture_pinch_end(struct ei_gesture_pinch *pinch, uint32_t is_cancelled) +{ + struct ei_device *device = ei_gesture_pinch_get_device(pinch); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch end event for non-gesture device"); + } + + if (!device->gesture_state.pinch.is_active) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch end event while no pinch is active"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + ei_queue_pinch_end_event(device, !!is_cancelled); + device->gesture_state.pinch.is_active = false; + return NULL; + } + + return maybe_error_on_device_state(device, "pinch end"); +} + +static struct brei_result * +handle_msg_gesture_pinch_cancelled(struct ei_gesture_pinch *pinch, uint32_t serial) +{ + struct ei *ei = ei_gesture_pinch_get_context(pinch); + struct ei_device *device = ei_gesture_pinch_get_device(pinch); + ei_update_serial(ei, serial); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch cancelled event for non-gesture device"); + } + + /* EIS may have cancelled us while we've ended the gesture, + * swallow the cancel if that happened */ + if (device->gesture_state.pinch.current_pinch) { + if (device->state == EI_DEVICE_STATE_EMULATING) + ei_queue_pinch_cancelled_event(device); + device->gesture_state.pinch.current_pinch = NULL; + return NULL; + } + + return maybe_error_on_device_state(device, "pinch cancel"); +} + +static const struct ei_gesture_pinch_interface pinch_interface = { + .destroyed = handle_msg_gesture_pinch_destroy, + .begin = handle_msg_gesture_pinch_begin, + .update = handle_msg_gesture_pinch_update, + .end = handle_msg_gesture_pinch_end, + .cancelled = handle_msg_gesture_pinch_cancelled, +}; + +const struct ei_gesture_pinch_interface * +ei_device_get_gesture_pinch_interface(struct ei_device *device) +{ + return &pinch_interface; +} + +static struct brei_result * +handle_msg_gesture_hold_destroy(struct ei_gesture_hold *hold, uint32_t serial) +{ + struct ei *ei = ei_gesture_hold_get_context(hold); + ei_update_serial(ei, serial); + + struct ei_device *device = ei_gesture_hold_get_device(hold); + ei_gesture_hold_unref(steal(&device->hold)); + + return NULL; +} + +static struct brei_result * +handle_msg_gesture_hold_begin(struct ei_gesture_hold *hold, uint32_t nfingers) +{ + struct ei_device *device = ei_gesture_hold_get_device(hold); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold begin event for non-gesture device"); + } + + if (nfingers == 0) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold begin event with zero fingers"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + if (device->gesture_state.hold.is_active) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold begin event while hold is active"); + } + device->gesture_state.hold.is_active = true; + device->gesture_state.hold.nfingers = nfingers; + ei_queue_hold_begin_event(device, nfingers); + return NULL; + } + + return maybe_error_on_device_state(device, "hold begin"); +} + +static struct brei_result * +handle_msg_gesture_hold_end(struct ei_gesture_hold *hold, uint32_t is_cancelled) +{ + struct ei_device *device = ei_gesture_hold_get_device(hold); + + DISCONNECT_IF_SENDER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold end event for non-gesture device"); + } + + if (!device->gesture_state.hold.is_active) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold end event while no hold is active"); + } + + if (device->state == EI_DEVICE_STATE_EMULATING) { + ei_queue_hold_end_event(device, !!is_cancelled); + device->gesture_state.hold.is_active = false; + return NULL; + } + + return maybe_error_on_device_state(device, "hold end"); +} + +static struct brei_result * +handle_msg_gesture_hold_cancelled(struct ei_gesture_hold *hold, uint32_t serial) +{ + struct ei *ei = ei_gesture_hold_get_context(hold); + struct ei_device *device = ei_gesture_hold_get_device(hold); + ei_update_serial(ei, serial); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold cancelled event for non-gesture device"); + } + + /* EIS may have cancelled us while we've ended the gesture, + * swallow the cancel if that happened */ + if (device->gesture_state.hold.current_hold) { + if (device->state == EI_DEVICE_STATE_EMULATING) + ei_queue_hold_cancelled_event(device); + device->gesture_state.hold.current_hold = NULL; + return NULL; + } + + return maybe_error_on_device_state(device, "hold cancel"); +} + +static const struct ei_gesture_hold_interface hold_interface = { + .destroyed = handle_msg_gesture_hold_destroy, + .begin = handle_msg_gesture_hold_begin, + .end = handle_msg_gesture_hold_end, + .cancelled = handle_msg_gesture_hold_cancelled, +}; + +const struct ei_gesture_hold_interface * +ei_device_get_gesture_hold_interface(struct ei_device *device) +{ + return &hold_interface; +} + static struct brei_result * handle_msg_text_destroy(struct ei_text *text, uint32_t serial) { @@ -1147,6 +1560,12 @@ ei_device_send_release(struct ei_device *device) ei_scroll_request_release(device->scroll); if (device->button) ei_button_request_release(device->button); + if (device->swipe) + ei_gesture_swipe_request_release(device->swipe); + if (device->pinch) + ei_gesture_pinch_request_release(device->pinch); + if (device->hold) + ei_gesture_hold_request_release(device->hold); int rc = ei_device_request_release(device); if (rc) @@ -1207,6 +1626,9 @@ ei_device_removed_by_server(struct ei_device *device) ei_touchscreen_unref(steal(&device->touchscreen)); ei_scroll_unref(steal(&device->scroll)); ei_button_unref(steal(&device->button)); + ei_gesture_swipe_unref(steal(&device->swipe)); + ei_gesture_pinch_unref(steal(&device->pinch)); + ei_gesture_hold_unref(steal(&device->hold)); ei_unregister_object(ei, &device->proto_object); ei_queue_device_removed_event(device); @@ -1282,6 +1704,7 @@ ei_device_has_capability(struct ei_device *device, enum ei_device_capability cap case EI_DEVICE_CAP_TOUCH: case EI_DEVICE_CAP_BUTTON: case EI_DEVICE_CAP_SCROLL: + case EI_DEVICE_CAP_GESTURES: case EI_DEVICE_CAP_TEXT: return mask_all(device->capabilities, cap); } @@ -2045,3 +2468,21 @@ ei_device_frame(struct ei_device *device, uint64_t time) ei_disconnect(ei_device_get_context(device)); return; } + +_public_ struct ei_swipe * +ei_device_swipe_new(struct ei_device *device, uint32_t nfingers) +{ + return ei_swipe_new(device, nfingers); +} + +_public_ struct ei_pinch * +ei_device_pinch_new(struct ei_device *device, uint32_t nfingers) +{ + return ei_pinch_new(device, nfingers); +} + +_public_ struct ei_hold * +ei_device_hold_new(struct ei_device *device, uint32_t nfingers) +{ + return ei_hold_new(device, nfingers); +} diff --git a/src/libei-device.h b/src/libei-device.h index 4525262..fa8e2ac 100644 --- a/src/libei-device.h +++ b/src/libei-device.h @@ -66,6 +66,9 @@ struct ei_device { struct ei_button *button; struct ei_keyboard *keyboard; struct ei_touchscreen *touchscreen; + struct ei_gesture_swipe *swipe; + struct ei_gesture_pinch *pinch; + struct ei_gesture_hold *hold; struct ei_text *text; struct list link; @@ -87,6 +90,33 @@ struct ei_device { bool x_is_cancelled, y_is_cancelled; } scroll_state; + struct { + struct { + /* receiver */ + bool is_active; + uint32_t nfingers; + + /* sender */ + struct ei_swipe *current_swipe; /* no ref! */ + } swipe; + struct { + /* receiver */ + bool is_active; + uint32_t nfingers; + + /* sender */ + struct ei_pinch *current_pinch; /* no ref! */ + } pinch; + struct { + /* receiver */ + bool is_active; + uint32_t nfingers; + + /* sender */ + struct ei_hold *current_hold; /* no ref! */ + } hold; + } gesture_state; + struct ei_keymap *keymap; char *pending_region_mapping_id; @@ -127,6 +157,13 @@ OBJECT_DECLARE_GETTER(ei_device, scroll_interface, const struct ei_scroll_interf OBJECT_DECLARE_GETTER(ei_device, button_interface, const struct ei_button_interface *); OBJECT_DECLARE_GETTER(ei_device, keyboard_interface, const struct ei_keyboard_interface *); OBJECT_DECLARE_GETTER(ei_device, touchscreen_interface, const struct ei_touchscreen_interface *); +OBJECT_DECLARE_GETTER(ei_device, + gesture_swipe_interface, + const struct ei_gesture_swipe_interface *); +OBJECT_DECLARE_GETTER(ei_device, + gesture_pinch_interface, + const struct ei_gesture_pinch_interface *); +OBJECT_DECLARE_GETTER(ei_device, gesture_hold_interface, const struct ei_gesture_hold_interface *); OBJECT_DECLARE_GETTER(ei_device, text_interface, const struct ei_text_interface *); OBJECT_DECLARE_SETTER(ei_device, type, enum ei_device_type); OBJECT_DECLARE_SETTER(ei_device, name, const char *); diff --git a/src/libei-event.c b/src/libei-event.c index e982aec..080ecdd 100644 --- a/src/libei-event.c +++ b/src/libei-event.c @@ -63,6 +63,17 @@ ei_event_type_to_string(enum ei_event_type type) CASE_RETURN_STRING(EI_EVENT_TOUCH_DOWN); CASE_RETURN_STRING(EI_EVENT_TOUCH_UP); CASE_RETURN_STRING(EI_EVENT_TOUCH_MOTION); + CASE_RETURN_STRING(EI_EVENT_SWIPE_BEGIN); + CASE_RETURN_STRING(EI_EVENT_SWIPE_UPDATE); + CASE_RETURN_STRING(EI_EVENT_SWIPE_END); + CASE_RETURN_STRING(EI_EVENT_SWIPE_CANCELLED); + CASE_RETURN_STRING(EI_EVENT_PINCH_BEGIN); + CASE_RETURN_STRING(EI_EVENT_PINCH_UPDATE); + CASE_RETURN_STRING(EI_EVENT_PINCH_END); + CASE_RETURN_STRING(EI_EVENT_PINCH_CANCELLED); + CASE_RETURN_STRING(EI_EVENT_HOLD_BEGIN); + CASE_RETURN_STRING(EI_EVENT_HOLD_END); + CASE_RETURN_STRING(EI_EVENT_HOLD_CANCELLED); CASE_RETURN_STRING(EI_EVENT_TEXT_KEYSYM); CASE_RETURN_STRING(EI_EVENT_TEXT_UTF8); } @@ -98,6 +109,17 @@ ei_event_destroy(struct ei_event *event) case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_UP: case EI_EVENT_TOUCH_MOTION: + case EI_EVENT_SWIPE_BEGIN: + case EI_EVENT_SWIPE_UPDATE: + case EI_EVENT_SWIPE_END: + case EI_EVENT_SWIPE_CANCELLED: + case EI_EVENT_PINCH_BEGIN: + case EI_EVENT_PINCH_UPDATE: + case EI_EVENT_PINCH_END: + case EI_EVENT_PINCH_CANCELLED: + case EI_EVENT_HOLD_BEGIN: + case EI_EVENT_HOLD_END: + case EI_EVENT_HOLD_CANCELLED: case EI_EVENT_TEXT_KEYSYM: break; case EI_EVENT_TEXT_UTF8: @@ -363,6 +385,110 @@ ei_event_touch_get_is_cancel(struct ei_event *event) return event->touch.is_cancel; } +_public_ uint32_t +ei_event_swipe_get_finger_count(struct ei_event *event) +{ + require_event_type(event, + 0, + EI_EVENT_SWIPE_BEGIN, + EI_EVENT_SWIPE_UPDATE, + EI_EVENT_SWIPE_END); + + return event->gestures.nfingers; +} + +_public_ double +ei_event_swipe_get_dx(struct ei_event *event) +{ + require_event_type(event, 0.0, EI_EVENT_SWIPE_UPDATE); + + return event->gestures.dx; +} + +_public_ double +ei_event_swipe_get_dy(struct ei_event *event) +{ + require_event_type(event, 0.0, EI_EVENT_SWIPE_UPDATE); + + return event->gestures.dy; +} + +_public_ bool +ei_event_swipe_get_is_cancelled(struct ei_event *event) +{ + require_event_type(event, false, EI_EVENT_SWIPE_END); + + return event->gestures.is_cancelled; +} + +_public_ uint32_t +ei_event_pinch_get_finger_count(struct ei_event *event) +{ + require_event_type(event, + 0, + EI_EVENT_PINCH_BEGIN, + EI_EVENT_PINCH_UPDATE, + EI_EVENT_PINCH_END); + + return event->gestures.nfingers; +} + +_public_ double +ei_event_pinch_get_dx(struct ei_event *event) +{ + require_event_type(event, 0.0, EI_EVENT_PINCH_UPDATE); + + return event->gestures.dx; +} + +_public_ double +ei_event_pinch_get_dy(struct ei_event *event) +{ + require_event_type(event, 0.0, EI_EVENT_PINCH_UPDATE); + + return event->gestures.dy; +} + +_public_ double +ei_event_pinch_get_rotation(struct ei_event *event) +{ + require_event_type(event, 0.0, EI_EVENT_PINCH_UPDATE); + + return event->gestures.degrees; +} + +_public_ double +ei_event_pinch_get_scale(struct ei_event *event) +{ + require_event_type(event, 0.0, EI_EVENT_PINCH_UPDATE); + + return event->gestures.scale; +} + +_public_ bool +ei_event_pinch_get_is_cancelled(struct ei_event *event) +{ + require_event_type(event, false, EI_EVENT_PINCH_END); + + return event->gestures.is_cancelled; +} + +_public_ uint32_t +ei_event_hold_get_finger_count(struct ei_event *event) +{ + require_event_type(event, 0, EI_EVENT_HOLD_BEGIN, EI_EVENT_HOLD_END); + + return event->gestures.nfingers; +} + +_public_ bool +ei_event_hold_get_is_cancelled(struct ei_event *event) +{ + require_event_type(event, false, EI_EVENT_HOLD_END); + + return event->gestures.is_cancelled; +} + _public_ uint32_t ei_event_text_get_keysym(struct ei_event *event) { @@ -403,6 +529,17 @@ ei_event_get_time(struct ei_event *event) EI_EVENT_TOUCH_DOWN, EI_EVENT_TOUCH_UP, EI_EVENT_TOUCH_MOTION, + EI_EVENT_SWIPE_BEGIN, + EI_EVENT_SWIPE_UPDATE, + EI_EVENT_SWIPE_END, + EI_EVENT_SWIPE_CANCELLED, + EI_EVENT_PINCH_BEGIN, + EI_EVENT_PINCH_UPDATE, + EI_EVENT_PINCH_END, + EI_EVENT_PINCH_CANCELLED, + EI_EVENT_HOLD_BEGIN, + EI_EVENT_HOLD_END, + EI_EVENT_HOLD_CANCELLED, EI_EVENT_TEXT_KEYSYM, EI_EVENT_TEXT_UTF8, EI_EVENT_FRAME); diff --git a/src/libei-event.h b/src/libei-event.h index 451c22c..98ebc83 100644 --- a/src/libei-event.h +++ b/src/libei-event.h @@ -81,6 +81,13 @@ struct ei_event { struct { struct ei_pingpong *pingpong; } sync; + struct { + uint32_t nfingers; + double dx, dy; + double degrees; + double scale; + bool is_cancelled; + } gestures; }; }; diff --git a/src/libei-gestures.c b/src/libei-gestures.c new file mode 100644 index 0000000..8ba60af --- /dev/null +++ b/src/libei-gestures.c @@ -0,0 +1,769 @@ +/* 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 "config.h" + +#include +#include + +#include "util-bits.h" +#include "util-io.h" +#include "util-macros.h" +#include "util-mem.h" +#include "util-strings.h" +#include "util-version.h" + +#include "ei-proto.h" +#include "libei-private.h" + +static void +ei_gesture_swipe_destroy(struct ei_gesture_swipe *swipe) +{ + struct ei *ei = ei_gesture_swipe_get_context(swipe); + ei_unregister_object(ei, &swipe->proto_object); +} + +OBJECT_IMPLEMENT_REF(ei_gesture_swipe); +OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_gesture_swipe); + +static OBJECT_IMPLEMENT_CREATE(ei_gesture_swipe); +static OBJECT_IMPLEMENT_PARENT(ei_gesture_swipe, ei_device); +OBJECT_IMPLEMENT_GETTER_AS_REF(ei_gesture_swipe, proto_object, const struct brei_object *); + +struct ei_device * +ei_gesture_swipe_get_device(struct ei_gesture_swipe *swipe) +{ + return ei_gesture_swipe_parent(swipe); +} + +struct ei * +ei_gesture_swipe_get_context(struct ei_gesture_swipe *swipe) +{ + return ei_device_get_context(ei_gesture_swipe_get_device(swipe)); +} + +const struct ei_gesture_swipe_interface * +ei_gesture_swipe_get_interface(struct ei_gesture_swipe *swipe) +{ + struct ei_device *device = ei_gesture_swipe_get_device(swipe); + return ei_device_get_gesture_swipe_interface(device); +} + +struct ei_gesture_swipe * +ei_gesture_swipe_new(struct ei_device *device, object_id_t id, uint32_t version) +{ + struct ei_gesture_swipe *swipe = ei_gesture_swipe_create(&device->object); + struct ei *ei = ei_device_get_context(device); + + swipe->proto_object.id = id; + swipe->proto_object.implementation = swipe; + swipe->proto_object.interface = &ei_gesture_swipe_proto_interface; + swipe->proto_object.version = version; + ei_register_object(ei, &swipe->proto_object); + + return swipe; /* ref owned by caller */ +} + +static void +ei_gesture_pinch_destroy(struct ei_gesture_pinch *pinch) +{ + struct ei *ei = ei_gesture_pinch_get_context(pinch); + ei_unregister_object(ei, &pinch->proto_object); +} + +OBJECT_IMPLEMENT_REF(ei_gesture_pinch); +OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_gesture_pinch); + +static OBJECT_IMPLEMENT_CREATE(ei_gesture_pinch); +static OBJECT_IMPLEMENT_PARENT(ei_gesture_pinch, ei_device); +OBJECT_IMPLEMENT_GETTER_AS_REF(ei_gesture_pinch, proto_object, const struct brei_object *); + +struct ei_device * +ei_gesture_pinch_get_device(struct ei_gesture_pinch *pinch) +{ + return ei_gesture_pinch_parent(pinch); +} + +struct ei * +ei_gesture_pinch_get_context(struct ei_gesture_pinch *pinch) +{ + return ei_device_get_context(ei_gesture_pinch_get_device(pinch)); +} + +const struct ei_gesture_pinch_interface * +ei_gesture_pinch_get_interface(struct ei_gesture_pinch *pinch) +{ + struct ei_device *device = ei_gesture_pinch_get_device(pinch); + return ei_device_get_gesture_pinch_interface(device); +} + +struct ei_gesture_pinch * +ei_gesture_pinch_new(struct ei_device *device, object_id_t id, uint32_t version) +{ + struct ei_gesture_pinch *pinch = ei_gesture_pinch_create(&device->object); + struct ei *ei = ei_device_get_context(device); + + pinch->proto_object.id = id; + pinch->proto_object.implementation = pinch; + pinch->proto_object.interface = &ei_gesture_pinch_proto_interface; + pinch->proto_object.version = version; + ei_register_object(ei, &pinch->proto_object); + + return pinch; /* ref owned by caller */ +} + +static void +ei_gesture_hold_destroy(struct ei_gesture_hold *hold) +{ + struct ei *ei = ei_gesture_hold_get_context(hold); + ei_unregister_object(ei, &hold->proto_object); +} + +OBJECT_IMPLEMENT_REF(ei_gesture_hold); +OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_gesture_hold); + +static OBJECT_IMPLEMENT_CREATE(ei_gesture_hold); +static OBJECT_IMPLEMENT_PARENT(ei_gesture_hold, ei_device); +OBJECT_IMPLEMENT_GETTER_AS_REF(ei_gesture_hold, proto_object, const struct brei_object *); + +struct ei_device * +ei_gesture_hold_get_device(struct ei_gesture_hold *hold) +{ + return ei_gesture_hold_parent(hold); +} + +struct ei * +ei_gesture_hold_get_context(struct ei_gesture_hold *hold) +{ + return ei_device_get_context(ei_gesture_hold_get_device(hold)); +} + +const struct ei_gesture_hold_interface * +ei_gesture_hold_get_interface(struct ei_gesture_hold *hold) +{ + struct ei_device *device = ei_gesture_hold_get_device(hold); + return ei_device_get_gesture_hold_interface(device); +} + +struct ei_gesture_hold * +ei_gesture_hold_new(struct ei_device *device, object_id_t id, uint32_t version) +{ + struct ei_gesture_hold *hold = ei_gesture_hold_create(&device->object); + struct ei *ei = ei_device_get_context(device); + + hold->proto_object.id = id; + hold->proto_object.implementation = hold; + hold->proto_object.interface = &ei_gesture_hold_proto_interface; + hold->proto_object.version = version; + ei_register_object(ei, &hold->proto_object); + + return hold; /* ref owned by caller */ +} + +static void +ei_gesture_destroy(struct ei_gesture *gesture) +{ + ei_device_unref(gesture->device); +} + +static OBJECT_IMPLEMENT_INIT(ei_gesture); +OBJECT_IMPLEMENT_REF(ei_gesture); +OBJECT_IMPLEMENT_UNREF(ei_gesture); + +struct ei_device * +ei_gesture_get_device(struct ei_gesture *gesture) +{ + return gesture->device; +} + +struct ei * +ei_gesture_get_context(struct ei_gesture *gesture) +{ + return ei_device_get_context(ei_gesture_get_device(gesture)); +} + +static void +ei_gesture_init(struct ei_gesture *gesture, struct ei_device *device, uint32_t nfingers) +{ + /* Not using the device as parent object because we need a ref + * to it */ + ei_gesture_init_object(gesture, NULL); + + gesture->state = EI_GESTURE_STATE_NEW; + gesture->device = ei_device_ref(device); + gesture->nfingers = nfingers; +} + +struct ei_swipe * +ei_swipe_new(struct ei_device *device, uint32_t nfingers) +{ + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + log_bug_client(ei_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return NULL; + } + + if (nfingers == 0) { + log_bug_client(ei_device_get_context(device), + "zero-finger gestures are not possible"); + return NULL; + } + + if (!device->swipe) + return NULL; + + struct ei_swipe *swipe = xalloc(sizeof *swipe); + ei_gesture_init(&swipe->base, device, nfingers); + + return swipe; +} + +_public_ struct ei_swipe * +ei_swipe_ref(struct ei_swipe *swipe) +{ + return (struct ei_swipe *)ei_gesture_ref(&swipe->base); +} + +_public_ struct ei_swipe * +ei_swipe_unref(struct ei_swipe *swipe) +{ + if (!swipe) + return NULL; + + struct ei_device *device = ei_gesture_get_device(&swipe->base); + + if (swipe == device->gesture_state.swipe.current_swipe) { + if (swipe->base.state == EI_GESTURE_STATE_BEGUN && + device->state == EI_DEVICE_STATE_EMULATING) { + ei_swipe_cancel(swipe); + } + } + + return (struct ei_swipe *)ei_gesture_unref(&swipe->base); +} + +struct ei * +ei_swipe_get_context(struct ei_swipe *swipe) +{ + return ei_gesture_get_context(&swipe->base); +} + +_public_ void +ei_swipe_begin(struct ei_swipe *swipe) +{ + struct ei_device *device = swipe->base.device; + struct ei *ei = ei_device_get_context(device); + + if (swipe->base.state != EI_GESTURE_STATE_NEW) { + log_bug_client(ei_gesture_get_context(&swipe->base), "gesture cannot begin twice"); + return; + } + + if (device->gesture_state.swipe.current_swipe) { + log_bug_client( + ei_gesture_get_context(&swipe->base), + "a different swipe gesture is already in process, ignoring this one"); + return; + } + + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return; + + if (!device->swipe) + return; + + if (swipe->base.nfingers == 0) { + log_bug_client(ei_device_get_context(device), + "zero-finger gestures are invalid, ignoring gesture"); + return; + } + + device->gesture_state.swipe.current_swipe = swipe; + + device->send_frame_event = true; + + swipe->base.state = EI_GESTURE_STATE_BEGUN; + int rc = ei_gesture_swipe_request_begin(device->swipe, swipe->base.nfingers); + if (rc) + ei_disconnect(ei); +} + +_public_ void +ei_swipe_update(struct ei_swipe *swipe, double x, double y) +{ + struct ei_device *device = swipe->base.device; + struct ei *ei = ei_device_get_context(device); + + if (swipe->base.state != EI_GESTURE_STATE_BEGUN) { + log_bug_client(ei_gesture_get_context(&swipe->base), + "gesture in invalid state for update"); + return; + } + + if (device->gesture_state.swipe.current_swipe != swipe) { + log_bug_client( + ei_gesture_get_context(&swipe->base), + "a different swipe gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return; + + if (!device->swipe) + return; + + device->send_frame_event = true; + + int rc = ei_gesture_swipe_request_update(device->swipe, x, y); + if (rc) + ei_disconnect(ei); +} + +static void +ei_swipe_end_cancel(struct ei_swipe *swipe, bool is_cancel) +{ + struct ei_device *device = swipe->base.device; + struct ei *ei = ei_device_get_context(device); + + if (swipe->base.state != EI_GESTURE_STATE_BEGUN) { + log_bug_client(ei_gesture_get_context(&swipe->base), + "gesture in invalid state for end/cancel"); + return; + } + + if (device->gesture_state.swipe.current_swipe != swipe) { + log_bug_client( + ei_gesture_get_context(&swipe->base), + "a different swipe gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return; + + if (!device->swipe) + return; + + swipe->base.state = EI_GESTURE_STATE_ENDED; + device->gesture_state.swipe.current_swipe = NULL; + + device->send_frame_event = true; + + int rc = ei_gesture_swipe_request_end(device->swipe, is_cancel); + if (rc) + ei_disconnect(ei); +} + +_public_ void +ei_swipe_end(struct ei_swipe *swipe) +{ + ei_swipe_end_cancel(swipe, false); +} + +_public_ void +ei_swipe_cancel(struct ei_swipe *swipe) +{ + ei_swipe_end_cancel(swipe, true); +} + +struct ei_pinch * +ei_pinch_new(struct ei_device *device, uint32_t nfingers) +{ + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + log_bug_client(ei_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return NULL; + } + + if (nfingers < 2) { + log_bug_client(ei_device_get_context(device), + "pinch gestures require at least 2 fingers"); + return NULL; + } + + if (!device->pinch) + return NULL; + + /* Not using the device as parent object because we need a ref + * to it */ + struct ei_pinch *pinch = xalloc(sizeof *pinch); + ei_gesture_init(&pinch->base, device, nfingers); + + return pinch; +} + +_public_ struct ei_pinch * +ei_pinch_ref(struct ei_pinch *pinch) +{ + return (struct ei_pinch *)ei_gesture_ref(&pinch->base); +} + +_public_ struct ei_pinch * +ei_pinch_unref(struct ei_pinch *pinch) +{ + if (!pinch) + return NULL; + + struct ei_device *device = ei_gesture_get_device(&pinch->base); + + if (pinch == device->gesture_state.pinch.current_pinch) { + if (pinch->base.state == EI_GESTURE_STATE_BEGUN && + device->state == EI_DEVICE_STATE_EMULATING) { + ei_pinch_cancel(pinch); + } + } + + return (struct ei_pinch *)ei_gesture_unref(&pinch->base); +} + +struct ei * +ei_pinch_get_context(struct ei_pinch *pinch) +{ + return ei_gesture_get_context(&pinch->base); +} + +_public_ void +ei_pinch_begin(struct ei_pinch *pinch) +{ + struct ei_device *device = pinch->base.device; + struct ei *ei = ei_device_get_context(device); + + if (pinch->base.state != EI_GESTURE_STATE_NEW) { + log_bug_client(ei_gesture_get_context(&pinch->base), "gesture cannot begin twice"); + return; + } + + if (device->gesture_state.pinch.current_pinch) { + log_bug_client( + ei_gesture_get_context(&pinch->base), + "a different pinch gesture is already in process, ignoring this one"); + return; + } + + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return; + + if (!device->pinch) + return; + + if (pinch->base.nfingers == 0) { + log_bug_client(ei_device_get_context(device), + "zero-finger gestures are invalid, ignoring gesture"); + return; + } + + device->gesture_state.pinch.current_pinch = pinch; + + device->send_frame_event = true; + + pinch->base.state = EI_GESTURE_STATE_BEGUN; + int rc = ei_gesture_pinch_request_begin(device->pinch, pinch->base.nfingers); + if (rc) + ei_disconnect(ei); +} + +_public_ void +ei_pinch_update(struct ei_pinch *pinch, double x, double y, double scale, double rotation) +{ + struct ei_device *device = pinch->base.device; + struct ei *ei = ei_device_get_context(device); + + if (pinch->base.state != EI_GESTURE_STATE_BEGUN) { + log_bug_client(ei_gesture_get_context(&pinch->base), + "gesture in invalid state for update"); + return; + } + + if (device->gesture_state.pinch.current_pinch != pinch) { + log_bug_client( + ei_gesture_get_context(&pinch->base), + "a different pinch gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return; + + if (!device->pinch) + return; + + device->send_frame_event = true; + + int rc = ei_gesture_pinch_request_update(device->pinch, x, y, scale, rotation); + if (rc) + ei_disconnect(ei); +} + +static void +ei_pinch_end_cancel(struct ei_pinch *pinch, bool is_cancel) +{ + struct ei_device *device = pinch->base.device; + struct ei *ei = ei_device_get_context(device); + + if (pinch->base.state != EI_GESTURE_STATE_BEGUN) { + log_bug_client(ei_gesture_get_context(&pinch->base), + "gesture in invalid state for end/cancel"); + return; + } + + if (device->gesture_state.pinch.current_pinch != pinch) { + log_bug_client( + ei_gesture_get_context(&pinch->base), + "a different pinch gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return; + + if (!device->pinch) + return; + + pinch->base.state = EI_GESTURE_STATE_ENDED; + device->gesture_state.pinch.current_pinch = NULL; + + device->send_frame_event = true; + + int rc = ei_gesture_pinch_request_end(device->pinch, is_cancel); + if (rc) + ei_disconnect(ei); +} + +_public_ void +ei_pinch_end(struct ei_pinch *pinch) +{ + ei_pinch_end_cancel(pinch, false); +} + +_public_ void +ei_pinch_cancel(struct ei_pinch *pinch) +{ + ei_pinch_end_cancel(pinch, true); +} + +struct ei_hold * +ei_hold_new(struct ei_device *device, uint32_t nfingers) +{ + if (!ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + log_bug_client(ei_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return NULL; + } + + if (nfingers == 0) { + log_bug_client(ei_device_get_context(device), + "zero-finger gestures are not possible"); + return NULL; + } + + if (!device->hold) + return NULL; + + /* Not using the device as parent object because we need a ref + * to it */ + struct ei_hold *hold = xalloc(sizeof *hold); + ei_gesture_init(&hold->base, device, nfingers); + + return hold; +} + +_public_ struct ei_hold * +ei_hold_ref(struct ei_hold *hold) +{ + return (struct ei_hold *)ei_gesture_ref(&hold->base); +} + +_public_ struct ei_hold * +ei_hold_unref(struct ei_hold *hold) +{ + if (!hold) + return NULL; + + struct ei_device *device = ei_gesture_get_device(&hold->base); + + if (hold == device->gesture_state.hold.current_hold) { + if (hold->base.state == EI_GESTURE_STATE_BEGUN && + device->state == EI_DEVICE_STATE_EMULATING) { + ei_hold_cancel(hold); + } + } + + return (struct ei_hold *)ei_gesture_unref(&hold->base); +} + +struct ei * +ei_hold_get_context(struct ei_hold *hold) +{ + return ei_gesture_get_context(&hold->base); +} + +_public_ void +ei_hold_begin(struct ei_hold *hold) +{ + struct ei_device *device = hold->base.device; + struct ei *ei = ei_device_get_context(device); + + if (hold->base.state != EI_GESTURE_STATE_NEW) { + log_bug_client(ei_gesture_get_context(&hold->base), "gesture cannot begin twice"); + return; + } + + if (device->gesture_state.hold.current_hold) { + log_bug_client(ei_gesture_get_context(&hold->base), + "a different hold gesture is already in process, ignoring this one"); + return; + } + + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return; + + if (!device->hold) + return; + + if (hold->base.nfingers == 0) { + log_bug_client(ei_device_get_context(device), + "zero-finger gestures are invalid, ignoring gesture"); + return; + } + + device->gesture_state.hold.current_hold = hold; + + device->send_frame_event = true; + + hold->base.state = EI_GESTURE_STATE_BEGUN; + int rc = ei_gesture_hold_request_begin(device->hold, hold->base.nfingers); + if (rc) + ei_disconnect(ei); +} + +static void +ei_hold_end_cancel(struct ei_hold *hold, bool is_cancel) +{ + struct ei_device *device = hold->base.device; + struct ei *ei = ei_device_get_context(device); + + if (hold->base.state != EI_GESTURE_STATE_BEGUN) { + log_bug_client(ei_gesture_get_context(&hold->base), + "gesture in invalid state for end/cancel"); + return; + } + + if (device->gesture_state.hold.current_hold != hold) { + log_bug_client(ei_gesture_get_context(&hold->base), + "a different hold gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EI_DEVICE_STATE_EMULATING) { + log_bug_client(ei_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) + return; + + if (!device->hold) + return; + + hold->base.state = EI_GESTURE_STATE_ENDED; + device->gesture_state.hold.current_hold = NULL; + + device->send_frame_event = true; + + int rc = ei_gesture_hold_request_end(device->hold, is_cancel); + if (rc) + ei_disconnect(ei); +} + +_public_ void +ei_hold_end(struct ei_hold *hold) +{ + ei_hold_end_cancel(hold, false); +} + +_public_ void +ei_hold_cancel(struct ei_hold *hold) +{ + ei_hold_end_cancel(hold, true); +} diff --git a/src/libei-gestures.h b/src/libei-gestures.h new file mode 100644 index 0000000..2efaf13 --- /dev/null +++ b/src/libei-gestures.h @@ -0,0 +1,123 @@ +/* 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. + */ + +#pragma once + +#include "util-list.h" +#include "util-object.h" + +#include "brei-shared.h" + +struct ei; +struct ei_device; + +/* This is the protocol-only object, not exposed in the API. See ei_swipe() for + * the C API object */ +struct ei_gesture_swipe { + struct object object; + struct brei_object proto_object; +}; + +OBJECT_DECLARE_GETTER(ei_gesture_swipe, context, struct ei *); +OBJECT_DECLARE_GETTER(ei_gesture_swipe, device, struct ei_device *); +OBJECT_DECLARE_GETTER(ei_gesture_swipe, proto_object, const struct brei_object *); +OBJECT_DECLARE_GETTER(ei_gesture_swipe, interface, const struct ei_gesture_swipe_interface *); +OBJECT_DECLARE_REF(ei_gesture_swipe); +OBJECT_DECLARE_UNREF(ei_gesture_swipe); + +struct ei_gesture_swipe * +ei_gesture_swipe_new(struct ei_device *device, object_id_t id, uint32_t version); + +/* This is the protocol-only object, not exposed in the API. See ei_pinch() for + * the C API object */ +struct ei_gesture_pinch { + struct object object; + struct brei_object proto_object; +}; + +OBJECT_DECLARE_GETTER(ei_gesture_pinch, context, struct ei *); +OBJECT_DECLARE_GETTER(ei_gesture_pinch, device, struct ei_device *); +OBJECT_DECLARE_GETTER(ei_gesture_pinch, proto_object, const struct brei_object *); +OBJECT_DECLARE_GETTER(ei_gesture_pinch, interface, const struct ei_gesture_pinch_interface *); +OBJECT_DECLARE_REF(ei_gesture_pinch); +OBJECT_DECLARE_UNREF(ei_gesture_pinch); + +struct ei_gesture_pinch * +ei_gesture_pinch_new(struct ei_device *device, object_id_t id, uint32_t version); + +/* This is the protocol-only object, not exposed in the API. See ei_hold() for + * the C API object */ +struct ei_gesture_hold { + struct object object; + struct brei_object proto_object; +}; + +OBJECT_DECLARE_GETTER(ei_gesture_hold, context, struct ei *); +OBJECT_DECLARE_GETTER(ei_gesture_hold, device, struct ei_device *); +OBJECT_DECLARE_GETTER(ei_gesture_hold, proto_object, const struct brei_object *); +OBJECT_DECLARE_GETTER(ei_gesture_hold, interface, const struct ei_gesture_hold_interface *); +OBJECT_DECLARE_REF(ei_gesture_hold); +OBJECT_DECLARE_UNREF(ei_gesture_hold); + +struct ei_gesture_hold * +ei_gesture_hold_new(struct ei_device *device, object_id_t id, uint32_t version); + +enum ei_gesture_state { EI_GESTURE_STATE_NEW, EI_GESTURE_STATE_BEGUN, EI_GESTURE_STATE_ENDED }; + +/* This is the parent object for all the C API gestures */ +struct ei_gesture { + struct object object; + struct ei_device *device; + enum ei_gesture_state state; + uint32_t nfingers; +}; + +OBJECT_DECLARE_REF(ei_gesture); +OBJECT_DECLARE_UNREF(ei_gesture); +OBJECT_DECLARE_GETTER(ei_gesture, context, struct ei *); +OBJECT_DECLARE_GETTER(ei_gesture, device, struct ei_device *); + +struct ei_swipe { + struct ei_gesture base; +}; + +struct ei_pinch { + struct ei_gesture base; +}; + +struct ei_hold { + struct ei_gesture base; +}; + +struct ei_swipe * +ei_swipe_new(struct ei_device *device, uint32_t nfingers); +OBJECT_DECLARE_GETTER(ei_swipe, context, struct ei *); + +struct ei_pinch * +ei_pinch_new(struct ei_device *device, uint32_t nfingers); +OBJECT_DECLARE_GETTER(ei_pinch, context, struct ei *); + +struct ei_hold * +ei_hold_new(struct ei_device *device, uint32_t nfingers); +OBJECT_DECLARE_GETTER(ei_hold, context, struct ei *); diff --git a/src/libei-handshake.c b/src/libei-handshake.c index 0bc6da2..c4bd80e 100644 --- a/src/libei-handshake.c +++ b/src/libei-handshake.c @@ -116,6 +116,15 @@ ei_handshake_initialize(struct ei_handshake *setup, uint32_t version) ei_handshake_request_interface_version(setup, EI_TOUCHSCREEN_INTERFACE_NAME, v->ei_touchscreen); + ei_handshake_request_interface_version(setup, + EI_GESTURE_SWIPE_INTERFACE_NAME, + v->ei_gesture_swipe); + ei_handshake_request_interface_version(setup, + EI_GESTURE_PINCH_INTERFACE_NAME, + v->ei_gesture_pinch); + ei_handshake_request_interface_version(setup, + EI_GESTURE_HOLD_INTERFACE_NAME, + v->ei_gesture_hold); ei_handshake_request_interface_version(setup, EI_TEXT_INTERFACE_NAME, v->ei_text); } diff --git a/src/libei-private.h b/src/libei-private.h index 141f3bc..29c9ae2 100644 --- a/src/libei-private.h +++ b/src/libei-private.h @@ -39,6 +39,7 @@ #include "libei-connection.h" #include "libei-device.h" #include "libei-event.h" +#include "libei-gestures.h" #include "libei-handshake.h" #include "libei-keyboard.h" #include "libei-pingpong.h" @@ -77,6 +78,9 @@ struct ei_interface_versions { uint32_t ei_button; uint32_t ei_keyboard; uint32_t ei_touchscreen; + uint32_t ei_gesture_swipe; + uint32_t ei_gesture_pinch; + uint32_t ei_gesture_hold; uint32_t ei_text; }; @@ -254,6 +258,43 @@ ei_queue_text_utf8_event(struct ei_device *device, const char *utf8); void ei_sync_event_send_done(struct ei_event *e); +void +ei_queue_swipe_begin_event(struct ei_device *device, uint32_t nfingers); + +void +ei_queue_swipe_update_event(struct ei_device *device, double dx, double dy); + +void +ei_queue_swipe_end_event(struct ei_device *device, bool is_cancelled); + +void +ei_queue_swipe_cancelled_event(struct ei_device *device); + +void +ei_queue_pinch_begin_event(struct ei_device *device, uint32_t nfingers); + +void +ei_queue_pinch_update_event(struct ei_device *device, + double dx, + double dy, + double scale, + double rotation); + +void +ei_queue_pinch_end_event(struct ei_device *device, bool is_cancelled); + +void +ei_queue_pinch_cancelled_event(struct ei_device *device); + +void +ei_queue_hold_begin_event(struct ei_device *device, uint32_t nfingers); + +void +ei_queue_hold_end_event(struct ei_device *device, bool is_cancelled); + +void +ei_queue_hold_cancelled_event(struct ei_device *device); + _printf_(6, 7) void ei_log_msg(struct ei *ei, enum ei_log_priority priority, diff --git a/src/libei-seat.c b/src/libei-seat.c index ebb6b89..0d888e1 100644 --- a/src/libei-seat.c +++ b/src/libei-seat.c @@ -251,6 +251,10 @@ ei_seat_has_capability(struct ei_seat *seat, enum ei_device_capability cap) return seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_BUTTON: return seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX] != 0; + case EI_DEVICE_CAP_GESTURES: + return seat->capabilities.map[EI_GESTURE_SWIPE_INTERFACE_INDEX] != 0 || + seat->capabilities.map[EI_GESTURE_PINCH_INTERFACE_INDEX] != 0 || + seat->capabilities.map[EI_GESTURE_HOLD_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_TEXT: return seat->capabilities.map[EI_TEXT_INTERFACE_INDEX] != 0; } @@ -287,6 +291,10 @@ ei_seat_cap_mask(struct ei_seat *seat, enum ei_device_capability cap) return seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX]; case EI_DEVICE_CAP_SCROLL: return seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX]; + case EI_DEVICE_CAP_GESTURES: + return seat->capabilities.map[EI_GESTURE_SWIPE_INTERFACE_INDEX] | + seat->capabilities.map[EI_GESTURE_PINCH_INTERFACE_INDEX] | + seat->capabilities.map[EI_GESTURE_HOLD_INTERFACE_INDEX]; case EI_DEVICE_CAP_TEXT: return seat->capabilities.map[EI_TEXT_INTERFACE_INDEX]; } diff --git a/src/libei.c b/src/libei.c index a6ec98a..17af78e 100644 --- a/src/libei.c +++ b/src/libei.c @@ -132,6 +132,9 @@ ei_create_context(bool is_sender, void *user_data) .ei_keyboard = VERSION_V(1), .ei_text = VERSION_V(1), .ei_touchscreen = VERSION_V(2), + .ei_gesture_swipe = VERSION_V(1), + .ei_gesture_pinch = VERSION_V(1), + .ei_gesture_hold = VERSION_V(1), }; /* This must be v1 until the server tells us otherwise */ ei->handshake = ei_handshake_new(ei, VERSION_V(1)); @@ -250,6 +253,17 @@ update_event_timestamp(struct ei_event *event, uint64_t time) case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_UP: case EI_EVENT_TOUCH_MOTION: + case EI_EVENT_SWIPE_BEGIN: + case EI_EVENT_SWIPE_UPDATE: + case EI_EVENT_SWIPE_END: + case EI_EVENT_SWIPE_CANCELLED: + case EI_EVENT_PINCH_BEGIN: + case EI_EVENT_PINCH_UPDATE: + case EI_EVENT_PINCH_END: + case EI_EVENT_PINCH_CANCELLED: + case EI_EVENT_HOLD_BEGIN: + case EI_EVENT_HOLD_END: + case EI_EVENT_HOLD_CANCELLED: case EI_EVENT_TEXT_KEYSYM: case EI_EVENT_TEXT_UTF8: if (event->timestamp != 0) { @@ -287,6 +301,17 @@ queue_event(struct ei *ei, struct ei_event *event) case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_UP: case EI_EVENT_TOUCH_MOTION: + case EI_EVENT_SWIPE_BEGIN: + case EI_EVENT_SWIPE_UPDATE: + case EI_EVENT_SWIPE_END: + case EI_EVENT_SWIPE_CANCELLED: + case EI_EVENT_PINCH_BEGIN: + case EI_EVENT_PINCH_UPDATE: + case EI_EVENT_PINCH_END: + case EI_EVENT_PINCH_CANCELLED: + case EI_EVENT_HOLD_BEGIN: + case EI_EVENT_HOLD_END: + case EI_EVENT_HOLD_CANCELLED: case EI_EVENT_TEXT_KEYSYM: case EI_EVENT_TEXT_UTF8: prefix = "pending "; @@ -630,6 +655,136 @@ ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid) queue_event(ei_device_get_context(device), e); } +void +ei_queue_swipe_begin_event(struct ei_device *device, uint32_t nfingers) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_SWIPE_BEGIN; + e->gestures.nfingers = nfingers; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_swipe_update_event(struct ei_device *device, double dx, double dy) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_SWIPE_UPDATE; + e->gestures.nfingers = device->gesture_state.swipe.nfingers; + e->gestures.dx = dx; + e->gestures.dy = dy; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_swipe_end_event(struct ei_device *device, bool is_cancelled) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_SWIPE_END; + e->gestures.nfingers = device->gesture_state.swipe.nfingers; + e->gestures.is_cancelled = is_cancelled; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_swipe_cancelled_event(struct ei_device *device) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_SWIPE_CANCELLED; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_pinch_begin_event(struct ei_device *device, uint32_t nfingers) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_PINCH_BEGIN; + e->gestures.nfingers = nfingers; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_pinch_update_event(struct ei_device *device, + double dx, + double dy, + double scale, + double rotation) +{ + struct ei_event *e = ei_event_new_for_device(device); + e->type = EI_EVENT_PINCH_UPDATE; + e->gestures.nfingers = device->gesture_state.pinch.nfingers; + e->gestures.scale = scale; + e->gestures.degrees = rotation; + e->gestures.dx = dx; + e->gestures.dy = dy; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_pinch_end_event(struct ei_device *device, bool is_cancelled) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_PINCH_END; + e->gestures.nfingers = device->gesture_state.pinch.nfingers; + e->gestures.is_cancelled = is_cancelled; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_pinch_cancelled_event(struct ei_device *device) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_PINCH_CANCELLED; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_hold_begin_event(struct ei_device *device, uint32_t nfingers) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_HOLD_BEGIN; + e->gestures.nfingers = nfingers; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_hold_end_event(struct ei_device *device, bool is_cancelled) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_HOLD_END; + e->gestures.nfingers = device->gesture_state.hold.nfingers; + e->gestures.is_cancelled = is_cancelled; + + queue_event(ei_device_get_context(device), e); +} + +void +ei_queue_hold_cancelled_event(struct ei_device *device) +{ + struct ei_event *e = ei_event_new_for_device(device); + + e->type = EI_EVENT_HOLD_CANCELLED; + + queue_event(ei_device_get_context(device), e); +} + void ei_queue_text_keysym_event(struct ei_device *device, uint32_t keysym, bool is_press) { diff --git a/src/libei.h b/src/libei.h index 6ffebc6..3588134 100644 --- a/src/libei.h +++ b/src/libei.h @@ -215,6 +215,39 @@ struct ei_region; */ struct ei_touch; +/** + * @struct ei_swipe + * + * A single swipe gesture. + * + * @see ei_device_swipe_new + * @see ei_swipe_begin + * @see ei_swipe_update + * @see ei_swipe_end + */ +struct ei_swipe; +/** + * @struct ei_pinch + * + * A single pinch gesture. + * + * @see ei_device_pinch_new + * @see ei_pinch_begin + * @see ei_pinch_update + * @see ei_pinch_end + */ +struct ei_pinch; +/** + * @struct ei_hold + * + * A single hold gesture. + * + * @see ei_device_hold_new + * @see ei_hold_begin + * @see ei_hold_end + */ +struct ei_hold; + /** * @struct ei_ping * @@ -297,12 +330,24 @@ enum ei_device_capability { * The device can send button events */ EI_DEVICE_CAP_BUTTON = (1 << 5), + /** + * The device can send gesture events. + * + * The ei protocol separates available gestures + * into swipe, pinch and hold and a device may support + * any number of those independently. For simplicity, + * the libei C API only provides one capability that + * is set if the device supports any of those gestures. + * + * @since 1.6 + */ + EI_DEVICE_CAP_GESTURES = (1 << 6), /** * The device can send text-like data * * @since 1.6 */ - EI_DEVICE_CAP_TEXT = (1 << 6), + EI_DEVICE_CAP_TEXT = (1 << 7), }; /** @@ -406,6 +451,7 @@ enum ei_event_type { * - any buttons or keys logically down are released * - any modifiers logically down are released * - any touches logically down are released + * - any gestures logically active are ended * * Sender clients must wait until @ref EI_EVENT_DEVICE_RESUMED * before sending events. @@ -606,6 +652,127 @@ enum ei_event_type { */ EI_EVENT_TOUCH_MOTION, + /** + * Event for a swipe begin. Only one swipe gesture may be active + * at any time. + * + * @note This event is only generated on a receiver ei context. + * See ei_device_swipe_new() and ei_swipe_begin() for the sender context API. + * + * @since 1.6 + */ + EI_EVENT_SWIPE_BEGIN = 900, + /** + * Event for an update to the current ongoing swipe gesture. + * + * This library accumulates all updates to the gesture within the same + * device frame into one gesture event. + * + * @note This event is only generated on a receiver ei context. + * See ei_device_swipe_new() and ei_swipe_update() for the sender context API. + * + * @since 1.6 + */ + EI_EVENT_SWIPE_UPDATE, + /** + * Event for the logical end to the current ongoing swipe gesture. + * + * @note This event is only generated on a receiver ei context. + * See ei_device_swipe_new() and ei_swipe_end() for the sender context API. + * + * @since 1.6 + */ + EI_EVENT_SWIPE_END, + /** + * Event for an EIS-initiated cancellation of a gesture. + * + * If this event is received the current on going gesture should be + * released without further updates or sending an end event (see + * ei_swipe_unref()). + * + * @note This event is only generated on a sender ei context. + * + * @since 1.6 + */ + EI_EVENT_SWIPE_CANCELLED, + + /** + * Event for a pinch begin. Only one pinch gesture may be active + * at any time. + * + * @note This event is only generated on a receiver ei context. + * See ei_device_pinch_new() and ei_pinch_begin() for the sender context API. + * + * @since 1.6 + */ + EI_EVENT_PINCH_BEGIN = 910, + /** + * Event for an update to the current ongoing pinch gesture. + * + * This library accumulates all updates to the gesture within the same + * device frame into one gesture event, e.g. where the gestures moves + * and rotates only one update event is generated. + * + * @note This event is only generated on a receiver ei context. + * See ei_device_pinch_new() and ei_pinch_update() for the sender context API. + * + * @since 1.6 + */ + EI_EVENT_PINCH_UPDATE, + /** + * Event for the logical end to the current ongoing pinch gesture. + * + * @note This event is only generated on a receiver ei context. + * See ei_device_pinch_new() and ei_pinch_end() for the sender context API. + * + * @since 1.6 + */ + EI_EVENT_PINCH_END, + /** + * Event for an EIS-initiated cancellation of a gesture. + * + * If this event is received the current on going gesture should be + * released without further updates or sending an end event (see + * ei_pinch_unref()). + * + * @note This event is only generated on a sender ei context. + * + * @since 1.6 + */ + EI_EVENT_PINCH_CANCELLED, + + /** + * Event for a hold begin. Only one hold gesture may be active + * at any time. + * + * @note This event is only generated on a receiver ei context. + * See ei_device_hold_new() and ei_hold_begin() for the sender context API. + * + * @since 1.6 + */ + EI_EVENT_HOLD_BEGIN = 920, + /** + * Event for the logical end to the current ongoing hold gesture. + * + * @note This event is only generated on a receiver ei context. + * See ei_device_hold_new() and ei_hold_end() for the sender context API. + * + * @since 1.6 + */ + EI_EVENT_HOLD_END, + /** + * Event for an EIS-initiated cancellation of a gesture. + * + * If this event is received the current on going gesture should be + * released without further updates or sending an end event (see + * ei_hold_unref()). + * + * @note This event is only generated on a sender ei context. + * + * @since 1.6 + */ + EI_EVENT_HOLD_CANCELLED, + /** * Event for a single keysym logically pressed/released on this device. * The keysym is an XKB-compatible keysym (not key code!) and may not be @@ -616,7 +783,7 @@ enum ei_event_type { * * @since 1.6 */ - EI_EVENT_TEXT_KEYSYM = 900, + EI_EVENT_TEXT_KEYSYM = 930, /** * Event for a UTF-8 compatible text sequence sent by this device. @@ -2079,6 +2246,297 @@ ei_touch_get_user_data(struct ei_touch *touch); struct ei_device * ei_touch_get_device(struct ei_touch *touch); +/** + * @ingroup libei-sender + * + * Initiate a new swipe gesture on a device with the @ref EI_DEVICE_CAP_GESTURES + * capability. This gesture does not immediately send events, use + * ei_swipe_begin(), ei_swipe_update() and ei_swipe_end(). + * + * The returned gesture has a refcount of at least 1, use ei_swipe_unref() to + * release resources associated with this gesture + * + * This method is only available on an ei sender context. + * + * If the underlying device does not support swipe gestures, this function + * returns NULL. + * + * @param device A device with @ref EI_DEVICE_CAP_GESTURES capability + * @param nfingers The number of fingers, must be greater than 0 + * @return A new swipe gesture or NULL on error + * + * @since 1.6 + */ +struct ei_swipe * +ei_device_swipe_new(struct ei_device *device, uint32_t nfingers); + +/** + * @ingroup libei-sender + * + * Starts the swipe gesture. + * + * This function can only be called once on an ei_swipe object. Further + * calls to ei_swipe_begin() on the same object are silently ignored. + * + * @param swipe A newly created swipe gesture + * + * @since 1.6 + */ +void +ei_swipe_begin(struct ei_swipe *swipe); + +/** + * @ingroup libei-sender + * + * Update the swipe gestures's position by the given relative delta. + * + * @since 1.6 + */ +void +ei_swipe_update(struct ei_swipe *swipe, double x, double y); + +/** + * @ingroup libei-sender + * + * Release this swipe. After this call, the swipe event becomes inert and + * no longer responds to either ei_swipe_begin(), + * ei_swipe_update(), ei_swipe_cancel() or + * ei_swipe_end() and the caller should call ei_swipe_unref(). + * + * @since 1.6 + */ +void +ei_swipe_end(struct ei_swipe *swipe); + +/** + * @ingroup libei-sender + * + * Cancel this swipe. After this call, the swipe event becomes inert and + * no longer responds to either ei_swipe_begin(), + * ei_swipe_update(), ei_swipe_cancel() or + * ei_swipe_end() and the caller should call ei_swipe_unref(). + * + * @since 1.6 + */ +void +ei_swipe_cancel(struct ei_swipe *swipe); + +/** + * @ingroup libei-sender + * + * Increase the refcount of this struct by one. Use ei_swipe_unref() to + * decrease the refcount. + * + * @return the argument passed into the function + * + * @since 1.6 + */ +struct ei_swipe * +ei_swipe_ref(struct ei_swipe *swipe); + +/** + * @ingroup libei-sender + * + * Decrease the refcount of this struct by one. When the refcount reaches + * zero, all allocated resources for this struct are released. + * + * @return always NULL + * + * @since 1.6 + */ +struct ei_swipe * +ei_swipe_unref(struct ei_swipe *swipe); + +/** + * @ingroup libei-sender + * + * Initiate a new pinch gesture on a device with the @ref EI_DEVICE_CAP_GESTURES + * capability. This gesture does not immediately send events, use + * ei_pinch_begin(), ei_pinch_update() and ei_pinch_end(). + * + * The returned gesture has a refcount of at least 1, use ei_pinch_unref() to + * release resources associated with this gesture + * + * This method is only available on an ei sender context. + * + * If the underlying device does not support pinch gestures, this function + * returns NULL. + * + * @param device A device with @ref EI_DEVICE_CAP_GESTURES capability + * @param nfingers The number of fingers, must be at least 2 + * @return A new pinch gesture or NULL on error + * + * @since 1.6 + */ +struct ei_pinch * +ei_device_pinch_new(struct ei_device *device, uint32_t nfingers); + +/** + * @ingroup libei-sender + * + * Starts the pinch gesture. + * + * This function can only be called once on an ei_pinch object. Further + * calls to ei_pinch_begin() on the same object are silently ignored. + * + * @param pinch A newly created pinch gesture + * + * @since 1.6 + */ +void +ei_pinch_begin(struct ei_pinch *pinch); + +/** + * @ingroup libei-sender + * + * Update the pinch gestures's position by the given relative delta. + * + * @since 1.6 + */ +void +ei_pinch_update(struct ei_pinch *pinch, double x, double y, double scale, double rotation); + +/** + * @ingroup libei-sender + * + * Release this pinch. After this call, the pinch event becomes inert and + * no longer responds to either ei_pinch_begin(), + * ei_pinch_update(), ei_pinch_cancel() or + * ei_pinch_end() and the caller should call ei_pinch_unref(). + * + * @since 1.6 + */ +void +ei_pinch_end(struct ei_pinch *pinch); + +/** + * @ingroup libei-sender + * + * Cancel this pinch. After this call, the pinch event becomes inert and + * no longer responds to either ei_pinch_begin(), + * ei_pinch_update(), ei_pinch_cancel() or + * ei_pinch_end() and the caller should call ei_pinch_unref(). + * + * @since 1.6 + */ +void +ei_pinch_cancel(struct ei_pinch *pinch); + +/** + * @ingroup libei-sender + * + * Increase the refcount of this struct by one. Use ei_pinch_unref() to + * decrease the refcount. + * + * @return the argument passed into the function + * + * @since 1.6 + */ +struct ei_pinch * +ei_pinch_ref(struct ei_pinch *pinch); + +/** + * @ingroup libei-sender + * + * Decrease the refcount of this struct by one. When the refcount reaches + * zero, all allocated resources for this struct are released. + * + * @return always NULL + * + * @since 1.6 + */ +struct ei_pinch * +ei_pinch_unref(struct ei_pinch *pinch); + +/** + * @ingroup libei-sender + * + * Initiate a new hold gesture on a device with the @ref EI_DEVICE_CAP_GESTURES + * capability. This gesture does not immediately send events, use + * ei_hold_begin() and ei_hold_end(). + * + * The returned gesture has a refcount of at least 1, use ei_hold_unref() to + * release resources associated with this gesture + * + * This method is only available on an ei sender context. + * + * If the underlying device does not support hold gestures, this function + * returns NULL. + * + * @param device A device with @ref EI_DEVICE_CAP_GESTURES capability + * @param nfingers The number of fingers, must be greater than 0 + * @return A new hold gesture or NULL on error + * + * @since 1.6 + */ +struct ei_hold * +ei_device_hold_new(struct ei_device *device, uint32_t nfingers); + +/** + * @ingroup libei-sender + * + * Starts the hold gesture. + * + * This function can only be called once on an ei_hold object. Further + * calls to ei_hold_begin() on the same object are silently ignored. + * + * @param hold A newly created hold gesture + * + * @since 1.6 + */ +void +ei_hold_begin(struct ei_hold *hold); + +/** + * @ingroup libei-sender + * + * Release this hold. After this call, the hold event becomes inert and + * no longer responds to either ei_hold_begin(), ei_hold_cancel() or + * ei_hold_end() and the caller should call ei_hold_unref(). + * + * @since 1.6 + */ +void +ei_hold_end(struct ei_hold *hold); + +/** + * @ingroup libei-sender + * + * Cancel this hold. After this call, the hold event becomes inert and + * no longer responds to either ei_hold_begin(), ei_hold_cancel() or + * ei_hold_end() and the caller should call ei_hold_unref(). + * + * @since 1.6 + */ +void +ei_hold_cancel(struct ei_hold *hold); + +/** + * @ingroup libei-sender + * + * Increase the refcount of this struct by one. Use ei_hold_unref() to + * decrease the refcount. + * + * @return the argument passed into the function + * + * @since 1.6 + */ +struct ei_hold * +ei_hold_ref(struct ei_hold *hold); + +/** + * @ingroup libei-sender + * + * Decrease the refcount of this struct by one. When the refcount reaches + * zero, all allocated resources for this struct are released. + * + * @return always NULL + * + * @since 1.6 + */ +struct ei_hold * +ei_hold_unref(struct ei_hold *hold); + /** * Return the seat from this event. * @@ -2341,6 +2799,145 @@ ei_event_touch_get_y(struct ei_event *event); bool ei_event_touch_get_is_cancel(struct ei_event *event); +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_SWIPE_BEGIN, + * @ref EI_EVENT_SWIPE_UPDATE or @ref EI_EVENT_SWIPE_END + * return the finger count for this gesture. + * + * @since 1.6 + */ +uint32_t +ei_event_swipe_get_finger_count(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_SWIPE_UPDATE + * return the relative x movement of the logical center + * of the gesture in logical pixels or mm, depending on the device type. + * + * @since 1.6 + */ +double +ei_event_swipe_get_dx(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_SWIPE_UPDATE + * return the relative y movement of the logical center + * of the gesture in logical pixels or mm, depending on the device type. + * + * @since 1.6 + */ +double +ei_event_swipe_get_dy(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_SWIPE_END + * return true if the gesture was cancelled, false otherwise. + * + * @since 1.6 + */ +bool +ei_event_swipe_get_is_cancelled(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_PINCH_BEGIN, + * @ref EI_EVENT_PINCH_UPDATE or @ref EI_EVENT_PINCH_END + * return the finger count for this gesture. + * + * @since 1.6 + */ +uint32_t +ei_event_pinch_get_finger_count(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_PINCH_UPDATE + * return the relative x movement of the logical center + * of the gesture in logical pixels or mm, depending on the device type. + * + * @since 1.6 + */ +double +ei_event_pinch_get_dx(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_PINCH_UPDATE + * return the relative y movement of the logical center + * of the gesture in logical pixels or mm, depending on the device type. + * + * @since 1.6 + */ +double +ei_event_pinch_get_dy(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_PINCH_UPDATE + * return the relative angle of rotation in degrees clockwise. + * + * @since 1.6 + */ +double +ei_event_pinch_get_rotation(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_PINCH_UPDATE + * return the normalized scale of the gesture. + * + * @since 1.6 + */ +double +ei_event_pinch_get_scale(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_PINCH_END + * return true if the gesture was cancelled, false otherwise. + * + * @since 1.6 + */ +bool +ei_event_pinch_get_is_cancelled(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_HOLD_BEGIN + * or @ref EI_EVENT_HOLD_END + * return the finger count for this gesture. + * + * @since 1.6 + */ +uint32_t +ei_event_hold_get_finger_count(struct ei_event *event); + +/** + * @ingroup libei-receiver + * + * For an event of type @ref EI_EVENT_HOLD_END + * return true if the gesture was cancelled, false otherwise. + * + * @since 1.6 + */ +bool +ei_event_hold_get_is_cancelled(struct ei_event *event); + /** * @ingroup libei-receiver * diff --git a/src/libeis-client.c b/src/libeis-client.c index 518cc85..16e518a 100644 --- a/src/libeis-client.c +++ b/src/libeis-client.c @@ -539,6 +539,9 @@ eis_client_new(struct eis *eis, int fd) .ei_keyboard = VERSION_V(1), .ei_text = VERSION_V(1), .ei_touchscreen = VERSION_V(2), + .ei_gesture_swipe = VERSION_V(1), + .ei_gesture_pinch = VERSION_V(1), + .ei_gesture_hold = VERSION_V(1), }; struct source *s = source_new(fd, client_dispatch, client); int rc = sink_add_source(eis->sink, s); diff --git a/src/libeis-client.h b/src/libeis-client.h index 1a63581..e3002f7 100644 --- a/src/libeis-client.h +++ b/src/libeis-client.h @@ -52,6 +52,9 @@ struct eis_client_interface_versions { uint32_t ei_button; uint32_t ei_keyboard; uint32_t ei_touchscreen; + uint32_t ei_gesture_swipe; + uint32_t ei_gesture_pinch; + uint32_t ei_gesture_hold; uint32_t ei_text; }; diff --git a/src/libeis-device.c b/src/libeis-device.c index 6335f02..86b14e8 100644 --- a/src/libeis-device.c +++ b/src/libeis-device.c @@ -33,6 +33,7 @@ #include "util-time.h" #include "eis-proto.h" +#include "libeis-gestures.h" #include "libeis-private.h" static_assert((int)EIS_DEVICE_TYPE_VIRTUAL == EIS_DEVICE_DEVICE_TYPE_VIRTUAL, "ABI mismatch"); @@ -147,6 +148,9 @@ eis_device_destroy(struct eis_device *device) eis_pointer_unref(device->pointer); eis_touchscreen_unref(device->touchscreen); eis_keyboard_unref(device->keyboard); + eis_gesture_swipe_unref(device->swipe); + eis_gesture_pinch_unref(device->pinch); + eis_gesture_hold_unref(device->hold); eis_text_unref(device->text); free(device->name); @@ -812,6 +816,309 @@ eis_device_get_touchscreen_interface(struct eis_device *device) return &touchscreen_interface; } +static struct brei_result * +client_msg_gesture_swipe_release(struct eis_gesture_swipe *swipe) +{ + struct eis_device *device = eis_gesture_swipe_get_device(swipe); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "swipe release event for non-gesture device"); + } + + if (device->swipe) { + eis_gesture_swipe_event_destroyed( + device->swipe, + eis_client_get_next_serial(eis_device_get_client(device))); + eis_gesture_swipe_unref(steal(&device->swipe)); + } + + return 0; +} + +static struct brei_result * +client_msg_gesture_swipe_begin(struct eis_gesture_swipe *swipe, uint32_t nfingers) +{ + struct eis_device *device = eis_gesture_swipe_get_device(swipe); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe begin event for non-gesture device"); + } + + if (nfingers == 0) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe begin event with zero fingers"); + } + + if (device->state == EIS_DEVICE_STATE_EMULATING) { + if (device->gesture_state.swipe.is_active) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe begin event while swipe is active"); + } + device->gesture_state.swipe.is_active = true; + device->gesture_state.swipe.nfingers = nfingers; + eis_queue_swipe_begin_event(device, nfingers); + return NULL; + } + + return maybe_error_on_device_state(device, "swipe begin"); +} + +static struct brei_result * +client_msg_gesture_swipe_update(struct eis_gesture_swipe *swipe, float dx, float dy) +{ + struct eis_device *device = eis_gesture_swipe_get_device(swipe); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe update event for non-gesture device"); + } + + if (device->gesture_state.swipe.is_active) { + if (device->state == EIS_DEVICE_STATE_EMULATING) + eis_queue_swipe_update_event(device, dx, dy); + return NULL; + } + + return maybe_error_on_device_state(device, "swipe update"); +} + +static struct brei_result * +client_msg_gesture_swipe_end(struct eis_gesture_swipe *swipe, uint32_t is_cancelled) +{ + struct eis_device *device = eis_gesture_swipe_get_device(swipe); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Swipe end event for non-gesture device"); + } + + if (device->gesture_state.swipe.is_active) { + if (device->state == EIS_DEVICE_STATE_EMULATING) + eis_queue_swipe_end_event(device, !!is_cancelled); + device->gesture_state.swipe.is_active = false; + return NULL; + } + + return maybe_error_on_device_state(device, "swipe end"); +} + +static const struct eis_gesture_swipe_interface swipe_interface = { + .release = client_msg_gesture_swipe_release, + .begin = client_msg_gesture_swipe_begin, + .update = client_msg_gesture_swipe_update, + .end = client_msg_gesture_swipe_end, +}; + +const struct eis_gesture_swipe_interface * +eis_device_get_gesture_swipe_interface(struct eis_device *device) +{ + return &swipe_interface; +} + +static struct brei_result * +client_msg_gesture_pinch_release(struct eis_gesture_pinch *pinch) +{ + struct eis_device *device = eis_gesture_pinch_get_device(pinch); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch release event for non-gesture device"); + } + + if (device->pinch) { + eis_gesture_pinch_event_destroyed( + device->pinch, + eis_client_get_next_serial(eis_device_get_client(device))); + eis_gesture_pinch_unref(steal(&device->pinch)); + } + + return 0; +} + +static struct brei_result * +client_msg_gesture_pinch_begin(struct eis_gesture_pinch *pinch, uint32_t nfingers) +{ + struct eis_device *device = eis_gesture_pinch_get_device(pinch); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch begin event for non-gesture device"); + } + + if (nfingers < 2) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch begin event with fewer than 2 fingers"); + } + + if (device->state == EIS_DEVICE_STATE_EMULATING) { + if (device->gesture_state.pinch.is_active) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch begin event while pinch is active"); + } + device->gesture_state.pinch.is_active = true; + device->gesture_state.pinch.nfingers = nfingers; + eis_queue_pinch_begin_event(device, nfingers); + return NULL; + } + + return maybe_error_on_device_state(device, "pinch begin"); +} + +static struct brei_result * +client_msg_gesture_pinch_update(struct eis_gesture_pinch *pinch, + float dx, + float dy, + float scale, + float rotation) +{ + struct eis_device *device = eis_gesture_pinch_get_device(pinch); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch update event for non-gesture device"); + } + + if (device->gesture_state.pinch.is_active) { + if (device->state == EIS_DEVICE_STATE_EMULATING) + eis_queue_pinch_update_event(device, dx, dy, scale, rotation); + return NULL; + } + + return maybe_error_on_device_state(device, "pinch update"); +} + +static struct brei_result * +client_msg_gesture_pinch_end(struct eis_gesture_pinch *pinch, uint32_t is_cancelled) +{ + struct eis_device *device = eis_gesture_pinch_get_device(pinch); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Pinch end event for non-gesture device"); + } + + if (device->gesture_state.pinch.is_active) { + if (device->state == EIS_DEVICE_STATE_EMULATING) + eis_queue_pinch_end_event(device, !!is_cancelled); + device->gesture_state.pinch.is_active = false; + return NULL; + } + + return maybe_error_on_device_state(device, "pinch end"); +} + +static const struct eis_gesture_pinch_interface pinch_interface = { + .release = client_msg_gesture_pinch_release, + .begin = client_msg_gesture_pinch_begin, + .update = client_msg_gesture_pinch_update, + .end = client_msg_gesture_pinch_end, +}; + +const struct eis_gesture_pinch_interface * +eis_device_get_gesture_pinch_interface(struct eis_device *device) +{ + return &pinch_interface; +} + +static struct brei_result * +client_msg_gesture_hold_release(struct eis_gesture_hold *hold) +{ + struct eis_device *device = eis_gesture_hold_get_device(hold); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "hold release event for non-gesture device"); + } + + if (device->hold) { + eis_gesture_hold_event_destroyed( + device->hold, + eis_client_get_next_serial(eis_device_get_client(device))); + eis_gesture_hold_unref(steal(&device->hold)); + } + + return 0; +} + +static struct brei_result * +client_msg_gesture_hold_begin(struct eis_gesture_hold *hold, uint32_t nfingers) +{ + struct eis_device *device = eis_gesture_hold_get_device(hold); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold begin event for non-gesture device"); + } + + if (nfingers == 0) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold begin event with zero fingers"); + } + + if (device->state == EIS_DEVICE_STATE_EMULATING) { + if (device->gesture_state.hold.is_active) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold begin event while hold is active"); + } + device->gesture_state.hold.is_active = true; + device->gesture_state.hold.nfingers = nfingers; + eis_queue_hold_begin_event(device, nfingers); + return NULL; + } + + return maybe_error_on_device_state(device, "hold begin"); +} + +static struct brei_result * +client_msg_gesture_hold_end(struct eis_gesture_hold *hold, uint32_t is_cancelled) +{ + struct eis_device *device = eis_gesture_hold_get_device(hold); + + DISCONNECT_IF_RECEIVER_CONTEXT(device); + + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, + "Hold end event for non-gesture device"); + } + + if (device->gesture_state.hold.is_active) { + if (device->state == EIS_DEVICE_STATE_EMULATING) + eis_queue_hold_end_event(device, !!is_cancelled); + device->gesture_state.hold.is_active = false; + return NULL; + } + + return maybe_error_on_device_state(device, "hold end"); +} + +static const struct eis_gesture_hold_interface hold_interface = { + .release = client_msg_gesture_hold_release, + .begin = client_msg_gesture_hold_begin, + .end = client_msg_gesture_hold_end, +}; + +const struct eis_gesture_hold_interface * +eis_device_get_gesture_hold_interface(struct eis_device *device) +{ + return &hold_interface; +} + static struct brei_result * client_msg_text_release(struct eis_text *text) { @@ -958,6 +1265,7 @@ eis_device_configure_capability(struct eis_device *device, enum eis_device_capab case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: + case EIS_DEVICE_CAP_GESTURES: case EIS_DEVICE_CAP_TEXT: mask_add(device->capabilities, cap); break; @@ -1107,6 +1415,31 @@ eis_device_add(struct eis_device *device) if (rc < 0) goto out; } + if (eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + device->swipe = eis_gesture_swipe_new(device); + rc = eis_device_event_interface(device, + eis_gesture_swipe_get_id(device->swipe), + EIS_GESTURE_SWIPE_INTERFACE_NAME, + eis_gesture_swipe_get_version(device->swipe)); + if (rc < 0) + goto out; + + device->pinch = eis_gesture_pinch_new(device); + rc = eis_device_event_interface(device, + eis_gesture_pinch_get_id(device->pinch), + EIS_GESTURE_PINCH_INTERFACE_NAME, + eis_gesture_pinch_get_version(device->pinch)); + if (rc < 0) + goto out; + + device->hold = eis_gesture_hold_new(device); + rc = eis_device_event_interface(device, + eis_gesture_hold_get_id(device->hold), + EIS_GESTURE_HOLD_INTERFACE_NAME, + eis_gesture_hold_get_version(device->hold)); + if (rc < 0) + goto out; + } if (eis_device_has_capability(device, EIS_DEVICE_CAP_TEXT)) { device->text = eis_text_new(device); rc = eis_device_event_interface(device, @@ -1175,6 +1508,20 @@ eis_device_remove(struct eis_device *device) eis_keyboard_event_destroyed(device->keyboard, eis_client_get_next_serial(client)); eis_keyboard_unref(steal(&device->keyboard)); } + if (device->swipe) { + eis_gesture_swipe_event_destroyed(device->swipe, + eis_client_get_next_serial(client)); + eis_gesture_swipe_unref(steal(&device->swipe)); + } + if (device->pinch) { + eis_gesture_pinch_event_destroyed(device->pinch, + eis_client_get_next_serial(client)); + eis_gesture_pinch_unref(steal(&device->pinch)); + } + if (device->hold) { + eis_gesture_hold_event_destroyed(device->hold, eis_client_get_next_serial(client)); + eis_gesture_hold_unref(steal(&device->hold)); + } if (device->text) { eis_text_event_destroyed(device->text, eis_client_get_next_serial(client)); eis_text_unref(steal(&device->text)); @@ -1205,6 +1552,7 @@ eis_device_has_capability(struct eis_device *device, enum eis_device_capability case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: + case EIS_DEVICE_CAP_GESTURES: case EIS_DEVICE_CAP_TEXT: return mask_all(device->capabilities, cap); } @@ -1721,6 +2069,9 @@ eis_device_pause(struct eis_device *device) for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) { device->touch_state.down[i] = UINT64_MAX; } + device->gesture_state.swipe.is_active = false; + device->gesture_state.pinch.is_active = false; + device->gesture_state.hold.is_active = false; } _public_ void @@ -1762,3 +2113,90 @@ eis_device_keyboard_send_xkb_modifiers(struct eis_device *device, latched, group); } + +_public_ void +eis_device_swipe_cancel(struct eis_device *device) +{ + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + log_bug_client(eis_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return; + } + + if (!device->swipe) + return; + + if (!device->gesture_state.swipe.is_active) + return; + + device->gesture_state.swipe.is_active = false; + struct eis_client *client = eis_device_get_client(device); + eis_gesture_swipe_event_cancelled(device->swipe, eis_client_get_next_serial(client)); +} + +_public_ void +eis_device_pinch_cancel(struct eis_device *device) +{ + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + log_bug_client(eis_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return; + } + + if (!device->pinch) + return; + + if (!device->gesture_state.pinch.is_active) + return; + + device->gesture_state.pinch.is_active = false; + struct eis_client *client = eis_device_get_client(device); + eis_gesture_pinch_event_cancelled(device->pinch, eis_client_get_next_serial(client)); +} + +_public_ void +eis_device_hold_cancel(struct eis_device *device) +{ + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + log_bug_client(eis_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return; + } + + if (!device->hold) + return; + + if (!device->gesture_state.hold.is_active) + return; + + device->gesture_state.hold.is_active = false; + struct eis_client *client = eis_device_get_client(device); + eis_gesture_hold_event_cancelled(device->hold, eis_client_get_next_serial(client)); +} + +_public_ struct eis_swipe * +eis_device_swipe_new(struct eis_device *device, uint32_t nfingers) +{ + struct eis_swipe *swipe = eis_swipe_new(device, nfingers); + + return swipe; +} + +_public_ struct eis_pinch * +eis_device_pinch_new(struct eis_device *device, uint32_t nfingers) +{ + struct eis_pinch *pinch = eis_pinch_new(device, nfingers); + + return pinch; +} + +_public_ struct eis_hold * +eis_device_hold_new(struct eis_device *device, uint32_t nfingers) +{ + struct eis_hold *hold = eis_hold_new(device, nfingers); + + return hold; +} diff --git a/src/libeis-device.h b/src/libeis-device.h index 1de5c54..cc1a603 100644 --- a/src/libeis-device.h +++ b/src/libeis-device.h @@ -58,6 +58,9 @@ struct eis_device { struct eis_button *button; struct eis_keyboard *keyboard; struct eis_touchscreen *touchscreen; + struct eis_gesture_swipe *swipe; + struct eis_gesture_pinch *pinch; + struct eis_gesture_hold *hold; struct eis_text *text; char *name; @@ -89,6 +92,33 @@ struct eis_device { struct { uint64_t down[EIS_MAX_TOUCHES]; /* touch id */ } touch_state; + + struct { + struct { + /* receiver */ + bool is_active; + uint32_t nfingers; + + /* sender */ + struct eis_swipe *current_swipe; /* no ref! */ + } swipe; + struct { + /* receiver */ + bool is_active; + uint32_t nfingers; + + /* sender */ + struct eis_pinch *current_pinch; /* no ref! */ + } pinch; + struct { + /* receiver */ + bool is_active; + uint32_t nfingers; + + /* sender */ + struct eis_hold *current_hold; /* no ref! */ + } hold; + } gesture_state; }; struct eis_touch { @@ -133,6 +163,15 @@ OBJECT_DECLARE_GETTER(eis_device, scroll_interface, const struct eis_scroll_inte OBJECT_DECLARE_GETTER(eis_device, button_interface, const struct eis_button_interface *); OBJECT_DECLARE_GETTER(eis_device, keyboard_interface, const struct eis_keyboard_interface *); OBJECT_DECLARE_GETTER(eis_device, touchscreen_interface, const struct eis_touchscreen_interface *); +OBJECT_DECLARE_GETTER(eis_device, + gesture_swipe_interface, + const struct eis_gesture_swipe_interface *); +OBJECT_DECLARE_GETTER(eis_device, + gesture_pinch_interface, + const struct eis_gesture_pinch_interface *); +OBJECT_DECLARE_GETTER(eis_device, + gesture_hold_interface, + const struct eis_gesture_hold_interface *); OBJECT_DECLARE_GETTER(eis_device, text_interface, const struct eis_text_interface *); static inline bool diff --git a/src/libeis-event.c b/src/libeis-event.c index 4f7f18a..c7732dd 100644 --- a/src/libeis-event.c +++ b/src/libeis-event.c @@ -58,6 +58,14 @@ eis_event_destroy(struct eis_event *event) case EIS_EVENT_TOUCH_MOTION: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_FRAME: + case EIS_EVENT_SWIPE_BEGIN: + case EIS_EVENT_SWIPE_UPDATE: + case EIS_EVENT_SWIPE_END: + case EIS_EVENT_PINCH_BEGIN: + case EIS_EVENT_PINCH_UPDATE: + case EIS_EVENT_PINCH_END: + case EIS_EVENT_HOLD_BEGIN: + case EIS_EVENT_HOLD_END: case EIS_EVENT_TEXT_KEYSYM: handled = true; break; @@ -220,6 +228,7 @@ eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capabilit case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: + case EIS_DEVICE_CAP_GESTURES: case EIS_DEVICE_CAP_TEXT: return mask_all(event->bind.capabilities, cap); } @@ -436,6 +445,110 @@ eis_event_touch_get_is_cancel(struct eis_event *event) return event->touch.is_cancel; } +_public_ uint32_t +eis_event_swipe_get_finger_count(struct eis_event *event) +{ + require_event_type(event, + 0, + EIS_EVENT_SWIPE_BEGIN, + EIS_EVENT_SWIPE_UPDATE, + EIS_EVENT_SWIPE_END); + + return event->gestures.nfingers; +} + +_public_ double +eis_event_swipe_get_dx(struct eis_event *event) +{ + require_event_type(event, 0.0, EIS_EVENT_SWIPE_UPDATE); + + return event->gestures.dx; +} + +_public_ double +eis_event_swipe_get_dy(struct eis_event *event) +{ + require_event_type(event, 0.0, EIS_EVENT_SWIPE_UPDATE); + + return event->gestures.dy; +} + +_public_ bool +eis_event_swipe_get_is_cancelled(struct eis_event *event) +{ + require_event_type(event, false, EIS_EVENT_SWIPE_END); + + return event->gestures.is_cancelled; +} + +_public_ uint32_t +eis_event_pinch_get_finger_count(struct eis_event *event) +{ + require_event_type(event, + 0, + EIS_EVENT_PINCH_BEGIN, + EIS_EVENT_PINCH_UPDATE, + EIS_EVENT_PINCH_END); + + return event->gestures.nfingers; +} + +_public_ double +eis_event_pinch_get_dx(struct eis_event *event) +{ + require_event_type(event, 0.0, EIS_EVENT_PINCH_UPDATE); + + return event->gestures.dx; +} + +_public_ double +eis_event_pinch_get_dy(struct eis_event *event) +{ + require_event_type(event, 0.0, EIS_EVENT_PINCH_UPDATE); + + return event->gestures.dy; +} + +_public_ double +eis_event_pinch_get_rotation(struct eis_event *event) +{ + require_event_type(event, 0.0, EIS_EVENT_PINCH_UPDATE); + + return event->gestures.degrees; +} + +_public_ double +eis_event_pinch_get_scale(struct eis_event *event) +{ + require_event_type(event, 0.0, EIS_EVENT_PINCH_UPDATE); + + return event->gestures.scale; +} + +_public_ bool +eis_event_pinch_get_is_cancelled(struct eis_event *event) +{ + require_event_type(event, false, EIS_EVENT_PINCH_END); + + return event->gestures.is_cancelled; +} + +_public_ uint32_t +eis_event_hold_get_finger_count(struct eis_event *event) +{ + require_event_type(event, 0, EIS_EVENT_HOLD_BEGIN, EIS_EVENT_HOLD_END); + + return event->gestures.nfingers; +} + +_public_ bool +eis_event_hold_get_is_cancelled(struct eis_event *event) +{ + require_event_type(event, false, EIS_EVENT_HOLD_END); + + return event->gestures.is_cancelled; +} + _public_ uint32_t eis_event_text_get_keysym(struct eis_event *event) { diff --git a/src/libeis-event.h b/src/libeis-event.h index 04c347c..b346833 100644 --- a/src/libeis-event.h +++ b/src/libeis-event.h @@ -75,6 +75,13 @@ struct eis_event { struct { struct eis_callback *callback; } sync; + struct { + uint32_t nfingers; + double dx, dy; /* relative motion */ + double degrees; + double scale; + bool is_cancelled; + } gestures; }; }; diff --git a/src/libeis-gestures.c b/src/libeis-gestures.c new file mode 100644 index 0000000..040d180 --- /dev/null +++ b/src/libeis-gestures.c @@ -0,0 +1,824 @@ +/* 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 "config.h" + +#include +#include + +#include "util-bits.h" +#include "util-io.h" +#include "util-macros.h" +#include "util-mem.h" +#include "util-strings.h" +#include "util-version.h" + +#include "eis-proto.h" +#include "libeis-client.h" +#include "libeis-gestures.h" +#include "libeis-private.h" + +static void +eis_gesture_swipe_destroy(struct eis_gesture_swipe *swipe) +{ + struct eis_client *client = eis_gesture_swipe_get_client(swipe); + eis_client_unregister_object(client, &swipe->proto_object); +} + +OBJECT_IMPLEMENT_REF(eis_gesture_swipe); +OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_gesture_swipe); + +static OBJECT_IMPLEMENT_CREATE(eis_gesture_swipe); +static OBJECT_IMPLEMENT_PARENT(eis_gesture_swipe, eis_device); +OBJECT_IMPLEMENT_GETTER_AS_REF(eis_gesture_swipe, proto_object, const struct brei_object *); + +uint32_t +eis_gesture_swipe_get_version(struct eis_gesture_swipe *swipe) +{ + return swipe->proto_object.version; +} + +object_id_t +eis_gesture_swipe_get_id(struct eis_gesture_swipe *swipe) +{ + return swipe->proto_object.id; +} + +struct eis_device * +eis_gesture_swipe_get_device(struct eis_gesture_swipe *swipe) +{ + return eis_gesture_swipe_parent(swipe); +} + +struct eis_client * +eis_gesture_swipe_get_client(struct eis_gesture_swipe *swipe) +{ + return eis_device_get_client(eis_gesture_swipe_get_device(swipe)); +} + +struct eis * +eis_gesture_swipe_get_context(struct eis_gesture_swipe *swipe) +{ + return eis_device_get_context(eis_gesture_swipe_get_device(swipe)); +} + +const struct eis_gesture_swipe_interface * +eis_gesture_swipe_get_interface(struct eis_gesture_swipe *swipe) +{ + struct eis_device *device = eis_gesture_swipe_get_device(swipe); + return eis_device_get_gesture_swipe_interface(device); +} + +struct eis_gesture_swipe * +eis_gesture_swipe_new(struct eis_device *device) +{ + struct eis_gesture_swipe *swipe = eis_gesture_swipe_create(&device->object); + struct eis_client *client = eis_device_get_client(device); + + swipe->proto_object.id = eis_client_get_new_id(client); + swipe->proto_object.implementation = swipe; + swipe->proto_object.interface = &eis_gesture_swipe_proto_interface; + swipe->proto_object.version = client->interface_versions.ei_gesture_swipe; + eis_client_register_object(client, &swipe->proto_object); + + return swipe; /* ref owned by caller */ +} + +static void +eis_gesture_pinch_destroy(struct eis_gesture_pinch *pinch) +{ + struct eis_client *client = eis_gesture_pinch_get_client(pinch); + eis_client_unregister_object(client, &pinch->proto_object); +} + +OBJECT_IMPLEMENT_REF(eis_gesture_pinch); +OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_gesture_pinch); + +static OBJECT_IMPLEMENT_CREATE(eis_gesture_pinch); +static OBJECT_IMPLEMENT_PARENT(eis_gesture_pinch, eis_device); +OBJECT_IMPLEMENT_GETTER_AS_REF(eis_gesture_pinch, proto_object, const struct brei_object *); + +uint32_t +eis_gesture_pinch_get_version(struct eis_gesture_pinch *pinch) +{ + return pinch->proto_object.version; +} + +object_id_t +eis_gesture_pinch_get_id(struct eis_gesture_pinch *pinch) +{ + return pinch->proto_object.id; +} + +struct eis_device * +eis_gesture_pinch_get_device(struct eis_gesture_pinch *pinch) +{ + return eis_gesture_pinch_parent(pinch); +} + +struct eis_client * +eis_gesture_pinch_get_client(struct eis_gesture_pinch *pinch) +{ + return eis_device_get_client(eis_gesture_pinch_get_device(pinch)); +} + +struct eis * +eis_gesture_pinch_get_context(struct eis_gesture_pinch *pinch) +{ + return eis_device_get_context(eis_gesture_pinch_get_device(pinch)); +} + +const struct eis_gesture_pinch_interface * +eis_gesture_pinch_get_interface(struct eis_gesture_pinch *pinch) +{ + struct eis_device *device = eis_gesture_pinch_get_device(pinch); + return eis_device_get_gesture_pinch_interface(device); +} + +struct eis_gesture_pinch * +eis_gesture_pinch_new(struct eis_device *device) +{ + struct eis_gesture_pinch *pinch = eis_gesture_pinch_create(&device->object); + struct eis_client *client = eis_device_get_client(device); + + pinch->proto_object.id = eis_client_get_new_id(client); + pinch->proto_object.implementation = pinch; + pinch->proto_object.interface = &eis_gesture_pinch_proto_interface; + pinch->proto_object.version = client->interface_versions.ei_gesture_pinch; + eis_client_register_object(client, &pinch->proto_object); + + return pinch; /* ref owned by caller */ +} + +static void +eis_gesture_hold_destroy(struct eis_gesture_hold *hold) +{ + struct eis_client *client = eis_gesture_hold_get_client(hold); + eis_client_unregister_object(client, &hold->proto_object); +} + +OBJECT_IMPLEMENT_REF(eis_gesture_hold); +OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_gesture_hold); + +static OBJECT_IMPLEMENT_CREATE(eis_gesture_hold); +static OBJECT_IMPLEMENT_PARENT(eis_gesture_hold, eis_device); +OBJECT_IMPLEMENT_GETTER_AS_REF(eis_gesture_hold, proto_object, const struct brei_object *); + +uint32_t +eis_gesture_hold_get_version(struct eis_gesture_hold *hold) +{ + return hold->proto_object.version; +} + +object_id_t +eis_gesture_hold_get_id(struct eis_gesture_hold *hold) +{ + return hold->proto_object.id; +} + +struct eis_device * +eis_gesture_hold_get_device(struct eis_gesture_hold *hold) +{ + return eis_gesture_hold_parent(hold); +} + +struct eis_client * +eis_gesture_hold_get_client(struct eis_gesture_hold *hold) +{ + return eis_device_get_client(eis_gesture_hold_get_device(hold)); +} + +struct eis * +eis_gesture_hold_get_context(struct eis_gesture_hold *hold) +{ + return eis_device_get_context(eis_gesture_hold_get_device(hold)); +} + +const struct eis_gesture_hold_interface * +eis_gesture_hold_get_interface(struct eis_gesture_hold *hold) +{ + struct eis_device *device = eis_gesture_hold_get_device(hold); + return eis_device_get_gesture_hold_interface(device); +} + +struct eis_gesture_hold * +eis_gesture_hold_new(struct eis_device *device) +{ + struct eis_gesture_hold *hold = eis_gesture_hold_create(&device->object); + struct eis_client *client = eis_device_get_client(device); + + hold->proto_object.id = eis_client_get_new_id(client); + hold->proto_object.implementation = hold; + hold->proto_object.interface = &eis_gesture_hold_proto_interface; + hold->proto_object.version = client->interface_versions.ei_gesture_hold; + eis_client_register_object(client, &hold->proto_object); + + return hold; /* ref owned by caller */ +} + +static void +eis_gesture_destroy(struct eis_gesture *gesture) +{ + eis_device_unref(gesture->device); +} + +static OBJECT_IMPLEMENT_INIT(eis_gesture); +OBJECT_IMPLEMENT_REF(eis_gesture); +OBJECT_IMPLEMENT_UNREF(eis_gesture); + +struct eis_device * +eis_gesture_get_device(struct eis_gesture *gesture) +{ + return gesture->device; +} + +struct eis * +eis_gesture_get_context(struct eis_gesture *gesture) +{ + return eis_device_get_context(eis_gesture_get_device(gesture)); +} + +static void +eis_gesture_init(struct eis_gesture *gesture, struct eis_device *device, uint32_t nfingers) +{ + /* Not using the device as parent object because we need a ref + * to it */ + eis_gesture_init_object(gesture, NULL); + + gesture->state = EIS_GESTURE_STATE_NEW; + gesture->device = eis_device_ref(device); + gesture->nfingers = nfingers; +} + +struct eis_swipe * +eis_swipe_new(struct eis_device *device, uint32_t nfingers) +{ + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + log_bug_client(eis_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return NULL; + } + + if (nfingers == 0) { + log_bug_client(eis_device_get_context(device), + "zero-finger gestures are not possible"); + return NULL; + } + + if (!device->swipe) + return NULL; + + struct eis_swipe *swipe = xalloc(sizeof *swipe); + eis_gesture_init(&swipe->base, device, nfingers); + + return swipe; +} + +_public_ struct eis_swipe * +eis_swipe_ref(struct eis_swipe *swipe) +{ + return (struct eis_swipe *)eis_gesture_ref(&swipe->base); +} + +_public_ struct eis_swipe * +eis_swipe_unref(struct eis_swipe *swipe) +{ + if (!swipe) + return NULL; + + struct eis_device *device = eis_gesture_get_device(&swipe->base); + + if (swipe == device->gesture_state.swipe.current_swipe) { + if (swipe->base.state == EIS_GESTURE_STATE_BEGUN && + device->state == EIS_DEVICE_STATE_EMULATING) { + eis_swipe_cancel(swipe); + } + } + + return (struct eis_swipe *)eis_gesture_unref(&swipe->base); +} + +struct eis * +eis_swipe_get_context(struct eis_swipe *swipe) +{ + return eis_gesture_get_context(&swipe->base); +} + +_public_ void +eis_swipe_begin(struct eis_swipe *swipe) +{ + struct eis_device *device = swipe->base.device; + struct eis_client *client = eis_device_get_client(device); + + if (swipe->base.state != EIS_GESTURE_STATE_NEW) { + log_bug_client(eis_gesture_get_context(&swipe->base), "gesture cannot begin twice"); + return; + } + + if (device->gesture_state.swipe.current_swipe) { + log_bug_client( + eis_gesture_get_context(&swipe->base), + "a different swipe gesture is already in process, ignoring this one"); + return; + } + + if (device->state != EIS_DEVICE_STATE_EMULATING) { + log_bug_client(eis_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (client->state == EIS_CLIENT_STATE_NEW || client->state == EIS_CLIENT_STATE_DISCONNECTED) + return; + + if (!device->swipe) + return; + + if (swipe->base.nfingers == 0) { + log_bug_client(eis_device_get_context(device), + "zero-finger gestures are invalid, ignoring gesture"); + return; + } + + device->gesture_state.swipe.current_swipe = swipe; + + device->send_frame_event = true; + + swipe->base.state = EIS_GESTURE_STATE_BEGUN; + int rc = eis_gesture_swipe_event_begin(device->swipe, swipe->base.nfingers); + if (rc) + eis_client_disconnect(client); +} + +_public_ void +eis_swipe_update(struct eis_swipe *swipe, double x, double y) +{ + struct eis_device *device = swipe->base.device; + struct eis_client *client = eis_device_get_client(device); + + if (swipe->base.state != EIS_GESTURE_STATE_BEGUN) { + log_bug_client(eis_gesture_get_context(&swipe->base), + "gesture in invalid state for update"); + return; + } + + if (device->gesture_state.swipe.current_swipe != swipe) { + log_bug_client( + eis_gesture_get_context(&swipe->base), + "a different swipe gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EIS_DEVICE_STATE_EMULATING) { + log_bug_client(eis_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (client->state == EIS_CLIENT_STATE_NEW || client->state == EIS_CLIENT_STATE_DISCONNECTED) + return; + + if (!device->swipe) + return; + + device->send_frame_event = true; + + int rc = eis_gesture_swipe_event_update(device->swipe, x, y); + if (rc) + eis_client_disconnect(client); +} + +static void +eis_swipe_end_cancel(struct eis_swipe *swipe, bool is_cancel) +{ + struct eis_device *device = swipe->base.device; + struct eis_client *client = eis_device_get_client(device); + + if (swipe->base.state != EIS_GESTURE_STATE_BEGUN) { + log_bug_client(eis_gesture_get_context(&swipe->base), + "gesture in invalid state for end/cancel"); + return; + } + + if (device->gesture_state.swipe.current_swipe != swipe) { + log_bug_client( + eis_gesture_get_context(&swipe->base), + "a different swipe gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EIS_DEVICE_STATE_EMULATING) { + log_bug_client(eis_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (client->state == EIS_CLIENT_STATE_NEW || client->state == EIS_CLIENT_STATE_DISCONNECTED) + return; + + if (!device->swipe) + return; + + swipe->base.state = EIS_GESTURE_STATE_ENDED; + device->gesture_state.swipe.current_swipe = NULL; + + device->send_frame_event = true; + + int rc = eis_gesture_swipe_event_end(device->swipe, is_cancel); + if (rc) + eis_client_disconnect(client); +} + +_public_ void +eis_swipe_end(struct eis_swipe *swipe) +{ + eis_swipe_end_cancel(swipe, false); +} + +_public_ void +eis_swipe_cancel(struct eis_swipe *swipe) +{ + eis_swipe_end_cancel(swipe, true); +} + +struct eis_pinch * +eis_pinch_new(struct eis_device *device, uint32_t nfingers) +{ + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + log_bug_client(eis_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return NULL; + } + + if (nfingers < 2) { + log_bug_client(eis_device_get_context(device), + "pinch gestures require at least 2 fingers"); + return NULL; + } + + if (!device->pinch) + return NULL; + + /* Not using the device as parent object because we need a ref + * to it */ + struct eis_pinch *pinch = xalloc(sizeof *pinch); + eis_gesture_init(&pinch->base, device, nfingers); + + return pinch; +} + +_public_ struct eis_pinch * +eis_pinch_ref(struct eis_pinch *pinch) +{ + return (struct eis_pinch *)eis_gesture_ref(&pinch->base); +} + +_public_ struct eis_pinch * +eis_pinch_unref(struct eis_pinch *pinch) +{ + if (!pinch) + return NULL; + + struct eis_device *device = eis_gesture_get_device(&pinch->base); + + if (pinch == device->gesture_state.pinch.current_pinch) { + if (pinch->base.state == EIS_GESTURE_STATE_BEGUN && + device->state == EIS_DEVICE_STATE_EMULATING) { + eis_pinch_cancel(pinch); + } + } + + return (struct eis_pinch *)eis_gesture_unref(&pinch->base); +} + +struct eis * +eis_pinch_get_context(struct eis_pinch *pinch) +{ + return eis_gesture_get_context(&pinch->base); +} + +_public_ void +eis_pinch_begin(struct eis_pinch *pinch) +{ + struct eis_device *device = pinch->base.device; + struct eis_client *client = eis_device_get_client(device); + + if (pinch->base.state != EIS_GESTURE_STATE_NEW) { + log_bug_client(eis_gesture_get_context(&pinch->base), "gesture cannot begin twice"); + return; + } + + if (device->gesture_state.pinch.current_pinch) { + log_bug_client( + eis_gesture_get_context(&pinch->base), + "a different pinch gesture is already in process, ignoring this one"); + return; + } + + if (device->state != EIS_DEVICE_STATE_EMULATING) { + log_bug_client(eis_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (client->state == EIS_CLIENT_STATE_NEW || client->state == EIS_CLIENT_STATE_DISCONNECTED) + return; + + if (!device->pinch) + return; + + if (pinch->base.nfingers == 0) { + log_bug_client(eis_device_get_context(device), + "zero-finger gestures are invalid, ignoring gesture"); + return; + } + + device->gesture_state.pinch.current_pinch = pinch; + + device->send_frame_event = true; + + pinch->base.state = EIS_GESTURE_STATE_BEGUN; + int rc = eis_gesture_pinch_event_begin(device->pinch, pinch->base.nfingers); + if (rc) + eis_client_disconnect(client); +} + +_public_ void +eis_pinch_update(struct eis_pinch *pinch, double x, double y, double scale, double rotation) +{ + struct eis_device *device = pinch->base.device; + struct eis_client *client = eis_device_get_client(device); + + if (pinch->base.state != EIS_GESTURE_STATE_BEGUN) { + log_bug_client(eis_gesture_get_context(&pinch->base), + "gesture in invalid state for update"); + return; + } + + if (device->gesture_state.pinch.current_pinch != pinch) { + log_bug_client( + eis_gesture_get_context(&pinch->base), + "a different pinch gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EIS_DEVICE_STATE_EMULATING) { + log_bug_client(eis_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (client->state == EIS_CLIENT_STATE_NEW || client->state == EIS_CLIENT_STATE_DISCONNECTED) + return; + + if (!device->pinch) + return; + + device->send_frame_event = true; + + int rc = eis_gesture_pinch_event_update(device->pinch, x, y, scale, rotation); + if (rc) + eis_client_disconnect(client); +} + +static void +eis_pinch_end_cancel(struct eis_pinch *pinch, bool is_cancel) +{ + struct eis_device *device = pinch->base.device; + struct eis_client *client = eis_device_get_client(device); + + if (pinch->base.state != EIS_GESTURE_STATE_BEGUN) { + log_bug_client(eis_gesture_get_context(&pinch->base), + "gesture in invalid state for end/cancel"); + return; + } + + if (device->gesture_state.pinch.current_pinch != pinch) { + log_bug_client( + eis_gesture_get_context(&pinch->base), + "a different pinch gesture is already in process, ignoring this one"); + return; + } + + if (device->state != EIS_DEVICE_STATE_EMULATING) { + log_bug_client(eis_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (client->state == EIS_CLIENT_STATE_NEW || client->state == EIS_CLIENT_STATE_DISCONNECTED) + return; + + if (!device->pinch) + return; + + pinch->base.state = EIS_GESTURE_STATE_ENDED; + device->gesture_state.pinch.current_pinch = NULL; + + device->send_frame_event = true; + + int rc = eis_gesture_pinch_event_end(device->pinch, is_cancel); + if (rc) + eis_client_disconnect(client); +} + +_public_ void +eis_pinch_end(struct eis_pinch *pinch) +{ + eis_pinch_end_cancel(pinch, false); +} + +_public_ void +eis_pinch_cancel(struct eis_pinch *pinch) +{ + eis_pinch_end_cancel(pinch, true); +} + +_public_ void +eis_hold_begin(struct eis_hold *hold) +{ + struct eis_device *device = hold->base.device; + struct eis_client *client = eis_device_get_client(device); + + if (hold->base.state != EIS_GESTURE_STATE_NEW) { + log_bug_client(eis_gesture_get_context(&hold->base), "gesture cannot begin twice"); + return; + } + + if (device->gesture_state.hold.current_hold) { + log_bug_client(eis_gesture_get_context(&hold->base), + "a different hold gesture is already in process, ignoring this one"); + return; + } + + if (device->state != EIS_DEVICE_STATE_EMULATING) { + log_bug_client(eis_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (client->state == EIS_CLIENT_STATE_NEW || client->state == EIS_CLIENT_STATE_DISCONNECTED) + return; + + if (!device->hold) + return; + + if (hold->base.nfingers == 0) { + log_bug_client(eis_device_get_context(device), + "zero-finger gestures are invalid, ignoring gesture"); + return; + } + + device->gesture_state.hold.current_hold = hold; + + device->send_frame_event = true; + + hold->base.state = EIS_GESTURE_STATE_BEGUN; + int rc = eis_gesture_hold_event_begin(device->hold, hold->base.nfingers); + if (rc) + eis_client_disconnect(client); +} + +struct eis_hold * +eis_hold_new(struct eis_device *device, uint32_t nfingers) +{ + if (!eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)) { + log_bug_client(eis_device_get_context(device), + "%s: device is not a gesture device", + __func__); + return NULL; + } + + if (nfingers == 0) { + log_bug_client(eis_device_get_context(device), + "zero-finger gestures are not possible"); + return NULL; + } + + if (!device->hold) + return NULL; + + /* Not using the device as parent object because we need a ref + * to it */ + struct eis_hold *hold = xalloc(sizeof *hold); + eis_gesture_init(&hold->base, device, nfingers); + + return hold; +} + +_public_ struct eis_hold * +eis_hold_ref(struct eis_hold *hold) +{ + return (struct eis_hold *)eis_gesture_ref(&hold->base); +} + +_public_ struct eis_hold * +eis_hold_unref(struct eis_hold *hold) +{ + if (!hold) + return NULL; + + struct eis_device *device = eis_gesture_get_device(&hold->base); + + if (hold == device->gesture_state.hold.current_hold) { + if (hold->base.state == EIS_GESTURE_STATE_BEGUN && + device->state == EIS_DEVICE_STATE_EMULATING) { + eis_hold_cancel(hold); + } + } + + return (struct eis_hold *)eis_gesture_unref(&hold->base); +} + +struct eis * +eis_hold_get_context(struct eis_hold *hold) +{ + return eis_gesture_get_context(&hold->base); +} + +static void +eis_hold_end_cancel(struct eis_hold *hold, bool is_cancel) +{ + struct eis_device *device = hold->base.device; + struct eis_client *client = eis_device_get_client(device); + + if (hold->base.state != EIS_GESTURE_STATE_BEGUN) { + log_bug_client(eis_gesture_get_context(&hold->base), + "gesture in invalid state for end/cancel"); + return; + } + + if (device->gesture_state.hold.current_hold != hold) { + log_bug_client(eis_gesture_get_context(&hold->base), + "a different hold gesture is already in process, ignoring this one"); + return; + } + + /* FIXME: what do we do with a gesture when the device stops emulating? */ + if (device->state != EIS_DEVICE_STATE_EMULATING) { + log_bug_client(eis_device_get_context(device), + "%s: device is not emulating", + __func__); + return; + } + + if (client->state == EIS_CLIENT_STATE_NEW || client->state == EIS_CLIENT_STATE_DISCONNECTED) + return; + + if (!device->hold) + return; + + hold->base.state = EIS_GESTURE_STATE_ENDED; + device->gesture_state.hold.current_hold = NULL; + + device->send_frame_event = true; + + int rc = eis_gesture_hold_event_end(device->hold, is_cancel); + if (rc) + eis_client_disconnect(client); +} + +_public_ void +eis_hold_end(struct eis_hold *hold) +{ + eis_hold_end_cancel(hold, false); +} + +_public_ void +eis_hold_cancel(struct eis_hold *hold) +{ + eis_hold_end_cancel(hold, true); +} diff --git a/src/libeis-gestures.h b/src/libeis-gestures.h new file mode 100644 index 0000000..82e39e0 --- /dev/null +++ b/src/libeis-gestures.h @@ -0,0 +1,132 @@ +/* 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. + */ + +#pragma once + +#include "util-list.h" +#include "util-object.h" + +#include "brei-shared.h" + +struct eis; +struct eis_device; + +/* This is the protocol-only object, not exposed in the API. See eis_swipe() for + * the C API object */ +struct eis_gesture_swipe { + struct object object; + struct brei_object proto_object; +}; + +OBJECT_DECLARE_GETTER(eis_gesture_swipe, id, object_id_t); +OBJECT_DECLARE_GETTER(eis_gesture_swipe, version, uint32_t); +OBJECT_DECLARE_GETTER(eis_gesture_swipe, context, struct eis *); +OBJECT_DECLARE_GETTER(eis_gesture_swipe, client, struct eis_client *); +OBJECT_DECLARE_GETTER(eis_gesture_swipe, device, struct eis_device *); +OBJECT_DECLARE_GETTER(eis_gesture_swipe, proto_object, const struct brei_object *); +OBJECT_DECLARE_GETTER(eis_gesture_swipe, interface, const struct eis_gesture_swipe_interface *); +OBJECT_DECLARE_REF(eis_gesture_swipe); +OBJECT_DECLARE_UNREF(eis_gesture_swipe); + +struct eis_gesture_swipe * +eis_gesture_swipe_new(struct eis_device *device); + +/* This is the protocol-only object, not exposed in the API. See eis_pinch() for + * the C API object */ +struct eis_gesture_pinch { + struct object object; + struct brei_object proto_object; +}; + +OBJECT_DECLARE_GETTER(eis_gesture_pinch, id, object_id_t); +OBJECT_DECLARE_GETTER(eis_gesture_pinch, version, uint32_t); +OBJECT_DECLARE_GETTER(eis_gesture_pinch, context, struct eis *); +OBJECT_DECLARE_GETTER(eis_gesture_pinch, client, struct eis_client *); +OBJECT_DECLARE_GETTER(eis_gesture_pinch, device, struct eis_device *); +OBJECT_DECLARE_GETTER(eis_gesture_pinch, proto_object, const struct brei_object *); +OBJECT_DECLARE_GETTER(eis_gesture_pinch, interface, const struct eis_gesture_pinch_interface *); +OBJECT_DECLARE_REF(eis_gesture_pinch); +OBJECT_DECLARE_UNREF(eis_gesture_pinch); + +struct eis_gesture_pinch * +eis_gesture_pinch_new(struct eis_device *device); + +/* This is the protocol-only object, not exposed in the API. See eis_hold() for + * the C API object */ +struct eis_gesture_hold { + struct object object; + struct brei_object proto_object; +}; + +OBJECT_DECLARE_GETTER(eis_gesture_hold, id, object_id_t); +OBJECT_DECLARE_GETTER(eis_gesture_hold, version, uint32_t); +OBJECT_DECLARE_GETTER(eis_gesture_hold, context, struct eis *); +OBJECT_DECLARE_GETTER(eis_gesture_hold, client, struct eis_client *); +OBJECT_DECLARE_GETTER(eis_gesture_hold, device, struct eis_device *); +OBJECT_DECLARE_GETTER(eis_gesture_hold, proto_object, const struct brei_object *); +OBJECT_DECLARE_GETTER(eis_gesture_hold, interface, const struct eis_gesture_hold_interface *); +OBJECT_DECLARE_REF(eis_gesture_hold); +OBJECT_DECLARE_UNREF(eis_gesture_hold); + +struct eis_gesture_hold * +eis_gesture_hold_new(struct eis_device *device); + +enum eis_gesture_state { EIS_GESTURE_STATE_NEW, EIS_GESTURE_STATE_BEGUN, EIS_GESTURE_STATE_ENDED }; + +/* This is the parent object for all the C API gestures */ +struct eis_gesture { + struct object object; + struct eis_device *device; + enum eis_gesture_state state; + uint32_t nfingers; +}; + +OBJECT_DECLARE_REF(eis_gesture); +OBJECT_DECLARE_UNREF(eis_gesture); +OBJECT_DECLARE_GETTER(eis_gesture, context, struct eis *); +OBJECT_DECLARE_GETTER(eis_gesture, device, struct eis_device *); + +struct eis_swipe { + struct eis_gesture base; +}; + +struct eis_pinch { + struct eis_gesture base; +}; + +struct eis_hold { + struct eis_gesture base; +}; + +struct eis_swipe * +eis_swipe_new(struct eis_device *device, uint32_t nfingers); +OBJECT_DECLARE_GETTER(eis_swipe, context, struct eis *); + +struct eis_pinch * +eis_pinch_new(struct eis_device *device, uint32_t nfingers); +OBJECT_DECLARE_GETTER(eis_pinch, context, struct eis *); + +struct eis_hold * +eis_hold_new(struct eis_device *device, uint32_t nfingers); +OBJECT_DECLARE_GETTER(eis_hold, context, struct eis *); diff --git a/src/libeis-handshake.c b/src/libeis-handshake.c index 9a72442..3a8af53 100644 --- a/src/libeis-handshake.c +++ b/src/libeis-handshake.c @@ -136,6 +136,9 @@ client_msg_finish(struct eis_handshake *setup) SEND_INTERFACE_VERSION(EIS_SCROLL, ei_scroll); SEND_INTERFACE_VERSION(EIS_KEYBOARD, ei_keyboard); SEND_INTERFACE_VERSION(EIS_TOUCHSCREEN, ei_touchscreen); + SEND_INTERFACE_VERSION(EIS_GESTURE_SWIPE, ei_gesture_swipe); + SEND_INTERFACE_VERSION(EIS_GESTURE_PINCH, ei_gesture_pinch); + SEND_INTERFACE_VERSION(EIS_GESTURE_HOLD, ei_gesture_hold); #undef SEND_INTERFACE_VERSION @@ -237,6 +240,9 @@ client_msg_interface_version(struct eis_handshake *setup, const char *name, uint VERSION_ENTRY(ei_scroll), VERSION_ENTRY(ei_keyboard), VERSION_ENTRY(ei_touchscreen), + VERSION_ENTRY(ei_gesture_swipe), + VERSION_ENTRY(ei_gesture_pinch), + VERSION_ENTRY(ei_gesture_hold), VERSION_ENTRY(ei_text), #undef VERSION_ENTRY }; diff --git a/src/libeis-private.h b/src/libeis-private.h index 36b8ce6..44061c0 100644 --- a/src/libeis-private.h +++ b/src/libeis-private.h @@ -164,6 +164,34 @@ eis_queue_text_utf8_event(struct eis_device *device, const char *utf8); void eis_sync_event_send_done(struct eis_event *e); +void +eis_queue_swipe_begin_event(struct eis_device *device, uint32_t nfingers); + +void +eis_queue_swipe_update_event(struct eis_device *device, double dx, double dy); + +void +eis_queue_swipe_end_event(struct eis_device *device, bool is_cancelled); + +void +eis_queue_pinch_begin_event(struct eis_device *device, uint32_t nfingers); + +void +eis_queue_pinch_update_event(struct eis_device *device, + double dx, + double dy, + double scale, + double rotation); + +void +eis_queue_pinch_end_event(struct eis_device *device, bool is_cancelled); + +void +eis_queue_hold_begin_event(struct eis_device *device, uint32_t nfingers); + +void +eis_queue_hold_end_event(struct eis_device *device, bool is_cancelled); + _printf_(6, 7) void eis_log_msg(struct eis *eis, enum eis_log_priority priority, diff --git a/src/libeis-seat.c b/src/libeis-seat.c index 03d0534..e1b42b8 100644 --- a/src/libeis-seat.c +++ b/src/libeis-seat.c @@ -103,6 +103,12 @@ convert_capabilities(uint64_t caps) capabilities |= EIS_DEVICE_CAP_BUTTON; if (caps & bit(EIS_SCROLL_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_SCROLL; + if (caps & bit(EIS_GESTURE_SWIPE_INTERFACE_INDEX)) + capabilities |= EIS_DEVICE_CAP_GESTURES; + if (caps & bit(EIS_GESTURE_PINCH_INTERFACE_INDEX)) + capabilities |= EIS_DEVICE_CAP_GESTURES; + if (caps & bit(EIS_GESTURE_HOLD_INTERFACE_INDEX)) + capabilities |= EIS_DEVICE_CAP_GESTURES; if (caps & bit(EIS_TEXT_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_TEXT; @@ -242,6 +248,27 @@ eis_seat_add(struct eis_seat *seat) mask_add(seat->capabilities.proto_mask, mask); } + if (seat->capabilities.c_mask & EIS_DEVICE_CAP_GESTURES && + client->interface_versions.ei_gesture_swipe > 0) { + uint64_t mask = bit(EIS_GESTURE_SWIPE_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, EIS_GESTURE_SWIPE_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + } + + if (seat->capabilities.c_mask & EIS_DEVICE_CAP_GESTURES && + client->interface_versions.ei_gesture_pinch > 0) { + uint64_t mask = bit(EIS_GESTURE_PINCH_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, EIS_GESTURE_PINCH_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + } + + if (seat->capabilities.c_mask & EIS_DEVICE_CAP_GESTURES && + client->interface_versions.ei_gesture_hold > 0) { + uint64_t mask = bit(EIS_GESTURE_HOLD_INTERFACE_INDEX); + eis_seat_event_capability(seat, mask, EIS_GESTURE_HOLD_INTERFACE_NAME); + mask_add(seat->capabilities.proto_mask, mask); + } + if (seat->capabilities.c_mask & EIS_DEVICE_CAP_TEXT && client->interface_versions.ei_text > 0) { uint64_t mask = bit(EIS_TEXT_INTERFACE_INDEX); @@ -341,6 +368,7 @@ eis_seat_configure_capability(struct eis_seat *seat, enum eis_device_capability case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: + case EIS_DEVICE_CAP_GESTURES: case EIS_DEVICE_CAP_TEXT: mask_add(seat->capabilities.c_mask, cap); break; @@ -357,6 +385,7 @@ eis_seat_has_capability(struct eis_seat *seat, enum eis_device_capability cap) case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: + case EIS_DEVICE_CAP_GESTURES: case EIS_DEVICE_CAP_TEXT: return mask_all(seat->capabilities.c_mask, cap); } diff --git a/src/libeis.c b/src/libeis.c index e9fb811..1508e02 100644 --- a/src/libeis.c +++ b/src/libeis.c @@ -153,6 +153,14 @@ eis_event_type_to_string(enum eis_event_type type) CASE_RETURN_STRING(EIS_EVENT_TOUCH_UP); CASE_RETURN_STRING(EIS_EVENT_TOUCH_MOTION); CASE_RETURN_STRING(EIS_EVENT_FRAME); + CASE_RETURN_STRING(EIS_EVENT_SWIPE_BEGIN); + CASE_RETURN_STRING(EIS_EVENT_SWIPE_UPDATE); + CASE_RETURN_STRING(EIS_EVENT_SWIPE_END); + CASE_RETURN_STRING(EIS_EVENT_PINCH_BEGIN); + CASE_RETURN_STRING(EIS_EVENT_PINCH_UPDATE); + CASE_RETURN_STRING(EIS_EVENT_PINCH_END); + CASE_RETURN_STRING(EIS_EVENT_HOLD_BEGIN); + CASE_RETURN_STRING(EIS_EVENT_HOLD_END); CASE_RETURN_STRING(EIS_EVENT_TEXT_KEYSYM); CASE_RETURN_STRING(EIS_EVENT_TEXT_UTF8); } @@ -175,6 +183,14 @@ update_event_timestamp(struct eis_event *event, uint64_t time) case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_TOUCH_MOTION: + case EIS_EVENT_SWIPE_BEGIN: + case EIS_EVENT_SWIPE_UPDATE: + case EIS_EVENT_SWIPE_END: + case EIS_EVENT_PINCH_BEGIN: + case EIS_EVENT_PINCH_UPDATE: + case EIS_EVENT_PINCH_END: + case EIS_EVENT_HOLD_BEGIN: + case EIS_EVENT_HOLD_END: case EIS_EVENT_TEXT_KEYSYM: case EIS_EVENT_TEXT_UTF8: if (event->timestamp != 0) { @@ -213,6 +229,14 @@ eis_queue_event(struct eis_event *event) case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_TOUCH_MOTION: + case EIS_EVENT_SWIPE_BEGIN: + case EIS_EVENT_SWIPE_UPDATE: + case EIS_EVENT_SWIPE_END: + case EIS_EVENT_PINCH_BEGIN: + case EIS_EVENT_PINCH_UPDATE: + case EIS_EVENT_PINCH_END: + case EIS_EVENT_HOLD_BEGIN: + case EIS_EVENT_HOLD_END: case EIS_EVENT_TEXT_KEYSYM: case EIS_EVENT_TEXT_UTF8: prefix = "pending "; @@ -484,6 +508,106 @@ eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid) eis_queue_event(e); } +void +eis_queue_swipe_begin_event(struct eis_device *device, uint32_t nfingers) +{ + struct eis_event *e = eis_event_new_for_device(device); + + e->type = EIS_EVENT_SWIPE_BEGIN; + e->gestures.nfingers = nfingers; + + eis_queue_event(e); +} + +void +eis_queue_swipe_update_event(struct eis_device *device, double dx, double dy) +{ + struct eis_event *e = eis_event_new_for_device(device); + + e->type = EIS_EVENT_SWIPE_UPDATE; + e->gestures.nfingers = device->gesture_state.swipe.nfingers; + e->gestures.dx = dx; + e->gestures.dy = dy; + + eis_queue_event(e); +} + +void +eis_queue_swipe_end_event(struct eis_device *device, bool is_cancelled) +{ + struct eis_event *e = eis_event_new_for_device(device); + + e->type = EIS_EVENT_SWIPE_END; + e->gestures.nfingers = device->gesture_state.swipe.nfingers; + e->gestures.is_cancelled = is_cancelled; + + eis_queue_event(e); +} + +void +eis_queue_pinch_begin_event(struct eis_device *device, uint32_t nfingers) +{ + struct eis_event *e = eis_event_new_for_device(device); + + e->type = EIS_EVENT_PINCH_BEGIN; + e->gestures.nfingers = nfingers; + + eis_queue_event(e); +} + +void +eis_queue_pinch_update_event(struct eis_device *device, + double dx, + double dy, + double scale, + double rotation) +{ + struct eis_event *e = eis_event_new_for_device(device); + e->type = EIS_EVENT_PINCH_UPDATE; + e->gestures.nfingers = device->gesture_state.pinch.nfingers; + e->gestures.scale = scale; + e->gestures.degrees = rotation; + e->gestures.dx = dx; + e->gestures.dy = dy; + + eis_queue_event(e); +} + +void +eis_queue_pinch_end_event(struct eis_device *device, bool is_cancelled) +{ + struct eis_event *e = eis_event_new_for_device(device); + + e->type = EIS_EVENT_PINCH_END; + e->gestures.nfingers = device->gesture_state.pinch.nfingers; + e->gestures.is_cancelled = is_cancelled; + + eis_queue_event(e); +} + +void +eis_queue_hold_begin_event(struct eis_device *device, uint32_t nfingers) +{ + struct eis_event *e = eis_event_new_for_device(device); + + e->type = EIS_EVENT_HOLD_BEGIN; + e->gestures.nfingers = nfingers; + + eis_queue_event(e); +} + +void +eis_queue_hold_end_event(struct eis_device *device, bool is_cancelled) +{ + struct eis_event *e = eis_event_new_for_device(device); + + e->type = EIS_EVENT_HOLD_END; + e->gestures.nfingers = device->gesture_state.hold.nfingers; + e->gestures.is_cancelled = is_cancelled; + + eis_queue_event(e); +} + void eis_queue_text_keysym_event(struct eis_device *device, uint32_t keysym, bool is_press) { diff --git a/src/libeis.h b/src/libeis.h index d9db61c..e7382b7 100644 --- a/src/libeis.h +++ b/src/libeis.h @@ -146,6 +146,18 @@ struct eis_keymap; * @struct eis_touch */ struct eis_touch; +/** + * @struct eis_swipe + */ +struct eis_swipe; +/** + * @struct eis_pinch + */ +struct eis_pinch; +/** + * @struct eis_hold + */ +struct eis_hold; /** * @struct eis_ping @@ -225,7 +237,11 @@ enum eis_device_capability { /** * @since 1.6 */ - EIS_DEVICE_CAP_TEXT = (1 << 6), + EIS_DEVICE_CAP_GESTURES = (1 << 6), + /** + * @since 1.6 + */ + EIS_DEVICE_CAP_TEXT = (1 << 7), }; /** @@ -405,12 +421,97 @@ enum eis_event_type { */ EIS_EVENT_TOUCH_MOTION, + /** + * Event for a swipe begin. Only one swipe gesture may be active + * at any time. + * + * @note This event is only generated on a sender eis context. + * See eis_device_swipe_new() and eis_swipe_begin() for the receiver context API. + * + * @since 1.6 + */ + EIS_EVENT_SWIPE_BEGIN = 900, + /** + * Event for an update to the current ongoing swipe gesture. + * + * This library accumulates all updates to the gesture within the same + * device frame into one gesture event. + * + * @note This event is only generated on a sender eis context. + * See eis_device_swipe_new() and eis_swipe_update() for the receiver context API. + * + * @since 1.6 + */ + EIS_EVENT_SWIPE_UPDATE, + /** + * Event for the logical end to the current ongoing swipe gesture. + * + * @note This event is only generated on a sender eis context. + * See eis_device_swipe_new() and eis_swipe_end() for the receiver context API. + * + * @since 1.6 + */ + EIS_EVENT_SWIPE_END, + + /** + * Event for a pinch begin. Only one pinch gesture may be active + * at any time. + * + * @note This event is only generated on a sender eis context. + * See eis_device_pinch_new() and eis_pinch_begin() for the receiver context API. + * + * @since 1.6 + */ + EIS_EVENT_PINCH_BEGIN = 910, + /** + * Event for an update to the current ongoing pinch gesture. + * + * This library accumulates all updates to the gesture within the same + * device frame into one gesture event, e.g. where the gestures moves + * and rotates only one update event is generated. + * + * @note This event is only generated on a sender eis context. + * See eis_device_pinch_new() and eis_pinch_update() for the receiver context API. + * + * @since 1.6 + */ + EIS_EVENT_PINCH_UPDATE, + /** + * Event for the logical end to the current ongoing pinch gesture. + * + * @note This event is only generated on a sender eis context. + * See eis_device_pinch_new() and eis_pinch_end() for the receiver context API. + * + * @since 1.6 + */ + EIS_EVENT_PINCH_END, + + /** + * Event for a hold begin. Only one hold gesture may be active + * at any time. + * + * @note This event is only generated on a sender eis context. + * See eis_device_hold_new() and eis_hold_begin() for the receiver context API. + * + * @since 1.6 + */ + EIS_EVENT_HOLD_BEGIN = 920, + /** + * Event for the logical end to the current ongoing hold gesture. + * + * @note This event is only generated on a sender eis context. + * See eis_device_hold_new() and eis_hold_end() for the receiver context API. + * + * @since 1.6 + */ + EIS_EVENT_HOLD_END, + /** * A key sym logical press or release event * * @since 1.6 */ - EIS_EVENT_TEXT_KEYSYM = 900, + EIS_EVENT_TEXT_KEYSYM = 1000, /** * A UTF-8 text event @@ -1372,7 +1473,8 @@ eis_device_remove(struct eis_device *device); * - any buttons or keys logically down are released * - any modifiers logically down are released * - any touches logically down are released - * No events will be sent for these releases back to a neutral state. + * - any gestures logically active are ended + * No events will be sent for these state changes back to a neutral state. * * @param device A connected device */ @@ -1724,6 +1826,363 @@ eis_touch_get_user_data(struct eis_touch *touch); struct eis_device * eis_touch_get_device(struct eis_touch *touch); +/** + * @ingroup libeis-sender + * + * Cancel any ongoing swipe gesture on a device with the @ref EIS_DEVICE_CAP_GESTURES + * capability. This notifies the client of the gesture cancelation and the client + * may no longer send @ref EIS_EVENT_SWIPE_UPDATE or + * @ref EIS_EVENT_SWIPE_END for the current gestures. + * + * Due to the asynchronous nature of the protocol, events of type + * @ref EIS_EVENT_SWIPE_UPDATE and/or + * @ref EIS_EVENT_SWIPE_END may have already been sent by the client. An EIS + * implementation must ignore those events. + * + * This method is only available on a sender eis context. + * + * If no swipe gesture is currently in progress, this function does nothing. + * + * @since 1.6 + */ +void +eis_device_swipe_cancel(struct eis_device *device); + +/** + * @ingroup libeis-sender + * + * Cancel any ongoing pinch gesture on a device with the @ref EIS_DEVICE_CAP_GESTURES + * capability. This notifies the client of the gesture cancelation and the client + * may no longer send @ref EIS_EVENT_PINCH_UPDATE or + * @ref EIS_EVENT_PINCH_END for the current gestures. + * + * Due to the asynchronous nature of the protocol, events of type + * @ref EIS_EVENT_PINCH_UPDATE and/or + * @ref EIS_EVENT_PINCH_END may have already been sent by the client. An EIS + * implementation must ignore those events. + * + * This method is only available on a sender eis context. + * + * If no pinch gesture is currently in progress, this function does nothing. + * + * @since 1.6 + */ +void +eis_device_pinch_cancel(struct eis_device *device); + +/** + * @ingroup libeis-sender + * + * Cancel any ongoing hold gesture on a device with the @ref EIS_DEVICE_CAP_GESTURES + * capability. This notifies the client of the gesture cancelation and the client + * may no longer send @ref EIS_EVENT_HOLD_END for the current gestures. + * + * Due to the asynchronous nature of the protocol, events of type + * @ref EIS_EVENT_HOLD_END may have already been sent by the client. An EIS + * implementation must ignore those events. + * + * This method is only available on a sender eis context. + * + * If no hold gesture is currently in progress, this function does nothing. + * + * @since 1.6 + */ +void +eis_device_hold_cancel(struct eis_device *device); + +/** + * @ingroup libeis-receiver + * + * Initiate a new swipe gesture on a device with the @ref EIS_DEVICE_CAP_GESTURES + * capability. This gesture does not immediately send events, use + * eis_swipe_begin(), eis_swipe_update() and eis_swipe_end(). + * + * The returned gesture has a refcount of at least 1, use eis_swipe_unref() to + * release resources associated with this gesture + * + * This method is only available on an eis receiver context. + * + * If the underlying device does not support swipe gestures, this function + * returns NULL. + * + * @param device A device with @ref EIS_DEVICE_CAP_GESTURES capability + * @param nfingers The number of fingers, must be greater than 0 + * @return A new swipe gesture or NULL on error + * + * @since 1.6 + */ +struct eis_swipe * +eis_device_swipe_new(struct eis_device *device, uint32_t nfingers); + +/** + * @ingroup libeis-receiver + * + * Starts the swipe gesture. + * + * This function can only be called once on an eis_swipe object. Further + * calls to eis_swipe_begin() on the same object are silently ignored. + * + * @param swipe A newly created swipe gesture + * + * @since 1.6 + */ +void +eis_swipe_begin(struct eis_swipe *swipe); + +/** + * @ingroup libeis-receiver + * + * Update the swipe gestures's position by the given relative delta. + * + * @since 1.6 + */ +void +eis_swipe_update(struct eis_swipe *swipe, double x, double y); + +/** + * @ingroup libeis-receiver + * + * Release this swipe. After this call, the swipe event becomes inert and + * no longer responds to either eis_swipe_begin(), + * eis_swipe_update(), eis_swipe_cancel() or + * eis_swipe_end() and the caller should call eis_swipe_unref(). + * + * @since 1.6 + */ +void +eis_swipe_end(struct eis_swipe *swipe); + +/** + * @ingroup libeis-receiver + * + * Cancel this swipe. After this call, the swipe event becomes inert and + * no longer responds to either eis_swipe_begin(), + * eis_swipe_update(), eis_swipe_cancel() or + * eis_swipe_end() and the caller should call eis_swipe_unref(). + * + * @since 1.6 + */ +void +eis_swipe_cancel(struct eis_swipe *swipe); + +/** + * @ingroup libeis-receiver + * + * Increase the refcount of this struct by one. Use eis_swipe_unref() to + * decrease the refcount. + * + * @return the argument passed into the function + * + * @since 1.6 + */ +struct eis_swipe * +eis_swipe_ref(struct eis_swipe *swipe); + +/** + * @ingroup libeis-receiver + * + * Decrease the refcount of this struct by one. When the refcount reaches + * zero, all allocated resources for this struct are released. + * + * @return always NULL + * + * @since 1.6 + */ +struct eis_swipe * +eis_swipe_unref(struct eis_swipe *swipe); + +/** + * @ingroup libeis-receiver + * + * Initiate a new pinch gesture on a device with the @ref EIS_DEVICE_CAP_GESTURES + * capability. This gesture does not immediately send events, use + * eis_pinch_begin(), eis_pinch_update() and eis_pinch_end(). + * + * The returned gesture has a refcount of at least 1, use eis_pinch_unref() to + * release resources associated with this gesture + * + * This method is only available on an eis receiver context. + * + * If the underlying device does not support pinch gestures, this function + * returns NULL. + * + * @param device A device with @ref EIS_DEVICE_CAP_GESTURES capability + * @param nfingers The number of fingers, must be at least 2 + * @return A new pinch gesture or NULL on error + * + * @since 1.6 + */ +struct eis_pinch * +eis_device_pinch_new(struct eis_device *device, uint32_t nfingers); + +/** + * @ingroup libeis-receiver + * + * Starts the pinch gesture. + * + * This function can only be called once on an eis_pinch object. Further + * calls to eis_pinch_begin() on the same object are silently ignored. + * + * @param pinch A newly created pinch gesture + * + * @since 1.6 + */ +void +eis_pinch_begin(struct eis_pinch *pinch); + +/** + * @ingroup libeis-receiver + * + * Update the pinch gestures's position by the given relative delta. + * + * @since 1.6 + */ +void +eis_pinch_update(struct eis_pinch *pinch, double x, double y, double scale, double rotation); + +/** + * @ingroup libeis-receiver + * + * Release this pinch. After this call, the pinch event becomes inert and + * no longer responds to either eis_pinch_begin(), + * eis_pinch_update(), eis_pinch_cancel() or + * eis_pinch_end() and the caller should call eis_pinch_unref(). + * + * @since 1.6 + */ +void +eis_pinch_end(struct eis_pinch *pinch); + +/** + * @ingroup libeis-receiver + * + * Cancel this pinch. After this call, the pinch event becomes inert and + * no longer responds to either eis_pinch_begin(), + * eis_pinch_update(), eis_pinch_cancel() or + * eis_pinch_end() and the caller should call eis_pinch_unref(). + * + * @since 1.6 + */ +void +eis_pinch_cancel(struct eis_pinch *pinch); + +/** + * @ingroup libeis-receiver + * + * Increase the refcount of this struct by one. Use eis_pinch_unref() to + * decrease the refcount. + * + * @return the argument passed into the function + * + * @since 1.6 + */ +struct eis_pinch * +eis_pinch_ref(struct eis_pinch *pinch); + +/** + * @ingroup libeis-receiver + * + * Decrease the refcount of this struct by one. When the refcount reaches + * zero, all allocated resources for this struct are released. + * + * @return always NULL + * + * @since 1.6 + */ +struct eis_pinch * +eis_pinch_unref(struct eis_pinch *pinch); + +/** + * @ingroup libeis-receiver + * + * Initiate a new hold gesture on a device with the @ref EIS_DEVICE_CAP_GESTURES + * capability. This gesture does not immediately send events, use + * eis_hold_begin() and eis_hold_end(). + * + * The returned gesture has a refcount of at least 1, use eis_hold_unref() to + * release resources associated with this gesture + * + * This method is only available on an eis receiver context. + * + * If the underlying device does not support hold gestures, this function + * returns NULL. + * + * @param device A device with @ref EIS_DEVICE_CAP_GESTURES capability + * @param nfingers The number of fingers, must be greater than 0 + * @return A new hold gesture or NULL on error + * + * @since 1.6 + */ +struct eis_hold * +eis_device_hold_new(struct eis_device *device, uint32_t nfingers); + +/** + * @ingroup libeis-receiver + * + * Starts the hold gesture. + * + * This function can only be called once on an eis_hold object. Further + * calls to eis_hold_begin() on the same object are silently ignored. + * + * @param hold A newly created hold gesture + * + * @since 1.6 + */ +void +eis_hold_begin(struct eis_hold *hold); + +/** + * @ingroup libeis-receiver + * + * Release this hold. After this call, the hold event becomes inert and + * no longer responds to either eis_hold_begin(), + * eis_hold_cancel() or + * eis_hold_end() and the caller should call eis_hold_unref(). + * + * @since 1.6 + */ +void +eis_hold_end(struct eis_hold *hold); + +/** + * @ingroup libeis-receiver + * + * Cancel this hold. After this call, the hold event becomes inert and + * no longer responds to either eis_hold_begin(), + * eis_hold_cancel() or + * eis_hold_end() and the caller should call eis_hold_unref(). + * + * @since 1.6 + */ +void +eis_hold_cancel(struct eis_hold *hold); + +/** + * @ingroup libeis-receiver + * + * Increase the refcount of this struct by one. Use eis_hold_unref() to + * decrease the refcount. + * + * @return the argument passed into the function + * + * @since 1.6 + */ +struct eis_hold * +eis_hold_ref(struct eis_hold *hold); + +/** + * @ingroup libeis-receiver + * + * Decrease the refcount of this struct by one. When the refcount reaches + * zero, all allocated resources for this struct are released. + * + * @return always NULL + * + * @since 1.6 + */ +struct eis_hold * +eis_hold_unref(struct eis_hold *hold); + /** * @ingroup libeis-sender * @@ -1915,6 +2374,145 @@ eis_event_touch_get_y(struct eis_event *event); bool eis_event_touch_get_is_cancel(struct eis_event *event); +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_SWIPE_BEGIN, + * @ref EIS_EVENT_SWIPE_UPDATE or @ref EIS_EVENT_SWIPE_END + * return the finger count for this gesture. + * + * @since 1.6 + */ +uint32_t +eis_event_swipe_get_finger_count(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_SWIPE_UPDATE + * return the relative x movement of the logical center + * of the gesture in logical pixels or mm, depending on the device type. + * + * @since 1.6 + */ +double +eis_event_swipe_get_dx(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_SWIPE_UPDATE + * return the relative y movement of the logical center + * of the gesture in logical pixels or mm, depending on the device type. + * + * @since 1.6 + */ +double +eis_event_swipe_get_dy(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_SWIPE_END + * return true if the gesture was cancelled, false otherwise. + * + * @since 1.6 + */ +bool +eis_event_swipe_get_is_cancelled(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_PINCH_BEGIN, + * @ref EIS_EVENT_PINCH_UPDATE or @ref EIS_EVENT_PINCH_END + * return the finger count for this gesture. + * + * @since 1.6 + */ +uint32_t +eis_event_pinch_get_finger_count(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_PINCH_UPDATE + * return the relative x movement of the logical center + * of the gesture in logical pixels or mm, depending on the device type. + * + * @since 1.6 + */ +double +eis_event_pinch_get_dx(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_PINCH_UPDATE + * return the relative y movement of the logical center + * of the gesture in logical pixels or mm, depending on the device type. + * + * @since 1.6 + */ +double +eis_event_pinch_get_dy(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_PINCH_UPDATE + * return the relative angle of rotation in degrees clockwise. + * + * @since 1.6 + */ +double +eis_event_pinch_get_rotation(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_PINCH_UPDATE + * return the normalized scale of the gesture. + * + * @since 1.6 + */ +double +eis_event_pinch_get_scale(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_PINCH_END + * return true if the gesture was cancelled, false otherwise. + * + * @since 1.6 + */ +bool +eis_event_pinch_get_is_cancelled(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_HOLD_BEGIN + * or @ref EIS_EVENT_HOLD_END + * return the finger count for this gesture. + * + * @since 1.6 + */ +uint32_t +eis_event_hold_get_finger_count(struct eis_event *event); + +/** + * @ingroup libeis-sender + * + * For an event of type @ref EIS_EVENT_HOLD_END + * return true if the gesture was cancelled, false otherwise. + * + * @since 1.6 + */ +bool +eis_event_hold_get_is_cancelled(struct eis_event *event); + /** * @ingroup libeis-sender * diff --git a/src/meson.build b/src/meson.build index 2da91ad..1abfa38 100644 --- a/src/meson.build +++ b/src/meson.build @@ -67,6 +67,7 @@ if build_libei 'libei-device.c', 'libei-event.c', 'libei-fd.c', + 'libei-gestures.c', 'libei-handshake.c', 'libei-keyboard.c', 'libei-log.c', @@ -147,6 +148,7 @@ if build_libeis 'libeis-device.c', 'libeis-event.c', 'libeis-fd.c', + 'libeis-gestures.c', 'libeis-handshake.c', 'libeis-keyboard.c', 'libeis-log.c', diff --git a/src/util-munit.h b/src/util-munit.h index c5d9fad..8423b33 100644 --- a/src/util-munit.h +++ b/src/util-munit.h @@ -135,6 +135,12 @@ static MunitResult _func(const MunitParameter params[], void *user_data) #define MUNIT_TEST_PARAM(name_) \ munit_parameters_get(params, name_) +#define MUNIT_TEST_PARAM_BOOL(name_) \ + streq(munit_parameters_get(params, name_), "true") + +#define MUNIT_TEST_PARAM_INT(name_) \ + atoi(munit_parameters_get(params, name_)) + /** * Defines a struct global_setup_function in a custom ELF section that we can * then find in munit_tests_run() to do setup based on argv and/or pass userdata diff --git a/test/eierpecken.c b/test/eierpecken.c index 45f3a4a..5178cb8 100644 --- a/test/eierpecken.c +++ b/test/eierpecken.c @@ -74,6 +74,7 @@ struct peck { struct eis_device *eis_button; struct eis_device *eis_scroll; struct eis_device *eis_touch; + struct eis_device *eis_gestures; struct ei_seat *ei_seat; struct ei_device *ei_pointer; @@ -82,6 +83,7 @@ struct peck { struct ei_device *ei_button; struct ei_device *ei_scroll; struct ei_device *ei_touch; + struct ei_device *ei_gestures; uint64_t now; @@ -174,6 +176,7 @@ peck_destroy(struct peck *peck) eis_device_unref(peck->eis_touch); eis_device_unref(peck->eis_button); eis_device_unref(peck->eis_scroll); + eis_device_unref(peck->eis_gestures); eis_seat_unref(peck->eis_seat); ei_device_unref(peck->ei_pointer); @@ -182,6 +185,7 @@ peck_destroy(struct peck *peck) ei_device_unref(peck->ei_touch); ei_device_unref(peck->ei_button); ei_device_unref(peck->ei_scroll); + ei_device_unref(peck->ei_gestures); ei_seat_unref(peck->ei_seat); ei_unref(peck->ei); @@ -350,6 +354,13 @@ peck_eis_get_default_touch(struct peck *peck) return peck->eis_touch; } +struct eis_device * +peck_eis_get_default_gestures(struct peck *peck) +{ + munit_assert_ptr_not_null(peck->eis_gestures); + return peck->eis_gestures; +} + struct ei_seat * peck_ei_get_default_seat(struct peck *peck) { @@ -399,6 +410,13 @@ peck_ei_get_default_touch(struct peck *peck) return peck->ei_touch; } +struct ei_device * +peck_ei_get_default_gestures(struct peck *peck) +{ + munit_assert_ptr_not_null(peck->ei_gestures); + return peck->ei_gestures; +} + /* Ensures that device frames in tests always have an ascending and fixed * interval. Every time this is called it adds 10ms to the time offset. */ @@ -749,6 +767,7 @@ peck_enable_eis_behavior(struct peck *peck, enum peck_eis_behavior behavior) peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); break; case PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT: case PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE: @@ -760,6 +779,9 @@ peck_enable_eis_behavior(struct peck *peck, enum peck_eis_behavior behavior) case PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE: case PECK_EIS_BEHAVIOR_ADD_KEYBOARD: case PECK_EIS_BEHAVIOR_ADD_TOUCH: + case PECK_EIS_BEHAVIOR_ADD_GESTURES: + flag_set(peck->eis_behavior, behavior); + break; case PECK_EIS_BEHAVIOR_HANDLE_DEVICE_READY: flag_set(peck->eis_behavior, behavior); break; @@ -818,6 +840,7 @@ peck_disable_eis_behavior(struct peck *peck, enum peck_eis_behavior behavior) case PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE: case PECK_EIS_BEHAVIOR_ADD_KEYBOARD: case PECK_EIS_BEHAVIOR_ADD_TOUCH: + case PECK_EIS_BEHAVIOR_ADD_GESTURES: case PECK_EIS_BEHAVIOR_HANDLE_DEVICE_READY: case PECK_EIS_BEHAVIOR_REJECT_CLIENT: case PECK_EIS_BEHAVIOR_ACCEPT_CLIENT: @@ -852,6 +875,7 @@ peck_enable_ei_behavior(struct peck *peck, enum peck_ei_behavior behavior) peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_TOUCH); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_BUTTON); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_GESTURES); break; case PECK_EI_BEHAVIOR_AUTOSTART: case PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER: @@ -860,6 +884,7 @@ peck_enable_ei_behavior(struct peck *peck, enum peck_ei_behavior behavior) case PECK_EI_BEHAVIOR_HANDLE_ADDED_TOUCH: case PECK_EI_BEHAVIOR_HANDLE_ADDED_BUTTON: case PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL: + case PECK_EI_BEHAVIOR_HANDLE_ADDED_GESTURES: case PECK_EI_BEHAVIOR_HANDLE_FRAME: case PECK_EI_BEHAVIOR_HANDLE_SYNC: case PECK_EI_BEHAVIOR_HANDLE_RESUMED: @@ -901,6 +926,7 @@ peck_disable_ei_behavior(struct peck *peck, enum peck_ei_behavior behavior) case PECK_EI_BEHAVIOR_HANDLE_ADDED_TOUCH: case PECK_EI_BEHAVIOR_HANDLE_ADDED_BUTTON: case PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL: + case PECK_EI_BEHAVIOR_HANDLE_ADDED_GESTURES: case PECK_EI_BEHAVIOR_HANDLE_FRAME: case PECK_EI_BEHAVIOR_HANDLE_SYNC: case PECK_EI_BEHAVIOR_HANDLE_RESUMED: @@ -921,6 +947,7 @@ peck_create_eis_seat(struct peck *peck, struct eis_client *client) eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_BUTTON); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_SCROLL); + eis_seat_configure_capability(seat, EIS_DEVICE_CAP_GESTURES); log_debug(peck, "EIS adding seat: '%s'\n", eis_seat_get_name(seat)); eis_seat_add(seat); @@ -1007,6 +1034,23 @@ peck_eis_create_touch(struct peck *peck, struct eis_seat *seat, const char *name return device; } +static inline struct eis_device * +peck_eis_create_gestures(struct peck *peck, struct eis_seat *seat, const char *name) +{ + struct eis_device *device = eis_seat_new_device(seat); + _unref_(eis_region) *region = eis_device_new_region(device); + + eis_region_set_offset(region, 0, 0); + eis_region_set_size(region, 1920, 1080); + + eis_device_configure_name(device, name); + eis_device_configure_capability(device, EIS_DEVICE_CAP_GESTURES); + eis_region_add(region); + eis_device_add(device); + + return device; +} + static inline void peck_handle_eis_seat_bind(struct peck *peck, struct eis_event *e) { @@ -1098,13 +1142,35 @@ peck_handle_eis_seat_bind(struct peck *peck, struct eis_event *e) } } + if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_GESTURES)) { + if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_ADD_GESTURES) && + !peck->eis_gestures) { + log_debug(peck, "EIS creating default gesture\n"); + _unref_(eis_device) *gestures = + peck_eis_create_gestures(peck, seat, "default gesture"); + + if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_RESUME_DEVICE)) + eis_device_resume(gestures); + + peck->eis_gestures = eis_device_ref(gestures); + } + } else { + if (peck->eis_gestures) { + log_debug(peck, "EIS removing default gesture\n"); + + _unref_(eis_device) *gestures = steal(&peck->eis_gestures); + eis_device_remove(gestures); + } + } + /* Removing all caps means removing the seat */ if (!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_BUTTON) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_SCROLL) && - !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH)) + !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH) && + !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_GESTURES)) eis_seat_remove(seat); } @@ -1124,6 +1190,8 @@ peck_eis_device_remove(struct peck *peck, struct eis_device *device) peck->eis_button = eis_device_unref(device); if (device == peck->eis_scroll) peck->eis_scroll = eis_device_unref(device); + if (device == peck->eis_gestures) + peck->eis_gestures = eis_device_unref(device); } bool @@ -1205,6 +1273,14 @@ _peck_dispatch_eis(struct peck *peck, int lineno) case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_TOUCH_MOTION: + case EIS_EVENT_SWIPE_BEGIN: + case EIS_EVENT_SWIPE_UPDATE: + case EIS_EVENT_SWIPE_END: + case EIS_EVENT_PINCH_BEGIN: + case EIS_EVENT_PINCH_UPDATE: + case EIS_EVENT_PINCH_END: + case EIS_EVENT_HOLD_BEGIN: + case EIS_EVENT_HOLD_END: case EIS_EVENT_TEXT_KEYSYM: case EIS_EVENT_TEXT_UTF8: peck->eis_needs_frame = true; @@ -1291,6 +1367,10 @@ peck_check_ei_added(struct peck *peck, struct ei_event *e) flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL)) return tristate_yes; + if (ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES) && + flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_ADDED_GESTURES)) + return tristate_yes; + return tristate_unset; } @@ -1367,10 +1447,22 @@ _peck_dispatch_ei(struct peck *peck, int lineno) case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_UP: case EI_EVENT_TOUCH_MOTION: + case EI_EVENT_SWIPE_BEGIN: + case EI_EVENT_SWIPE_UPDATE: + case EI_EVENT_SWIPE_END: + case EI_EVENT_PINCH_BEGIN: + case EI_EVENT_PINCH_UPDATE: + case EI_EVENT_PINCH_END: + case EI_EVENT_HOLD_BEGIN: + case EI_EVENT_HOLD_END: case EI_EVENT_TEXT_KEYSYM: case EI_EVENT_TEXT_UTF8: peck->ei_needs_frame = true; break; + case EI_EVENT_SWIPE_CANCELLED: + case EI_EVENT_PINCH_CANCELLED: + case EI_EVENT_HOLD_CANCELLED: + break; } log_debug(peck, @@ -1405,6 +1497,7 @@ _peck_dispatch_ei(struct peck *peck, int lineno) EI_DEVICE_CAP_TOUCH, EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, + EI_DEVICE_CAP_GESTURES, NULL); break; } @@ -1428,6 +1521,9 @@ _peck_dispatch_ei(struct peck *peck, int lineno) if (!peck->ei_scroll && ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) peck->ei_scroll = ei_device_ref(device); + if (!peck->ei_gestures && + ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) + peck->ei_gestures = ei_device_ref(device); break; } case EI_EVENT_DEVICE_RESUMED: @@ -1688,6 +1784,70 @@ _peck_ei_touch_event(struct ei *ei, enum ei_event_type type, double x, double y, return event; } +struct ei_event * +_peck_ei_swipe_event(struct ei *ei, enum ei_event_type type, int lineno) +{ + assert(type == EI_EVENT_SWIPE_BEGIN || type == EI_EVENT_SWIPE_UPDATE || + type == EI_EVENT_SWIPE_END); + + struct ei_event *event = _peck_ei_next_event(ei, type, lineno); + + return event; +} + +struct ei_event * +_peck_ei_pinch_event(struct ei *ei, enum ei_event_type type, int lineno) +{ + assert(type == EI_EVENT_PINCH_BEGIN || type == EI_EVENT_PINCH_UPDATE || + type == EI_EVENT_PINCH_END); + + struct ei_event *event = _peck_ei_next_event(ei, type, lineno); + + return event; +} + +struct ei_event * +_peck_ei_hold_event(struct ei *ei, enum ei_event_type type, int lineno) +{ + assert(type == EI_EVENT_HOLD_BEGIN || type == EI_EVENT_HOLD_END); + + struct ei_event *event = _peck_ei_next_event(ei, type, lineno); + + return event; +} + +struct eis_event * +_peck_eis_swipe_event(struct eis *eis, enum eis_event_type type, int lineno) +{ + assert(type == EIS_EVENT_SWIPE_BEGIN || type == EIS_EVENT_SWIPE_UPDATE || + type == EIS_EVENT_SWIPE_END); + + struct eis_event *event = _peck_eis_next_event(eis, type, lineno); + + return event; +} + +struct eis_event * +_peck_eis_pinch_event(struct eis *eis, enum eis_event_type type, int lineno) +{ + assert(type == EIS_EVENT_PINCH_BEGIN || type == EIS_EVENT_PINCH_UPDATE || + type == EIS_EVENT_PINCH_END); + + struct eis_event *event = _peck_eis_next_event(eis, type, lineno); + + return event; +} + +struct eis_event * +_peck_eis_hold_event(struct eis *eis, enum eis_event_type type, int lineno) +{ + assert(type == EIS_EVENT_HOLD_BEGIN || type == EIS_EVENT_HOLD_END); + + struct eis_event *event = _peck_eis_next_event(eis, type, lineno); + + return event; +} + const char * peck_ei_event_name(struct ei_event *e) { @@ -1726,6 +1886,17 @@ peck_ei_event_type_name(enum ei_event_type type) CASE_STRING(TOUCH_DOWN); CASE_STRING(TOUCH_UP); CASE_STRING(TOUCH_MOTION); + CASE_STRING(SWIPE_BEGIN); + CASE_STRING(SWIPE_UPDATE); + CASE_STRING(SWIPE_END); + CASE_STRING(SWIPE_CANCELLED); + CASE_STRING(PINCH_BEGIN); + CASE_STRING(PINCH_UPDATE); + CASE_STRING(PINCH_END); + CASE_STRING(PINCH_CANCELLED); + CASE_STRING(HOLD_BEGIN); + CASE_STRING(HOLD_END); + CASE_STRING(HOLD_CANCELLED); CASE_STRING(TEXT_KEYSYM); CASE_STRING(TEXT_UTF8); } @@ -1767,6 +1938,14 @@ peck_eis_event_type_name(enum eis_event_type type) CASE_STRING(TOUCH_DOWN); CASE_STRING(TOUCH_UP); CASE_STRING(TOUCH_MOTION); + CASE_STRING(SWIPE_BEGIN); + CASE_STRING(SWIPE_UPDATE); + CASE_STRING(SWIPE_END); + CASE_STRING(PINCH_BEGIN); + CASE_STRING(PINCH_UPDATE); + CASE_STRING(PINCH_END); + CASE_STRING(HOLD_BEGIN); + CASE_STRING(HOLD_END); CASE_STRING(FRAME); CASE_STRING(TEXT_KEYSYM); CASE_STRING(TEXT_UTF8); diff --git a/test/eierpecken.h b/test/eierpecken.h index e2af837..2be58d2 100644 --- a/test/eierpecken.h +++ b/test/eierpecken.h @@ -106,6 +106,7 @@ enum peck_eis_behavior { PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE, PECK_EIS_BEHAVIOR_ADD_KEYBOARD, PECK_EIS_BEHAVIOR_ADD_TOUCH, + PECK_EIS_BEHAVIOR_ADD_GESTURES, PECK_EIS_BEHAVIOR_RESUME_DEVICE, PECK_EIS_BEHAVIOR_SUSPEND_DEVICE, @@ -137,6 +138,7 @@ enum peck_ei_behavior { PECK_EI_BEHAVIOR_HANDLE_ADDED_TOUCH, PECK_EI_BEHAVIOR_HANDLE_ADDED_BUTTON, PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL, + PECK_EI_BEHAVIOR_HANDLE_ADDED_GESTURES, PECK_EI_BEHAVIOR_HANDLE_RESUMED, PECK_EI_BEHAVIOR_HANDLE_PAUSED, @@ -246,6 +248,9 @@ peck_eis_get_default_pointer_absolute(struct peck *peck); struct eis_device * peck_eis_get_default_touch(struct peck *peck); +struct eis_device * +peck_eis_get_default_gestures(struct peck *peck); + struct eis_device * peck_eis_get_default_button(struct peck *peck); @@ -273,6 +278,9 @@ peck_ei_get_default_pointer_absolute(struct peck *peck); struct ei_device * peck_ei_get_default_touch(struct peck *peck); +struct ei_device * +peck_ei_get_default_gestures(struct peck *peck); + uint64_t peck_ei_now(struct peck *peck); @@ -375,6 +383,62 @@ _peck_eis_touch_event(struct eis *eis, enum eis_event_type type, double x, doubl struct ei_event * _peck_ei_touch_event(struct ei *ei, enum ei_event_type type, double x, double y, int lineno); +#define peck_eis_swipe_begin(_ei) \ + _peck_eis_swipe_event(_ei, EIS_EVENT_SWIPE_BEGIN, __LINE__) +#define peck_eis_swipe_update(_ei) \ + _peck_eis_swipe_event(_ei, EIS_EVENT_SWIPE_UPDATE, __LINE__) +#define peck_eis_swipe_end(_ei) \ + _peck_eis_swipe_event(_ei, EIS_EVENT_SWIPE_END, __LINE__) + +struct eis_event * +_peck_eis_swipe_event(struct eis *eis, enum eis_event_type type, int lineno); + +#define peck_eis_pinch_begin(_ei) \ + _peck_eis_pinch_event(_ei, EIS_EVENT_PINCH_BEGIN, __LINE__) +#define peck_eis_pinch_update(_ei) \ + _peck_eis_pinch_event(_ei, EIS_EVENT_PINCH_UPDATE, __LINE__) +#define peck_eis_pinch_end(_ei) \ + _peck_eis_pinch_event(_ei, EIS_EVENT_PINCH_END, __LINE__) + +struct eis_event * +_peck_eis_pinch_event(struct eis *eis, enum eis_event_type type, int lineno); + +#define peck_eis_hold_begin(_ei) \ + _peck_eis_hold_event(_ei, EIS_EVENT_HOLD_BEGIN, __LINE__) +#define peck_eis_hold_end(_ei) \ + _peck_eis_hold_event(_ei, EIS_EVENT_HOLD_END, __LINE__) + +struct eis_event * +_peck_eis_hold_event(struct eis *eis, enum eis_event_type type, int lineno); + +#define peck_ei_swipe_begin(_ei) \ + _peck_ei_swipe_event(_ei, EI_EVENT_SWIPE_BEGIN, __LINE__) +#define peck_ei_swipe_update(_ei) \ + _peck_ei_swipe_event(_ei, EI_EVENT_SWIPE_UPDATE, __LINE__) +#define peck_ei_swipe_end(_ei) \ + _peck_ei_swipe_event(_ei, EI_EVENT_SWIPE_END, __LINE__) + +struct ei_event * +_peck_ei_swipe_event(struct ei *ei, enum ei_event_type type, int lineno); + +#define peck_ei_pinch_begin(_ei) \ + _peck_ei_pinch_event(_ei, EI_EVENT_PINCH_BEGIN, __LINE__) +#define peck_ei_pinch_update(_ei) \ + _peck_ei_pinch_event(_ei, EI_EVENT_PINCH_UPDATE, __LINE__) +#define peck_ei_pinch_end(_ei) \ + _peck_ei_pinch_event(_ei, EI_EVENT_PINCH_END, __LINE__) + +struct ei_event * +_peck_ei_pinch_event(struct ei *ei, enum ei_event_type type, int lineno); + +#define peck_ei_hold_begin(_ei) \ + _peck_ei_hold_event(_ei, EI_EVENT_HOLD_BEGIN, __LINE__) +#define peck_ei_hold_end(_ei) \ + _peck_ei_hold_event(_ei, EI_EVENT_HOLD_END, __LINE__) + +struct ei_event * +_peck_ei_hold_event(struct ei *ei, enum ei_event_type type, int lineno); + const char * peck_ei_event_name(struct ei_event *e); @@ -398,6 +462,9 @@ 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(ei_swipe); +DEFINE_UNREF_CLEANUP_FUNC(ei_pinch); +DEFINE_UNREF_CLEANUP_FUNC(ei_hold); DEFINE_UNREF_CLEANUP_FUNC(eis); DEFINE_UNREF_CLEANUP_FUNC(eis_client); @@ -408,6 +475,9 @@ DEFINE_UNREF_CLEANUP_FUNC(eis_keymap); DEFINE_UNREF_CLEANUP_FUNC(eis_seat); DEFINE_UNREF_CLEANUP_FUNC(eis_region); DEFINE_UNREF_CLEANUP_FUNC(eis_ping); +DEFINE_UNREF_CLEANUP_FUNC(eis_swipe); +DEFINE_UNREF_CLEANUP_FUNC(eis_pinch); +DEFINE_UNREF_CLEANUP_FUNC(eis_hold); /* Macros intended just for readability to make it more obvious which part of a test handles server vs client */ diff --git a/test/test-ei-device.c b/test/test-ei-device.c index 079c93f..e5f3768 100644 --- a/test/test-ei-device.c +++ b/test/test-ei-device.c @@ -67,11 +67,14 @@ MUNIT_TEST(test_ei_device_basics) munit_assert_false( eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)); + munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)); eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER); munit_assert_true(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)); eis_device_configure_capability(device, EIS_DEVICE_CAP_KEYBOARD); munit_assert_true(eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)); + eis_device_configure_capability(device, EIS_DEVICE_CAP_GESTURES); + munit_assert_true(eis_device_has_capability(device, EIS_DEVICE_CAP_GESTURES)); eis_device_add(device); @@ -100,6 +103,7 @@ MUNIT_TEST(test_ei_device_basics) munit_assert_false( ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_false(ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)); + munit_assert_true(ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)); } return MUNIT_OK; @@ -1940,6 +1944,176 @@ MUNIT_TEST(test_ei_device_remove_no_stop_emulating_event) return MUNIT_OK; } +MUNIT_TEST_WITH_PARAMS(test_ei_device_gesture_swipe, + "@cancel", + "true", + "false", + "@nfingers", + "1", + "2", + "3", + "4") +{ + _unref_(peck) *peck = peck_new(); + bool cancel = MUNIT_TEST_PARAM_BOOL("@cancel"); + int nfingers = MUNIT_TEST_PARAM_INT("@nfingers"); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + with_client(peck) { + struct ei_device *device = peck_ei_get_default_gestures(peck); + _unref_(ei_swipe) *swipe = ei_device_swipe_new(device, nfingers); + ei_swipe_begin(swipe); + ei_device_frame(device, peck_ei_now(peck)); + + ei_swipe_update(swipe, 1.1, 2.2); + ei_device_frame(device, peck_ei_now(peck)); + + if (cancel) + ei_swipe_cancel(swipe); + else + ei_swipe_end(swipe); + ei_device_frame(device, peck_ei_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *begin = peck_eis_swipe_begin(eis); + munit_assert_int(eis_event_swipe_get_finger_count(begin), ==, nfingers); + + _unref_(eis_event) *update = peck_eis_swipe_update(eis); + munit_assert_int(eis_event_swipe_get_finger_count(update), ==, nfingers); + munit_assert_float(eis_event_swipe_get_dx(update), ==, 1.1); + munit_assert_float(eis_event_swipe_get_dy(update), ==, 2.2); + + _unref_(eis_event) *end = peck_eis_swipe_end(eis); + munit_assert_int(eis_event_swipe_get_finger_count(end), ==, nfingers); + if (cancel) + munit_assert_true(eis_event_swipe_get_is_cancelled(end)); + else + munit_assert_false(eis_event_swipe_get_is_cancelled(end)); + + peck_assert_no_eis_events(eis); + } + + return MUNIT_OK; +} + +MUNIT_TEST_WITH_PARAMS(test_ei_device_gesture_pinch, + "@cancel", + "true", + "false", + "@nfingers", + "2", + "3", + "4") +{ + _unref_(peck) *peck = peck_new(); + bool cancel = MUNIT_TEST_PARAM_BOOL("@cancel"); + int nfingers = MUNIT_TEST_PARAM_INT("@nfingers"); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + with_client(peck) { + struct ei_device *device = peck_ei_get_default_gestures(peck); + _unref_(ei_pinch) *pinch = ei_device_pinch_new(device, nfingers); + ei_pinch_begin(pinch); + ei_device_frame(device, peck_ei_now(peck)); + + ei_pinch_update(pinch, 1.1, 2.2, 3.3, 4.4); + ei_device_frame(device, peck_ei_now(peck)); + + if (cancel) + ei_pinch_cancel(pinch); + else + ei_pinch_end(pinch); + ei_device_frame(device, peck_ei_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *begin = peck_eis_pinch_begin(eis); + munit_assert_int(eis_event_pinch_get_finger_count(begin), ==, nfingers); + + _unref_(eis_event) *update = peck_eis_pinch_update(eis); + munit_assert_int(eis_event_pinch_get_finger_count(update), ==, nfingers); + munit_assert_float(eis_event_pinch_get_dx(update), ==, 1.1); + munit_assert_float(eis_event_pinch_get_dy(update), ==, 2.2); + munit_assert_float(eis_event_pinch_get_scale(update), ==, 3.3); + munit_assert_float(eis_event_pinch_get_rotation(update), ==, 4.4); + + _unref_(eis_event) *end = peck_eis_pinch_end(eis); + munit_assert_int(eis_event_pinch_get_finger_count(end), ==, nfingers); + if (cancel) + munit_assert_true(eis_event_pinch_get_is_cancelled(end)); + else + munit_assert_false(eis_event_pinch_get_is_cancelled(end)); + + peck_assert_no_eis_events(eis); + } + + return MUNIT_OK; +} + +MUNIT_TEST_WITH_PARAMS(test_ei_device_gesture_hold, + "@cancel", + "true", + "false", + "@nfingers", + "1", + "2", + "3", + "4") +{ + _unref_(peck) *peck = peck_new(); + bool cancel = MUNIT_TEST_PARAM_BOOL("@cancel"); + int nfingers = MUNIT_TEST_PARAM_INT("@nfingers"); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + with_client(peck) { + struct ei_device *device = peck_ei_get_default_gestures(peck); + _unref_(ei_hold) *hold = ei_device_hold_new(device, nfingers); + ei_hold_begin(hold); + ei_device_frame(device, peck_ei_now(peck)); + + if (cancel) + ei_hold_cancel(hold); + else + ei_hold_end(hold); + ei_device_frame(device, peck_ei_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + _unref_(eis_event) *begin = peck_eis_hold_begin(eis); + munit_assert_int(eis_event_hold_get_finger_count(begin), ==, nfingers); + + _unref_(eis_event) *end = peck_eis_hold_end(eis); + munit_assert_int(eis_event_hold_get_finger_count(end), ==, nfingers); + if (cancel) + munit_assert_true(eis_event_hold_get_is_cancelled(end)); + else + munit_assert_false(eis_event_hold_get_is_cancelled(end)); + + peck_assert_no_eis_events(eis); + } + + return MUNIT_OK; +} + MUNIT_TEST(test_passive_ei_device_start_stop_emulating) { _unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER); @@ -3006,3 +3180,759 @@ MUNIT_TEST(test_passive_ei_flush_frame) return MUNIT_OK; } + +MUNIT_TEST_WITH_PARAMS(test_passive_ei_device_gesture_swipe, + "@cancel", + "true", + "false", + "@nfingers", + "1", + "2", + "3", + "4") +{ + _unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER); + bool cancel = MUNIT_TEST_PARAM_BOOL("@cancel"); + int nfingers = MUNIT_TEST_PARAM_INT("@nfingers"); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + uint32_t sequence = 123; + + with_server(peck) { + struct eis_device *device = peck_eis_get_default_gestures(peck); + + eis_device_start_emulating(device, sequence); + + _unref_(eis_swipe) *swipe = eis_device_swipe_new(device, nfingers); + eis_swipe_begin(swipe); + eis_device_frame(device, peck_eis_now(peck)); + + eis_swipe_update(swipe, 1.1, 2.2); + eis_device_frame(device, peck_eis_now(peck)); + + if (cancel) + eis_swipe_cancel(swipe); + else + eis_swipe_end(swipe); + eis_device_frame(device, peck_eis_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); + + _unref_(ei_event) *begin = peck_ei_swipe_begin(ei); + munit_assert_int(ei_event_swipe_get_finger_count(begin), ==, nfingers); + + _unref_(ei_event) *update = peck_ei_swipe_update(ei); + munit_assert_int(ei_event_swipe_get_finger_count(update), ==, nfingers); + munit_assert_float(ei_event_swipe_get_dx(update), ==, 1.1); + munit_assert_float(ei_event_swipe_get_dy(update), ==, 2.2); + + _unref_(ei_event) *end = peck_ei_swipe_end(ei); + munit_assert_int(ei_event_swipe_get_finger_count(end), ==, nfingers); + if (cancel) + munit_assert_true(ei_event_swipe_get_is_cancelled(end)); + else + munit_assert_false(ei_event_swipe_get_is_cancelled(end)); + peck_assert_no_ei_events(ei); + } + + return MUNIT_OK; +} + +MUNIT_TEST_WITH_PARAMS(test_passive_ei_device_gesture_pinch, + "@cancel", + "true", + "false", + "@nfingers", + "2", + "3", + "4") +{ + _unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER); + bool cancel = MUNIT_TEST_PARAM_BOOL("@cancel"); + int nfingers = MUNIT_TEST_PARAM_INT("@nfingers"); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + uint32_t sequence = 123; + + with_server(peck) { + struct eis_device *device = peck_eis_get_default_gestures(peck); + + eis_device_start_emulating(device, sequence); + + _unref_(eis_pinch) *pinch = eis_device_pinch_new(device, nfingers); + eis_pinch_begin(pinch); + eis_device_frame(device, peck_eis_now(peck)); + + eis_pinch_update(pinch, 1.1, 2.2, 3.3, 4.4); + eis_device_frame(device, peck_eis_now(peck)); + + if (cancel) + eis_pinch_cancel(pinch); + else + eis_pinch_end(pinch); + eis_device_frame(device, peck_eis_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); + + _unref_(ei_event) *begin = peck_ei_pinch_begin(ei); + munit_assert_int(ei_event_pinch_get_finger_count(begin), ==, nfingers); + + _unref_(ei_event) *update = peck_ei_pinch_update(ei); + munit_assert_int(ei_event_pinch_get_finger_count(update), ==, nfingers); + munit_assert_float(ei_event_pinch_get_dx(update), ==, 1.1); + munit_assert_float(ei_event_pinch_get_dy(update), ==, 2.2); + munit_assert_float(ei_event_pinch_get_scale(update), ==, 3.3); + munit_assert_float(ei_event_pinch_get_rotation(update), ==, 4.4); + + _unref_(ei_event) *end = peck_ei_pinch_end(ei); + munit_assert_int(ei_event_pinch_get_finger_count(end), ==, nfingers); + if (cancel) + munit_assert_true(ei_event_pinch_get_is_cancelled(end)); + else + munit_assert_false(ei_event_pinch_get_is_cancelled(end)); + + peck_assert_no_ei_events(ei); + } + + return MUNIT_OK; +} + +MUNIT_TEST_WITH_PARAMS(test_passive_ei_device_gesture_hold, + "@cancel", + "true", + "false", + "@nfingers", + "1", + "2", + "3", + "4") +{ + _unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER); + bool cancel = MUNIT_TEST_PARAM_BOOL("@cancel"); + int nfingers = MUNIT_TEST_PARAM_INT("@nfingers"); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + + peck_dispatch_until_stable(peck); + + uint32_t sequence = 123; + with_server(peck) { + struct eis_device *device = peck_eis_get_default_gestures(peck); + + eis_device_start_emulating(device, sequence); + + _unref_(eis_hold) *hold = eis_device_hold_new(device, nfingers); + eis_hold_begin(hold); + eis_device_frame(device, peck_eis_now(peck)); + + if (cancel) + eis_hold_cancel(hold); + else + eis_hold_end(hold); + eis_device_frame(device, peck_eis_now(peck)); + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); + + _unref_(ei_event) *begin = peck_ei_hold_begin(ei); + munit_assert_int(ei_event_hold_get_finger_count(begin), ==, nfingers); + + _unref_(ei_event) *end = peck_ei_hold_end(ei); + munit_assert_int(ei_event_hold_get_finger_count(end), ==, nfingers); + if (cancel) + munit_assert_true(ei_event_hold_get_is_cancelled(end)); + else + munit_assert_false(ei_event_hold_get_is_cancelled(end)); + + peck_assert_no_ei_events(ei); + } + + return MUNIT_OK; +} + +enum gesture_type { SWIPE, PINCH, HOLD }; + +static inline enum gesture_type +gesture_type_from_string(const char *string) +{ + if (streq(string, "swipe")) + return SWIPE; + else if (streq(string, "pinch")) + return PINCH; + else if (streq(string, "hold")) + return HOLD; + abort(); +} + +MUNIT_TEST_WITH_PARAMS(test_eis_device_gesture_cancel_noop, "@gesture", "swipe", "pinch", "hold") +{ + enum gesture_type gesture = gesture_type_from_string(MUNIT_TEST_PARAM("@gesture")); + _unref_(peck) *peck = peck_new(); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + /* Test 1: Canceling when no gesture is active - should be a noop */ + with_server(peck) { + struct eis_device *device = peck_eis_get_default_gestures(peck); + + switch (gesture) { + case SWIPE: + eis_device_swipe_cancel(device); + break; + case PINCH: + eis_device_pinch_cancel(device); + break; + case HOLD: + eis_device_hold_cancel(device); + break; + } + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + /* Should have no events since no gesture was active */ + peck_assert_no_ei_events(ei); + } + + /* Test 2: Canceling after gesture has ended - should be a noop */ + int nfingers = 2; + + with_client(peck) { + struct ei_device *device = peck_ei_get_default_gestures(peck); + + switch (gesture) { + case SWIPE: { + _unref_(ei_swipe) *swipe = ei_device_swipe_new(device, nfingers); + ei_swipe_begin(swipe); + ei_device_frame(device, peck_ei_now(peck)); + ei_swipe_end(swipe); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + case PINCH: { + _unref_(ei_pinch) *pinch = ei_device_pinch_new(device, nfingers); + ei_pinch_begin(pinch); + ei_device_frame(device, peck_ei_now(peck)); + ei_pinch_end(pinch); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + case HOLD: { + _unref_(ei_hold) *hold = ei_device_hold_new(device, nfingers); + ei_hold_begin(hold); + ei_device_frame(device, peck_ei_now(peck)); + ei_hold_end(hold); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + } + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + struct eis_device *device = peck_eis_get_default_gestures(peck); + + switch (gesture) { + case SWIPE: { + _unref_(eis_event) *begin = peck_eis_swipe_begin(eis); + _unref_(eis_event) *end = peck_eis_swipe_end(eis); + + /* Cancel after end - should be a noop */ + eis_device_swipe_cancel(device); + break; + } + case PINCH: { + _unref_(eis_event) *begin = peck_eis_pinch_begin(eis); + _unref_(eis_event) *end = peck_eis_pinch_end(eis); + + eis_device_pinch_cancel(device); + break; + } + case HOLD: { + _unref_(eis_event) *begin = peck_eis_hold_begin(eis); + _unref_(eis_event) *end = peck_eis_hold_end(eis); + + eis_device_hold_cancel(device); + break; + } + } + } + + peck_dispatch_until_stable(peck); + + with_client(peck) { + /* No cancel event should be sent */ + peck_assert_no_ei_events(ei); + } + + return MUNIT_OK; +} + +MUNIT_TEST_WITH_PARAMS(test_eis_device_gesture_double_cancel, "@gesture", "swipe", "pinch", "hold") +{ + enum gesture_type gesture = gesture_type_from_string(MUNIT_TEST_PARAM("@gesture")); + _unref_(peck) *peck = peck_new(); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + int nfingers = 3; + + _unref_(ei_swipe) *swipe = NULL; + _unref_(ei_pinch) *pinch = NULL; + _unref_(ei_hold) *hold = NULL; + + with_client(peck) { + struct ei_device *device = peck_ei_get_default_gestures(peck); + + switch (gesture) { + case SWIPE: { + swipe = ei_device_swipe_new(device, nfingers); + ei_swipe_begin(swipe); + ei_device_frame(device, peck_ei_now(peck)); + + ei_swipe_update(swipe, 1.1, 2.2); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + case PINCH: { + pinch = ei_device_pinch_new(device, nfingers); + ei_pinch_begin(pinch); + ei_device_frame(device, peck_ei_now(peck)); + + ei_pinch_update(pinch, 1.1, 2.2, 3.3, 4.4); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + case HOLD: { + hold = ei_device_hold_new(device, nfingers); + ei_hold_begin(hold); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + } + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + struct eis_device *device = peck_eis_get_default_gestures(peck); + _unref_(eis_event) *begin = NULL; + _unref_(eis_event) *update = NULL; + + switch (gesture) { + case SWIPE: { + begin = peck_eis_swipe_begin(eis); + update = peck_eis_swipe_update(eis); + eis_device_swipe_cancel(device); + eis_device_swipe_cancel(device); /* should be a noop */ + break; + } + case PINCH: { + begin = peck_eis_pinch_begin(eis); + update = peck_eis_pinch_update(eis); + + eis_device_pinch_cancel(device); + eis_device_pinch_cancel(device); /* should be noop */ + break; + } + case HOLD: { + begin = peck_eis_hold_begin(eis); + + eis_device_hold_cancel(device); + eis_device_hold_cancel(device); /* should be noop */ + break; + } + } + } + + peck_dispatch_ei(peck); + + with_client(peck) { + _unref_(ei_event) *canceled = NULL; + switch (gesture) { + case SWIPE: + canceled = peck_ei_next_event(ei, EI_EVENT_SWIPE_CANCELLED); + break; + case PINCH: + canceled = peck_ei_next_event(ei, EI_EVENT_PINCH_CANCELLED); + break; + case HOLD: + canceled = peck_ei_next_event(ei, EI_EVENT_HOLD_CANCELLED); + break; + } + + /* Should not receive duplicate cancel event */ + peck_assert_no_ei_events(ei); + } + return MUNIT_OK; +} + +MUNIT_TEST(test_eis_device_gesture_cancel_on_non_gesture_device) +{ + _unref_(peck) *peck = peck_new(); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + /* Enable log capture to verify error messages */ + peck_eis_enable_log_capture(peck); + + with_server(peck) { + struct eis_device *device = peck_eis_get_default_pointer(peck); + + /* These should log errors but not crash */ + with_nonfatal_eis_bug(peck) + { + eis_device_swipe_cancel(device); + eis_device_pinch_cancel(device); + eis_device_hold_cancel(device); + } + } + + /* Verify error was logged */ + with_server(peck) { + char **logs = peck_eis_get_log_capture(peck, EIS_LOG_PRIORITY_ERROR); + int count = 0; + for (char **log = logs; *log; log++) { + if (strstr(*log, "device is not a gesture device")) + count++; + } + /* Should have 3 error messages, one for each cancel call */ + munit_assert_int(count, ==, 3); + } + + peck_eis_disable_log_capture(peck); + + return MUNIT_OK; +} + +MUNIT_TEST_WITH_PARAMS(test_ei_device_gesture_no_second_gesture, + "@gesture", + "swipe", + "pinch", + "hold") +{ + const char *gesture_param = MUNIT_TEST_PARAM("@gesture"); + enum gesture_type gesture = gesture_type_from_string(gesture_param); + _unref_(peck) *peck = peck_new(); + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + peck_ei_enable_log_capture(peck); + + int nfingers = 3; + + with_client(peck) { + struct ei_device *device = peck_ei_get_default_gestures(peck); + + switch (gesture) { + case SWIPE: { + _unref_(ei_swipe) *swipe1 = ei_device_swipe_new(device, nfingers); + _unref_(ei_swipe) *swipe2 = ei_device_swipe_new(device, nfingers); + ei_swipe_begin(swipe1); + ei_device_frame(device, peck_ei_now(peck)); + + ei_swipe_update(swipe1, 1.1, 2.2); + ei_device_frame(device, peck_ei_now(peck)); + + with_nonfatal_ei_bug(peck) + { + ei_swipe_begin(swipe2); + ei_device_frame(device, peck_ei_now(peck)); + ei_swipe_update(swipe2, 3.3, 4.4); + ei_device_frame(device, peck_ei_now(peck)); + } + break; + } + case PINCH: { + _unref_(ei_pinch) *pinch1 = ei_device_pinch_new(device, nfingers); + _unref_(ei_pinch) *pinch2 = ei_device_pinch_new(device, nfingers); + ei_pinch_begin(pinch1); + ei_device_frame(device, peck_ei_now(peck)); + + ei_pinch_update(pinch1, 1.1, 2.2, 3.3, 4.4); + ei_device_frame(device, peck_ei_now(peck)); + + with_nonfatal_ei_bug(peck) + { + ei_pinch_begin(pinch2); + ei_device_frame(device, peck_ei_now(peck)); + + ei_pinch_update(pinch2, 1.1, 2.2, 3.3, 4.4); + ei_device_frame(device, peck_ei_now(peck)); + } + break; + } + case HOLD: { + _unref_(ei_hold) *hold1 = ei_device_hold_new(device, nfingers); + _unref_(ei_hold) *hold2 = ei_device_hold_new(device, nfingers); + ei_hold_begin(hold1); + ei_device_frame(device, peck_ei_now(peck)); + with_nonfatal_ei_bug(peck) + { + ei_hold_begin(hold2); + ei_device_frame(device, peck_ei_now(peck)); + } + break; + } + } + + char **logs = peck_ei_get_log_capture(peck, EI_LOG_PRIORITY_ERROR); + int count = 0; + _cleanup_free_ char *message = + xaprintf("a different %s gesture is already in process", gesture_param); + for (char **log = logs; *log; log++) { + if (strstr(*log, message)) + count++; + } + /* Should have 1 error message for the rejected second gesture begin */ + munit_assert_int(count, ==, 1); + } + + peck_dispatch_until_stable(peck); + + return MUNIT_OK; +} + +MUNIT_TEST_WITH_PARAMS(test_ei_device_gesture_unref_cancels, "@gesture", "swipe", "pinch", "hold") +{ + enum gesture_type gesture = gesture_type_from_string(MUNIT_TEST_PARAM("@gesture")); + _unref_(peck) *peck = peck_new(); + const int nfingers = 2; + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + with_client(peck) { + struct ei_device *device = peck_ei_get_default_gestures(peck); + + switch (gesture) { + case SWIPE: { + struct ei_swipe *swipe = ei_device_swipe_new(device, nfingers); + ei_swipe_begin(swipe); + ei_device_frame(device, peck_ei_now(peck)); + + ei_swipe_update(swipe, 1.1, 2.2); + ei_device_frame(device, peck_ei_now(peck)); + + /* Unref while gesture is still active - should cancel */ + ei_swipe_unref(swipe); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + case PINCH: { + struct ei_pinch *pinch = ei_device_pinch_new(device, nfingers); + ei_pinch_begin(pinch); + ei_device_frame(device, peck_ei_now(peck)); + + ei_pinch_update(pinch, 1.1, 2.2, 3.3, 4.4); + ei_device_frame(device, peck_ei_now(peck)); + + /* Unref while gesture is still active - should cancel */ + ei_pinch_unref(pinch); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + case HOLD: { + struct ei_hold *hold = ei_device_hold_new(device, nfingers); + ei_hold_begin(hold); + ei_device_frame(device, peck_ei_now(peck)); + + /* Unref while gesture is still active - should cancel */ + ei_hold_unref(hold); + ei_device_frame(device, peck_ei_now(peck)); + break; + } + } + } + + peck_dispatch_until_stable(peck); + + with_server(peck) { + switch (gesture) { + case SWIPE: { + _unref_(eis_event) *begin = peck_eis_swipe_begin(eis); + munit_assert_int(eis_event_swipe_get_finger_count(begin), ==, nfingers); + + _unref_(eis_event) *update = peck_eis_swipe_update(eis); + munit_assert_int(eis_event_swipe_get_finger_count(update), ==, nfingers); + + _unref_(eis_event) *end = peck_eis_swipe_end(eis); + munit_assert_int(eis_event_swipe_get_finger_count(end), ==, nfingers); + munit_assert_true(eis_event_swipe_get_is_cancelled(end)); + break; + } + case PINCH: { + _unref_(eis_event) *begin = peck_eis_pinch_begin(eis); + munit_assert_int(eis_event_pinch_get_finger_count(begin), ==, nfingers); + + _unref_(eis_event) *update = peck_eis_pinch_update(eis); + munit_assert_int(eis_event_pinch_get_finger_count(update), ==, nfingers); + + _unref_(eis_event) *end = peck_eis_pinch_end(eis); + munit_assert_int(eis_event_pinch_get_finger_count(end), ==, nfingers); + munit_assert_true(eis_event_pinch_get_is_cancelled(end)); + break; + } + case HOLD: { + _unref_(eis_event) *begin = peck_eis_hold_begin(eis); + munit_assert_int(eis_event_hold_get_finger_count(begin), ==, nfingers); + + _unref_(eis_event) *end = peck_eis_hold_end(eis); + munit_assert_int(eis_event_hold_get_finger_count(end), ==, nfingers); + munit_assert_true(eis_event_hold_get_is_cancelled(end)); + break; + } + } + + peck_assert_no_eis_events(eis); + } + + return MUNIT_OK; +} + +MUNIT_TEST_WITH_PARAMS(test_passive_ei_device_gesture_unref_cancels, + "@gesture", + "swipe", + "pinch", + "hold") +{ + enum gesture_type gesture = gesture_type_from_string(MUNIT_TEST_PARAM("@gesture")); + _unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER); + const int nfingers = 2; + + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); + peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_GESTURES); + peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); + peck_dispatch_until_stable(peck); + + uint32_t sequence = 123; + + with_server(peck) { + struct eis_device *device = peck_eis_get_default_gestures(peck); + + eis_device_start_emulating(device, sequence); + + switch (gesture) { + case SWIPE: { + struct eis_swipe *swipe = eis_device_swipe_new(device, nfingers); + eis_swipe_begin(swipe); + eis_device_frame(device, peck_eis_now(peck)); + + eis_swipe_update(swipe, 1.1, 2.2); + eis_device_frame(device, peck_eis_now(peck)); + + /* Unref while gesture is still active - should cancel */ + eis_swipe_unref(swipe); + eis_device_frame(device, peck_eis_now(peck)); + break; + } + case PINCH: { + struct eis_pinch *pinch = eis_device_pinch_new(device, nfingers); + eis_pinch_begin(pinch); + eis_device_frame(device, peck_eis_now(peck)); + + eis_pinch_update(pinch, 1.1, 2.2, 3.3, 4.4); + eis_device_frame(device, peck_eis_now(peck)); + + /* Unref while gesture is still active - should cancel */ + eis_pinch_unref(pinch); + eis_device_frame(device, peck_eis_now(peck)); + break; + } + case HOLD: { + struct eis_hold *hold = eis_device_hold_new(device, nfingers); + eis_hold_begin(hold); + eis_device_frame(device, peck_eis_now(peck)); + + /* Unref while gesture is still active - should cancel */ + eis_hold_unref(hold); + eis_device_frame(device, peck_eis_now(peck)); + break; + } + } + } + + 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); + + switch (gesture) { + case SWIPE: { + _unref_(ei_event) *begin = peck_ei_swipe_begin(ei); + munit_assert_int(ei_event_swipe_get_finger_count(begin), ==, nfingers); + + _unref_(ei_event) *update = peck_ei_swipe_update(ei); + munit_assert_int(ei_event_swipe_get_finger_count(update), ==, nfingers); + + _unref_(ei_event) *end = peck_ei_swipe_end(ei); + munit_assert_int(ei_event_swipe_get_finger_count(end), ==, nfingers); + munit_assert_true(ei_event_swipe_get_is_cancelled(end)); + break; + } + case PINCH: { + _unref_(ei_event) *begin = peck_ei_pinch_begin(ei); + munit_assert_int(ei_event_pinch_get_finger_count(begin), ==, nfingers); + + _unref_(ei_event) *update = peck_ei_pinch_update(ei); + munit_assert_int(ei_event_pinch_get_finger_count(update), ==, nfingers); + + _unref_(ei_event) *end = peck_ei_pinch_end(ei); + munit_assert_int(ei_event_pinch_get_finger_count(end), ==, nfingers); + munit_assert_true(ei_event_pinch_get_is_cancelled(end)); + break; + } + case HOLD: { + _unref_(ei_event) *begin = peck_ei_hold_begin(ei); + munit_assert_int(ei_event_hold_get_finger_count(begin), ==, nfingers); + + _unref_(ei_event) *end = peck_ei_hold_end(ei); + munit_assert_int(ei_event_hold_get_finger_count(end), ==, nfingers); + munit_assert_true(ei_event_hold_get_is_cancelled(end)); + break; + } + } + + peck_assert_no_ei_events(ei); + } + + return MUNIT_OK; +} diff --git a/tools/ei-debug-events.c b/tools/ei-debug-events.c index be809c6..beb0ca5 100644 --- a/tools/ei-debug-events.c +++ b/tools/ei-debug-events.c @@ -332,6 +332,72 @@ print_pong_event(struct ei_event *event) printf(" id: %#" PRIx64 "\n", ei_ping_get_id(ping)); } +static void +print_swipe_event(struct ei_event *event) +{ + print_device(event); + + printf(" fingers: %u\n", ei_event_swipe_get_finger_count(event)); + if (ei_event_get_type(event) == EI_EVENT_SWIPE_UPDATE) + printf(" delta: [%.2f, %.2f]\n", + ei_event_swipe_get_dx(event), + ei_event_swipe_get_dy(event)); + if (ei_event_get_type(event) == EI_EVENT_SWIPE_END) + printf(" cancelled: %s\n", + ei_event_swipe_get_is_cancelled(event) ? "true" : "false"); +} + +static void +print_pinch_event(struct ei_event *event) +{ + print_device(event); + + printf(" fingers: %u\n", ei_event_pinch_get_finger_count(event)); + if (ei_event_get_type(event) == EI_EVENT_PINCH_UPDATE) { + printf(" delta: [%.2f, %.2f]\n", + ei_event_pinch_get_dx(event), + ei_event_pinch_get_dy(event)); + printf(" scale: %.2f\n", ei_event_pinch_get_scale(event)); + printf(" rotation: %.2f\n", ei_event_pinch_get_rotation(event)); + } + if (ei_event_get_type(event) == EI_EVENT_PINCH_END) + printf(" cancelled: %s\n", + ei_event_pinch_get_is_cancelled(event) ? "true" : "false"); +} + +static void +print_hold_event(struct ei_event *event) +{ + print_device(event); + + printf(" fingers: %u\n", ei_event_hold_get_finger_count(event)); + if (ei_event_get_type(event) == EI_EVENT_HOLD_END) + printf(" cancelled: %s\n", + ei_event_hold_get_is_cancelled(event) ? "true" : "false"); +} + +static void +print_gesture_cancel_event(struct ei_event *event) +{ + print_device(event); + const char *type = NULL; + switch (ei_event_get_type(event)) { + case EI_EVENT_PINCH_CANCELLED: + type = "pinch"; + break; + case EI_EVENT_SWIPE_CANCELLED: + type = "swipe"; + break; + case EI_EVENT_HOLD_CANCELLED: + type = "hold"; + break; + default: + abort(); + break; + } + printf(" %s cancelled\n", type); +} + int main(int argc, char **argv) { @@ -502,6 +568,25 @@ main(int argc, char **argv) break; case EI_EVENT_SYNC: break; + case EI_EVENT_SWIPE_BEGIN: + case EI_EVENT_SWIPE_UPDATE: + case EI_EVENT_SWIPE_END: + print_swipe_event(e); + break; + case EI_EVENT_PINCH_BEGIN: + case EI_EVENT_PINCH_UPDATE: + case EI_EVENT_PINCH_END: + print_pinch_event(e); + break; + case EI_EVENT_HOLD_BEGIN: + case EI_EVENT_HOLD_END: + print_hold_event(e); + break; + case EI_EVENT_PINCH_CANCELLED: + case EI_EVENT_SWIPE_CANCELLED: + case EI_EVENT_HOLD_CANCELLED: + print_gesture_cancel_event(e); + break; } } } diff --git a/tools/ei-demo-client.c b/tools/ei-demo-client.c index b12de52..d289831 100644 --- a/tools/ei-demo-client.c +++ b/tools/ei-demo-client.c @@ -305,6 +305,7 @@ main(int argc, char **argv) _unref_(ei_device) *kbd = NULL; _unref_(ei_device) *abs = NULL; _unref_(ei_device) *touch = NULL; + _unref_(ei_device) *gestures = NULL; _unref_(ei_device) *text = NULL; bool stop = false; @@ -312,6 +313,7 @@ main(int argc, char **argv) bool have_kbd = false; bool have_abs = false; bool have_touch = false; + bool have_gestures = false; bool have_text = false; struct ei_seat *default_seat = NULL; @@ -351,6 +353,7 @@ main(int argc, char **argv) EI_DEVICE_CAP_TOUCH, EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, + EI_DEVICE_CAP_GESTURES, EI_DEVICE_CAP_TEXT, NULL); break; @@ -388,6 +391,12 @@ main(int argc, char **argv) touch = ei_device_ref(device); handle_regions(device); } + if (ei_device_has_capability(device, EI_DEVICE_CAP_GESTURES)) { + colorprint("New gesture device: %s\n", + ei_device_get_name(device)); + gestures = ei_device_ref(device); + handle_regions(device); + } if (ei_device_has_capability(device, EI_DEVICE_CAP_TEXT)) { colorprint("New text device: %s\n", ei_device_get_name(device)); @@ -419,6 +428,12 @@ main(int argc, char **argv) colorprint("Touch device was resumed\n"); have_touch = true; } + if (ei_event_get_device(e) == gestures) { + if (!receiver) + ei_device_start_emulating(gestures, ++sequence); + colorprint("Gesture device was resumed\n"); + have_gestures = true; + } if (ei_event_get_device(e) == text) { if (!receiver) ei_device_start_emulating(text, ++sequence); @@ -526,6 +541,50 @@ main(int argc, char **argv) case EI_EVENT_SYNC: { colorprint("sync\n"); } break; + case EI_EVENT_SWIPE_BEGIN: { + colorprint("swipe begin: %ufg\n", + ei_event_swipe_get_finger_count(e)); + } break; + case EI_EVENT_SWIPE_UPDATE: { + colorprint("swipe update %.2f/%.2f\n", + ei_event_swipe_get_dx(e), + ei_event_swipe_get_dy(e)); + } break; + case EI_EVENT_SWIPE_END: { + colorprint("swipe end, cancelled: %s\n", + ei_event_swipe_get_is_cancelled(e) ? "true" : "false"); + } break; + case EI_EVENT_PINCH_BEGIN: { + colorprint("pinch begin: %ufg\n", + ei_event_pinch_get_finger_count(e)); + } break; + case EI_EVENT_PINCH_UPDATE: { + colorprint("pinch update %.2f/%.2f rotation: %.2f scale %.2f\n", + ei_event_pinch_get_dx(e), + ei_event_pinch_get_dy(e), + ei_event_pinch_get_rotation(e), + ei_event_pinch_get_scale(e)); + } break; + case EI_EVENT_PINCH_END: { + colorprint("pinch end, cancelled: %s\n", + ei_event_pinch_get_is_cancelled(e) ? "true" : "false"); + } break; + case EI_EVENT_HOLD_BEGIN: { + colorprint("hold begin: %ufg\n", ei_event_hold_get_finger_count(e)); + } break; + case EI_EVENT_HOLD_END: { + colorprint("hold end, cancelled: %s\n", + ei_event_hold_get_is_cancelled(e) ? "true" : "false"); + } break; + case EI_EVENT_SWIPE_CANCELLED: { + colorprint("swipe cancelled by server\n"); + } break; + case EI_EVENT_PINCH_CANCELLED: { + colorprint("pinch cancelled by server\n"); + } break; + case EI_EVENT_HOLD_CANCELLED: { + colorprint("hold cancelled by server\n"); + } break; default: { colorprint("ERROR: Unhandled event type %u (%s)\n", type, @@ -607,6 +666,80 @@ main(int argc, char **argv) } } + if (have_gestures) { + const int nfingers = 3; + static int counter = 0; + static double scale = 1.0; + static struct ei_swipe *swipe = NULL; + static struct ei_pinch *pinch = NULL; + static struct ei_hold *hold = NULL; + + switch (counter++ % 8) { + case 0: + swipe = ei_device_swipe_new(gestures, nfingers); + if (swipe) { + colorprint("sending swipe begin event\n"); + ei_swipe_begin(swipe); + ei_device_frame(gestures, now); + } + break; + case 1: + if (swipe) { + colorprint("sending swipe update event\n"); + ei_swipe_update(swipe, 1.5, 2.3); + ei_device_frame(gestures, now); + } + break; + case 2: + if (swipe) { + colorprint("sending swipe cancel event\n"); + ei_swipe_cancel(swipe); + ei_device_frame(gestures, now); + swipe = ei_swipe_unref(swipe); + } + break; + case 3: + pinch = ei_device_pinch_new(gestures, nfingers); + if (pinch) { + colorprint("sending pinch begin event\n"); + ei_pinch_begin(pinch); + ei_device_frame(gestures, now); + } + break; + case 4: + if (pinch) { + colorprint("sending pinch update event\n"); + ei_pinch_update(pinch, 1.5, 2.3, scale += 0.2, 1.7); + ei_device_frame(gestures, now); + } + break; + case 5: + if (pinch) { + colorprint("sending pinch cancel event\n"); + ei_pinch_cancel(pinch); + ei_device_frame(gestures, now); + pinch = ei_pinch_unref(pinch); + } + break; + case 6: + hold = ei_device_hold_new(gestures, nfingers); + if (hold) { + colorprint("sending hold begin event\n"); + ei_hold_begin(hold); + ei_device_frame(gestures, now); + } + break; + case 7: + if (hold) { + colorprint("sending hold cancel event\n"); + ei_hold_cancel(hold); + ei_device_frame(gestures, now); + hold = ei_hold_unref(hold); + } + break; + } + } + if (have_text) { static int key = 0; colorprint("sending text event\n"); @@ -635,6 +768,8 @@ main(int argc, char **argv) ei_device_close(abs); if (touch) ei_device_close(touch); + if (gestures) + ei_device_close(gestures); if (text) ei_device_close(text); if (default_seat) { diff --git a/tools/eis-demo-server.c b/tools/eis-demo-server.c index 9cf04ad..afdf445 100644 --- a/tools/eis-demo-server.c +++ b/tools/eis-demo-server.c @@ -50,6 +50,8 @@ #include #include +#include "libeis.h" + #if HAVE_LIBXKBCOMMON #include #endif @@ -151,6 +153,10 @@ eis_demo_client_destroy(struct eis_demo_client *democlient) eis_device_unref(democlient->abs); eis_device_unref(democlient->kbd); eis_device_unref(democlient->touchscreen); + eis_device_unref(democlient->gestures); + eis_swipe_unref(democlient->swipe); + eis_pinch_unref(democlient->pinch); + eis_hold_unref(democlient->hold); eis_device_unref(democlient->text); } @@ -349,6 +355,17 @@ add_device(struct eis_demo_server *server, case EIS_DEVICE_CAP_SCROLL: /* Mixed in with pointer/abs - good enough for a demo server */ break; + case EIS_DEVICE_CAP_GESTURES: { + struct eis_device *gestures = eis_seat_new_device(seat); + eis_device_configure_name(gestures, "test gestures"); + eis_device_configure_capability(gestures, EIS_DEVICE_CAP_GESTURES); + colorprint("Creating gestures device %s for %s\n", + eis_device_get_name(gestures), + eis_client_get_name(client)); + eis_device_add(gestures); + device = steal(&gestures); + break; + } case EIS_DEVICE_CAP_TEXT: { struct eis_device *text = eis_seat_new_device(seat); eis_device_configure_name(text, "test text device"); @@ -409,6 +426,7 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server, struct eis_e eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_BUTTON); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_SCROLL); + eis_seat_configure_capability(seat, EIS_DEVICE_CAP_GESTURES); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TEXT); eis_seat_add(seat); /* Note: we don't have a ref to this seat ourselves anywhere */ @@ -479,6 +497,17 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server, struct eis_e } } + if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_GESTURES)) { + if (!democlient->gestures) + democlient->gestures = + add_device(server, client, seat, EIS_DEVICE_CAP_GESTURES); + } else { + if (democlient->gestures) { + eis_device_remove(democlient->gestures); + democlient->gestures = eis_device_unref(democlient->gestures); + } + } + if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TEXT)) { if (!democlient->text) democlient->text = @@ -496,6 +525,7 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server, struct eis_e !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH) && + !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_GESTURES) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TEXT)) eis_seat_remove(seat); @@ -532,6 +562,9 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server, struct eis_e if (democlient->touchscreen == device) democlient->touchscreen = NULL; + if (democlient->gestures == device) + democlient->gestures = NULL; + if (democlient->text == device) democlient->text = NULL; @@ -586,6 +619,39 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server, struct eis_e case EIS_EVENT_TOUCH_UP: { colorprint("touch up %u\n", eis_event_touch_get_id(e)); } break; + case EIS_EVENT_SWIPE_BEGIN: { + colorprint("swipe begin: %ufg\n", eis_event_swipe_get_finger_count(e)); + } break; + case EIS_EVENT_SWIPE_UPDATE: { + colorprint("swipe update %.2f/%.2f\n", + eis_event_swipe_get_dx(e), + eis_event_swipe_get_dy(e)); + } break; + case EIS_EVENT_SWIPE_END: { + colorprint("swipe end, cancelled: %s\n", + eis_event_swipe_get_is_cancelled(e) ? "true" : "false"); + } break; + case EIS_EVENT_PINCH_BEGIN: { + colorprint("pinch begin: %ufg\n", eis_event_pinch_get_finger_count(e)); + } break; + case EIS_EVENT_PINCH_UPDATE: { + colorprint("pinch update %.2f/%.2f rotation: %.2f scale %.2f\n", + eis_event_pinch_get_dx(e), + eis_event_pinch_get_dy(e), + eis_event_pinch_get_rotation(e), + eis_event_pinch_get_scale(e)); + } break; + case EIS_EVENT_PINCH_END: { + colorprint("pinch end, cancelled: %s\n", + eis_event_pinch_get_is_cancelled(e) ? "true" : "false"); + } break; + case EIS_EVENT_HOLD_BEGIN: { + colorprint("hold begin: %ufg\n", eis_event_hold_get_finger_count(e)); + } break; + case EIS_EVENT_HOLD_END: { + colorprint("hold end, cancelled: %s\n", + eis_event_hold_get_is_cancelled(e) ? "true" : "false"); + } break; case EIS_EVENT_TEXT_KEYSYM: { char buf[128] = { 0 }; uint32_t keysym = eis_event_text_get_keysym(e); @@ -789,6 +855,7 @@ main(int argc, char **argv) struct eis_device *kbd = democlient->kbd; struct eis_device *abs = democlient->abs; struct eis_device *touchscreen = democlient->touchscreen; + struct eis_device *gestures = democlient->gestures; struct eis_device *text = democlient->text; if (ptr) { colorprint("sending motion event\n"); @@ -862,6 +929,86 @@ main(int argc, char **argv) break; } } + if (gestures) { + const int nfingers = 3; + static int counter = 0; + static double scale = 1.0; + struct eis_swipe *swipe = democlient->swipe; + struct eis_pinch *pinch = democlient->pinch; + struct eis_hold *hold = democlient->hold; + + switch (counter++ % 8) { + case 0: + swipe = eis_device_swipe_new(gestures, nfingers); + if (swipe) { + colorprint("sending swipe begin event\n"); + eis_swipe_begin(swipe); + eis_device_frame(gestures, now); + democlient->swipe = swipe; + } + break; + case 1: + if (swipe) { + colorprint("sending swipe update event\n"); + eis_swipe_update(swipe, 1.5, 2.3); + eis_device_frame(gestures, now); + } + break; + case 2: + if (swipe) { + colorprint("sending swipe cancel event\n"); + eis_swipe_cancel(swipe); + eis_device_frame(gestures, now); + democlient->swipe = eis_swipe_unref(swipe); + } + break; + case 3: + pinch = eis_device_pinch_new(gestures, nfingers); + if (pinch) { + colorprint("sending pinch begin event\n"); + eis_pinch_begin(pinch); + eis_device_frame(gestures, now); + democlient->pinch = pinch; + } + break; + case 4: + if (pinch) { + colorprint("sending pinch update event\n"); + eis_pinch_update(pinch, + 1.5, + 2.3, + scale += 0.2, + 1.7); + eis_device_frame(gestures, now); + } + break; + case 5: + if (pinch) { + colorprint("sending pinch cancel event\n"); + eis_pinch_cancel(pinch); + eis_device_frame(gestures, now); + democlient->pinch = eis_pinch_unref(pinch); + } + break; + case 6: + hold = eis_device_hold_new(gestures, nfingers); + if (hold) { + colorprint("sending hold begin event\n"); + eis_hold_begin(hold); + eis_device_frame(gestures, now); + democlient->hold = hold; + } + break; + case 7: + if (hold) { + colorprint("sending hold cancel event\n"); + eis_hold_cancel(hold); + eis_device_frame(gestures, now); + democlient->hold = eis_hold_unref(hold); + } + break; + } + } if (text) { static int key = 0; diff --git a/tools/eis-demo-server.h b/tools/eis-demo-server.h index 20057ea..2fcfb03 100644 --- a/tools/eis-demo-server.h +++ b/tools/eis-demo-server.h @@ -40,7 +40,11 @@ struct eis_demo_client { struct eis_device *kbd; struct eis_device *abs; struct eis_device *touchscreen; + struct eis_device *gestures; struct eis_touch *touch; + struct eis_swipe *swipe; + struct eis_pinch *pinch; + struct eis_hold *hold; struct eis_device *text; };