1 /* src/client/interface_conf.c */
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" /* token_from_line(),parsetset_singlechar() */
13 #include "../common/readwrite.h" /* atomic_write_start(), atomic_write_finish(),
14 * detect_atomic_leftover(), try_fwrite()
16 #include "../common/rexit.h" /* exit_err(), exit_trouble() */
17 #include "../common/try_malloc.h" /* try_malloc() */
18 #include "array_append.h" /* array_append() */
19 #include "cleanup.h" /* set_cleanup_flag() */
20 #include "command_db.h" /* get_command() */
21 #include "keybindings.h" /* KeyBinding, KeyBindingDB, get_command_to_keycode()*/
22 #include "map.h" /* map_center() */
23 #include "parse.h" /* EDIT_STARTED, parse_file(), parse_flagval(),
24 * parse_and_reduce_to_readyflag(), parse_id_uniq()
25 * parsetest_defcontext(), parse_unknown_arg(),
26 * parsetest_too_many_values(), parse_init_entry()
28 #include "wincontrol.h" /* toggle_window() */
29 #include "windows.h" /* Win, free_winDB(), make_v_screen_and_init_win_sizes() */
30 #include "world.h" /* global world */
34 /* Editing state flags set / checked in tokens_into_entries(). */
42 READY_WIN = NAME_SET | WIDTH_SET | HEIGHT_SET | BREAK_SET,
49 /* Used in load_interface_conf() to temporarily store designated values for
50 * world.winDB.order and world.winDB.active. If those were set right away
51 * without all windows having already been initialized, orderly exits on error
52 * would be impossible, for windows are cleaned out by working toggling them off
53 * piece by peice following these values in unload_interfac_conf().
55 static char * tmp_order;
56 static char tmp_active;
60 /* Write into "file" keybindings in "kbdb" in config file format. */
61 static void write_keybindings(FILE * file, struct KeyBindingDB * kbdb);
63 /* Write into file "val" interpreted as pointing either to string (type = 's'),
64 * int16 ('i') or char ('c'), prefixed with "prefix" and put into single quotes
67 static void write_def(FILE * file, char * prefix, uint8_t quotes, char * val,
70 /* Read in "token0" and "token1" as interface config definition lines, apply
71 * definitions to WinDB, KeyBindingsDBs and tmp_order/tmp_active. If argument is
72 * "KEY", get third token via token_from_line() for complete keybinding.
74 static void tokens_into_entries(char * token0, char * token1);
76 /* If "win" points to non-NULL Win struct, append to it to WinDB.wins array and
77 * add its ID to world.winDB.ids.
79 static void write_if_win(struct Win ** win);
81 /* Start block setting data for window "token1" id if "token0" == "str_win".*/
82 static uint8_t start_win(char * token0, char * token1, char * str_win,
83 uint8_t * win_flags, struct Win ** win);
85 /* Start block setting window order and focus if "token0" = "str_ord".
86 * "tmp_order" stores the designated world.winDB.order string (only later to
87 * be realized in load_interface_conf() when all windows have been initialized),
88 * as does "tmp_active" for the designated world.winDB.active char, defaulting
89 * to the first char in the window order string if not otherwise specified.
91 static uint8_t start_ord(char * token0, char * token1, char * str_ord,
94 /* Start block setting non-window-specific keybindings if "token0" == "str_kbd".
95 * "token1" must be "global", "wingeom" or "winkeys" to set KeyBindingDB to edit
96 * ("kbdb") to world.kb_global, world.kb_wingeom or world.kb_winkeys.
98 static uint8_t start_kbd(char * token0, char * token1, char * str_kbd,
99 uint8_t * kbd_flags, struct KeyBindingDB ** kbdb);
101 /* Helper to tokens_into_entries(), sets specific entry member data. "token0"
102 * and the state of flags determine what entry member to edit; "win" and "kbdb"
103 * are entries to write "token1" data into (as is the global "tmp_active"). If
104 * "token0" is "str_key", a keybinding is defined and "token2" is read/must not
105 * be NULL. In that case, the line read is checked against having a 4th token.
107 static uint8_t set_members(char * token0, char * token1, uint8_t * win_flags,
108 uint8_t * ord_flags,uint8_t kbd_flags,char * str_key,
109 struct Win * win, struct KeyBindingDB * kbdb);
111 /* Add keybinding defined in "token1" as keycode and "token2" as command to
112 * "kbdb" if "flags" are set to EDIT_STARTED.
114 static void set_keybindings(char * token1, uint8_t flags,
115 struct KeyBindingDB * kbdb);
119 static void write_keybindings(FILE * file, struct KeyBindingDB * kbdb)
124 for (i_kb = 0; i_kb < kbdb->n_of_kbs; i_kb++)
126 size_t size = strlen(tok0) + strlen(sep) + 3 + strlen(sep)
127 + strlen(kbdb->kbs[i_kb].command->dsc_short) + 1 + 1;
128 char * line = try_malloc(size, __func__);
129 int test = snprintf(line, size, "%s%s%d%s%s\n",
130 tok0, sep, kbdb->kbs[i_kb].keycode, sep,
131 kbdb->kbs[i_kb].command->dsc_short);
132 exit_trouble(test < 0, __func__, "snprintf");
133 try_fwrite(line, sizeof(char), strlen(line), file, __func__);
140 static void write_def(FILE * file, char * prefix, uint8_t quotes, char * val,
143 char * val_str = NULL;
144 int test_val_str = 1;
147 val_str = strdup(val);
151 size_t size_val_str = 6 + 1;
152 val_str = try_malloc(size_val_str, __func__);
153 test_val_str = snprintf(val_str, size_val_str, "%d", (int16_t) *val);
155 else if ('c' == type)
157 size_t size_val_str = 1 + 1;
158 val_str = try_malloc(size_val_str, __func__);
159 test_val_str = snprintf(val_str, size_val_str, "%c", * val);
161 exit_trouble(test_val_str < 0, __func__, "snprintf");
162 char * quote = quotes ? "'": "";
164 size_t size = strlen(prefix) + strlen(val_str) + (2 * strlen(quote))
166 char * line = try_malloc(size, __func__);
167 int test = snprintf(line, size, "%s%s%s%s%s",
168 prefix, quote, val_str, quote, affix);
169 exit_trouble(test < 0, __func__, "snprintf");
171 try_fwrite(line, sizeof(char), strlen(line), file, __func__);
177 static void tokens_into_entries(char * token0, char * token1)
179 char * str_win = "WINDOW";
180 char * str_ord = "WIN_ORDER";
181 char * str_kbd = "KEYBINDINGS";
182 char * str_key = "KEY";
183 static uint8_t win_flags = READY_WIN;
184 static uint8_t ord_flags = READY_ORD;
185 static uint8_t kbd_flags = READY_KBD;
186 static struct Win * win = NULL;
187 static struct KeyBindingDB * kbdb = NULL;
188 if (!token0 || !strcmp(token0, str_win) || !strcmp(token0, str_ord)
189 || !strcmp(token0, str_kbd))
191 parse_and_reduce_to_readyflag(&win_flags, READY_WIN);
192 parse_and_reduce_to_readyflag(&ord_flags, READY_ORD);
193 parse_and_reduce_to_readyflag(&kbd_flags, READY_KBD);
198 if (strcmp(token0, str_key))
200 parsetest_too_many_values();
202 if (!( start_win(token0, token1, str_win, &win_flags, &win)
203 || start_ord(token0, token1, str_ord, &ord_flags)
204 || start_kbd(token0, token1, str_kbd, &kbd_flags, &kbdb)
205 || set_members(token0, token1, &win_flags, &ord_flags, kbd_flags,
206 str_key, win, kbdb)))
215 static void write_if_win(struct Win ** win)
219 (*win)->target_height_type = (0 >= (*win)->target_height);
220 (*win)->target_width_type = (0 >= (*win)->target_width);;
221 size_t old_ids_size = strlen(world.winDB.ids);
222 size_t new_size = old_ids_size + 1 + 1;
223 char * new_ids = try_malloc(new_size, __func__);
224 int test = snprintf(new_ids,new_size,"%s%c",world.winDB.ids,(*win)->id);
225 exit_trouble(test < 0, __func__, "snprintf");
226 free(world.winDB.ids);
227 world.winDB.ids = new_ids;
228 array_append(old_ids_size, sizeof(struct Win), *win,
229 (void **) &world.winDB.wins);
237 static uint8_t start_win(char * token0, char * token1, char * str_win,
238 uint8_t * win_flags, struct Win ** win)
240 if (strcmp(token0, str_win))
244 *win = (struct Win *) parse_init_entry(win_flags, sizeof(struct Win));
245 parsetest_singlechar(token1);
246 parse_id_uniq(world.winDB.ids && (NULL!=strchr(world.winDB.ids,token1[0])));
247 (*win)->id = token1[0];
253 static uint8_t start_ord(char * token0, char * token1, char * str_ord,
256 if (strcmp(token0, str_ord))
260 *ord_flags = EDIT_STARTED;
262 for (; i < strlen(token1); i++)
264 err_line(!strchr(world.winDB.legal_ids, token1[i]), "Illegal ID(s).");
267 tmp_order = strdup(token1);
268 if (0 < strlen(tmp_order))
270 tmp_active = tmp_order[0];
277 static uint8_t start_kbd(char * token0, char * token1, char * str_kbd,
278 uint8_t * kbd_flags, struct KeyBindingDB ** kbdb)
280 if (strcmp(token0, str_kbd))
284 *kbd_flags = EDIT_STARTED;
285 if (!strcmp(token1, "global"))
287 *kbdb = &world.kb_global;
289 else if (!strcmp(token1, "wingeom"))
291 *kbdb = &world.kb_wingeom;
293 else if (!strcmp(token1, "winkeys"))
295 *kbdb = &world.kb_winkeys;
299 err_line(1, "Value must be 'global', 'wingeom' or 'winkeys'.");
306 static uint8_t set_members(char * token0, char * token1, uint8_t * win_flags,
307 uint8_t * ord_flags,uint8_t kbd_flags,char * str_key,
308 struct Win * win, struct KeyBindingDB * kbdb)
310 if ( parse_flagval(token0, token1, "NAME", win_flags,
311 NAME_SET, 's', (char *) &win->title)
312 || parse_flagval(token0, token1, "WIDTH", win_flags,
313 WIDTH_SET, 'i', (char *) &win->target_width)
314 || parse_flagval(token0, token1, "HEIGHT", win_flags,
315 HEIGHT_SET, 'i', (char *) &win->target_height));
316 else if (parse_flagval(token0, token1, "BREAK", win_flags,
317 BREAK_SET, '8', (char *) &win->linebreak))
319 err_line(2 < win->linebreak, "Value must be 0, 1 or 2.");
321 else if (parse_flagval(token0, token1, "WIN_FOCUS", ord_flags,
322 FOCUS_SET, 'c', &tmp_active))
324 char * err_null = "Value not empty as it should be.";
325 char * err_outside = "ID not found in WIN_ORDER ID series.";
326 err_line(!strlen(tmp_order) && tmp_active, err_null);
327 err_line(!strchr(tmp_order, tmp_active), err_outside);
329 else if (!strcmp(token0, str_key))
331 if (*win_flags & EDIT_STARTED)
333 set_keybindings(token1, *win_flags, &win->kb);
337 set_keybindings(token1, kbd_flags, kbdb);
349 static void set_keybindings(char * token1, uint8_t flags,
350 struct KeyBindingDB * kbdb)
352 char * token2 = token_from_line(NULL);
353 err_line(!token2, "No binding to key given.");
354 parsetest_too_many_values();
355 char * err_code = "Invalid keycode. Must be >= 0 and < 1000.";
356 parsetest_defcontext(flags);
357 char * err_many = "No more than 255 keybindings allowed in one section.";
358 err_line(UINT8_MAX == kbdb->n_of_kbs, err_many);
359 struct KeyBinding kb;
360 uint8_t test = strlen(token1);
361 err_line(!test, err_code);
363 for (i = 0; '\0' != token1[i]; i++)
365 test= i > 2 || '0' > token1[i] || '9' < token1[i];
366 err_line(test, err_code);
368 kb.keycode = atoi(token1);
369 char * err_uniq = "Binding to key already defined.";
370 err_line(NULL != get_command_to_keycode(kbdb, kb.keycode), err_uniq);
371 kb.command = get_command(token2);
372 err_line(!(kb.command), "No such command in command DB.");
373 array_append(kbdb->n_of_kbs, sizeof(struct KeyBinding), (void *) &kb,
380 extern void obey_argv(int argc, char * argv[])
383 while (-1 != (opt = getopt(argc, argv, "i:")))
387 world.path_interface = optarg;
398 extern void save_interface_conf()
401 FILE * file = atomic_write_start(world.path_interface, &path_tmp);
402 char * str_keybs = "\nKEYBINDINGS ";
403 write_def(file, str_keybs, 1, "global", 's');
404 write_keybindings(file, &world.kb_global);
405 write_def(file, str_keybs, 1, "wingeom", 's');
406 write_keybindings(file, &world.kb_wingeom);
407 write_def(file, str_keybs, 1, "winkeys", 's');
408 write_keybindings(file, &world.kb_winkeys);
409 write_def(file, "\nWIN_ORDER ", 1, world.winDB.order, 's');
410 if (world.winDB.active)
412 write_def(file, "WIN_FOCUS ", 1, &world.winDB.active, 'c');
415 for (i = 0; i < strlen(world.winDB.ids); i++)
417 write_def(file, "\nWINDOW ", 0, &world.winDB.ids[i], 'c');
418 struct Win * win = get_win_by_id(world.winDB.ids[i]);
419 write_def(file, "NAME ", 1, win->title, 's');
420 write_def(file, "BREAK ", 0, (char *) &win->linebreak, 'i');
421 write_def(file, "WIDTH ", 0, (char *) &win->target_width, 'i');
422 write_def(file, "HEIGHT ", 0, (char *) &win->target_height, 'i');
423 write_keybindings(file, &win->kb);
425 atomic_write_finish(file, world.path_interface, path_tmp);
430 extern void load_interface_conf()
432 world.winDB.ids = try_malloc(1, __func__);
433 world.winDB.ids[0] = '\0';
434 world.winDB.wins = NULL;
435 tmp_order = try_malloc(1, __func__);
438 detect_atomic_leftover(world.path_interface);
439 parse_file(world.path_interface, tokens_into_entries);
440 char * err = "Not all expected windows defined in config file.";
441 exit_err(strlen(world.winDB.legal_ids) != strlen(world.winDB.ids), err);
442 make_v_screen_and_init_win_sizes();
443 world.winDB.order = try_malloc(1, __func__);
444 world.winDB.order[0] = '\0';
446 for (i = 0; i < strlen(tmp_order); toggle_window(tmp_order[i]), i++);
447 world.winDB.active = tmp_active;
449 set_cleanup_flag(CLEANUP_INTERFACE);
454 extern void unload_interface_conf()
456 free(world.kb_global.kbs);
457 world.kb_global.kbs = NULL;
458 world.kb_global.n_of_kbs = 0;
459 free(world.kb_wingeom.kbs);
460 world.kb_wingeom.kbs = NULL;
461 world.kb_wingeom.n_of_kbs = 0;
462 free(world.kb_winkeys.kbs);
463 world.kb_winkeys.kbs = NULL;
464 world.kb_winkeys.n_of_kbs = 0;
465 while ('\0' != world.winDB.active)
467 toggle_window(world.winDB.active);
470 delwin(world.winDB.v_screen);
475 extern void reload_interface_conf()
477 unload_interface_conf();
478 load_interface_conf();
480 world.winDB.v_screen_offset = 0;