ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

widget.c (18095B)


      1 /*
      2  * Copyright (c) 2021-2026 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 <string.h>
     18 
     19 #include "khash.h"
     20 
     21 #include "err.h"
     22 #include "ltkd.h"
     23 #include "widget.h"
     24 #include "proto_types.h"
     25 
     26 #include <ltk/ltk.h>
     27 #include <ltk/event.h>
     28 #include <ltk/eventdefs.h>
     29 #include <ltk/memory.h>
     30 #include <ltk/rect.h>
     31 #include <ltk/util.h>
     32 
     33 KHASH_MAP_INIT_STR(widget, ltkd_widget *)
     34 static khash_t(widget) *widget_hash = NULL;
     35 /* Hack to make ltkd_destroy_widget_hash work */
     36 /* FIXME: any better way to do this? */
     37 static int hash_locked = 0;
     38 
     39 static int ltkd_widget_button_event(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg);
     40 static int ltkd_widget_motion_notify(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg);
     41 static int ltkd_widget_scroll_event(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg);
     42 static int ltkd_widget_resize(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg);
     43 static int ltkd_widget_change_state(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg);
     44 
     45 /* FIXME: generic event handling functions that take the actual uint32_t event type, etc. */
     46 int
     47 ltkd_widget_queue_specific_event(ltkd_widget *widget, const char *type, uint32_t mask, const char *data) {
     48 	ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid);
     49 	for (size_t i = 0; i < widget->masks_num; i++) {
     50 		if (widget->event_masks[i].lwmask & mask) {
     51 			ltkd_queue_sock_write_fmt(
     52 			    widget->event_masks[i].client,
     53 			    "eventl %s %s %s\n", widget->id, type, data
     54 			);
     55 			if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client))
     56 				return 1;
     57 		} else if (widget->event_masks[i].wmask & mask) {
     58 			ltkd_queue_sock_write_fmt(
     59 			    widget->event_masks[i].client,
     60 			    "event %s %s %s\n", widget->id, type, data
     61 			);
     62 		}
     63 	}
     64 	return 0;
     65 }
     66 
     67 static int
     68 ltkd_widget_resize(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg) {
     69 	(void)widget_unused;
     70 	(void)args;
     71 	ltkd_widget *widget = LTK_CAST_ARG_VOIDP(arg);
     72 	ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid);
     73 	for (size_t i = 0; i < widget->masks_num; i++) {
     74 		if (widget->event_masks[i].lmask & LTKD_PEVENTMASK_RESIZE) {
     75 			ltkd_queue_sock_write_fmt(
     76 			    widget->event_masks[i].client,
     77 			    "eventl %s widget configure %d %d %d %d\n",
     78 			    widget->id, ltkwid->lrect.x, ltkwid->lrect.y,
     79 			    ltkwid->lrect.w, ltkwid->lrect.h
     80 			);
     81 			if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client))
     82 				return 1;
     83 		} else if (widget->event_masks[i].mask & LTKD_PEVENTMASK_RESIZE) {
     84 			ltkd_queue_sock_write_fmt(
     85 			    widget->event_masks[i].client,
     86 			    "event %s widget configure %d %d %d %d\n",
     87 			    widget->id, ltkwid->lrect.x, ltkwid->lrect.y,
     88 			    ltkwid->lrect.w, ltkwid->lrect.h
     89 			);
     90 		}
     91 	}
     92 	return 0;
     93 }
     94 
     95 static int
     96 ltkd_widget_change_state(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg) {
     97 	(void)widget_unused;
     98 	(void)args;
     99 	ltkd_widget *widget = LTK_CAST_ARG_VOIDP(arg);
    100 	ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid);
    101 	/* FIXME: give old and new state in event */
    102 	for (size_t i = 0; i < widget->masks_num; i++) {
    103 		if (widget->event_masks[i].lmask & LTKD_PEVENTMASK_STATECHANGE) {
    104 			ltkd_queue_sock_write_fmt(
    105 			    widget->event_masks[i].client,
    106 			    "eventl %s widget statechange\n", widget->id
    107 			);
    108 			if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client))
    109 				return 1;
    110 		} else if (widget->event_masks[i].mask & LTKD_PEVENTMASK_STATECHANGE) {
    111 			ltkd_queue_sock_write_fmt(
    112 			    widget->event_masks[i].client,
    113 			    "event %s widget statechange\n", widget->id
    114 			);
    115 		}
    116 	}
    117 	return 0;
    118 }
    119 
    120 static void
    121 ltkd_destroy_widget_hash(void) {
    122 	hash_locked = 1;
    123 	khint_t k;
    124 	ltkd_widget *ptr;
    125 	for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
    126 		if (kh_exist(widget_hash, k)) {
    127 			ptr = kh_value(widget_hash, k);
    128 			ltk_free((char *)kh_key(widget_hash, k));
    129 			ltkd_widget_destroy(ptr, 1);
    130 		}
    131 	}
    132 	kh_destroy(widget, widget_hash);
    133 	widget_hash = NULL;
    134 	hash_locked = 0;
    135 }
    136 
    137 void
    138 ltkd_widgets_cleanup(void) {
    139 	if (widget_hash)
    140 		ltkd_destroy_widget_hash();
    141 }
    142 
    143 static client_event_mask *
    144 get_mask_struct(ltkd_widget *widget, int client) {
    145 	for (size_t i = 0; i < widget->masks_num; i++) {
    146 		if (widget->event_masks[i].client == client)
    147 			return &widget->event_masks[i];
    148 	}
    149 	widget->masks_alloc = ideal_array_size(widget->masks_alloc, widget->masks_num + 1);
    150 	widget->event_masks = ltk_reallocarray(widget->event_masks, widget->masks_alloc, sizeof(client_event_mask));
    151 	client_event_mask *m = &widget->event_masks[widget->masks_num];
    152 	widget->masks_num++;
    153 	m->client = client;
    154 	m->mask = m->lmask = m->wmask = m->lwmask = 0;
    155 	return m;
    156 }
    157 
    158 static ltkd_event_handler widget_handlers[] = {
    159 	{&ltkd_widget_button_event, LTK_WIDGET_SIGNAL_MOUSE_PRESS},
    160 	{&ltkd_widget_button_event, LTK_WIDGET_SIGNAL_MOUSE_RELEASE},
    161 	{&ltkd_widget_motion_notify, LTK_WIDGET_SIGNAL_MOTION_NOTIFY},
    162 	{&ltkd_widget_scroll_event, LTK_WIDGET_SIGNAL_MOUSE_SCROLL},
    163 	{NULL, LTK_WIDGET_SIGNAL_KEY_PRESS}, /* FIXME: add key press here */
    164 	{NULL, LTK_WIDGET_SIGNAL_KEY_RELEASE},
    165 	{&ltkd_widget_resize, LTK_WIDGET_SIGNAL_RESIZE},
    166 	{&ltkd_widget_change_state, LTK_WIDGET_SIGNAL_CHANGE_STATE},
    167 };
    168 
    169 static uint32_t
    170 get_widget_mask(ltkd_widget *widget) {
    171 	uint32_t cur_mask = 0;
    172 	for (size_t i = 0; i < widget->masks_num; i++) {
    173 		cur_mask |= widget->event_masks[i].mask;
    174 		cur_mask |= widget->event_masks[i].lmask;
    175 	}
    176 	return cur_mask;
    177 }
    178 
    179 static uint32_t
    180 get_widget_special_mask(ltkd_widget *widget) {
    181 	uint32_t cur_mask = 0;
    182 	for (size_t i = 0; i < widget->masks_num; i++) {
    183 		cur_mask |= widget->event_masks[i].wmask;
    184 		cur_mask |= widget->event_masks[i].lwmask;
    185 	}
    186 	return cur_mask;
    187 }
    188 
    189 static void
    190 set_event_handlers(ltkd_widget *widget, uint32_t before, uint32_t after, ltkd_event_handler *handlers, size_t num_handlers) {
    191 	for (size_t i = 0; i < num_handlers; i++) {
    192 		if (!(before & 1) && (after & 1)) {
    193 			if (handlers[i].callback) {
    194 				ltk_widget_register_signal_handler(
    195 					widget->ltkid, handlers[i].type,
    196 					handlers[i].callback, LTK_MAKE_ARG_VOIDP(widget)
    197 				);
    198 			}
    199 		} else if ((before & 1) && !(after & 1)) {
    200 			ltk_widget_remove_signal_handler_by_callback(widget->ltkid, handlers[i].callback);
    201 		}
    202 		before >>= 1;
    203 		after >>= 1;
    204 	}
    205 }
    206 
    207 static void
    208 ltkd_widget_set_event_handlers(ltkd_widget *widget, uint32_t *set_mask, uint32_t new_mask) {
    209 	uint32_t before = get_widget_mask(widget);
    210 	*set_mask = new_mask;
    211 	uint32_t after = get_widget_mask(widget);
    212 	set_event_handlers(widget, before, after, widget_handlers, LENGTH(widget_handlers));
    213 }
    214 
    215 static void
    216 ltkd_widget_set_special_event_handlers(ltkd_widget *widget, uint32_t *set_mask, uint32_t new_mask) {
    217 	uint32_t before = get_widget_special_mask(widget);
    218 	*set_mask = new_mask;
    219 	uint32_t after = get_widget_special_mask(widget);
    220 	set_event_handlers(widget, before, after, widget->event_handlers, widget->num_event_handlers);
    221 }
    222 
    223 void
    224 ltkd_widget_set_event_mask(ltkd_widget *widget, int client, uint32_t mask) {
    225 	client_event_mask *m = get_mask_struct(widget, client);
    226 	ltkd_widget_set_event_handlers(widget, &m->mask, mask);
    227 }
    228 
    229 void
    230 ltkd_widget_set_event_lmask(ltkd_widget *widget, int client, uint32_t mask) {
    231 	client_event_mask *m = get_mask_struct(widget, client);
    232 	ltkd_widget_set_event_handlers(widget, &m->lmask, mask);
    233 }
    234 
    235 void
    236 ltkd_widget_set_event_wmask(ltkd_widget *widget, int client, uint32_t mask) {
    237 	client_event_mask *m = get_mask_struct(widget, client);
    238 	ltkd_widget_set_special_event_handlers(widget, &m->wmask, mask);
    239 }
    240 
    241 void
    242 ltkd_widget_set_event_lwmask(ltkd_widget *widget, int client, uint32_t mask) {
    243 	client_event_mask *m = get_mask_struct(widget, client);
    244 	ltkd_widget_set_special_event_handlers(widget, &m->lwmask, mask);
    245 }
    246 
    247 void
    248 ltkd_widget_add_to_event_mask(ltkd_widget *widget, int client, uint32_t mask) {
    249 	client_event_mask *m = get_mask_struct(widget, client);
    250 	ltkd_widget_set_event_handlers(widget, &m->mask, m->mask | mask);
    251 }
    252 
    253 void
    254 ltkd_widget_add_to_event_lmask(ltkd_widget *widget, int client, uint32_t mask) {
    255 	client_event_mask *m = get_mask_struct(widget, client);
    256 	ltkd_widget_set_event_handlers(widget, &m->lmask, m->lmask | mask);
    257 }
    258 
    259 void
    260 ltkd_widget_add_to_event_wmask(ltkd_widget *widget, int client, uint32_t mask) {
    261 	client_event_mask *m = get_mask_struct(widget, client);
    262 	ltkd_widget_set_special_event_handlers(widget, &m->wmask, m->wmask | mask);
    263 }
    264 
    265 void
    266 ltkd_widget_add_to_event_lwmask(ltkd_widget *widget, int client, uint32_t mask) {
    267 	client_event_mask *m = get_mask_struct(widget, client);
    268 	ltkd_widget_set_special_event_handlers(widget, &m->lwmask, m->lwmask | mask);
    269 }
    270 
    271 void
    272 ltkd_widget_remove_from_event_mask(ltkd_widget *widget, int client, uint32_t mask) {
    273 	client_event_mask *m = get_mask_struct(widget, client);
    274 	ltkd_widget_set_event_handlers(widget, &m->mask, m->mask & ~mask);
    275 }
    276 
    277 void
    278 ltkd_widget_remove_from_event_lmask(ltkd_widget *widget, int client, uint32_t mask) {
    279 	client_event_mask *m = get_mask_struct(widget, client);
    280 	ltkd_widget_set_event_handlers(widget, &m->lmask, m->lmask & ~mask);
    281 }
    282 
    283 void
    284 ltkd_widget_remove_from_event_wmask(ltkd_widget *widget, int client, uint32_t mask) {
    285 	client_event_mask *m = get_mask_struct(widget, client);
    286 	ltkd_widget_set_special_event_handlers(widget, &m->wmask, m->wmask & ~mask);
    287 }
    288 
    289 void
    290 ltkd_widget_remove_from_event_lwmask(ltkd_widget *widget, int client, uint32_t mask) {
    291 	client_event_mask *m = get_mask_struct(widget, client);
    292 	ltkd_widget_set_special_event_handlers(widget, &m->lwmask, m->lwmask & ~mask);
    293 }
    294 
    295 /* FIXME: any way to optimize the whole event mask handling a bit? */
    296 void
    297 ltkd_widget_remove_client(int client) {
    298 	khint_t k;
    299 	ltkd_widget *ptr;
    300 	for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
    301 		if (kh_exist(widget_hash, k)) {
    302 			ptr = kh_value(widget_hash, k);
    303 			for (size_t i = 0; i < ptr->masks_num; i++) {
    304 				if (ptr->event_masks[i].client == client) {
    305 					uint32_t before = get_widget_mask(ptr);
    306 					uint32_t befores = get_widget_special_mask(ptr);
    307 					memmove(ptr->event_masks + i, ptr->event_masks + i + 1, ptr->masks_num - i - 1);
    308 					ptr->masks_num--;
    309 					/* FIXME: maybe reset to NULL in that case? */
    310 					if (ptr->masks_num > 0) {
    311 						size_t sz = ideal_array_size(ptr->masks_alloc, ptr->masks_num);
    312 						if (sz != ptr->masks_alloc) {
    313 							ptr->masks_alloc = sz;
    314 							ptr->event_masks = ltk_reallocarray(ptr->event_masks, sz, sizeof(client_event_mask));
    315 						}
    316 					}
    317 					uint32_t after = get_widget_mask(ptr);
    318 					uint32_t afters = get_widget_special_mask(ptr);
    319 					set_event_handlers(ptr, before, after, widget_handlers, LENGTH(widget_handlers));
    320 					set_event_handlers(ptr, befores, afters, ptr->event_handlers, ptr->num_event_handlers);
    321 					break;
    322 				}
    323 			}
    324 		}
    325 	}
    326 }
    327 
    328 void
    329 ltkd_widgets_init() {
    330 	widget_hash = kh_init(widget);
    331 	if (!widget_hash) ltkd_fatal_errno("Unable to initialize widget hash table.\n");
    332 }
    333 
    334 /* FIXME: fix global and local coordinates! */
    335 static int
    336 queue_mouse_event(ltkd_widget *widget, ltk_event_type type, int x, int y) {
    337 	uint32_t mask;
    338 	char *typename;
    339 	switch (type) {
    340 	case LTK_MOTION_EVENT:
    341 		mask = LTKD_PEVENTMASK_MOUSEMOTION;
    342 		typename = "mousemotion";
    343 		break;
    344 	case LTK_2BUTTONPRESS_EVENT:
    345 		mask = LTKD_PEVENTMASK_MOUSEPRESS;
    346 		typename = "2mousepress";
    347 		break;
    348 	case LTK_3BUTTONPRESS_EVENT:
    349 		mask = LTKD_PEVENTMASK_MOUSEPRESS;
    350 		typename = "3mousepress";
    351 		break;
    352 	case LTK_BUTTONRELEASE_EVENT:
    353 		mask = LTKD_PEVENTMASK_MOUSERELEASE;
    354 		typename = "mouserelease";
    355 		break;
    356 	case LTK_2BUTTONRELEASE_EVENT:
    357 		mask = LTKD_PEVENTMASK_MOUSERELEASE;
    358 		typename = "2mouserelease";
    359 		break;
    360 	case LTK_3BUTTONRELEASE_EVENT:
    361 		mask = LTKD_PEVENTMASK_MOUSERELEASE;
    362 		typename = "3mouserelease";
    363 		break;
    364 	case LTK_BUTTONPRESS_EVENT:
    365 	default:
    366 		mask = LTKD_PEVENTMASK_MOUSEPRESS;
    367 		typename = "mousepress";
    368 		break;
    369 	}
    370 	ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid);
    371 	for (size_t i = 0; i < widget->masks_num; i++) {
    372 		if (widget->event_masks[i].lmask & mask) {
    373 			ltkd_queue_sock_write_fmt(
    374 			    widget->event_masks[i].client,
    375 			    "eventl %s widget %s %d %d %d %d\n",
    376 			    widget->id, typename, x, y, x, y
    377 			    /* x - widget->rect.x, y - widget->rect.y */
    378 			);
    379 			if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client))
    380 				return 1;
    381 		} else if (widget->event_masks[i].mask & mask) {
    382 			ltkd_queue_sock_write_fmt(
    383 			    widget->event_masks[i].client,
    384 			    "event %s widget %s %d %d %d %d\n",
    385 			    widget->id, typename, x, y, x, y
    386 			    /* x - widget->rect.x, y - widget->rect.y */
    387 			);
    388 		}
    389 	}
    390 	return 0;
    391 }
    392 
    393 static int
    394 ltkd_widget_button_event(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg) {
    395 	(void)widget_unused;
    396 	ltkd_widget *widget = LTK_CAST_ARG_VOIDP(arg);
    397 	ltk_button_event *event = LTK_GET_ARG_BUTTON_EVENT(args, 0);
    398 	return queue_mouse_event(widget, event->type, event->x, event->y);
    399 }
    400 
    401 static int
    402 ltkd_widget_motion_notify(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg) {
    403 	(void)widget_unused;
    404 	ltkd_widget *widget = LTK_CAST_ARG_VOIDP(arg);
    405 	ltk_motion_event *event = LTK_GET_ARG_MOTION_EVENT(args, 0);
    406 	return queue_mouse_event(widget, event->type, event->x, event->y);
    407 }
    408 
    409 /* FIXME: global/local coords (like above) */
    410 static int
    411 ltkd_widget_scroll_event(ltk_widget *widget_unused, ltk_callback_arglist args, ltk_callback_arg arg) {
    412 	(void)widget_unused;
    413 	ltk_scroll_event *event = LTK_GET_ARG_SCROLL_EVENT(args, 0);
    414 	ltkd_widget *widget = LTK_CAST_ARG_VOIDP(arg);
    415 	ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid);
    416 	uint32_t mask = LTKD_PEVENTMASK_MOUSESCROLL;
    417 	for (size_t i = 0; i < widget->masks_num; i++) {
    418 		if (widget->event_masks[i].lmask & mask) {
    419 			ltkd_queue_sock_write_fmt(
    420 			    widget->event_masks[i].client,
    421 			    "eventl %s widget %s %d %d %d %d %d %d\n",
    422 			    widget->id, "mousescroll", event->x, event->y, event->x, event->y, event->dx, event->dy
    423 			    /* x - widget->widget->rect.x, y - widget->widget->rect.y */
    424 			);
    425 			if (ltkd_handle_lock_client(ltkwid->window, widget->event_masks[i].client))
    426 				return 1;
    427 		} else if (widget->event_masks[i].mask & mask) {
    428 			ltkd_queue_sock_write_fmt(
    429 			    widget->event_masks[i].client,
    430 			    "event %s widget %s %d %d %d %d %d %d\n",
    431 			    widget->id, "mousescroll", event->x, event->y, event->x, event->y, event->dx, event->dy
    432 			    /* x - widget->widget->rect.x, y - widget->widget->rect.y */
    433 			);
    434 		}
    435 	}
    436 	return 0;
    437 }
    438 
    439 static int
    440 ltkd_widget_id_free(const char *id) {
    441 	khint_t k;
    442 	k = kh_get(widget, widget_hash, id);
    443 	if (k != kh_end(widget_hash)) {
    444 		return 0;
    445 	}
    446 	return 1;
    447 }
    448 
    449 ltkd_widget *
    450 ltkd_get_widget(const char *id, ltk_widget_type type, ltkd_error *err) {
    451 	khint_t k;
    452 	ltkd_widget *widget;
    453 	k = kh_get(widget, widget_hash, id);
    454 	if (k == kh_end(widget_hash)) {
    455 		err->type = ERR_INVALID_WIDGET_ID;
    456 		return NULL;
    457 	}
    458 	widget = kh_value(widget_hash, k);
    459 	ltk_widget *ltkwid = ltk_get_widget_from_id(widget->ltkid);
    460 	if (type != LTK_WIDGET_UNKNOWN && LTK_WIDGET_TYPE(ltkwid) != type) {
    461 		err->type = ERR_INVALID_WIDGET_TYPE;
    462 		return NULL;
    463 	}
    464 	return widget;
    465 }
    466 
    467 static void
    468 ltkd_set_widget(ltkd_widget *widget, const char *id) {
    469 	int ret;
    470 	khint_t k;
    471 	/* FIXME: make sure no widget is overwritten here */
    472 	char *tmp = ltk_strdup(id);
    473 	k = kh_put(widget, widget_hash, tmp, &ret);
    474 	kh_value(widget_hash, k) = widget;
    475 }
    476 
    477 static void
    478 ltkd_remove_widget(const char *id) {
    479 	if (hash_locked)
    480 		return;
    481 	khint_t k;
    482 	k = kh_get(widget, widget_hash, id);
    483 	if (k != kh_end(widget_hash)) {
    484 		ltk_free((char *)kh_key(widget_hash, k));
    485 		kh_del(widget, widget_hash, k);
    486 	}
    487 }
    488 
    489 ltkd_widget *
    490 ltkd_widget_create(
    491     ltk_widget_id ltkid, const char *id,
    492     ltkd_event_handler *event_handlers, size_t num_event_handlers, ltkd_error *err){
    493 	if (!ltkd_widget_id_free(id)) {
    494 		err->type = ERR_WIDGET_ID_IN_USE;
    495 		return NULL;
    496 	}
    497 	ltkd_widget *w = ltk_malloc(sizeof(ltkd_widget));
    498 	w->ltkid = ltkid;
    499 	w->id = ltk_strdup(id);
    500 	w->event_masks = NULL;
    501 	w->masks_num = w->masks_alloc = 0;
    502 	w->event_handlers = event_handlers;
    503 	w->num_event_handlers = num_event_handlers;
    504 	ltkd_set_widget(w, id);
    505 	return w;
    506 }
    507 
    508 void
    509 ltkd_widget_destroy(ltkd_widget *widget, int shallow) {
    510 	ltkd_remove_widget(widget->id);
    511 	ltk_free(widget->id);
    512 	widget->id = NULL;
    513 	ltk_free(widget->event_masks);
    514 	widget->event_masks = NULL;
    515 	ltk_widget_id_destroy(widget->ltkid, shallow);
    516 	ltk_free(widget);
    517 }
    518 
    519 int
    520 ltkd_widget_destroy_cmd(
    521     ltk_widget_id windowid,
    522     ltkd_cmd_token *tokens,
    523     size_t num_tokens,
    524     ltkd_error *err) {
    525 	(void)windowid;
    526 	int shallow = 1;
    527 	if (num_tokens != 2 && num_tokens != 3) {
    528 		err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
    529 		err->arg = -1;
    530 		return 1;
    531 	}
    532 	if (tokens[1].contains_nul) {
    533 		err->type = ERR_INVALID_ARGUMENT;
    534 		err->arg = 1;
    535 		return 1;
    536 	} else if (num_tokens == 3 && tokens[2].contains_nul) {
    537 		err->type = ERR_INVALID_ARGUMENT;
    538 		err->arg = 2;
    539 		return 1;
    540 	}
    541 	if (num_tokens == 3) {
    542 		if (strcmp(tokens[2].text, "deep") == 0) {
    543 			shallow = 0;
    544 		} else if (strcmp(tokens[2].text, "shallow") == 0) {
    545 			shallow = 1;
    546 		} else {
    547 			err->type = ERR_INVALID_ARGUMENT;
    548 			err->arg = 2;
    549 			return 1;
    550 		}
    551 	}
    552 	ltkd_widget *widget = ltkd_get_widget(tokens[1].text, LTK_WIDGET_UNKNOWN, err);
    553 	if (!widget) {
    554 		err->arg = 1;
    555 		return 1;
    556 	}
    557 	ltkd_widget_destroy(widget, shallow);
    558 	return 0;
    559 }