ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

config.c (21569B)


      1 /* FIXME: This really is horrible. */
      2 
      3 #include <stdio.h>
      4 #include <ctype.h>
      5 #include <string.h>
      6 #include <limits.h>
      7 
      8 #include "util.h"
      9 #include "keys.h"
     10 #include "memory.h"
     11 #include "config.h"
     12 
     13 static int parse_keysym(char *text, size_t len, ltk_keysym *sym_ret);
     14 
     15 ltk_config *global_config = NULL;
     16 
     17 enum toktype {
     18 	STRING,
     19 	SECTION,
     20 	EQUALS,
     21 	NEWLINE,
     22 	ERROR,
     23 	END
     24 };
     25 
     26 #if 0
     27 static const char *
     28 toktype_str(enum toktype type) {
     29 	switch (type) {
     30 	case STRING:
     31 		return "string";
     32 		break;
     33 	case SECTION:
     34 		return "section";
     35 		break;
     36 	case EQUALS:
     37 		return "equals";
     38 		break;
     39 	case NEWLINE:
     40 		return "newline";
     41 		break;
     42 	case ERROR:
     43 		return "error";
     44 		break;
     45 	case END:
     46 		return "end of file";
     47 		break;
     48 	default:
     49 		return "unknown";
     50 	}
     51 }
     52 #endif
     53 
     54 struct token {
     55 	char *text;
     56 	size_t len;
     57 	enum toktype type;
     58 	size_t line;        /* line in original input */
     59 	size_t line_offset; /* offset from start of line */
     60 };
     61 
     62 struct lexstate {
     63 	const char *filename;
     64 	char *text;
     65 	size_t len;        /* length of text */
     66 	size_t cur;        /* current byte position */
     67 	size_t cur_line;   /* current line */
     68 	size_t line_start; /* byte offset of start of current line */
     69 };
     70 
     71 static struct token
     72 next_token(struct lexstate *s) {
     73 	char c;
     74 	struct token tok;
     75 	int finished = 0;
     76 	while (1) {
     77 		if (s->cur >= s->len)
     78 			return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1};
     79 		while (isspace(c = s->text[s->cur])) {
     80 			s->cur++;
     81 			if (c == '\n') {
     82 				struct token tok = (struct token){s->text + s->cur - 1, 1, NEWLINE, s->cur_line, s->cur - s->line_start};
     83 				s->cur_line++;
     84 				s->line_start = s->cur;
     85 				return tok;
     86 			}
     87 			if (s->cur >= s->len)
     88 				return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1};
     89 		}
     90 
     91 		switch (s->text[s->cur]) {
     92 		case '#':
     93 			s->cur++;
     94 			while (s->cur < s->len && s->text[s->cur] != '\n')
     95 				s->cur++;
     96 			continue;
     97 		case '[':
     98 			s->cur++;
     99 			tok = (struct token){s->text + s->cur, 0, SECTION, s->cur_line, s->cur - s->line_start + 1};
    100 			finished = 0;
    101 			while (s->cur < s->len) {
    102 				char c = s->text[s->cur];
    103 				if (c == '\n') {
    104 					break;
    105 				} else if (c == ']') {
    106 					s->cur++;
    107 					finished = 1;
    108 					break;
    109 				} else  {
    110 					tok.len++;
    111 				}
    112 				s->cur++;
    113 			}
    114 			if (!finished) {
    115 				tok.text = "Unfinished section name";
    116 				tok.len = strlen("Unfinished section name");
    117 				tok.type = ERROR;
    118 			}
    119 			break;
    120 		case '=':
    121 			tok = (struct token){s->text + s->cur, 1, EQUALS, s->cur_line, s->cur - s->line_start + 1};
    122 			s->cur++;
    123 			break;
    124 		case '"':
    125 			/* FIXME: error if next char is not whitespace or end */
    126 			s->cur++;
    127 			tok = (struct token){s->text + s->cur, 0, STRING, s->cur_line, s->cur - s->line_start + 1};
    128 			size_t shift = 0, bs = 0;
    129 			finished = 0;
    130 			while (s->cur < s->len) {
    131 				char c = s->text[s->cur];
    132 				if (c == '\n') {
    133 					break;
    134 				} else if (c == '\\') {
    135 					shift += bs;
    136 					tok.len += bs;
    137 					bs = (bs + 1) % 2;
    138 				} else if (c == '"') {
    139 					if (bs) {
    140 						shift++;
    141 						tok.len++;
    142 						bs = 0;
    143 					} else {
    144 						s->cur++;
    145 						finished = 1;
    146 						break;
    147 					}
    148 				} else {
    149 					tok.len++;
    150 				}
    151 				s->text[s->cur - shift] = s->text[s->cur];
    152 				s->cur++;
    153 			}
    154 			if (!finished) {
    155 				tok.text = "Unfinished string";
    156 				tok.len = strlen("Unfinished string");
    157 				tok.type = ERROR;
    158 			}
    159 			break;
    160 		default:
    161 			tok = (struct token){s->text + s->cur, 1, STRING, s->cur_line, s->cur - s->line_start + 1};
    162 			s->cur++;
    163 			while (s->cur < s->len) {
    164 				char c = s->text[s->cur];
    165 				if (isspace(c) || c == '=') {
    166 					break;
    167 				} else if (c == '"') {
    168 					tok.text = "Unexpected start of string";
    169 					tok.len = strlen("Unexpected start of string");
    170 					tok.type = ERROR;
    171 					tok.line_offset = s->cur - s->line_start + 1;
    172 				} else if (c == '[' || c == ']') {
    173 					tok.text = "Unexpected start or end of section name";
    174 					tok.len = strlen("Unexpected start or end of section name");
    175 					tok.type = ERROR;
    176 					tok.line_offset = s->cur - s->line_start + 1;
    177 				}
    178 				tok.len++;
    179 				s->cur++;
    180 			}
    181 		}
    182 		return tok;
    183 	}
    184 }
    185 
    186 #undef MIN
    187 #define MIN(a, b) ((a) < (b) ? (a) : (b))
    188 
    189 static int
    190 parse_modmask(char *modmask_str, size_t len, ltk_mod_type *mask_ret) {
    191 	size_t cur = 0;
    192 	*mask_ret = 0;
    193 	while (cur < len) {
    194 		if (str_array_equal("shift", modmask_str + cur, MIN(5, len - cur))) {
    195 			cur += 5;
    196 			*mask_ret |= LTK_MOD_SHIFT;
    197 		} else if (str_array_equal("ctrl", modmask_str + cur, MIN(4, len - cur))) {
    198 			cur += 4;
    199 			*mask_ret |= LTK_MOD_CTRL;
    200 		} else if (str_array_equal("alt", modmask_str + cur, MIN(3, len - cur))) {
    201 			cur += 3;
    202 			*mask_ret |= LTK_MOD_ALT;
    203 		} else if (str_array_equal("super", modmask_str + cur, MIN(5, len - cur))) {
    204 			cur += 5;
    205 			*mask_ret |= LTK_MOD_SUPER;
    206 		} else if (str_array_equal("any", modmask_str + cur, MIN(3, len - cur))) {
    207 			cur += 3;
    208 			*mask_ret = UINT_MAX;
    209 		} else {
    210 			return 1;
    211 		}
    212 		if (cur < len && modmask_str[cur] != '|')
    213 			return 1;
    214 		else
    215 			cur++;
    216 	}
    217 	return 0;
    218 }
    219 
    220 static int
    221 parse_flags(char *text, size_t len, ltk_key_binding_flags *flags_ret) {
    222 	if (str_array_equal("run-always", text, len))
    223 		*flags_ret = LTK_KEY_BINDING_RUN_ALWAYS;
    224 	else
    225 		return 1;
    226 	return 0;
    227 }
    228 
    229 static int
    230 parse_keypress_binding(
    231     struct lexstate *s, struct token *tok,
    232     ltk_keypress_binding *binding_ret,
    233     char **func_name_ret, size_t *func_len_ret,
    234     char **errstr) {
    235 	ltk_keypress_binding b = {NULL, NULL, LTK_KEY_NONE, LTK_MOD_NONE, LTK_KEY_BINDING_NOFLAGS};
    236 	*tok = next_token(s);
    237 	char *msg = NULL;
    238 	if (tok->type != STRING) {
    239 		msg = "Invalid token type";
    240 		goto error;
    241 	}
    242 	*func_name_ret = tok->text;
    243 	*func_len_ret = tok->len;
    244 	struct token prevtok;
    245 	int text_init = 0, rawtext_init = 0, sym_init = 0, mods_init = 0, flags_init = 0;
    246 	while (1) {
    247 		*tok = next_token(s);
    248 		if (tok->type == NEWLINE || tok->type == END)
    249 			break;
    250 		prevtok = *tok;
    251 		*tok = next_token(s);
    252 		if (prevtok.type != STRING) {
    253 			msg = "Invalid token type";
    254 			*tok = prevtok;
    255 			goto error;
    256 		} else if (tok->type != STRING) {
    257 			msg = "Invalid token type";
    258 			goto error;
    259 		} else if (str_array_equal("text", prevtok.text, prevtok.len)) {
    260 			if (rawtext_init) {
    261 				msg = "Rawtext already specified";
    262 				goto error;
    263 			} else if (sym_init) {
    264 				msg = "Keysym already specified";
    265 				goto error;
    266 			} else if (text_init) {
    267 				msg = "Duplicate text specification";
    268 				goto error;
    269 			}
    270 			b.text = ltk_strndup(tok->text, tok->len);
    271 			text_init = 1;
    272 		} else if (str_array_equal("rawtext", prevtok.text, prevtok.len)) {
    273 			if (text_init) {
    274 				msg = "Text already specified";
    275 				goto error;
    276 			} else if (sym_init) {
    277 				msg = "Keysym already specified";
    278 				goto error;
    279 			} else if (rawtext_init) {
    280 				msg = "Duplicate rawtext specification";
    281 				goto error;
    282 			}
    283 			b.rawtext = ltk_strndup(tok->text, tok->len);
    284 			rawtext_init = 1;
    285 		} else if (str_array_equal("sym", prevtok.text, prevtok.len)) {
    286 			if (text_init) {
    287 				msg = "Text already specified";
    288 				goto error;
    289 			} else if (rawtext_init) {
    290 				msg = "Rawtext already specified";
    291 				goto error;
    292 			} else if (sym_init) {
    293 				msg = "Duplicate keysym specification";
    294 				goto error;
    295 			} else if (parse_keysym(tok->text, tok->len, &b.sym)) {
    296 				msg = "Invalid keysym specification";
    297 				goto error;
    298 			}
    299 			sym_init = 1;
    300 		} else if (str_array_equal("mods", prevtok.text, prevtok.len)) {
    301 			if (mods_init) {
    302 				msg = "Duplicate mods specification";
    303 				goto error;
    304 			} else if (parse_modmask(tok->text, tok->len, &b.mods)) {
    305 				msg = "Invalid mods specification";
    306 				goto error;
    307 			}
    308 			mods_init = 1;
    309 		} else if (str_array_equal("flags", prevtok.text, prevtok.len)) {
    310 			if (flags_init) {
    311 				msg = "Duplicate flags specification";
    312 				goto error;
    313 			} else if (parse_flags(tok->text, tok->len, &b.flags)) {
    314 				msg = "Invalid flags specification";
    315 				goto error;
    316 			}
    317 			flags_init = 1;
    318 		} else {
    319 			msg = "Invalid keyword";
    320 			*tok = prevtok;
    321 			goto error;
    322 		}
    323 	};
    324 	if (!text_init && !rawtext_init && !sym_init) {
    325 		msg = "One of text, rawtext, and sym must be initialized";
    326 		goto error;
    327 	}
    328 	*binding_ret = b;
    329 	return 0;
    330 error:
    331 	free(b.text);
    332 	free(b.rawtext);
    333 	if (msg) {
    334 		*errstr = ltk_print_fmt(
    335 		    "%s, line %zu, offset %zu: %s", s->filename, tok->line, tok->line_offset, msg
    336 		);
    337 	} else {
    338 		*errstr = NULL;
    339 	}
    340 	return 1;
    341 }
    342 
    343 static int
    344 parse_keybinding(
    345     struct lexstate *s,
    346     struct token *tok,
    347     char *widget,
    348     size_t len,
    349     keypress_binding_handler press_handler,
    350     keyrelease_binding_handler release_handler,
    351     char **errstr) {
    352 	char *msg = NULL;
    353 	*tok = next_token(s);
    354 	if (tok->type == SECTION || tok->type == END) {
    355 		return -1;
    356 	} else if (tok->type == NEWLINE) {
    357 		return 0; /* empty line */
    358 	} else if (tok->type != STRING) {
    359 		msg = "Invalid token type";
    360 		goto error;
    361 	} else if (str_array_equal("bind-keypress", tok->text, tok->len)) {
    362 		ltk_keypress_binding b;
    363 		char *name;
    364 		size_t nlen;
    365 		if (parse_keypress_binding(s, tok, &b, &name, &nlen, errstr))
    366 			return 1;
    367 		if (press_handler(widget, len, name, nlen, b)) {
    368 			msg = "Invalid key binding";
    369 			goto error;
    370 		}
    371 	} else if (str_array_equal("bind-keyrelease", tok->text, tok->len)) {
    372 		ltk_keypress_binding b;
    373 		char *name;
    374 		size_t nlen;
    375 		if (parse_keypress_binding(s, tok, &b, &name, &nlen, errstr))
    376 			return 1;
    377 		if (b.text || b.rawtext) {
    378 			free(b.text);
    379 			free(b.rawtext);
    380 			msg = "Text and rawtext may only be specified for keypress bindings";
    381 			goto error;
    382 		}
    383 		if (release_handler(widget, len, name, nlen, (ltk_keyrelease_binding){b.sym, b.mods, b.flags})) {
    384 			msg = "Invalid key binding";
    385 			goto error;
    386 		}
    387 	} else {
    388 		msg = "Invalid statement";
    389 		goto error;
    390 	}
    391 	return 0;
    392 error:
    393 	if (msg) {
    394 		*errstr = ltk_print_fmt(
    395 		    "%s, line %zu, offset %zu: %s", s->filename, tok->line, tok->line_offset, msg
    396 		);
    397 	}
    398 	return 1;
    399 }
    400 
    401 static void
    402 push_lang_mapping(ltk_config *c) {
    403 	if (c->mappings_alloc == c->mappings_len) {
    404 		c->mappings_alloc = ideal_array_size(c->mappings_alloc, c->mappings_len + 1);
    405 		c->mappings = ltk_reallocarray(c->mappings, c->mappings_alloc, sizeof(ltk_language_mapping));
    406 	}
    407 	c->mappings[c->mappings_len].lang = NULL;
    408 	c->mappings[c->mappings_len].mappings = NULL;
    409 	c->mappings[c->mappings_len].mappings_alloc = 0;
    410 	c->mappings[c->mappings_len].mappings_len = 0;
    411 	c->mappings_len++;
    412 }
    413 
    414 static void
    415 push_text_mapping(ltk_config *c, char *text1, size_t len1, char *text2, size_t len2) {
    416 	if (c->mappings_len == 0)
    417 		return; /* I guess just fail silently... */
    418 	ltk_language_mapping *m = &c->mappings[c->mappings_len - 1];
    419 	if (m->mappings_alloc == m->mappings_len) {
    420 		m->mappings_alloc = ideal_array_size(m->mappings_alloc, m->mappings_len + 1);
    421 		m->mappings = ltk_reallocarray(m->mappings, m->mappings_alloc, sizeof(ltk_keytext_mapping));
    422 	}
    423 	m->mappings[m->mappings_len].from = ltk_strndup(text1, len1);
    424 	m->mappings[m->mappings_len].to = ltk_strndup(text2, len2);
    425 	m->mappings_len++;
    426 }
    427 
    428 static void
    429 destroy_config(ltk_config *c) {
    430 	for (size_t i = 0; i < c->mappings_len; i++) {
    431 		ltk_free(c->mappings[i].lang);
    432 		for (size_t j = 0; j < c->mappings[i].mappings_len; j++) {
    433 			ltk_free(c->mappings[i].mappings[j].from);
    434 			ltk_free(c->mappings[i].mappings[j].to);
    435 		}
    436 		ltk_free(c->mappings[i].mappings);
    437 	}
    438 	ltk_free(c->general.line_editor);
    439 	ltk_free(c->mappings);
    440 	ltk_free(c);
    441 }
    442 
    443 void
    444 ltk_config_cleanup(void) {
    445 	if (global_config)
    446 		destroy_config(global_config);
    447 	global_config = NULL;
    448 }
    449 
    450 ltk_config *
    451 ltk_config_get(void) {
    452 	return global_config;
    453 }
    454 
    455 int
    456 ltk_config_get_language_index(char *lang, size_t *idx_ret) {
    457 	if (!global_config)
    458 		return 1;
    459 	for (size_t i = 0; i < global_config->mappings_len; i++) {
    460 		if (!strcmp(lang, global_config->mappings[i].lang)) {
    461 			*idx_ret = i;
    462 			return 0;
    463 		}
    464 	}
    465 	return 1;
    466 }
    467 
    468 ltk_language_mapping *
    469 ltk_config_get_language_mapping(size_t idx) {
    470 	if (!global_config || idx >= global_config->mappings_len)
    471 		return NULL;
    472 	return &global_config->mappings[idx];
    473 }
    474 
    475 int
    476 str_array_prefix(const char *str, const char *ar, size_t len) {
    477 	size_t slen = strlen(str);
    478 	if (len < slen)
    479 		return 0;
    480 	return !strncmp(str, ar, slen);
    481 }
    482 
    483 /* WARNING: errstr must be freed! */
    484 /* FIXME: make ltk_load_file give size_t; handle errors there (copy from ledit) */
    485 static int
    486 load_from_text(
    487     const char *filename,
    488     char *file_contents,
    489     size_t len,
    490     keypress_binding_handler press_handler,
    491     keyrelease_binding_handler release_handler,
    492     char **errstr) {
    493 	ltk_config *config = ltk_malloc(sizeof(ltk_config));
    494 	config->mappings = NULL;
    495 	config->mappings_alloc = config->mappings_len = 0;
    496 	config->general.explicit_focus = 0;
    497 	config->general.all_activatable = 0;
    498 	config->general.line_editor = NULL;
    499 
    500 	struct lexstate s = {filename, file_contents, len, 0, 1, 0};
    501 	struct token tok = next_token(&s);
    502 	int start_of_line = 1;
    503 	char *msg = NULL;
    504 	struct token secttok;
    505 	while (tok.type != END) {
    506 		switch (tok.type) {
    507 		case SECTION:
    508 			if (!start_of_line) {
    509 				msg = "Section can only start at new line";
    510 				goto error;
    511 			}
    512 			secttok = tok;
    513 			tok = next_token(&s);
    514 			if (tok.type != NEWLINE && tok.type != END) {
    515 				msg = "Section must be alone on line";
    516 				goto error;
    517 			}
    518 			/* FIXME: generalize (at least once more options are added) */
    519 			if (str_array_equal("general", secttok.text, secttok.len)) {
    520 				struct token prev1tok, prev2tok;
    521 				while (1) {
    522 					tok = next_token(&s);
    523 					if (tok.type == SECTION || tok.type == END)
    524 						break;
    525 					else if (tok.type == NEWLINE)
    526 						continue;
    527 					prev2tok = tok;
    528 					tok = next_token(&s);
    529 					prev1tok = tok;
    530 					tok = next_token(&s);
    531 					if (prev2tok.type != STRING || prev1tok.type != EQUALS || tok.type != STRING) {
    532 						msg = "Invalid assignment statement";
    533 						goto error;
    534 					}
    535 					if (str_array_equal("explicit-focus", prev2tok.text, prev2tok.len)) {
    536 						if (str_array_equal("true", tok.text, tok.len)) {
    537 							config->general.explicit_focus = 1;
    538 						} else if (str_array_equal("false", tok.text, tok.len)) {
    539 							config->general.explicit_focus = 0;
    540 						} else {
    541 							msg = "Invalid boolean setting";
    542 							goto error;
    543 						}
    544 					} else if (str_array_equal("all-activatable", prev2tok.text, prev2tok.len)) {
    545 						if (str_array_equal("true", tok.text, tok.len)) {
    546 							config->general.all_activatable = 1;
    547 						} else if (str_array_equal("false", tok.text, tok.len)) {
    548 							config->general.all_activatable = 0;
    549 						} else {
    550 							msg = "Invalid boolean setting";
    551 							goto error;
    552 						}
    553 					} else if (str_array_equal("line-editor", prev2tok.text, prev2tok.len)) {
    554 						config->general.line_editor = ltk_strndup(tok.text, tok.len);
    555 					} else {
    556 						msg = "Invalid setting";
    557 						goto error;
    558 					}
    559 					tok = next_token(&s);
    560 					if (tok.type == END) {
    561 						break;
    562 					} else if (tok.type != NEWLINE) {
    563 						msg = "Invalid assignment statement";
    564 						goto error;
    565 					}
    566 					start_of_line = 1;
    567 				}
    568 			} else if (str_array_prefix("key-binding:", secttok.text, secttok.len)) {
    569 				int ret = 0;
    570 				char *widget = secttok.text + strlen("key-binding:");
    571 				size_t len = secttok.len - strlen("key-binding:");
    572 				while (1) {
    573 					if ((ret = parse_keybinding(&s, &tok, widget, len, press_handler, release_handler, errstr)) > 0) {
    574 						goto errornomsg;
    575 					} else if (ret < 0) {
    576 						start_of_line = 1;
    577 						break;
    578 					}
    579 				}
    580 			} else if (str_array_equal("key-mapping", secttok.text, secttok.len)) {
    581 				int lang_init = 0;
    582 				push_lang_mapping(config);
    583 				struct token prev1tok, prev2tok;
    584 				while (1) {
    585 					tok = next_token(&s);
    586 					if (tok.type == SECTION || tok.type == END)
    587 						break;
    588 					else if (tok.type == NEWLINE)
    589 						continue;
    590 					prev2tok = tok;
    591 					tok = next_token(&s);
    592 					prev1tok = tok;
    593 					tok = next_token(&s);
    594 					if (prev2tok.type != STRING) {
    595 						msg = "Invalid statement in language mapping";
    596 						goto error;
    597 					}
    598 					if (str_array_equal("language", prev2tok.text, prev2tok.len)) {
    599 						if (prev1tok.type != EQUALS || tok.type != STRING) {
    600 							msg = "Invalid language assignment";
    601 							goto error;
    602 						} else if (lang_init) {
    603 							msg = "Language already set";
    604 							goto error;
    605 						}
    606 						config->mappings[config->mappings_len - 1].lang = ltk_strndup(tok.text, tok.len);
    607 						lang_init = 1;
    608 					} else if (str_array_equal("map", prev2tok.text, prev2tok.len)) {
    609 						if (prev1tok.type != STRING || tok.type != STRING) {
    610 							msg = "Invalid map statement";
    611 							goto error;
    612 						}
    613 						push_text_mapping(config, prev1tok.text, prev1tok.len, tok.text, tok.len);
    614 					} else {
    615 						msg = "Invalid statement in language mapping";
    616 						goto error;
    617 					}
    618 					tok = next_token(&s);
    619 					if (tok.type == END) {
    620 						break;
    621 					} else if (tok.type != NEWLINE) {
    622 						msg = "Invalid statement in language mapping";
    623 						goto error;
    624 					}
    625 					start_of_line = 1;
    626 				}
    627 				if (!lang_init) {
    628 					msg = "Language not set for language mapping";
    629 					goto error;
    630 				}
    631 			} else {
    632 				msg = "Invalid section";
    633 				goto error;
    634 			}
    635 			break;
    636 		case NEWLINE:
    637 			start_of_line = 1;
    638 			break;
    639 		default:
    640 			msg = "Invalid token";
    641 			goto error;
    642 			break;
    643 		}
    644 	}
    645 	global_config = config;
    646 	return 0;
    647 error:
    648 	if (msg) {
    649 		*errstr = ltk_print_fmt(
    650 		    "%s, line %zu, offset %zu: %s", filename, tok.line, tok.line_offset, msg
    651 		);
    652 	}
    653 errornomsg:
    654 	destroy_config(config);
    655 	return 1;
    656 }
    657 
    658 int
    659 ltk_config_parsefile(
    660     const char *filename,
    661     keypress_binding_handler press_handler,
    662     keyrelease_binding_handler release_handler,
    663     char **errstr) {
    664 	unsigned long len = 0;
    665 	char *ferrstr = NULL;
    666 	char *file_contents = ltk_read_file(filename, &len, &ferrstr);
    667 	if (!file_contents) {
    668 		*errstr = ltk_print_fmt("Unable to open file \"%s\": %s", filename, ferrstr);
    669 		return 1;
    670 	}
    671 	int ret = load_from_text(filename, file_contents, len, press_handler, release_handler, errstr);
    672 	ltk_free(file_contents);
    673 	return ret;
    674 }
    675 
    676 /* FIXME: update this */
    677 const char *default_config = "[general]\n"
    678 "explicit-focus = true\n"
    679 "all-activatable = true\n"
    680 "[key-binding:widget]\n"
    681 "bind-keypress move-next sym tab\n"
    682 "bind-keypress move-prev sym tab mods shift\n"
    683 "bind-keypress move-next text n\n"
    684 "bind-keypress move-prev text p\n"
    685 "bind-keypress focus-active sym return\n"
    686 "bind-keypress unfocus-active sym escape\n"
    687 "bind-keypress set-pressed sym return flags run-always\n"
    688 "bind-keyrelease unset-pressed sym return flags run-always\n"
    689 "[key-mapping]\n"
    690 "language = \"English (US)\"\n";
    691 
    692 /* FIXME: improve this configuration */
    693 int
    694 ltk_config_load_default(
    695     keypress_binding_handler press_handler,
    696     keyrelease_binding_handler release_handler,
    697     char **errstr) {
    698 	char *config_copied = ltk_strdup(default_config);
    699 	int ret = load_from_text("<default config>", config_copied, strlen(config_copied), press_handler, release_handler, errstr);
    700 	free(config_copied);
    701 	return ret;
    702 }
    703 
    704 void
    705 ltk_keypress_binding_destroy(ltk_keypress_binding b) {
    706 	ltk_free(b.text);
    707 	ltk_free(b.rawtext);
    708 }
    709 
    710 /* FIXME: which additional ones are needed here? */
    711 static struct keysym_mapping {
    712 	char *name;
    713 	ltk_keysym keysym;
    714 } keysym_map[] = {
    715 	{"backspace", LTK_KEY_BACKSPACE},
    716 	{"begin", LTK_KEY_BEGIN},
    717 	{"break", LTK_KEY_BREAK},
    718 	{"cancel", LTK_KEY_CANCEL},
    719 	{"clear", LTK_KEY_CLEAR},
    720 	{"delete", LTK_KEY_DELETE},
    721 	{"down", LTK_KEY_DOWN},
    722 	{"end", LTK_KEY_END},
    723 	{"escape", LTK_KEY_ESCAPE},
    724 	{"execute", LTK_KEY_EXECUTE},
    725 
    726 	{"f1", LTK_KEY_F1},
    727 	{"f10", LTK_KEY_F10},
    728 	{"f11", LTK_KEY_F11},
    729 	{"f12", LTK_KEY_F12},
    730 	{"f2", LTK_KEY_F2},
    731 	{"f3", LTK_KEY_F3},
    732 	{"f4", LTK_KEY_F4},
    733 	{"f5", LTK_KEY_F5},
    734 	{"f6", LTK_KEY_F6},
    735 	{"f7", LTK_KEY_F7},
    736 	{"f8", LTK_KEY_F8},
    737 	{"f9", LTK_KEY_F9},
    738 
    739 	{"find", LTK_KEY_FIND},
    740 	{"help", LTK_KEY_HELP},
    741 	{"home", LTK_KEY_HOME},
    742 	{"insert", LTK_KEY_INSERT},
    743 
    744 	{"kp-0", LTK_KEY_KP_0},
    745 	{"kp-1", LTK_KEY_KP_1},
    746 	{"kp-2", LTK_KEY_KP_2},
    747 	{"kp-3", LTK_KEY_KP_3},
    748 	{"kp-4", LTK_KEY_KP_4},
    749 	{"kp-5", LTK_KEY_KP_5},
    750 	{"kp-6", LTK_KEY_KP_6},
    751 	{"kp-7", LTK_KEY_KP_7},
    752 	{"kp-8", LTK_KEY_KP_8},
    753 	{"kp-9", LTK_KEY_KP_9},
    754 	{"kp-add", LTK_KEY_KP_ADD},
    755 	{"kp-begin", LTK_KEY_KP_BEGIN},
    756 	{"kp-decimal", LTK_KEY_KP_DECIMAL},
    757 	{"kp-delete", LTK_KEY_KP_DELETE},
    758 	{"kp-divide", LTK_KEY_KP_DIVIDE},
    759 	{"kp-down", LTK_KEY_KP_DOWN},
    760 	{"kp-end", LTK_KEY_KP_END},
    761 	{"kp-enter", LTK_KEY_KP_ENTER},
    762 	{"kp-equal", LTK_KEY_KP_EQUAL},
    763 	{"kp-home", LTK_KEY_KP_HOME},
    764 	{"kp-insert", LTK_KEY_KP_INSERT},
    765 	{"kp-left", LTK_KEY_KP_LEFT},
    766 	{"kp-multiply", LTK_KEY_KP_MULTIPLY},
    767 	{"kp-next", LTK_KEY_KP_NEXT},
    768 	{"kp-page-down", LTK_KEY_KP_PAGE_DOWN},
    769 	{"kp-page-up", LTK_KEY_KP_PAGE_UP},
    770 	{"kp-prior", LTK_KEY_KP_PRIOR},
    771 	{"kp-right", LTK_KEY_KP_RIGHT},
    772 	{"kp-separator", LTK_KEY_KP_SEPARATOR},
    773 	{"kp-space", LTK_KEY_KP_SPACE},
    774 	{"kp-subtract", LTK_KEY_KP_SUBTRACT},
    775 	{"kp-tab", LTK_KEY_KP_TAB},
    776 	{"kp-up", LTK_KEY_KP_UP},
    777 
    778 	{"left", LTK_KEY_LEFT},
    779 	{"linefeed", LTK_KEY_LINEFEED},
    780 	{"menu", LTK_KEY_MENU},
    781 	{"mode-switch", LTK_KEY_MODE_SWITCH},
    782 	{"next", LTK_KEY_NEXT},
    783 	{"num-lock", LTK_KEY_NUM_LOCK},
    784 	{"page-down", LTK_KEY_PAGE_DOWN},
    785 	{"page-up", LTK_KEY_PAGE_UP},
    786 	{"pause", LTK_KEY_PAUSE},
    787 	{"print", LTK_KEY_PRINT},
    788 	{"prior", LTK_KEY_PRIOR},
    789 
    790 	{"redo", LTK_KEY_REDO},
    791 	{"return", LTK_KEY_RETURN},
    792 	{"right", LTK_KEY_RIGHT},
    793 	{"script-switch", LTK_KEY_SCRIPT_SWITCH},
    794 	{"scroll-lock", LTK_KEY_SCROLL_LOCK},
    795 	{"select", LTK_KEY_SELECT},
    796 	{"space", LTK_KEY_SPACE},
    797 	{"sysreq", LTK_KEY_SYS_REQ},
    798 	{"tab", LTK_KEY_TAB},
    799 	{"up", LTK_KEY_UP},
    800 	{"undo", LTK_KEY_UNDO},
    801 };
    802 
    803 GEN_CB_MAP_HELPERS(keysym_map, struct keysym_mapping, name)
    804 
    805 static int
    806 parse_keysym(char *keysym_str, size_t len, ltk_keysym *sym) {
    807 	struct keysym_mapping *km = keysym_map_get_entry(keysym_str, len);
    808 	if (!km)
    809 		return 1;
    810 	*sym = km->keysym;
    811 	return 0;
    812 }