tablet: sync tools already in proximity at startup

If a tool is in proximity when we init, send a proximity event immediately.

This is only partially reliable due to the current kernel behavior:
* if the tool comes into proximity when there is no evdev client, the device
  won't send any events and must be lifted out-of-proximity first. Patch is in
  the works, see https://patchwork.kernel.org/patch/5924611/
* before 3.19, if the tool was in proximity (with an evdev client attached),
  but goes out of proximity and back in with no client connected, we get an
  immediate proximity out event from the kernel once we connect to the device
  and no further events after that.
  See kernel commit b905811a49bcd6e6726ce5bbb591f57aaddfd3be

Otherwise, things work as expected. The above should be fixed in the kernel
anyway.

Note that this changes the order of events during a udev seat init, before we
had all DEVICE_ADDED events in a row, now the proximity event may be
interspersed.

Reported-by: Jason Gerecke <killertofu@gmail.com>
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Acked-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
This commit is contained in:
Peter Hutterer 2015-02-26 13:55:08 +10:00
parent bd6d427829
commit 411f95b17f
6 changed files with 136 additions and 1 deletions

View file

@ -897,6 +897,7 @@ static struct evdev_dispatch_interface tp_interface = {
tp_device_removed, /* device_suspended, treat as remove */
tp_device_added, /* device_resumed, treat as add */
tp_tag_device,
NULL, /* post_added */
};
static void

View file

