Add a frame event to logically group events together

Already present in e.g. libinput and wayland, this event allows us to
group several events together to denote them as a logical group.
Required for multi-touch but as we've learned with Wayland it's also
required to group other events (scroll events in the case of Wayland).

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2021-08-23 10:50:23 +10:00
parent 6f70e97cce
commit 55594cd09c
21 changed files with 190 additions and 2 deletions

View file

@ -141,6 +141,10 @@ message TouchUp {
uint32 touchid = 2;
}
message Frame {
uint32 deviceid = 1;
}
message ClientMessage {
oneof msg {
Connect connect = 1;
@ -159,6 +163,7 @@ message ClientMessage {
TouchUp touch_up = 14;
ConfigureName configure_name = 15;
ConfigureCapabilities configure_caps = 16;
Frame frame = 17;
}
}

View file

@ -528,3 +528,12 @@ ei_touch_up(struct ei_touch *touch)
touch->state = TOUCH_IS_UP;
ei_send_touch_up(touch->device, touch->tracking_id);
}
_public_ void
ei_device_frame(struct ei_device *device)
{
if (device->state != EI_DEVICE_STATE_RESUMED)
return;
ei_send_frame(device);
}

View file

@ -207,6 +207,9 @@ ei_send_seat_unbind(struct ei_seat *seat);
int
ei_send_close_device(struct ei_device *device);
int
ei_send_frame(struct ei_device *device);
void
ei_queue_device_removed_event(struct ei_device *device);

View file

