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 (17833B)


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