URI: 
       tAdd event masks - 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 99531db6c1821736ff1a975ec9872fc033d94df7
   DIR parent 488ed473efaa6153752374f9c27f67fe45dc4823
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Fri, 24 Jun 2022 14:31:59 +0200
       
       Add event masks
       
       This is very weird and buggy right now.
       See socket_format.txt for some open problems.
       
       Diffstat:
         M Makefile                            |       7 ++++---
         M README.md                           |       2 ++
         M socket_format.txt                   |      76 +++++++++++++++++++++++++++----
         M src/box.c                           |      11 +++++------
         M src/button.c                        |      16 +++++++++-------
         M src/button.h                        |       7 ++++---
         M src/grid.c                          |      14 +++++++-------
         M src/label.c                         |      10 ++++++----
         M src/label.h                         |       1 +
         M src/ltk.h                           |       5 +++++
         M src/ltkd.c                          |     324 ++++++++++++++++++++++++++------
         M src/menu.c                          |      46 ++++++++++++++++---------------
         A src/proto_types.h                   |      51 +++++++++++++++++++++++++++++++
         M src/scrollbar.c                     |      11 +++++++----
         M src/scrollbar.h                     |       1 +
         M src/surface_cache.c                 |       2 +-
         M src/widget.c                        |     211 +++++++++++++++++++++++++++++--
         M src/widget.h                        |      58 ++++++++++++++++++-------------
         M test.gui                            |       1 +
         M test.sh                             |       2 +-
         M testbox.sh                          |       9 +++++----
       
       21 files changed, 703 insertions(+), 162 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       t@@ -55,8 +55,8 @@ OBJ = \
                src/graphics_xlib.o \
                src/surface_cache.o \
                src/event_xlib.o \
       +        src/err.c \
                $(EXTRA_OBJ)
       -#        src/draw.o \
        
        # Note: This could be improved so a change in a header only causes the .c files
        # which include that header to be recompiled, but the compile times are
       t@@ -83,8 +83,9 @@ HDR = \
                src/surface_cache.h \
                src/macros.h \
                src/event.h \
       -        src/xlib_shared.h
       -#        src/draw.h \
       +        src/xlib_shared.h \
       +        src/err.h \
       +        src/proto_types.h
        
        all: src/ltkd src/ltkc
        
   DIR diff --git a/README.md b/README.md
       t@@ -20,3 +20,5 @@ Also read the comment in './test.sh'.
        
        Note: I know the default theme is butt-ugly at the moment. It is mainly
        to test things, not to look pretty.
       +
       +Note: Read 'socket_format.txt' for some documentation and open problems.
   DIR diff --git a/socket_format.txt b/socket_format.txt
       t@@ -1,5 +1,4 @@
       -Note: This is not fully implemented yet; it is just here to
       -collect my thoughts while I keep working.
       +Requests:
        
        <widget type> <widget id> <command> <args>
        > grid grd1 create 2 2
       t@@ -14,12 +13,73 @@ within a string.
        Double quotes must be escaped in strings, like so:
        > button btn1 create "Bla\"bla"
        
       -When the server sends a reply, the format is the same, but
       -there are some special cases, such as "get-text". When the
       -client asks to get the text for a widget, only the text is
       -sent back, but still inside double quotes, with double quotes
       -belonging to the text escaped.
       -
        Essentially, the individual messages are separated by line
        breaks (\n), but line breaks within strings don't break the
        message.
       +
       +Replies:
       +
       +Not properly implemented yet.
       +The requests will probably have to include a sequence number eventually, which
       +the replies will also give back so it's possible to know when the appropriate
       +reply has been received.
       +
       +Errors:
       +
       +err <error number> <number of bad argument or -1> <string description of error>
       +
       +Events:
       +
       +event[l] <widget id> <widget type or "widget" for generic events> <event name> [further data]
       +
       +By default, no events are reported. An event mask has to be set first:
       +
       +mask-add <widget id> <type> <event name> [lock]
       +mask-remove <widget id> <type> <event name> [lock]
       +mask-set <widget id> <type> <event name> [lock]
       +
       +<type> is either "widget" for generic events (mousepress, mouserelease,
       +mousemotion, configure, statechange) or the widget type for specific events.
       +The only specific event currently supported is "press" for both "button"
       +and "menuentry". Note that currently, only a single mask type can be
       +added/removed at once instead of combining multiple in one request
       +(i.e. it isn't possible to do something like "mousepress|mouserelease" yet).
       +
       +If "lock" is set, the "lock mask" is manipulated instead of the normal one.
       +If an event occurs that is included in the lock mask, the event will start
       +with "eventl" instead of "event", and the ltk server blocks until it gets the
       +request "event-unlock [true/false]" from the client that received the event.
       +Note that if multiple clients have an event in their lock mask, all of them will
       +receive the event, but only one of them is chosen to listen for the event-unlock
       +(basically, this is unspecified behavior that should be avoided). If event-unlock
       +includes "true", the event is not processed further by the ltk server. If "false"
       +is given instead, the event is processed as usual by the ltk server.
       +
       +This was added to allow functionality like in regular GUI toolkits where it is
       +possible to override events completely. The problem is that it currently isn't
       +really clear where exactly the command should be emitted and whether it really
       +makes sense to block all further processing (some processing has to be done
       +even now for it to make any sense at all). That could possibly lead to very
       +weird bugs. It also currently isn't possible to do much after locking because
       +no useful low-level functions for widgets exist (yet?). All in all, I'm not
       +entirely sure how to make this work nicely so it is actually useful.
       +Since all of this is pushed over a socket and will probably be able to run
       +over a network connection eventually, it will also cause problems with latency.
       +
       +Miscellaneous:
       +
       +It probably isn't too great for security when anyone can do anything with the
       +window. Maybe it would be better to allow different clients to have different
       +permissions? For instance, maybe only the main client could change things, but
       +other clients could have readonly permissions for things like screenreaders.
       +That would probably get very over-complicated, though.
       +
       +I'm also seriously considering switching to a binary socket format. It's nice
       +to have a text format, but it's an absolute pain to process, because everything
       +has to be converted from/to text. It also isn't nearly as efficient, especially
       +if more complicated things are done, such as listening for all mousemotion events.
       +Of course, it could be made much more efficient with various lookup tables
       +(it isn't implemented very efficiently currently), but still not nearly as good
       +as a binary protocol. The idea would be to have a binary protocol, but to still
       +have something like ltkc that converts the protocol to a text format so simple
       +shell clients can still exist, but more complicated programs aren't hindered by it.
   DIR diff --git a/src/box.c b/src/box.c
       t@@ -59,7 +59,7 @@ static struct ltk_widget_vtable vtable = {
                .get_child_at_pos = &ltk_box_get_child_at_pos,
                .mouse_leave = NULL,
                .mouse_enter = NULL,
       -        .type = LTK_BOX,
       +        .type = LTK_WIDGET_BOX,
                .flags = 0,
        };
        
       t@@ -358,8 +358,8 @@ ltk_box_cmd_add(
                        err->arg = -1;
                        return 1;
                }
       -        box = (ltk_box *)ltk_get_widget(tokens[1], LTK_BOX, err);
       -        widget = ltk_get_widget(tokens[3], LTK_WIDGET, err);
       +        box = (ltk_box *)ltk_get_widget(tokens[1], LTK_WIDGET_BOX, err);
       +        widget = ltk_get_widget(tokens[3], LTK_WIDGET_ANY, err);
                if (!box) {
                        err->arg = 1;
                        return 1;
       t@@ -409,8 +409,8 @@ ltk_box_cmd_remove(
                        err->arg = -1;
                        return 1;
                }
       -        box = (ltk_box *)ltk_get_widget(tokens[1], LTK_BOX, err);
       -        widget = ltk_get_widget(tokens[3], LTK_WIDGET, err);
       +        box = (ltk_box *)ltk_get_widget(tokens[1], LTK_WIDGET_BOX, err);
       +        widget = ltk_get_widget(tokens[3], LTK_WIDGET_ANY, err);
                if (!box) {
                        err->arg = 1;
                        return 1;
       t@@ -441,7 +441,6 @@ ltk_box_cmd_create(
                        err->arg = -1;
                        return 1;
                }
       -        /* FIXME: race condition */
                if (!ltk_widget_id_free(tokens[1])) {
                        err->type = ERR_WIDGET_ID_IN_USE;
                        err->arg = 1;
   DIR diff --git a/src/button.c b/src/button.c
       t@@ -20,6 +20,7 @@
        #include <string.h>
        #include <stdarg.h>
        
       +#include "proto_types.h"
        #include "event.h"
        #include "memory.h"
        #include "color.h"
       t@@ -59,8 +60,8 @@ static struct ltk_widget_vtable vtable = {
                .destroy = &ltk_button_destroy,
                .child_size_change = NULL,
                .remove_child = NULL,
       -        .type = LTK_BUTTON,
       -        .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE | LTK_ACTIVATABLE_ALWAYS,
       +        .type = LTK_WIDGET_BUTTON,
       +        .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
        };
        
        static struct {
       t@@ -116,13 +117,15 @@ ltk_button_uninitialize_theme(ltk_window *window) {
                ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
        }
        
       +/* FIXME: only keep text in surface to avoid large surface */
        static void
        ltk_button_draw(ltk_widget *self, ltk_rect clip) {
                ltk_button *button = (ltk_button *)self;
                ltk_rect rect = button->widget.rect;
                ltk_rect clip_final = ltk_rect_intersect(clip, rect);
                ltk_surface *s;
       -        if (!ltk_surface_cache_get_surface(self->surface_key, &s) || self->dirty)
       +        ltk_surface_cache_request_surface_size(button->key, self->rect.w, self->rect.h);
       +        if (!ltk_surface_cache_get_surface(button->key, &s) || self->dirty)
                        ltk_button_redraw_surface(button, s);
                ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y);
        }
       t@@ -165,9 +168,8 @@ ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) {
        
        static int
        ltk_button_mouse_release(ltk_widget *self, ltk_button_event *event) {
       -        ltk_button *button = (ltk_button *)self;
                if ((self->state & LTK_PRESSED) && event->button == LTK_BUTTONL && ltk_collide_rect(self->rect, event->x, event->y)) {
       -                ltk_queue_event(button->widget.window, LTK_EVENT_BUTTON, button->widget.id, "button_click");
       +                ltk_queue_specific_event(self, "button", LTK_PWEVENTMASK_BUTTON_PRESS, "press");
                        return 1;
                }
                return 0;
       t@@ -184,6 +186,7 @@ ltk_button_create(ltk_window *window, const char *id, char *text) {
                button->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2;
                button->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2;
                ltk_fill_widget_defaults(&button->widget, id, window, &vtable, button->widget.ideal_w, button->widget.ideal_h);
       +        button->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, button->widget.ideal_w, button->widget.ideal_h);
                button->widget.dirty = 1;
        
                return button;
       t@@ -197,8 +200,7 @@ ltk_button_destroy(ltk_widget *self, int shallow) {
                        ltk_warn("Tried to destroy NULL button.\n");
                        return;
                }
       -        /* FIXME: this should be generic part of widget */
       -        ltk_surface_cache_release_key(self->surface_key);
       +        ltk_surface_cache_release_key(button->key);
                ltk_text_line_destroy(button->tl);
                ltk_remove_widget(self->id);
                ltk_remove_widget(button->widget.id);
   DIR diff --git a/src/button.h b/src/button.h
       t@@ -14,8 +14,8 @@
         * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
         */
        
       -#ifndef _LTK_BUTTON_H_
       -#define _LTK_BUTTON_H_
       +#ifndef LTK_BUTTON_H
       +#define LTK_BUTTON_H
        
        /* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */
        
       t@@ -24,6 +24,7 @@
        typedef struct {
                ltk_widget widget;
                ltk_text_line *tl;
       +        ltk_surface_cache_key *key;
        } ltk_button;
        
        int ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value);
       t@@ -37,4 +38,4 @@ int ltk_button_cmd(
            ltk_error *
        );
        
       -#endif /* _LTK_BUTTON_H_ */
       +#endif /* LTK_BUTTON_H */
   DIR diff --git a/src/grid.c b/src/grid.c
       t@@ -68,7 +68,7 @@ static struct ltk_widget_vtable vtable = {
                .mouse_enter = NULL,
                .key_press = NULL,
                .key_release = NULL,
       -        .type = LTK_GRID,
       +        .type = LTK_WIDGET_GRID,
                .flags = 0,
        };
        
       t@@ -414,8 +414,8 @@ ltk_grid_cmd_add(
                        err->arg = -1;
                        return 1;
                }
       -        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, err);
       -        widget = ltk_get_widget(tokens[3], LTK_WIDGET, err);
       +        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_WIDGET_GRID, err);
       +        widget = ltk_get_widget(tokens[3], LTK_WIDGET_ANY, err);
                if (!grid) {
                        err->arg = 1;
                        return 1;
       t@@ -490,8 +490,8 @@ ltk_grid_cmd_ungrid(
                        err->arg = -1;
                        return 1;
                }
       -        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, err);
       -        widget = ltk_get_widget(tokens[3], LTK_WIDGET, err);
       +        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_WIDGET_GRID, err);
       +        widget = ltk_get_widget(tokens[3], LTK_WIDGET_ANY, err);
                if (!grid) {
                        err->arg = 1;
                        return 1;
       t@@ -560,7 +560,7 @@ ltk_grid_cmd_set_row_weight(
                        err->arg = -1;
                        return 1;
                }
       -        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, err);
       +        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_WIDGET_GRID, err);
                if (!grid) {
                        err->arg = 1;
                        return 1;
       t@@ -598,7 +598,7 @@ ltk_grid_cmd_set_column_weight(
                        err->arg = -1;
                        return 1;
                }
       -        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, err);
       +        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_WIDGET_GRID, err);
                if (!grid) {
                        err->arg = 1;
                        return 1;
   DIR diff --git a/src/label.c b/src/label.c
       t@@ -57,8 +57,8 @@ static struct ltk_widget_vtable vtable = {
                .motion_notify = NULL,
                .mouse_leave = NULL,
                .mouse_enter = NULL,
       -        .type = LTK_LABEL,
       -        .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE,
       +        .type = LTK_WIDGET_LABEL,
       +        .flags = LTK_NEEDS_REDRAW,
        };
        
        static struct {
       t@@ -96,7 +96,8 @@ ltk_label_draw(ltk_widget *self, ltk_rect clip) {
                ltk_rect rect = label->widget.rect;
                ltk_rect clip_final = ltk_rect_intersect(clip, rect);
                ltk_surface *s;
       -        if (!ltk_surface_cache_get_surface(self->surface_key, &s) || self->dirty)
       +        ltk_surface_cache_request_surface_size(label->key, self->rect.w, self->rect.h);
       +        if (!ltk_surface_cache_get_surface(label->key, &s) || self->dirty)
                        ltk_label_redraw_surface(label, s);
                ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y);
        }
       t@@ -126,6 +127,7 @@ ltk_label_create(ltk_window *window, const char *id, char *text) {
                label->widget.ideal_w = text_w + theme.pad * 2;
                label->widget.ideal_h = text_h + theme.pad * 2;
                ltk_fill_widget_defaults(&label->widget, id, window, &vtable, label->widget.ideal_w, label->widget.ideal_h);
       +        label->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, label->widget.ideal_w, label->widget.ideal_h);
        
                return label;
        }
       t@@ -138,7 +140,7 @@ ltk_label_destroy(ltk_widget *self, int shallow) {
                        ltk_warn("Tried to destroy NULL label.\n");
                        return;
                }
       -        ltk_surface_cache_release_key(self->surface_key);
       +        ltk_surface_cache_release_key(label->key);
                ltk_text_line_destroy(label->tl);
                ltk_remove_widget(self->id);
                ltk_free(self->id);
   DIR diff --git a/src/label.h b/src/label.h
       t@@ -24,6 +24,7 @@
        typedef struct {
                ltk_widget widget;
                ltk_text_line *tl;
       +        ltk_surface_cache_key *key;
        } ltk_label;
        
        int ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value);
   DIR diff --git a/src/ltk.h b/src/ltk.h
       t@@ -18,6 +18,7 @@
        #define _LTK_H_
        
        #include <stdint.h>
       +#include "proto_types.h"
        
        typedef enum {
                LTK_EVENT_RESIZE = 1 << 0,
       t@@ -100,5 +101,9 @@ int ltk_register_timer(long first, long repeat, void (*callback)(void *), void *
        void ltk_window_register_popup(ltk_window *window, ltk_widget *popup);
        void ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup);
        void ltk_window_unregister_all_popups(ltk_window *window);
       +int ltk_handle_lock_client(ltk_window *window, int client);
       +int ltk_queue_specific_event(ltk_widget *widget, const char *type, uint32_t mask, const char *data);
       +int ltk_queue_sock_write(int client, const char *str, int len);
       +int ltk_queue_sock_write_fmt(int client, const char *fmt, ...);
        
        #endif
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -1,6 +1,5 @@
        /* FIXME: backslashes should be parsed properly! */
        /* FIXME: Figure out how to properly print window id */
       -/* FIXME: PROPERLY CLEANUP ALL THEMES */
        /* FIXME: error checking in tokenizer (is this necessary?) */
        /* FIXME: parsing doesn't work properly with bs? */
        /* FIXME: strip whitespace at end of lines in socket format */
       t@@ -125,11 +124,9 @@ static int read_sock(struct ltk_sock_info *sock);
        static int push_token(struct token_list *tl, char *token);
        static int read_sock(struct ltk_sock_info *sock);
        static int write_sock(struct ltk_sock_info *sock);
       -static int queue_sock_write(struct ltk_sock_info *sock, const char *str, int len);
       -static int queue_sock_write_fmt(struct ltk_sock_info *sock, const char *fmt, ...);
        static int tokenize_command(struct ltk_sock_info *sock);
        static int ltk_set_root_widget_cmd(ltk_window *window, char **tokens, int num_tokens, ltk_error *err);
       -static void process_commands(ltk_window *window, struct ltk_sock_info *sock);
       +static int process_commands(ltk_window *window, int client);
        static int add_client(int fd);
        static int listen_sock(const char *sock_path);
        static int accept_sock(int listenfd);
       t@@ -137,7 +134,6 @@ static int accept_sock(int listenfd);
        static short maxsocket = -1;
        static short running = 1;
        static short sock_write_available = 0;
       -static int listenfd = -1;
        static char *ltk_dir = NULL;
        static FILE *ltk_logfile = NULL;
        static char *sock_path = NULL;
       t@@ -187,25 +183,91 @@ int main(int argc, char *argv[]) {
                return ltk_mainloop(main_window);
        }
        
       +/* FIXME: need to recalculate maxfd when removing client */
       +static struct {
       +        fd_set rallfds, wallfds;
       +        int maxfd;
       +        int listenfd;
       +} sock_state;
       +
       +int
       +ltk_handle_lock_client(ltk_window *window, int client) {
       +        if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
       +                return 0;
       +        fd_set rfds, wfds, rallfds, wallfds;
       +        int clifd = sockets[client].fd;
       +        FD_ZERO(&rallfds);
       +        FD_ZERO(&wallfds);
       +        FD_SET(clifd, &rallfds);
       +        FD_SET(clifd, &wallfds);
       +        int rretval, wretval;
       +        struct timeval tv, tv_master;
       +        tv_master.tv_sec = 0;
       +        tv_master.tv_usec = 20000;
       +        while (1) {
       +                rfds = rallfds;
       +                wfds = wallfds;
       +                /* separate these because the writing fds are usually
       +                   always ready for writing */
       +                tv = tv_master;
       +                rretval = select(clifd + 1, &rfds, NULL, NULL, &tv);
       +                /* value of tv doesn't really matter anymore here because the
       +                   necessary framerate-limiting delay is already done */
       +                wretval = select(clifd + 1, NULL, &wfds, NULL, &tv);
       +
       +                if (rretval > 0 || ((sockets[client].write_cur != sockets[client].write_len) && wretval > 0)) {
       +                        if (FD_ISSET(clifd, &rfds)) {
       +                                if (read_sock(&sockets[client]) == 0) {
       +                                        FD_CLR(clifd, &sock_state.rallfds);
       +                                        FD_CLR(clifd, &sock_state.wallfds);
       +                                        ltk_widget_remove_client(client);
       +                                        sockets[clifd].fd = -1;
       +                                        close(clifd);
       +                                        int newmaxsocket = -1;
       +                                        for (int j = 0; j <= maxsocket; j++) {
       +                                                if (sockets[j].fd >= 0)
       +                                                        newmaxsocket = j;
       +                                        }
       +                                        maxsocket = newmaxsocket;
       +                                        if (maxsocket == -1) {
       +                                                ltk_quit(window);
       +                                                break;
       +                                        }
       +                                        return 0;
       +                                } else {
       +                                        int ret;
       +                                        if ((ret = process_commands(window, client)) == 1)
       +                                                return 1;
       +                                        else if (ret == -1)
       +                                                return 0;
       +                                }
       +                        }
       +                        if (FD_ISSET(clifd, &wfds)) {
       +                                write_sock(&sockets[client]);
       +                        }
       +                }
       +        }
       +        return 0;
       +}
       +
        static int
        ltk_mainloop(ltk_window *window) {
                ltk_event event;
       -        fd_set rfds, wfds, rallfds, wallfds;
       -        int maxfd;
       +        fd_set rfds, wfds;
                int rretval, wretval;
                int clifd;
                struct timeval tv, tv_master;
                tv_master.tv_sec = 0;
                tv_master.tv_usec = 20000;
        
       -        FD_ZERO(&rallfds);
       -        FD_ZERO(&wallfds);
       +        FD_ZERO(&sock_state.rallfds);
       +        FD_ZERO(&sock_state.wallfds);
        
       -        if ((listenfd = listen_sock(sock_path)) < 0)
       +        if ((sock_state.listenfd = listen_sock(sock_path)) < 0)
                        ltk_fatal_errno("Error listening on socket.\n");
        
       -        FD_SET(listenfd, &rallfds);
       -        maxfd = listenfd;
       +        FD_SET(sock_state.listenfd, &sock_state.rallfds);
       +        sock_state.maxfd = sock_state.listenfd;
        
                printf("%lu", renderer_get_window_id(main_window->renderdata));
                fflush(stdout);
       t@@ -221,31 +283,31 @@ ltk_mainloop(ltk_window *window) {
                /* FIXME: framerate limiting for draw */
        
                while (running) {
       -                rfds = rallfds;
       -                wfds = wallfds;
       +                rfds = sock_state.rallfds;
       +                wfds = sock_state.wallfds;
                        /* separate these because the writing fds are usually
                           always ready for writing */
                        tv = tv_master;
       -                rretval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
       +                rretval = select(sock_state.maxfd + 1, &rfds, NULL, NULL, &tv);
                        /* value of tv doesn't really matter anymore here because the
                           necessary framerate-limiting delay is already done */
       -                wretval = select(maxfd + 1, NULL, &wfds, NULL, &tv);
       +                wretval = select(sock_state.maxfd + 1, NULL, &wfds, NULL, &tv);
                        while (ltk_events_pending(window->renderdata)) {
                                ltk_next_event(window->renderdata, &event);
                                ltk_handle_event(window, &event);
                        }
        
                        if (rretval > 0 || (sock_write_available && wretval > 0)) {
       -                        if (FD_ISSET(listenfd, &rfds)) {
       -                                if ((clifd = accept_sock(listenfd)) < 0) {
       +                        if (FD_ISSET(sock_state.listenfd, &rfds)) {
       +                                if ((clifd = accept_sock(sock_state.listenfd)) < 0) {
                                                /* FIXME: Just log this! */
                                                ltk_fatal_errno("Error accepting socket connection.\n");
                                        }
                                        int i = add_client(clifd);
       -                                FD_SET(clifd, &rallfds);
       -                                FD_SET(clifd, &wallfds);
       -                                if (clifd > maxfd)
       -                                        maxfd = clifd;
       +                                FD_SET(clifd, &sock_state.rallfds);
       +                                FD_SET(clifd, &sock_state.wallfds);
       +                                if (clifd > sock_state.maxfd)
       +                                        sock_state.maxfd = clifd;
                                        if (i > maxsocket)
                                                maxsocket = i;
                                        continue;
       t@@ -255,8 +317,9 @@ ltk_mainloop(ltk_window *window) {
                                                continue;
                                        if (FD_ISSET(clifd, &rfds)) {
                                                if (read_sock(&sockets[i]) == 0) {
       -                                                FD_CLR(clifd, &rallfds);
       -                                                FD_CLR(clifd, &wallfds);
       +                                                ltk_widget_remove_client(i);
       +                                                FD_CLR(clifd, &sock_state.rallfds);
       +                                                FD_CLR(clifd, &sock_state.wallfds);
                                                        sockets[i].fd = -1;
                                                        close(clifd);
                                                        int newmaxsocket = -1;
       t@@ -270,7 +333,7 @@ ltk_mainloop(ltk_window *window) {
                                                                break;
                                                        }
                                                } else {
       -                                                process_commands(window, &sockets[i]);
       +                                                process_commands(window, i);
                                                }
                                        }
                                        if (FD_ISSET(clifd, &wfds)) {
       t@@ -315,7 +378,7 @@ ltk_mainloop(ltk_window *window) {
                                        int event_len = strlen(cur->data);
                                        for (int i = 0; i <= maxsocket; i++) {
                                                if (sockets[i].fd != -1 && sockets[i].event_mask & cur->event_type) {
       -                                                if (queue_sock_write(&sockets[i], cur->data, event_len) < 0)
       +                                                if (ltk_queue_sock_write(i, cur->data, event_len) < 0)
                                                                ltk_fatal_errno("Unable to queue event.\n");
                                                }
                                        }
       t@@ -414,8 +477,8 @@ open_log(char *dir) {
        
        void
        ltk_cleanup(void) {
       -        if (listenfd >= 0)
       -                close(listenfd);
       +        if (sock_state.listenfd >= 0)
       +                close(sock_state.listenfd);
                if (ltk_dir)
                        ltk_free(ltk_dir);
                if (ltk_logfile)
       t@@ -479,7 +542,7 @@ ltk_set_root_widget_cmd(
                        err->arg = -1;
                        return 1;
                }
       -        widget = ltk_get_widget(tokens[1], LTK_WIDGET, err);
       +        widget = ltk_get_widget(tokens[1], LTK_WIDGET_ANY, err);
                if (!widget) {
                        err->arg = 1;
                        return 1;
       t@@ -501,24 +564,29 @@ ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) {
                        window->dirty_rect = ltk_rect_union(rect, window->dirty_rect);
        }
        
       -void
       -ltk_queue_event(ltk_window *window, ltk_userevent_type type, const char *id, const char *data) {
       -        /* FIXME: make it nicer and safer */
       -        struct ltk_event_queue *new = ltk_malloc(sizeof(struct ltk_event_queue));
       -        new->event_type = type;
       -        int id_len = strlen(id);
       -        int data_len = strlen(data);
       -        new->data = ltk_malloc(id_len + data_len + 3);
       -        strcpy(new->data, id);
       -        new->data[id_len] = ' ';
       -        strcpy(new->data + id_len + 1, data);
       -        new->data[id_len + data_len + 1] = '\n';
       -        new->data[id_len + data_len + 2] = '\0';
       -        new->next = window->first_event;
       -        window->first_event = new;
       -        new->prev = NULL;
       -        if (!window->last_event)
       -                window->last_event = new;
       +/* FIXME: generic event handling functions that take the actual uint32_t event type, etc. */
       +int
       +ltk_queue_specific_event(ltk_widget *widget, const char *type, uint32_t mask, const char *data) {
       +        int lock_client = -1;
       +        for (size_t i = 0; i < widget->masks_num; i++) {
       +                if (widget->event_masks[i].lwmask & mask) {
       +                        ltk_queue_sock_write_fmt(
       +                            widget->event_masks[i].client,
       +                            "eventl %s %s %s\n", widget->id, type, data
       +                        );
       +                        lock_client = widget->event_masks[i].client;
       +                } else if (widget->event_masks[i].wmask & mask) {
       +                        ltk_queue_sock_write_fmt(
       +                            widget->event_masks[i].client,
       +                            "event %s %s %s\n", widget->id, type, data
       +                        );
       +                }
       +        }
       +        if (lock_client >= 0) {
       +                if (ltk_handle_lock_client(widget->window, lock_client))
       +                        return 1;
       +        }
       +        return 0;
        }
        
        static void
       t@@ -1115,8 +1183,11 @@ move_write_pos(struct ltk_sock_info *sock) {
           Returns -1 on error, 0 otherwise.
           Note: The string must include all '\n', etc. as defined in the protocol. This
           function just adds the given string verbatim. */
       -static int
       -queue_sock_write(struct ltk_sock_info *sock, const char *str, int len) {
       +int
       +ltk_queue_sock_write(int client, const char *str, int len) {
       +        if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
       +                return 1;
       +        struct ltk_sock_info *sock = &sockets[client];
                move_write_pos(sock);
                if (len < 0)
                        len = strlen(str);
       t@@ -1132,8 +1203,11 @@ queue_sock_write(struct ltk_sock_info *sock, const char *str, int len) {
                return 0;
        }
        
       -static int
       -queue_sock_write_fmt(struct ltk_sock_info *sock, const char *fmt, ...) {
       +int
       +ltk_queue_sock_write_fmt(int client, const char *fmt, ...) {
       +        if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
       +                return 1;
       +        struct ltk_sock_info *sock = &sockets[client];
                move_write_pos(sock);
                va_list args;
                va_start(args, fmt);
       t@@ -1193,13 +1267,136 @@ tokenize_command(struct ltk_sock_info *sock) {
                return 1;
        }
        
       +/* FIXME: currently no type-checking when setting specific widget mask */
       +/* FIXME: this is really ugly and inefficient right now - it will be replaced with something
       +   more generic at some point (or maybe just with a binary protocol?) */
       +static int
       +handle_mask_command(int client, char **tokens, size_t num_tokens, ltk_error *err) {
       +        if (num_tokens != 4 && num_tokens != 5) {
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
       +                return 1;
       +        }
       +        uint32_t mask = 0;
       +        int lock = 0;
       +        int special = 0;
       +        ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET_ANY, err);
       +        if (!widget) {
       +                err->arg = 1;
       +                return 1;
       +        }
       +        if (!strcmp(tokens[2], "widget")) {
       +                if (!strcmp(tokens[3], "mousepress")) {
       +                        mask = LTK_PEVENTMASK_MOUSEPRESS;
       +                } else if (!strcmp(tokens[3], "mouserelease")) {
       +                        mask = LTK_PEVENTMASK_MOUSERELEASE;
       +                } else if (!strcmp(tokens[3], "mousemotion")) {
       +                        mask = LTK_PEVENTMASK_MOUSEMOTION;
       +                } else if (!strcmp(tokens[3], "configure")) {
       +                        mask = LTK_PEVENTMASK_CONFIGURE;
       +                } else if (!strcmp(tokens[3], "statechange")) {
       +                        mask = LTK_PEVENTMASK_STATECHANGE;
       +                } else if (!strcmp(tokens[3], "none")) {
       +                        mask = LTK_PEVENTMASK_NONE;
       +                } else {
       +                        err->type = ERR_INVALID_ARGUMENT;
       +                        err->arg = 3;
       +                        return 1;
       +                }
       +        } else if (!strcmp(tokens[2], "menuentry")) {
       +                if (!strcmp(tokens[3], "press")) {
       +                        mask = LTK_PWEVENTMASK_MENUENTRY_PRESS;
       +                } else if (!strcmp(tokens[3], "none")) {
       +                        mask = LTK_PWEVENTMASK_MENUENTRY_NONE;
       +                } else {
       +                        err->type = ERR_INVALID_ARGUMENT;
       +                        err->arg = 3;
       +                        return 1;
       +                }
       +                special = 1;
       +        } else if (!strcmp(tokens[2], "button")) {
       +                if (!strcmp(tokens[3], "press")) {
       +                        mask = LTK_PWEVENTMASK_BUTTON_PRESS;
       +                } else if (!strcmp(tokens[3], "none")) {
       +                        mask = LTK_PWEVENTMASK_BUTTON_NONE;
       +                } else {
       +                        err->type = ERR_INVALID_ARGUMENT;
       +                        err->arg = 3;
       +                        return 1;
       +                }
       +                special = 1;
       +        } else {
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 2;
       +        }
       +        if (num_tokens == 5) {
       +                if (!strcmp(tokens[4], "lock")) {
       +                        lock = 1;
       +                } else {
       +                        err->type = ERR_INVALID_ARGUMENT;
       +                        err->arg = 4;
       +                        return 1;
       +                }
       +        }
       +
       +        if (!strcmp(tokens[0], "mask-add")) {
       +                if (lock) {
       +                        if (special)
       +                                ltk_widget_add_to_event_lwmask(widget, client, mask);
       +                        else
       +                                ltk_widget_add_to_event_lmask(widget, client, mask);
       +                } else {
       +                        if (special)
       +                                ltk_widget_add_to_event_wmask(widget, client, mask);
       +                        else
       +                                ltk_widget_add_to_event_mask(widget, client, mask);
       +                }
       +        } else if (!strcmp(tokens[0], "mask-set")) {
       +                if (lock) {
       +                        if (special)
       +                                ltk_widget_set_event_lwmask(widget, client, mask);
       +                        else
       +                                ltk_widget_set_event_lmask(widget, client, mask);
       +                } else {
       +                        if (special)
       +                                ltk_widget_set_event_wmask(widget, client, mask);
       +                        else
       +                                ltk_widget_set_event_mask(widget, client, mask);
       +                }
       +        } else if (!strcmp(tokens[0], "mask-remove")) {
       +                if (lock) {
       +                        if (special)
       +                                ltk_widget_remove_from_event_lwmask(widget, client, mask);
       +                        else
       +                                ltk_widget_remove_from_event_lmask(widget, client, mask);
       +                } else {
       +                        if (special)
       +                                ltk_widget_remove_from_event_wmask(widget, client, mask);
       +                        else
       +                                ltk_widget_remove_from_event_mask(widget, client, mask);
       +                }
       +        } else {
       +                err->type = ERR_INVALID_COMMAND;
       +                err->arg = 0;
       +                return 1;
       +        }
       +        return 0;
       +}
       +
        /* Process the commands as they are read from the socket. */
       -static void
       -process_commands(ltk_window *window, struct ltk_sock_info *sock) {
       +/* Returns 1 if command was 'event-unlock true',
       +   -1 if command was 'event-unlock false', 0 otherwise. */
       +static int
       +process_commands(ltk_window *window, int client) {
       +        if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
       +                return 0;
       +        struct ltk_sock_info *sock = &sockets[client];
                char **tokens;
                int num_tokens;
                ltk_error errdetail = {ERR_NONE, -1};
                int err;
       +        int retval = 0;
       +        int last = 0;
                while (!tokenize_command(sock)) {
                        err = 0;
                        tokens = sock->tokens.tokens;
       t@@ -1226,6 +1423,22 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) {
                                ltk_quit(window);
                        } else if (strcmp(tokens[0], "destroy") == 0) {
                                err = ltk_widget_destroy_cmd(window, tokens, num_tokens, &errdetail);
       +                } else if (strncmp(tokens[0], "mask", 4) == 0) {
       +                        err = handle_mask_command(client, tokens, num_tokens, &errdetail);
       +                } else if (strcmp(tokens[0], "event-unlock") == 0) {
       +                        if (num_tokens != 2) {
       +                                errdetail.type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                                err = 1;
       +                        } else if (strcmp(tokens[1], "true") == 0) {
       +                                retval = 1;
       +                        } else if (strcmp(tokens[1], "false") == 0) {
       +                                retval = -1;
       +                        } else {
       +                                err = 1;
       +                                errdetail.type = ERR_INVALID_ARGUMENT;
       +                                errdetail.arg = 1;
       +                        }
       +                        last = 1;
                        } else {
                                errdetail.type = ERR_INVALID_COMMAND;
                                errdetail.arg = -1;
       t@@ -1234,9 +1447,11 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) {
                        sock->tokens.num_tokens = 0;
                        if (err) {
                                const char *errmsg = errtype_to_string(errdetail.type);
       -                        if (queue_sock_write_fmt(sock, "err %d arg %d msg \"%s\"\n", errdetail.type, errdetail.arg, errmsg) < 0)
       +                        if (ltk_queue_sock_write_fmt(client, "err %d %d \"%s\"\n", errdetail.type, errdetail.arg, errmsg) < 0)
                                        ltk_fatal("Unable to queue socket write.\n");
                        }
       +                if (last)
       +                        break;
                }
                if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0] != sock->read) {
                        memmove(sock->read, sock->tokens.tokens[0], sock->read + sock->read_len - sock->tokens.tokens[0]);
       t@@ -1251,4 +1466,5 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) {
                        sock->read_len = 0;
                        sock->read_cur = 0;
                }
       +        return retval;
        }
   DIR diff --git a/src/menu.c b/src/menu.c
       t@@ -23,6 +23,7 @@
        #include <stdarg.h>
        #include <math.h>
        
       +#include "proto_types.h"
        #include "event.h"
        #include "memory.h"
        #include "color.h"
       t@@ -116,7 +117,7 @@ static void ltk_menuentry_detach_submenu(ltk_menuentry *e);
        
        static int ltk_menu_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err);
        
       -#define IN_SUBMENU(e) (e->widget.parent && e->widget.parent->vtable->type == LTK_MENU && ((ltk_menu *)e->widget.parent)->is_submenu)
       +#define IN_SUBMENU(e) (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)e->widget.parent)->is_submenu)
        
        static struct ltk_widget_vtable vtable = {
                .key_press = NULL,
       t@@ -134,7 +135,7 @@ static struct ltk_widget_vtable vtable = {
                .destroy = &ltk_menu_destroy,
                .child_size_change = &recalc_ideal_menu_size,
                .remove_child = &ltk_menu_remove_child,
       -        .type = LTK_MENU,
       +        .type = LTK_WIDGET_MENU,
                .flags = LTK_NEEDS_REDRAW,
        };
        
       t@@ -154,7 +155,7 @@ static struct ltk_widget_vtable entry_vtable = {
                .destroy = &ltk_menuentry_destroy,
                .child_size_change = NULL,
                .remove_child = NULL,
       -        .type = LTK_MENUENTRY,
       +        .type = LTK_WIDGET_MENUENTRY,
                .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_HOVER_IS_ACTIVE,
        };
        
       t@@ -294,7 +295,7 @@ static void
        ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) {
                ltk_menuentry *e = (ltk_menuentry *)self;
                int in_submenu = IN_SUBMENU(e);
       -        int submenus_opened = self->parent && self->parent->vtable->type == LTK_MENU && ((ltk_menu *)self->parent)->popup_submenus;
       +        int submenus_opened = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus;
                if (!(self->state & (LTK_ACTIVE | LTK_PRESSED))) {
                        /* Note: This only has to take care of the submenu that is the direct child
                           of e because ltk_window_set_active_widget already calls change_state for
       t@@ -306,7 +307,7 @@ ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) {
                           ((self->state & LTK_ACTIVE) && (in_submenu || submenus_opened))) &&
                           e->submenu && e->submenu->widget.hidden) {
                        popup_active_menu(e);
       -                if (self->parent && self->parent->vtable->type == LTK_MENU)
       +                if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENU)
                                ((ltk_menu *)self->parent)->popup_submenus = 1;
                }
        }
       t@@ -614,15 +615,16 @@ ltk_menuentry_mouse_release(ltk_widget *self, ltk_button_event *event) {
                (void)event;
                ltk_menuentry *e = (ltk_menuentry *)self;
                int in_submenu = IN_SUBMENU(e);
       -        int keep_popup = self->parent && self->parent->vtable->type == LTK_MENU && ((ltk_menu *)self->parent)->popup_submenus;
       +        int keep_popup = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus;
                /* FIXME: problem when scrolling because actual shown rect may not be entire rect */
                if ((self->state & LTK_PRESSED) && event->button == LTK_BUTTONL && ltk_collide_rect(self->rect, event->x, event->y)) {
                        if (in_submenu || !keep_popup) {
                                ltk_window_unregister_all_popups(self->window);
                        }
       -                ltk_queue_event(self->window, LTK_EVENT_MENU, self->id, "menu_entry_click");
       +                ltk_queue_specific_event(self, "menu", LTK_PWEVENTMASK_MENUENTRY_PRESS, "press");
       +                return 1;
                }
       -        return 1;
       +        return 0;
        }
        
        static int
       t@@ -662,8 +664,8 @@ ltk_menu_hide(ltk_widget *self) {
                ltk_window_unregister_popup(self->window, self);
                ltk_window_invalidate_rect(self->window, self->rect);
                /* FIXME: this is really ugly/hacky */
       -        if (menu->unpopup_submenus_on_hide && self->parent && self->parent->vtable->type == LTK_MENUENTRY &&
       -            self->parent->parent && self->parent->parent->vtable->type == LTK_MENU) {
       +        if (menu->unpopup_submenus_on_hide && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY &&
       +            self->parent->parent && self->parent->parent->vtable->type == LTK_WIDGET_MENU) {
                        ((ltk_menu *)self->parent->parent)->popup_submenus = 0;
                }
                menu->unpopup_submenus_on_hide = 1;
       t@@ -677,7 +679,7 @@ popup_active_menu(ltk_menuentry *e) {
                int in_submenu = 0, was_opened_left = 0;
                ltk_rect menu_rect = e->widget.rect;
                ltk_rect entry_rect = e->widget.rect;
       -        if (e->widget.parent && e->widget.parent->vtable->type == LTK_MENU) {
       +        if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) {
                        ltk_menu *menu = (ltk_menu *)e->widget.parent;
                        in_submenu = menu->is_submenu;
                        was_opened_left = menu->was_opened_left;
       t@@ -861,7 +863,7 @@ static void
        recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget) {
                ltk_menu *menu = (ltk_menu *)self;
                /* If widget with size change is submenu, it doesn't affect this menu */
       -        if (widget && widget->vtable->type == LTK_MENU) {
       +        if (widget && widget->vtable->type == LTK_WIDGET_MENU) {
                        ltk_widget_resize(widget);
                        return;
                }
       t@@ -1143,12 +1145,12 @@ ltk_menu_cmd_insert_entry(
                        err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_WIDGET_MENU, err);
                if (!menu) {
                        err->arg = 1;
                        return 1;
                }
       -        e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_MENUENTRY, err);
       +        e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_WIDGET_MENUENTRY, err);
                if (!e) {
                        err->arg = 3;
                        return 1;
       t@@ -1181,12 +1183,12 @@ ltk_menu_cmd_add_entry(
                        err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_WIDGET_MENU, err);
                if (!menu) {
                        err->arg = 1;
                        return 1;
                }
       -        e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_MENUENTRY, err);
       +        e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_WIDGET_MENUENTRY, err);
                if (!e) {
                        err->arg = 3;
                        return 1;
       t@@ -1213,7 +1215,7 @@ ltk_menu_cmd_remove_entry_index(
                        err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_WIDGET_MENU, err);
                if (!menu) {
                        err->arg = 1;
                        return 1;
       t@@ -1246,7 +1248,7 @@ ltk_menu_cmd_remove_entry_id(
                        err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_WIDGET_MENU, err);
                if (!menu) {
                        err->arg = 1;
                        return 1;
       t@@ -1273,7 +1275,7 @@ ltk_menu_cmd_remove_all_entries(
                        err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_WIDGET_MENU, err);
                if (!menu) {
                        err->arg = 1;
                        return 1;
       t@@ -1322,12 +1324,12 @@ ltk_menuentry_cmd_attach_submenu(
                        err->arg = -1;
                        return 1;
                }
       -        e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_MENUENTRY, err);
       +        e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_WIDGET_MENUENTRY, err);
                if (!e) {
                        err->arg = 1;
                        return 1;
                }
       -        submenu = (ltk_menu *)ltk_get_widget(tokens[3], LTK_MENU, err);
       +        submenu = (ltk_menu *)ltk_get_widget(tokens[3], LTK_WIDGET_MENU, err);
                if (!submenu) {
                        err->arg = 3;
                        return 1;
       t@@ -1354,7 +1356,7 @@ ltk_menuentry_cmd_detach_submenu(
                        err->arg = -1;
                        return 1;
                }
       -        e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_MENUENTRY, err);
       +        e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_WIDGET_MENUENTRY, err);
                if (!e) {
                        err->arg = 1;
                        return 1;
   DIR diff --git a/src/proto_types.h b/src/proto_types.h
       t@@ -0,0 +1,51 @@
       +#ifndef LTK_PROTO_TYPES_H
       +#define LTK_PROTO_TYPES_H
       +
       +#define LTK_WIDGET_UNKNOWN   0
       +#define LTK_WIDGET_ANY       1
       +#define LTK_WIDGET_GRID      2
       +#define LTK_WIDGET_BUTTON    3
       +#define LTK_WIDGET_LABEL     4
       +#define LTK_WIDGET_BOX       5
       +#define LTK_WIDGET_MENU      6
       +#define LTK_WIDGET_MENUENTRY 7
       +#define LTK_NUM_WIDGETS      8
       +
       +#define LTK_WIDGETMASK_UNKNOWN    (UINT32_C(1) << LTK_WIDGET_UNKNOWN)
       +#define LTK_WIDGETMASK_ANY        (UINT32_C(0xFFFF))
       +#define LTK_WIDGETMASK_GRID       (UINT32_C(1) << LTK_WIDGET_GRID)
       +#define LTK_WIDGETMASK_BUTTON     (UINT32_C(1) << LTK_WIDGET_BUTTON)
       +#define LTK_WIDGETMASK_LABEL      (UINT32_C(1) << LTK_WIDGET_LABEL)
       +#define LTK_WIDGETMASK_BOX        (UINT32_C(1) << LTK_WIDGET_BOX)
       +#define LTK_WIDGETMASK_MENU       (UINT32_C(1) << LTK_WIDGET_MENU)
       +#define LTK_WIDGETMASK_MENUENTRY  (UINT32_C(1) << LTK_WIDGET_MENUENTRY)
       +
       +/* 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_PWEVENT_MENUENTRY_PRESS     0
       +#define LTK_PWEVENTMASK_MENUENTRY_NONE  (UINT32_C(0))
       +#define LTK_PWEVENTMASK_MENUENTRY_PRESS (UINT32_C(1) << LTK_PWEVENT_MENUENTRY_PRESS)
       +
       +#define LTK_PWEVENT_BUTTON_PRESS     0
       +#define LTK_PWEVENTMASK_BUTTON_NONE  (UINT32_C(0))
       +#define LTK_PWEVENTMASK_BUTTON_PRESS (UINT32_C(1) << LTK_PWEVENT_BUTTON_PRESS)
       +
       +#endif /* LTK_PROTO_TYPES_H */
   DIR diff --git a/src/scrollbar.c b/src/scrollbar.c
       t@@ -52,9 +52,9 @@ static struct ltk_widget_vtable vtable = {
                .mouse_enter = NULL,
                .child_size_change = NULL,
                .remove_child = NULL,
       -        .type = LTK_UNKNOWN, /* FIXME */
       +        .type = LTK_WIDGET_UNKNOWN, /* FIXME */
                /* FIXME: need different activatable state so arrow keys don't move onto scrollbar */
       -        .flags = LTK_NEEDS_REDRAW | LTK_NEEDS_SURFACE | LTK_ACTIVATABLE_ALWAYS,
       +        .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
        };
        
        static struct {
       t@@ -147,7 +147,8 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) {
                        fg = &theme.fg_normal;
                }
                ltk_surface *s;
       -        ltk_surface_cache_get_surface(self->surface_key, &s);
       +        ltk_surface_cache_request_surface_size(scrollbar->key, self->rect.w, self->rect.h);
       +        ltk_surface_cache_get_surface(scrollbar->key, &s);
                ltk_surface_fill_rect(s, bg, (ltk_rect){0, 0, rect.w, rect.h});
                /* FIXME: maybe too much calculation in draw function - move to
                   resizing function? */
       t@@ -242,6 +243,7 @@ ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback
                        sc->widget.rect.w = theme.size;
                sc->callback = callback;
                sc->callback_data = data;
       +        sc->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, sc->widget.ideal_w, sc->widget.ideal_h);
        
                return sc;
        }
       t@@ -249,6 +251,7 @@ ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback
        static void
        ltk_scrollbar_destroy(ltk_widget *self, int shallow) {
                (void)shallow;
       -        ltk_surface_cache_release_key(self->surface_key);
       +        ltk_scrollbar *sc = (ltk_scrollbar *)self;
       +        ltk_surface_cache_release_key(sc->key);
                ltk_free(self);
        }
   DIR diff --git a/src/scrollbar.h b/src/scrollbar.h
       t@@ -28,6 +28,7 @@ typedef struct {
                int last_mouse_x;
                int last_mouse_y;
                ltk_orientation orient;
       +        ltk_surface_cache_key *key;
        } ltk_scrollbar;
        
        void ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size);
   DIR diff --git a/src/surface_cache.c b/src/surface_cache.c
       t@@ -143,7 +143,7 @@ ltk_surface_cache_get_unnamed_key(ltk_surface_cache *cache, int min_w, int min_h
                key->min_w = min_w;
                key->min_h = min_h;
                key->is_named = 0;
       -        key->widget_type = LTK_UNKNOWN;
       +        key->widget_type = LTK_WIDGET_UNKNOWN;
                key->id = -1;
                key->refcount = 1;
                return key;
   DIR diff --git a/src/widget.c b/src/widget.c
       t@@ -55,6 +55,117 @@ ltk_destroy_widget_hash(void) {
                hash_locked = 0;
        }
        
       +/* FIXME: any way to optimize the whole event mask handling a bit? */
       +void
       +ltk_widget_remove_client(int client) {
       +        khint_t k;
       +        ltk_widget *ptr;
       +        for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
       +                if (kh_exist(widget_hash, k)) {
       +                        ptr = kh_value(widget_hash, k);
       +                        for (size_t i = 0; i < ptr->masks_num; i++) {
       +                                if (ptr->event_masks[i].client == client) {
       +                                        memmove(ptr->event_masks + i, ptr->event_masks + i + 1, ptr->masks_num - i - 1);
       +                                        ptr->masks_num--;
       +                                        size_t sz = ideal_array_size(ptr->masks_alloc, ptr->masks_num - 1);
       +                                        if (sz != ptr->masks_alloc) {
       +                                                ptr->masks_alloc = sz;
       +                                                ptr->event_masks = ltk_reallocarray(ptr->event_masks, sz, sizeof(client_event_mask));
       +                                        }
       +                                        break;
       +                                }
       +                        }
       +                }
       +        }
       +}
       +
       +static client_event_mask *
       +get_mask_struct(ltk_widget *widget, int client) {
       +        for (size_t i = 0; i < widget->masks_num; i++) {
       +                if (widget->event_masks[i].client == client)
       +                        return &widget->event_masks[i];
       +        }
       +        widget->masks_alloc = ideal_array_size(widget->masks_alloc, widget->masks_num + 1);
       +        widget->event_masks = ltk_reallocarray(widget->event_masks, widget->masks_alloc, sizeof(client_event_mask));
       +        client_event_mask *m = &widget->event_masks[widget->masks_num];
       +        widget->masks_num++;
       +        m->client = client;
       +        m->mask = m->lmask = m->wmask = m->lwmask = 0;
       +        return m;
       +}
       +
       +void
       +ltk_widget_set_event_mask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->mask = mask;
       +}
       +
       +void
       +ltk_widget_set_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->lmask = mask;
       +}
       +
       +void
       +ltk_widget_set_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->wmask = mask;
       +}
       +
       +void
       +ltk_widget_set_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->lwmask = mask;
       +}
       +
       +void
       +ltk_widget_add_to_event_mask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->mask |= mask;
       +}
       +
       +void
       +ltk_widget_add_to_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->lmask |= mask;
       +}
       +
       +void
       +ltk_widget_add_to_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->wmask |= mask;
       +}
       +
       +void
       +ltk_widget_add_to_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->lwmask |= mask;
       +}
       +
       +void
       +ltk_widget_remove_from_event_mask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->mask &= ~mask;
       +}
       +
       +void
       +ltk_widget_remove_from_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->lmask &= ~mask;
       +}
       +
       +void
       +ltk_widget_remove_from_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->wmask &= ~mask;
       +}
       +
       +void
       +ltk_widget_remove_from_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
       +        client_event_mask *m = get_mask_struct(widget, client);
       +        m->lwmask &= ~mask;
       +}
       +
        void
        ltk_widgets_init() {
                widget_hash = kh_init(widget);
       t@@ -77,11 +188,6 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
                widget->window = window;
                widget->parent = NULL;
        
       -        if (vtable->flags & LTK_NEEDS_SURFACE)
       -                widget->surface_key = ltk_surface_cache_get_unnamed_key(window->surface_cache, w, h);
       -        else
       -                widget->surface_key = NULL;
       -
                /* FIXME: possibly check that draw and destroy aren't NULL */
                widget->vtable = vtable;
        
       t@@ -92,6 +198,9 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
                widget->rect.w = w;
                widget->rect.h = h;
        
       +        widget->event_masks = NULL;
       +        widget->masks_num = widget->masks_alloc = 0;
       +
                widget->row = 0;
                widget->column = 0;
                widget->row_span = 0;
       t@@ -146,17 +255,56 @@ ltk_widget_hide(ltk_widget *widget) {
           That would make a bit more sense */
        void
        ltk_widget_resize(ltk_widget *widget) {
       -        /* FIXME: should surface maybe be resized first? */
       +        int lock_client = -1;
       +        for (size_t i = 0; i < widget->masks_num; i++) {
       +                if (widget->event_masks[i].lmask & LTK_PEVENTMASK_CONFIGURE) {
       +                        ltk_queue_sock_write_fmt(
       +                            widget->event_masks[i].client,
       +                            "eventl %s widget configure %d %d %d %d\n",
       +                            widget->id, widget->rect.x, widget->rect.y,
       +                            widget->rect.w, widget->rect.h
       +                        );
       +                        lock_client = widget->event_masks[i].client;
       +                } else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) {
       +                        ltk_queue_sock_write_fmt(
       +                            widget->event_masks[i].client,
       +                            "event %s widget configure %d %d %d %d\n",
       +                            widget->id, widget->rect.x, widget->rect.y,
       +                            widget->rect.w, widget->rect.h
       +                        );
       +                }
       +        }
       +        if (lock_client >= 0) {
       +                if (ltk_handle_lock_client(widget->window, lock_client))
       +                        return;
       +        }
                if (widget->vtable->resize)
                        widget->vtable->resize(widget);
       -        if (!(widget->vtable->flags & LTK_NEEDS_SURFACE))
       -                return;
       -        ltk_surface_cache_request_surface_size(widget->surface_key, widget->rect.w, widget->rect.h);
                widget->dirty = 1;
        }
        
        void
        ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) {
       +        int lock_client = -1;
       +        /* FIXME: give old and new state in event */
       +        for (size_t i = 0; i < widget->masks_num; i++) {
       +                if (widget->event_masks[i].lmask & LTK_PEVENTMASK_CONFIGURE) {
       +                        ltk_queue_sock_write_fmt(
       +                            widget->event_masks[i].client,
       +                            "eventl %s widget statechange\n", widget->id
       +                        );
       +                        lock_client = widget->event_masks[i].client;
       +                } else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) {
       +                        ltk_queue_sock_write_fmt(
       +                            widget->event_masks[i].client,
       +                            "event %s widget statechange\n", widget->id
       +                        );
       +                }
       +        }
       +        if (lock_client >= 0) {
       +                if (ltk_handle_lock_client(widget->window, lock_client))
       +                        return;
       +        }
                if (widget->vtable->change_state)
                        widget->vtable->change_state(widget, old_state);
                if (widget->vtable->flags & LTK_NEEDS_REDRAW) {
       t@@ -195,6 +343,34 @@ is_parent(ltk_widget *parent, ltk_widget *child) {
                return child != NULL;
        }
        
       +static int
       +queue_mouse_event(ltk_widget *widget, char *type, uint32_t mask, int x, int y) {
       +        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 - 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\n",
       +                            widget->id, type, 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: This is still weird. */
        void
        ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) {
       t@@ -228,7 +404,9 @@ 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 (cur_widget->vtable->mouse_press)
       +                        if (queue_mouse_event(cur_widget, "mousepress", LTK_PEVENTMASK_MOUSEPRESS, 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 */
                                if (first && event->button == LTK_BUTTONL && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
       t@@ -257,8 +435,11 @@ ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event) {
                        widget = get_widget_under_pointer(widget, event->x, event->y);
                }
                /* FIXME: loop up to top of hierarchy if not handled */
       -        if (widget && widget->vtable->mouse_release)
       +        if (widget && queue_mouse_event(widget, "mouserelease", LTK_PEVENTMASK_MOUSERELEASE, event->x, event->y)) {
       +                /* NOP */
       +        } else if (widget && widget->vtable->mouse_release) {
                        widget->vtable->mouse_release(widget, event);
       +        }
                if (event->button == LTK_BUTTONL) {
                        ltk_window_set_pressed_widget(window, NULL);
                        /* send motion notify to widget under pointer */
       t@@ -290,7 +471,9 @@ ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) {
                while (cur_widget) {
                        int handled = 0;
                        if (cur_widget->state != LTK_DISABLED) {
       -                        if (cur_widget->vtable->motion_notify)
       +                        if (queue_mouse_event(cur_widget, "mousemotion", LTK_PEVENTMASK_MOUSEMOTION, event->x, event->y))
       +                                handled = 1;
       +                        else if (cur_widget->vtable->motion_notify)
                                        handled = cur_widget->vtable->motion_notify(cur_widget, event);
                                /* set first non-disabled widget to hover widget */
                                /* FIXME: should enter/leave event be sent to parent
       t@@ -329,7 +512,7 @@ ltk_get_widget(const char *id, ltk_widget_type type, ltk_error *err) {
                        return NULL;
                }
                widget = kh_value(widget_hash, k);
       -        if (type != LTK_WIDGET && widget->vtable->type != type) {
       +        if (type != LTK_WIDGET_ANY && widget->vtable->type != type) {
                        err->type = ERR_INVALID_WIDGET_TYPE;
                        return NULL;
                }
       t@@ -397,7 +580,7 @@ ltk_widget_destroy_cmd(
                                return 1;
                        }
                }
       -        ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET, err);
       +        ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET_ANY, err);
                if (!widget) {
                        err->arg = 1;
                        return 1;
   DIR diff --git a/src/widget.h b/src/widget.h
       t@@ -25,6 +25,7 @@
        
        
        typedef struct ltk_widget ltk_widget;
       +typedef uint32_t ltk_widget_type;
        
        typedef enum {
                LTK_ACTIVATABLE_NORMAL = 1,
       t@@ -32,8 +33,7 @@ typedef enum {
                LTK_ACTIVATABLE_ALWAYS = 1|2,
                LTK_GRABS_INPUT = 4,
                LTK_NEEDS_REDRAW = 8,
       -        LTK_NEEDS_SURFACE = 16, /* FIXME: let widgets handle this themselves */
       -        LTK_HOVER_IS_ACTIVE = 32,
       +        LTK_HOVER_IS_ACTIVE = 16,
        } ltk_widget_flags;
        
        typedef enum {
       t@@ -49,47 +49,44 @@ typedef enum {
        } ltk_orientation;
        
        typedef enum {
       -        /* FIXME: maybe remove normal or set it to 0 */
       -        LTK_NORMAL = 1,
       -        LTK_HOVER = 2,
       -        LTK_PRESSED = 4,
       -        LTK_ACTIVE = 8,
       -        LTK_HOVERACTIVE = 2 | 8,
       -        LTK_DISABLED = 16,
       +        LTK_NORMAL = 0,
       +        LTK_HOVER = 1,
       +        LTK_PRESSED = 2,
       +        LTK_ACTIVE = 4,
       +        LTK_HOVERACTIVE = 1 | 4,
       +        LTK_DISABLED = 8,
        } ltk_widget_state;
        
       -typedef enum {
       -        /* for e.g. scrollbar, which can't be directly accessed by users */
       -        LTK_UNKNOWN = 0,
       -        LTK_GRID,
       -        LTK_BUTTON,
       -        LTK_DRAW,
       -        LTK_LABEL,
       -        LTK_WIDGET,
       -        LTK_BOX,
       -        LTK_MENU,
       -        LTK_MENUENTRY,
       -        LTK_NUM_WIDGETS
       -} ltk_widget_type;
       -
        #include "surface_cache.h"
        
        struct ltk_window;
        
        struct ltk_widget_vtable;
        
       +typedef struct {
       +        int client;      /* index of client */
       +        uint32_t mask;   /* generic event mask */
       +        uint32_t lmask;  /* generic lock mask */
       +        uint32_t wmask;  /* event mask for specific widget type */
       +        uint32_t lwmask; /* lock event mask for specific widget type */
       +} client_event_mask;
       +
        struct ltk_widget {
                struct ltk_window *window;
                struct ltk_widget *parent;
                char *id;
        
       -        ltk_surface_cache_key *surface_key;
                struct ltk_widget_vtable *vtable;
        
                ltk_rect rect;
                unsigned int ideal_w;
                unsigned int ideal_h;
        
       +        client_event_mask *event_masks;
       +        /* FIXME: kind of a waste of space to use size_t here */
       +        size_t masks_num;
       +        size_t masks_alloc;
       +
                ltk_widget_state state;
                unsigned int sticky;
                unsigned short row;
       t@@ -152,5 +149,18 @@ void ltk_remove_widget(const char *id);
        void ltk_widgets_cleanup();
        void ltk_widgets_init();
        void ltk_widget_resize(ltk_widget *widget);
       +void ltk_widget_remove_client(int client);
       +void ltk_widget_set_event_mask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_set_event_lmask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_set_event_wmask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_set_event_lwmask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_add_to_event_mask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_add_to_event_lmask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_add_to_event_wmask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_add_to_event_lwmask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_remove_from_event_mask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_remove_from_event_lmask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_remove_from_event_wmask(ltk_widget *widget, int client, uint32_t mask);
       +void ltk_widget_remove_from_event_lwmask(ltk_widget *widget, int client, uint32_t mask);
        
        #endif /* LTK_WIDGET_H */
   DIR diff --git a/test.gui b/test.gui
       t@@ -19,3 +19,4 @@ button btn6 create "2 I'm another boring button."
        box box2 add btn4 ew
        box box2 add btn5 e
        box box2 add btn6
       +mask-add btn1 button press
   DIR diff --git a/test.sh b/test.sh
       t@@ -16,7 +16,7 @@ fi
        cat test.gui | ./src/ltkc $ltk_id | while read cmd
        do
                case "$cmd" in
       -        "btn1 button_click")
       +        "event btn1 button press")
                        echo "quit"
                        ;;
                *)
   DIR diff --git a/testbox.sh b/testbox.sh
       t@@ -7,18 +7,19 @@ if [ $? -ne 0 ]; then
                exit 1
        fi
        
       -cmds="box box1 create vertical\nset-root-widget box1\nbutton exit_btn create \"Exit\"\nbox box1 add exit_btn
       +cmds="box box1 create vertical\nset-root-widget box1\nbutton exit_btn create \"Exit\"\nmask-add exit_btn button press\nbox box1 add exit_btn
        $(curl -s gopher://lumidify.org | awk -F'\t' '
        BEGIN {btn = 0; lbl = 0;}
        /^i/ { printf "label lbl%s create \"%s\"\nbox box1 add lbl%s w\n", lbl, substr($1, 2), lbl; lbl++ }
       -/^[1gI]/ { printf "button btn%s create \"%s\"\nbox box1 add btn%s w\n", btn, substr($1, 2), btn; btn++ }')"
       +/^[1gI]/ { printf "button btn%s create \"%s\"\nbox box1 add btn%s w\n", btn, substr($1, 2), btn; btn++ }')
       +mask-add btn0 button press"
        echo "$cmds" | ./src/ltkc $ltk_id | while read cmd
        do
                case "$cmd" in
       -        "exit_btn button_click")
       +        "event exit_btn button press")
                        echo "quit"
                        ;;
       -        "btn0 button_click")
       +        "event btn0 button press")
                        echo "button bla create \"safhaskfldshk\"\nbox box1 add bla w"
                        ;;
                *)