diff --git a/tests/Makefile b/tests/Makefile index e02090c..ae42f9e 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -5,9 +5,9 @@ LIBS = -lthread -lbio PROG = unit_test TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \ - ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c + ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c wayland_test.c TESTOBJ = $(TESTSRC:.c=.o) -PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c +PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c wayland_state.c PARENTOBJ = $(PARENTSRC:%.c=unit_%.o) OBJS = $(TESTOBJ) $(PARENTOBJ) diff --git a/tests/test.h b/tests/test.h index 4698951..6a103a2 100644 --- a/tests/test.h +++ b/tests/test.h @@ -23,6 +23,9 @@ void hmap_long_utf8_keys(struct ct*); void hmap_binary_keys_and_invalid_lengths(struct ct*); void trie_exact_prefix_and_duplicate(struct ct*); void trie_optional_outputs_and_invalid_lengths(struct ct*); +void wayland_press_release_state(struct ct*); +void wayland_repeat_state(struct ct*); +void wayland_repeat_cancellation(struct ct*); void production_maps_load(struct ct*); void transmap_states(struct ct*); void korean_sequences(struct ct*); diff --git a/tests/unit_test.c b/tests/unit_test.c index 8eccf45..c4846e9 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -72,6 +72,9 @@ static const struct ct_test tests[] = { { "hmap/binary-invalid-lengths", hmap_binary_keys_and_invalid_lengths }, { "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate }, { "trie/optional-invalid-lengths", trie_optional_outputs_and_invalid_lengths }, + { "wayland/press-release", wayland_press_release_state }, + { "wayland/repeat", wayland_repeat_state }, + { "wayland/repeat-cancellation", wayland_repeat_cancellation }, { "map/production-lifecycle", production_maps_load }, { "transmap/states", transmap_states }, { "hangul/sequences", korean_sequences }, diff --git a/tests/wayland_test.c b/tests/wayland_test.c new file mode 100644 index 0000000..35109cf --- /dev/null +++ b/tests/wayland_test.c @@ -0,0 +1,67 @@ +#include "test.h" +#include "../wayland_state.h" + +void +wayland_press_release_state(struct ct *t) +{ + Wlstate s; + + wlstateinit(&s); + CT_CHECK(t, wlstatepress(&s, 30, 1, 0, 0)); + CT_CHECK(t, !wlstaterelease(&s, 30)); + CT_CHECK(t, wlstaterelease(&s, 30)); + CT_CHECK(t, wlstatepress(&s, 31, 0, 0, 0)); + CT_CHECK(t, wlstaterelease(&s, 31)); + CT_CHECK(t, !wlstatepress(&s, Wlkeymax, 1, 0, 0)); + CT_CHECK(t, wlstaterelease(&s, Wlkeymax)); +} + +void +wayland_repeat_state(struct ct *t) +{ + Wlstate s; + u32int key; + + wlstateinit(&s); + wlstaterepeatinfo(&s, 25, 400); + CT_CHECK(t, wlstatepress(&s, 14, 1, 1, 100)); + CT_EQ_INT(t, 400, wlstatetimeout(&s, 100)); + CT_CHECK(t, !wlstaterepeat(&s, 499, &key)); + CT_CHECK(t, wlstaterepeat(&s, 500, &key)); + CT_EQ_UINT(t, 14, key); + CT_EQ_INT(t, 40, wlstatetimeout(&s, 500)); + CT_CHECK(t, !wlstaterelease(&s, 14)); + CT_EQ_INT(t, -1, wlstatetimeout(&s, 500)); + + CT_CHECK(t, wlstatepress(&s, 15, 1, 0, 600)); + CT_EQ_INT(t, -1, wlstatetimeout(&s, 600)); + CT_CHECK(t, !wlstaterelease(&s, 15)); + CT_CHECK(t, wlstatepress(&s, 16, 0, 1, 600)); + CT_EQ_INT(t, -1, wlstatetimeout(&s, 600)); + CT_CHECK(t, wlstaterelease(&s, 16)); +} + +void +wayland_repeat_cancellation(struct ct *t) +{ + Wlstate s; + + wlstateinit(&s); + wlstaterepeatinfo(&s, 20, 300); + CT_CHECK(t, wlstatepress(&s, 20, 1, 1, 0)); + CT_CHECK(t, wlstatepress(&s, 19, 0, 0, 10)); + CT_EQ_INT(t, -1, wlstatetimeout(&s, 10)); + CT_CHECK(t, !wlstaterelease(&s, 20)); + CT_CHECK(t, wlstaterelease(&s, 19)); + CT_CHECK(t, wlstatepress(&s, 20, 1, 1, 0)); + wlstaterepeatinfo(&s, 30, 200); + CT_EQ_INT(t, -1, wlstatetimeout(&s, 0)); + CT_CHECK(t, !wlstaterelease(&s, 20)); + CT_CHECK(t, wlstatepress(&s, 21, 1, 1, 0)); + wlstatecancelrepeat(&s); + CT_EQ_INT(t, -1, wlstatetimeout(&s, 0)); + CT_CHECK(t, !wlstaterelease(&s, 21)); + CT_CHECK(t, wlstatepress(&s, 22, 1, 1, 0)); + wlstateclear(&s); + CT_CHECK(t, wlstaterelease(&s, 22)); +} diff --git a/wayland.c b/wayland.c index bfcdfb4..9cd5c4f 100644 --- a/wayland.c +++ b/wayland.c @@ -1,18 +1,20 @@ #include "dat.h" #include "fn.h" +#include "wayland_state.h" -#include -#include -#include -#include +#include #include +#include #include +#include +#include #include #include #include "input-method-unstable-v2-client-protocol.h" #include "virtual-keyboard-unstable-v1-client-protocol.h" static struct wl_display *dpy; +static struct wl_registry *reg; static struct wl_seat *seat; static struct zwp_input_method_manager_v2 *immgr; static struct zwp_virtual_keyboard_manager_v1 *vkmgr; @@ -24,9 +26,25 @@ static struct xkb_keymap *keymap; static struct xkb_state *xkbst; static int active; static int pending; +static int newactivation; +static int running; static u32int imserial; +static u32int seatname; +static u32int immgrname; +static u32int vkmgrname; static Channel *replyc; static uvlong owner; +static Wlstate keys; + +static uvlong +nowms(void) +{ + struct timespec ts; + + if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0) + return 0; + return (uvlong)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} static void sendkey(u32int ks, u32int mod, Keyres *res) @@ -45,17 +63,20 @@ sendkey(u32int ks, u32int mod, Keyres *res) } static void -sendrelease(void) +releaseowner(void) { Keyreq kr; Keyres res; + if(owner == 0) + return; memset(&kr, 0, sizeof kr); kr.owner = owner; kr.op = Keyrelease; kr.reply = replyc; chansend(keyc, &kr); chanrecv(replyc, &res); + owner = 0; } static u32int @@ -95,47 +116,101 @@ mget(void) } static void -kpress(uint32_t time, uint32_t keycode, uint32_t state) +putresult(Keyres *res) +{ + char commit[Maxutf], preedit[Maxutf]; + int plen; + + if(im == nil) + return; + stoutf(&res->commit, commit, sizeof commit); + stoutf(&res->preedit, preedit, sizeof preedit); + if(commit[0] != '\0') + zwp_input_method_v2_commit_string(im, commit); + plen = strlen(preedit); + zwp_input_method_v2_set_preedit_string(im, preedit, plen, plen); + zwp_input_method_v2_commit(im, imserial); +} + +static void +forwardkey(u32int time, u32int keycode, u32int state) +{ + if(vk != nil) + zwp_virtual_keyboard_v1_key(vk, time, keycode, state); +} + +static void +keypress(u32int time, u32int keycode) { xkb_keysym_t ks; u32int k, mod; Keyres res; - char commit[Maxutf], preedit[Maxutf]; - int plen; + int repeatable; - if(state != WL_KEYBOARD_KEY_STATE_PRESSED || xkbst == nil){ - zwp_virtual_keyboard_v1_key(vk, time, keycode, state); + if(xkbst == nil || keycode >= Wlkeymax){ + forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED); + wlstatepress(&keys, keycode, 0, 0, nowms()); return; } ks = xkb_state_key_get_one_sym(xkbst, keycode + 8); k = kget(ks); if(k == 0){ - zwp_virtual_keyboard_v1_key(vk, time, keycode, state); + forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED); + wlstatepress(&keys, keycode, 0, 0, nowms()); return; } mod = mget(); sendkey(k, mod, &res); - stoutf(&res.commit, commit, sizeof commit); - stoutf(&res.preedit, preedit, sizeof preedit); - if(commit[0] != '\0'){ - zwp_input_method_v2_commit_string(im, commit); - zwp_input_method_v2_commit(im, imserial); - } - plen = strlen(preedit); - zwp_input_method_v2_set_preedit_string(im, preedit, plen, plen); - zwp_input_method_v2_commit(im, imserial); + putresult(&res); + repeatable = keymap != nil && + xkb_keymap_key_repeats(keymap, keycode + 8); + wlstatepress(&keys, keycode, res.eaten, repeatable, nowms()); if(!res.eaten) - zwp_virtual_keyboard_v1_key(vk, time, keycode, state); + forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED); +} + +static void +keyrepeat(u32int time, u32int keycode) +{ + xkb_keysym_t ks; + u32int k; + Keyres res; + + if(xkbst == nil) + return; + ks = xkb_state_key_get_one_sym(xkbst, keycode + 8); + k = kget(ks); + if(k == 0) + return; + sendkey(k, mget(), &res); + putresult(&res); + /* The original eaten press still owns its eventual physical release. */ + if(!res.eaten){ + forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED); + forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_RELEASED); + } +} + +static void +kpress(u32int time, u32int keycode, u32int state) +{ + if(state == WL_KEYBOARD_KEY_STATE_PRESSED){ + keypress(time, keycode); + return; + } + if(wlstaterelease(&keys, keycode)) + forwardkey(time, keycode, state); } static void kg_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 *g, - uint32_t format, int32_t fd, uint32_t size) + u32int format, int32_t fd, u32int size) { char *s; (void)data; (void)g; + wlstatecancelrepeat(&keys); if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1){ close(fd); return; @@ -155,13 +230,14 @@ kg_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 *g, xkbst = xkb_state_new(keymap); munmap(s, size); } - zwp_virtual_keyboard_v1_keymap(vk, format, fd, size); + if(vk != nil) + zwp_virtual_keyboard_v1_keymap(vk, format, fd, size); close(fd); } static void kg_key(void *data, struct zwp_input_method_keyboard_grab_v2 *g, - uint32_t serial, uint32_t time, uint32_t key, uint32_t state) + u32int serial, u32int time, u32int key, u32int state) { (void)data; (void)g; @@ -171,15 +247,15 @@ kg_key(void *data, struct zwp_input_method_keyboard_grab_v2 *g, static void kg_mods(void *data, struct zwp_input_method_keyboard_grab_v2 *g, - uint32_t serial, uint32_t dep, uint32_t lat, uint32_t lck, - uint32_t group) + u32int serial, u32int dep, u32int lat, u32int lck, u32int group) { (void)data; (void)g; (void)serial; if(xkbst != nil) xkb_state_update_mask(xkbst, dep, lat, lck, 0, 0, group); - zwp_virtual_keyboard_v1_modifiers(vk, dep, lat, lck, group); + if(vk != nil) + zwp_virtual_keyboard_v1_modifiers(vk, dep, lat, lck, group); } static void @@ -188,8 +264,7 @@ kg_repeat(void *data, struct zwp_input_method_keyboard_grab_v2 *g, { (void)data; (void)g; - (void)rate; - (void)delay; + wlstaterepeatinfo(&keys, rate, delay); } static const struct zwp_input_method_keyboard_grab_v2_listener @@ -206,6 +281,7 @@ im_activate(void *data, struct zwp_input_method_v2 *m) (void)data; (void)m; pending = 1; + newactivation = 1; } static void @@ -214,11 +290,12 @@ im_deactivate(void *data, struct zwp_input_method_v2 *m) (void)data; (void)m; pending = 0; + wlstateclear(&keys); } static void im_surrounding(void *data, struct zwp_input_method_v2 *m, - const char *text, uint32_t cursor, uint32_t anchor) + const char *text, u32int cursor, u32int anchor) { (void)data; (void)m; @@ -228,7 +305,7 @@ im_surrounding(void *data, struct zwp_input_method_v2 *m, } static void -im_textchange(void *data, struct zwp_input_method_v2 *m, uint32_t cause) +im_textchange(void *data, struct zwp_input_method_v2 *m, u32int cause) { (void)data; (void)m; @@ -237,7 +314,7 @@ im_textchange(void *data, struct zwp_input_method_v2 *m, uint32_t cause) static void im_content(void *data, struct zwp_input_method_v2 *m, - uint32_t hint, uint32_t purpose) + u32int hint, u32int purpose) { (void)data; (void)m; @@ -245,28 +322,42 @@ im_content(void *data, struct zwp_input_method_v2 *m, (void)purpose; } +static void +endactivation(void) +{ + wlstateclear(&keys); + active = 0; + releaseowner(); + if(grab != nil){ + zwp_input_method_keyboard_grab_v2_release(grab); + grab = nil; + } +} + +static void +beginactivation(void) +{ + if(active) + endactivation(); + active = 1; + owner = ownernew(); + grab = zwp_input_method_v2_grab_keyboard(im); + if(grab != nil) + zwp_input_method_keyboard_grab_v2_add_listener(grab, + &grab_listener, nil); +} + static void im_done(void *data, struct zwp_input_method_v2 *m) { (void)data; (void)m; imserial++; - if(pending && !active){ - active = 1; - owner = ownernew(); - grab = zwp_input_method_v2_grab_keyboard(im); - if(grab != nil) - zwp_input_method_keyboard_grab_v2_add_listener(grab, - &grab_listener, nil); - }else if(!pending && active){ - active = 0; - sendrelease(); - owner = 0; - if(grab != nil){ - zwp_input_method_keyboard_grab_v2_release(grab); - grab = nil; - } - } + if(newactivation && pending) + beginactivation(); + else if(!pending && active) + endactivation(); + newactivation = 0; } static void @@ -274,6 +365,8 @@ im_unavail(void *data, struct zwp_input_method_v2 *m) { (void)data; (void)m; + wlstateclear(&keys); + running = 0; fprint(2, "strans: wayland: input-method unavailable\n"); } @@ -288,28 +381,34 @@ static const struct zwp_input_method_v2_listener im_listener = { }; static void -reg_global(void *data, struct wl_registry *r, uint32_t name, - const char *iface, uint32_t version) +reg_global(void *data, struct wl_registry *r, u32int name, + const char *iface, u32int version) { (void)data; - if(strcmp(iface, wl_seat_interface.name) == 0) + if(strcmp(iface, wl_seat_interface.name) == 0 && seat == nil){ + seatname = name; seat = wl_registry_bind(r, name, &wl_seat_interface, version < 5 ? version : 5); - else if(strcmp(iface, zwp_input_method_manager_v2_interface.name) == 0) + }else if(strcmp(iface, + zwp_input_method_manager_v2_interface.name) == 0 && immgr == nil){ + immgrname = name; immgr = wl_registry_bind(r, name, &zwp_input_method_manager_v2_interface, 1); - else if(strcmp(iface, - zwp_virtual_keyboard_manager_v1_interface.name) == 0) + }else if(strcmp(iface, + zwp_virtual_keyboard_manager_v1_interface.name) == 0 && vkmgr == nil){ + vkmgrname = name; vkmgr = wl_registry_bind(r, name, &zwp_virtual_keyboard_manager_v1_interface, 1); + } } static void -reg_remove(void *data, struct wl_registry *r, uint32_t name) +reg_remove(void *data, struct wl_registry *r, u32int name) { (void)data; (void)r; - (void)name; + if(name == seatname || name == immgrname || name == vkmgrname) + running = 0; } static const struct wl_registry_listener reg_listener = { @@ -317,37 +416,159 @@ static const struct wl_registry_listener reg_listener = { .global_remove = reg_remove, }; -void -waylandthread(void *_) +static int +dispatch(void) { - struct wl_registry *reg; + struct pollfd pfd; + u32int key; + uvlong now; + int flush, timeout, n; - (void)_; - threadsetname("wayland"); - while((dpy = wl_display_connect(nil)) == nil) - poll(nil, 0, 1000); - xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if(xkb == nil){ + pfd.fd = wl_display_get_fd(dpy); + while(running){ + if(wl_display_dispatch_pending(dpy) < 0) + return -1; + if(!running) + break; + now = nowms(); + if(wlstaterepeat(&keys, now, &key)) + keyrepeat((u32int)now, key); + while(wl_display_prepare_read(dpy) != 0) + if(wl_display_dispatch_pending(dpy) < 0) + return -1; + flush = wl_display_flush(dpy); + if(flush < 0 && errno != EAGAIN){ + wl_display_cancel_read(dpy); + return -1; + } + pfd.events = POLLIN; + if(flush < 0) + pfd.events |= POLLOUT; + pfd.revents = 0; + timeout = wlstatetimeout(&keys, nowms()); + n = poll(&pfd, 1, timeout); + if(n < 0){ + wl_display_cancel_read(dpy); + if(errno == EINTR) + continue; + return -1; + } + if(n == 0){ + wl_display_cancel_read(dpy); + continue; + } + if(pfd.revents & POLLIN){ + if(wl_display_read_events(dpy) < 0) + return -1; + }else + wl_display_cancel_read(dpy); + if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) + return -1; + if(pfd.revents & POLLOUT) + wl_display_flush(dpy); + } + return 0; +} + +static void +resetconnection(void) +{ + dpy = nil; + reg = nil; + seat = nil; + immgr = nil; + vkmgr = nil; + im = nil; + grab = nil; + vk = nil; + xkb = nil; + keymap = nil; + xkbst = nil; + active = 0; + pending = 0; + newactivation = 0; + running = 1; + imserial = 0; + seatname = 0; + immgrname = 0; + vkmgrname = 0; + owner = 0; + wlstateinit(&keys); +} + +static void +cleanup(void) +{ + running = 0; + wlstateclear(&keys); + releaseowner(); + active = 0; + if(xkbst != nil) + xkb_state_unref(xkbst); + if(keymap != nil) + xkb_keymap_unref(keymap); + if(xkb != nil) + xkb_context_unref(xkb); + if(grab != nil) + wl_proxy_destroy((struct wl_proxy*)grab); + if(vk != nil) + wl_proxy_destroy((struct wl_proxy*)vk); + if(im != nil) + wl_proxy_destroy((struct wl_proxy*)im); + if(vkmgr != nil) + wl_proxy_destroy((struct wl_proxy*)vkmgr); + if(immgr != nil) + wl_proxy_destroy((struct wl_proxy*)immgr); + if(seat != nil) + wl_proxy_destroy((struct wl_proxy*)seat); + if(reg != nil) + wl_registry_destroy(reg); + if(dpy != nil){ wl_display_disconnect(dpy); dpy = nil; - return; } +} + +static void +connectonce(void) +{ + resetconnection(); + dpy = wl_display_connect(nil); + if(dpy == nil) + return; + xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if(xkb == nil) + return; reg = wl_display_get_registry(dpy); + if(reg == nil) + return; wl_registry_add_listener(reg, ®_listener, nil); - wl_display_roundtrip(dpy); + if(wl_display_roundtrip(dpy) < 0) + return; + if(!running) + return; if(seat == nil || immgr == nil || vkmgr == nil){ fprint(2, "strans: wayland: compositor missing input-method-v2\n"); - wl_display_disconnect(dpy); - dpy = nil; return; } im = zwp_input_method_manager_v2_get_input_method(immgr, seat); - zwp_input_method_v2_add_listener(im, &im_listener, nil); vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat); - replyc = chancreate(sizeof(Keyres), 0); - while(wl_display_dispatch(dpy) != -1) - ; - chanfree(replyc); - replyc = nil; + if(im == nil || vk == nil) + return; + zwp_input_method_v2_add_listener(im, &im_listener, nil); + dispatch(); +} + +void +waylandthread(void *_) +{ + (void)_; + threadsetname("wayland"); + replyc = chancreate(sizeof(Keyres), 0); + for(;;){ + connectonce(); + cleanup(); + poll(nil, 0, 1000); + } } diff --git a/wayland_state.c b/wayland_state.c new file mode 100644 index 0000000..9849637 --- /dev/null +++ b/wayland_state.c @@ -0,0 +1,107 @@ +#include "wayland_state.h" + +#include +#include + +enum { + Downforward = 1, + Downeaten = 2, +}; + +void +wlstateinit(Wlstate *s) +{ + memset(s, 0, sizeof *s); +} + +void +wlstatecancelrepeat(Wlstate *s) +{ + s->repeating = 0; + s->repeatkey = 0; + s->repeatat = 0; +} + +void +wlstateclear(Wlstate *s) +{ + memset(s->down, 0, sizeof s->down); + wlstatecancelrepeat(s); +} + +void +wlstaterepeatinfo(Wlstate *s, int rate, int delay) +{ + /* Settings changes end the old schedule; a later press starts a new one. */ + wlstatecancelrepeat(s); + s->rate = rate > 0 ? rate : 0; + s->delay = delay > 0 ? delay : 0; +} + +int +wlstatepress(Wlstate *s, uint32_t key, int eaten, int repeatable, + uint64_t now) +{ + uint64_t delay; + + if(key >= Wlkeymax) + return 0; + s->down[key] = eaten ? Downeaten : Downforward; + wlstatecancelrepeat(s); + if(eaten && repeatable && s->rate > 0){ + delay = s->delay; + if(UINT64_MAX - now < delay) + s->repeatat = UINT64_MAX; + else + s->repeatat = now + delay; + s->repeatkey = key; + s->repeating = 1; + } + return 1; +} + +int +wlstaterelease(Wlstate *s, uint32_t key) +{ + int forward; + + if(key >= Wlkeymax) + return 1; + forward = s->down[key] != Downeaten; + s->down[key] = 0; + if(s->repeating && s->repeatkey == key) + wlstatecancelrepeat(s); + return forward; +} + +int +wlstatetimeout(Wlstate *s, uint64_t now) +{ + uint64_t d; + + if(!s->repeating) + return -1; + if(now >= s->repeatat) + return 0; + d = s->repeatat - now; + return d > INT_MAX ? INT_MAX : (int)d; +} + +int +wlstaterepeat(Wlstate *s, uint64_t now, uint32_t *key) +{ + uint64_t step; + + if(!s->repeating || now < s->repeatat) + return 0; + if(key != 0) + *key = s->repeatkey; + step = 1000 / s->rate; + if(step == 0) + step = 1; + if(UINT64_MAX - now < step) + s->repeatat = UINT64_MAX; + else + s->repeatat = now + step; + return 1; +} diff --git a/wayland_state.h b/wayland_state.h new file mode 100644 index 0000000..9102d06 --- /dev/null +++ b/wayland_state.h @@ -0,0 +1,31 @@ +#ifndef WAYLAND_STATE_H +#define WAYLAND_STATE_H + +#include + +enum { + /* Wayland key codes are Linux input-event codes. */ + Wlkeymax = 0x300, +}; + +typedef struct Wlstate Wlstate; +struct Wlstate +{ + unsigned char down[Wlkeymax]; + uint32_t repeatkey; + uint64_t repeatat; + int rate; + int delay; + int repeating; +}; + +void wlstateinit(Wlstate*); +int wlstatepress(Wlstate*, uint32_t, int, int, uint64_t); +int wlstaterelease(Wlstate*, uint32_t); +void wlstaterepeatinfo(Wlstate*, int, int); +void wlstatecancelrepeat(Wlstate*); +void wlstateclear(Wlstate*); +int wlstatetimeout(Wlstate*, uint64_t); +int wlstaterepeat(Wlstate*, uint64_t, uint32_t*); + +#endif