From: Peter Hutterer Date: Fri, 7 Feb 2014 05:18:17 +0000 (+1000) Subject: touchpad: add support for multi-finger tapping X-Git-Tag: 0.2.0~50^2~12 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=d4bd05184adcfe65602a451f24f244c107174e40;p=platform%2Fupstream%2Flibinput.git touchpad: add support for multi-finger tapping Signed-off-by: Peter Hutterer --- diff --git a/doc/touchpad-tap-state-machine.svg b/doc/touchpad-tap-state-machine.svg new file mode 100644 index 00000000..50ebc713 --- /dev/null +++ b/doc/touchpad-tap-state-machine.svg @@ -0,0 +1,771 @@ +IDLETOUCHfirstfinger downfinger upbutton +1presstimeoutmove > thresholdsecondfinger downTOUCH_2secondfinger upbutton +2pressmove > +thresholdtimeoutbutton 1releasebutton +2releaseTAPPEDtimeoutfirstfinger downDRAGGINGfirstfinger upbtn1releaseIDLEthirdfinger downTOUCH_3secondfinger upbutton 3pressbutton 3releasemove > thresholdIDLEtimeoutthirdfinger upfirstfinger upIDLEfourthfinger downDRAGGING_OR_DOUBLETAPtimeoutfirstfinger upbutton +1releasebutton +1pressbtn1releasesecondfinger downmove > thresholdHOLDfirstfinger upsecondfinger downTOUCH_2_HOLDsecondfinger upfirstfinger upthirdfinger downTOUCH_3_HOLDsecondfinger upthirdfinger upfourthfinger downDEADsecondfinger upthirdfinger upfourthfinger downfirstfinger upfirstfinger upfirstfinger upIDLEif fingercount == 0secondfinger upDRAGGING_2firstfinger upsecondfinger downthirdfinger downbtn1releasephysbuttonpressfourthfinger downphysbuttonpressbutton 1releaseDRAGGING_WAITtimeoutfirstfinger down diff --git a/src/Makefile.am b/src/Makefile.am index da3a0480..579ed25f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,6 +13,7 @@ libinput_la_SOURCES = \ evdev.h \ evdev-mt-touchpad.c \ evdev-mt-touchpad.h \ + evdev-mt-touchpad-tap.c \ evdev-touchpad.c \ filter.c \ filter.h \ diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c new file mode 100644 index 00000000..7ac592b3 --- /dev/null +++ b/src/evdev-mt-touchpad-tap.c @@ -0,0 +1,586 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of Red Hat + * not be used in advertising or publicity pertaining to distribution + * of the software without specific, written prior permission. Red + * Hat makes no representations about the suitability of this software + * for any purpose. It is provided "as is" without express or implied + * warranty. + * + * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "evdev-mt-touchpad.h" + +#define CASE_RETURN_STRING(a) case a: return #a; + +#define DEFAULT_TAP_TIMEOUT_PERIOD 180 +#define DEFAULT_TAP_MOVE_THRESHOLD 30 + +enum tap_event { + TAP_EVENT_TOUCH = 12, + TAP_EVENT_MOTION, + TAP_EVENT_RELEASE, + TAP_EVENT_BUTTON, + TAP_EVENT_TIMEOUT, +}; + +/***************************************** + * DO NOT EDIT THIS FILE! + * + * Look at the state diagram in doc/touchpad-tap-state-machine.svg, or + * online at + * https://drive.google.com/file/d/0B1NwWmji69noYTdMcU1kTUZuUVE/edit?usp=sharing + * (it's a http://draw.io diagram) + * + * Any changes in this file must be represented in the diagram. + */ + +static inline const char* +tap_state_to_str(enum tp_tap_state state) { + + switch(state) { + CASE_RETURN_STRING(TAP_STATE_IDLE); + CASE_RETURN_STRING(TAP_STATE_HOLD); + CASE_RETURN_STRING(TAP_STATE_TOUCH); + CASE_RETURN_STRING(TAP_STATE_TAPPED); + CASE_RETURN_STRING(TAP_STATE_TOUCH_2); + CASE_RETURN_STRING(TAP_STATE_TOUCH_2_HOLD); + CASE_RETURN_STRING(TAP_STATE_TOUCH_3); + CASE_RETURN_STRING(TAP_STATE_TOUCH_3_HOLD); + CASE_RETURN_STRING(TAP_STATE_DRAGGING); + CASE_RETURN_STRING(TAP_STATE_DRAGGING_WAIT); + CASE_RETURN_STRING(TAP_STATE_DRAGGING_OR_DOUBLETAP); + CASE_RETURN_STRING(TAP_STATE_DRAGGING_2); + CASE_RETURN_STRING(TAP_STATE_DEAD); + } + return NULL; +} + +static inline const char* +tap_event_to_str(enum tap_event event) { + + switch(event) { + CASE_RETURN_STRING(TAP_EVENT_TOUCH); + CASE_RETURN_STRING(TAP_EVENT_MOTION); + CASE_RETURN_STRING(TAP_EVENT_RELEASE); + CASE_RETURN_STRING(TAP_EVENT_TIMEOUT); + CASE_RETURN_STRING(TAP_EVENT_BUTTON); + } + return NULL; +} +#undef CASE_RETURN_STRING + +static void +tp_tap_notify(struct tp_dispatch *tp, + uint32_t time, + int nfingers, + enum libinput_pointer_button_state state) +{ + int32_t button; + + switch (nfingers) { + case 1: button = BTN_LEFT; break; + case 2: button = BTN_RIGHT; break; + case 3: button = BTN_MIDDLE; break; + default: + return; + } + + pointer_notify_button(&tp->device->base, + time, + button, + state); +} + +static void +tp_tap_set_timer(struct tp_dispatch *tp, uint32_t time) +{ + uint32_t timeout = time + DEFAULT_TAP_TIMEOUT_PERIOD; + struct itimerspec its; + + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = timeout / 1000; + its.it_value.tv_nsec = (timeout % 1000) * 1000 * 1000; + timerfd_settime(tp->tap.timer_fd, TFD_TIMER_ABSTIME, &its, NULL); + + tp->tap.timeout = timeout; +} + +static void +tp_tap_clear_timer(struct tp_dispatch *tp) +{ + tp->tap.timeout = 0; +} + +static void +tp_tap_idle_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + case TAP_EVENT_MOTION: + log_info("invalid event, no fingers are down\n"); + break; + case TAP_EVENT_TIMEOUT: + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH_2; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TAPPED; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_TIMEOUT: + case TAP_EVENT_MOTION: + tp->tap.state = TAP_STATE_HOLD; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH_2; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_IDLE; + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_tapped_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_MOTION: + case TAP_EVENT_RELEASE: + log_info("invalid event when fingers are up\n"); + break; + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH_3; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_HOLD; + tp_tap_notify(tp, time, 2, LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, time, 2, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_MOTION: + tp_tap_clear_timer(tp); + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_TOUCH_2_HOLD; + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH_3; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_HOLD; + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_TOUCH_2_HOLD; + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_TOUCH_3_HOLD; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TOUCH_2_HOLD; + tp_tap_notify(tp, time, 3, LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, time, 3, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TOUCH_2_HOLD; + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING_2; + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_DRAGGING; + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_dragging_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING_2; + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_DRAGGING_WAIT; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + /* noop */ + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_RELEASE: + case TAP_EVENT_MOTION: + break; + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_DRAGGING; + break; + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + /* noop */ + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_dead_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_RELEASE: + if (tp->nfingers_down == 0) + tp->tap.state = TAP_STATE_IDLE; + break; + case TAP_EVENT_TOUCH: + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + case TAP_EVENT_BUTTON: + break; + } +} + +static void +tp_tap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + enum tp_tap_state current; + if (!tp->tap.enabled) + return; + + current = tp->tap.state; + + switch(tp->tap.state) { + case TAP_STATE_IDLE: + tp_tap_idle_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH: + tp_tap_touch_handle_event(tp, event, time); + break; + case TAP_STATE_HOLD: + tp_tap_hold_handle_event(tp, event, time); + break; + case TAP_STATE_TAPPED: + tp_tap_tapped_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH_2: + tp_tap_touch2_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH_2_HOLD: + tp_tap_touch2_hold_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH_3: + tp_tap_touch3_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH_3_HOLD: + tp_tap_touch3_hold_handle_event(tp, event, time); + break; + case TAP_STATE_DRAGGING_OR_DOUBLETAP: + tp_tap_dragging_or_doubletap_handle_event(tp, event, time); + break; + case TAP_STATE_DRAGGING: + tp_tap_dragging_handle_event(tp, event, time); + break; + case TAP_STATE_DRAGGING_WAIT: + tp_tap_dragging_wait_handle_event(tp, event, time); + break; + case TAP_STATE_DRAGGING_2: + tp_tap_dragging2_handle_event(tp, event, time); + break; + case TAP_STATE_DEAD: + tp_tap_dead_handle_event(tp, event, time); + break; + } + + if (tp->tap.state == TAP_STATE_IDLE || tp->tap.state == TAP_STATE_DEAD) + tp_tap_clear_timer(tp); + + log_debug("%s → %s → %s\n", tap_state_to_str(current), tap_event_to_str(event), tap_state_to_str(tp->tap.state)); +} + +static bool +tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp, struct tp_touch *t) +{ + int threshold = DEFAULT_TAP_MOVE_THRESHOLD; + double dx, dy; + + tp_get_delta(t, &dx, &dy); + + return dx * dx + dy * dy > threshold * threshold; +} + +int +tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time) +{ + struct tp_touch *t; + + if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) + tp_tap_handle_event(tp, TAP_EVENT_BUTTON, time); + + tp_for_each_touch(tp, t) { + if (!t->dirty || t->state == TOUCH_NONE) + continue; + + if (t->state == TOUCH_BEGIN) + tp_tap_handle_event(tp, TAP_EVENT_TOUCH, time); + else if (t->state == TOUCH_END) + tp_tap_handle_event(tp, TAP_EVENT_RELEASE, time); + else if (tp->tap.state != TAP_STATE_IDLE && + tp_tap_exceeds_motion_threshold(tp, t)) + tp_tap_handle_event(tp, TAP_EVENT_MOTION, time); + } + + return 0; +} + +static void +tp_tap_timeout_handler(void *data) +{ + struct tp_dispatch *touchpad = data; + uint64_t expires; + int len; + struct timespec ts; + uint32_t now; + + len = read(touchpad->tap.timer_fd, &expires, sizeof expires); + if (len != sizeof expires) + /* This will only happen if the application made the fd + * non-blocking, but this function should only be called + * upon the timeout, so lets continue anyway. */ + fprintf(stderr, "timerfd read error: %m\n"); + + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + + tp_tap_handle_timeout(touchpad, now); +} + +unsigned int +tp_tap_handle_timeout(struct tp_dispatch *tp, uint32_t time) +{ + if (!tp->tap.enabled) + return 0; + + if (tp->tap.timeout && tp->tap.timeout <= time) { + tp_tap_clear_timer(tp); + tp_tap_handle_event(tp, TAP_EVENT_TIMEOUT, time); + } + + return tp->tap.timeout; +} + +int +tp_init_tap(struct tp_dispatch *tp) +{ + tp->tap.state = TAP_STATE_IDLE; + tp->tap.timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + + if (tp->tap.timer_fd == -1) + return -1; + + tp->tap.source = + libinput_add_fd(tp->device->base.seat->libinput, + tp->tap.timer_fd, + tp_tap_timeout_handler, + tp); + + if (tp->tap.source == NULL) { + close(tp->tap.timer_fd); + return -1; + } + + tp->tap.enabled = 1; /* FIXME */ + + return 0; +} diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 73ea8a8d..d1268f6e 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -369,6 +369,7 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) return; } + tp_tap_handle_state(tp, time); if (t->history.count < 4) return; @@ -487,6 +488,9 @@ tp_init(struct tp_dispatch *tp, if (tp_init_accel(tp, diagonal) != 0) return -1; + if (tp_init_tap(tp) != 0) + return -1; + return 0; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 907aec83..973b4784 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -45,6 +45,22 @@ enum touch_state { TOUCH_END }; +enum tp_tap_state { + TAP_STATE_IDLE = 4, + TAP_STATE_TOUCH, + TAP_STATE_HOLD, + TAP_STATE_TAPPED, + TAP_STATE_TOUCH_2, + TAP_STATE_TOUCH_2_HOLD, + TAP_STATE_TOUCH_3, + TAP_STATE_TOUCH_3_HOLD, + TAP_STATE_DRAGGING_OR_DOUBLETAP, + TAP_STATE_DRAGGING, + TAP_STATE_DRAGGING_WAIT, + TAP_STATE_DRAGGING_2, + TAP_STATE_DEAD, /**< finger count exceeded */ +}; + struct tp_motion { int32_t x; int32_t y; @@ -97,6 +113,14 @@ struct tp_dispatch { } buttons; /* physical buttons */ enum touchpad_event queued; + + struct { + bool enabled; + int timer_fd; + struct libinput_source *source; + unsigned int timeout; + enum tp_tap_state state; + } tap; }; #define tp_for_each_touch(_tp, _t) \ @@ -105,4 +129,13 @@ struct tp_dispatch { void tp_get_delta(struct tp_touch *t, double *dx, double *dy); +int +tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time); + +unsigned int +tp_tap_handle_timeout(struct tp_dispatch *tp, uint32_t time); + +int +tp_init_tap(struct tp_dispatch *tp); + #endif