@ -162,6 +162,7 @@ log_wire_message(struct ei *ei, const ClientMessage *msg, int error)
MSG_STRING_CASE(TOUCH_UP);
MSG_STRING_CASE(CONFIGURE_NAME);
MSG_STRING_CASE(CONFIGURE_CAPS);
MSG_STRING_CASE(FRAME);
}
if (message == NULL)
assert(!"Unimplemented message type");
@ -374,6 +375,16 @@ ei_proto_send_touch_up(struct ei_device *device, uint32_t tid)
return ei_proto_send_msg(ei_device_get_context(device), &msg);
}
static int
ei_proto_send_frame(struct ei_device *device)
{
prepare_msg(FRAME, Frame, frame);
frame.deviceid = device->id;
return ei_proto_send_msg(ei_device_get_context(device), &msg);
}
static const struct ei_proto_requests requests = {
.connect = ei_proto_send_connect,
.disconnect = ei_proto_send_disconnect,
@ -389,6 +400,7 @@ static const struct ei_proto_requests requests = {
.touch_down = ei_proto_send_touch_down,
.touch_motion = ei_proto_send_touch_motion,
.touch_up = ei_proto_send_touch_up,
.frame = ei_proto_send_frame,
};
const struct ei_proto_requests *

View file

@ -74,6 +74,7 @@ struct ei_proto_requests {
int (*touch_motion)(struct ei_device *device,
uint32_t tid, double x, double y);
int (*touch_up)(struct ei_device *device, uint32_t tid);
int (*frame)(struct ei_device *device);
};
int

View file

@ -549,6 +549,20 @@ ei_send_seat_unbind(struct ei_seat *seat)
return rc;
}
int
ei_send_frame(struct ei_device *device)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
int rc = ei->requests->frame(device);
if (rc)
ei_disconnect(ei);
return rc;
}
int
ei_send_pointer_rel(struct ei_device *device, double x, double y)
{

View file

@ -911,6 +911,14 @@ ei_keymap_get_context(struct ei_keymap *keymap);
struct ei *
ei_device_get_context(struct ei_device *device);
/**
* Generate a frame event to group the current set of events
* into a logical hardware event. This function **must** be called after any
* other event has been generated.
*/
void
ei_device_frame(struct ei_device *device);
/**
* Generate a relative motion event on a device with
* the @ref EI_DEVICE_CAP_POINTER capability.

View file

@ -249,6 +249,25 @@ client_msg_unbind_seat(struct eis_client *client, uint32_t seatid)
return -EINVAL;
}
static int
client_msg_frame(struct eis_client *client, uint32_t deviceid)
{
struct eis_seat *seat;
list_for_each(seat, &client->seats, link) {
struct eis_device *device;
list_for_each(device, &seat->devices, link) {
if (device->id == deviceid) {
eis_device_frame(device);
break;
}
}
}
return 0;
}
static int
client_msg_pointer_rel(struct eis_client *client, uint32_t deviceid,
double x, double y)
@ -496,6 +515,7 @@ static const struct eis_proto_interface intf_state_connected = {
.touch_up = client_msg_touch_up,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
.frame = client_msg_frame,
};
static const struct eis_proto_interface *interfaces[] = {

View file

@ -260,6 +260,16 @@ eis_device_has_capability(struct eis_device *device,
return false;
}
int
eis_device_frame(struct eis_device *device)
{
if (device->state != EIS_DEVICE_STATE_RESUMED)
return -EINVAL;
eis_queue_frame_event(device);
return 0;
}
int
eis_device_pointer_rel(struct eis_device *device,

View file

@ -51,6 +51,7 @@ eis_event_destroy(struct eis_event *event)
case EIS_EVENT_TOUCH_DOWN:
case EIS_EVENT_TOUCH_MOTION:
case EIS_EVENT_TOUCH_UP:
case EIS_EVENT_FRAME:
handled = true;
break;
}

View file

@ -238,6 +238,8 @@ void
eis_device_set_client_keymap(struct eis_device *device,
enum eis_keymap_type type,
int keymap_fd, size_t size);
int
eis_device_frame(struct eis_device *device);
int
eis_device_pointer_rel(struct eis_device *device,
@ -307,6 +309,9 @@ eis_queue_seat_unbind_event(struct eis_seat *seat);
void
eis_queue_device_closed_event(struct eis_device *device);
void
eis_queue_frame_event(struct eis_device *device);
void
eis_queue_pointer_rel_event(struct eis_device *device, double x, double y);

View file

@ -377,6 +377,9 @@ eis_proto_handle_message(struct eis_client *client,
proto->configure_caps->allowed_capabilities,
proto->configure_caps->denied_capabilities);
break;
case CLIENT_MESSAGE__MSG_FRAME:
rc = call(frame, client, proto->frame->deviceid);
break;
default:
rc = -EBADMSG;
break;

View file

@ -55,6 +55,7 @@ struct eis_proto_interface {
int (*configure_capabilities)(struct eis_client *client,
bool policy_is_allow,
uint32_t allow, uint32_t deny);
int (*frame) (struct eis_client *client, uint32_t deviceid);
};
struct eis_proto_requests {

View file

@ -117,6 +117,7 @@ eis_event_type_to_string(enum eis_event_type type)
CASE_RETURN_STRING(EIS_EVENT_TOUCH_DOWN);
CASE_RETURN_STRING(EIS_EVENT_TOUCH_UP);
CASE_RETURN_STRING(EIS_EVENT_TOUCH_MOTION);
CASE_RETURN_STRING(EIS_EVENT_FRAME);
}
assert(!"Unhandled event type");
@ -174,6 +175,14 @@ eis_queue_device_closed_event(struct eis_device *device)
eis_queue_event(e);
}
void
eis_queue_frame_event(struct eis_device *device)
{
struct eis_event *e = eis_event_new_for_device(device);
e->type = EIS_EVENT_FRAME;
eis_queue_event(e);
}
void
eis_queue_pointer_rel_event(struct eis_device *device,
double dx, double dy)

View file

@ -117,6 +117,16 @@ enum eis_event_type {
*/
EIS_EVENT_DEVICE_CLOSED,
/**
* "Hardware" frame event. This event **must** be sent by the client
* and notifies the server that the previous set of events belong to
* the same logical hardware event.
*
* This event is most commonly used to implement multitouch (multiple
* touches may update within the same hardware scanout cycle).
*/
EIS_EVENT_FRAME = 100,
EIS_EVENT_POINTER_MOTION = 300,
EIS_EVENT_POINTER_MOTION_ABSOLUTE,
EIS_EVENT_POINTER_BUTTON,

View file

@ -315,6 +315,7 @@ peck_new(void)
munit_assert_int(rc, ==, 0);
peck->eis = eis;
peck->eis_behavior = PECK_EIS_BEHAVIOR_NONE;
peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_FRAME);
peck->logger = logger_new("peck", peck);
logger_set_handler(peck->logger, peck_log_handler);
@ -354,6 +355,7 @@ peck_enable_eis_behavior(struct peck *peck, enum peck_eis_behavior behavior)
case PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT:
case PECK_EIS_BEHAVIOR_HANDLE_UNBIND_SEAT:
case PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE:
case PECK_EIS_BEHAVIOR_HANDLE_FRAME:
case PECK_EIS_BEHAVIOR_ADD_POINTER:
case PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE:
case PECK_EIS_BEHAVIOR_ADD_KEYBOARD:
@ -620,6 +622,10 @@ _peck_dispatch_eis(struct peck *peck, int lineno)
else
process_event = tristate_no;
break;
case EIS_EVENT_FRAME:
if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_FRAME))
process_event = tristate_yes;
break;
default:
break;
}
@ -855,11 +861,20 @@ peck_assert_no_ei_events(struct ei *ei)
void
peck_assert_no_eis_events(struct eis *eis)
{
struct peck *peck = eis_get_user_data(eis);
eis_dispatch(eis);
while (true) {
_unref_(eis_event) *e = eis_get_event(eis);
if (!e)
return;
if (peck && flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_FRAME) &&
eis_event_get_type(e) == EIS_EVENT_FRAME) {
log_debug(peck, "Skipping over frame event\n");
continue;
}
munit_errorf("Expected empty event queue, have: %s\n",
peck_eis_event_name(e));
}
@ -893,6 +908,14 @@ _peck_eis_next_event(struct eis *eis, enum eis_event_type type, int lineno)
struct eis_event *event = eis_get_event(eis);
struct peck *peck = eis_get_user_data(eis);
if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_FRAME)) {
while (eis_event_get_type(event) == EIS_EVENT_FRAME) {
eis_event_unref(event);
log_debug(peck, "Skipping over frame event\n");
event = eis_get_event(eis);
}
}
if (!event)
munit_errorf("Expected EIS event type %s, got none, line %d\n",
peck_eis_event_type_name(type),
@ -976,6 +999,7 @@ peck_eis_event_type_name(enum eis_event_type type)
CASE_STRING(TOUCH_DOWN);
CASE_STRING(TOUCH_UP);
CASE_STRING(TOUCH_MOTION);
CASE_STRING(FRAME);
}
#undef CASE_STRING
assert(!"Unhandled EIS event type");

