From 3a566262fee757855d7a81c682fc327c5b7b954b Mon Sep 17 00:00:00 2001
From: Christian Helmuth <christian.helmuth@genode-labs.com>
Date: Thu, 7 Sep 2023 15:52:02 +0200
Subject: [PATCH] lx_emul: multi-touch trackpad support in evdev.c

Also addresses remarks in issue #3019.
---
 repos/dde_linux/run/usb_hid_raw.run           |  20 +-
 .../lib/lx_emul/shadow/drivers/input/evdev.c  | 350 +++++++++++++++---
 repos/ports/run/vbox5_genode_usb_hid_raw.run  |  56 +--
 3 files changed, 338 insertions(+), 88 deletions(-)

diff --git a/repos/dde_linux/run/usb_hid_raw.run b/repos/dde_linux/run/usb_hid_raw.run
index 06e6bd38a7..a935210b47 100644
--- a/repos/dde_linux/run/usb_hid_raw.run
+++ b/repos/dde_linux/run/usb_hid_raw.run
@@ -172,7 +172,7 @@ install_config {
 #
 set fd [open [run_dir]/genode/usb_host_drv.config w]
 puts $fd {
-<config bios_handoff="yes">
+<config bios_handoff="no">
 	<report devices="yes"/>
 	<policy label_prefix="usb_hid_drv" class="0x3"/>
 </config>}
@@ -201,7 +201,7 @@ grep_output {^\[init }
 unify_output {(?n) on usb-usbbus.*$} ""
 unify_output {(?n) at usb-usbbus.*\)} ")"
 unify_output {(?n)hid-generic.*input:} "hid-generic: input:"
-unify_output {(?n)usb-[0-9]-[0-9]:} "usb-X-X:"
+unify_output {(?n)usb-[0-9]-[0-9]} "usb-X-X"
 unify_output {(?n)device number [0-9]+} "device number X"
 unify_output {(?n)input[0-9]} "inputX"
 unify_output {(?n) as /devices/.*} ""
@@ -212,9 +212,8 @@ compare_output_to {
 [init -> event_dump] Input event #0	PRESS KEY_X 65534	key count: 1
 [init -> event_dump] Input event #1	RELEASE KEY_X	key count: 0
 [init -> event_dump] Input event #2	PRESS BTN_LEFT 65534	key count: 1
-[init -> event_dump] Input event #3	REL_MOTION -1+0	key count: 1
-[init -> event_dump] Input event #4	REL_MOTION +0+1	key count: 1
-[init -> event_dump] Input event #5	RELEASE BTN_LEFT	key count: 0
+[init -> event_dump] Input event #3	REL_MOTION -1+1	key count: 1
+[init -> event_dump] Input event #4	RELEASE BTN_LEFT	key count: 0
 [init -> usb_hid_drv] usb usb-X-X: USB disconnect, device number X
 [init -> usb_hid_drv] Disconnected device: inputX
 [init -> usb_hid_drv] Disconnected device: inputX
@@ -224,10 +223,9 @@ compare_output_to {
 [init -> usb_hid_drv] input: HID 03eb:204d
 [init -> usb_hid_drv] Connected device: inputX (HID 03eb:204d)
 [init -> usb_hid_drv] hid-generic: input: USB HID v1.11 Mouse [HID 03eb:204d]
-[init -> event_dump] Input event #6	PRESS KEY_X 65534	key count: 1
-[init -> event_dump] Input event #7	RELEASE KEY_X	key count: 0
-[init -> event_dump] Input event #8	PRESS BTN_LEFT 65534	key count: 1
-[init -> event_dump] Input event #9	REL_MOTION -1+0	key count: 1
-[init -> event_dump] Input event #10	REL_MOTION +0+1	key count: 1
-[init -> event_dump] Input event #11	RELEASE BTN_LEFT	key count: 0
+[init -> event_dump] Input event #5	PRESS KEY_X 65534	key count: 1
+[init -> event_dump] Input event #6	RELEASE KEY_X	key count: 0
+[init -> event_dump] Input event #7	PRESS BTN_LEFT 65534	key count: 1
+[init -> event_dump] Input event #8	REL_MOTION -1+1	key count: 1
+[init -> event_dump] Input event #9	RELEASE BTN_LEFT	key count: 0
 }
diff --git a/repos/dde_linux/src/lib/lx_emul/shadow/drivers/input/evdev.c b/repos/dde_linux/src/lib/lx_emul/shadow/drivers/input/evdev.c
index 71931e71a0..4b4f14555f 100644
--- a/repos/dde_linux/src/lib/lx_emul/shadow/drivers/input/evdev.c
+++ b/repos/dde_linux/src/lib/lx_emul/shadow/drivers/input/evdev.c
@@ -21,50 +21,62 @@
 #include <linux/slab.h>
 #include <linux/module.h>
 #include <linux/input.h>
+#include <linux/input/mt.h>
 #include <linux/init.h>
 #include <linux/device.h>
 
+/*
+ * TODO differentiate touchpads, trackpads, touchscreen, etc.
+ *
+ * (from Documentation/input/event-codes.rst)
+ *
+ * INPUT_PROP_DIRECT + INPUT_PROP_POINTER
+ * --------------------------------------
+ *
+ * The INPUT_PROP_DIRECT property indicates that device coordinates should be
+ * directly mapped to screen coordinates (not taking into account trivial
+ * transformations, such as scaling, flipping and rotating). Non-direct input
+ * devices require non-trivial transformation, such as absolute to relative
+ * transformation for touchpads. Typical direct input devices: touchscreens,
+ * drawing tablets; non-direct devices: touchpads, mice.
+ *
+ * The INPUT_PROP_POINTER property indicates that the device is not transposed
+ * on the screen and thus requires use of an on-screen pointer to trace user's
+ * movements.  Typical pointer devices: touchpads, tablets, mice; non-pointer
+ * device: touchscreen.
+ *
+ * If neither INPUT_PROP_DIRECT or INPUT_PROP_POINTER are set, the property is
+ * considered undefined and the device type should be deduced in the
+ * traditional way, using emitted event types.
+ */
+
+struct evdev_mt_slot
+{
+	int tracking_id; /* -1 means unused */
+	int x, y, ox, oy;
+};
+#define INIT_MT_SLOT (struct evdev_mt_slot){ -1, -1, -1, -1, -1 }
+
+/* just stay with primary and secondary touch for now */
+enum { MAX_MT_SLOTS = 2, PRIMARY = 0, SECONDARY = 1, };
+
+struct evdev_mt
+{
+	unsigned             num_slots;
+	unsigned             cur_slot;
+	struct evdev_mt_slot slots[MAX_MT_SLOTS];
+};
 
 struct evdev
 {
 	struct genode_event *event;
 	struct input_handle  handle;
+	unsigned             pending;
+	int                  rel_x, rel_y, rel_wx, rel_wy;
+	struct evdev_mt      mt;
 };
 
 
-static void submit_rel_motion(struct genode_event_submit *submit,
-                              unsigned code, int value)
-{
-	switch (code) {
-	case REL_X:      submit->rel_motion(submit, value, 0); break;
-	case REL_Y:      submit->rel_motion(submit, 0, value); break;
-
-	case REL_HWHEEL: submit->wheel(submit, value, 0);      break;
-	case REL_WHEEL:  submit->wheel(submit, 0, value);      break;
-
-	/* skip for now because of large values */
-	case REL_HWHEEL_HI_RES: break;
-	case REL_WHEEL_HI_RES:  break;
-
-	default:
-		printk("Unsupported relative motion event code=%d dropped\n", code);
-	}
-}
-
-
-static void submit_key(struct genode_event_submit *submit,
-                       unsigned code, bool press)
-{
-	/* map BTN_TOUCH to BTN_LEFT */
-	if (code == BTN_TOUCH) code = BTN_LEFT;
-
-	if (press)
-		submit->press(submit, lx_emul_event_keycode(code));
-	else
-		submit->release(submit, lx_emul_event_keycode(code));
-}
-
-
 struct genode_event_generator_ctx
 {
 	struct evdev *evdev;
@@ -73,31 +85,244 @@ struct genode_event_generator_ctx
 };
 
 
+struct name { char s[32]; };
+#define NAME_INIT(name, fmt, ...) snprintf(name.s, sizeof(name.s), fmt, ## __VA_ARGS__)
+
+
+static struct name name_of_type(unsigned type)
+{
+	struct name result = { { 0 } };
+
+	switch (type) {
+	case EV_SYN: NAME_INIT(result, "SYN"); break;
+	case EV_KEY: NAME_INIT(result, "KEY"); break;
+	case EV_REL: NAME_INIT(result, "REL"); break;
+	case EV_ABS: NAME_INIT(result, "ABS"); break;
+	case EV_MSC: NAME_INIT(result, "MSC"); break;
+	default:     NAME_INIT(result, "%3u", type);
+	}
+
+	return result;
+}
+#define NAME_OF_TYPE(type) name_of_type(type).s
+
+
+static struct name name_of_code(unsigned type, unsigned code)
+{
+	struct name result = { { 0 } };
+
+	switch (type) {
+	case EV_SYN:
+		switch (code) {
+		case SYN_REPORT: NAME_INIT(result, "REPORT"); break;
+
+		default: NAME_INIT(result, "%u", code);
+		} break;
+
+	case EV_KEY:
+		switch (code) {
+		case BTN_LEFT:           NAME_INIT(result, "BTN_LEFT");           break;
+		case BTN_RIGHT:          NAME_INIT(result, "BTN_RIGHT");          break;
+		case BTN_TOOL_FINGER:    NAME_INIT(result, "BTN_TOOL_FINGER");    break;
+		case BTN_TOUCH:          NAME_INIT(result, "BTN_TOUCH");          break;
+		case BTN_TOOL_DOUBLETAP: NAME_INIT(result, "BTN_TOOL_DOUBLETAP"); break;
+		case BTN_TOOL_TRIPLETAP: NAME_INIT(result, "BTN_TOOL_TRIPLETAP"); break;
+		case BTN_TOOL_QUADTAP:   NAME_INIT(result, "BTN_TOOL_QUADTAP");   break;
+		case BTN_TOOL_QUINTTAP:  NAME_INIT(result, "BTN_TOOL_QUINTTAP");  break;
+
+		default: NAME_INIT(result, "%u", code);
+		} break;
+
+	case EV_REL:
+		switch (code) {
+		case REL_X:      NAME_INIT(result, "X");      break;
+		case REL_Y:      NAME_INIT(result, "Y");      break;
+		case REL_HWHEEL: NAME_INIT(result, "HWHEEL"); break;
+		case REL_WHEEL:  NAME_INIT(result, "WHEEL");  break;
+		case REL_MISC:   NAME_INIT(result, "MISC");   break;
+
+		default: NAME_INIT(result, "%u", code);
+		} break;
+
+	case EV_ABS:
+		switch (code) {
+		case ABS_X:              NAME_INIT(result, "X");              break;
+		case ABS_Y:              NAME_INIT(result, "Y");              break;
+		case ABS_MISC:           NAME_INIT(result, "MISC");           break;
+		case ABS_MT_SLOT:        NAME_INIT(result, "MT_SLOT");        break;
+		case ABS_MT_POSITION_X:  NAME_INIT(result, "MT_POSITION_X");  break;
+		case ABS_MT_POSITION_Y:  NAME_INIT(result, "MT_POSITION_Y");  break;
+		case ABS_MT_TOOL_TYPE:   NAME_INIT(result, "MT_TOOL_TYPE");   break;
+		case ABS_MT_TRACKING_ID: NAME_INIT(result, "MT_TRACKING_ID"); break;
+
+		default: NAME_INIT(result, "%u", code);
+		} break;
+
+	case EV_MSC:
+		switch (code) {
+		case MSC_SCAN:      NAME_INIT(result, "SCAN");      break;
+		case MSC_TIMESTAMP: NAME_INIT(result, "TIMESTAMP"); break;
+
+		default: NAME_INIT(result, "%u", code);
+		} break;
+
+	default:     NAME_INIT(result, "%u", code);
+	}
+
+	return result;
+}
+#define NAME_OF_CODE(type, code) name_of_code(type, code).s
+
+
+static bool handle_key(struct evdev *evdev, struct input_value const *v,
+                       struct genode_event_submit *submit)
+{
+	unsigned code = v->code;
+
+	if (v->type != EV_KEY)
+		return false;
+
+	/* map BTN_TOUCH to BTN_LEFT */
+	if (code == BTN_TOUCH) code = BTN_LEFT;
+
+	if (v->value)
+		submit->press(submit, lx_emul_event_keycode(code));
+	else
+		submit->release(submit, lx_emul_event_keycode(code));
+
+	return true;
+}
+
+
+static bool record_rel(struct evdev *evdev, struct input_value const *v)
+{
+	if (v->type != EV_REL)
+		return false;
+
+	switch (v->code) {
+	case REL_X:      evdev->rel_x  += v->value; break;
+	case REL_Y:      evdev->rel_y  += v->value; break;
+	case REL_HWHEEL: evdev->rel_wx += v->value; break;
+	case REL_WHEEL:  evdev->rel_wy += v->value; break;
+
+	default:
+		return false;
+	}
+	evdev->pending++;
+
+	return true;
+}
+
+
+static bool record_abs(struct evdev *evdev, struct input_value const *v)
+{
+	struct evdev_mt * const mt = &evdev->mt;
+
+	if (v->type != EV_ABS)
+		return false;
+
+	if (mt->num_slots) {
+		switch (v->code) {
+		case ABS_MT_SLOT:
+			mt->cur_slot = (v->value >= 0 ? v->value : 0);
+			break;
+
+		case ABS_MT_TRACKING_ID:
+			if (mt->cur_slot < mt->num_slots) {
+				mt->slots[mt->cur_slot] = INIT_MT_SLOT;
+				evdev->pending++;
+			}
+			break;
+
+		case ABS_MT_POSITION_X:
+			if (mt->cur_slot < mt->num_slots) {
+				mt->slots[mt->cur_slot].x = v->value;
+				evdev->pending++;
+			}
+			break;
+
+		case ABS_MT_POSITION_Y:
+			if (mt->cur_slot < mt->num_slots) {
+				mt->slots[mt->cur_slot].y = v->value;
+				evdev->pending++;
+			}
+			break;
+
+		default:
+			return false;
+		}
+	} else {
+		/* XXX absolute events not supported currently */
+		return false;
+	}
+
+	return true;
+}
+
+
+static void submit_mt_motion(struct evdev_mt_slot       *slot,
+                             struct genode_event_submit *submit)
+{
+	if (slot->ox != -1 && slot->oy != -1)
+		submit->rel_motion(submit, slot->x - slot->ox, slot->y - slot->oy);
+
+	slot->ox = slot->x;
+	slot->oy = slot->y;
+}
+
+
+static bool submit_on_syn(struct evdev *evdev, struct input_value const *v,
+                          struct genode_event_submit *submit)
+{
+	struct evdev_mt * const mt = &evdev->mt;
+
+	if (v->type != EV_SYN || v->code != SYN_REPORT)
+		return false;
+	if (!evdev->pending)
+		return true;
+
+	if (mt->num_slots) {
+		submit_mt_motion(&mt->slots[PRIMARY],   submit);
+		submit_mt_motion(&mt->slots[SECONDARY], submit);
+	} else {
+		if (evdev->rel_x || evdev->rel_y) {
+			submit->rel_motion(submit, evdev->rel_x, evdev->rel_y);
+			evdev->rel_x = evdev->rel_y = 0;
+		}
+		if (evdev->rel_wx || evdev->rel_wy) {
+			submit->wheel(submit, evdev->rel_wx, evdev->rel_wy);
+			evdev->rel_wx = evdev->rel_wy = 0;
+		}
+		/* XXX absolute events not supported currently */
+	}
+
+	evdev->pending = 0;
+
+	return true;
+}
+
 static void evdev_event_generator(struct genode_event_generator_ctx *ctx,
                                   struct genode_event_submit *submit)
 {
-	int i;
+	for (int i = 0; i < ctx->count; i++) {
+		struct evdev *evdev = ctx->evdev;
 
-	for (i = 0; i < ctx->count; i++) {
-		unsigned const type  = ctx->values[i].type;
-		unsigned const code  = ctx->values[i].code;
-		unsigned const value = ctx->values[i].value;
+		struct input_value const *v = &ctx->values[i];
+
+		bool processed = false;
 
 		/* filter input_repeat_key() */
-		if ((type == EV_KEY) && (value > 1)) continue;
+		if ((v->type == EV_KEY) && (v->value > 1)) continue;
 
-		/* filter EV_SYN and EV_MSC */
-		if (type == EV_SYN || type == EV_MSC) continue;
+		processed |= handle_key(evdev, v, submit);
+		processed |= record_abs(evdev, v);
+		processed |= record_rel(evdev, v);
+		processed |= submit_on_syn(evdev, v, submit);
 
-		switch (type) {
-		case EV_KEY: submit_key(submit, code, value);        break;
-		case EV_REL: submit_rel_motion(submit, code, value); break;
-
-		default:
-			printk("Unsupported Event[%u/%u] device=%s, type=%d, code=%d, value=%d dropped\n",
-			       i + 1, ctx->count, dev_name(&ctx->evdev->handle.dev->dev), type, code, value);
-			continue;
-		}
+		if (!processed)
+			printk("Dropping unsupported Event[%u/%u] device=%s type=%s code=%s value=%d\n",
+			       i + 1, ctx->count, evdev->handle.dev->name,
+			       NAME_OF_TYPE(v->type), NAME_OF_CODE(v->type, v->code), v->value);
 	}
 }
 
@@ -141,6 +366,31 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
 	evdev->handle.handler = handler;
 	evdev->handle.name    = dev->name;
 
+	if (dev->mt) {
+		struct evdev_mt *mt = &evdev->mt;
+
+		mt->num_slots = min(dev->mt->num_slots, MAX_MT_SLOTS);
+		mt->cur_slot = 0;
+		for (int i = 0; i < sizeof(mt->slots)/sizeof(*mt->slots); i++)
+			mt->slots[i] = INIT_MT_SLOT;
+
+		/* disable undesired events */
+		clear_bit(ABS_MT_TOOL_TYPE,   dev->absbit);
+		clear_bit(ABS_X,              dev->absbit);
+		clear_bit(ABS_Y,              dev->absbit);
+		clear_bit(BTN_TOOL_FINGER,    dev->keybit);
+		clear_bit(BTN_TOOL_DOUBLETAP, dev->keybit);
+		clear_bit(BTN_TOOL_TRIPLETAP, dev->keybit);
+		clear_bit(BTN_TOOL_QUADTAP,   dev->keybit);
+		clear_bit(BTN_TOOL_QUINTTAP,  dev->keybit);
+		clear_bit(BTN_TOUCH,          dev->keybit);
+	}
+
+	/* disable undesired events */
+	clear_bit(EV_MSC,            dev->evbit);
+	clear_bit(REL_HWHEEL_HI_RES, dev->relbit);
+	clear_bit(REL_WHEEL_HI_RES,  dev->relbit);
+
 	error = input_register_handle(&evdev->handle);
 	if (error)
 		goto err_free_handle;
diff --git a/repos/ports/run/vbox5_genode_usb_hid_raw.run b/repos/ports/run/vbox5_genode_usb_hid_raw.run
index 3f59c7f041..bf8cb8d472 100644
--- a/repos/ports/run/vbox5_genode_usb_hid_raw.run
+++ b/repos/ports/run/vbox5_genode_usb_hid_raw.run
@@ -288,39 +288,41 @@ regexp {(\[init -\> log_terminal\] \[init -\> event_dump\] Input event #0\t.*)}
 
 run_genode_until {.*\[init -\> event_dump\] Input event #11.*\n} 60 [output_spawn_id]
 
-unify_output { dev_info:} ""
-unify_output { usb [0-9\-]+:} ""
-unify_output { number [0-9]+} ""
-unify_output {(?n)on usb-usbbus.*$} ""
-unify_output {(?n)using .*$} ""
-unify_output {(?n)^.*__wait_event.*$} ""
-unify_output {(?n)^.*Failed to submit URB.*$} ""
-unify_output {(?n)^.*dev_warn.*$} ""
+# pay only attention to the output of init and its children
+grep_output {^\[init }
+
+unify_output {(?n) on usb-usbbus.*$} ""
+unify_output {(?n) at usb-usbbus.*\)} ")"
+unify_output {(?n)hid-generic.*input:} "hid-generic: input:"
+unify_output {(?n)usb-[0-9]-[0-9]} "usb-X-X"
+unify_output {(?n)device number [0-9]+} "device number X"
+unify_output {(?n)input[0-9]} "inputX"
+unify_output {(?n) as /devices/.*} ""
+unify_output {(?n)^\[init -\> usb_drv.*} ""
+unify_output {(?n)^\[init -\> log_terminal\] \[init -\> usb_drv.*} ""
 unify_output {(?n)^.*dangling allocation.*$} ""
-unify_output {(?n)^.*Warning:.*$} ""
 unify_output {(?n)^.*usbfs: process .* did not claim interface.*$} ""
-unify_output { [0-9][0-9][0-9][0-9]:[0-9][0-9][0-9][0-9] } " "
-filter_out_color_escape_sequences
 trim_lines
 
 compare_output_to {
 [init -> log_terminal] [init -> event_dump] Input event #0	PRESS KEY_X 65534	key count: 1
 [init -> log_terminal] [init -> event_dump] Input event #1	RELEASE KEY_X	key count: 0
 [init -> log_terminal] [init -> event_dump] Input event #2	PRESS BTN_LEFT 65534	key count: 1
-[init -> log_terminal] [init -> event_dump] Input event #3	REL_MOTION -1+0	key count: 1
-[init -> log_terminal] [init -> event_dump] Input event #4	REL_MOTION +0+1	key count: 1
-[init -> log_terminal] [init -> event_dump] Input event #5	RELEASE BTN_LEFT	key count: 0
-[init -> usb_drv] USB disconnect, device
-[init -> log_terminal] [init -> usb_drv -> usb_drv] USB disconnect, device
-[init -> usb_drv] new full-speed USB device
-[init -> virtualbox] Attach USB device usb-2-8
-[init -> log_terminal] [init -> usb_drv -> usb_drv] new full-speed USB device
-[init -> log_terminal] [init -> usb_hid_drv] input: USB HID v1.11 Keyboard [HID 03eb:204d]
-[init -> log_terminal] [init -> usb_hid_drv] input: USB HID v1.11 Mouse [HID 03eb:204d]
-[init -> log_terminal] [init -> event_dump] Input event #6	PRESS KEY_X 65534	key count: 1
-[init -> log_terminal] [init -> event_dump] Input event #7	RELEASE KEY_X	key count: 0
-[init -> log_terminal] [init -> event_dump] Input event #8	PRESS BTN_LEFT 65534	key count: 1
-[init -> log_terminal] [init -> event_dump] Input event #9	REL_MOTION -1+0	key count: 1
-[init -> log_terminal] [init -> event_dump] Input event #10	REL_MOTION +0+1	key count: 1
-[init -> log_terminal] [init -> event_dump] Input event #11	RELEASE BTN_LEFT	key count: 0
+[init -> log_terminal] [init -> event_dump] Input event #3	REL_MOTION -1+1	key count: 1
+[init -> log_terminal] [init -> event_dump] Input event #4	RELEASE BTN_LEFT	key count: 0
+[init -> log_terminal] [init -> usb_hid_drv] usb usb-X-X: USB disconnect, device number X
+[init -> log_terminal] [init -> usb_hid_drv] Disconnected device: inputX
+[init -> log_terminal] [init -> usb_hid_drv] Disconnected device: inputX
+[init -> virtualbox] Attach USB device usb-X-X
+[init -> log_terminal] [init -> usb_hid_drv] input: HID 03eb:204d
+[init -> log_terminal] [init -> usb_hid_drv] Connected device: inputX (HID 03eb:204d)
+[init -> log_terminal] [init -> usb_hid_drv] hid-generic: input: USB HID v1.11 Keyboard [HID 03eb:204d]
+[init -> log_terminal] [init -> usb_hid_drv] input: HID 03eb:204d
+[init -> log_terminal] [init -> usb_hid_drv] Connected device: inputX (HID 03eb:204d)
+[init -> log_terminal] [init -> usb_hid_drv] hid-generic: input: USB HID v1.11 Mouse [HID 03eb:204d]
+[init -> log_terminal] [init -> event_dump] Input event #5	PRESS KEY_X 65534	key count: 1
+[init -> log_terminal] [init -> event_dump] Input event #6	RELEASE KEY_X	key count: 0
+[init -> log_terminal] [init -> event_dump] Input event #7	PRESS BTN_LEFT 65534	key count: 1
+[init -> log_terminal] [init -> event_dump] Input event #8	REL_MOTION -1+1	key count: 1
+[init -> log_terminal] [init -> event_dump] Input event #9	RELEASE BTN_LEFT	key count: 0
 }