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