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