lx_emul: improve motion-device handling in evdev

The key element of the improvement is differentiated processing of
events of the following device types.

  Mouse:       relative motion
  Pointer:     absolute motion (Qemu usb-tablet and IP-KVM devices)
  Touchpad:    relative motion via absolute touchpad coordinates
  Touchtool:   absolute motion (e.g., stylus)
  Touchscreen: absolute motion and finger (multi-) touch

Processing is done in two stages for one "input packet". First, all
events of the packet are recorded into the current evdev state with
device-type specific operations. Then, appropriate Genode input events
are generated from the accumulated evdev state in the submission stage
(again by device-type specific functions).

A simple version of tap-to-click was added to the touchpad support.

Fixes #5105
This commit is contained in:
Christian Helmuth 2024-01-19 15:43:53 +01:00
parent a0e0000108
commit 1dcc6fda6b
4 changed files with 867 additions and 272 deletions

View File

@ -232,8 +232,8 @@ trim_lines
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+1 key count: 1
[init -> event_dump] Input event #2 REL_MOTION -1+1 key count: 0
[init -> event_dump] Input event #3 PRESS BTN_LEFT 65534 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
@ -242,11 +242,11 @@ compare_output_to {
[init -> usb_hid_drv] Connected device: inputX (HID 03eb:204d)
[init -> usb_hid_drv] hid-generic: input: USB HID v1.11 Keyboard [HID 03eb:204d]
[init -> usb_hid_drv] input: HID 03eb:204d
[init -> usb_hid_drv] Connected device: inputX (HID 03eb:204d)
[init -> usb_hid_drv] Connected device: inputX (HID 03eb:204d) MOUSE
[init -> usb_hid_drv] hid-generic: input: USB HID v1.11 Mouse [HID 03eb:204d]
[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 #7 REL_MOTION -1+1 key count: 0
[init -> event_dump] Input event #8 PRESS BTN_LEFT 65534 key count: 1
[init -> event_dump] Input event #9 RELEASE BTN_LEFT key count: 0
}

View File

@ -26,57 +26,609 @@
#include <linux/device.h>
/*
* TODO differentiate touchpads, trackpads, touchscreen, etc.
* Input devices with motion events
*
* (from Documentation/input/event-codes.rst)
*
* INPUT_PROP_DIRECT + INPUT_PROP_POINTER
* --------------------------------------
* (from Documentation/input/event-codes.rst and multi-touch-protocol.rst)
*
* 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.
* transformations, such as scaling, flipping and rotating).
* -> touchscreen, tablet (stylus/pen)
*
* 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.
* Non-direct input devices may require non-trivial transformation, such as
* absolute to relative transformation.
* -> mouse, touchpad
*
* 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.
* Historically a touch device with BTN_TOOL_FINGER and BTN_TOUCH was
* interpreted as a touchpad by userspace, while a similar device without
* BTN_TOOL_FINGER was interpreted as a touchscreen. For backwards
* compatibility with current userspace it is recommended to follow this
* distinction.
*
* In Linux, stylus/pen tool proximity is reported by BTN_TOOL_PEN/RUBBER plus
* ABS_DISTANCE events. The actual contact to the surface emits an additional
* BTN_TOUCH event. For multi-touch devices, the "tool" is also reported via
* BTN_TOOL_FINGER/DOUBLETAP etc.
*
* Thus, these devices must be differentiated.
*
* Mouse: relative motion
* Pointer: absolute motion (Qemu usb-tablet and IP-KVM devices)
* Touchpad: relative motion via absolute touchpad coordinates
* Touchtool: absolute motion (e.g., stylus)
* Touchscreen: absolute motion and finger (multi-) touch
*/
static bool is_rel_dev(struct input_dev *dev)
{
return test_bit(EV_REL, dev->evbit) && test_bit(REL_X, dev->relbit);
}
static bool is_abs_dev(struct input_dev *dev)
{
return test_bit(EV_ABS, dev->evbit) && test_bit(ABS_X, dev->absbit);
}
static bool is_touch_dev(struct input_dev *dev)
{
return test_bit(BTN_TOUCH, dev->keybit);
}
static bool is_tool_dev(struct input_dev *dev)
{
return test_bit(BTN_TOOL_PEN, dev->keybit)
|| test_bit(BTN_TOOL_RUBBER, dev->keybit)
|| test_bit(BTN_TOOL_BRUSH, dev->keybit)
|| test_bit(BTN_TOOL_PENCIL, dev->keybit)
|| test_bit(BTN_TOOL_AIRBRUSH, dev->keybit)
|| test_bit(BTN_TOOL_MOUSE, dev->keybit)
|| test_bit(BTN_TOOL_LENS, dev->keybit);
}
enum evdev_motion {
MOTION_NONE,
MOTION_MOUSE, /* relative motion */
MOTION_POINTER, /* absolute motion */
MOTION_TOUCHPAD, /* relative motion based on absolute axes */
MOTION_TOUCHTOOL, /* absolute motion */
MOTION_TOUCHSCREEN, /* absolute motion */
};
enum evdev_motion evdev_motion(struct input_dev const *dev)
{
if (is_rel_dev(dev))
return MOTION_MOUSE;
if (!is_abs_dev(dev))
return MOTION_NONE;
if (!is_touch_dev(dev))
return MOTION_POINTER;
if (test_bit(BTN_TOOL_FINGER, dev->keybit))
return MOTION_TOUCHPAD;
if (is_tool_dev(dev))
return MOTION_TOUCHTOOL;
return MOTION_TOUCHSCREEN;
}
struct evdev_mt_slot
{
int tracking_id; /* -1 means unused */
int 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, };
/*
* Maximum number of touch slots supported.
*
* Many Linux drivers report 2 to 10 slots, the Magic Trackpad reports 16. The
* Surface driver reports 64, which we just ignore.
*/
enum { MAX_MT_SLOTS = 16 };
struct evdev_mt
{
bool pending;
unsigned num_slots;
unsigned cur_slot;
struct evdev_mt_slot slots[MAX_MT_SLOTS];
};
#define array_for_each_element(element, array) \
for ((element) = (array); \
(element) < ((array) + ARRAY_SIZE((array))); \
(element)++)
#define for_each_mt_slot(slot, mt) \
array_for_each_element(slot, (mt)->slots)
struct evdev_key
{
bool pending;
unsigned code;
bool press;
typeof(jiffies) jiffies;
};
#define INIT_KEY (struct evdev_key){ false, 0, false }
#define EVDEV_KEY(code, press) (struct evdev_key){ true, code, press, jiffies }
struct evdev_keys
{
unsigned pending; /* pending keys counter */
struct evdev_key key[16]; /* max 16 keys per packet */
};
#define for_each_key(key, keys, pending_only) \
array_for_each_element(key, (keys)->key) \
if (!(pending_only) || (key)->pending)
#define for_each_pending_key(key, keys) \
if ((keys)->pending) \
for_each_key(key, keys, true)
struct evdev_xy
{
bool pending;
int x, y;
};
#define INIT_XY (struct evdev_xy){ false, 0, 0 }
struct evdev_touchpad
{
typeof(jiffies) touch_time;
bool btn_left_pressed; /* state of (physical) BTN_LEFT */
};
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;
enum evdev_motion motion;
/* record of all events in one packet - submitted on SYN */
unsigned tool; /* BTN_TOOL_* or 0 */
struct evdev_keys keys;
struct evdev_xy rel;
struct evdev_xy wheel;
struct evdev_xy abs;
struct evdev_mt mt;
/* device-specific state machine */
union {
struct evdev_touchpad touchpad;
};
};
/* helper functions (require declarations above) */
#include "evdev.h"
static bool record_mouse(struct evdev *evdev, struct input_value const *v)
{
if (v->type != EV_REL || evdev->motion != MOTION_MOUSE)
return false;
switch (v->code) {
case REL_X: evdev->rel.pending = true; evdev->rel.x += v->value; break;
case REL_Y: evdev->rel.pending = true; evdev->rel.y += v->value; break;
case REL_HWHEEL: evdev->wheel.pending = true; evdev->wheel.x += v->value; break;
case REL_WHEEL: evdev->wheel.pending = true; evdev->wheel.y += v->value; break;
default:
return false;
}
return true;
}
static bool record_abs(struct evdev *evdev, struct input_value const *v)
{
if (v->type != EV_ABS)
return false;
switch (v->code) {
case ABS_X: evdev->abs.pending = true; evdev->abs.x = v->value; break;
case ABS_Y: evdev->abs.pending = true; evdev->abs.y = v->value; break;
default:
return false;
}
return true;
}
static bool record_wheel(struct evdev *evdev, struct input_value const *v)
{
if (v->type != EV_REL)
return false;
switch (v->code) {
case REL_HWHEEL: evdev->wheel.pending = true; evdev->wheel.x += v->value; break;
case REL_WHEEL: evdev->wheel.pending = true; evdev->wheel.y += v->value; break;
default:
return false;
}
return true;
}
static bool record_pointer(struct evdev *evdev, struct input_value const *v)
{
if (evdev->motion != MOTION_POINTER)
return false;
return record_abs(evdev, v) || record_wheel(evdev, v);
}
static bool record_touchtool(struct evdev *evdev, struct input_value const *v)
{
if (evdev->motion != MOTION_TOUCHTOOL)
return false;
return record_abs(evdev, v) || record_wheel(evdev, v);
}
static bool record_mt(struct evdev_mt *mt, struct input_value const *v)
{
if (v->type != EV_ABS || !mt->num_slots)
return false;
switch (v->code) {
case ABS_MT_SLOT:
mt->cur_slot = (v->value >= 0 ? v->value : 0);
/* nothing pending yet */
break;
case ABS_MT_TRACKING_ID:
if (mt->cur_slot < mt->num_slots) {
mt->slots[mt->cur_slot].id = v->value >= 0 ? mt->cur_slot : -1;
mt->pending = true;
}
break;
case ABS_MT_POSITION_X:
if (mt->cur_slot < mt->num_slots) {
mt->slots[mt->cur_slot].x = v->value;
mt->pending = true;
}
break;
case ABS_MT_POSITION_Y:
if (mt->cur_slot < mt->num_slots) {
mt->slots[mt->cur_slot].y = v->value;
mt->pending = true;
}
break;
default:
return false;
}
return true;
}
static bool record_touchpad(struct evdev *evdev, struct input_value const *v)
{
if (evdev->motion != MOTION_TOUCHPAD)
return false;
/* monitor (physical) button state clashing with tap-to-click */
if (v->type == EV_KEY && v->code == BTN_LEFT)
evdev->touchpad.btn_left_pressed = !!v->value;
/* only multi-touch pads supported currently */
return record_mt(&evdev->mt, v);
}
static bool record_touchscreen(struct evdev *evdev, struct input_value const *v)
{
if (evdev->motion != MOTION_TOUCHSCREEN)
return false;
/* only multi-touch screens supported currently */
return record_mt(&evdev->mt, v);
}
static bool is_tool_key(unsigned code)
{
switch (code) {
case BTN_TOOL_PEN:
case BTN_TOOL_RUBBER:
case BTN_TOOL_BRUSH:
case BTN_TOOL_PENCIL:
case BTN_TOOL_AIRBRUSH:
case BTN_TOOL_FINGER:
case BTN_TOOL_MOUSE:
case BTN_TOOL_LENS:
case BTN_TOOL_QUINTTAP:
case BTN_TOOL_DOUBLETAP:
case BTN_TOOL_TRIPLETAP:
case BTN_TOOL_QUADTAP:
return true;
default:
return false;
}
}
static bool record_key(struct evdev *evdev, struct input_value const *v)
{
struct evdev_keys * const keys = &evdev->keys;
if (v->type != EV_KEY)
return false;
if (is_tool_key(v->code)) {
evdev->tool = v->value ? v->code : 0;
} else {
struct evdev_key *key;
for_each_key(key, keys, false) {
if (key->pending)
continue;
*key = EVDEV_KEY(v->code, !!v->value);
keys->pending++;
break;
}
}
return true;
}
static void submit_press_release(struct evdev_key *key, struct evdev_keys *keys,
struct genode_event_submit *submit)
{
if (!key->pending)
return;
if (key->press)
submit->press(submit, lx_emul_event_keycode(key->code));
else
submit->release(submit, lx_emul_event_keycode(key->code));
*key = INIT_KEY;
keys->pending--;
}
static void submit_keys(struct evdev_keys *keys, struct genode_event_submit *submit)
{
struct evdev_key *key;
if (!keys->pending)
return;
for_each_pending_key(key, keys)
submit_press_release(key, keys, submit);
}
static void submit_mouse(struct evdev *evdev, struct genode_event_submit *submit)
{
if (evdev->motion != MOTION_MOUSE)
return;
if (evdev->rel.pending) {
submit->rel_motion(submit, evdev->rel.x, evdev->rel.y);
evdev->rel = INIT_XY;
}
if (evdev->wheel.pending) {
submit->wheel(submit, evdev->wheel.x, evdev->wheel.y);
evdev->wheel = INIT_XY;
}
}
static void submit_pointer(struct evdev *evdev, struct genode_event_submit *submit)
{
if (evdev->motion != MOTION_POINTER)
return;
if (evdev->abs.pending) {
submit->abs_motion(submit, evdev->abs.x, evdev->abs.y);
evdev->abs.pending = false;
}
if (evdev->wheel.pending) {
submit->wheel(submit, evdev->wheel.x, evdev->wheel.y);
evdev->wheel = INIT_XY;
}
}
static void submit_touchtool(struct evdev *evdev, struct genode_event_submit *submit)
{
struct evdev_keys * const keys = &evdev->keys;
struct evdev_key *key;
if (evdev->motion != MOTION_TOUCHTOOL)
return;
if (evdev->abs.pending) {
submit->abs_motion(submit, evdev->abs.x, evdev->abs.y);
evdev->abs.pending = false;
}
/* submit recorded tool on BTN_TOUCH */
for_each_pending_key(key, keys) {
if (key->code != BTN_TOUCH)
continue;
key->code = evdev->tool;
submit_press_release(key, keys, submit);
break;
}
}
static void touchpad_tap_to_click(struct evdev_keys *keys, struct evdev_touchpad *tp,
struct genode_event_submit *submit)
{
enum { TAP_TIME = 130 /* max touch duration in ms */ };
struct evdev_key *key;
if (!keys->pending)
return;
for_each_pending_key(key, keys) {
if (key->code != BTN_TOUCH)
continue;
if (key->press && !tp->btn_left_pressed) {
tp->touch_time = key->jiffies;
} else {
if (time_before(key->jiffies, tp->touch_time + msecs_to_jiffies(TAP_TIME))) {
submit->press(submit, lx_emul_event_keycode(BTN_LEFT));
submit->release(submit, lx_emul_event_keycode(BTN_LEFT));
}
tp->touch_time = 0;
}
*key = INIT_KEY;
keys->pending--;
break;
}
}
static void submit_touchpad(struct evdev *evdev, struct genode_event_submit *submit)
{
struct evdev_mt * const mt = &evdev->mt;
struct evdev_mt_slot *slot;
if (evdev->motion != MOTION_TOUCHPAD)
return;
/*
* TODO device state model
*
* - click without small motion (if pad is pressable button)
* - two-finger scrolling
* - edge scrolling
* - virtual-button regions
*
* https://wayland.freedesktop.org/libinput/doc/latest/tapping.html
*/
if (mt->pending) {
for_each_mt_slot(slot, mt) {
if (slot->id == -1) {
*slot = INIT_MT_SLOT;
continue;
}
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;
}
mt->pending = false;
}
touchpad_tap_to_click(&evdev->keys, &evdev->touchpad, submit);
}
static void submit_touchscreen(struct evdev *evdev, struct genode_event_submit *submit)
{
struct evdev_mt * const mt = &evdev->mt;
struct evdev_keys * const keys = &evdev->keys;
struct evdev_key *key;
struct evdev_mt_slot *slot;
if (evdev->motion != MOTION_TOUCHSCREEN)
return;
if (mt->pending) {
for_each_mt_slot(slot, mt) {
if (slot->id == -1 && slot->ox != -1 && slot->oy != -1) {
submit->touch_release(submit, slot->id);
*slot = INIT_MT_SLOT;
continue;
}
/* skip unchanged slots */
if (slot->ox == slot->x && slot->oy == slot->y)
continue;
if (slot->x != -1 && slot->y != -1) {
struct genode_event_touch_args args = {
.finger = slot->id,
.xpos = slot->x,
.ypos = slot->y,
.width = 1
};
submit->touch(submit, &args);
}
slot->ox = slot->x;
slot->oy = slot->y;
}
mt->pending = false;
}
/* filter BTN_TOUCH */
for_each_pending_key(key, keys) {
if (key->code != BTN_TOUCH)
continue;
*key = INIT_KEY;
keys->pending--;
break;
}
}
static bool submit_on_syn(struct evdev *evdev, struct input_value const *v,
struct genode_event_submit *submit)
{
if (v->type != EV_SYN || v->code != SYN_REPORT)
return false;
/* motion devices */
submit_mouse(evdev, submit);
submit_pointer(evdev, submit);
submit_touchpad(evdev, submit);
submit_touchtool(evdev, submit);
submit_touchscreen(evdev, submit);
/* submit keys not handled above */
submit_keys(&evdev->keys, submit);
return true;
}
struct genode_event_generator_ctx
{
struct evdev *evdev;
@ -85,222 +637,6 @@ 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)
{
@ -317,9 +653,12 @@ static void evdev_event_generator(struct genode_event_generator_ctx *ctx,
/* filter input_repeat_key() */
if ((v->type == EV_KEY) && (v->value > 1)) continue;
processed |= handle_key(evdev, v, submit);
processed |= record_abs(evdev, v);
processed |= record_rel(evdev, v);
processed |= record_mouse(evdev, v);
processed |= record_pointer(evdev, v);
processed |= record_touchpad(evdev, v);
processed |= record_touchtool(evdev, v);
processed |= record_touchscreen(evdev, v);
processed |= record_key(evdev, v);
processed |= submit_on_syn(evdev, v, submit);
if (!processed)
@ -351,6 +690,55 @@ static void evdev_event(struct input_handle *handle,
}
static void init_motion(struct evdev *evdev)
{
struct input_dev *dev = evdev->handle.dev;
evdev->motion = evdev_motion(dev);
switch (evdev->motion) {
case MOTION_NONE:
case MOTION_MOUSE:
case MOTION_POINTER:
/* nothing to do */
break;
case MOTION_TOUCHPAD:
case MOTION_TOUCHTOOL:
case MOTION_TOUCHSCREEN:
if (dev->mt) {
struct evdev_mt *mt = &evdev->mt;
struct evdev_mt_slot *slot;
mt->num_slots = min(dev->mt->num_slots, MAX_MT_SLOTS);
mt->cur_slot = 0;
for_each_mt_slot(slot, mt)
*slot = INIT_MT_SLOT;
/* disable undesired events */
clear_bit(ABS_X, dev->absbit);
clear_bit(ABS_Y, dev->absbit);
clear_bit(ABS_PRESSURE, dev->absbit);
clear_bit(ABS_MT_TOUCH_MAJOR, dev->absbit);
clear_bit(ABS_MT_TOUCH_MINOR, dev->absbit);
clear_bit(ABS_MT_WIDTH_MAJOR, dev->absbit);
clear_bit(ABS_MT_WIDTH_MINOR, dev->absbit);
clear_bit(ABS_MT_ORIENTATION, dev->absbit);
clear_bit(ABS_MT_TOOL_TYPE, dev->absbit);
clear_bit(ABS_MT_PRESSURE, dev->absbit);
clear_bit(ABS_MT_TOOL_X, dev->absbit);
clear_bit(ABS_MT_TOOL_Y, dev->absbit);
} else {
/* disable undesired events */
clear_bit(ABS_PRESSURE, dev->absbit);
clear_bit(ABS_DISTANCE, dev->absbit);
}
break;
}
}
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
@ -369,25 +757,7 @@ 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);
}
init_motion(evdev);
/* disable undesired events */
clear_bit(EV_MSC, dev->evbit);
@ -402,10 +772,12 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
if (error)
goto err_unregister_handle;
printk("Connected device: %s (%s at %s)\n",
printk("Connected device: %s (%s at %s) %s%s\n",
dev_name(&dev->dev),
dev->name ?: "unknown",
dev->phys ?: "unknown");
dev->phys ?: "unknown",
dev->mt ? "MULTITOUCH " : "",
evdev->motion != MOTION_NONE ? NAME_OF_MOTION(evdev->motion) : "");
return 0;

