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

widget.c (40866B)


      1 /*
      2  * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
      3  *
      4  * Permission to use, copy, modify, and/or distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <stdarg.h>
     18 #include <stdint.h>
     19 
     20 #include "event.h"
     21 #include "rect.h"
     22 #include "widget.h"
     23 #include "color.h"
     24 #include "ltk.h"
     25 #include "memory.h"
     26 #include "util.h"
     27 #include "khash.h"
     28 #include "surface_cache.h"
     29 #include "config.h"
     30 #include "array.h"
     31 #include "keys.h"
     32 
     33 static int cb_focus_active(ltk_window *window, ltk_key_event *event, int handled);
     34 static int cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled);
     35 static int cb_move_prev(ltk_window *window, ltk_key_event *event, int handled);
     36 static int cb_move_next(ltk_window *window, ltk_key_event *event, int handled);
     37 static int cb_move_left(ltk_window *window, ltk_key_event *event, int handled);
     38 static int cb_move_right(ltk_window *window, ltk_key_event *event, int handled);
     39 static int cb_move_up(ltk_window *window, ltk_key_event *event, int handled);
     40 static int cb_move_down(ltk_window *window, ltk_key_event *event, int handled);
     41 static int cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled);
     42 static int cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled);
     43 static int cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled);
     44 
     45 struct key_cb {
     46 	char *func_name;
     47 	int (*callback)(ltk_window *, ltk_key_event *, int handled);
     48 };
     49 
     50 static struct key_cb cb_map[] = {
     51 	{"focus-active", &cb_focus_active},
     52 	{"move-down", &cb_move_down},
     53 	{"move-left", &cb_move_left},
     54 	{"move-next", &cb_move_next},
     55 	{"move-prev", &cb_move_prev},
     56 	{"move-right", &cb_move_right},
     57 	{"move-up", &cb_move_up},
     58 	{"remove-popups", &cb_remove_popups},
     59 	{"set-pressed", &cb_set_pressed},
     60 	{"unfocus-active", &cb_unfocus_active},
     61 	{"unset-pressed", &cb_unset_pressed},
     62 };
     63 
     64 
     65 struct keypress_cfg {
     66 	ltk_keypress_binding b;
     67 	struct key_cb cb;
     68 };
     69 
     70 struct keyrelease_cfg {
     71 	ltk_keyrelease_binding b;
     72 	struct key_cb cb;
     73 };
     74 
     75 LTK_ARRAY_INIT_DECL_STATIC(keypress, struct keypress_cfg)
     76 LTK_ARRAY_INIT_IMPL_STATIC(keypress, struct keypress_cfg)
     77 LTK_ARRAY_INIT_DECL_STATIC(keyrelease, struct keyrelease_cfg)
     78 LTK_ARRAY_INIT_IMPL_STATIC(keyrelease, struct keyrelease_cfg)
     79 
     80 static ltk_array(keypress) *keypresses = NULL;
     81 static ltk_array(keyrelease) *keyreleases = NULL;
     82 
     83 GEN_CB_MAP_HELPERS(cb_map, struct key_cb, func_name)
     84 
     85 /* FIXME: most of this is duplicated code */
     86 /* FIXME: document that pointers inside binding are taken over! */
     87 int ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b);
     88 int ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b);
     89 
     90 int
     91 ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b) {
     92 	if (!keypresses)
     93 		keypresses = ltk_array_create(keypress, 1);
     94 	struct key_cb *cb = cb_map_get_entry(func_name, func_len);
     95 	if (!cb)
     96 		return 1;
     97 	struct keypress_cfg cfg = {b, *cb};
     98 	ltk_array_append(keypress, keypresses, cfg);
     99 	return 0;
    100 }
    101 
    102 int
    103 ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b) {
    104 	if (!keyreleases)
    105 		keyreleases = ltk_array_create(keyrelease, 1);
    106 	struct key_cb *cb = cb_map_get_entry(func_name, func_len);
    107 	if (!cb)
    108 		return 1;
    109 	struct keyrelease_cfg cfg = {b, *cb};
    110 	ltk_array_append(keyrelease, keyreleases, cfg);
    111 	return 0;
    112 }
    113 
    114 static void
    115 destroy_keypress_cfg(struct keypress_cfg cfg) {
    116 	ltk_keypress_binding_destroy(cfg.b);
    117 }
    118 
    119 void
    120 ltk_widget_cleanup(void) {
    121 	ltk_array_destroy_deep(keypress, keypresses, &destroy_keypress_cfg);
    122 	ltk_array_destroy(keyrelease, keyreleases);
    123 	keypresses = NULL;
    124 	keyreleases = NULL;
    125 }
    126 
    127 static void ltk_destroy_widget_hash(void);
    128 
    129 KHASH_MAP_INIT_STR(widget, ltk_widget *)
    130 static khash_t(widget) *widget_hash = NULL;
    131 /* Hack to make ltk_destroy_widget_hash work */
    132 /* FIXME: any better way to do this? */
    133 static int hash_locked = 0;
    134 
    135 /* needed for passing keyboard events down the hierarchy */
    136 static ltk_widget **widget_stack = NULL;
    137 static size_t widget_stack_alloc = 0;
    138 static size_t widget_stack_len = 0;
    139 
    140 static void
    141 ltk_destroy_widget_hash(void) {
    142 	hash_locked = 1;
    143 	khint_t k;
    144 	ltk_widget *ptr;
    145 	ltk_error err;
    146 	for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
    147 		if (kh_exist(widget_hash, k)) {
    148 			ptr = kh_value(widget_hash, k);
    149 			ltk_free((char *)kh_key(widget_hash, k));
    150 			ltk_widget_destroy(ptr, 1, &err);
    151 		}
    152 	}
    153 	kh_destroy(widget, widget_hash);
    154 	widget_hash = NULL;
    155 	hash_locked = 0;
    156 }
    157 
    158 /* FIXME: any way to optimize the whole event mask handling a bit? */
    159 void
    160 ltk_widget_remove_client(int client) {
    161 	khint_t k;
    162 	ltk_widget *ptr;
    163 	for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
    164 		if (kh_exist(widget_hash, k)) {
    165 			ptr = kh_value(widget_hash, k);
    166 			for (size_t i = 0; i < ptr->masks_num; i++) {
    167 				if (ptr->event_masks[i].client == client) {
    168 					memmove(ptr->event_masks + i, ptr->event_masks + i + 1, ptr->masks_num - i - 1);
    169 					ptr->masks_num--;
    170 					/* FIXME: maybe reset to NULL in that case? */
    171 					if (ptr->masks_num > 0) {
    172 						size_t sz = ideal_array_size(ptr->masks_alloc, ptr->masks_num);
    173 						if (sz != ptr->masks_alloc) {
    174 							ptr->masks_alloc = sz;
    175 							ptr->event_masks = ltk_reallocarray(ptr->event_masks, sz, sizeof(client_event_mask));
    176 						}
    177 					}
    178 					break;
    179 				}
    180 			}
    181 		}
    182 	}
    183 }
    184 
    185 static client_event_mask *
    186 get_mask_struct(ltk_widget *widget, int client) {
    187 	for (size_t i = 0; i < widget->masks_num; i++) {
    188 		if (widget->event_masks[i].client == client)
    189 			return &widget->event_masks[i];
    190 	}
    191 	widget->masks_alloc = ideal_array_size(widget->masks_alloc, widget->masks_num + 1);
    192 	widget->event_masks = ltk_reallocarray(widget->event_masks, widget->masks_alloc, sizeof(client_event_mask));
    193 	client_event_mask *m = &widget->event_masks[widget->masks_num];
    194 	widget->masks_num++;
    195 	m->client = client;
    196 	m->mask = m->lmask = m->wmask = m->lwmask = 0;
    197 	return m;
    198 }
    199 
    200 void
    201 ltk_widget_set_event_mask(ltk_widget *widget, int client, uint32_t mask) {
    202 	client_event_mask *m = get_mask_struct(widget, client);
    203 	m->mask = mask;
    204 }
    205 
    206 void
    207 ltk_widget_set_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
    208 	client_event_mask *m = get_mask_struct(widget, client);
    209 	m->lmask = mask;
    210 }
    211 
    212 void
    213 ltk_widget_set_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
    214 	client_event_mask *m = get_mask_struct(widget, client);
    215 	m->wmask = mask;
    216 }
    217 
    218 void
    219 ltk_widget_set_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
    220 	client_event_mask *m = get_mask_struct(widget, client);
    221 	m->lwmask = mask;
    222 }
    223 
    224 void
    225 ltk_widget_add_to_event_mask(ltk_widget *widget, int client, uint32_t mask) {
    226 	client_event_mask *m = get_mask_struct(widget, client);
    227 	m->mask |= mask;
    228 }
    229 
    230 void
    231 ltk_widget_add_to_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
    232 	client_event_mask *m = get_mask_struct(widget, client);
    233 	m->lmask |= mask;
    234 }
    235 
    236 void
    237 ltk_widget_add_to_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
    238 	client_event_mask *m = get_mask_struct(widget, client);
    239 	m->wmask |= mask;
    240 }
    241 
    242 void
    243 ltk_widget_add_to_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
    244 	client_event_mask *m = get_mask_struct(widget, client);
    245 	m->lwmask |= mask;
    246 }
    247 
    248 void
    249 ltk_widget_remove_from_event_mask(ltk_widget *widget, int client, uint32_t mask) {
    250 	client_event_mask *m = get_mask_struct(widget, client);
    251 	m->mask &= ~mask;
    252 }
    253 
    254 void
    255 ltk_widget_remove_from_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
    256 	client_event_mask *m = get_mask_struct(widget, client);
    257 	m->lmask &= ~mask;
    258 }
    259 
    260 void
    261 ltk_widget_remove_from_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
    262 	client_event_mask *m = get_mask_struct(widget, client);
    263 	m->wmask &= ~mask;
    264 }
    265 
    266 void
    267 ltk_widget_remove_from_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
    268 	client_event_mask *m = get_mask_struct(widget, client);
    269 	m->lwmask &= ~mask;
    270 }
    271 
    272 void
    273 ltk_widgets_init() {
    274 	widget_hash = kh_init(widget);
    275 	if (!widget_hash) ltk_fatal_errno("Unable to initialize widget hash table.\n");
    276 }
    277 
    278 void
    279 ltk_widgets_cleanup() {
    280 	free(widget_stack);
    281 	if (widget_hash)
    282 		ltk_destroy_widget_hash();
    283 }
    284 
    285 void
    286 ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
    287     struct ltk_widget_vtable *vtable, int w, int h) {
    288 	if (id)
    289 		widget->id = ltk_strdup(id);
    290 	else
    291 		widget->id = NULL;
    292 	widget->window = window;
    293 	widget->parent = NULL;
    294 
    295 	/* FIXME: possibly check that draw and destroy aren't NULL */
    296 	widget->vtable = vtable;
    297 
    298 	widget->state = LTK_NORMAL;
    299 	widget->row = 0;
    300 	widget->lrect.x = 0;
    301 	widget->lrect.y = 0;
    302 	widget->lrect.w = w;
    303 	widget->lrect.h = h;
    304 	widget->crect.x = 0;
    305 	widget->crect.y = 0;
    306 	widget->crect.w = w;
    307 	widget->crect.h = h;
    308 	widget->popup = 0;
    309 
    310 	widget->ideal_w = widget->ideal_h = 0;
    311 
    312 	widget->event_masks = NULL;
    313 	widget->masks_num = widget->masks_alloc = 0;
    314 
    315 	widget->row = 0;
    316 	widget->column = 0;
    317 	widget->row_span = 0;
    318 	widget->column_span = 0;
    319 	widget->sticky = 0;
    320 	widget->dirty = 1;
    321 	widget->hidden = 0;
    322 }
    323 
    324 void
    325 ltk_widget_hide(ltk_widget *widget) {
    326 	if (widget->vtable->hide)
    327 		widget->vtable->hide(widget);
    328 	widget->hidden = 1;
    329 	/* remove hover state */
    330 	/* FIXME: this needs to call change_state but that might cause issues */
    331 	ltk_widget *hover = widget->window->hover_widget;
    332 	while (hover) {
    333 		if (hover == widget) {
    334 			widget->window->hover_widget->state &= ~LTK_HOVER;
    335 			widget->window->hover_widget = NULL;
    336 			break;
    337 		}
    338 		hover = hover->parent;
    339 	}
    340 	ltk_widget *pressed = widget->window->pressed_widget;
    341 	while (pressed) {
    342 		if (pressed == widget) {
    343 			widget->window->pressed_widget->state &= ~LTK_PRESSED;
    344 			widget->window->pressed_widget = NULL;
    345 			break;
    346 		}
    347 		pressed = pressed->parent;
    348 	}
    349 	ltk_widget *active = widget->window->active_widget;
    350 	/* if current active widget is child, set active widget to widget above in hierarchy */
    351 	int set_next = 0;
    352 	while (active) {
    353 		if (active == widget) {
    354 			set_next = 1;
    355 		/* FIXME: use config values for all_activatable */
    356 		} else if (set_next && (active->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
    357 			ltk_window_set_active_widget(active->window, active);
    358 			break;
    359 		}
    360 		active = active->parent;
    361 	}
    362 	if (set_next && !active)
    363 		ltk_window_set_active_widget(active->window, NULL);
    364 }
    365 
    366 /* FIXME: Maybe pass the new width as arg here?
    367    That would make a bit more sense */
    368 /* FIXME: maybe give global and local position in event */
    369 void
    370 ltk_widget_resize(ltk_widget *widget) {
    371 	int lock_client = -1;
    372 	for (size_t i = 0; i < widget->masks_num; i++) {
    373 		if (widget->event_masks[i].lmask & LTK_PEVENTMASK_CONFIGURE) {
    374 			ltk_queue_sock_write_fmt(
    375 			    widget->event_masks[i].client,
    376 			    "eventl %s widget configure %d %d %d %d\n",
    377 			    widget->id, widget->lrect.x, widget->lrect.y,
    378 			    widget->lrect.w, widget->lrect.h
    379 			);
    380 			lock_client = widget->event_masks[i].client;
    381 		} else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) {
    382 			ltk_queue_sock_write_fmt(
    383 			    widget->event_masks[i].client,
    384 			    "event %s widget configure %d %d %d %d\n",
    385 			    widget->id, widget->lrect.x, widget->lrect.y,
    386 			    widget->lrect.w, widget->lrect.h
    387 			);
    388 		}
    389 	}
    390 	if (lock_client >= 0) {
    391 		if (ltk_handle_lock_client(widget->window, lock_client))
    392 			return;
    393 	}
    394 	if (widget->vtable->resize)
    395 		widget->vtable->resize(widget);
    396 	widget->dirty = 1;
    397 }
    398 
    399 void
    400 ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) {
    401 	if (old_state == widget->state)
    402 		return;
    403 	int lock_client = -1;
    404 	/* FIXME: give old and new state in event */
    405 	for (size_t i = 0; i < widget->masks_num; i++) {
    406 		if (widget->event_masks[i].lmask & LTK_PEVENTMASK_CONFIGURE) {
    407 			ltk_queue_sock_write_fmt(
    408 			    widget->event_masks[i].client,
    409 			    "eventl %s widget statechange\n", widget->id
    410 			);
    411 			lock_client = widget->event_masks[i].client;
    412 		} else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) {
    413 			ltk_queue_sock_write_fmt(
    414 			    widget->event_masks[i].client,
    415 			    "event %s widget statechange\n", widget->id
    416 			);
    417 		}
    418 	}
    419 	if (lock_client >= 0) {
    420 		if (ltk_handle_lock_client(widget->window, lock_client))
    421 			return;
    422 	}
    423 	if (widget->vtable->change_state)
    424 		widget->vtable->change_state(widget, old_state);
    425 	if (widget->vtable->flags & LTK_NEEDS_REDRAW) {
    426 		widget->dirty = 1;
    427 		ltk_window_invalidate_widget_rect(widget->window, widget);
    428 	}
    429 }
    430 
    431 /* x and y are global! */
    432 static ltk_widget *
    433 get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret) {
    434 	ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
    435 	ltk_widget *next = NULL;
    436 	*local_x_ret = x - glob.x;
    437 	*local_y_ret = y - glob.y;
    438 	while (widget && widget->vtable->get_child_at_pos) {
    439 		next = widget->vtable->get_child_at_pos(widget, *local_x_ret, *local_y_ret);
    440 		if (!next) {
    441 			break;
    442 		} else {
    443 			widget = next;
    444 			if (next->popup) {
    445 				*local_x_ret = x - next->lrect.x;
    446 				*local_y_ret = y - next->lrect.y;
    447 			} else {
    448 				*local_x_ret -= next->lrect.x;
    449 				*local_y_ret -= next->lrect.y;
    450 			}
    451 		}
    452 	}
    453 	return widget;
    454 }
    455 
    456 static ltk_widget *
    457 get_hover_popup(ltk_window *window, int x, int y) {
    458 	for (size_t i = window->popups_num; i-- > 0;) {
    459 		if (ltk_collide_rect(window->popups[i]->crect, x, y))
    460 			return window->popups[i];
    461 	}
    462 	return NULL;
    463 }
    464 
    465 static int
    466 is_parent(ltk_widget *parent, ltk_widget *child) {
    467 	while (child && child != parent) {
    468 		child = child->parent;
    469 	}
    470 	return child != NULL;
    471 }
    472 
    473 /* FIXME: fix global and local coordinates! */
    474 static int
    475 queue_mouse_event(ltk_widget *widget, ltk_event_type type, int x, int y) {
    476 	uint32_t mask;
    477 	char *typename;
    478 	switch (type) {
    479 	case LTK_MOTION_EVENT:
    480 		mask = LTK_PEVENTMASK_MOUSEMOTION;
    481 		typename = "mousemotion";
    482 		break;
    483 	case LTK_2BUTTONPRESS_EVENT:
    484 		mask = LTK_PEVENTMASK_2MOUSEPRESS;
    485 		typename = "2mousepress";
    486 		break;
    487 	case LTK_3BUTTONPRESS_EVENT:
    488 		mask = LTK_PEVENTMASK_3MOUSEPRESS;
    489 		typename = "3mousepress";
    490 		break;
    491 	case LTK_BUTTONRELEASE_EVENT:
    492 		mask = LTK_PEVENTMASK_MOUSERELEASE;
    493 		typename = "mouserelease";
    494 		break;
    495 	case LTK_2BUTTONRELEASE_EVENT:
    496 		mask = LTK_PEVENTMASK_2MOUSERELEASE;
    497 		typename = "2mouserelease";
    498 		break;
    499 	case LTK_3BUTTONRELEASE_EVENT:
    500 		mask = LTK_PEVENTMASK_3MOUSERELEASE;
    501 		typename = "3mouserelease";
    502 		break;
    503 	case LTK_BUTTONPRESS_EVENT:
    504 	default:
    505 		mask = LTK_PEVENTMASK_MOUSEPRESS;
    506 		typename = "mousepress";
    507 		break;
    508 	}
    509 	int lock_client = -1;
    510 	for (size_t i = 0; i < widget->masks_num; i++) {
    511 		if (widget->event_masks[i].lmask & mask) {
    512 			ltk_queue_sock_write_fmt(
    513 			    widget->event_masks[i].client,
    514 			    "eventl %s widget %s %d %d %d %d\n",
    515 			    widget->id, typename, x, y, x, y
    516 			    /* x - widget->rect.x, y - widget->rect.y */
    517 			);
    518 			lock_client = widget->event_masks[i].client;
    519 		} else if (widget->event_masks[i].mask & mask) {
    520 			ltk_queue_sock_write_fmt(
    521 			    widget->event_masks[i].client,
    522 			    "event %s widget %s %d %d %d %d\n",
    523 			    widget->id, typename, x, y, x, y
    524 			    /* x - widget->rect.x, y - widget->rect.y */
    525 			);
    526 		}
    527 	}
    528 	if (lock_client >= 0) {
    529 		if (ltk_handle_lock_client(widget->window, lock_client))
    530 			return 1;
    531 	}
    532 	return 0;
    533 }
    534 
    535 /* FIXME: global/local coords (like above) */
    536 static int
    537 queue_scroll_event(ltk_widget *widget, int x, int y, int dx, int dy) {
    538 	uint32_t mask = LTK_PEVENTMASK_MOUSESCROLL;
    539 	int lock_client = -1;
    540 	for (size_t i = 0; i < widget->masks_num; i++) {
    541 		if (widget->event_masks[i].lmask & mask) {
    542 			ltk_queue_sock_write_fmt(
    543 			    widget->event_masks[i].client,
    544 			    "eventl %s widget %s %d %d %d %d %d %d\n",
    545 			    widget->id, "mousescroll", x, y, x, y, dx, dy
    546 			    /* x - widget->rect.x, y - widget->rect.y */
    547 			);
    548 			lock_client = widget->event_masks[i].client;
    549 		} else if (widget->event_masks[i].mask & mask) {
    550 			ltk_queue_sock_write_fmt(
    551 			    widget->event_masks[i].client,
    552 			    "event %s widget %s %d %d %d %d %d %d\n",
    553 			    widget->id, "mousescroll", x, y, x, y, dx, dy
    554 			    /* x - widget->rect.x, y - widget->rect.y */
    555 			);
    556 		}
    557 	}
    558 	if (lock_client >= 0) {
    559 		if (ltk_handle_lock_client(widget->window, lock_client))
    560 			return 1;
    561 	}
    562 	return 0;
    563 }
    564 
    565 static void
    566 ensure_active_widget_shown(ltk_window *window) {
    567 	ltk_widget *widget = window->active_widget;
    568 	if (!widget)
    569 		return;
    570 	ltk_rect r = widget->lrect;
    571 	while (widget->parent) {
    572 		if (widget->parent->vtable->ensure_rect_shown)
    573 			widget->parent->vtable->ensure_rect_shown(widget->parent, r);
    574 		widget = widget->parent;
    575 		r.x += widget->lrect.x;
    576 		r.y += widget->lrect.y;
    577 		/* FIXME: this currently just aborts if a widget is positioned
    578 		   absolutely because I'm not sure what the best action would
    579 		   be in that case */
    580 		if (widget->popup)
    581 			break;
    582 	}
    583 	ltk_window_invalidate_widget_rect(window, widget);
    584 }
    585 
    586 /* FIXME: come up with a more elegant way to handle this? */
    587 /* FIXME: Handle hidden state here instead of in widgets */
    588 /* FIXME: handle disabled state */
    589 static int
    590 prev_child(ltk_window *window) {
    591 	if (!window->root_widget)
    592 		return 0;
    593 	ltk_config *config = ltk_config_get();
    594 	ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
    595 	ltk_widget *new, *cur = window->active_widget;
    596 	int changed = 0;
    597 	ltk_widget *prevcur = cur;
    598 	while (1) {
    599 		if (cur) {
    600 			while (cur->parent) {
    601 				new = NULL;
    602 				if (cur->parent->vtable->prev_child)
    603 					new = cur->parent->vtable->prev_child(cur->parent, cur);
    604 				if (new) {
    605 					cur = new;
    606 					ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
    607 					while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
    608 						cur = new;
    609 						if (cur->vtable->flags & act_flags)
    610 							last_activatable = cur;
    611 					}
    612 					if (last_activatable) {
    613 						cur = last_activatable;
    614 						changed = 1;
    615 						break;
    616 					}
    617 				} else {
    618 					cur = cur->parent;
    619 					if (cur->vtable->flags & act_flags) {
    620 						changed = 1;
    621 						break;
    622 					}
    623 				}
    624 			}
    625 		}
    626 		if (!changed) {
    627 			cur = window->root_widget;
    628 			ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
    629 			while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
    630 				cur = new;
    631 				if (cur->vtable->flags & act_flags)
    632 					last_activatable = cur;
    633 			}
    634 			if (last_activatable)
    635 				cur = last_activatable;
    636 		}
    637 		if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
    638 			break;
    639 		prevcur = cur;
    640 	}
    641 	/* FIXME: What exactly should be done if no activatable widget exists? */
    642 	if (cur != window->active_widget) {
    643 		ltk_window_set_active_widget(window, cur);
    644 		ensure_active_widget_shown(window);
    645 		return 1;
    646 	}
    647 	return 0;
    648 }
    649 
    650 static int
    651 next_child(ltk_window *window) {
    652 	if (!window->root_widget)
    653 		return 0;
    654 	ltk_config *config = ltk_config_get();
    655 	ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
    656 	ltk_widget *new, *cur = window->active_widget;
    657 	int changed = 0;
    658 	ltk_widget *prevcur = cur;
    659 	while (1) {
    660 		if (cur) {
    661 			while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
    662 				cur = new;
    663 				if (cur->vtable->flags & act_flags) {
    664 					changed = 1;
    665 					break;
    666 				}
    667 			}
    668 			if (!changed) {
    669 				while (cur->parent) {
    670 					new = NULL;
    671 					if (cur->parent->vtable->next_child)
    672 						new = cur->parent->vtable->next_child(cur->parent, cur);
    673 					if (new) {
    674 						cur = new;
    675 						if (cur->vtable->flags & act_flags) {
    676 							changed = 1;
    677 							break;
    678 						}
    679 						while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
    680 							cur = new;
    681 							if (cur->vtable->flags & act_flags) {
    682 								changed = 1;
    683 								break;
    684 							}
    685 						}
    686 						if (changed)
    687 							break;
    688 					} else {
    689 						cur = cur->parent;
    690 					}
    691 				}
    692 			}
    693 		}
    694 		if (!changed) {
    695 			cur = window->root_widget;
    696 			if (!(cur->vtable->flags & act_flags)) {
    697 				while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
    698 					cur = new;
    699 					if (cur->vtable->flags & act_flags)
    700 						break;
    701 				}
    702 			}
    703 			if (!(cur->vtable->flags & act_flags))
    704 				cur = window->root_widget;
    705 		}
    706 		if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
    707 			break;
    708 		prevcur = cur;
    709 	}
    710 	if (cur != window->active_widget) {
    711 		ltk_window_set_active_widget(window, cur);
    712 		ensure_active_widget_shown(window);
    713 		return 1;
    714 	}
    715 	return 0;
    716 }
    717 
    718 /* FIXME: moving up/down/left/right needs to be rethought
    719    it generally is a bit weird, and in particular, nearest_child always searches for the child
    720    that has the smallest distance to the given rect, so it may not be the child that the user
    721    expects when going down (e.g. a vertical box with one widget closer vertically but on the
    722    other side horizontally, thus possibly leading to a different widget that is farther away
    723    vertically to be chosen instead) - what would be logical here? */
    724 static ltk_widget *
    725 nearest_child(ltk_widget *widget, ltk_rect r) {
    726 	ltk_point local = ltk_global_to_widget_pos(widget, r.x, r.y);
    727 	return widget->vtable->nearest_child(widget, (ltk_rect){local.x, local.y, r.w, r.h});
    728 }
    729 
    730 /* FIXME: maybe wrap around in these two functions? */
    731 static int
    732 left_top_child(ltk_window *window, int left) {
    733 	if (!window->root_widget)
    734 		return 0;
    735 	ltk_config *config = ltk_config_get();
    736 	ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
    737 	ltk_widget *new, *cur = window->active_widget;
    738 	ltk_rect old_rect = {0, 0, 0, 0};
    739 	if (cur) {
    740 		ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y};
    741 		old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
    742 	}
    743 	if (cur) {
    744 		while (cur->parent) {
    745 			new = NULL;
    746 			if (left) {
    747 				if (cur->parent->vtable->nearest_child_left)
    748 					new = cur->parent->vtable->nearest_child_left(cur->parent, cur);
    749 			} else {
    750 				if (cur->parent->vtable->nearest_child_above)
    751 					new = cur->parent->vtable->nearest_child_above(cur->parent, cur);
    752 			}
    753 			if (new) {
    754 				cur = new;
    755 				ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
    756 				while (cur->vtable->nearest_child && (new = nearest_child(cur, old_rect))) {
    757 					cur = new;
    758 					if (cur->vtable->flags & act_flags)
    759 						last_activatable = cur;
    760 				}
    761 				if (last_activatable) {
    762 					cur = last_activatable;
    763 					break;
    764 				}
    765 			} else {
    766 				cur = cur->parent;
    767 				if (cur->vtable->flags & act_flags) {
    768 					break;
    769 				}
    770 			}
    771 		}
    772 	} else {
    773 		cur = window->root_widget;
    774 		ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
    775 		ltk_rect r = {cur->lrect.w, cur->lrect.h, 0, 0};
    776 		while (cur->vtable->nearest_child && (new = nearest_child(cur, r))) {
    777 			cur = new;
    778 			if (cur->vtable->flags & act_flags)
    779 				last_activatable = cur;
    780 		}
    781 		if (last_activatable)
    782 			cur = last_activatable;
    783 	}
    784 	/* FIXME: What exactly should be done if no activatable widget exists? */
    785 	if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
    786 		ltk_window_set_active_widget(window, cur);
    787 		ensure_active_widget_shown(window);
    788 		return 1;
    789 	}
    790 	return 0;
    791 }
    792 
    793 static int
    794 right_bottom_child(ltk_window *window, int right) {
    795 	if (!window->root_widget)
    796 		return 0;
    797 	ltk_config *config = ltk_config_get();
    798 	ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
    799 	ltk_widget *new, *cur = window->active_widget;
    800 	int changed = 0;
    801 	ltk_rect old_rect = {0, 0, 0, 0};
    802 	ltk_rect corner = {0, 0, 0, 0};
    803 	if (cur) {
    804 		ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y};
    805 		corner = (ltk_rect){glob.x, glob.y, 0, 0};
    806 		old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
    807 		while (cur->vtable->nearest_child && (new = nearest_child(cur, corner))) {
    808 			cur = new;
    809 			if (cur->vtable->flags & act_flags) {
    810 				changed = 1;
    811 				break;
    812 			}
    813 		}
    814 		if (!changed) {
    815 			while (cur->parent) {
    816 				new = NULL;
    817 				if (right) {
    818 					if (cur->parent->vtable->nearest_child_right)
    819 						new = cur->parent->vtable->nearest_child_right(cur->parent, cur);
    820 				} else {
    821 					if (cur->parent->vtable->nearest_child_below)
    822 						new = cur->parent->vtable->nearest_child_below(cur->parent, cur);
    823 				}
    824 				if (new) {
    825 					cur = new;
    826 					if (cur->vtable->flags & act_flags) {
    827 						changed = 1;
    828 						break;
    829 					}
    830 					while (cur->vtable->nearest_child && (new = nearest_child(cur, old_rect))) {
    831 						cur = new;
    832 						if (cur->vtable->flags & act_flags) {
    833 							changed = 1;
    834 							break;
    835 						}
    836 					}
    837 					if (changed)
    838 						break;
    839 				} else {
    840 					cur = cur->parent;
    841 				}
    842 			}
    843 		}
    844 	} else {
    845 		cur = window->root_widget;
    846 		if (!(cur->vtable->flags & act_flags)) {
    847 			while (cur->vtable->nearest_child && (new = nearest_child(cur, (ltk_rect){0, 0, 0, 0}))) {
    848 				cur = new;
    849 				if (cur->vtable->flags & act_flags)
    850 					break;
    851 			}
    852 		}
    853 		if (!(cur->vtable->flags & act_flags))
    854 			cur = window->root_widget;
    855 	}
    856 	if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
    857 		ltk_window_set_active_widget(window, cur);
    858 		ensure_active_widget_shown(window);
    859 		return 1;
    860 	}
    861 	return 0;
    862 }
    863 
    864 /* FIXME: maybe just set this when active widget changes */
    865 /* -> but would also need to change it when widgets are created/destroyed or parents change */
    866 static void
    867 gen_widget_stack(ltk_widget *bottom) {
    868 	widget_stack_len = 0;
    869 	while (bottom) {
    870 		if (widget_stack_len + 1 > widget_stack_alloc) {
    871 			widget_stack_alloc = ideal_array_size(widget_stack_alloc, widget_stack_len + 1);
    872 			widget_stack = ltk_reallocarray(widget_stack, widget_stack_alloc, sizeof(ltk_widget *));
    873 		}
    874 		widget_stack[widget_stack_len++] = bottom;
    875 		bottom = bottom->parent;
    876 	}
    877 }
    878 
    879 /* FIXME: The focus behavior needs to be rethought. It's currently hard-coded in the vtable for each
    880    widget type, but what if the program using ltk wants to catch keyboard events even if the widget
    881    doesn't do that by default? */
    882 static int
    883 cb_focus_active(ltk_window *window, ltk_key_event *event, int handled) {
    884 	(void)event;
    885 	(void)handled;
    886 	if (window->active_widget && !(window->active_widget->state & LTK_FOCUSED)) {
    887 		/* FIXME: maybe also set widgets above in hierarchy? */
    888 		ltk_widget_state old_state = window->active_widget->state;
    889 		window->active_widget->state |= LTK_FOCUSED;
    890 		ltk_widget_change_state(window->active_widget, old_state);
    891 		return 1;
    892 	}
    893 	return 0;
    894 }
    895 
    896 static int
    897 cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled) {
    898 	(void)event;
    899 	(void)handled;
    900 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED) && (window->active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) {
    901 		ltk_widget_state old_state = window->active_widget->state;
    902 		window->active_widget->state &= ~LTK_FOCUSED;
    903 		ltk_widget_change_state(window->active_widget, old_state);
    904 		return 1;
    905 	}
    906 	return 0;
    907 }
    908 
    909 static int
    910 cb_move_prev(ltk_window *window, ltk_key_event *event, int handled) {
    911 	(void)event;
    912 	(void)handled;
    913 	return prev_child(window);
    914 }
    915 
    916 static int
    917 cb_move_next(ltk_window *window, ltk_key_event *event, int handled) {
    918 	(void)event;
    919 	(void)handled;
    920 	return next_child(window);
    921 }
    922 
    923 static int
    924 cb_move_left(ltk_window *window, ltk_key_event *event, int handled) {
    925 	(void)event;
    926 	(void)handled;
    927 	return left_top_child(window, 1);
    928 }
    929 
    930 static int
    931 cb_move_right(ltk_window *window, ltk_key_event *event, int handled) {
    932 	(void)event;
    933 	(void)handled;
    934 	return right_bottom_child(window, 1);
    935 }
    936 
    937 static int
    938 cb_move_up(ltk_window *window, ltk_key_event *event, int handled) {
    939 	(void)event;
    940 	(void)handled;
    941 	return left_top_child(window, 0);
    942 }
    943 
    944 static int
    945 cb_move_down(ltk_window *window, ltk_key_event *event, int handled) {
    946 	(void)event;
    947 	(void)handled;
    948 	return right_bottom_child(window, 0);
    949 }
    950 
    951 static int
    952 cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled) {
    953 	(void)event;
    954 	(void)handled;
    955 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
    956 		/* FIXME: only set pressed if needs keyboard? */
    957 		ltk_window_set_pressed_widget(window, window->active_widget, 0);
    958 		return 1;
    959 	}
    960 	return 0;
    961 }
    962 
    963 static int
    964 cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled) {
    965 	(void)event;
    966 	(void)handled;
    967 	if (window->pressed_widget) {
    968 		ltk_window_set_pressed_widget(window, NULL, 1);
    969 		return 1;
    970 	}
    971 	return 0;
    972 }
    973 
    974 static int
    975 cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled) {
    976 	(void)event;
    977 	(void)handled;
    978 	if (window->popups_num > 0) {
    979 		ltk_window_unregister_all_popups(window);
    980 		return 1;
    981 	}
    982 	return 0;
    983 }
    984 
    985 /* FIXME: should keyrelease events be ignored if the corresponding keypress event
    986    was consumed for movement? */
    987 /* FIXME: check if there's any weirdness when combining return and mouse press */
    988 /* FIXME: maybe it doesn't really make sense to make e.g. text entry pressed when enter is pressed? */
    989 /* FIXME: implement key binding flag to run before widget handler is called */
    990 void
    991 ltk_window_key_press_event(ltk_window *window, ltk_key_event *event) {
    992 	int handled = 0;
    993 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
    994 		gen_widget_stack(window->active_widget);
    995 		for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
    996 			/* FIXME: send event to socket! */
    997 			if (widget_stack[i]->vtable->key_press && widget_stack[i]->vtable->key_press(widget_stack[i], event)) {
    998 				handled = 1;
    999 				break;
   1000 			}
   1001 		}
   1002 	}
   1003 	if (!keypresses)
   1004 		return;
   1005 	ltk_keypress_binding *b = NULL;
   1006 	for (size_t i = 0; i < ltk_array_length(keypresses); i++) {
   1007 		b = &ltk_array_get(keypresses, i).b;
   1008 		if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
   1009 			continue;
   1010 		} else if (b->text) {
   1011 			if (event->mapped && !strcmp(b->text, event->mapped))
   1012 				handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
   1013 		} else if (b->rawtext) {
   1014 			if (event->text && !strcmp(b->text, event->text))
   1015 				handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
   1016 		} else if (b->sym != LTK_KEY_NONE) {
   1017 			if (event->sym == b->sym)
   1018 				handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
   1019 		}
   1020 	}
   1021 
   1022 }
   1023 
   1024 /* FIXME: need to actually check if any of parent widgets are focused and still pass to them even if bottom widget not focused? */
   1025 void
   1026 ltk_window_key_release_event(ltk_window *window, ltk_key_event *event) {
   1027 	/* FIXME: emit event */
   1028 	int handled = 0;
   1029 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
   1030 		gen_widget_stack(window->active_widget);
   1031 		for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
   1032 			if (widget_stack[i]->vtable->key_release && widget_stack[i]->vtable->key_release(widget_stack[i], event)) {
   1033 				handled = 1;
   1034 				break;
   1035 			}
   1036 		}
   1037 	}
   1038 	if (!keyreleases)
   1039 		return;
   1040 	ltk_keyrelease_binding *b = NULL;
   1041 	for (size_t i = 0; i < ltk_array_length(keyreleases); i++) {
   1042 		b = &ltk_array_get(keyreleases, i).b;
   1043 		if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
   1044 			continue;
   1045 		} else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) {
   1046 			handled |= ltk_array_get(keyreleases, i).cb.callback(window, event, handled);
   1047 		}
   1048 	}
   1049 }
   1050 
   1051 /* FIXME: This is still weird. */
   1052 void
   1053 ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) {
   1054 	ltk_widget *widget = get_hover_popup(window, event->x, event->y);
   1055 	int check_hide = 0;
   1056 	if (!widget) {
   1057 		widget = window->root_widget;
   1058 		check_hide = 1;
   1059 	}
   1060 	if (!widget) {
   1061 		ltk_window_unregister_all_popups(window);
   1062 		return;
   1063 	}
   1064 	int orig_x = event->x, orig_y = event->y;
   1065 	ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
   1066 	/* FIXME: need to add more flags for more fine-grained control
   1067 	   -> also, should the widget still get mouse_press even if state doesn't change? */
   1068 	/* FIXME: doesn't work with e.g. disabled menu entries */
   1069 	if (!(cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
   1070 		ltk_window_unregister_all_popups(window);
   1071 	}
   1072 
   1073 	/* FIXME: this doesn't make much sense if the popups aren't a
   1074 	   hierarchy (right now, they're just menus, so that's always
   1075 	   a hierarchy */
   1076 	/* don't hide popups if they are children of the now pressed widget */
   1077 	if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0])))
   1078 		ltk_window_unregister_all_popups(window);
   1079 
   1080 	/* FIXME: popups don't always have their children geometrically contained within parents,
   1081 	   so this won't work properly in all cases */
   1082 	int first = 1;
   1083 	while (cur_widget) {
   1084 		int handled = 0;
   1085 		ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
   1086 		event->x = local.x;
   1087 		event->y = local.y;
   1088 		if (cur_widget->state != LTK_DISABLED) {
   1089 			/* FIXME: figure out whether this makes sense - currently, all widgets (unless disabled)
   1090 			   get mouse press, but they are only set to pressed if they are activatable */
   1091 			if (queue_mouse_event(cur_widget, event->type, event->x, event->y))
   1092 				handled = 1;
   1093 			else if (cur_widget->vtable->mouse_press)
   1094 				handled = cur_widget->vtable->mouse_press(cur_widget, event);
   1095 			/* set first non-disabled widget to pressed widget */
   1096 			/* FIXME: use config values for all_activatable */
   1097 			if (first && event->button == LTK_BUTTONL && event->type == LTK_BUTTONPRESS_EVENT && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
   1098 				ltk_window_set_pressed_widget(window, cur_widget, 0);
   1099 				first = 0;
   1100 			}
   1101 		}
   1102 		if (!handled)
   1103 			cur_widget = cur_widget->parent;
   1104 		else
   1105 			break;
   1106 	}
   1107 }
   1108 
   1109 void
   1110 ltk_window_mouse_scroll_event(ltk_window *window, ltk_scroll_event *event) {
   1111 	/* FIXME: should it first be sent to pressed widget? */
   1112 	ltk_widget *widget = get_hover_popup(window, event->x, event->y);
   1113 	if (!widget)
   1114 		widget = window->root_widget;
   1115 	if (!widget)
   1116 		return;
   1117 	int orig_x = event->x, orig_y = event->y;
   1118 	ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
   1119 	/* FIXME: same issue with popups like in mouse_press above */
   1120 	while (cur_widget) {
   1121 		int handled = 0;
   1122 		ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
   1123 		event->x = local.x;
   1124 		event->y = local.y;
   1125 		if (cur_widget->state != LTK_DISABLED) {
   1126 			if (queue_scroll_event(cur_widget, event->x, event->y, event->dx, event->dy))
   1127 				handled = 1;
   1128 			else if (cur_widget->vtable->mouse_scroll)
   1129 				handled = cur_widget->vtable->mouse_scroll(cur_widget, event);
   1130 		}
   1131 		if (!handled)
   1132 			cur_widget = cur_widget->parent;
   1133 		else
   1134 			break;
   1135 	}
   1136 }
   1137 
   1138 void
   1139 ltk_window_fake_motion_event(ltk_window *window, int x, int y) {
   1140 	ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = x, .y = y};
   1141 	ltk_window_motion_notify_event(window, &e);
   1142 }
   1143 
   1144 void
   1145 ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event) {
   1146 	ltk_widget *widget = window->pressed_widget;
   1147 	int orig_x = event->x, orig_y = event->y;
   1148 	/* FIXME: why does this only take pressed widget and popups into account? */
   1149 	if (!widget) {
   1150 		widget = get_hover_popup(window, event->x, event->y);
   1151 		widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
   1152 	}
   1153 	/* FIXME: loop up to top of hierarchy if not handled */
   1154 	if (widget && queue_mouse_event(widget, event->type, event->x, event->y)) {
   1155 		/* NOP */
   1156 	} else if (widget && widget->vtable->mouse_release) {
   1157 		widget->vtable->mouse_release(widget, event);
   1158 	}
   1159 	if (event->button == LTK_BUTTONL && event->type == LTK_BUTTONRELEASE_EVENT) {
   1160 		int release = 0;
   1161 		if (window->pressed_widget) {
   1162 			ltk_rect prect = window->pressed_widget->lrect;
   1163 			ltk_point pglob = ltk_widget_pos_to_global(window->pressed_widget, 0, 0);
   1164 			if (ltk_collide_rect((ltk_rect){pglob.x, pglob.y, prect.w, prect.h}, orig_x, orig_y))
   1165 				release = 1;
   1166 		}
   1167 		ltk_window_set_pressed_widget(window, NULL, release);
   1168 		/* send motion notify to widget under pointer */
   1169 		/* FIXME: only when not collide with rect? */
   1170 		ltk_window_fake_motion_event(window, orig_x, orig_y);
   1171 	}
   1172 }
   1173 
   1174 void
   1175 ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) {
   1176 	ltk_widget *widget = get_hover_popup(window, event->x, event->y);
   1177 	int orig_x = event->x, orig_y = event->y;
   1178 	if (!widget) {
   1179 		widget = window->pressed_widget;
   1180 		if (widget) {
   1181 			ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
   1182 			event->x = local.x;
   1183 			event->y = local.y;
   1184 			if (widget->vtable->motion_notify)
   1185 				widget->vtable->motion_notify(widget, event);
   1186 			return;
   1187 		}
   1188 		widget = window->root_widget;
   1189 	}
   1190 	if (!widget)
   1191 		return;
   1192 	ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
   1193 	if (!ltk_collide_rect((ltk_rect){0, 0, widget->lrect.w, widget->lrect.h}, local.x, local.y)) {
   1194 		ltk_window_set_hover_widget(widget->window, NULL, event);
   1195 		return;
   1196 	}
   1197 	ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
   1198 	int first = 1;
   1199 	while (cur_widget) {
   1200 		int handled = 0;
   1201 		ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
   1202 		event->x = local.x;
   1203 		event->y = local.y;
   1204 		if (cur_widget->state != LTK_DISABLED) {
   1205 			if (queue_mouse_event(cur_widget, LTK_MOTION_EVENT, event->x, event->y))
   1206 				handled = 1;
   1207 			else if (cur_widget->vtable->motion_notify)
   1208 				handled = cur_widget->vtable->motion_notify(cur_widget, event);
   1209 			/* set first non-disabled widget to hover widget */
   1210 			/* FIXME: should enter/leave event be sent to parent
   1211 			   when moving from/to widget nested in parent? */
   1212 			/* FIXME: use config values for all_activatable */
   1213 			if (first && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
   1214 				event->x = orig_x;
   1215 				event->y = orig_y;
   1216 				ltk_window_set_hover_widget(window, cur_widget, event);
   1217 				first = 0;
   1218 			}
   1219 		}
   1220 		if (!handled)
   1221 			cur_widget = cur_widget->parent;
   1222 		else
   1223 			break;
   1224 	}
   1225 	if (first) {
   1226 		event->x = orig_x;
   1227 		event->y = orig_y;
   1228 		ltk_window_set_hover_widget(window, NULL, event);
   1229 	}
   1230 }
   1231 
   1232 int
   1233 ltk_widget_id_free(const char *id) {
   1234 	khint_t k;
   1235 	k = kh_get(widget, widget_hash, id);
   1236 	if (k != kh_end(widget_hash)) {
   1237 		return 0;
   1238 	}
   1239 	return 1;
   1240 }
   1241 
   1242 ltk_widget *
   1243 ltk_get_widget(const char *id, ltk_widget_type type, ltk_error *err) {
   1244 	khint_t k;
   1245 	ltk_widget *widget;
   1246 	k = kh_get(widget, widget_hash, id);
   1247 	if (k == kh_end(widget_hash)) {
   1248 		err->type = ERR_INVALID_WIDGET_ID;
   1249 		return NULL;
   1250 	}
   1251 	widget = kh_value(widget_hash, k);
   1252 	if (type != LTK_WIDGET_ANY && widget->vtable->type != type) {
   1253 		err->type = ERR_INVALID_WIDGET_TYPE;
   1254 		return NULL;
   1255 	}
   1256 	return widget;
   1257 }
   1258 
   1259 void
   1260 ltk_set_widget(ltk_widget *widget, const char *id) {
   1261 	int ret;
   1262 	khint_t k;
   1263 	/* FIXME: make sure no widget is overwritten here */
   1264 	char *tmp = ltk_strdup(id);
   1265 	k = kh_put(widget, widget_hash, tmp, &ret);
   1266 	kh_value(widget_hash, k) = widget;
   1267 }
   1268 
   1269 void
   1270 ltk_remove_widget(const char *id) {
   1271 	if (hash_locked)
   1272 		return;
   1273 	khint_t k;
   1274 	k = kh_get(widget, widget_hash, id);
   1275 	if (k != kh_end(widget_hash)) {
   1276 		ltk_free((char *)kh_key(widget_hash, k));
   1277 		kh_del(widget, widget_hash, k);
   1278 	}
   1279 }
   1280 
   1281 int
   1282 ltk_widget_destroy(ltk_widget *widget, int shallow, ltk_error *err) {
   1283 	/* widget->parent->remove_child should never be NULL because of the fact that
   1284 	   the widget is set as parent, but let's just check anyways... */
   1285 	int invalid = 0;
   1286 	if (widget->parent && widget->parent->vtable->remove_child) {
   1287 		invalid = widget->parent->vtable->remove_child(
   1288 		    widget, widget->parent, err
   1289 		);
   1290 	}
   1291 	ltk_remove_widget(widget->id);
   1292 	ltk_free(widget->id);
   1293 	widget->id = NULL;
   1294 	ltk_free(widget->event_masks);
   1295 	widget->event_masks = NULL;
   1296 	widget->vtable->destroy(widget, shallow);
   1297 
   1298 	return invalid;
   1299 }
   1300 
   1301 int
   1302 ltk_widget_destroy_cmd(
   1303     ltk_window *window,
   1304     ltk_cmd_token *tokens,
   1305     size_t num_tokens,
   1306     ltk_error *err) {
   1307 	(void)window;
   1308 	int shallow = 1;
   1309 	if (num_tokens != 2 && num_tokens != 3) {
   1310 		err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
   1311 		err->arg = -1;
   1312 		return 1;
   1313 	}
   1314 	if (tokens[1].contains_nul) {
   1315 		err->type = ERR_INVALID_ARGUMENT;
   1316 		err->arg = 1;
   1317 		return 1;
   1318 	} else if (num_tokens == 3 && tokens[2].contains_nul) {
   1319 		err->type = ERR_INVALID_ARGUMENT;
   1320 		err->arg = 2;
   1321 		return 1;
   1322 	}
   1323 	if (num_tokens == 3) {
   1324 		if (strcmp(tokens[2].text, "deep") == 0) {
   1325 			shallow = 0;
   1326 		} else if (strcmp(tokens[2].text, "shallow") == 0) {
   1327 			shallow = 1;
   1328 		} else {
   1329 			err->type = ERR_INVALID_ARGUMENT;
   1330 			err->arg = 2;
   1331 			return 1;
   1332 		}
   1333 	}
   1334 	ltk_widget *widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err);
   1335 	if (!widget) {
   1336 		err->arg = 1;
   1337 		return 1;
   1338 	}
   1339 	if (ltk_widget_destroy(widget, shallow, err)) {
   1340 		err->arg = -1;
   1341 		return 1;
   1342 	}
   1343 	return 0;
   1344 }