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

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", &ltk_combobox_get_keybinding_parseinfo},
     71 	{"entry", &ltk_entry_get_keybinding_parseinfo},
     72 	{"window", &ltk_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", &ltk_general_get_theme_parseinfo, NULL, 0},
     82 	{"theme:window", &ltk_window_get_theme_parseinfo, NULL, 0},
     83 	{"theme:button", &ltk_button_get_theme_parseinfo, "theme:window", 0},
     84 	{"theme:entry", &ltk_entry_get_theme_parseinfo, "theme:window", 0},
     85 	{"theme:label", &ltk_label_get_theme_parseinfo, "theme:window", 0},
     86 	{"theme:scrollbar", &ltk_scrollbar_get_theme_parseinfo, "theme:window", 0},
     87 	{"theme:menu", &ltk_menu_get_theme_parseinfo, "theme:window", 0},
     88 	{"theme:menuentry", &ltk_menuentry_get_theme_parseinfo, "theme:window", 0},
     89 	{"theme:submenu", &ltk_submenu_get_theme_parseinfo, "theme:window", 0},
     90 	{"theme:submenuentry", &ltk_submenuentry_get_theme_parseinfo, "theme:window", 0},
     91 	{"theme:checkbutton", &ltk_checkbutton_get_theme_parseinfo, "theme:window", 0},
     92 	{"theme:radiobutton", &ltk_radiobutton_get_theme_parseinfo, "theme:window", 0},
     93 	{"theme:combobox", &ltk_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 }