View File

@ -0,0 +1,223 @@
/*
* \brief Linux emulation environment: evdev helpers
* \author Christian Helmuth
* \date 2024-01-29
*/
/*
* Copyright (C) 2024 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
#ifndef _SHADOW__DRIVERS__INPUT__EVDEV_H_
#define _SHADOW__DRIVERS__INPUT__EVDEV_H_
struct name { char s[32]; };
#define NAME_INIT(name, fmt, ...) snprintf(name.s, sizeof(name.s), fmt, ## __VA_ARGS__)
static struct name name_of_motion(enum evdev_motion motion)
{
struct name result = { { 0 } };
switch (motion) {
case MOTION_NONE: NAME_INIT(result, "NONE"); break;
case MOTION_MOUSE: NAME_INIT(result, "MOUSE"); break;
case MOTION_POINTER: NAME_INIT(result, "POINTER"); break;
case MOTION_TOUCHPAD: NAME_INIT(result, "TOUCHPAD"); break;
case MOTION_TOUCHTOOL: NAME_INIT(result, "TOUCHTOOL"); break;
case MOTION_TOUCHSCREEN: NAME_INIT(result, "TOUCHSCREEN"); break;
}
return result;
}
#define NAME_OF_MOTION(type) name_of_motion(type).s
static struct name name_of_prop(unsigned prop)
{
struct name result = { { 0 } };
switch (prop) {
case INPUT_PROP_POINTER: NAME_INIT(result, "POINTER"); break;
case INPUT_PROP_DIRECT: NAME_INIT(result, "DIRECT"); break;
case INPUT_PROP_BUTTONPAD: NAME_INIT(result, "BUTTONPAD"); break;
case INPUT_PROP_SEMI_MT: NAME_INIT(result, "SEMI_MT"); break;
case INPUT_PROP_TOPBUTTONPAD: NAME_INIT(result, "TOPBUTTONPAD"); break;
case INPUT_PROP_POINTING_STICK: NAME_INIT(result, "POINTING_STICK"); break;
case INPUT_PROP_ACCELEROMETER: NAME_INIT(result, "ACCELEROMETER"); break;
default: NAME_INIT(result, "%2u", prop);
}
return result;
}
#define NAME_OF_PROP(type) name_of_prop(type).s
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;
case EV_SW: NAME_INIT(result, "SW "); break;
case EV_LED: NAME_INIT(result, "LED"); break;
case EV_REP: NAME_INIT(result, "REP"); 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_MIDDLE: NAME_INIT(result, "BTN_MIDDLE"); break;
case BTN_SIDE: NAME_INIT(result, "BTN_SIDE"); break;
case BTN_EXTRA: NAME_INIT(result, "BTN_EXTRA"); break;
case BTN_FORWARD: NAME_INIT(result, "BTN_FORWARD"); break;
case BTN_BACK: NAME_INIT(result, "BTN_BACK"); break;
case BTN_TASK: NAME_INIT(result, "BTN_TASK"); break;
case BTN_TOOL_PEN: NAME_INIT(result, "BTN_TOOL_PEN"); break;
case BTN_TOOL_RUBBER: NAME_INIT(result, "BTN_TOOL_RUBBER"); break;
case BTN_TOOL_FINGER: NAME_INIT(result, "BTN_TOOL_FINGER"); break;
case BTN_TOUCH: NAME_INIT(result, "BTN_TOUCH"); break;
case BTN_STYLUS: NAME_INIT(result, "BTN_STYLUS"); break;
case BTN_STYLUS2: NAME_INIT(result, "BTN_STYLUS2"); 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;
case REL_WHEEL_HI_RES: NAME_INIT(result, "WHEEL_HI_RES"); break;
case REL_HWHEEL_HI_RES: NAME_INIT(result, "HWHEEL_HI_RES"); 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_PRESSURE: NAME_INIT(result, "PRESSURE"); break;
case ABS_DISTANCE: NAME_INIT(result, "DISTANCE"); break;
case ABS_MISC: NAME_INIT(result, "MISC"); break;
case ABS_MT_SLOT: NAME_INIT(result, "MT_SLOT"); break;
case ABS_MT_TOUCH_MAJOR: NAME_INIT(result, "MT_TOUCH_MAJOR"); break;
case ABS_MT_TOUCH_MINOR: NAME_INIT(result, "MT_TOUCH_MINOR"); break;
case ABS_MT_WIDTH_MAJOR: NAME_INIT(result, "MT_WIDTH_MAJOR"); break;
case ABS_MT_WIDTH_MINOR: NAME_INIT(result, "MT_WIDTH_MINOR"); break;
case ABS_MT_ORIENTATION: NAME_INIT(result, "MT_ORIENTATION"); 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;
case ABS_MT_PRESSURE: NAME_INIT(result, "MT_PRESSURE"); break;
case ABS_MT_DISTANCE: NAME_INIT(result, "MT_DISTANCE"); break;
case ABS_MT_TOOL_X: NAME_INIT(result, "MT_TOOL_X"); break;
case ABS_MT_TOOL_Y: NAME_INIT(result, "MT_TOOL_Y"); 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 void log_event_in_packet(unsigned cur, unsigned max,
struct input_value const *v,
struct evdev const *evdev)
{
printk("--- Event[%u/%u] %s '%s' %s type=%s code=%s value=%d\n", cur, max,
dev_name(&evdev->handle.dev->dev), evdev->handle.dev->name, NAME_OF_MOTION(evdev->motion),
NAME_OF_TYPE(v->type), NAME_OF_CODE(v->type, v->code), v->value);
}
static void log_device_info(struct input_dev *dev)
{
unsigned bit;
printk("device: %s (%s at %s)\n", dev_name(&dev->dev), dev->name ?: "unknown", dev->phys ?: "unknown");
printk(" propbit:");
for_each_set_bit(bit, dev->propbit, INPUT_PROP_CNT)
printk(" %s", NAME_OF_PROP(bit));
printk("\n");
printk(" evbit: ");
for_each_set_bit(bit, dev->evbit, EV_CNT)
printk(" %s", NAME_OF_TYPE(bit));
printk("\n");
if (test_bit(EV_REL, dev->evbit)) {
printk(" relbit: ");
for_each_set_bit(bit, dev->relbit, REL_CNT)
printk(" %s", NAME_OF_CODE(EV_REL, bit));
printk("\n");
}
if (test_bit(EV_ABS, dev->evbit)) {
printk(" absbit: ");
for_each_set_bit(bit, dev->absbit, ABS_CNT)
printk(" %s", NAME_OF_CODE(EV_ABS, bit));
printk("\n");
}
if (test_bit(EV_ABS, dev->evbit) || test_bit(EV_REL, dev->evbit)) {
printk(" keybit: ");
for_each_set_bit(bit, dev->keybit, KEY_CNT)
printk(" %s", NAME_OF_CODE(EV_KEY, bit));
printk("\n");
}
if (test_bit(EV_LED, dev->evbit)) {
unsigned count = 0;
for_each_set_bit(bit, dev->ledbit, LED_CNT)
count++;
printk(" leds: %u\n", count);
}
printk(" hint_events_per_packet: %u max_vals: %u\n", dev->hint_events_per_packet, dev->max_vals);
if (dev->mt) {
printk(" dev->mt->flags=%x\n", dev->mt->flags);
printk(" dev->mt->num_slots=%u\n", dev->mt->num_slots);
}
if (dev->absinfo) {
printk(" absinfo: %px\n", dev->absinfo);
}
}
#endif /* _SHADOW__DRIVERS__INPUT__EVDEV_H_ */

View File

@ -307,8 +307,8 @@ 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+1 key count: 1
[init -> log_terminal] [init -> event_dump] Input event #2 REL_MOTION -1+1 key count: 0
[init -> log_terminal] [init -> event_dump] Input event #3 PRESS BTN_LEFT 65534 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
@ -318,11 +318,11 @@ compare_output_to {
[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] Connected device: inputX (HID 03eb:204d) MOUSE
[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 #7 REL_MOTION -1+1 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 RELEASE BTN_LEFT key count: 0
}