@ -190,7 +190,7 @@ tablet_update_tool(struct tablet_dispatch *tablet,
tablet_set_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY);
tablet_unset_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY);
}
else
else if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
}
@ -868,6 +868,9 @@ tablet_flush(struct tablet_dispatch *tablet,
tablet->current_tool_id,
tablet->current_tool_serial);
if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
return;
if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) {
/* Release all stylus buttons */
memset(tablet->button_state.stylus_buttons,
@ -915,7 +918,11 @@ tablet_flush(struct tablet_dispatch *tablet,
tablet_change_to_left_handed(device);
}
}
static inline void
tablet_reset_state(struct tablet_dispatch *tablet)
{
/* Update state */
memcpy(&tablet->prev_button_state,
&tablet->button_state,
@ -946,6 +953,7 @@ tablet_process(struct evdev_dispatch *dispatch,
break;
case EV_SYN:
tablet_flush(tablet, device, time);
tablet_reset_state(tablet);
break;
default:
log_error(device->base.seat->libinput,
@ -970,6 +978,47 @@ tablet_destroy(struct evdev_dispatch *dispatch)
free(tablet);
}
static void
tablet_check_initial_proximity(struct evdev_device *device,
struct evdev_dispatch *dispatch)
{
bool tool_in_prox = false;
int code, state;
enum libinput_tool_type tool;
struct tablet_dispatch *tablet = (struct tablet_dispatch*)dispatch;
for (tool = LIBINPUT_TOOL_PEN; tool <= LIBINPUT_TOOL_MAX; tool++) {
code = tablet_tool_to_evcode(tool);
/* we only expect one tool to be in proximity at a time */
if (libevdev_fetch_event_value(device->evdev,
EV_KEY,
code,
&state) && state) {
tool_in_prox = true;
break;
}
}
if (!tool_in_prox)
return;
tablet_update_tool(tablet, device, tool, state);
tablet->current_tool_id =
libevdev_get_event_value(device->evdev,
EV_ABS,
ABS_MISC);
tablet->current_tool_serial =
libevdev_get_event_value(device->evdev,
EV_MSC,
MSC_SERIAL);
tablet_flush(tablet,
device,
libinput_now(device->base.seat->libinput));
}
static struct evdev_dispatch_interface tablet_interface = {
tablet_process,
NULL, /* remove */
@ -979,6 +1028,7 @@ static struct evdev_dispatch_interface tablet_interface = {
NULL, /* device_suspended */
NULL, /* device_resumed */
NULL, /* tag_device */
tablet_check_initial_proximity,
};
static int

View file

@ -28,6 +28,7 @@
#define LIBINPUT_TABLET_AXIS_NONE 0
#define LIBINPUT_TOOL_NONE 0
#define LIBINPUT_TOOL_MAX LIBINPUT_TOOL_LENS
enum tablet_status {
TABLET_NONE = 0,
@ -156,4 +157,24 @@ axis_to_evcode(const enum libinput_tablet_axis axis)
return evcode;
}
static inline int
tablet_tool_to_evcode(enum libinput_tool_type type)
{
int code;
switch (type) {
case LIBINPUT_TOOL_PEN: code = BTN_TOOL_PEN; break;
case LIBINPUT_TOOL_ERASER: code = BTN_TOOL_RUBBER; break;
case LIBINPUT_TOOL_BRUSH: code = BTN_TOOL_BRUSH; break;
case LIBINPUT_TOOL_PENCIL: code = BTN_TOOL_PENCIL; break;
case LIBINPUT_TOOL_AIRBRUSH: code = BTN_TOOL_AIRBRUSH; break;
case LIBINPUT_TOOL_FINGER: code = BTN_TOOL_FINGER; break;
case LIBINPUT_TOOL_MOUSE: code = BTN_TOOL_MOUSE; break;
case LIBINPUT_TOOL_LENS: code = BTN_TOOL_LENS; break;
default:
abort();
}
return code;
}
#endif

View file

@ -803,6 +803,7 @@ struct evdev_dispatch_interface fallback_interface = {
NULL, /* device_suspended */
NULL, /* device_resumed */
fallback_tag_device,
NULL, /* post_added */
};
static uint32_t
@ -1592,6 +1593,10 @@ evdev_notify_added_device(struct evdev_device *device)
}
notify_added_device(&device->base);
if (device->dispatch->interface->post_added)
device->dispatch->interface->post_added(device,
device->dispatch);
}
static int

View file

@ -196,6 +196,11 @@ struct evdev_dispatch_interface {
/* Tag device with one of EVDEV_TAG */
void (*tag_device)(struct evdev_device *device,
struct udev_device *udev_device);
/* Called immediately after the LIBINPUT_EVENT_DEVICE_ADDED event
* was sent */
void (*post_added)(struct evdev_device *device,
struct evdev_dispatch *dispatch);
};
struct evdev_dispatch {

View file

@ -1157,6 +1157,58 @@ START_TEST(tool_capabilities)
}
END_TEST
START_TEST(tool_in_prox_before_start)
{
struct libinput *li;
struct litest_device *dev = litest_current_device();
struct libinput_event *event;
struct axis_replacement axes[] = {
{ ABS_DISTANCE, 10 },
{ ABS_TILT_X, 0 },
{ ABS_TILT_Y, 0 },
{ -1, -1 }
};
const char *devnode;
litest_tablet_proximity_in(dev, 10, 10, axes);
/* for simplicity, we create a new litest context */
devnode = libevdev_uinput_get_devnode(dev->uinput);
li = litest_create_context();
libinput_path_add_device(li, devnode);
litest_wait_for_event_of_type(li,
LIBINPUT_EVENT_DEVICE_ADDED,
-1);
event = libinput_get_event(li);
libinput_event_destroy(event);
litest_wait_for_event_of_type(li,
LIBINPUT_EVENT_TABLET_PROXIMITY,
-1);
event = libinput_get_event(li);
libinput_event_destroy(event);
litest_assert_empty_queue(li);
litest_tablet_motion(dev, 10, 20, axes);
litest_tablet_motion(dev, 30, 40, axes);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_AXIS);
litest_assert_empty_queue(li);
litest_event(dev, EV_KEY, BTN_STYLUS, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_STYLUS, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_BUTTON);
litest_tablet_proximity_out(dev);
litest_wait_for_event_of_type(li,
LIBINPUT_EVENT_TABLET_PROXIMITY,
-1);
libinput_unref(li);
}
END_TEST
START_TEST(mouse_tool)
{
struct litest_device *dev = litest_current_device();
@ -1615,6 +1667,7 @@ main(int argc, char **argv)
{
litest_add("tablet:tool", tool_ref, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
litest_add_no_device("tablet:tool", tool_capabilities);
litest_add("tablet:tool", tool_in_prox_before_start, LITEST_TABLET, LITEST_ANY);
litest_add("tablet:tool_serial", tool_serial, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
litest_add("tablet:tool_serial", serial_changes_tool, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
litest_add("tablet:tool_serial", invalid_serials, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);