home · contact · privacy
Refactor file parsing patterns.
[plomrogue] / src / client / interface_conf.c
1 /* src/client/interface_conf.c */
2
3 #define _POSIX_C_SOURCE 200809L /* getopt(), optarg, strdup() */
4 #include "interface_conf.h"
5 #include <ncurses.h> /* delwin() */
6 #include <stddef.h> /* NULL, size_t */
7 #include <stdint.h> /* UINT8_MAX, uint8_t, uint32_t */
8 #include <stdlib.h> /* EXIT_SUCCESS, atoi(), exit(), free() */
9 #include <stdio.h> /* FILE, sprintf() */
10 #include <string.h> /* strchr(), strcmp(), strdup(), strlen() */
11 #include <unistd.h> /* optarg, getopt() */
12 #include "../common/parse_file.h" /* EDIT_STARTED, parse_file(), parse_val(),
13                                    * token_from_line(), parsetest_singlechar(),
14                                    * parse_and_reduce_to_readyflag(),
15                                    * parsetest_defcontext(),parse_unknown_arg(),
16                                    * parsetest_too_many_values(),
17                                    * parse_id_uniq(), parse_init_entry()
18                                    */
19 #include "../common/readwrite.h" /* try_fopen(), try_fclose_unlink_rename(),
20                                   * try_fwrite()
21                                   */
22 #include "../common/rexit.h" /* exit_err(), exit_trouble() */
23 #include "../common/try_malloc.h" /* try_malloc() */
24 #include "array_append.h" /* array_append() */
25 #include "cleanup.h" /* set_cleanup_flag() */
26 #include "command_db.h" /* get_command() */
27 #include "keybindings.h" /* KeyBinding, KeyBindingDB, get_command_to_keycode() */
28 #include "map.h" /* map_center() */
29 #include "wincontrol.h" /* toggle_window() */
30 #include "windows.h" /* Win, free_winDB(), make_v_screen_and_init_win_sizes() */
31 #include "world.h" /* global world */
32
33
34
35 /* Editing state flags set / checked in tokens_into_entries(). */
36 enum flag
37 {
38     NAME_SET   = 0x02,
39     WIDTH_SET  = 0x04,
40     HEIGHT_SET = 0x08,
41     BREAK_SET  = 0x10,
42     FOCUS_SET  = 0x02,
43     READY_WIN  = NAME_SET | WIDTH_SET | HEIGHT_SET | BREAK_SET,
44     READY_ORD  = 0x00,
45     READY_KBD  = 0x00
46 };
47
48
49
50 /* Used in load_interface_conf() to temporarily store designated values for
51  * world.winDB.order and world.winDB.active. If those were set right away
52  * without all windows having already been initialized, orderly exits on error
53  * would be impossible, for windows are cleaned out by working toggling them off
54  * piece by peice following these values in unload_interfac_conf().
55  */
56 static char * tmp_order;
57 static char tmp_active;
58
59
60
61 /* Write into "file" keybindings in "kbdb" in config file format. */
62 static void write_keybindings(FILE * file, struct KeyBindingDB * kbdb);
63
64 /* Write into file "val" interpreted as pointing either to string (type = 's'),
65  * int16 ('i') or char ('c'), prefixed with "prefix" and put into single quotes
66  * if 0 != "quotes".
67  */
68 static void write_def(FILE * file, char * prefix, uint8_t quotes, char * val,
69                       char type);
70
71 /* Read in "token0" and "token1" as interface config definition lines, apply
72  * definitions to WinDB, KeyBindingsDBs and tmp_order/tmp_active. If argument is
73  * "KEY", get third token via token_from_line() for complete keybinding.
74  */
75 static void tokens_into_entries(char * token0, char * token1);
76
77 /* If "win" points to non-NULL Win struct, append to it to WinDB.wins array and
78  * add its ID to world.winDB.ids.
79  */
80 static void write_if_win(struct Win ** win);
81
82 /* Start block setting data for window "token1" id if "token0" == "str_win".*/
83 static uint8_t start_win(char * token0, char * token1, char * str_win,
84                          uint8_t * win_flags, struct Win ** win);
85
86 /* Start block setting window order and focus if "token0" = "str_ord".
87  * "tmp_order" stores the designated world.winDB.order string (only later to
88  * be realized in load_interface_conf() when all windows have been initialized),
89  * as does "tmp_active" for the designated world.winDB.active char, defaulting
90  * to the first char in the window order string if not otherwise specified.
91  */
92 static uint8_t start_ord(char * token0, char * token1, char * str_ord,
93                          uint8_t * ord_flags);
94
95 /* Start block setting non-window-specific keybindings if "token0" == "str_kbd".
96  * "token1" must be "global", "wingeom" or "winkeys" to set KeyBindingDB to edit
97  * ("kbdb") to world.kb_global, world.kb_wingeom or world.kb_winkeys.
98  */
99 static uint8_t start_kbd(char * token0, char * token1, char * str_kbd,
100                          uint8_t * kbd_flags, struct KeyBindingDB ** kbdb);
101
102 /* Helper to tokens_into_entries(), sets specific entry member data. "token0"
103  * and the state of flags determine what entry member to edit; "win" and "kbdb"
104  * are entries to write "token1" data into (as is the global "tmp_active"). If
105  * "token0" is "KEY", a keybinding is defined and "token2" is read / must not be
106  * NULL. In that case, the line read is checked against having a 4th token.
107  */
108 static uint8_t set_members(char * token0, char * token1, uint8_t * win_flags,
109                            uint8_t * ord_flags, uint8_t kbd_flags,
110                            struct Win * win, struct KeyBindingDB * kbdb);
111
112 /* Add keybinding defined in "token1" as keycode and "token2" as command to
113  * "kbdb" if "flags" are set to EDIT_STARTED.
114  */
115 static void set_keybindings(char * token1, uint8_t flags,
116                             struct KeyBindingDB * kbdb);
117
118
119
120 static void write_keybindings(FILE * file, struct KeyBindingDB * kbdb)
121 {
122     char * f_name = "write_keybindings()";
123     char * sep = " ";
124     char * tok0 = "KEY";
125     uint8_t i_kb;
126     for (i_kb = 0; i_kb < kbdb->n_of_kbs; i_kb++)
127     {
128         size_t size = strlen(tok0) + strlen(sep) + 3 + strlen(sep)
129                       + strlen(kbdb->kbs[i_kb].command->dsc_short) + 1 + 1;
130         char * line = try_malloc(size, f_name);
131         int test = snprintf(line, size, "%s%s%d%s%s\n",
132                              tok0, sep, kbdb->kbs[i_kb].keycode, sep,
133                              kbdb->kbs[i_kb].command->dsc_short);
134         exit_trouble(test < 0, f_name, "snprintf()");
135         try_fwrite(line, sizeof(char), strlen(line), file, f_name);
136         free(line);
137     }
138 }
139
140
141
142 static void write_def(FILE * file, char * prefix, uint8_t quotes, char * val,
143                       char type)
144 {
145     char * f_name = "write_def()";
146     char * val_str;
147     int test_val_str = 1;
148     if      ('s' == type)
149     {
150         val_str = strdup(val);
151     }
152     if      ('i' == type)
153     {
154         size_t size_val_str = 6 + 1;
155         val_str = try_malloc(size_val_str, f_name);
156         test_val_str = snprintf(val_str, size_val_str, "%d", (int16_t) *val);
157     }
158     else if ('c' == type)
159     {
160         size_t size_val_str = 1 + 1;
161         val_str = try_malloc(size_val_str, f_name);
162         test_val_str = snprintf(val_str, size_val_str, "%c", * val);
163     }
164     exit_trouble(test_val_str < 0, f_name, "snprintf()");
165     char * quote = quotes ? "'": "";
166     char * affix = "\n";
167     size_t size =   strlen(prefix) + strlen(val_str) + (2 * strlen(quote))
168                   + strlen(affix) + 1;
169     char * line = try_malloc(size, f_name);
170     int test = snprintf(line, size, "%s%s%s%s%s",
171                         prefix, quote, val_str, quote, affix);
172     exit_trouble(test < 0, f_name, "snprintf()");
173     free(val_str);
174     try_fwrite(line, sizeof(char), strlen(line), file, f_name);
175     free(line);
176 }
177
178
179
180 static void tokens_into_entries(char * token0, char * token1)
181 {
182     char * str_win = "WINDOW";
183     char * str_ord = "WIN_ORDER";
184     char * str_kbd = "KEYBINDINGS";
185     static uint8_t win_flags = READY_WIN;
186     static uint8_t ord_flags = READY_ORD;
187     static uint8_t kbd_flags = READY_KBD;
188     static struct Win * win = NULL;
189     static struct KeyBindingDB * kbdb = NULL;
190     if (!token0 || !strcmp(token0, str_win) || !strcmp(token0, str_ord)
191                 || !strcmp(token0, str_kbd))
192     {
193         parse_and_reduce_to_readyflag(&win_flags, READY_WIN);
194         parse_and_reduce_to_readyflag(&ord_flags, READY_ORD);
195         parse_and_reduce_to_readyflag(&kbd_flags, READY_KBD);
196         write_if_win(&win);
197     }
198     if (token0)
199     {
200         if (strcmp(token0, "KEY"))
201         {
202             parsetest_too_many_values();
203         }
204        if (!(   start_win(token0, token1, str_win, &win_flags, &win)
205              || start_ord(token0, token1, str_ord, &ord_flags)
206              || start_kbd(token0, token1, str_kbd, &kbd_flags, &kbdb)
207              || set_members(token0, token1, &win_flags, &ord_flags, kbd_flags,
208                             win, kbdb)))
209         {
210            parse_unknown_arg();
211         }
212     }
213 }
214
215
216
217 static void write_if_win(struct Win ** win)
218 {
219     char * f_name = "write_if_win()";
220     if (*win)
221     {
222         (*win)->target_height_type = (0 >= (*win)->target_height);
223         (*win)->target_width_type = (0 >= (*win)->target_width);;
224         size_t old_ids_size = strlen(world.winDB.ids);
225         size_t new_size = old_ids_size + 1 + 1;
226         char * new_ids = try_malloc(new_size, f_name);
227         int test = snprintf(new_ids,new_size,"%s%c",world.winDB.ids,(*win)->id);
228         exit_trouble(test < 0, f_name, "snprintf()");
229         free(world.winDB.ids);
230         world.winDB.ids = new_ids;
231         array_append(old_ids_size, sizeof(struct Win), *win,
232                      (void **) &world.winDB.wins);
233         free(*win);
234         *win = NULL;
235     }
236 }
237
238
239
240 static uint8_t start_win(char * token0, char * token1, char * str_win,
241                          uint8_t * win_flags, struct Win ** win)
242 {
243     if (strcmp(token0, str_win))
244     {
245         return 0;
246     }
247     *win = (struct Win *) parse_init_entry(win_flags, sizeof(struct Win));
248     parsetest_singlechar(token1);
249     parse_id_uniq(world.winDB.ids && (NULL!=strchr(world.winDB.ids,token1[0])));
250     (*win)->id = token1[0];
251     return 1;
252 }
253
254
255
256 static uint8_t start_ord(char * token0, char * token1, char * str_ord,
257                          uint8_t * ord_flags)
258 {
259     if (strcmp(token0, str_ord))
260     {
261         return 0;
262     }
263     *ord_flags = EDIT_STARTED;
264     uint32_t i = 0;
265     for (; i < strlen(token1); i++)
266     {
267         err_line(!strchr(world.winDB.legal_ids, token1[i]), "Illegal ID(s).");
268     }
269     free(tmp_order);
270     tmp_order = strdup(token1);
271     if (0 < strlen(tmp_order))
272     {
273         tmp_active = tmp_order[0];
274     }
275     return 1;
276 }
277
278
279
280 static uint8_t start_kbd(char * token0, char * token1, char * str_kbd,
281                          uint8_t * kbd_flags, struct KeyBindingDB ** kbdb)
282 {
283     if (strcmp(token0, str_kbd))
284     {
285         return 0;
286     }
287     *kbd_flags = EDIT_STARTED;
288     if      (!strcmp(token1, "global"))
289     {
290         *kbdb = &world.kb_global;
291     }
292     else if (!strcmp(token1, "wingeom"))
293     {
294         *kbdb = &world.kb_wingeom;
295     }
296     else if (!strcmp(token1, "winkeys"))
297     {
298         *kbdb = &world.kb_winkeys;
299     }
300     else
301     {
302         err_line(1, "Value must be 'global', 'wingeom' or 'winkeys'.");
303     }
304     return 1;
305 }
306
307
308
309 static uint8_t set_members(char * token0, char * token1, uint8_t * win_flags,
310                            uint8_t * ord_flags, uint8_t kbd_flags,
311                            struct Win * win, struct KeyBindingDB * kbdb)
312 {
313     if (   parse_val(token0, token1, "NAME", win_flags,
314                      NAME_SET, 's', (char *) &win->title)
315         || parse_val(token0, token1, "WIDTH", win_flags,
316                      WIDTH_SET, 'i', (char *) &win->target_width)
317         || parse_val(token0, token1, "HEIGHT", win_flags,
318                      HEIGHT_SET, 'i', (char *) &win->target_height));
319     else if (parse_val(token0, token1, "BREAK", win_flags,
320                        BREAK_SET, '8', (char *) &win->linebreak))
321     {
322         err_line(2 < win->linebreak, "Value must be 0, 1 or 2.");
323     }
324     else if (parse_val(token0, token1, "WIN_FOCUS", ord_flags,
325                        FOCUS_SET, 'c', &tmp_active))
326     {
327         char * err_null = "Value not empty as it should be.";
328         char * err_outside = "ID not found in WIN_ORDER ID series.";
329         err_line(!strlen(tmp_order) && tmp_active, err_null);
330         err_line(!strchr(tmp_order, tmp_active), err_outside);
331     }
332     else if (!strcmp(token0, "KEY"))
333     {
334         if (*win_flags & EDIT_STARTED)
335         {
336             set_keybindings(token1, *win_flags, &win->kb);
337         }
338         else
339         {
340             set_keybindings(token1, kbd_flags, kbdb);
341         }
342     }
343     else
344     {
345         return 0;
346     }
347     return 1;
348 }
349
350
351
352 static void set_keybindings(char * token1, uint8_t flags,
353                             struct KeyBindingDB * kbdb)
354 {
355     char * token2 = token_from_line(NULL);
356     err_line(!token2, "No binding to key given.");
357     parsetest_too_many_values();
358     char * err_code = "Invalid keycode. Must be >= 0 and < 1000.";
359     parsetest_defcontext(flags);
360     char * err_many = "No more than 255 keybindings allowed in one section.";
361     err_line(UINT8_MAX == kbdb->n_of_kbs, err_many);
362     struct KeyBinding kb;
363     uint8_t test = strlen(token1);
364     err_line(!test, err_code);
365     uint8_t i;
366     for (i = 0; '\0' != token1[i]; i++)
367     {
368         test= i > 2 || '0' > token1[i] || '9' < token1[i];
369         err_line(test, err_code);
370     }
371     kb.keycode = atoi(token1);
372     char * err_uniq = "Binding to key already defined.";
373     err_line(NULL != get_command_to_keycode(kbdb, kb.keycode), err_uniq);
374     kb.command = get_command(token2);
375     err_line(!(kb.command), "No such command in command DB.");
376     array_append(kbdb->n_of_kbs, sizeof(struct KeyBinding), (void *) &kb,
377                  (void **) kbdb);
378     kbdb->n_of_kbs++;
379 }
380
381
382
383 extern void obey_argv(int argc, char * argv[])
384 {
385     int opt;
386     while (-1 != (opt = getopt(argc, argv, "i:")))
387     {
388         if      ('i' == opt)
389         {
390             world.path_interface = optarg;
391         }
392         else
393         {
394             exit(EXIT_FAILURE);
395         }
396     }
397 }
398
399
400
401 extern void save_interface_conf()
402 {
403     char * f_name = "save_interface_conf()";
404     char * path = world.path_interface;
405     size_t size = strlen(path) + 4 + 1;
406     char * path_tmp = try_malloc(size, f_name);
407     int test = snprintf(path_tmp, size, "%s_tmp", path);
408     exit_trouble(test < 0, f_name, "snprintf()");
409     FILE * file = try_fopen(path_tmp, "w", f_name);
410     char * str_keybs = "\nKEYBINDINGS ";
411     write_def(file, str_keybs, 1, "global", 's');
412     write_keybindings(file, &world.kb_global);
413     write_def(file, str_keybs, 1, "wingeom", 's');
414     write_keybindings(file, &world.kb_wingeom);
415     write_def(file, str_keybs, 1, "winkeys", 's');
416     write_keybindings(file, &world.kb_winkeys);
417     write_def(file, "\nWIN_ORDER ", 1, world.winDB.order, 's');
418     if (world.winDB.active)
419     {
420         write_def(file, "WIN_FOCUS ", 1, &world.winDB.active, 'c');
421     }
422     uint8_t i;
423     for (i = 0; i < strlen(world.winDB.ids); i++)
424     {
425         write_def(file, "\nWINDOW ", 0, &world.winDB.ids[i], 'c');
426         struct Win * win = get_win_by_id(world.winDB.ids[i]);
427         write_def(file, "NAME ", 1, win->title, 's');
428         write_def(file, "BREAK ", 0, (char *) &win->linebreak, 'i');
429         write_def(file, "WIDTH ", 0, (char *) &win->target_width, 'i');
430         write_def(file, "HEIGHT ", 0, (char *) &win->target_height, 'i');
431         write_keybindings(file, &win->kb);
432     }
433     try_fclose_unlink_rename(file, path_tmp, path, f_name);
434     free(path_tmp);
435 }
436
437
438
439 extern void load_interface_conf()
440 {
441     char * f_name = "load_interface_conf()";
442     world.winDB.ids    = try_malloc(1, f_name);
443     world.winDB.ids[0] = '\0';
444     world.winDB.wins = NULL;
445     tmp_order    = try_malloc(1, f_name);
446     tmp_order[0] = '\0';
447     tmp_active   = '\0';
448     parse_file(world.path_interface, tokens_into_entries);
449     char * err = "Not all expected windows defined in config file.";
450     exit_err(strlen(world.winDB.legal_ids) != strlen(world.winDB.ids), err);
451     make_v_screen_and_init_win_sizes();
452     world.winDB.order = try_malloc(1, f_name);
453     world.winDB.order[0] = '\0';
454     uint8_t i;
455     for (i = 0; i < strlen(tmp_order); toggle_window(tmp_order[i]), i++);
456     world.winDB.active = tmp_active;
457     free(tmp_order);
458     set_cleanup_flag(CLEANUP_INTERFACE);
459 }
460
461
462
463 extern void unload_interface_conf()
464 {
465     free(world.kb_global.kbs);
466     world.kb_global.kbs = NULL;
467     world.kb_global.n_of_kbs = 0;
468     free(world.kb_wingeom.kbs);
469     world.kb_wingeom.kbs = NULL;
470     world.kb_wingeom.n_of_kbs = 0;
471     free(world.kb_winkeys.kbs);
472     world.kb_winkeys.kbs = NULL;
473     world.kb_winkeys.n_of_kbs = 0;
474     while ('\0' != world.winDB.active)
475     {
476         toggle_window(world.winDB.active);
477     }
478     free_winDB();
479     delwin(world.winDB.v_screen);
480 }
481
482
483
484 extern void reload_interface_conf()
485 {
486     unload_interface_conf();
487     load_interface_conf();
488     map_center();
489     world.winDB.v_screen_offset = 0;
490 }