config.c (41413B)
1 /* 2 * Copyright (c) 2022-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 /* FIXME: This really is horrible. */ 18 19 #include <ctype.h> 20 #include <string.h> 21 #include <limits.h> 22 23 #include "util.h" 24 #include "memory.h" 25 #include "config.h" 26 #include "sort_search.h" 27 #include "widget_internal.h" 28 29 GEN_SORT_SEARCH_HELPERS(keybinding, ltk_keybinding_cb, text) 30 GEN_SORT_SEARCH_HELPERS(theme, ltk_theme_parseinfo, key) 31 LTK_ARRAY_INIT_IMPL(keypress, ltk_keypress_cfg) 32 LTK_ARRAY_INIT_IMPL(keyrelease, ltk_keyrelease_cfg) 33 34 static ltk_general_config general_config; 35 static ltk_language_mapping *mappings = NULL; 36 static size_t mappings_alloc = 0, mappings_len = 0; 37 38 static ltk_array(cmd) *ltk_parse_cmd(const char *cmdtext, size_t len); 39 40 static ltk_theme_parseinfo general_parseinfo[] = { 41 {"line-editor", THEME_CMD, {.cmd = &general_config.line_editor}, {.cmd = NULL}, 0, 0, 0}, 42 {"option-chooser", THEME_CMD, {.cmd = &general_config.option_chooser}, {.cmd = NULL}, 0, 0, 0}, 43 {"dpi-scale", THEME_DOUBLE, {.d = &general_config.dpi_scale}, {.d = 1.0}, 10, 10000, 0}, 44 {"explicit-focus", THEME_BOOL, {.b = &general_config.explicit_focus}, {.b = 0}, 0, 0, 0}, 45 {"all-activatable", THEME_BOOL, {.b = &general_config.all_activatable}, {.b = 0}, 0, 0, 0}, 46 {"fixed-dpi", THEME_DOUBLE, {.d = &general_config.fixed_dpi}, {.d = 96.0}, 100, 400000, 0}, 47 /* FIXME: warning if set to true but xrandr not enabled */ 48 #if USE_XRANDR 49 {"mixed-dpi", THEME_BOOL, {.b = &general_config.mixed_dpi}, {.b = 1}, 0, 0, 0}, 50 #else 51 {"mixed-dpi", THEME_BOOL, {.b = &general_config.mixed_dpi}, {.b = 0}, 0, 0, 0}, 52 #endif 53 }; 54 55 /* just to use the same interface for all theme sections */ 56 static void 57 ltk_general_get_theme_parseinfo(ltk_theme_parseinfo **parseinfo, size_t *len) { 58 *parseinfo = general_parseinfo; 59 *len = LENGTH(general_parseinfo); 60 } 61 62 static struct { 63 const char *name; 64 void (*get_parseinfo)( 65 ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret, 66 ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret, 67 ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret 68 ); 69 } keybinding_handlers[] = { 70 {"combobox", <k_combobox_get_keybinding_parseinfo}, 71 {"entry", <k_entry_get_keybinding_parseinfo}, 72 {"window", <k_window_get_keybinding_parseinfo}, 73 }; 74 75 static struct theme_handlerinfo { 76 const char *name; 77 void (*get_parseinfo)(ltk_theme_parseinfo **parseinfo, size_t *len); 78 const char *parent; 79 int finished; 80 } theme_handlers[] = { 81 {"general", <k_general_get_theme_parseinfo, NULL, 0}, 82 {"theme:window", <k_window_get_theme_parseinfo, NULL, 0}, 83 {"theme:button", <k_button_get_theme_parseinfo, "theme:window", 0}, 84 {"theme:entry", <k_entry_get_theme_parseinfo, "theme:window", 0}, 85 {"theme:label", <k_label_get_theme_parseinfo, "theme:window", 0}, 86 {"theme:scrollbar", <k_scrollbar_get_theme_parseinfo, "theme:window", 0}, 87 {"theme:menu", <k_menu_get_theme_parseinfo, "theme:window", 0}, 88 {"theme:menuentry", <k_menuentry_get_theme_parseinfo, "theme:window", 0}, 89 {"theme:submenu", <k_submenu_get_theme_parseinfo, "theme:window", 0}, 90 {"theme:submenuentry", <k_submenuentry_get_theme_parseinfo, "theme:window", 0}, 91 {"theme:checkbutton", <k_checkbutton_get_theme_parseinfo, "theme:window", 0}, 92 {"theme:radiobutton", <k_radiobutton_get_theme_parseinfo, "theme:window", 0}, 93 {"theme:combobox", <k_combobox_get_theme_parseinfo, "theme:window", 0}, 94 }; 95 96 GEN_SORT_SEARCH_HELPERS(themehandler, struct theme_handlerinfo, name) 97 98 static void 99 sort_themehandlers(void) { 100 ltk_theme_parseinfo *parseinfo; 101 size_t len; 102 themehandler_sort(theme_handlers, LENGTH(theme_handlers)); 103 for (size_t i = 0; i < LENGTH(theme_handlers); i++) { 104 theme_handlers[i].get_parseinfo(&parseinfo, &len); 105 theme_sort(parseinfo, len); 106 } 107 } 108 109 LTK_ARRAY_INIT_FUNC_DECL_STATIC(cmdpiece, struct ltk_cmd_piece) 110 LTK_ARRAY_INIT_IMPL_STATIC(cmdpiece, struct ltk_cmd_piece) 111 LTK_ARRAY_INIT_FUNC_DECL_STATIC(cmd, ltk_array(cmdpiece) *) 112 LTK_ARRAY_INIT_IMPL_STATIC(cmd, ltk_array(cmdpiece) *) 113 114 static void 115 cmd_piece_free_helper(struct ltk_cmd_piece p) { 116 if (p.text) 117 ltk_free(p.text); 118 } 119 120 static void 121 cmd_free_helper(ltk_array(cmdpiece) *arr) { 122 ltk_array_destroy_deep(cmdpiece, arr, &cmd_piece_free_helper); 123 } 124 125 static ltk_array(cmd) * 126 copy_cmd(ltk_array(cmd) *cmd) { 127 ltk_array(cmd) *cmdcopy = ltk_array_create(cmd, ltk_array_len(cmd)); 128 for (size_t i = 0; i < ltk_array_len(cmd); i++) { 129 ltk_array(cmdpiece) *piece = ltk_array_get(cmd, i); 130 ltk_array(cmdpiece) *piececopy = ltk_array_create(cmdpiece, ltk_array_len(piece)); 131 for (size_t j = 0; j < ltk_array_len(piece); j++) { 132 struct ltk_cmd_piece p = {NULL, ltk_array_get(piece, j).type}; 133 if (ltk_array_get(piece, j).text) 134 p.text = ltk_strdup(ltk_array_get(piece, j).text); 135 ltk_array_append(cmdpiece, piececopy, p); 136 } 137 ltk_array_append(cmd, cmdcopy, piececopy); 138 } 139 return cmdcopy; 140 } 141 142 /* FIXME: handle '#' or no '#' in color specification */ 143 static int 144 handle_theme_setting(ltk_renderdata *renderdata, ltk_theme_parseinfo *entry, const char *value) { 145 const char *errstr = NULL; 146 char *endptr = NULL; 147 /* FIXME: better warnings */ 148 long long ll; 149 switch (entry->type) { 150 case THEME_INT: 151 if (entry->max > INT_MAX) 152 entry->max = INT_MAX; 153 if (entry->min < INT_MIN) 154 entry->min = INT_MIN; 155 *(entry->ptr.i) = ltk_strtonum(value, entry->min, entry->max, &errstr); 156 if (errstr) 157 return 1; 158 entry->initialized = 1; 159 break; 160 case THEME_DOUBLE: 161 /* FIXME: maybe overflow prevention here as well */ 162 ll = ltk_strtoscalednum(value, entry->min, entry->max, &endptr, &errstr); 163 if (errstr || *endptr != '\0') 164 return 1; 165 *(entry->ptr.d) = ll / 100.0; 166 entry->initialized = 1; 167 break; 168 case THEME_UINT: 169 if (entry->max > INT_MAX) 170 entry->max = INT_MAX; 171 if (entry->min < 0) 172 entry->min = 0; 173 *(entry->ptr.u) = ltk_strtonum(value, entry->min, entry->max, &errstr); 174 if (errstr) 175 return 1; 176 entry->initialized = 1; 177 break; 178 case THEME_SIZE: 179 if (entry->max > INT_MAX) 180 entry->max = INT_MAX; 181 if (entry->min < INT_MIN) 182 entry->min = INT_MIN; 183 entry->ptr.size->unit = LTK_UNIT_PX; 184 entry->ptr.size->val = ltk_strtoscalednum(value, entry->min, entry->max, &endptr, &errstr); 185 if (errstr) 186 return 1; 187 if (*endptr == '\0') { 188 /* NOP */ 189 } else if (!strcmp(endptr, "px")) { 190 entry->ptr.size->unit = LTK_UNIT_PX; 191 } else if (!strcmp(endptr, "pt")) { 192 entry->ptr.size->unit = LTK_UNIT_PT; 193 } else if (!strcmp(endptr, "mm")) { 194 entry->ptr.size->unit = LTK_UNIT_MM; 195 } else { 196 return 1; 197 } 198 entry->initialized = 1; 199 break; 200 case THEME_STRING: 201 *(entry->ptr.str) = ltk_strdup(value); 202 entry->initialized = 1; 203 break; 204 case THEME_COLOR: 205 /* FIXME: warning message possibly misleading because this can fail for reasons 206 other than an invalid color specification */ 207 if (!(*(entry->ptr.color) = ltk_color_create(renderdata, value))) 208 return 1; 209 entry->initialized = 1; 210 break; 211 case THEME_CMD: 212 if (!(*(entry->ptr.cmd) = ltk_parse_cmd(value, strlen(value)))) 213 return 1; 214 entry->initialized = 1; 215 break; 216 case THEME_BOOL: 217 if (strcmp(value, "true") == 0) { 218 *(entry->ptr.b) = 1; 219 } else if (strcmp(value, "false") == 0) { 220 *(entry->ptr.b) = 0; 221 } else { 222 return 1; 223 } 224 entry->initialized = 1; 225 break; 226 case THEME_BORDERSIDES: 227 *(entry->ptr.border) = LTK_BORDER_NONE; 228 for (const char *c = value; *c != '\0'; c++) { 229 switch (*c) { 230 case 't': 231 *(entry->ptr.border) |= LTK_BORDER_TOP; 232 break; 233 case 'b': 234 *(entry->ptr.border) |= LTK_BORDER_BOTTOM; 235 break; 236 case 'l': 237 *(entry->ptr.border) |= LTK_BORDER_LEFT; 238 break; 239 case 'r': 240 *(entry->ptr.border) |= LTK_BORDER_RIGHT; 241 break; 242 default: 243 return 1; 244 } 245 } 246 entry->initialized = 1; 247 break; 248 default: 249 ltk_fatal("Invalid theme setting type. This should not happen.\n"); 250 } 251 return 0; 252 } 253 254 static int 255 fill_single_theme_defaults(ltk_renderdata *renderdata, struct theme_handlerinfo *handler) { 256 ltk_theme_parseinfo *parseinfo; 257 size_t len; 258 if (handler->finished) 259 return 0; 260 handler->get_parseinfo(&parseinfo, &len); 261 ltk_theme_parseinfo *parent_parseinfo = NULL; 262 size_t parent_len = 0; 263 if (handler->parent) { 264 struct theme_handlerinfo *parent_handler = themehandler_get_entry( 265 theme_handlers, LENGTH(theme_handlers), handler->parent, strlen(handler->parent) 266 ); 267 /* Yes, this will cause an infinite loop if there's a cycle in the parent 268 relationship. However, there is absolutely no reason to construct such a cycle. */ 269 /* FIXME: warning if not found */ 270 if (parent_handler) { 271 if (fill_single_theme_defaults(renderdata, parent_handler)) 272 return 1; 273 parent_handler->get_parseinfo(&parent_parseinfo, &parent_len); 274 } 275 } 276 for (size_t i = 0; i < len; i++) { 277 ltk_theme_parseinfo *e = &parseinfo[i]; 278 if (e->initialized) 279 continue; 280 ltk_theme_parseinfo *ep = parent_parseinfo ? 281 theme_get_entry(parent_parseinfo, parent_len, e->key, strlen(e->key)) : NULL; 282 switch (e->type) { 283 case THEME_INT: 284 *(e->ptr.i) = ep ? *(ep->ptr.i) : e->defaultval.i; 285 e->initialized = 1; 286 break; 287 case THEME_UINT: 288 *(e->ptr.u) = ep ? *(ep->ptr.u) : e->defaultval.u; 289 e->initialized = 1; 290 break; 291 case THEME_DOUBLE: 292 *(e->ptr.d) = ep ? *(ep->ptr.d) : e->defaultval.d; 293 e->initialized = 1; 294 break; 295 case THEME_SIZE: 296 *(e->ptr.size) = ep ? *(ep->ptr.size) : e->defaultval.size; 297 e->initialized = 1; 298 break; 299 case THEME_STRING: 300 if (ep) 301 *(e->ptr.str) = *(ep->ptr.str) ? ltk_strdup(*(ep->ptr.str)) : NULL; 302 else if (e->defaultval.str) 303 *(e->ptr.str) = ltk_strdup(e->defaultval.str); 304 else 305 *(e->ptr.str) = NULL; 306 e->initialized = 1; 307 break; 308 case THEME_COLOR: 309 if (ep) { 310 if (!(*(e->ptr.color) = ltk_color_copy(renderdata, *(ep->ptr.color)))) 311 return 1; 312 } else if (!e->defaultval.color) { 313 return 1; /* colors must always be initialized */ 314 } else if (!(*(e->ptr.color) = ltk_color_create(renderdata, e->defaultval.color))) { 315 return 1; 316 } 317 e->initialized = 1; 318 break; 319 case THEME_CMD: 320 if (ep) { 321 /* There is no reason to ever use this, but whatever */ 322 if (!(*(e->ptr.cmd) = copy_cmd(*(ep->ptr.cmd)))) 323 return 1; 324 } else if (!e->defaultval.cmd) { 325 *(e->ptr.cmd) = NULL; 326 } else if (!(*(e->ptr.cmd) = ltk_parse_cmd(e->defaultval.cmd, strlen(e->defaultval.cmd)))) { 327 return 1; 328 } 329 e->initialized = 1; 330 break; 331 case THEME_BOOL: 332 *(e->ptr.b) = ep ? *(ep->ptr.b) : e->defaultval.b; 333 e->initialized = 1; 334 break; 335 case THEME_BORDERSIDES: 336 *(e->ptr.border) = ep ? *(ep->ptr.border) : e->defaultval.border; 337 e->initialized = 1; 338 break; 339 default: 340 ltk_fatal("Invalid theme setting type. This should not happen.\n"); 341 } 342 } 343 handler->finished = 1; 344 return 0; 345 } 346 347 static int 348 fill_theme_defaults(ltk_renderdata *renderdata) { 349 for (size_t j = 0; j < LENGTH(theme_handlers); j++) { 350 if (fill_single_theme_defaults(renderdata, &theme_handlers[j])) 351 return 1; 352 } 353 return 0; 354 } 355 356 static void 357 uninitialize_theme(ltk_renderdata *renderdata) { 358 ltk_theme_parseinfo *parseinfo; 359 size_t len; 360 for (size_t j = 0; j < LENGTH(theme_handlers); j++) { 361 theme_handlers[j].get_parseinfo(&parseinfo, &len); 362 theme_handlers[j].finished = 0; 363 for (size_t i = 0; i < len; i++) { 364 ltk_theme_parseinfo *e = &parseinfo[i]; 365 if (!e->initialized) 366 continue; 367 switch (e->type) { 368 case THEME_STRING: 369 ltk_free(*(e->ptr.str)); 370 e->initialized = 0; 371 break; 372 case THEME_COLOR: 373 ltk_color_destroy(renderdata, *(e->ptr.color)); 374 e->initialized = 0; 375 break; 376 case THEME_CMD: 377 ltk_array_destroy_deep(cmd, *(e->ptr.cmd), &cmd_free_helper); 378 e->initialized = 0; 379 break; 380 case THEME_SIZE: 381 case THEME_INT: 382 case THEME_UINT: 383 case THEME_BOOL: 384 case THEME_BORDERSIDES: 385 case THEME_DOUBLE: 386 e->initialized = 0; 387 break; 388 default: 389 ltk_fatal("Invalid theme setting type. This should not happen.\n"); 390 } 391 } 392 } 393 } 394 395 static int 396 register_keypress(ltk_array(keypress) *bindings, ltk_keybinding_cb *arr, size_t arrlen, const char *func_name, size_t func_len, ltk_keypress_binding b) { 397 ltk_keybinding_cb *cb = keybinding_get_entry(arr, arrlen, func_name, func_len); 398 if (!cb) 399 return 1; 400 ltk_keypress_cfg cfg = {b, *cb}; 401 ltk_array_append(keypress, bindings, cfg); 402 return 0; 403 } 404 405 static int 406 register_keyrelease(ltk_array(keyrelease) *bindings, ltk_keybinding_cb *arr, size_t arrlen, const char *func_name, size_t func_len, ltk_keyrelease_binding b) { 407 ltk_keybinding_cb *cb = keybinding_get_entry(arr, arrlen, func_name, func_len); 408 if (!cb) 409 return 1; 410 ltk_keyrelease_cfg cfg = {b, *cb}; 411 ltk_array_append(keyrelease, bindings, cfg); 412 return 0; 413 } 414 415 static int 416 handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b) { 417 ltk_keybinding_cb *press_cbs = NULL, *release_cbs = NULL; 418 size_t press_len = 0, release_len = 0; 419 ltk_array(keypress) *presses = NULL; 420 ltk_array(keyrelease) *releases = NULL; 421 for (size_t i = 0; i < LENGTH(keybinding_handlers); i++) { 422 if (str_array_equal(keybinding_handlers[i].name, widget_name, wlen)) { 423 keybinding_handlers[i].get_parseinfo( 424 &press_cbs, &press_len, &release_cbs, &release_len, &presses, &releases 425 ); 426 if (!press_cbs || !presses) 427 return 1; 428 return register_keypress(presses, press_cbs, press_len, name, nlen, b); 429 } 430 } 431 return 1; 432 } 433 434 static int 435 handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b) { 436 ltk_keybinding_cb *press_cbs = NULL, *release_cbs = NULL; 437 size_t press_len = 0, release_len = 0; 438 ltk_array(keypress) *presses = NULL; 439 ltk_array(keyrelease) *releases = NULL; 440 for (size_t i = 0; i < LENGTH(keybinding_handlers); i++) { 441 if (str_array_equal(keybinding_handlers[i].name, widget_name, wlen)) { 442 keybinding_handlers[i].get_parseinfo( 443 &press_cbs, &press_len, &release_cbs, &release_len, &presses, &releases 444 ); 445 if (!release_cbs || !releases) 446 return 1; 447 return register_keyrelease(releases, release_cbs, release_len, name, nlen, b); 448 } 449 } 450 return 1; 451 } 452 453 static void 454 sort_keybindings(void) { 455 ltk_keybinding_cb *press_cbs = NULL, *release_cbs = NULL; 456 size_t press_len = 0, release_len = 0; 457 ltk_array(keypress) *presses = NULL; 458 ltk_array(keyrelease) *releases = NULL; 459 for (size_t i = 0; i < LENGTH(keybinding_handlers); i++) { 460 keybinding_handlers[i].get_parseinfo( 461 &press_cbs, &press_len, &release_cbs, &release_len, &presses, &releases 462 ); 463 if (press_cbs) 464 keybinding_sort(press_cbs, press_len); 465 if (release_cbs) 466 keybinding_sort(release_cbs, release_len); 467 } 468 } 469 470 static void 471 destroy_keypress_cfg(ltk_keypress_cfg cfg) { 472 ltk_free(cfg.b.text); 473 ltk_free(cfg.b.rawtext); 474 } 475 476 void 477 ltk_keypress_bindings_destroy(ltk_array(keypress) *arr) { 478 ltk_array_destroy_deep(keypress, arr, &destroy_keypress_cfg); 479 } 480 481 void 482 ltk_keyrelease_bindings_destroy(ltk_array(keyrelease) *arr) { 483 ltk_array_destroy(keyrelease, arr); 484 } 485 486 static void sort_keysyms(void); 487 static int parse_keysym(char *text, size_t len, ltk_keysym *sym_ret); 488 489 enum toktype { 490 STRING, 491 SECTION, 492 EQUALS, 493 NEWLINE, 494 ERROR, 495 END 496 }; 497 498 #if 0 499 static const char * 500 toktype_str(enum toktype type) { 501 switch (type) { 502 case STRING: 503 return "string"; 504 break; 505 case SECTION: 506 return "section"; 507 break; 508 case EQUALS: 509 return "equals"; 510 break; 511 case NEWLINE: 512 return "newline"; 513 break; 514 case ERROR: 515 return "error"; 516 break; 517 case END: 518 return "end of file"; 519 break; 520 default: 521 return "unknown"; 522 } 523 } 524 #endif 525 526 struct token { 527 char *text; 528 size_t len; 529 enum toktype type; 530 size_t line; /* line in original input */ 531 size_t line_offset; /* offset from start of line */ 532 }; 533 534 struct lexstate { 535 const char *filename; 536 char *text; 537 size_t len; /* length of text */ 538 size_t cur; /* current byte position */ 539 size_t cur_line; /* current line */ 540 size_t line_start; /* byte offset of start of current line */ 541 }; 542 543 static struct token 544 next_token(struct lexstate *s) { 545 char c; 546 struct token tok; 547 int finished = 0; 548 while (1) { 549 if (s->cur >= s->len) 550 return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1}; 551 while (isspace(c = s->text[s->cur])) { 552 s->cur++; 553 if (c == '\n') { 554 struct token tok = (struct token){s->text + s->cur - 1, 1, NEWLINE, s->cur_line, s->cur - s->line_start}; 555 s->cur_line++; 556 s->line_start = s->cur; 557 return tok; 558 } 559 if (s->cur >= s->len) 560 return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1}; 561 } 562 563 switch (s->text[s->cur]) { 564 case '#': 565 s->cur++; 566 while (s->cur < s->len && s->text[s->cur] != '\n') 567 s->cur++; 568 continue; 569 case '[': 570 s->cur++; 571 tok = (struct token){s->text + s->cur, 0, SECTION, s->cur_line, s->cur - s->line_start + 1}; 572 finished = 0; 573 while (s->cur < s->len) { 574 char c = s->text[s->cur]; 575 if (c == '\n') { 576 break; 577 } else if (c == ']') { 578 s->cur++; 579 finished = 1; 580 break; 581 } else { 582 tok.len++; 583 } 584 s->cur++; 585 } 586 if (!finished) { 587 tok.text = "Unfinished section name"; 588 tok.len = strlen("Unfinished section name"); 589 tok.type = ERROR; 590 } 591 break; 592 case '=': 593 tok = (struct token){s->text + s->cur, 1, EQUALS, s->cur_line, s->cur - s->line_start + 1}; 594 s->cur++; 595 break; 596 case '"': 597 /* FIXME: error if next char is not whitespace or end */ 598 s->cur++; 599 tok = (struct token){s->text + s->cur, 0, STRING, s->cur_line, s->cur - s->line_start + 1}; 600 size_t shift = 0, bs = 0; 601 finished = 0; 602 while (s->cur < s->len) { 603 char c = s->text[s->cur]; 604 if (c == '\n') { 605 break; 606 } else if (c == '\\') { 607 shift += bs; 608 tok.len += bs; 609 bs = (bs + 1) % 2; 610 } else if (c == '"') { 611 if (bs) { 612 shift++; 613 tok.len++; 614 bs = 0; 615 } else { 616 s->cur++; 617 finished = 1; 618 break; 619 } 620 } else { 621 tok.len++; 622 } 623 s->text[s->cur - shift] = s->text[s->cur]; 624 s->cur++; 625 } 626 if (!finished) { 627 tok.text = "Unfinished string"; 628 tok.len = strlen("Unfinished string"); 629 tok.type = ERROR; 630 } 631 break; 632 default: 633 tok = (struct token){s->text + s->cur, 1, STRING, s->cur_line, s->cur - s->line_start + 1}; 634 s->cur++; 635 while (s->cur < s->len) { 636 char c = s->text[s->cur]; 637 if (isspace(c) || c == '=') { 638 break; 639 } else if (c == '"') { 640 tok.text = "Unexpected start of string"; 641 tok.len = strlen("Unexpected start of string"); 642 tok.type = ERROR; 643 tok.line_offset = s->cur - s->line_start + 1; 644 } else if (c == '[' || c == ']') { 645 tok.text = "Unexpected start or end of section name"; 646 tok.len = strlen("Unexpected start or end of section name"); 647 tok.type = ERROR; 648 tok.line_offset = s->cur - s->line_start + 1; 649 } 650 tok.len++; 651 s->cur++; 652 } 653 } 654 return tok; 655 } 656 } 657 658 #undef MIN 659 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 660 661 static int 662 parse_modmask(char *modmask_str, size_t len, ltk_mod_type *mask_ret) { 663 size_t cur = 0; 664 *mask_ret = 0; 665 while (cur < len) { 666 if (str_array_equal("shift", modmask_str + cur, MIN(5, len - cur))) { 667 cur += 5; 668 *mask_ret |= LTK_MOD_SHIFT; 669 } else if (str_array_equal("ctrl", modmask_str + cur, MIN(4, len - cur))) { 670 cur += 4; 671 *mask_ret |= LTK_MOD_CTRL; 672 } else if (str_array_equal("alt", modmask_str + cur, MIN(3, len - cur))) { 673 cur += 3; 674 *mask_ret |= LTK_MOD_ALT; 675 } else if (str_array_equal("super", modmask_str + cur, MIN(5, len - cur))) { 676 cur += 5; 677 *mask_ret |= LTK_MOD_SUPER; 678 } else if (str_array_equal("any", modmask_str + cur, MIN(3, len - cur))) { 679 cur += 3; 680 *mask_ret = UINT_MAX; 681 } else { 682 return 1; 683 } 684 if (cur < len && modmask_str[cur] != '|') 685 return 1; 686 else 687 cur++; 688 } 689 return 0; 690 } 691 692 static int 693 parse_flags(char *text, size_t len, ltk_key_binding_flags *flags_ret) { 694 if (str_array_equal("run-always", text, len)) 695 *flags_ret = LTK_KEY_BINDING_RUN_ALWAYS; 696 else 697 return 1; 698 return 0; 699 } 700 701 static int 702 parse_keypress_binding( 703 struct lexstate *s, struct token *tok, 704 ltk_keypress_binding *binding_ret, 705 char **func_name_ret, size_t *func_len_ret, 706 char **errstr) { 707 ltk_keypress_binding b = {NULL, NULL, LTK_KEY_NONE, LTK_MOD_NONE, LTK_KEY_BINDING_NOFLAGS}; 708 *tok = next_token(s); 709 char *msg = NULL; 710 if (tok->type != STRING) { 711 msg = "Invalid token type"; 712 goto error; 713 } 714 *func_name_ret = tok->text; 715 *func_len_ret = tok->len; 716 struct token prevtok; 717 int text_init = 0, rawtext_init = 0, sym_init = 0, mods_init = 0, flags_init = 0; 718 while (1) { 719 *tok = next_token(s); 720 if (tok->type == NEWLINE || tok->type == END) 721 break; 722 prevtok = *tok; 723 *tok = next_token(s); 724 if (prevtok.type != STRING) { 725 msg = "Invalid token type"; 726 *tok = prevtok; 727 goto error; 728 } else if (tok->type != STRING) { 729 msg = "Invalid token type"; 730 goto error; 731 } else if (str_array_equal("text", prevtok.text, prevtok.len)) { 732 if (rawtext_init) { 733 msg = "Rawtext already specified"; 734 goto error; 735 } else if (sym_init) { 736 msg = "Keysym already specified"; 737 goto error; 738 } else if (text_init) { 739 msg = "Duplicate text specification"; 740 goto error; 741 } 742 b.text = ltk_strndup(tok->text, tok->len); 743 text_init = 1; 744 } else if (str_array_equal("rawtext", prevtok.text, prevtok.len)) { 745 if (text_init) { 746 msg = "Text already specified"; 747 goto error; 748 } else if (sym_init) { 749 msg = "Keysym already specified"; 750 goto error; 751 } else if (rawtext_init) { 752 msg = "Duplicate rawtext specification"; 753 goto error; 754 } 755 b.rawtext = ltk_strndup(tok->text, tok->len); 756 rawtext_init = 1; 757 } else if (str_array_equal("sym", prevtok.text, prevtok.len)) { 758 if (text_init) { 759 msg = "Text already specified"; 760 goto error; 761 } else if (rawtext_init) { 762 msg = "Rawtext already specified"; 763 goto error; 764 } else if (sym_init) { 765 msg = "Duplicate keysym specification"; 766 goto error; 767 } else if (parse_keysym(tok->text, tok->len, &b.sym)) { 768 msg = "Invalid keysym specification"; 769 goto error; 770 } 771 sym_init = 1; 772 } else if (str_array_equal("mods", prevtok.text, prevtok.len)) { 773 if (mods_init) { 774 msg = "Duplicate mods specification"; 775 goto error; 776 } else if (parse_modmask(tok->text, tok->len, &b.mods)) { 777 msg = "Invalid mods specification"; 778 goto error; 779 } 780 mods_init = 1; 781 } else if (str_array_equal("flags", prevtok.text, prevtok.len)) { 782 if (flags_init) { 783 msg = "Duplicate flags specification"; 784 goto error; 785 } else if (parse_flags(tok->text, tok->len, &b.flags)) { 786 msg = "Invalid flags specification"; 787 goto error; 788 } 789 flags_init = 1; 790 } else { 791 msg = "Invalid keyword"; 792 *tok = prevtok; 793 goto error; 794 } 795 }; 796 if (!text_init && !rawtext_init && !sym_init) { 797 msg = "One of text, rawtext, and sym must be initialized"; 798 goto error; 799 } 800 *binding_ret = b; 801 return 0; 802 error: 803 ltk_free(b.text); 804 ltk_free(b.rawtext); 805 if (msg) { 806 *errstr = ltk_print_fmt( 807 "%s, line %zu, offset %zu: %s", s->filename, tok->line, tok->line_offset, msg 808 ); 809 } else { 810 *errstr = NULL; 811 } 812 return 1; 813 } 814 815 static int 816 parse_keybinding( 817 struct lexstate *s, 818 struct token *tok, 819 char *widget, 820 size_t len, 821 char **errstr) { 822 char *msg = NULL; 823 *tok = next_token(s); 824 if (tok->type == SECTION || tok->type == END) { 825 return -1; 826 } else if (tok->type == NEWLINE) { 827 return 0; /* empty line */ 828 } else if (tok->type != STRING) { 829 msg = "Invalid token type"; 830 goto error; 831 } else if (str_array_equal("bind-keypress", tok->text, tok->len)) { 832 ltk_keypress_binding b; 833 char *name; 834 size_t nlen; 835 if (parse_keypress_binding(s, tok, &b, &name, &nlen, errstr)) 836 return 1; 837 if (handle_keypress_binding(widget, len, name, nlen, b)) { 838 msg = "Invalid key binding"; 839 goto error; 840 } 841 } else if (str_array_equal("bind-keyrelease", tok->text, tok->len)) { 842 ltk_keypress_binding b; 843 char *name; 844 size_t nlen; 845 if (parse_keypress_binding(s, tok, &b, &name, &nlen, errstr)) 846 return 1; 847 if (b.text || b.rawtext) { 848 ltk_free(b.text); 849 ltk_free(b.rawtext); 850 msg = "Text and rawtext may only be specified for keypress bindings"; 851 goto error; 852 } 853 if (handle_keyrelease_binding(widget, len, name, nlen, (ltk_keyrelease_binding){b.sym, b.mods, b.flags})) { 854 msg = "Invalid key binding"; 855 goto error; 856 } 857 } else { 858 msg = "Invalid statement"; 859 goto error; 860 } 861 return 0; 862 error: 863 if (msg) { 864 *errstr = ltk_print_fmt( 865 "%s, line %zu, offset %zu: %s", s->filename, tok->line, tok->line_offset, msg 866 ); 867 } 868 return 1; 869 } 870 871 static void 872 push_lang_mapping(void) { 873 if (mappings_alloc == mappings_len) { 874 mappings_alloc = ideal_array_size(mappings_alloc, mappings_len + 1); 875 mappings = ltk_reallocarray(mappings, mappings_alloc, sizeof(ltk_language_mapping)); 876 } 877 mappings[mappings_len].lang = NULL; 878 mappings[mappings_len].mappings = NULL; 879 mappings[mappings_len].mappings_alloc = 0; 880 mappings[mappings_len].mappings_len = 0; 881 mappings_len++; 882 } 883 884 static void 885 push_text_mapping(char *text1, size_t len1, char *text2, size_t len2) { 886 if (mappings_len == 0) 887 return; /* I guess just fail silently... */ 888 ltk_language_mapping *m = &mappings[mappings_len - 1]; 889 if (m->mappings_alloc == m->mappings_len) { 890 m->mappings_alloc = ideal_array_size(m->mappings_alloc, m->mappings_len + 1); 891 m->mappings = ltk_reallocarray(m->mappings, m->mappings_alloc, sizeof(ltk_keytext_mapping)); 892 } 893 m->mappings[m->mappings_len].from = ltk_strndup(text1, len1); 894 m->mappings[m->mappings_len].to = ltk_strndup(text2, len2); 895 m->mappings_len++; 896 } 897 898 void 899 ltk_config_cleanup(ltk_renderdata *renderdata) { 900 if (mappings) { 901 for (size_t i = 0; i < mappings_len; i++) { 902 ltk_free(mappings[i].lang); 903 for (size_t j = 0; j < mappings[i].mappings_len; j++) { 904 ltk_free(mappings[i].mappings[j].from); 905 ltk_free(mappings[i].mappings[j].to); 906 } 907 ltk_free(mappings[i].mappings); 908 } 909 ltk_free(mappings); 910 mappings = NULL; 911 mappings_len = mappings_alloc = 0; 912 } 913 uninitialize_theme(renderdata); 914 } 915 916 /* FIXME: error if not initialized */ 917 ltk_general_config * 918 ltk_config_get_general(void) { 919 return &general_config; 920 } 921 922 int 923 ltk_config_get_language_index(char *lang, size_t *idx_ret) { 924 if (!mappings) 925 return 1; 926 for (size_t i = 0; i < mappings_len; i++) { 927 if (!strcmp(lang, mappings[i].lang)) { 928 *idx_ret = i; 929 return 0; 930 } 931 } 932 return 1; 933 } 934 935 ltk_language_mapping * 936 ltk_config_get_language_mapping(size_t idx) { 937 if (idx >= mappings_len) 938 return NULL; 939 return &mappings[idx]; 940 } 941 942 static int 943 str_array_prefix(const char *str, const char *ar, size_t len) { 944 size_t slen = strlen(str); 945 if (len < slen) 946 return 0; 947 return !strncmp(str, ar, slen); 948 } 949 950 /* FIXME: The current model is kind of weird because most parts of the config 951 are stored in the other object files. This makes it difficult to support 952 reloading of the config since the old config needs to be kept until the 953 new config has been successfully loaded, but the parseinfos include direct 954 pointers to the config. It might be better to just have one huge config 955 struct that includes everything. That would also make it a bit clearer when 956 something hasn't been initialized yet. */ 957 /* WARNING: errstr must be freed! */ 958 /* FIXME: make ltk_load_file give size_t; handle errors there (copy from ledit) */ 959 static int 960 load_from_text( 961 ltk_renderdata *renderdata, 962 const char *filename, 963 char *file_contents, 964 size_t len, 965 char **errstr) { 966 sort_keysyms(); 967 sort_keybindings(); 968 sort_themehandlers(); 969 970 struct lexstate s = {filename, file_contents, len, 0, 1, 0}; 971 struct token tok = next_token(&s); 972 int start_of_line = 1; 973 char *msg = NULL; 974 struct token secttok; 975 txtbuf *themeval = txtbuf_new(); 976 while (tok.type != END) { 977 switch (tok.type) { 978 case SECTION: 979 if (!start_of_line) { 980 msg = "Section can only start at new line"; 981 goto error; 982 } 983 secttok = tok; 984 tok = next_token(&s); 985 if (tok.type != NEWLINE && tok.type != END) { 986 msg = "Section must be alone on line"; 987 goto error; 988 } 989 if (str_array_prefix("key-binding:", secttok.text, secttok.len)) { 990 int ret = 0; 991 char *widget = secttok.text + strlen("key-binding:"); 992 size_t len = secttok.len - strlen("key-binding:"); 993 while (1) { 994 if ((ret = parse_keybinding(&s, &tok, widget, len, errstr)) > 0) { 995 goto errornomsg; 996 } else if (ret < 0) { 997 start_of_line = 1; 998 break; 999 } 1000 } 1001 } else if (str_array_equal("key-mapping", secttok.text, secttok.len)) { 1002 int lang_init = 0; 1003 push_lang_mapping(); 1004 struct token prev1tok, prev2tok; 1005 while (1) { 1006 tok = next_token(&s); 1007 if (tok.type == SECTION || tok.type == END) 1008 break; 1009 else if (tok.type == NEWLINE) 1010 continue; 1011 prev2tok = tok; 1012 tok = next_token(&s); 1013 prev1tok = tok; 1014 tok = next_token(&s); 1015 if (prev2tok.type != STRING) { 1016 msg = "Invalid statement in language mapping"; 1017 goto error; 1018 } 1019 if (str_array_equal("language", prev2tok.text, prev2tok.len)) { 1020 if (prev1tok.type != EQUALS || tok.type != STRING) { 1021 msg = "Invalid language assignment"; 1022 goto error; 1023 } else if (lang_init) { 1024 msg = "Language already set"; 1025 goto error; 1026 } 1027 mappings[mappings_len - 1].lang = ltk_strndup(tok.text, tok.len); 1028 lang_init = 1; 1029 } else if (str_array_equal("map", prev2tok.text, prev2tok.len)) { 1030 if (prev1tok.type != STRING || tok.type != STRING) { 1031 msg = "Invalid map statement"; 1032 goto error; 1033 } 1034 push_text_mapping(prev1tok.text, prev1tok.len, tok.text, tok.len); 1035 } else { 1036 msg = "Invalid statement in language mapping"; 1037 goto error; 1038 } 1039 tok = next_token(&s); 1040 if (tok.type == END) { 1041 break; 1042 } else if (tok.type != NEWLINE) { 1043 msg = "Invalid statement in language mapping"; 1044 goto error; 1045 } 1046 start_of_line = 1; 1047 } 1048 if (!lang_init) { 1049 msg = "Language not set for language mapping"; 1050 goto error; 1051 } 1052 } else { 1053 struct token prev1tok, prev2tok; 1054 struct theme_handlerinfo *handler = themehandler_get_entry( 1055 theme_handlers, LENGTH(theme_handlers), secttok.text, secttok.len 1056 ); 1057 if (!handler) { 1058 msg = "Invalid section"; 1059 goto error; 1060 } 1061 ltk_theme_parseinfo *parseinfo; 1062 size_t parseinfo_len; 1063 handler->get_parseinfo(&parseinfo, &parseinfo_len); 1064 while (1) { 1065 tok = next_token(&s); 1066 if (tok.type == SECTION || tok.type == END) 1067 break; 1068 else if (tok.type == NEWLINE) 1069 continue; 1070 prev2tok = tok; 1071 tok = next_token(&s); 1072 prev1tok = tok; 1073 tok = next_token(&s); 1074 if (prev2tok.type != STRING || prev1tok.type != EQUALS || tok.type != STRING) { 1075 msg = "Syntax error in assignment statement"; 1076 goto error; 1077 } 1078 ltk_theme_parseinfo *parse_entry = theme_get_entry( 1079 parseinfo, parseinfo_len, prev2tok.text, prev2tok.len 1080 ); 1081 if (!parse_entry) { 1082 msg = "Invalid left-hand side in assignment statement"; 1083 goto error; 1084 } else if (parse_entry->initialized) { 1085 msg = "Duplicate assignment"; 1086 goto error; 1087 } 1088 /* temporarly copy to txtbuf so it is NUL-terminated (the alternative 1089 would be to use replacements for ltk_strtonum, etc. that accept 1090 a length parameter) */ 1091 txtbuf_set_textn(themeval, tok.text, tok.len); 1092 if (handle_theme_setting(renderdata, parse_entry, themeval->text)) { 1093 msg = "Invalid right-hand side in assignment"; 1094 goto error; 1095 } 1096 tok = next_token(&s); 1097 if (tok.type == END) { 1098 break; 1099 } else if (tok.type != NEWLINE) { 1100 msg = "Syntax error in assignment statement"; 1101 goto error; 1102 } 1103 start_of_line = 1; 1104 } 1105 } 1106 break; 1107 case NEWLINE: 1108 start_of_line = 1; 1109 break; 1110 default: 1111 msg = "Invalid token"; 1112 goto error; 1113 break; 1114 } 1115 } 1116 /* FIXME: better error reporting */ 1117 if (fill_theme_defaults(renderdata)) { 1118 *errstr = ltk_strdup("Unable to load theme defaults"); 1119 goto errornomsg; 1120 } 1121 txtbuf_destroy(themeval); 1122 return 0; 1123 error: 1124 if (msg) { 1125 *errstr = ltk_print_fmt( 1126 "%s, line %zu, offset %zu: %s", filename, tok.line, tok.line_offset, msg 1127 ); 1128 } 1129 errornomsg: 1130 ltk_config_cleanup(renderdata); 1131 txtbuf_destroy(themeval); 1132 return 1; 1133 } 1134 1135 int 1136 ltk_config_parsefile(ltk_renderdata *renderdata, const char *filename, char **errstr) { 1137 unsigned long len = 0; 1138 char *ferrstr = NULL; 1139 char *file_contents = ltk_read_file(filename, &len, &ferrstr); 1140 if (!file_contents) { 1141 *errstr = ltk_print_fmt("Unable to open file \"%s\": %s", filename, ferrstr); 1142 return 1; 1143 } 1144 int ret = load_from_text(renderdata, filename, file_contents, len, errstr); 1145 ltk_free(file_contents); 1146 return ret; 1147 } 1148 1149 /* FIXME: update this */ 1150 static const char *default_config = "[general]\n" 1151 "explicit-focus = true\n" 1152 "all-activatable = true\n" 1153 "[key-binding:window]\n" 1154 "bind-keypress move-next sym tab\n" 1155 "bind-keypress move-prev sym tab mods shift\n" 1156 "bind-keypress move-next text n\n" 1157 "bind-keypress move-prev text p\n" 1158 "bind-keypress focus-active sym return\n" 1159 "bind-keypress unfocus-active sym escape\n" 1160 "bind-keypress set-pressed sym return flags run-always\n" 1161 "bind-keyrelease unset-pressed sym return flags run-always\n" 1162 "[key-mapping]\n" 1163 "language = \"English (US)\"\n"; 1164 1165 /* FIXME: improve this configuration */ 1166 int 1167 ltk_config_load_default(ltk_renderdata *renderdata, char **errstr) { 1168 char *config_copied = ltk_strdup(default_config); 1169 int ret = load_from_text(renderdata, "<default config>", config_copied, strlen(config_copied), errstr); 1170 ltk_free(config_copied); 1171 return ret; 1172 } 1173 1174 /* FIXME: which additional ones are needed here? */ 1175 static struct keysym_mapping { 1176 char *name; 1177 ltk_keysym keysym; 1178 } keysym_map[] = { 1179 {"backspace", LTK_KEY_BACKSPACE}, 1180 {"begin", LTK_KEY_BEGIN}, 1181 {"break", LTK_KEY_BREAK}, 1182 {"cancel", LTK_KEY_CANCEL}, 1183 {"clear", LTK_KEY_CLEAR}, 1184 {"delete", LTK_KEY_DELETE}, 1185 {"down", LTK_KEY_DOWN}, 1186 {"end", LTK_KEY_END}, 1187 {"escape", LTK_KEY_ESCAPE}, 1188 {"execute", LTK_KEY_EXECUTE}, 1189 1190 {"f1", LTK_KEY_F1}, 1191 {"f10", LTK_KEY_F10}, 1192 {"f11", LTK_KEY_F11}, 1193 {"f12", LTK_KEY_F12}, 1194 {"f2", LTK_KEY_F2}, 1195 {"f3", LTK_KEY_F3}, 1196 {"f4", LTK_KEY_F4}, 1197 {"f5", LTK_KEY_F5}, 1198 {"f6", LTK_KEY_F6}, 1199 {"f7", LTK_KEY_F7}, 1200 {"f8", LTK_KEY_F8}, 1201 {"f9", LTK_KEY_F9}, 1202 1203 {"find", LTK_KEY_FIND}, 1204 {"help", LTK_KEY_HELP}, 1205 {"home", LTK_KEY_HOME}, 1206 {"insert", LTK_KEY_INSERT}, 1207 1208 {"kp-0", LTK_KEY_KP_0}, 1209 {"kp-1", LTK_KEY_KP_1}, 1210 {"kp-2", LTK_KEY_KP_2}, 1211 {"kp-3", LTK_KEY_KP_3}, 1212 {"kp-4", LTK_KEY_KP_4}, 1213 {"kp-5", LTK_KEY_KP_5}, 1214 {"kp-6", LTK_KEY_KP_6}, 1215 {"kp-7", LTK_KEY_KP_7}, 1216 {"kp-8", LTK_KEY_KP_8}, 1217 {"kp-9", LTK_KEY_KP_9}, 1218 {"kp-add", LTK_KEY_KP_ADD}, 1219 {"kp-begin", LTK_KEY_KP_BEGIN}, 1220 {"kp-decimal", LTK_KEY_KP_DECIMAL}, 1221 {"kp-delete", LTK_KEY_KP_DELETE}, 1222 {"kp-divide", LTK_KEY_KP_DIVIDE}, 1223 {"kp-down", LTK_KEY_KP_DOWN}, 1224 {"kp-end", LTK_KEY_KP_END}, 1225 {"kp-enter", LTK_KEY_KP_ENTER}, 1226 {"kp-equal", LTK_KEY_KP_EQUAL}, 1227 {"kp-home", LTK_KEY_KP_HOME}, 1228 {"kp-insert", LTK_KEY_KP_INSERT}, 1229 {"kp-left", LTK_KEY_KP_LEFT}, 1230 {"kp-multiply", LTK_KEY_KP_MULTIPLY}, 1231 {"kp-next", LTK_KEY_KP_NEXT}, 1232 {"kp-page-down", LTK_KEY_KP_PAGE_DOWN}, 1233 {"kp-page-up", LTK_KEY_KP_PAGE_UP}, 1234 {"kp-prior", LTK_KEY_KP_PRIOR}, 1235 {"kp-right", LTK_KEY_KP_RIGHT}, 1236 {"kp-separator", LTK_KEY_KP_SEPARATOR}, 1237 {"kp-space", LTK_KEY_KP_SPACE}, 1238 {"kp-subtract", LTK_KEY_KP_SUBTRACT}, 1239 {"kp-tab", LTK_KEY_KP_TAB}, 1240 {"kp-up", LTK_KEY_KP_UP}, 1241 1242 {"left", LTK_KEY_LEFT}, 1243 {"left-tab", LTK_KEY_LEFT_TAB}, 1244 {"linefeed", LTK_KEY_LINEFEED}, 1245 {"menu", LTK_KEY_MENU}, 1246 {"mode-switch", LTK_KEY_MODE_SWITCH}, 1247 {"next", LTK_KEY_NEXT}, 1248 {"num-lock", LTK_KEY_NUM_LOCK}, 1249 {"page-down", LTK_KEY_PAGE_DOWN}, 1250 {"page-up", LTK_KEY_PAGE_UP}, 1251 {"pause", LTK_KEY_PAUSE}, 1252 {"print", LTK_KEY_PRINT}, 1253 {"prior", LTK_KEY_PRIOR}, 1254 1255 {"redo", LTK_KEY_REDO}, 1256 {"return", LTK_KEY_RETURN}, 1257 {"right", LTK_KEY_RIGHT}, 1258 {"script-switch", LTK_KEY_SCRIPT_SWITCH}, 1259 {"scroll-lock", LTK_KEY_SCROLL_LOCK}, 1260 {"select", LTK_KEY_SELECT}, 1261 {"space", LTK_KEY_SPACE}, 1262 {"sysreq", LTK_KEY_SYS_REQ}, 1263 {"tab", LTK_KEY_TAB}, 1264 {"up", LTK_KEY_UP}, 1265 {"undo", LTK_KEY_UNDO}, 1266 }; 1267 1268 GEN_SORT_SEARCH_HELPERS(keysym, struct keysym_mapping, name) 1269 1270 static void 1271 sort_keysyms(void) { 1272 keysym_sort(keysym_map, LENGTH(keysym_map)); 1273 } 1274 1275 static int 1276 parse_keysym(char *keysym_str, size_t len, ltk_keysym *sym) { 1277 struct keysym_mapping *km = keysym_get_entry(keysym_map, LENGTH(keysym_map), keysym_str, len); 1278 if (!km) 1279 return 1; 1280 *sym = km->keysym; 1281 return 0; 1282 } 1283 1284 /* FIXME: this is really ugly */ 1285 /* FIXME: this handles double-quote, but the config parser already uses that, so 1286 it's kind of weird because it's parsed twice (also backslashes are parsed twice). */ 1287 static ltk_array(cmd) * 1288 ltk_parse_cmd(const char *cmdtext, size_t len) { 1289 int bs = 0; 1290 int in_sqstr = 0; 1291 int in_dqstr = 0; 1292 int in_ws = 1; 1293 int inout_used = 0, input_used = 0, output_used = 0; 1294 char c; 1295 size_t cur_start = 0; 1296 int offset = 0; 1297 ltk_array(cmdpiece) *cur_arg = ltk_array_create(cmdpiece, 1); 1298 ltk_array(cmd) *cmd = ltk_array_create(cmd, 4); 1299 char *cmdcopy = ltk_strndup(cmdtext, len); 1300 for (size_t i = 0; i < len; i++) { 1301 c = cmdcopy[i]; 1302 if (c == '\\') { 1303 if (bs) { 1304 offset++; 1305 bs = 0; 1306 } else { 1307 bs = 1; 1308 } 1309 } else if (isspace(c)) { 1310 if (!in_sqstr && !in_dqstr) { 1311 if (bs) { 1312 if (in_ws) { 1313 in_ws = 0; 1314 cur_start = i; 1315 offset = 0; 1316 } else { 1317 offset++; 1318 } 1319 bs = 0; 1320 } else if (!in_ws) { 1321 /* FIXME: shouldn't this be < instead of <=? */ 1322 if (cur_start <= i - offset) { 1323 struct ltk_cmd_piece p = {ltk_strndup(cmdcopy + cur_start, i - cur_start - offset), LTK_CMD_TEXT}; 1324 ltk_array_append(cmdpiece, cur_arg, p); 1325 } 1326 /* FIXME: cmd is named horribly */ 1327 ltk_array_append(cmd, cmd, cur_arg); 1328 cur_arg = ltk_array_create(cmdpiece, 1); 1329 in_ws = 1; 1330 offset = 0; 1331 } 1332 /* FIXME: parsing weird here - bs just ignored */ 1333 } else if (bs) { 1334 bs = 0; 1335 } 1336 } else if (c == '%') { 1337 if (bs) { 1338 if (in_ws) { 1339 cur_start = i; 1340 offset = 0; 1341 } else { 1342 offset++; 1343 } 1344 bs = 0; 1345 } else if (!in_sqstr && i < len - 1 && (cmdcopy[i + 1] == 'f' || cmdcopy[i + 1] == 'i' || cmdcopy[i + 1] == 'o')) { 1346 if (!in_ws && cur_start < i - offset) { 1347 struct ltk_cmd_piece p = {ltk_strndup(cmdcopy + cur_start, i - cur_start - offset), LTK_CMD_TEXT}; 1348 ltk_array_append(cmdpiece, cur_arg, p); 1349 } 1350 struct ltk_cmd_piece p = {NULL, LTK_CMD_INOUT_FILE}; 1351 switch (cmdcopy[i + 1]) { 1352 case 'f': 1353 p.type = LTK_CMD_INOUT_FILE; 1354 if (input_used || output_used) 1355 goto error; 1356 inout_used = 1; 1357 break; 1358 case 'i': 1359 p.type = LTK_CMD_INPUT_FILE; 1360 if (inout_used) 1361 goto error; 1362 input_used = 1; 1363 break; 1364 case 'o': 1365 p.type = LTK_CMD_OUTPUT_FILE; 1366 if (inout_used) 1367 goto error; 1368 output_used = 1; 1369 break; 1370 default: 1371 ltk_fatal("Impossible."); 1372 } 1373 ltk_array_append(cmdpiece, cur_arg, p); 1374 i++; 1375 cur_start = i + 1; 1376 offset = 0; 1377 } else if (in_ws) { 1378 cur_start = i; 1379 offset = 0; 1380 } 1381 in_ws = 0; 1382 } else if (c == '"') { 1383 if (in_sqstr) { 1384 bs = 0; 1385 } else if (bs) { 1386 if (in_ws) { 1387 cur_start = i; 1388 offset = 0; 1389 } else { 1390 offset++; 1391 } 1392 bs = 0; 1393 } else if (in_dqstr) { 1394 offset++; 1395 in_dqstr = 0; 1396 continue; 1397 } else { 1398 in_dqstr = 1; 1399 if (in_ws) { 1400 cur_start = i + 1; 1401 offset = 0; 1402 } else { 1403 offset++; 1404 continue; 1405 } 1406 } 1407 in_ws = 0; 1408 } else if (c == '\'') { 1409 if (in_dqstr) { 1410 bs = 0; 1411 } else if (bs) { 1412 if (in_ws) { 1413 cur_start = i; 1414 offset = 0; 1415 } else { 1416 offset++; 1417 } 1418 bs = 0; 1419 } else if (in_sqstr) { 1420 offset++; 1421 in_sqstr = 0; 1422 continue; 1423 } else { 1424 in_sqstr = 1; 1425 if (in_ws) { 1426 cur_start = i + 1; 1427 offset = 0; 1428 } else { 1429 offset++; 1430 continue; 1431 } 1432 } 1433 in_ws = 0; 1434 } else if (bs) { 1435 if (!in_sqstr && !in_dqstr) { 1436 if (in_ws) { 1437 cur_start = i; 1438 offset = 0; 1439 } else { 1440 offset++; 1441 } 1442 } 1443 bs = 0; 1444 in_ws = 0; 1445 } else { 1446 if (in_ws) { 1447 cur_start = i; 1448 offset = 0; 1449 } 1450 in_ws = 0; 1451 } 1452 cmdcopy[i - offset] = cmdcopy[i]; 1453 } 1454 /* FIXME: proper error messages with errstr */ 1455 if (in_sqstr || in_dqstr) { 1456 /*ltk_warn("Unterminated string in command\n");*/ 1457 goto error; 1458 } 1459 if (!in_ws) { 1460 if (cur_start <= len - offset) { 1461 struct ltk_cmd_piece p = {ltk_strndup(cmdcopy + cur_start, len - cur_start - offset), LTK_CMD_TEXT}; 1462 ltk_array_append(cmdpiece, cur_arg, p); 1463 } 1464 ltk_array_append(cmd, cmd, cur_arg); 1465 cur_arg = NULL; 1466 } 1467 if (cmd->len == 0) { 1468 /*ltk_warn("Empty command\n");*/ 1469 goto error; 1470 } 1471 ltk_free(cmdcopy); 1472 return cmd; 1473 error: 1474 ltk_free(cmdcopy); 1475 if (cur_arg) 1476 ltk_array_destroy_deep(cmdpiece, cur_arg, &cmd_piece_free_helper); 1477 ltk_array_destroy_deep(cmd, cmd, &cmd_free_helper); 1478 return NULL; 1479 }