View file

@ -80,6 +80,11 @@ enum peck_eis_behavior {
*/
PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE,
/**
* Handle frame events. This behavior is enabled by default.
*/
PECK_EIS_BEHAVIOR_HANDLE_FRAME,
/**
* Create default devices
*/

View file

@ -237,8 +237,11 @@ MUNIT_TEST(test_ei_device_pointer_rel)
with_client(peck) {
struct ei_device *device = peck_ei_get_default_pointer(peck);
ei_device_pointer_motion(device, 1, 2);
ei_device_frame(device);
ei_device_pointer_motion(device, 0.3, 1.4);
ei_device_frame(device);
ei_device_pointer_motion(device, 100, 200);
ei_device_frame(device);
}
peck_dispatch_until_stable(peck);
@ -419,8 +422,10 @@ MUNIT_TEST(test_ei_device_pointer_abs)
maxx = ei_region_get_x(r) + ei_region_get_width(r);
maxy = ei_region_get_y(r) + ei_region_get_height(r);
for (int i = 0; i < 10; i++)
for (int i = 0; i < 10; i++) {
ei_device_pointer_motion_absolute(device, 1 * i , 2 + i);
ei_device_frame(device);
}
}
peck_dispatch_until_stable(peck);
@ -437,7 +442,9 @@ MUNIT_TEST(test_ei_device_pointer_abs)
with_client(peck) {
/* outside of pointer range, expect to be discarded */
ei_device_pointer_motion_absolute(device, maxx + 1, maxy/2);
ei_device_frame(device);
ei_device_pointer_motion_absolute(device, maxx/2 , maxy + 1);
ei_device_frame(device);
}
peck_dispatch_until_stable(peck);
@ -487,7 +494,9 @@ MUNIT_TEST(test_ei_device_touch)
_unref_(ei_touch) *t = ei_device_touch_new(device);
ei_touch_down(t, 1, 2);
ei_device_frame(device);
ei_touch_motion(t, 200, 500);
ei_device_frame(device);
ei_touch_up(t);
}
@ -508,13 +517,19 @@ MUNIT_TEST(test_ei_device_touch)
_unref_(ei_touch) *t = ei_device_touch_new(device);
/* outside clip range, expect touch to be dropped */
ei_touch_down(t, maxx + 1, maxy/2);
ei_device_frame(device);
ei_touch_motion(t, maxx + 1, maxy/3);
ei_device_frame(device);
ei_touch_up(t);
ei_device_frame(device);
/* outside clip range, expect touch to be dropped */
ei_touch_down(t, maxx/2, maxy + 1);
ei_device_frame(device);
ei_touch_motion(t, maxx/3, maxy + 1);
ei_device_frame(device);
ei_touch_up(t);
ei_device_frame(device);
}
peck_dispatch_until_stable(peck);
@ -526,9 +541,12 @@ MUNIT_TEST(test_ei_device_touch)
with_client(peck) {
_unref_(ei_touch) *t = ei_device_touch_new(device);
ei_touch_down(t, 100, 200);
ei_device_frame(device);
/* outside allowed range, generates a touch up */
ei_touch_motion(t, maxx + 1, 200);
ei_device_frame(device);
ei_touch_up(t);
ei_device_frame(device);
}
peck_dispatch_until_stable(peck);
@ -541,7 +559,9 @@ MUNIT_TEST(test_ei_device_touch)
with_client(peck) {
_unref_(ei_touch) *t = ei_device_touch_new(device);
ei_touch_down(t, 100, 100);
ei_device_frame(device);
/* client forgets to touch up, touch_unref takes care of it */
/* FIXME: this doesn't work with frames */
}
peck_dispatch_until_stable(peck);
@ -558,11 +578,14 @@ MUNIT_TEST(test_ei_device_touch)
/* touch never set down */
_unref_(ei_touch) *t2 = ei_device_touch_new(device);
ei_touch_up(t2);
ei_device_frame(device);
/* touch never set down */
_unref_(ei_touch) *t3 = ei_device_touch_new(device);
ei_touch_motion(t3, 100, 200);
ei_device_frame(device);
ei_touch_up(t3);
ei_device_frame(device);
}
peck_dispatch_until_stable(peck);
@ -574,10 +597,15 @@ MUNIT_TEST(test_ei_device_touch)
/* touch re-used */
_unref_(ei_touch) *t = ei_device_touch_new(device);
ei_touch_down(t, 100, 200);
ei_device_frame(device);
ei_touch_up(t);
ei_device_frame(device);
ei_touch_down(t, 200, 300);
ei_device_frame(device);
ei_touch_motion(t, 300, 400);
ei_device_frame(device);
ei_touch_up(t);
ei_device_frame(device);
}
peck_dispatch_until_stable(peck);
@ -592,10 +620,15 @@ MUNIT_TEST(test_ei_device_touch)
/* double-down, double-up */
_unref_(ei_touch) *t = ei_device_touch_new(device);
ei_touch_down(t, 100, 200);
ei_device_frame(device);
ei_touch_down(t, 200, 300); /* ignored */
ei_device_frame(device);
ei_touch_motion(t, 300, 400);
ei_device_frame(device);
ei_touch_up(t);
ei_device_frame(device);
ei_touch_up(t); /* ignored */
ei_device_frame(device);
}
peck_dispatch_until_stable(peck);
@ -627,10 +660,14 @@ MUNIT_TEST(test_ei_device_multitouch)
_unref_(ei_touch) *t1 = ei_device_touch_new(device);
_unref_(ei_touch) *t2 = ei_device_touch_new(device);
ei_touch_down(t1, 1, 2);
ei_device_frame(device);
ei_touch_motion(t1, 2, 3);
ei_device_frame(device);
ei_touch_down(t2, 3, 4);
ei_device_frame(device);
ei_touch_motion(t2, 4, 5);
ei_device_frame(device);
ei_touch_up(t2);
ei_touch_up(t1);

