URI: 
       config.c - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 }