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

ltk.c (17932B)


      1 /*
      2  * Copyright (c) 2016-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 <locale.h>
     18 #include <pwd.h>
     19 #include <stdint.h>
     20 #include <stdlib.h>
     21 #include <string.h>
     22 #include <time.h>
     23 #include <unistd.h>
     24 
     25 #include <sys/wait.h>
     26 
     27 #include "ltk.h"
     28 #include "array.h"
     29 #include "button.h"
     30 #include "config.h"
     31 #include "entry.h"
     32 #include "event.h"
     33 #include "eventdefs.h"
     34 #include "graphics.h"
     35 #include "image.h"
     36 #include "label.h"
     37 #include "macros.h"
     38 #include "memory.h"
     39 #include "menu.h"
     40 #include "rect.h"
     41 #include "scrollbar.h"
     42 #include "text.h"
     43 #include "util.h"
     44 #include "widget.h"
     45 #include "widget_internal.h"
     46 
     47 typedef struct {
     48 	ltk_widget *caller;
     49 	char *infile;
     50 	char *outfile;
     51 	int pid;
     52 } ltk_cmdinfo;
     53 
     54 LTK_ARRAY_INIT_DECL_STATIC(window, ltk_window *)
     55 LTK_ARRAY_INIT_IMPL_STATIC(window, ltk_window *)
     56 LTK_ARRAY_INIT_DECL_STATIC(rwindow, ltk_renderwindow *)
     57 LTK_ARRAY_INIT_IMPL_STATIC(rwindow, ltk_renderwindow *)
     58 LTK_ARRAY_INIT_DECL_STATIC(cmdinfo, ltk_cmdinfo)
     59 LTK_ARRAY_INIT_IMPL_STATIC(cmdinfo, ltk_cmdinfo)
     60 
     61 static struct {
     62 	ltk_renderdata *renderdata;
     63 	ltk_text_context *text_context;
     64 	ltk_clipboard *clipboard;
     65 	ltk_array(window) *windows;
     66 	ltk_array(rwindow) *rwindows;
     67 	/* PID of external command called e.g. by text widget to edit text.
     68 	   ON exit, cmd_caller->vtable->cmd_return is called with the text
     69 	   the external command wrote to a file. */
     70 	/*FIXME: this needs to be checked whenever a widget is destroyed!*/
     71 	ltk_array(cmdinfo) *cmds;
     72 	size_t cur_kbd;
     73 } shared_data = {NULL, NULL, NULL, NULL, NULL, NULL, 0};
     74 
     75 typedef struct {
     76 	void (*callback)(ltk_callback_arg data);
     77 	ltk_callback_arg data;
     78 	struct timespec repeat;
     79 	struct timespec remaining;
     80 	int id;
     81 } ltk_timer;
     82 
     83 static ltk_timer *timers = NULL;
     84 static size_t timers_num = 0;
     85 static size_t timers_alloc = 0;
     86 
     87 static void ltk_handle_event(ltk_event *event);
     88 
     89 static short running = 1;
     90 
     91 typedef struct {
     92 	char *name;
     93 	void (*cleanup)(void);
     94 } ltk_widget_funcs;
     95 
     96 /* FIXME: I guess the names aren't needed anymore here, but who
     97    knows if I'll need them again sometime... */
     98 static ltk_widget_funcs widget_funcs[] = {
     99 	{
    100 		.name = "entry",
    101 		.cleanup = &ltk_entry_cleanup,
    102 	},
    103 	{
    104 		.name = "combobox",
    105 		.cleanup = &ltk_combobox_cleanup,
    106 	},
    107 	{
    108 		/* Handler for window theme. */
    109 		.name = "window",
    110 		.cleanup = &ltk_window_cleanup,
    111 	}
    112 };
    113 
    114 ltk_renderdata *
    115 ltk_get_renderer(void) {
    116 	/* FIXME: check if initialized? */
    117 	return shared_data.renderdata;
    118 }
    119 
    120 int
    121 ltk_init(void) {
    122 	/* FIXME: should ltk set this? probably not */
    123 	setlocale(LC_CTYPE, "");
    124 	char *ltk_dir = ltk_setup_directory("LTKDIR", ".ltk", 0);
    125 	if (!ltk_dir)
    126 		ltk_fatal_errno("Unable to setup ltk directory.\n");
    127 	shared_data.cur_kbd = 0;
    128 
    129 	shared_data.renderdata = ltk_renderer_create();
    130 	if (!shared_data.renderdata)
    131 		return 1; /* FIXME: clean up */
    132 
    133 	/* FIXME: search different directories for config */
    134 	/* FIXME: don't print error if config file doesn't exist */
    135 	char *config_path = ltk_strcat_useful(ltk_dir, "/ltk.cfg");
    136 	ltk_free0(ltk_dir);
    137 	char *errstr = NULL;
    138 	if (ltk_config_parsefile(shared_data.renderdata, config_path, &errstr)) {
    139 		if (errstr) {
    140 			ltk_warn("Unable to load config: %s\n", errstr);
    141 			ltk_free0(errstr);
    142 		}
    143 		if (ltk_config_load_default(shared_data.renderdata, &errstr)) {
    144 			/* FIXME: I guess errstr isn't freed here, but whatever */
    145 			/* FIXME: return error instead of dying */
    146 			ltk_fatal("Unable to load default config: %s\n", errstr);
    147 		}
    148 	}
    149 	ltk_free0(config_path);
    150 
    151 	ltk_events_init(shared_data.renderdata);
    152 	shared_data.text_context = ltk_text_context_create(shared_data.renderdata);
    153 	shared_data.clipboard = ltk_clipboard_create(shared_data.renderdata);
    154 	/* FIXME: configure cache size; check for overflow */
    155 	ltk_image_init(shared_data.renderdata, 1024 * 1024 * 4);
    156 	shared_data.windows = ltk_array_create(window, 1);
    157 	shared_data.rwindows = ltk_array_create(rwindow, 1);
    158 	shared_data.cmds = ltk_array_create(cmdinfo, 1);
    159 	return 0; /* FIXME: or maybe 1? */
    160 }
    161 
    162 static struct {
    163 	struct timespec last;
    164 	struct timespec lasttimer;
    165 } mainloop_data;
    166 
    167 void
    168 ltk_mainloop_init(void) {
    169 	clock_gettime(CLOCK_MONOTONIC, &mainloop_data.last);
    170 	mainloop_data.lasttimer = mainloop_data.last;
    171 
    172 	/* initialize keyboard mapping */
    173 	ltk_event event;
    174 	ltk_generate_keyboard_event(shared_data.renderdata, &event);
    175 	ltk_handle_event(&event);
    176 }
    177 
    178 /* FIXME: maybe split this up into multiple stages */
    179 void
    180 ltk_mainloop_step(int limit_framerate) {
    181 	ltk_event event;
    182 
    183 	/* FIXME: make time management smarter - maybe always figure out how long
    184 	   it will take until the next timer is due and then sleep if no other events
    185 	   are happening (would need separate parameter to turn that off when a
    186 	   different mainloop is used) */
    187 	struct timespec now, elapsed, sleep_time;
    188 	sleep_time.tv_sec = 0;
    189 
    190 	int pid = -1;
    191 	int wstatus = 0;
    192 	/* FIXME: kill all children on exit? */
    193 	/* -> at least unlink any files? */
    194 	if ((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) {
    195 		ltk_cmdinfo *info;
    196 		/* FIXME: should commands be split into read/write and block write commands during external editing? */
    197 		for (size_t i = 0; i < ltk_array_len(shared_data.cmds); i++) {
    198 			info = &(ltk_array_get(shared_data.cmds, i));
    199 			if (info->pid == pid) {
    200 				/* FIXME: actually NULL this when widgets are destroyed */
    201 				if (!info->caller) {
    202 					ltk_warn("Widget disappeared while text was being edited in external program\n");
    203 				/* FIXME: call overwritten cmd_return! */
    204 				} else if (info->caller->vtable->cmd_return) {
    205 					size_t file_len = 0;
    206 					char *errstr = NULL;
    207 					char *filename = info->outfile ? info->outfile : info->infile;
    208 					char *contents = ltk_read_file(filename, &file_len, &errstr);
    209 					if (!contents) {
    210 						ltk_warn("Unable to read file '%s' written by external command: %s\n", filename, errstr);
    211 					} else {
    212 						info->caller->vtable->cmd_return(info->caller, contents, file_len);
    213 						ltk_free0(contents);
    214 					}
    215 				}
    216 				/* FIXME: error checking */
    217 				unlink(info->infile);
    218 				ltk_free(info->infile);
    219 				if (info->outfile) {
    220 					unlink(info->outfile);
    221 					ltk_free(info->outfile);
    222 				}
    223 				ltk_array_delete(cmdinfo, shared_data.cmds, i, 1);
    224 				break;
    225 			}
    226 		}
    227 	}
    228 	while (!ltk_next_event(
    229 	    shared_data.renderdata,
    230 	    ltk_array_get_buf(shared_data.rwindows),
    231 	    ltk_array_len(shared_data.rwindows),
    232 	    shared_data.clipboard, shared_data.cur_kbd, &event)) {
    233 		ltk_handle_event(&event);
    234 	}
    235 
    236 	clock_gettime(CLOCK_MONOTONIC, &now);
    237 	ltk_timespecsub(&now, &mainloop_data.lasttimer, &elapsed);
    238 	/* Note: it should be safe to give the same pointer as the first and
    239 	   last argument, as long as ltk_timespecsub/add isn't changed incompatibly */
    240 	size_t i = 0;
    241 	while (i < timers_num) {
    242 		ltk_timespecsub(&timers[i].remaining, &elapsed, &timers[i].remaining);
    243 		if (timers[i].remaining.tv_sec < 0 ||
    244 		    (timers[i].remaining.tv_sec == 0 && timers[i].remaining.tv_nsec == 0)) {
    245 			timers[i].callback(timers[i].data);
    246 			if (timers[i].repeat.tv_sec == 0 && timers[i].repeat.tv_nsec == 0) {
    247 				/* remove timer because it has no repeat */
    248 				memmove(timers + i, timers + i + 1, sizeof(ltk_timer) * (timers_num - i - 1));
    249 			} else {
    250 				ltk_timespecadd(&timers[i].remaining, &timers[i].repeat, &timers[i].remaining);
    251 				i++;
    252 			}
    253 		} else {
    254 			i++;
    255 		}
    256 	}
    257 	mainloop_data.lasttimer = now;
    258 
    259 	for (size_t i = 0; i < shared_data.windows->len; i++) {
    260 		ltk_window *window = shared_data.windows->buf[i];
    261 		if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) {
    262 			ltk_widget_draw(LTK_CAST_WIDGET(window), NULL, 0, 0, (ltk_rect){0, 0, 0, 0});
    263 		}
    264 	}
    265 
    266 	if (limit_framerate) {
    267 		clock_gettime(CLOCK_MONOTONIC, &now);
    268 		ltk_timespecsub(&now, &mainloop_data.last, &elapsed);
    269 		/* FIXME: configure framerate */
    270 		if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
    271 			sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
    272 			nanosleep(&sleep_time, NULL);
    273 		}
    274 		mainloop_data.last = now;
    275 	}
    276 }
    277 
    278 void
    279 ltk_mainloop_quit(void) {
    280 	/* FIXME: maybe prevent other events from running? */
    281 	running = 0;
    282 }
    283 
    284 void
    285 ltk_mainloop_restartable(void) {
    286 	ltk_mainloop_init();
    287 	while (running) {
    288 		ltk_mainloop_step(1);
    289 	}
    290 }
    291 
    292 void
    293 ltk_mainloop(void) {
    294 	ltk_mainloop_restartable();
    295 	ltk_deinit();
    296 }
    297 
    298 void
    299 ltk_deinit(void) {
    300 	/* if renderdata is NULL, the other initialization can't have happened either */
    301 	if (running || !shared_data.renderdata)
    302 		return;
    303 	if (shared_data.cmds) {
    304 		for (size_t i = 0; i < ltk_array_len(shared_data.cmds); i++) {
    305 			/* FIXME: maybe kill child processes? */
    306 			ltk_free((ltk_array_get(shared_data.cmds, i)).infile);
    307 			if (ltk_array_get(shared_data.cmds, i).outfile)
    308 				ltk_free((ltk_array_get(shared_data.cmds, i)).outfile);
    309 		}
    310 		ltk_array_destroy(cmdinfo, shared_data.cmds);
    311 	}
    312 	shared_data.cmds = NULL;
    313 	if (shared_data.windows) {
    314 		for (size_t i = 0; i < ltk_array_len(shared_data.windows); i++) {
    315 			ltk_window *window = ltk_array_get(shared_data.windows, i);
    316 			ltk_widget_destroy(LTK_CAST_WIDGET(window), 0);
    317 		}
    318 		ltk_array_destroy(window, shared_data.windows);
    319 	}
    320 	shared_data.windows = NULL;
    321 	if (shared_data.rwindows)
    322 		ltk_array_destroy(rwindow, shared_data.rwindows);
    323 	shared_data.rwindows = NULL;
    324 	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
    325 		if (widget_funcs[i].cleanup)
    326 			widget_funcs[i].cleanup();
    327 	}
    328 	ltk_config_cleanup(shared_data.renderdata);
    329 	if (shared_data.text_context)
    330 		ltk_text_context_destroy(shared_data.text_context);
    331 	shared_data.text_context = NULL;
    332 	if (shared_data.clipboard)
    333 		ltk_clipboard_destroy(shared_data.clipboard);
    334 	shared_data.clipboard = NULL;
    335 	ltk_events_cleanup();
    336 	ltk_renderer_destroy(shared_data.renderdata);
    337 	shared_data.renderdata = NULL;
    338 }
    339 
    340 /* FIXME: check everywhere if initialized already */
    341 ltk_window *
    342 ltk_window_create(const char *title, int x, int y, unsigned int w, unsigned int h) {
    343 	/* FIXME: more asserts, or maybe global "initialized" flag */
    344 	ltk_assert(shared_data.renderdata != NULL);
    345 	ltk_assert(shared_data.windows != NULL);
    346 	ltk_assert(shared_data.rwindows != NULL);
    347 	ltk_window *window = ltk_window_create_intern(shared_data.renderdata, title, x, y, w, h);
    348 	ltk_array_append(window, shared_data.windows, window);
    349 	ltk_array_append(rwindow, shared_data.rwindows, window->renderwindow);
    350 	return window;
    351 }
    352 
    353 void
    354 ltk_window_destroy(ltk_widget *self, int shallow) {
    355 	/* FIXME: would it make sense to do something with 'shallow' here? */
    356 	(void)shallow;
    357 	ltk_window *window = LTK_CAST_WINDOW(self);
    358 	for (size_t i = 0; i < ltk_array_len(shared_data.windows); i++) {
    359 		if (ltk_array_get(shared_data.windows, i) == window) {
    360 			ltk_array_delete(window, shared_data.windows, i, 1);
    361 			ltk_array_delete(rwindow, shared_data.rwindows, i, 1);
    362 			break;
    363 		}
    364 	}
    365 	ltk_window_destroy_intern(window);
    366 }
    367 
    368 ltk_clipboard *
    369 ltk_get_clipboard(void) {
    370 	/* FIXME: what to do when not initialized? */
    371 	return shared_data.clipboard;
    372 }
    373 
    374 /* FIXME: optimize timer handling - maybe also a sort of priority queue */
    375 /* FIXME: JUST USE A GENERIC DYNAMIC ARRAY ALREADY!!!!! */
    376 void
    377 ltk_unregister_timer(int timer_id) {
    378 	for (size_t i = 0; i < timers_num; i++) {
    379 		if (timers[i].id == timer_id) {
    380 			memmove(
    381 			    timers + i,
    382 			    timers + i + 1,
    383 			    sizeof(ltk_timer) * (timers_num - i - 1)
    384 			);
    385 			timers_num--;
    386 			size_t sz = ideal_array_size(timers_alloc, timers_num);
    387 			if (sz != timers_alloc) {
    388 				timers_alloc = sz;
    389 				timers = ltk_reallocarray(
    390 				    timers, sz, sizeof(ltk_timer)
    391 				);
    392 			}
    393 			return;
    394 		}
    395 	}
    396 }
    397 
    398 /* repeat <= 0 means no repeat, first <= 0 means run as soon as possible */
    399 int
    400 ltk_register_timer(long first, long repeat, void (*callback)(ltk_callback_arg data), ltk_callback_arg data) {
    401 	if (first < 0)
    402 		first = 0;
    403 	if (repeat < 0)
    404 		repeat = 0;
    405 	if (timers_num == timers_alloc) {
    406 		timers_alloc = ideal_array_size(timers_alloc, timers_num + 1);
    407 		timers = ltk_reallocarray(
    408 		    timers, timers_alloc, sizeof(ltk_timer)
    409 		);
    410 	}
    411 	/* FIXME: better finding of id */
    412 	/* FIXME: maybe store sorted by id */
    413 	int id = 0;
    414 	for (size_t i = 0; i < timers_num; i++) {
    415 		if (timers[i].id >= id)
    416 			id = timers[i].id + 1;
    417 	}
    418 	ltk_timer *t = &timers[timers_num++];
    419 	t->callback = callback;
    420 	t->data = data;
    421 	t->repeat.tv_sec = repeat / 1000;
    422 	t->repeat.tv_nsec = (repeat % 1000) * 1000;
    423 	t->remaining.tv_sec = first / 1000;
    424 	t->remaining.tv_nsec = (first % 1000) * 1000;
    425 	t->id = id;
    426 	return id;
    427 }
    428 
    429 LTK_ARRAY_INIT_DECL_STATIC(str, char *)
    430 LTK_ARRAY_INIT_IMPL_STATIC(str, char *)
    431 
    432 static void
    433 str_free_helper(char *elem) {
    434 	ltk_free(elem);
    435 }
    436 
    437 int
    438 ltk_call_cmd(ltk_widget *caller, ltk_array(cmd) *cmd, const char *text, size_t textlen) {
    439 	/* FIXME: maybe support stdin/stdout without temporary files by just piping directly */
    440 	/* FIXME: support environment variable $TMPDIR */
    441 	ltk_cmdinfo info = {
    442 		.caller = NULL, .infile = NULL, .outfile = NULL, .pid = -1
    443 	};
    444 	ltk_array(str) *cmdstr = ltk_array_create(str, 4);
    445 	txtbuf *tmpbuf = txtbuf_new();
    446 	int needs_stdin = 1;
    447 	int needs_stdout = 1;
    448 
    449 	int infd = -1, outfd = -1;
    450 
    451 	info.infile = ltk_strdup("/tmp/ltk.XXXXXX");
    452 	infd = mkstemp(info.infile);
    453 	if (infd == -1) {
    454 		ltk_warn_errno("Unable to create temporary input file while trying to run command.");
    455 		ltk_free(info.infile);
    456 		info.infile = NULL; /* so it isn't unlinked below */
    457 		goto error;
    458 	}
    459 	/* FIXME: give file descriptor directly to modified version of ltk_write_file */
    460 	char *errstr = NULL;
    461 	if (ltk_write_file(info.infile, text, textlen, &errstr)) {
    462 		ltk_warn("Unable to write to temporary input file '%s' while trying to run command.", info.infile, errstr);
    463 		goto error;
    464 	}
    465 
    466 	for (size_t i = 0; i < ltk_array_len(cmd); i++) {
    467 		ltk_array(cmdpiece) *pa = ltk_array_get(cmd, i);
    468 		for (size_t j = 0; j < ltk_array_len(pa); j++) {
    469 			struct ltk_cmd_piece p = ltk_array_get(pa, j);
    470 			switch (p.type) {
    471 			case LTK_CMD_TEXT:
    472 				txtbuf_append(tmpbuf, p.text);
    473 				break;
    474 			case LTK_CMD_INOUT_FILE:
    475 				needs_stdout = 0;
    476 				/* fall through */
    477 			case LTK_CMD_INPUT_FILE:
    478 				needs_stdin = 0;
    479 				txtbuf_append(tmpbuf, info.infile);
    480 				break;
    481 			case LTK_CMD_OUTPUT_FILE:
    482 				needs_stdout = 0;
    483 				if (!info.outfile) {
    484 					info.outfile = ltk_strdup("/tmp/ltk.XXXXXX");
    485 					outfd = mkstemp(info.outfile);
    486 					if (outfd == -1) {
    487 						ltk_warn_errno("Unable to create temporary output file while trying to run command.");
    488 						ltk_free(info.outfile);
    489 						info.outfile = NULL; /* so it isn't unlinked below */
    490 						goto error;
    491 					}
    492 				}
    493 				txtbuf_append(tmpbuf, info.outfile);
    494 				break;
    495 			default:
    496 				ltk_warn("Invalid command piece type. This should not happen.");
    497 				goto error;
    498 			}
    499 		}
    500 		ltk_array_append(str, cmdstr, txtbuf_get_textcopy(tmpbuf));
    501 		txtbuf_clear(tmpbuf);
    502 	}
    503 	/* if no output file was specified, we still need to create it for stdout */
    504 	if (needs_stdout) {
    505 		info.outfile = ltk_strdup("/tmp/ltk.XXXXXX");
    506 		outfd = mkstemp(info.outfile);
    507 		if (outfd == -1) {
    508 			ltk_warn_errno("Unable to create temporary output file while trying to run command.");
    509 			ltk_free(info.outfile);
    510 			info.outfile = NULL; /* so it isn't unlinked below */
    511 			goto error;
    512 		}
    513 	}
    514 	ltk_array_append(str, cmdstr, NULL); /* necessary for execve */
    515 	txtbuf_destroy(tmpbuf);
    516 	tmpbuf = NULL;
    517 
    518 	int fret = -1;
    519 	if ((fret = fork()) < 0) {
    520 		ltk_warn("Unable to fork\n");
    521 		goto error;
    522 	} else if (fret == 0) {
    523 		if (needs_stdin) {
    524 			if (dup2(infd, fileno(stdin)) == -1)
    525 				ltk_fatal("Unable to set up stdin in child process.");
    526 		}
    527 		if (needs_stdout) {
    528 			int fd = outfd == -1 ? infd : outfd;
    529 			if (dup2(fd, fileno(stdout)) == -1)
    530 				ltk_fatal("Unable to set up stdout in child process.");
    531 		}
    532 		if (execvp(cmdstr->buf[0], cmdstr->buf) == -1)
    533 			ltk_fatal("Unable to exec external command.");
    534 	}
    535 	ltk_array_destroy_deep(str, cmdstr, &str_free_helper);
    536 
    537 	info.pid = fret;
    538 	info.caller = caller;
    539 	ltk_array_append(cmdinfo, shared_data.cmds, info);
    540 
    541 	if (infd != -1)
    542 		close(infd); /* FIXME: error checking also on close */
    543 	if (outfd != -1)
    544 		close(outfd);
    545 	return 0;
    546 error:
    547 	if (infd != -1)
    548 		close(infd); /* FIXME: error checking also on close and unlink */
    549 	if (outfd != -1)
    550 		close(outfd);
    551 	if (tmpbuf)
    552 		txtbuf_destroy(tmpbuf);
    553 	if (info.infile) {
    554 		unlink(info.infile);
    555 		ltk_free(info.infile);
    556 	}
    557 	if (info.outfile) {
    558 		unlink(info.outfile);
    559 		ltk_free(info.outfile);
    560 	}
    561 	ltk_array_destroy_deep(str, cmdstr, &str_free_helper);
    562 	return 1;
    563 }
    564 
    565 static void
    566 ltk_handle_event(ltk_event *event) {
    567 	size_t kbd_idx;
    568 	if (event->type == LTK_KEYBOARDCHANGE_EVENT) {
    569 		/* FIXME: emit event */
    570 		if (ltk_config_get_language_index(event->keyboard.new_kbd, &kbd_idx))
    571 			ltk_warn("No language mapping for language \"%s\".\n", event->keyboard.new_kbd);
    572 		else
    573 			shared_data.cur_kbd = kbd_idx;
    574 	} else {
    575 		if (event->any.window_id < ltk_array_len(shared_data.windows)) {
    576 			ltk_window_handle_event(ltk_array_get(shared_data.windows, event->any.window_id), event);
    577 		}
    578 	}
    579 }
    580 
    581 ltk_text_line *
    582 ltk_text_line_create_default(const char *font, int font_size, char *text, int take_over_text, int width) {
    583 	return ltk_text_line_create(shared_data.text_context, font, font_size, text, take_over_text, width);
    584 }
    585 
    586 ltk_text_line *
    587 ltk_text_line_create_const_text_default(const char *font, int font_size, const char *text, int width) {
    588 	return ltk_text_line_create_const_text(shared_data.text_context, font, font_size, text, width);
    589 }