URI: 
       tAdd double/triple-click; add explicit scroll event - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit b610b280bbdbda3dba8f8fc3e67961f26857da43
   DIR parent 99773bbc2bcaea288c5d8b657bdff74ae69e216a
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Wed, 16 Aug 2023 22:11:10 +0200
       
       Add double/triple-click; add explicit scroll event
       
       Diffstat:
         M src/array.h                         |      65 +++++++++++++++++--------------
         M src/box.c                           |      16 ++++++++--------
         M src/entry.c                         |       2 ++
         M src/event.h                         |      10 ++++++++--
         M src/event_xlib.c                    |     187 ++++++++++++++++++++++++++-----
         M src/eventdefs.h                     |      18 +++++++++++++-----
         M src/grid.c                          |       1 +
         M src/ltkd.c                          |      11 ++++++++---
         M src/menu.c                          |      46 ++++++++++++++-----------------
         M src/proto_types.h                   |      45 +++++++++++++++++++------------
         M src/scrollbar.c                     |       2 +-
         M src/widget.c                        |     111 ++++++++++++++++++++++++++++---
         M src/widget.h                        |       3 +++
       
       13 files changed, 394 insertions(+), 123 deletions(-)
       ---
   DIR diff --git a/src/array.h b/src/array.h
       t@@ -30,27 +30,34 @@
        #include "util.h"
        #include "memory.h"
        
       -#define LTK_ARRAY_INIT_DECL_BASE(name, type, storage)                                                        \
       -typedef struct {                                                                                        \
       -        type *buf;                                                                                        \
       -        size_t buf_size;                                                                                \
       -        size_t len;                                                                                        \
       -} ltk_array_##name;                                                                                        \
       -                                                                                                        \
       -storage ltk_array_##name *ltk_array_create_##name(size_t initial_len);                                        \
       -storage type ltk_array_pop_##name(ltk_array_##name *ar);                                                \
       -storage void ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len);                \
       -storage void ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len);        \
       -storage void ltk_array_resize_##name(ltk_array_##name *ar, size_t size);                                \
       -storage void ltk_array_destroy_##name(ltk_array_##name *ar);                                                \
       -storage void ltk_array_clear_##name(ltk_array_##name *ar);                                                \
       -storage void ltk_array_append_##name(ltk_array_##name *ar, type elem);                                        \
       -storage void ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type));                \
       -storage type ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index);                                \
       -storage void ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e);
       +/* FIXME: make this work on more compilers? */
       +#if (defined(__GNUC__) || defined(__clang__))
       +#define LTK_UNUSED_FUNC __attribute__((unused))
       +#else
       +#define LTK_UNUSED_FUNC
       +#endif
       +
       +#define LTK_ARRAY_INIT_DECL_BASE(name, type, storage)                                                                        \
       +typedef struct {                                                                                                        \
       +        type *buf;                                                                                                        \
       +        size_t buf_size;                                                                                                \
       +        size_t len;                                                                                                        \
       +} ltk_array_##name;                                                                                                        \
       +                                                                                                                        \
       +LTK_UNUSED_FUNC storage ltk_array_##name *ltk_array_create_##name(size_t initial_len);                                        \
       +LTK_UNUSED_FUNC storage type ltk_array_pop_##name(ltk_array_##name *ar);                                                \
       +LTK_UNUSED_FUNC storage void ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len);                \
       +LTK_UNUSED_FUNC storage void ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len);        \
       +LTK_UNUSED_FUNC storage void ltk_array_resize_##name(ltk_array_##name *ar, size_t size);                                \
       +LTK_UNUSED_FUNC storage void ltk_array_destroy_##name(ltk_array_##name *ar);                                                \
       +LTK_UNUSED_FUNC storage void ltk_array_clear_##name(ltk_array_##name *ar);                                                \
       +LTK_UNUSED_FUNC storage void ltk_array_append_##name(ltk_array_##name *ar, type elem);                                        \
       +LTK_UNUSED_FUNC storage void ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type));                \
       +LTK_UNUSED_FUNC storage type ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index);                                \
       +LTK_UNUSED_FUNC storage void ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e);
        
        #define LTK_ARRAY_INIT_IMPL_BASE(name, type, storage)                                                        \
       -storage ltk_array_##name *                                                                                \
       +LTK_UNUSED_FUNC storage ltk_array_##name *                                                                \
        ltk_array_create_##name(size_t initial_len) {                                                                \
                if (initial_len == 0)                                                                                \
                        ltk_fatal("Array length is zero\n");                                                        \
       t@@ -61,7 +68,7 @@ ltk_array_create_##name(size_t initial_len) {                                                                \
                return ar;                                                                                        \
        }                                                                                                        \
                                                                                                                \
       -storage type                                                                                                \
       +LTK_UNUSED_FUNC storage type                                                                                \
        ltk_array_pop_##name(ltk_array_##name *ar) {                                                                \
                if (ar->len == 0)                                                                                 \
                        ltk_fatal("Array empty; cannot pop.\n");                                                \
       t@@ -70,7 +77,7 @@ ltk_array_pop_##name(ltk_array_##name *ar) {                                                                \
        }                                                                                                        \
                                                                                                                \
        /* FIXME: having this function in the public interface is ugly */                                        \
       -storage void                                                                                                \
       +LTK_UNUSED_FUNC storage void                                                                                \
        ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len) {                                \
                if (index > ar->len)                                                                                 \
                        ltk_fatal("Array index out of bounds\n");                                                \
       t@@ -82,7 +89,7 @@ ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len) {                
                    (ar->len - len - index) * sizeof(type));                                                        \
        }                                                                                                        \
                                                                                                                \
       -storage void                                                                                                \
       +LTK_UNUSED_FUNC storage void                                                                                \
        ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len) {                        \
                ltk_array_prepare_gap_##name(ar, index, len);                                                        \
                for (size_t i = 0; i < len; i++) {                                                                \
       t@@ -90,20 +97,20 @@ ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t l
                }                                                                                                \
        }                                                                                                        \
                                                                                                                \
       -storage void                                                                                                \
       +LTK_UNUSED_FUNC storage void                                                                                \
        ltk_array_append_##name(ltk_array_##name *ar, type elem) {                                                \
                if (ar->len == ar->buf_size)                                                                        \
                        ltk_array_resize_##name(ar, ar->len + 1);                                                \
                ar->buf[ar->len++] = elem;                                                                        \
        }                                                                                                        \
                                                                                                                \
       -storage void                                                                                                \
       +LTK_UNUSED_FUNC storage void                                                                                \
        ltk_array_clear_##name(ltk_array_##name *ar) {                                                                \
                ar->len = 0;                                                                                        \
                ltk_array_resize_##name(ar, 1);                                                                        \
        }                                                                                                        \
                                                                                                                \
       -storage void                                                                                                \
       +LTK_UNUSED_FUNC storage void                                                                                \
        ltk_array_resize_##name(ltk_array_##name *ar, size_t len) {                                                \
                size_t new_size = ideal_array_size(ar->buf_size, len);                                                \
                if (new_size != ar->buf_size) {                                                                        \
       t@@ -113,7 +120,7 @@ ltk_array_resize_##name(ltk_array_##name *ar, size_t len) {                                                \
                }                                                                                               \
        }                                                                                                        \
                                                                                                                \
       -storage void                                                                                                \
       +LTK_UNUSED_FUNC storage void                                                                                \
        ltk_array_destroy_##name(ltk_array_##name *ar) {                                                        \
                if (!ar)                                                                                        \
                        return;                                                                                        \
       t@@ -121,7 +128,7 @@ ltk_array_destroy_##name(ltk_array_##name *ar) {                                                        \
                ltk_free(ar);                                                                                        \
        }                                                                                                        \
                                                                                                                \
       -storage void                                                                                                \
       +LTK_UNUSED_FUNC storage void                                                                                \
        ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)) {                        \
                if (!ar)                                                                                        \
                        return;                                                                                        \
       t@@ -131,14 +138,14 @@ ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)) 
                ltk_array_destroy_##name(ar);                                                                        \
        }                                                                                                        \
                                                                                                                \
       -storage type                                                                                                \
       +LTK_UNUSED_FUNC storage type                                                                                \
        ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index) {                                                \
                if (index >= ar->len)                                                                                \
                        ltk_fatal("Index out of bounds.\n");                                                        \
                return ar->buf[index];                                                                                \
        }                                                                                                        \
                                                                                                                \
       -storage void                                                                                                \
       +LTK_UNUSED_FUNC storage void                                                                                \
        ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e) {                                        \
                if (index >= ar->len)                                                                                \
                        ltk_fatal("Index out of bounds.\n");                                                        \
   DIR diff --git a/src/box.c b/src/box.c
       t@@ -40,7 +40,7 @@ static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, uns
        static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err);
        /* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow, ltk_error *err); */
        static void ltk_box_scroll(ltk_widget *self);
       -static int ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event);
       +static int ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event);
        static ltk_widget *ltk_box_get_child_at_pos(ltk_widget *self, int x, int y);
        static void ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r);
        
       t@@ -65,7 +65,8 @@ static struct ltk_widget_vtable vtable = {
                .remove_child = &ltk_box_remove,
                .key_press = NULL,
                .key_release = NULL,
       -        .mouse_press = &ltk_box_mouse_press,
       +        .mouse_press = NULL,
       +        .mouse_scroll = &ltk_box_mouse_scroll,
                .mouse_release = NULL,
                .motion_notify = NULL,
                .get_child_at_pos = &ltk_box_get_child_at_pos,
       t@@ -471,19 +472,18 @@ ltk_box_get_child_at_pos(ltk_widget *self, int x, int y) {
        }
        
        static int
       -ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event) {
       +ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) {
                ltk_box *box = (ltk_box *)self;
       -        /* FIXME: combine multiple events into one for efficiency */
       -        if (event->button == LTK_BUTTON4 || event->button == LTK_BUTTON5) {
       +        if (event->dy) {
       +                /* FIXME: horizontal scrolling, etc. */
                        /* FIXME: configure scrollstep */
       -                int delta = event->button == LTK_BUTTON4 ? -15 : 15;
       +                int delta = event->dy * -15;
                        ltk_scrollbar_scroll((ltk_widget *)box->sc, delta, 0);
                        ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y);
                        ltk_window_fake_motion_event(self->window, glob.x, glob.y);
                        return 1;
       -        } else {
       -                return 0;
                }
       +        return 0;
        }
        
        /* box <box id> add <widget id> [sticky] */
   DIR diff --git a/src/entry.c b/src/entry.c
       t@@ -53,6 +53,8 @@ static int ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event);
        static int ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event);
        static int ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event);
        
       +/* FIXME: give entire key event, not just text */
       +/* FIXME: also allow binding key release, not just press */
        typedef void (*cb_func)(ltk_entry *, char *, size_t);
        
        /* FIXME: configure mouse actions, e.g. select-word-under-pointer, move-cursor-to-pointer */
   DIR diff --git a/src/event.h b/src/event.h
       t@@ -12,6 +12,12 @@ typedef struct {
        typedef struct {
                ltk_event_type type;
                int x, y;
       +        int dx, dy;
       +} ltk_scroll_event;
       +
       +typedef struct {
       +        ltk_event_type type;
       +        int x, y;
        } ltk_motion_event;
        
        typedef struct {
       t@@ -43,6 +49,7 @@ typedef struct {
        typedef union {
                ltk_event_type type;
                ltk_button_event button;
       +        ltk_scroll_event scroll;
                ltk_motion_event motion;
                ltk_key_event key;
                ltk_configure_event configure;
       t@@ -52,10 +59,9 @@ typedef union {
        
        #include "ltk.h"
        
       -int ltk_events_pending(ltk_renderdata *renderdata);
        void ltk_events_cleanup(void);
        /* WARNING: Text returned in key and keyboard events must be copied before calling this function again! */
       -void ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event);
       +int ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event);
        void ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event);
        
        #endif /* LTK_EVENT_H */
   DIR diff --git a/src/event_xlib.c b/src/event_xlib.c
       t@@ -13,11 +13,35 @@
        static char *text = NULL;
        static size_t text_alloc = 0;
        static char *cur_kbd = NULL;
       -
       -int
       -ltk_events_pending(ltk_renderdata *renderdata) {
       -        return XPending(renderdata->dpy);
       -}
       +/* FIXME: support more buttons?
       +   -> What even makes sense here? Mice can support a bunch
       +   of buttons, but what is sensible here? Just adding
       +   support for higher button numbers would cause problems
       +   when adding other backends (e.g. SDL) that might do
       +   things completely differently. */
       +/* FIXME: support touch events? */
       +/* times of last button press/release,
       +   used to implement double/triple-click */
       +static Time last_button_press[] = {0, 0, 0};
       +static Time last_button_release[] = {0, 0, 0};
       +/* positions of last press/release so double/triple-click is
       +   only generated when the position is near enough */
       +static struct point {
       +        int x;
       +        int y;
       +} press_pos[] = {{0, 0}, {0, 0}, {0, 0}};
       +static struct point release_pos[] = {{0, 0}, {0, 0}, {0, 0}};
       +/* stores whether the last button press already was
       +   a double-click to decide if a triple-click should
       +   be generated (same for release) */
       +static int was_2press[] = {0, 0, 0};
       +static int was_2release[] = {0, 0, 0};
       +/* Used to store special next event - currently just
       +   used to implement double/triple-click because the
       +   actual double/triple-click/release event is
       +   generated in addition to the regular press/release */
       +static int next_event_valid = 0;
       +static ltk_button_event next_event;
        
        void
        ltk_events_cleanup(void) {
       t@@ -34,10 +58,6 @@ get_button(unsigned int button) {
                case Button1: return LTK_BUTTONL;
                case Button2: return LTK_BUTTONM;
                case Button3: return LTK_BUTTONR;
       -        case 4: return LTK_BUTTON4;
       -        case 5: return LTK_BUTTON5;
       -        case 6: return LTK_BUTTON6;
       -        case 7: return LTK_BUTTON7;
                default: return LTK_BUTTONL; /* FIXME: what to do here? */
                }
        }
       t@@ -154,35 +174,138 @@ ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event) {
                XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
        }
        
       -void
       -ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) {
       +#define DISTSQ(x0, y0, x1, y1) (((x1) - (x0)) * ((x1) - (x0)) + ((y1) - (y0)) * ((y1) - (y0)))
       +/* return value 0 means valid event returned,
       +   1 means no events pending,
       +   2 means event discarded (need to call again) */
       +static int
       +next_event_base(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) {
       +        if (next_event_valid) {
       +                next_event_valid = 0;
       +                *event = (ltk_event){.button = next_event};
       +                return 0;
       +        }
                XEvent xevent;
       +        if (!XPending(renderdata->dpy))
       +                return 1;
                XNextEvent(renderdata->dpy, &xevent);
                if (renderdata->xkb_supported && xevent.type == renderdata->xkb_event_type) {
                        ltk_generate_keyboard_event(renderdata, event);
       -                return;
       +                return 0;
                }
                *event = (ltk_event){.type = LTK_UNKNOWN_EVENT};
                if (XFilterEvent(&xevent, None))
       -                return;
       +                return 2;
       +        int button = 0;
                switch (xevent.type) {
                case ButtonPress:
       -                *event = (ltk_event){.button = {
       -                        .type = LTK_BUTTONPRESS_EVENT,
       -                        .button = get_button(xevent.xbutton.button),
       -                        .x = xevent.xbutton.x,
       -                        .y = xevent.xbutton.y
       -                }};
       +                button = xevent.xbutton.button;
       +                /* FIXME: are the buttons really always defined as exactly these values? */
       +                if (button >= 1 && button <= 3) {
       +                        if (xevent.xbutton.time - last_button_press[button] <= DOUBLECLICK_TIME &&
       +                            DISTSQ(press_pos[button].x, press_pos[button].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) {
       +                                if (was_2press[button]) {
       +                                        /* reset so normal press is sent again next time */
       +                                        was_2press[button] = 0;
       +                                        last_button_press[button] = 0;
       +                                        next_event = (ltk_button_event){
       +                                                .type = LTK_3BUTTONPRESS_EVENT,
       +                                                .button = get_button(button),
       +                                                .x = xevent.xbutton.x,
       +                                                .y = xevent.xbutton.y
       +                                        };
       +                                } else {
       +                                        was_2press[button] = 1;
       +                                        last_button_press[button] = xevent.xbutton.time;
       +                                        next_event = (ltk_button_event){
       +                                                .type = LTK_2BUTTONPRESS_EVENT,
       +                                                .button = get_button(button),
       +                                                .x = xevent.xbutton.x,
       +                                                .y = xevent.xbutton.y
       +                                        };
       +                                }
       +                                next_event_valid = 1;
       +                        } else {
       +                                last_button_press[button] = xevent.xbutton.time;
       +                        }
       +                        *event = (ltk_event){.button = {
       +                                .type = LTK_BUTTONPRESS_EVENT,
       +                                .button = get_button(button),
       +                                .x = xevent.xbutton.x,
       +                                .y = xevent.xbutton.y
       +                        }};
       +                        press_pos[button].x = xevent.xbutton.x;
       +                        press_pos[button].y = xevent.xbutton.y;
       +                } else if (button >= 4 && button <= 7) {
       +                        /* FIXME: compress multiple scroll events into one */
       +                        *event = (ltk_event){.scroll = {
       +                                .type = LTK_SCROLL_EVENT,
       +                                .x = xevent.xbutton.x,
       +                                .y = xevent.xbutton.y,
       +                                .dx = 0,
       +                                .dy = 0
       +                        }};
       +                        switch (button) {
       +                        case 4:
       +                                event->scroll.dy = 1;
       +                                break;
       +                        case 5:
       +                                event->scroll.dy = -1;
       +                                break;
       +                        case 6:
       +                                event->scroll.dx = -1;
       +                                break;
       +                        case 7:
       +                                event->scroll.dx = 1;
       +                                break;
       +                        }
       +                } else {
       +                        return 2;
       +                }
                        break;
                case ButtonRelease:
       -                *event = (ltk_event){.button = {
       -                        .type = LTK_BUTTONRELEASE_EVENT,
       -                        .button = get_button(xevent.xbutton.button),
       -                        .x = xevent.xbutton.x,
       -                        .y = xevent.xbutton.y
       -                }};
       +                button = xevent.xbutton.button;
       +                if (button >= 1 && button <= 3) {
       +                        if (xevent.xbutton.time - last_button_release[button] <= DOUBLECLICK_TIME &&
       +                            DISTSQ(release_pos[button].x, release_pos[button].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) {
       +                                if (was_2release[button]) {
       +                                        /* reset so normal release is sent again next time */
       +                                        was_2release[button] = 0;
       +                                        last_button_release[button] = 0;
       +                                        next_event = (ltk_button_event){
       +                                                .type = LTK_3BUTTONRELEASE_EVENT,
       +                                                .button = get_button(button),
       +                                                .x = xevent.xbutton.x,
       +                                                .y = xevent.xbutton.y
       +                                        };
       +                                } else {
       +                                        was_2release[button] = 1;
       +                                        last_button_release[button] = xevent.xbutton.time;
       +                                        next_event = (ltk_button_event){
       +                                                .type = LTK_2BUTTONRELEASE_EVENT,
       +                                                .button = get_button(button),
       +                                                .x = xevent.xbutton.x,
       +                                                .y = xevent.xbutton.y
       +                                        };
       +                                }
       +                                next_event_valid = 1;
       +                        } else {
       +                                last_button_release[button] = xevent.xbutton.time;
       +                        }
       +                        *event = (ltk_event){.button = {
       +                                .type = LTK_BUTTONRELEASE_EVENT,
       +                                .button = get_button(button),
       +                                .x = xevent.xbutton.x,
       +                                .y = xevent.xbutton.y
       +                        }};
       +                        release_pos[button].x = xevent.xbutton.x;
       +                        release_pos[button].y = xevent.xbutton.y;
       +                } else {
       +                        return 2;
       +                }
                        break;
                case MotionNotify:
       +                /* FIXME: compress motion events */
                        *event = (ltk_event){.motion = {
                                .type = LTK_MOTION_EVENT,
                                .x = xevent.xmotion.x,
       t@@ -216,13 +339,27 @@ ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) 
                                        .w = xevent.xexpose.width,
                                        .h = xevent.xexpose.height
                                }};
       +                } else {
       +                        return 2;
                        }
                        break;
                case ClientMessage:
                        if ((Atom)xevent.xclient.data.l[0] == renderdata->wm_delete_msg)
                                *event = (ltk_event){.type = LTK_WINDOWCLOSE_EVENT};
       +                else
       +                        return 2;
                        break;
                default:
                        break;
                }
       +        return 0;
       +}
       +
       +int
       +ltk_next_event(ltk_renderdata *renderdata, size_t lang_index, ltk_event *event) {
       +        int ret = 0;
       +        while ((ret = next_event_base(renderdata, lang_index, event)) == 2) {
       +                /* NOP */
       +        }
       +        return ret;
        }
   DIR diff --git a/src/eventdefs.h b/src/eventdefs.h
       t@@ -1,10 +1,23 @@
        #ifndef LTK_EVENTDEFS_H
        #define LTK_EVENTDEFS_H
        
       +/* FIXME: add to config */
       +#define DOUBLECLICK_TIME 250
       +/* square of distance to make calculation simpler */
       +#define DOUBLECLICK_DISTSQ 25
       +/* FIXME: reduce amount of scroll/motion events */
       +
        typedef enum {
                LTK_UNKNOWN_EVENT, /* FIXME: a bit weird */
                LTK_BUTTONPRESS_EVENT,
                LTK_BUTTONRELEASE_EVENT,
       +        /* double-click/release */
       +        LTK_2BUTTONPRESS_EVENT,
       +        LTK_2BUTTONRELEASE_EVENT,
       +        /* triple-click/release */
       +        LTK_3BUTTONPRESS_EVENT,
       +        LTK_3BUTTONRELEASE_EVENT,
       +        LTK_SCROLL_EVENT,
                LTK_MOTION_EVENT,
                LTK_KEYPRESS_EVENT,
                LTK_KEYRELEASE_EVENT,
       t@@ -20,11 +33,6 @@ typedef enum {
                LTK_BUTTONL,
                LTK_BUTTONM,
                LTK_BUTTONR,
       -        /* FIXME: dedicated scroll event */
       -        LTK_BUTTON4,
       -        LTK_BUTTON5,
       -        LTK_BUTTON6,
       -        LTK_BUTTON7
        } ltk_button_type;
        
        /* FIXME: just steal the definitions from X when using Xlib so no conversion is necessary? */
   DIR diff --git a/src/grid.c b/src/grid.c
       t@@ -72,6 +72,7 @@ static struct ltk_widget_vtable vtable = {
                .child_size_change = &ltk_grid_child_size_change,
                .remove_child = &ltk_grid_ungrid,
                .mouse_press = NULL,
       +        .mouse_scroll = NULL,
                .mouse_release = NULL,
                .motion_notify = NULL,
                .get_child_at_pos = &ltk_grid_get_child_at_pos,
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -450,10 +450,8 @@ ltk_mainloop(ltk_window *window) {
                        /* value of tv doesn't really matter anymore here because the
                           necessary framerate-limiting delay is already done */
                        wretval = select(sock_state.maxfd + 1, NULL, &wfds, NULL, &tv);
       -                while (ltk_events_pending(window->renderdata)) {
       -                        ltk_next_event(window->renderdata, window->cur_kbd, &event);
       +                while (!ltk_next_event(window->renderdata, window->cur_kbd, &event))
                                ltk_handle_event(window, &event);
       -                }
        
                        if (rretval > 0 || (sock_write_available && wretval > 0)) {
                                if (FD_ISSET(sock_state.listenfd, &rfds)) {
       t@@ -1222,9 +1220,16 @@ ltk_handle_event(ltk_window *window, ltk_event *event) {
                        ltk_window_key_release_event(window, &event->key);
                        break;
                case LTK_BUTTONPRESS_EVENT:
       +        case LTK_2BUTTONPRESS_EVENT:
       +        case LTK_3BUTTONPRESS_EVENT:
                        ltk_window_mouse_press_event(window, &event->button);
                        break;
       +        case LTK_SCROLL_EVENT:
       +                ltk_window_mouse_scroll_event(window, &event->scroll);
       +                break;
                case LTK_BUTTONRELEASE_EVENT:
       +        case LTK_2BUTTONRELEASE_EVENT:
       +        case LTK_3BUTTONRELEASE_EVENT:
                        ltk_window_mouse_release_event(window, &event->button);
                        break;
                case LTK_MOTION_EVENT:
   DIR diff --git a/src/menu.c b/src/menu.c
       t@@ -16,6 +16,10 @@
        
        /* NOTE: The implementation of menus and menu entries is a collection of ugly hacks. */
        
       +/* FIXME: parent is pressed when scroll arrows pressed */
       +/* -> this is because the pressed handling checks if the widget is activatable, then goes to the parent,
       +   but the child isn't geometrically in the parent here, so that's weird */
       +
        #include <stdio.h>
        #include <stdlib.h>
        #include <stdint.h>
       t@@ -94,7 +98,7 @@ static void ltk_menu_scroll_callback(void *data);
        static void stop_scrolling(ltk_menu *menu);
        static ltk_widget *ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y);
        static int set_scroll_timer(ltk_menu *menu, int x, int y);
       -static int ltk_menu_mouse_press(ltk_widget *self, ltk_button_event *event);
       +static int ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event);
        static void ltk_menu_hide(ltk_widget *self);
        static void popup_active_menu(ltk_menuentry *e);
        static void unpopup_active_entry(ltk_menuentry *e);
       t@@ -136,7 +140,8 @@ static ltk_widget *ltk_menuentry_get_child(ltk_widget *self);
        static struct ltk_widget_vtable vtable = {
                .key_press = NULL,
                .key_release = NULL,
       -        .mouse_press = &ltk_menu_mouse_press,
       +        .mouse_press = NULL,
       +        .mouse_scroll = &ltk_menu_mouse_scroll,
                .motion_notify = &ltk_menu_motion_notify,
                .mouse_release = NULL,
                .mouse_enter = &ltk_menu_mouse_enter,
       t@@ -646,7 +651,8 @@ ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) {
        /* FIXME: make sure timers are always destroyed when widget is destroyed */
        static int
        set_scroll_timer(ltk_menu *menu, int x, int y) {
       -        if (!ltk_collide_rect(menu->widget.lrect, x, y))
       +        /* this check probably isn't necessary, but whatever */
       +        if (x < 0 || y < 0 || x >= menu->widget.lrect.w || y >= menu->widget.lrect.h)
                        return 0;
                int t = 0, b = 0, l = 0,r = 0;
                struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme;
       t@@ -694,30 +700,20 @@ ltk_menuentry_release(ltk_widget *self) {
        }
        
        static int
       -ltk_menu_mouse_press(ltk_widget *self, ltk_button_event *event) {
       +ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) {
                ltk_menu *menu = (ltk_menu *)self;
       -        /* FIXME: configure scroll step */
                ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y);
       -        switch (event->button) {
       -        case LTK_BUTTON4:
       -                ltk_menu_scroll(menu, 1, 0, 0, 0, 10);
       -                ltk_window_fake_motion_event(self->window, glob.x, glob.y);
       -                break;
       -        case LTK_BUTTON5:
       -                ltk_menu_scroll(menu, 0, 1, 0, 0, 10);
       -                ltk_window_fake_motion_event(self->window, glob.x, glob.y);
       -                break;
       -        case LTK_BUTTON6:
       -                ltk_menu_scroll(menu, 0, 0, 1, 0, 10);
       -                ltk_window_fake_motion_event(self->window, glob.x, glob.y);
       -                break;
       -        case LTK_BUTTON7:
       -                ltk_menu_scroll(menu, 0, 0, 0, 1, 10);
       -                ltk_window_fake_motion_event(self->window, glob.x, glob.y);
       -                break;
       -        default:
       -                return 0;
       -        }
       +        /* FIXME: configure scroll step */
       +        /* FIXME: fix the interface for ltk_menu_scroll */
       +        if (event->dx > 0)
       +                ltk_menu_scroll(menu, 0, 0, 0, 1, event->dx * 10);
       +        else if (event->dx < 0)
       +                ltk_menu_scroll(menu, 0, 0, 1, 0, -event->dx * 10);
       +        if (event->dy > 0)
       +                ltk_menu_scroll(menu, 1, 0, 0, 0, event->dy * 10);
       +        else if (event->dy < 0)
       +                ltk_menu_scroll(menu, 0, 1, 0, 0, -event->dy * 10);
       +        ltk_window_fake_motion_event(self->window, glob.x, glob.y);
                return 1;
        }
        
   DIR diff --git a/src/proto_types.h b/src/proto_types.h
       t@@ -24,23 +24,34 @@
        
        /* P == protocol; W == widget */
        
       -#define LTK_PEVENT_MOUSEPRESS    0
       -#define LTK_PEVENT_MOUSERELEASE  1
       -#define LTK_PEVENT_MOUSEMOTION   2
       -#define LTK_PEVENT_KEYPRESS      3
       -#define LTK_PEVENT_KEYRELEASE    4
       -#define LTK_PEVENT_CONFIGURE     5
       -#define LTK_PEVENT_STATECHANGE   6
       -
       -#define LTK_PEVENTMASK_NONE          (UINT32_C(0))
       -#define LTK_PEVENTMASK_MOUSEPRESS    (UINT32_C(1) << LTK_PEVENT_MOUSEPRESS)
       -#define LTK_PEVENTMASK_MOUSERELEASE  (UINT32_C(1) << LTK_PEVENT_MOUSERELEASE)
       -#define LTK_PEVENTMASK_MOUSEMOTION   (UINT32_C(1) << LTK_PEVENT_MOUSEMOTION)
       -#define LTK_PEVENTMASK_KEYPRESS      (UINT32_C(1) << LTK_PEVENT_KEYPRESS)
       -#define LTK_PEVENTMASK_KEYRELEASE    (UINT32_C(1) << LTK_PEVENT_KEYRELEASE)
       -#define LTK_PEVENTMASK_CONFIGURE     (UINT32_C(1) << LTK_PEVENT_CONFIGURE)
       -#define LTK_PEVENTMASK_EXPOSE        (UINT32_C(1) << LTK_PEVENT_EXPOSE)
       -#define LTK_PEVENTMASK_STATECHANGE   (UINT32_C(1) << LTK_PEVENT_STATECHANGE)
       +#define LTK_PEVENT_MOUSEPRESS     0
       +#define LTK_PEVENT_2MOUSEPRESS    1
       +#define LTK_PEVENT_3MOUSEPRESS    2
       +#define LTK_PEVENT_MOUSERELEASE   3
       +#define LTK_PEVENT_2MOUSERELEASE  4
       +#define LTK_PEVENT_3MOUSERELEASE  5
       +#define LTK_PEVENT_MOUSEMOTION    6
       +#define LTK_PEVENT_MOUSESCROLL    7
       +#define LTK_PEVENT_KEYPRESS       8
       +#define LTK_PEVENT_KEYRELEASE     9
       +#define LTK_PEVENT_CONFIGURE      10
       +#define LTK_PEVENT_STATECHANGE    11
       +
       +/* FIXME: standardize names - internally, buttonpress is used, here it's mousepress... */
       +#define LTK_PEVENTMASK_NONE           (UINT32_C(0))
       +#define LTK_PEVENTMASK_MOUSEPRESS     (UINT32_C(1) << LTK_PEVENT_MOUSEPRESS)
       +#define LTK_PEVENTMASK_2MOUSEPRESS    (UINT32_C(1) << LTK_PEVENT_2MOUSEPRESS)
       +#define LTK_PEVENTMASK_3MOUSEPRESS    (UINT32_C(1) << LTK_PEVENT_3MOUSEPRESS)
       +#define LTK_PEVENTMASK_MOUSERELEASE   (UINT32_C(1) << LTK_PEVENT_MOUSERELEASE)
       +#define LTK_PEVENTMASK_2MOUSERELEASE  (UINT32_C(1) << LTK_PEVENT_2MOUSERELEASE)
       +#define LTK_PEVENTMASK_3MOUSERELEASE  (UINT32_C(1) << LTK_PEVENT_3MOUSERELEASE)
       +#define LTK_PEVENTMASK_MOUSEMOTION    (UINT32_C(1) << LTK_PEVENT_MOUSEMOTION)
       +#define LTK_PEVENTMASK_KEYPRESS       (UINT32_C(1) << LTK_PEVENT_KEYPRESS)
       +#define LTK_PEVENTMASK_KEYRELEASE     (UINT32_C(1) << LTK_PEVENT_KEYRELEASE)
       +#define LTK_PEVENTMASK_CONFIGURE      (UINT32_C(1) << LTK_PEVENT_CONFIGURE)
       +#define LTK_PEVENTMASK_EXPOSE         (UINT32_C(1) << LTK_PEVENT_EXPOSE)
       +#define LTK_PEVENTMASK_STATECHANGE    (UINT32_C(1) << LTK_PEVENT_STATECHANGE)
       +#define LTK_PEVENTMASK_MOUSESCROLL    (UINT32_C(1) << LTK_PEVENT_MOUSESCROLL)
        
        #define LTK_PWEVENT_MENUENTRY_PRESS     0
        #define LTK_PWEVENTMASK_MENUENTRY_NONE  (UINT32_C(0))
   DIR diff --git a/src/scrollbar.c b/src/scrollbar.c
       t@@ -165,7 +165,7 @@ static int
        ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) {
                ltk_scrollbar *sc = (ltk_scrollbar *)self;
                int max_pos;
       -        if (event->button != LTK_BUTTONL)
       +        if (event->button != LTK_BUTTONL || event->type != LTK_BUTTONPRESS_EVENT)
                        return 0;
                int ex = event->x, ey = event->y;
                ltk_rect handle_rect = handle_get_rect(sc);
   DIR diff --git a/src/widget.c b/src/widget.c
       t@@ -469,14 +469,47 @@ is_parent(ltk_widget *parent, ltk_widget *child) {
        
        /* FIXME: fix global and local coordinates! */
        static int
       -queue_mouse_event(ltk_widget *widget, char *type, uint32_t mask, int x, int y) {
       +queue_mouse_event(ltk_widget *widget, ltk_event_type type, int x, int y) {
       +        uint32_t mask;
       +        char *typename;
       +        switch (type) {
       +        case LTK_MOTION_EVENT:
       +                mask = LTK_PEVENTMASK_MOUSEMOTION;
       +                typename = "mousemotion";
       +                break;
       +        case LTK_2BUTTONPRESS_EVENT:
       +                mask = LTK_PEVENTMASK_2MOUSEPRESS;
       +                typename = "2mousepress";
       +                break;
       +        case LTK_3BUTTONPRESS_EVENT:
       +                mask = LTK_PEVENTMASK_3MOUSEPRESS;
       +                typename = "3mousepress";
       +                break;
       +        case LTK_BUTTONRELEASE_EVENT:
       +                mask = LTK_PEVENTMASK_MOUSERELEASE;
       +                typename = "mouserelease";
       +                break;
       +        case LTK_2BUTTONRELEASE_EVENT:
       +                mask = LTK_PEVENTMASK_2MOUSERELEASE;
       +                typename = "2mouserelease";
       +                break;
       +        case LTK_3BUTTONRELEASE_EVENT:
       +                mask = LTK_PEVENTMASK_3MOUSERELEASE;
       +                typename = "3mouserelease";
       +                break;
       +        case LTK_BUTTONPRESS_EVENT:
       +        default:
       +                mask = LTK_PEVENTMASK_MOUSEPRESS;
       +                typename = "mousepress";
       +                break;
       +        }
                int lock_client = -1;
                for (size_t i = 0; i < widget->masks_num; i++) {
                        if (widget->event_masks[i].lmask & mask) {
                                ltk_queue_sock_write_fmt(
                                    widget->event_masks[i].client,
                                    "eventl %s widget %s %d %d %d %d\n",
       -                            widget->id, type, x, y, x, y
       +                            widget->id, typename, x, y, x, y
                                    /* x - widget->rect.x, y - widget->rect.y */
                                );
                                lock_client = widget->event_masks[i].client;
       t@@ -484,7 +517,37 @@ queue_mouse_event(ltk_widget *widget, char *type, uint32_t mask, int x, int y) {
                                ltk_queue_sock_write_fmt(
                                    widget->event_masks[i].client,
                                    "event %s widget %s %d %d %d %d\n",
       -                            widget->id, type, x, y, x, y
       +                            widget->id, typename, x, y, x, y
       +                            /* x - widget->rect.x, y - widget->rect.y */
       +                        );
       +                }
       +        }
       +        if (lock_client >= 0) {
       +                if (ltk_handle_lock_client(widget->window, lock_client))
       +                        return 1;
       +        }
       +        return 0;
       +}
       +
       +/* FIXME: global/local coords (like above) */
       +static int
       +queue_scroll_event(ltk_widget *widget, int x, int y, int dx, int dy) {
       +        uint32_t mask = LTK_PEVENTMASK_MOUSESCROLL;
       +        int lock_client = -1;
       +        for (size_t i = 0; i < widget->masks_num; i++) {
       +                if (widget->event_masks[i].lmask & mask) {
       +                        ltk_queue_sock_write_fmt(
       +                            widget->event_masks[i].client,
       +                            "eventl %s widget %s %d %d %d %d %d %d\n",
       +                            widget->id, "mousescroll", x, y, x, y, dx, dy
       +                            /* x - widget->rect.x, y - widget->rect.y */
       +                        );
       +                        lock_client = widget->event_masks[i].client;
       +                } else if (widget->event_masks[i].mask & mask) {
       +                        ltk_queue_sock_write_fmt(
       +                            widget->event_masks[i].client,
       +                            "event %s widget %s %d %d %d %d %d %d\n",
       +                            widget->id, "mousescroll", x, y, x, y, dx, dy
                                    /* x - widget->rect.x, y - widget->rect.y */
                                );
                        }
       t@@ -1011,6 +1074,8 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) {
                if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0])))
                        ltk_window_unregister_all_popups(window);
        
       +        /* FIXME: popups don't always have their children geometrically contained within parents,
       +           so this won't work properly in all cases */
                int first = 1;
                while (cur_widget) {
                        int handled = 0;
       t@@ -1020,13 +1085,13 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) {
                        if (cur_widget->state != LTK_DISABLED) {
                                /* FIXME: figure out whether this makes sense - currently, all widgets (unless disabled)
                                   get mouse press, but they are only set to pressed if they are activatable */
       -                        if (queue_mouse_event(cur_widget, "mousepress", LTK_PEVENTMASK_MOUSEPRESS, event->x, event->y))
       +                        if (queue_mouse_event(cur_widget, event->type, event->x, event->y))
                                        handled = 1;
                                else if (cur_widget->vtable->mouse_press)
                                        handled = cur_widget->vtable->mouse_press(cur_widget, event);
                                /* set first non-disabled widget to pressed widget */
                                /* FIXME: use config values for all_activatable */
       -                        if (first && event->button == LTK_BUTTONL && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
       +                        if (first && event->button == LTK_BUTTONL && event->type == LTK_BUTTONPRESS_EVENT && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
                                        ltk_window_set_pressed_widget(window, cur_widget, 0);
                                        first = 0;
                                }
       t@@ -1039,6 +1104,35 @@ ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) {
        }
        
        void
       +ltk_window_mouse_scroll_event(ltk_window *window, ltk_scroll_event *event) {
       +        /* FIXME: should it first be sent to pressed widget? */
       +        ltk_widget *widget = get_hover_popup(window, event->x, event->y);
       +        if (!widget)
       +                widget = window->root_widget;
       +        if (!widget)
       +                return;
       +        int orig_x = event->x, orig_y = event->y;
       +        ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
       +        /* FIXME: same issue with popups like in mouse_press above */
       +        while (cur_widget) {
       +                int handled = 0;
       +                ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
       +                event->x = local.x;
       +                event->y = local.y;
       +                if (cur_widget->state != LTK_DISABLED) {
       +                        if (queue_scroll_event(cur_widget, event->x, event->y, event->dx, event->dy))
       +                                handled = 1;
       +                        else if (cur_widget->vtable->mouse_scroll)
       +                                handled = cur_widget->vtable->mouse_scroll(cur_widget, event);
       +                }
       +                if (!handled)
       +                        cur_widget = cur_widget->parent;
       +                else
       +                        break;
       +        }
       +}
       +
       +void
        ltk_window_fake_motion_event(ltk_window *window, int x, int y) {
                ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = x, .y = y};
                ltk_window_motion_notify_event(window, &e);
       t@@ -1048,17 +1142,18 @@ void
        ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event) {
                ltk_widget *widget = window->pressed_widget;
                int orig_x = event->x, orig_y = event->y;
       +        /* FIXME: why does this only take pressed widget and popups into account? */
                if (!widget) {
                        widget = get_hover_popup(window, event->x, event->y);
                        widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
                }
                /* FIXME: loop up to top of hierarchy if not handled */
       -        if (widget && queue_mouse_event(widget, "mouserelease", LTK_PEVENTMASK_MOUSERELEASE, event->x, event->y)) {
       +        if (widget && queue_mouse_event(widget, event->type, event->x, event->y)) {
                        /* NOP */
                } else if (widget && widget->vtable->mouse_release) {
                        widget->vtable->mouse_release(widget, event);
                }
       -        if (event->button == LTK_BUTTONL) {
       +        if (event->button == LTK_BUTTONL && event->type == LTK_BUTTONRELEASE_EVENT) {
                        int release = 0;
                        if (window->pressed_widget) {
                                ltk_rect prect = window->pressed_widget->lrect;
       t@@ -1104,7 +1199,7 @@ ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) {
                        event->x = local.x;
                        event->y = local.y;
                        if (cur_widget->state != LTK_DISABLED) {
       -                        if (queue_mouse_event(cur_widget, "mousemotion", LTK_PEVENTMASK_MOUSEMOTION, event->x, event->y))
       +                        if (queue_mouse_event(cur_widget, LTK_MOTION_EVENT, event->x, event->y))
                                        handled = 1;
                                else if (cur_widget->vtable->motion_notify)
                                        handled = cur_widget->vtable->motion_notify(cur_widget, event);
   DIR diff --git a/src/widget.h b/src/widget.h
       t@@ -119,8 +119,10 @@ struct ltk_widget {
        struct ltk_widget_vtable {
                int (*key_press)(struct ltk_widget *, ltk_key_event *);
                int (*key_release)(struct ltk_widget *, ltk_key_event *);
       +        /* press/release also receive double/triple-click/release */
                int (*mouse_press)(struct ltk_widget *, ltk_button_event *);
                int (*mouse_release)(struct ltk_widget *, ltk_button_event *);
       +        int (*mouse_scroll)(struct ltk_widget *, ltk_scroll_event *);
                int (*motion_notify)(struct ltk_widget *, ltk_motion_event *);
                int (*mouse_leave)(struct ltk_widget *, ltk_motion_event *);
                int (*mouse_enter)(struct ltk_widget *, ltk_motion_event *);
       t@@ -168,6 +170,7 @@ void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state);
        void ltk_window_key_press_event(ltk_window *window, ltk_key_event *event);
        void ltk_window_key_release_event(ltk_window *window, ltk_key_event *event);
        void ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event);
       +void ltk_window_mouse_scroll_event(ltk_window *window, ltk_scroll_event *event);
        void ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event);
        void ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event);
        void ltk_window_fake_motion_event(ltk_window *window, int x, int y);