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