ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 99531db6c1821736ff1a975ec9872fc033d94df7
parent 488ed473efaa6153752374f9c27f67fe45dc4823
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:
MMakefile | 7++++---
MREADME.md | 2++
Msocket_format.txt | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/box.c | 11+++++------
Msrc/button.c | 16+++++++++-------
Msrc/button.h | 7++++---
Msrc/grid.c | 14+++++++-------
Msrc/label.c | 10++++++----
Msrc/label.h | 1+
Msrc/ltk.h | 5+++++
Msrc/ltkd.c | 324++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/menu.c | 46++++++++++++++++++++++++----------------------
Asrc/proto_types.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/scrollbar.c | 11+++++++----
Msrc/scrollbar.h | 1+
Msrc/surface_cache.c | 2+-
Msrc/widget.c | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/widget.h | 58++++++++++++++++++++++++++++++++++------------------------
Mtest.gui | 1+
Mtest.sh | 2+-
Mtestbox.sh | 9+++++----
21 files changed, 703 insertions(+), 162 deletions(-)

diff --git a/Makefile b/Makefile @@ -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 @@ -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 diff --git a/README.md b/README.md @@ -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. diff --git a/socket_format.txt b/socket_format.txt @@ -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 @@ -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. diff --git a/src/box.c b/src/box.c @@ -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, }; @@ -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; @@ -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; @@ -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; diff --git a/src/button.c b/src/button.c @@ -20,6 +20,7 @@ #include <string.h> #include <stdarg.h> +#include "proto_types.h" #include "event.h" #include "memory.h" #include "color.h" @@ -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 { @@ -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); } @@ -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; @@ -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; @@ -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); diff --git a/src/button.h b/src/button.h @@ -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" */ @@ -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); @@ -37,4 +38,4 @@ int ltk_button_cmd( ltk_error * ); -#endif /* _LTK_BUTTON_H_ */ +#endif /* LTK_BUTTON_H */ diff --git a/src/grid.c b/src/grid.c @@ -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, }; @@ -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; @@ -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; @@ -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; @@ -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; diff --git a/src/label.c b/src/label.c @@ -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 { @@ -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); } @@ -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; } @@ -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); diff --git a/src/label.h b/src/label.h @@ -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); diff --git a/src/ltk.h b/src/ltk.h @@ -18,6 +18,7 @@ #define _LTK_H_ #include <stdint.h> +#include "proto_types.h" typedef enum { LTK_EVENT_RESIZE = 1 << 0, @@ -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 diff --git a/src/ltkd.c b/src/ltkd.c @@ -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 */ @@ -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); @@ -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; @@ -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); @@ -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; @@ -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; @@ -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)) { @@ -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"); } } @@ -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) @@ -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; @@ -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 @@ -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); @@ -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); @@ -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; @@ -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; @@ -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]); @@ -1251,4 +1466,5 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) { sock->read_len = 0; sock->read_cur = 0; } + return retval; } diff --git a/src/menu.c b/src/menu.c @@ -23,6 +23,7 @@ #include <stdarg.h> #include <math.h> +#include "proto_types.h" #include "event.h" #include "memory.h" #include "color.h" @@ -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, @@ -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, }; @@ -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, }; @@ -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 @@ -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; } } @@ -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 @@ -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; @@ -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; @@ -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; } @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; diff --git a/src/proto_types.h b/src/proto_types.h @@ -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 */ diff --git a/src/scrollbar.c b/src/scrollbar.c @@ -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 { @@ -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? */ @@ -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; } @@ -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); } diff --git a/src/scrollbar.h b/src/scrollbar.h @@ -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); diff --git a/src/surface_cache.c b/src/surface_cache.c @@ -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; diff --git a/src/widget.c b/src/widget.c @@ -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); @@ -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; @@ -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; @@ -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) { @@ -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) { @@ -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)) { @@ -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 */ @@ -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 @@ -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; } @@ -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; diff --git a/src/widget.h b/src/widget.h @@ -25,6 +25,7 @@ typedef struct ltk_widget ltk_widget; +typedef uint32_t ltk_widget_type; typedef enum { LTK_ACTIVATABLE_NORMAL = 1, @@ -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 { @@ -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; @@ -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 */ diff --git a/test.gui b/test.gui @@ -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 diff --git a/test.sh b/test.sh @@ -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" ;; *) diff --git a/testbox.sh b/testbox.sh @@ -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" ;; *)