xref: /dragonfly/contrib/dialog/dlg_keys.c (revision b2dabe2e739bd72461a68ac543307c2dedfb048c)
1 /*
2  *  $Id: dlg_keys.c,v 1.62 2022/04/14 22:08:43 tom Exp $
3  *
4  *  dlg_keys.c -- runtime binding support for dialog
5  *
6  *  Copyright 2006-2020,2022  Thomas E. Dickey
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License, version 2.1
10  *  as published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this program; if not, write to
19  *        Free Software Foundation, Inc.
20  *        51 Franklin St., Fifth Floor
21  *        Boston, MA 02110, USA.
22  */
23 
24 #include <dlg_internals.h>
25 #include <dlg_keys.h>
26 
27 #define LIST_BINDINGS struct _list_bindings
28 
29 #define CHR_BACKSLASH   '\\'
30 #define IsOctal(ch)     ((ch) >= '0' && (ch) <= '7')
31 
32 LIST_BINDINGS {
33     LIST_BINDINGS *link;
34     WINDOW *win;              /* window on which widget gets input */
35     const char *name;                   /* widget name */
36     bool buttons;             /* true only for dlg_register_buttons() */
37     DLG_KEYS_BINDING *binding;          /* list of bindings */
38 };
39 
40 #define WILDNAME "*"
41 static LIST_BINDINGS *all_bindings;
42 static const DLG_KEYS_BINDING end_keys_binding = END_KEYS_BINDING;
43 
44 /*
45  * For a given named widget's window, associate a binding table.
46  */
47 void
dlg_register_window(WINDOW * win,const char * name,DLG_KEYS_BINDING * binding)48 dlg_register_window(WINDOW *win, const char *name, DLG_KEYS_BINDING * binding)
49 {
50     LIST_BINDINGS *p, *q;
51 
52     for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) {
53           if (p->win == win && !strcmp(p->name, name)) {
54               p->binding = binding;
55               return;
56           }
57     }
58     /* add built-in bindings at the end of the list (see compare_bindings). */
59     if ((p = dlg_calloc(LIST_BINDINGS, 1)) != 0) {
60           p->win = win;
61           p->name = name;
62           p->binding = binding;
63           if (q != 0) {
64               q->link = p;
65           } else {
66               all_bindings = p;
67           }
68     }
69 #if defined(HAVE_DLG_TRACE) && defined(HAVE_RC_FILE)
70     /*
71      * Trace the binding information assigned to this window.  For most widgets
72      * there is only one binding table.  forms have two, so the trace will be
73      * longer.  Since compiled-in bindings are only visible when the widget is
74      * registered, there is no other way to see what bindings are available,
75      * than by running dialog and tracing it.
76      */
77     DLG_TRACE(("# dlg_register_window %s\n", name));
78     dlg_dump_keys(dialog_state.trace_output);
79     dlg_dump_window_keys(dialog_state.trace_output, win);
80     DLG_TRACE(("# ...done dlg_register_window %s\n", name));
81 #endif
82 }
83 
84 /*
85  * A few CHR_xxx symbols in the default bindings are provided to fill in for
86  * incomplete terminal descriptions, or for consistency.
87  *
88  * A terminal description normally has an appropriate setting for kbs, which
89  * dialog assumes is the same as the curses value for KEY_BACKSPACE.  But just
90  * in case, dialog supplies both.
91  *
92  * Also, while a terminal description may have KEY_DC, that normally is not the
93  * same as the shifted-backspace key provided with rxvt/xterm and imitators of
94  * those.  Accommodate that by checking the return value of erasechar().
95  *
96  * The killchar() function's return value need not correspond to any of the
97  * KEY_xxx symbols.  Just map CHR_KILL to that.
98  */
99 static int
actual_curses_key(DLG_KEYS_BINDING * p)100 actual_curses_key(DLG_KEYS_BINDING * p)
101 {
102     int result = p->curses_key;
103     int checks;
104 
105     switch (p->curses_key) {
106     case CHR_KILL:
107           if ((checks = killchar()) > 0) {
108               result = checks;
109           }
110           break;
111     case CHR_BACKSPACE:
112           if ((checks = erasechar()) > 0) {
113               result = checks;
114           }
115           break;
116     case CHR_DELETE:
117           if ((checks = erasechar()) > 0 &&
118               (checks == result)) {
119               result = CHR_BACKSPACE;
120           }
121           break;
122     }
123     return result;
124 }
125 
126 /*
127  * Unlike dlg_lookup_key(), this looks for either widget-builtin or rc-file
128  * definitions, depending on whether 'win' is null.
129  */
130 static int
key_is_bound(WINDOW * win,const char * name,int curses_key,int function_key)131 key_is_bound(WINDOW *win, const char *name, int curses_key, int function_key)
132 {
133     LIST_BINDINGS *p;
134 
135     for (p = all_bindings; p != 0; p = p->link) {
136           if (p->win == win && !dlg_strcmp(p->name, name)) {
137               int n;
138               for (n = 0; p->binding[n].is_function_key >= 0; ++n) {
139                     if (actual_curses_key(&(p->binding[n])) == curses_key
140                         && p->binding[n].is_function_key == function_key) {
141                         return TRUE;
142                     }
143               }
144           }
145     }
146     return FALSE;
147 }
148 
149 /*
150  * Call this function after dlg_register_window(), for the list of button
151  * labels associated with the widget.
152  *
153  * Ensure that dlg_lookup_key() will not accidentally translate a key that
154  * we would like to use for a button abbreviation to some other key, e.g.,
155  * h/j/k/l for navigation into a cursor key.  Do this by binding the key
156  * to itself.
157  *
158  * See dlg_char_to_button().
159  */
160 void
dlg_register_buttons(WINDOW * win,const char * name,const char ** buttons)161 dlg_register_buttons(WINDOW *win, const char *name, const char **buttons)
162 {
163     int n;
164     LIST_BINDINGS *p;
165     DLG_KEYS_BINDING *q;
166 
167     if (buttons == 0)
168           return;
169 
170     for (n = 0; buttons[n] != 0; ++n) {
171           int curses_key = dlg_button_to_char(buttons[n]);
172 
173           /* ignore binding if there is no key to bind */
174           if (curses_key < 0)
175               continue;
176 
177           /* ignore multibyte characters */
178           if (curses_key >= KEY_MIN)
179               continue;
180 
181           /* if it is not bound in the widget, skip it (no conflicts) */
182           if (!key_is_bound(win, name, curses_key, FALSE))
183               continue;
184 
185 #ifdef HAVE_RC_FILE
186           /* if it is bound in the rc-file, skip it */
187           if (key_is_bound(0, name, curses_key, FALSE))
188               continue;
189 #endif
190 
191           if ((p = dlg_calloc(LIST_BINDINGS, 1)) != 0) {
192               if ((q = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0) {
193                     q[0].is_function_key = 0;
194                     q[0].curses_key = curses_key;
195                     q[0].dialog_key = curses_key;
196                     q[1] = end_keys_binding;
197 
198                     p->win = win;
199                     p->name = name;
200                     p->buttons = TRUE;
201                     p->binding = q;
202 
203                     /* put these at the beginning, to override the widget's table */
204                     p->link = all_bindings;
205                     all_bindings = p;
206               } else {
207                     free(p);
208               }
209           }
210     }
211 }
212 
213 /*
214  * Remove the bindings for a given window.
215  */
216 void
dlg_unregister_window(WINDOW * win)217 dlg_unregister_window(WINDOW *win)
218 {
219     LIST_BINDINGS *p, *q;
220 
221     for (p = all_bindings, q = 0; p != 0; p = p->link) {
222           if (p->win == win) {
223               if (q != 0) {
224                     q->link = p->link;
225               } else {
226                     all_bindings = p->link;
227               }
228               /* the user-defined and buttons-bindings all are length=1 */
229               if (p->binding[1].is_function_key < 0)
230                     free(p->binding);
231               free(p);
232               dlg_unregister_window(win);
233               break;
234           }
235           q = p;
236     }
237 }
238 
239 /*
240  * Call this after wgetch(), using the same window pointer and passing
241  * the curses-key.
242  *
243  * If there is no binding associated with the widget, it simply returns
244  * the given curses-key.
245  *
246  * Parameters:
247  *        win is the window on which the wgetch() was done.
248  *        curses_key is the value returned by wgetch().
249  *        fkey in/out (on input, it is nonzero if curses_key is a function key,
250  *                  and on output, it is nonzero if the result is a function key).
251  */
252 int
dlg_lookup_key(WINDOW * win,int curses_key,int * fkey)253 dlg_lookup_key(WINDOW *win, int curses_key, int *fkey)
254 {
255     LIST_BINDINGS *p;
256     DLG_KEYS_BINDING *q;
257 
258     /*
259      * Ignore mouse clicks, since they are already encoded properly.
260      */
261 #ifdef KEY_MOUSE
262     if (*fkey != 0 && curses_key == KEY_MOUSE) {
263           ;
264     } else
265 #endif
266           /*
267            * Ignore resize events, since they are already encoded properly.
268            */
269 #ifdef KEY_RESIZE
270     if (*fkey != 0 && curses_key == KEY_RESIZE) {
271           ;
272     } else
273 #endif
274     if (*fkey == 0 || curses_key < KEY_MAX) {
275           const char *name = WILDNAME;
276           if (win != 0) {
277               for (p = all_bindings; p != 0; p = p->link) {
278                     if (p->win == win) {
279                         name = p->name;
280                         break;
281                     }
282               }
283           }
284           for (p = all_bindings; p != 0; p = p->link) {
285               if (p->win == win ||
286                     (p->win == 0 &&
287                      (!strcmp(p->name, name) || !strcmp(p->name, WILDNAME)))) {
288                     int function_key = (*fkey != 0);
289                     for (q = p->binding; q->is_function_key >= 0; ++q) {
290                         if (p->buttons
291                               && !function_key
292                               && actual_curses_key(q) == (int) dlg_toupper(curses_key)) {
293                               *fkey = 0;
294                               return q->dialog_key;
295                         }
296                         if (actual_curses_key(q) == curses_key
297                               && q->is_function_key == function_key) {
298                               *fkey = q->dialog_key;
299                               return *fkey;
300                         }
301                     }
302               }
303           }
304     }
305     return curses_key;
306 }
307 
308 /*
309  * Test a dialog internal keycode to see if it corresponds to one of the push
310  * buttons on the widget such as "OK".
311  *
312  * This is only useful if there are user-defined key bindings, since there are
313  * no built-in bindings that map directly to DLGK_OK, etc.
314  *
315  * See also dlg_ok_buttoncode().
316  */
317 int
dlg_result_key(int dialog_key,int fkey GCC_UNUSED,int * resultp)318 dlg_result_key(int dialog_key, int fkey GCC_UNUSED, int *resultp)
319 {
320     int done = FALSE;
321 
322     DLG_TRACE(("# dlg_result_key(dialog_key=%d, fkey=%d)\n", dialog_key, fkey));
323 #ifdef KEY_RESIZE
324     if (dialog_state.had_resize) {
325           if (dialog_key == ERR) {
326               dialog_key = 0;
327           } else {
328               dialog_state.had_resize = FALSE;
329           }
330     } else if (fkey && dialog_key == KEY_RESIZE) {
331           dialog_state.had_resize = TRUE;
332     }
333 #endif
334 #ifdef HAVE_RC_FILE
335     if (fkey) {
336           switch ((DLG_KEYS_ENUM) dialog_key) {
337           case DLGK_OK:
338               if (!dialog_vars.nook) {
339                     *resultp = DLG_EXIT_OK;
340                     done = TRUE;
341               }
342               break;
343           case DLGK_CANCEL:
344               if (!dialog_vars.nocancel) {
345                     *resultp = DLG_EXIT_CANCEL;
346                     done = TRUE;
347               }
348               break;
349           case DLGK_EXTRA:
350               if (dialog_vars.extra_button) {
351                     *resultp = DLG_EXIT_EXTRA;
352                     done = TRUE;
353               }
354               break;
355           case DLGK_HELP:
356               if (dialog_vars.help_button) {
357                     *resultp = DLG_EXIT_HELP;
358                     done = TRUE;
359               }
360               break;
361           case DLGK_ESC:
362               *resultp = DLG_EXIT_ESC;
363               done = TRUE;
364               break;
365           default:
366               break;
367           }
368     } else
369 #endif
370     if (dialog_key == ESC) {
371           *resultp = DLG_EXIT_ESC;
372           done = TRUE;
373     } else if (dialog_key == ERR) {
374           *resultp = DLG_EXIT_ERROR;
375           done = TRUE;
376     }
377 
378     return done;
379 }
380 
381 /*
382  * If a key was bound to one of the button-codes in dlg_result_key(), fake
383  * a button-value and an "Enter" key to cause the calling widget to return
384  * the corresponding status.
385  *
386  * See dlg_ok_buttoncode(), which maps settings for ok/extra/help and button
387  * number into exit-code.
388  */
389 int
dlg_button_key(int exit_code,int * button,int * dialog_key,int * fkey)390 dlg_button_key(int exit_code, int *button, int *dialog_key, int *fkey)
391 {
392     int changed = FALSE;
393     switch (exit_code) {
394     case DLG_EXIT_OK:
395           if (!dialog_vars.nook) {
396               *button = 0;
397               changed = TRUE;
398           }
399           break;
400     case DLG_EXIT_EXTRA:
401           if (dialog_vars.extra_button) {
402               *button = dialog_vars.nook ? 0 : 1;
403               changed = TRUE;
404           }
405           break;
406     case DLG_EXIT_CANCEL:
407           if (!dialog_vars.nocancel) {
408               *button = dialog_vars.nook ? 1 : 2;
409               changed = TRUE;
410           }
411           break;
412     case DLG_EXIT_HELP:
413           if (dialog_vars.help_button) {
414               int cancel = dialog_vars.nocancel ? 0 : 1;
415               int extra = dialog_vars.extra_button ? 1 : 0;
416               int okay = dialog_vars.nook ? 0 : 1;
417               *button = okay + extra + cancel;
418               changed = TRUE;
419           }
420           break;
421     }
422     if (changed) {
423           DLG_TRACE(("# dlg_button_key(%d:%s) button %d\n",
424                        exit_code, dlg_exitcode2s(exit_code), *button));
425           *dialog_key = *fkey = DLGK_ENTER;
426     }
427     return changed;
428 }
429 
430 int
dlg_ok_button_key(int exit_code,int * button,int * dialog_key,int * fkey)431 dlg_ok_button_key(int exit_code, int *button, int *dialog_key, int *fkey)
432 {
433     int result;
434     DIALOG_VARS save;
435 
436     dlg_save_vars(&save);
437     dialog_vars.nocancel = TRUE;
438 
439     result = dlg_button_key(exit_code, button, dialog_key, fkey);
440 
441     dlg_restore_vars(&save);
442     return result;
443 }
444 
445 #ifdef HAVE_RC_FILE
446 typedef struct {
447     const char *name;
448     int code;
449 } CODENAME;
450 
451 #define ASCII_NAME(name,code)  { #name, code }
452 #define CURSES_NAME(upper) { #upper, KEY_ ## upper }
453 #define COUNT_CURSES  TableSize(curses_names)
454 static const CODENAME curses_names[] =
455 {
456     ASCII_NAME(ESC, '\033'),
457     ASCII_NAME(CR, '\r'),
458     ASCII_NAME(LF, '\n'),
459     ASCII_NAME(FF, '\f'),
460     ASCII_NAME(TAB, '\t'),
461     ASCII_NAME(DEL, '\177'),
462 
463     CURSES_NAME(DOWN),
464     CURSES_NAME(UP),
465     CURSES_NAME(LEFT),
466     CURSES_NAME(RIGHT),
467     CURSES_NAME(HOME),
468     CURSES_NAME(BACKSPACE),
469     CURSES_NAME(F0),
470     CURSES_NAME(DL),
471     CURSES_NAME(IL),
472     CURSES_NAME(DC),
473     CURSES_NAME(IC),
474     CURSES_NAME(EIC),
475     CURSES_NAME(CLEAR),
476     CURSES_NAME(EOS),
477     CURSES_NAME(EOL),
478     CURSES_NAME(SF),
479     CURSES_NAME(SR),
480     CURSES_NAME(NPAGE),
481     CURSES_NAME(PPAGE),
482     CURSES_NAME(STAB),
483     CURSES_NAME(CTAB),
484     CURSES_NAME(CATAB),
485     CURSES_NAME(ENTER),
486     CURSES_NAME(PRINT),
487     CURSES_NAME(LL),
488     CURSES_NAME(A1),
489     CURSES_NAME(A3),
490     CURSES_NAME(B2),
491     CURSES_NAME(C1),
492     CURSES_NAME(C3),
493     CURSES_NAME(BTAB),
494     CURSES_NAME(BEG),
495     CURSES_NAME(CANCEL),
496     CURSES_NAME(CLOSE),
497     CURSES_NAME(COMMAND),
498     CURSES_NAME(COPY),
499     CURSES_NAME(CREATE),
500     CURSES_NAME(END),
501     CURSES_NAME(EXIT),
502     CURSES_NAME(FIND),
503     CURSES_NAME(HELP),
504     CURSES_NAME(MARK),
505     CURSES_NAME(MESSAGE),
506     CURSES_NAME(MOVE),
507     CURSES_NAME(NEXT),
508     CURSES_NAME(OPEN),
509     CURSES_NAME(OPTIONS),
510     CURSES_NAME(PREVIOUS),
511     CURSES_NAME(REDO),
512     CURSES_NAME(REFERENCE),
513     CURSES_NAME(REFRESH),
514     CURSES_NAME(REPLACE),
515     CURSES_NAME(RESTART),
516     CURSES_NAME(RESUME),
517     CURSES_NAME(SAVE),
518     CURSES_NAME(SBEG),
519     CURSES_NAME(SCANCEL),
520     CURSES_NAME(SCOMMAND),
521     CURSES_NAME(SCOPY),
522     CURSES_NAME(SCREATE),
523     CURSES_NAME(SDC),
524     CURSES_NAME(SDL),
525     CURSES_NAME(SELECT),
526     CURSES_NAME(SEND),
527     CURSES_NAME(SEOL),
528     CURSES_NAME(SEXIT),
529     CURSES_NAME(SFIND),
530     CURSES_NAME(SHELP),
531     CURSES_NAME(SHOME),
532     CURSES_NAME(SIC),
533     CURSES_NAME(SLEFT),
534     CURSES_NAME(SMESSAGE),
535     CURSES_NAME(SMOVE),
536     CURSES_NAME(SNEXT),
537     CURSES_NAME(SOPTIONS),
538     CURSES_NAME(SPREVIOUS),
539     CURSES_NAME(SPRINT),
540     CURSES_NAME(SREDO),
541     CURSES_NAME(SREPLACE),
542     CURSES_NAME(SRIGHT),
543     CURSES_NAME(SRSUME),
544     CURSES_NAME(SSAVE),
545     CURSES_NAME(SSUSPEND),
546     CURSES_NAME(SUNDO),
547     CURSES_NAME(SUSPEND),
548     CURSES_NAME(UNDO),
549 };
550 
551 #define DIALOG_NAME(upper) { #upper, DLGK_ ## upper }
552 #define COUNT_DIALOG  TableSize(dialog_names)
553 static const CODENAME dialog_names[] =
554 {
555     DIALOG_NAME(OK),
556     DIALOG_NAME(CANCEL),
557     DIALOG_NAME(EXTRA),
558     DIALOG_NAME(HELP),
559     DIALOG_NAME(ESC),
560     DIALOG_NAME(PAGE_FIRST),
561     DIALOG_NAME(PAGE_LAST),
562     DIALOG_NAME(PAGE_NEXT),
563     DIALOG_NAME(PAGE_PREV),
564     DIALOG_NAME(ITEM_FIRST),
565     DIALOG_NAME(ITEM_LAST),
566     DIALOG_NAME(ITEM_NEXT),
567     DIALOG_NAME(ITEM_PREV),
568     DIALOG_NAME(FIELD_FIRST),
569     DIALOG_NAME(FIELD_LAST),
570     DIALOG_NAME(FIELD_NEXT),
571     DIALOG_NAME(FIELD_PREV),
572     DIALOG_NAME(FORM_FIRST),
573     DIALOG_NAME(FORM_LAST),
574     DIALOG_NAME(FORM_NEXT),
575     DIALOG_NAME(FORM_PREV),
576     DIALOG_NAME(GRID_UP),
577     DIALOG_NAME(GRID_DOWN),
578     DIALOG_NAME(GRID_LEFT),
579     DIALOG_NAME(GRID_RIGHT),
580     DIALOG_NAME(DELETE_LEFT),
581     DIALOG_NAME(DELETE_RIGHT),
582     DIALOG_NAME(DELETE_ALL),
583     DIALOG_NAME(ENTER),
584     DIALOG_NAME(BEGIN),
585     DIALOG_NAME(FINAL),
586     DIALOG_NAME(SELECT),
587     DIALOG_NAME(HELPFILE),
588     DIALOG_NAME(TRACE),
589     DIALOG_NAME(TOGGLE),
590     DIALOG_NAME(LEAVE)
591 };
592 
593 #define MAP2(letter,actual) { letter, actual }
594 
595 static const struct {
596     int letter;
597     int actual;
598 } escaped_letters[] = {
599 
600     MAP2('a', DLG_CTRL('G')),
601           MAP2('b', DLG_CTRL('H')),
602           MAP2('f', DLG_CTRL('L')),
603           MAP2('n', DLG_CTRL('J')),
604           MAP2('r', DLG_CTRL('M')),
605           MAP2('s', CHR_SPACE),
606           MAP2('t', DLG_CTRL('I')),
607           MAP2('\\', '\\'),
608 };
609 
610 #undef MAP2
611 
612 static char *
skip_white(char * s)613 skip_white(char *s)
614 {
615     while (*s != '\0' && isspace(UCH(*s)))
616           ++s;
617     return s;
618 }
619 
620 static char *
skip_black(char * s)621 skip_black(char *s)
622 {
623     while (*s != '\0' && !isspace(UCH(*s)))
624           ++s;
625     return s;
626 }
627 
628 /*
629  * Find a user-defined binding, given the curses key code.
630  */
631 static DLG_KEYS_BINDING *
find_binding(char * widget,int curses_key)632 find_binding(char *widget, int curses_key)
633 {
634     LIST_BINDINGS *p;
635     DLG_KEYS_BINDING *result = 0;
636 
637     for (p = all_bindings; p != 0; p = p->link) {
638           if (p->win == 0
639               && !dlg_strcmp(p->name, widget)
640               && actual_curses_key(p->binding) == curses_key) {
641               result = p->binding;
642               break;
643           }
644     }
645     return result;
646 }
647 
648 /*
649  * Built-in bindings have a nonzero "win" member, and the associated binding
650  * table can have more than one entry.  We keep those last, since lookups will
651  * find the user-defined bindings first and use those.
652  *
653  * Sort "*" (all-widgets) entries past named widgets, since those are less
654  * specific.
655  */
656 static int
compare_bindings(LIST_BINDINGS * a,LIST_BINDINGS * b)657 compare_bindings(LIST_BINDINGS * a, LIST_BINDINGS * b)
658 {
659     int result = 0;
660     if (a->win == b->win) {
661           if (!strcmp(a->name, b->name)) {
662               result = actual_curses_key(a->binding) - actual_curses_key(b->binding);
663           } else if (!strcmp(b->name, WILDNAME)) {
664               result = -1;
665           } else if (!strcmp(a->name, WILDNAME)) {
666               result = 1;
667           } else {
668               result = dlg_strcmp(a->name, b->name);
669           }
670     } else if (b->win) {
671           result = -1;
672     } else {
673           result = 1;
674     }
675     return result;
676 }
677 
678 /*
679  * Find a user-defined binding, given the curses key code.  If it does not
680  * exist, create a new one, inserting it into the linked list, keeping it
681  * sorted to simplify lookups for user-defined bindings that can override
682  * the built-in bindings.
683  */
684 static DLG_KEYS_BINDING *
make_binding(char * widget,int curses_key,int is_function,int dialog_key)685 make_binding(char *widget, int curses_key, int is_function, int dialog_key)
686 {
687     LIST_BINDINGS *entry = 0;
688     DLG_KEYS_BINDING *data = 0;
689     char *name;
690     DLG_KEYS_BINDING *result = find_binding(widget, curses_key);
691 
692     if (result == 0
693           && (entry = dlg_calloc(LIST_BINDINGS, 1)) != 0
694           && (data = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0
695           && (name = dlg_strclone(widget)) != 0) {
696           LIST_BINDINGS *p, *q;
697 
698           entry->name = name;
699           entry->binding = data;
700 
701           data[0].is_function_key = is_function;
702           data[0].curses_key = curses_key;
703           data[0].dialog_key = dialog_key;
704 
705           data[1] = end_keys_binding;
706 
707           for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) {
708               if (compare_bindings(entry, p) < 0) {
709                     break;
710               }
711           }
712           if (q != 0) {
713               q->link = entry;
714           } else {
715               all_bindings = entry;
716           }
717           if (p != 0) {
718               entry->link = p;
719           }
720           result = data;
721     } else if (entry != 0) {
722           free(entry);
723           if (data)
724               free(data);
725     }
726 
727     return result;
728 }
729 
730 static int
decode_escaped(char ** string)731 decode_escaped(char **string)
732 {
733     int result = 0;
734 
735     if (IsOctal(**string)) {
736           int limit = 3;
737           while (limit-- > 0 && IsOctal(**string)) {
738               int ch = (**string);
739               *string += 1;
740               result = (result << 3) | (ch - '0');
741           }
742     } else {
743           unsigned n;
744 
745           for (n = 0; n < TableSize(escaped_letters); ++n) {
746               if (**string == escaped_letters[n].letter) {
747                     *string += 1;
748                     result = escaped_letters[n].actual;
749                     break;
750               }
751           }
752     }
753     return result;
754 }
755 
756 static char *
encode_escaped(int value)757 encode_escaped(int value)
758 {
759     static char result[80];
760     unsigned n;
761     bool found = FALSE;
762     for (n = 0; n < TableSize(escaped_letters); ++n) {
763           if (value == escaped_letters[n].actual) {
764               found = TRUE;
765               sprintf(result, "%c", escaped_letters[n].letter);
766               break;
767           }
768     }
769     if (!found) {
770           sprintf(result, "%03o", value & 0xff);
771     }
772     return result;
773 }
774 
775 /*
776  * Parse the parameters of the "bindkey" configuration-file entry.  This
777  * expects widget name which may be "*", followed by curses key definition and
778  * then dialog key definition.
779  *
780  * The curses key "should" be one of the names (ignoring case) from
781  * curses_names[], but may also be a single control character (prefix "^" or
782  * "~" depending on whether it is C0 or C1), or an escaped single character.
783  * Binding a printable character with dialog is possible but not useful.
784  *
785  * The dialog key must be one of the names from dialog_names[].
786  */
787 int
dlg_parse_bindkey(char * params)788 dlg_parse_bindkey(char *params)
789 {
790     char *p = skip_white(params);
791     int result = FALSE;
792     char *widget;
793     int curses_key;
794     int dialog_key;
795 
796     curses_key = -1;
797     dialog_key = -1;
798     widget = p;
799 
800     p = skip_black(p);
801     if (p != widget && *p != '\0') {
802           char *q;
803           unsigned xx;
804           bool escaped = FALSE;
805           int modified = 0;
806           int is_function = FALSE;
807 
808           *p++ = '\0';
809           p = skip_white(p);
810           q = p;
811           while (*p != '\0' && curses_key < 0) {
812               if (escaped) {
813                     escaped = FALSE;
814                     curses_key = decode_escaped(&p);
815               } else if (*p == CHR_BACKSLASH) {
816                     escaped = TRUE;
817               } else if (modified) {
818                     if (*p == '?') {
819                         curses_key = ((modified == '^')
820                                           ? 127
821                                           : 255);
822                     } else {
823                         curses_key = ((modified == '^')
824                                           ? (*p & 0x1f)
825                                           : ((*p & 0x1f) | 0x80));
826                     }
827               } else if (*p == '^') {
828                     modified = *p;
829               } else if (*p == '~') {
830                     modified = *p;
831               } else if (isspace(UCH(*p))) {
832                     break;
833               }
834               ++p;
835           }
836           if (!isspace(UCH(*p))) {
837               ;
838           } else {
839               *p++ = '\0';
840               if (curses_key < 0) {
841                     char fprefix[2];
842                     char check[2];
843                     int keynumber;
844                     if (sscanf(q, "%1[Ff]%d%c", fprefix, &keynumber, check) == 2) {
845                         curses_key = KEY_F(keynumber);
846                         is_function = TRUE;
847                     } else {
848                         for (xx = 0; xx < COUNT_CURSES; ++xx) {
849                               if (!dlg_strcmp(curses_names[xx].name, q)) {
850                                   curses_key = curses_names[xx].code;
851                                   is_function = (curses_key >= KEY_MIN);
852                                   break;
853                               }
854                         }
855                     }
856               }
857           }
858           q = skip_white(p);
859           p = skip_black(q);
860           if (p != q) {
861               for (xx = 0; xx < COUNT_DIALOG; ++xx) {
862                     if (!dlg_strcmp(dialog_names[xx].name, q)) {
863                         dialog_key = dialog_names[xx].code;
864                         break;
865                     }
866               }
867           }
868           if (*widget != '\0'
869               && curses_key >= 0
870               && dialog_key >= 0
871               && make_binding(widget, curses_key, is_function, dialog_key) != 0) {
872               result = TRUE;
873           }
874     }
875     return result;
876 }
877 
878 static void
dump_curses_key(FILE * fp,int curses_key)879 dump_curses_key(FILE *fp, int curses_key)
880 {
881     if (curses_key > KEY_MIN) {
882           unsigned n;
883           bool found = FALSE;
884           for (n = 0; n < COUNT_CURSES; ++n) {
885               if (curses_names[n].code == curses_key) {
886                     fprintf(fp, "%s", curses_names[n].name);
887                     found = TRUE;
888                     break;
889               }
890           }
891           if (!found) {
892 #ifdef KEY_MOUSE
893               if (is_DLGK_MOUSE(curses_key)) {
894                     fprintf(fp, "MOUSE-");
895                     dump_curses_key(fp, curses_key - M_EVENT);
896               } else
897 #endif
898               if (curses_key >= KEY_F(0)) {
899                     fprintf(fp, "F%d", curses_key - KEY_F(0));
900               } else {
901                     fprintf(fp, "curses%d", curses_key);
902               }
903           }
904     } else if (curses_key >= 0 && curses_key < 32) {
905           fprintf(fp, "^%c", curses_key + 64);
906     } else if (curses_key == 127) {
907           fprintf(fp, "^?");
908     } else if (curses_key >= 128 && curses_key < 160) {
909           fprintf(fp, "~%c", curses_key - 64);
910     } else if (curses_key == 255) {
911           fprintf(fp, "~?");
912     } else if (curses_key > 32 &&
913                  curses_key < 127 &&
914                  curses_key != CHR_BACKSLASH) {
915           fprintf(fp, "%c", curses_key);
916     } else {
917           fprintf(fp, "%c%s", CHR_BACKSLASH, encode_escaped(curses_key));
918     }
919 }
920 
921 static void
dump_dialog_key(FILE * fp,int dialog_key)922 dump_dialog_key(FILE *fp, int dialog_key)
923 {
924     unsigned n;
925     bool found = FALSE;
926     for (n = 0; n < COUNT_DIALOG; ++n) {
927           if (dialog_names[n].code == dialog_key) {
928               fputs(dialog_names[n].name, fp);
929               found = TRUE;
930               break;
931           }
932     }
933     if (!found) {
934           fprintf(fp, "dialog%d", dialog_key);
935     }
936 }
937 
938 static void
dump_one_binding(FILE * fp,WINDOW * win,const char * widget,DLG_KEYS_BINDING * binding)939 dump_one_binding(FILE *fp,
940                      WINDOW *win,
941                      const char *widget,
942                      DLG_KEYS_BINDING * binding)
943 {
944     int actual;
945     int fkey = (actual_curses_key(binding) > 255);
946 
947     fprintf(fp, "bindkey %s ", widget);
948     dump_curses_key(fp, actual_curses_key(binding));
949     fputc(' ', fp);
950     dump_dialog_key(fp, binding->dialog_key);
951     actual = dlg_lookup_key(win, actual_curses_key(binding), &fkey);
952 #ifdef KEY_MOUSE
953     if (is_DLGK_MOUSE(actual_curses_key(binding)) && is_DLGK_MOUSE(actual)) {
954           ;                             /* EMPTY */
955     } else
956 #endif
957     if (actual != binding->dialog_key) {
958           fprintf(fp, "\t# overridden by ");
959           dump_dialog_key(fp, actual);
960     }
961     fputc('\n', fp);
962 }
963 
964 /*
965  * Dump bindings for the given window.  If it is a null, then this dumps the
966  * initial bindings which were loaded from the rc-file that are used as
967  * overall defaults.
968  */
969 void
dlg_dump_window_keys(FILE * fp,WINDOW * win)970 dlg_dump_window_keys(FILE *fp, WINDOW *win)
971 {
972     if (fp != 0) {
973           LIST_BINDINGS *p;
974           DLG_KEYS_BINDING *q;
975           const char *last = "";
976 
977           for (p = all_bindings; p != 0; p = p->link) {
978               if (p->win == win) {
979                     if (dlg_strcmp(last, p->name)) {
980                         fprintf(fp, "# key bindings for %s widgets%s\n",
981                                   !strcmp(p->name, WILDNAME) ? "all" : p->name,
982                                   win == 0 ? " (user-defined)" : "");
983                         last = p->name;
984                     }
985                     for (q = p->binding; q->is_function_key >= 0; ++q) {
986                         dump_one_binding(fp, win, p->name, q);
987                     }
988               }
989           }
990     }
991 }
992 
993 /*
994  * Dump all of the bindings which are not specific to a given widget, i.e.,
995  * the "win" member is null.
996  */
997 void
dlg_dump_keys(FILE * fp)998 dlg_dump_keys(FILE *fp)
999 {
1000     if (fp != 0) {
1001           LIST_BINDINGS *p;
1002           unsigned count = 0;
1003 
1004           for (p = all_bindings; p != 0; p = p->link) {
1005               if (p->win == 0) {
1006                     ++count;
1007               }
1008           }
1009           if (count != 0) {
1010               dlg_dump_window_keys(fp, 0);
1011           }
1012     }
1013 }
1014 #endif /* HAVE_RC_FILE */
1015