View file

@ -471,6 +471,7 @@ MUNIT_TEST(test_xdotool_rel_motion)
with_client(peck) {
struct ei_device *device = peck_ei_get_default_pointer(peck);
ei_device_pointer_motion(device, -1, 10);
ei_device_frame(device);
ei_device_close(device);
ei_unref(ei);
peck_drop_ei(peck);

View file

@ -362,17 +362,23 @@ int main(int argc, char **argv)
/* BTN_LEFT */
colorprint("sending button event\n");
ei_device_pointer_button(ptr, BTN_LEFT, true);
ei_device_frame(ptr);
ei_device_pointer_button(ptr, BTN_LEFT, false);
colorprint("sending scroll event\n");
ei_device_frame(ptr);
colorprint("sending scroll events\n");
ei_device_pointer_scroll(ptr, 1, 1);
ei_device_frame(ptr);
ei_device_pointer_scroll_discrete(ptr, 1, 1);
ei_device_frame(ptr);
}
if (have_kbd) {
static int key = 0;
colorprint("sending key event\n");
ei_device_keyboard_key(kbd, KEY_Q + key, true); /* KEY_Q */
ei_device_frame(ptr);
ei_device_keyboard_key(kbd, KEY_Q + key, false); /* KEY_Q */
ei_device_frame(ptr);
key = (key + 1) % 6;
}
@ -380,6 +386,7 @@ int main(int argc, char **argv)
static int x, y;
colorprint("sending abs event\n");
ei_device_pointer_motion_absolute(abs, 150 + ++x, 150 - ++y);
ei_device_frame(ptr);
}
}

View file

@ -278,6 +278,9 @@ eis_demo_server_printf_handle_event(struct eis_demo_server *server,
eis_event_keyboard_get_key_is_press(e));
}
break;
case EIS_EVENT_FRAME:
/* nothing to do, we're not fancy enough to accumulate events properly */
break;
default:
abort();
}