mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-05-04 21:18:04 +02:00
eis: keep track of touch IDs and don't allow duplicate ones
A client sending duplicate touch IDs will be disconnected but motion or end events for touches that no longer exist will be silently ignored. The tracking state uses a uint64_t to store currently valid touch ids - since the whole range of touch ids are uint32_t (including zero) this is the simple way of using an out-of-range marker value (UINT64_MAX) Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/368>
This commit is contained in:
parent
84c23989e9
commit
330b54d389
3 changed files with 160 additions and 3 deletions
|
|
@ -25,6 +25,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util-macros.h"
|
||||
#include "util-bits.h"
|
||||
|
|
@ -641,6 +642,35 @@ eis_device_get_keyboard_interface(struct eis_device *device)
|
|||
return &keyboard_interface;
|
||||
}
|
||||
|
||||
/* Returns true and the position of the touch with the given ID, or
|
||||
* false and the first position that is available
|
||||
*/
|
||||
static bool
|
||||
find_touch(struct eis_device *device, uint32_t touchid, size_t *index)
|
||||
{
|
||||
ssize_t first_available = -1;
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
|
||||
if (device->touch_state.down[i] != UINT64_MAX) {
|
||||
if (device->touch_state.down[i] == touchid) {
|
||||
if (index)
|
||||
*index = i;
|
||||
return true;
|
||||
}
|
||||
} else if (first_available < 0) {
|
||||
first_available = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (index) {
|
||||
if (first_available < 0)
|
||||
*index = EIS_MAX_TOUCHES;
|
||||
else
|
||||
*index = (size_t)first_available;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_touch_down(struct eis_touchscreen *touchscreen,
|
||||
uint32_t touchid, float x, float y)
|
||||
|
|
@ -655,6 +685,17 @@ client_msg_touch_down(struct eis_touchscreen *touchscreen,
|
|||
}
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
size_t first_available;
|
||||
if (find_touch(device, touchid, &first_available)) {
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Touch down event for duplicated touch ID");
|
||||
}
|
||||
|
||||
if (first_available >= EIS_MAX_TOUCHES)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_ERROR,
|
||||
"Too many simultaneous touch events");
|
||||
|
||||
device->touch_state.down[first_available] = touchid;
|
||||
eis_queue_touch_down_event(device, touchid, x, y);
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -676,13 +717,26 @@ client_msg_touch_motion(struct eis_touchscreen *touchscreen,
|
|||
}
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
eis_queue_touch_motion_event(device, touchid, x, y);
|
||||
/* Silently ignore motion for non-existing touches */
|
||||
if (find_touch(device, touchid, NULL))
|
||||
eis_queue_touch_motion_event(device, touchid, x, y);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return maybe_error_on_device_state(device, "touch motion");
|
||||
}
|
||||
|
||||
static bool
|
||||
release_touch(struct eis_device *device, uint32_t touchid)
|
||||
{
|
||||
size_t index;
|
||||
bool rc = find_touch(device, touchid, &index);
|
||||
if (rc)
|
||||
device->touch_state.down[index] = UINT64_MAX;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_touch_up(struct eis_touchscreen *touchscreen, uint32_t touchid)
|
||||
{
|
||||
|
|
@ -696,7 +750,8 @@ client_msg_touch_up(struct eis_touchscreen *touchscreen, uint32_t touchid)
|
|||
}
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
eis_queue_touch_up_event(device, touchid);
|
||||
if (release_touch(device, touchid))
|
||||
eis_queue_touch_up_event(device, touchid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -722,7 +777,8 @@ client_msg_touch_cancel(struct eis_touchscreen *touchscreen, uint32_t touchid)
|
|||
}
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
eis_queue_touch_cancel_event(device, touchid);
|
||||
if (release_touch(device, touchid))
|
||||
eis_queue_touch_cancel_event(device, touchid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -776,6 +832,10 @@ eis_seat_new_device(struct eis_seat *seat)
|
|||
|
||||
list_append(&seat->devices, &device->link);
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
|
||||
device->touch_state.down[i] = UINT64_MAX;
|
||||
}
|
||||
|
||||
return eis_device_ref(device);
|
||||
}
|
||||
|
||||
|
|
@ -1463,6 +1523,10 @@ eis_device_pause(struct eis_device *device)
|
|||
eis_device_event_paused(device, eis_client_get_next_serial(client));
|
||||
|
||||
memset(device->key_button_state.down, 0, sizeof(device->key_button_state.down));
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
|
||||
device->touch_state.down[i] = UINT64_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
_public_ void
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@
|
|||
#define KEY_MAX 0x2ffU
|
||||
#define KEY_CNT (KEY_MAX + 1)
|
||||
|
||||
#define EIS_MAX_TOUCHES 16
|
||||
|
||||
enum eis_device_state {
|
||||
EIS_DEVICE_STATE_NEW,
|
||||
EIS_DEVICE_STATE_AWAITING_READY,
|
||||
|
|
@ -82,6 +84,10 @@ struct eis_device {
|
|||
struct {
|
||||
unsigned char down[NCHARS(KEY_CNT)];
|
||||
} key_button_state;
|
||||
|
||||
struct {
|
||||
uint64_t down[EIS_MAX_TOUCHES]; /* touch id */
|
||||
} touch_state;
|
||||
};
|
||||
|
||||
struct eis_touch {
|
||||
|
|
|
|||
|
|
@ -1261,3 +1261,90 @@ class TestEiProtocol:
|
|||
else:
|
||||
ei.callback_roundtrip()
|
||||
assert status.disconnected is False
|
||||
|
||||
def test_touch_disconnect_on_duplicate_id(self, eis):
|
||||
"""
|
||||
Ensure EIS disconnects us when we use a duplicate touch id
|
||||
"""
|
||||
|
||||
ei = eis.ei
|
||||
|
||||
@dataclass
|
||||
class Status:
|
||||
device: EiDevice = None
|
||||
touchscreen: Optional[EiTouchscreen] = None
|
||||
disconnected: bool = False
|
||||
resumed: bool = False
|
||||
serial: int = 0
|
||||
|
||||
status = Status()
|
||||
|
||||
def on_interface(device, object, name, version, new_objects):
|
||||
logger.debug(
|
||||
"new capability",
|
||||
device=device,
|
||||
object=object,
|
||||
name=name,
|
||||
version=version,
|
||||
)
|
||||
if name == InterfaceName.EI_TOUCHSCREEN:
|
||||
assert status.touchscreen is None
|
||||
status.touchscreen = new_objects["object"]
|
||||
|
||||
def on_device_resumed(device, serial):
|
||||
status.resumed = True
|
||||
status.serial = serial
|
||||
|
||||
def on_new_device(seat, device, version, new_objects):
|
||||
logger.debug("new device", object=new_objects["device"])
|
||||
status.device = new_objects["device"]
|
||||
status.device.connect("Interface", on_interface)
|
||||
status.device.connect("Resumed", on_device_resumed)
|
||||
|
||||
def on_new_object(o: Interface):
|
||||
logger.debug("new object", object=o)
|
||||
if o.name == InterfaceName.EI_SEAT:
|
||||
ei.seat_fill_capability_masks(o)
|
||||
o.connect("Device", on_new_device)
|
||||
|
||||
ei.context.connect("register", on_new_object)
|
||||
ei.dispatch()
|
||||
|
||||
def on_disconnected(connection, last_serial, reason, explanation):
|
||||
status.disconnected = True
|
||||
|
||||
def on_connection(setup, serial, id, version, new_objects={}):
|
||||
connection = new_objects["connection"]
|
||||
connection.connect("Disconnected", on_disconnected)
|
||||
|
||||
setup = ei.handshake
|
||||
setup.connect("Connection", on_connection)
|
||||
ei.init_default_sender_connection(interface_versions={"ei_touchscreen": 1})
|
||||
|
||||
ei.wait_for_seat()
|
||||
seat = ei.seats[0]
|
||||
ei.send(seat.Bind(seat.bind_mask([InterfaceName.EI_TOUCHSCREEN])))
|
||||
ei.wait_for(lambda: status.touchscreen and status.resumed)
|
||||
|
||||
assert status.touchscreen is not None
|
||||
|
||||
ei.send(status.device.StartEmulating(status.serial, 123))
|
||||
logger.debug("Sending touch events")
|
||||
touchid = 1
|
||||
touchscreen = status.touchscreen
|
||||
device = status.device
|
||||
ei.send(touchscreen.Down(touchid, 10, 20))
|
||||
ei.send(device.Frame(status.serial, int(time.time())))
|
||||
ei.send(touchscreen.Motion(touchid, 10, 25))
|
||||
ei.send(device.Frame(status.serial, int(time.time())))
|
||||
|
||||
ei.send(touchscreen.Down(touchid, 10, 20))
|
||||
try:
|
||||
ei.send(device.Frame(status.serial, int(time.time())))
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
ei.dispatch()
|
||||
ei.wait_for(lambda: status.disconnected)
|
||||
|
||||
assert status.disconnected is True
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue