2 * $Id: dlg_keys.c,v 1.62 2022/04/14 22:08:43 tom Exp $
4 * dlg_keys.c -- runtime binding support for dialog
6 * Copyright 2006-2020,2022 Thomas E. Dickey
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.
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.
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.
24 #include <dlg_internals.h>
27 #define LIST_BINDINGS struct _list_bindings
29 #define CHR_BACKSLASH '\\'
30 #define IsOctal(ch) ((ch) >= '0' && (ch) <= '7')
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 */
41 static LIST_BINDINGS
*all_bindings
;
42 static const DLG_KEYS_BINDING end_keys_binding
= END_KEYS_BINDING
;
45 * For a given named widget's window, associate a binding table.
48 dlg_register_window(WINDOW
*win
, const char *name
, DLG_KEYS_BINDING
* binding
)
52 for (p
= all_bindings
, q
= 0; p
!= 0; q
= p
, p
= p
->link
) {
53 if (p
->win
== win
&& !strcmp(p
->name
, name
)) {
58 /* add built-in bindings at the end of the list (see compare_bindings). */
59 if ((p
= dlg_calloc(LIST_BINDINGS
, 1)) != 0) {
69 #if defined(HAVE_DLG_TRACE) && defined(HAVE_RC_FILE)
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.
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
));
85 * A few CHR_xxx symbols in the default bindings are provided to fill in for
86 * incomplete terminal descriptions, or for consistency.
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.
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().
96 * The killchar() function's return value need not correspond to any of the
97 * KEY_xxx symbols. Just map CHR_KILL to that.
100 actual_curses_key(DLG_KEYS_BINDING
* p
)
102 int result
= p
->curses_key
;
105 switch (p
->curses_key
) {
107 if ((checks
= killchar()) > 0) {
112 if ((checks
= erasechar()) > 0) {
117 if ((checks
= erasechar()) > 0 &&
118 (checks
== result
)) {
119 result
= CHR_BACKSPACE
;
127 * Unlike dlg_lookup_key(), this looks for either widget-builtin or rc-file
128 * definitions, depending on whether 'win' is null.
131 key_is_bound(WINDOW
*win
, const char *name
, int curses_key
, int function_key
)
135 for (p
= all_bindings
; p
!= 0; p
= p
->link
) {
136 if (p
->win
== win
&& !dlg_strcmp(p
->name
, name
)) {
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
) {
150 * Call this function after dlg_register_window(), for the list of button
151 * labels associated with the widget.
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
158 * See dlg_char_to_button().
161 dlg_register_buttons(WINDOW
*win
, const char *name
, const char **buttons
)
170 for (n
= 0; buttons
[n
] != 0; ++n
) {
171 int curses_key
= dlg_button_to_char(buttons
[n
]);
173 /* ignore binding if there is no key to bind */
177 /* ignore multibyte characters */
178 if (curses_key
>= KEY_MIN
)
181 /* if it is not bound in the widget, skip it (no conflicts) */
182 if (!key_is_bound(win
, name
, curses_key
, FALSE
))
186 /* if it is bound in the rc-file, skip it */
187 if (key_is_bound(0, name
, curses_key
, FALSE
))
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
;
203 /* put these at the beginning, to override the widget's table */
204 p
->link
= all_bindings
;
214 * Remove the bindings for a given window.
217 dlg_unregister_window(WINDOW
*win
)
219 LIST_BINDINGS
*p
, *q
;
221 for (p
= all_bindings
, q
= 0; p
!= 0; p
= p
->link
) {
226 all_bindings
= p
->link
;
228 /* the user-defined and buttons-bindings all are length=1 */
229 if (p
->binding
[1].is_function_key
< 0)
232 dlg_unregister_window(win
);
240 * Call this after wgetch(), using the same window pointer and passing
243 * If there is no binding associated with the widget, it simply returns
244 * the given curses-key.
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).
253 dlg_lookup_key(WINDOW
*win
, int curses_key
, int *fkey
)
259 * Ignore mouse clicks, since they are already encoded properly.
262 if (*fkey
!= 0 && curses_key
== KEY_MOUSE
) {
267 * Ignore resize events, since they are already encoded properly.
270 if (*fkey
!= 0 && curses_key
== KEY_RESIZE
) {
274 if (*fkey
== 0 || curses_key
< KEY_MAX
) {
275 const char *name
= WILDNAME
;
277 for (p
= all_bindings
; p
!= 0; p
= p
->link
) {
284 for (p
= all_bindings
; p
!= 0; p
= p
->link
) {
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
) {
292 && actual_curses_key(q
) == (int) dlg_toupper(curses_key
)) {
294 return q
->dialog_key
;
296 if (actual_curses_key(q
) == curses_key
297 && q
->is_function_key
== function_key
) {
298 *fkey
= q
->dialog_key
;
309 * Test a dialog internal keycode to see if it corresponds to one of the push
310 * buttons on the widget such as "OK".
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.
315 * See also dlg_ok_buttoncode().
318 dlg_result_key(int dialog_key
, int fkey GCC_UNUSED
, int *resultp
)
322 DLG_TRACE(("# dlg_result_key(dialog_key=%d, fkey=%d)\n", dialog_key
, fkey
));
324 if (dialog_state
.had_resize
) {
325 if (dialog_key
== ERR
) {
328 dialog_state
.had_resize
= FALSE
;
330 } else if (fkey
&& dialog_key
== KEY_RESIZE
) {
331 dialog_state
.had_resize
= TRUE
;
336 switch ((DLG_KEYS_ENUM
) dialog_key
) {
338 if (!dialog_vars
.nook
) {
339 *resultp
= DLG_EXIT_OK
;
344 if (!dialog_vars
.nocancel
) {
345 *resultp
= DLG_EXIT_CANCEL
;
350 if (dialog_vars
.extra_button
) {
351 *resultp
= DLG_EXIT_EXTRA
;
356 if (dialog_vars
.help_button
) {
357 *resultp
= DLG_EXIT_HELP
;
362 *resultp
= DLG_EXIT_ESC
;
370 if (dialog_key
== ESC
) {
371 *resultp
= DLG_EXIT_ESC
;
373 } else if (dialog_key
== ERR
) {
374 *resultp
= DLG_EXIT_ERROR
;
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.
386 * See dlg_ok_buttoncode(), which maps settings for ok/extra/help and button
387 * number into exit-code.
390 dlg_button_key(int exit_code
, int *button
, int *dialog_key
, int *fkey
)
395 if (!dialog_vars
.nook
) {
401 if (dialog_vars
.extra_button
) {
402 *button
= dialog_vars
.nook
? 0 : 1;
406 case DLG_EXIT_CANCEL
:
407 if (!dialog_vars
.nocancel
) {
408 *button
= dialog_vars
.nook
? 1 : 2;
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
;
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
;
431 dlg_ok_button_key(int exit_code
, int *button
, int *dialog_key
, int *fkey
)
436 dlg_save_vars(&save
);
437 dialog_vars
.nocancel
= TRUE
;
439 result
= dlg_button_key(exit_code
, button
, dialog_key
, fkey
);
441 dlg_restore_vars(&save
);
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
[] =
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'),
468 CURSES_NAME(BACKSPACE
),
497 CURSES_NAME(COMMAND
),
505 CURSES_NAME(MESSAGE
),
509 CURSES_NAME(OPTIONS
),
510 CURSES_NAME(PREVIOUS
),
512 CURSES_NAME(REFERENCE
),
513 CURSES_NAME(REFRESH
),
514 CURSES_NAME(REPLACE
),
515 CURSES_NAME(RESTART
),
519 CURSES_NAME(SCANCEL
),
520 CURSES_NAME(SCOMMAND
),
522 CURSES_NAME(SCREATE
),
534 CURSES_NAME(SMESSAGE
),
537 CURSES_NAME(SOPTIONS
),
538 CURSES_NAME(SPREVIOUS
),
541 CURSES_NAME(SREPLACE
),
545 CURSES_NAME(SSUSPEND
),
547 CURSES_NAME(SUSPEND
),
551 #define DIALOG_NAME(upper) { #upper, DLGK_ ## upper }
552 #define COUNT_DIALOG TableSize(dialog_names)
553 static const CODENAME dialog_names
[] =
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
),
587 DIALOG_NAME(HELPFILE
),
593 #define MAP2(letter,actual) { letter, actual }
595 static const struct {
598 } escaped_letters
[] = {
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')),
615 while (*s
!= '\0' && isspace(UCH(*s
)))
623 while (*s
!= '\0' && !isspace(UCH(*s
)))
629 * Find a user-defined binding, given the curses key code.
631 static DLG_KEYS_BINDING
*
632 find_binding(char *widget
, int curses_key
)
635 DLG_KEYS_BINDING
*result
= 0;
637 for (p
= all_bindings
; p
!= 0; p
= p
->link
) {
639 && !dlg_strcmp(p
->name
, widget
)
640 && actual_curses_key(p
->binding
) == curses_key
) {
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.
653 * Sort "*" (all-widgets) entries past named widgets, since those are less
657 compare_bindings(LIST_BINDINGS
* a
, LIST_BINDINGS
* b
)
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
)) {
665 } else if (!strcmp(a
->name
, WILDNAME
)) {
668 result
= dlg_strcmp(a
->name
, b
->name
);
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.
684 static DLG_KEYS_BINDING
*
685 make_binding(char *widget
, int curses_key
, int is_function
, int dialog_key
)
687 LIST_BINDINGS
*entry
= 0;
688 DLG_KEYS_BINDING
*data
= 0;
690 DLG_KEYS_BINDING
*result
= find_binding(widget
, curses_key
);
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
;
699 entry
->binding
= data
;
701 data
[0].is_function_key
= is_function
;
702 data
[0].curses_key
= curses_key
;
703 data
[0].dialog_key
= dialog_key
;
705 data
[1] = end_keys_binding
;
707 for (p
= all_bindings
, q
= 0; p
!= 0; q
= p
, p
= p
->link
) {
708 if (compare_bindings(entry
, p
) < 0) {
715 all_bindings
= entry
;
721 } else if (entry
!= 0) {
731 decode_escaped(char **string
)
735 if (IsOctal(**string
)) {
737 while (limit
-- > 0 && IsOctal(**string
)) {
740 result
= (result
<< 3) | (ch
- '0');
745 for (n
= 0; n
< TableSize(escaped_letters
); ++n
) {
746 if (**string
== escaped_letters
[n
].letter
) {
748 result
= escaped_letters
[n
].actual
;
757 encode_escaped(int value
)
759 static char result
[80];
762 for (n
= 0; n
< TableSize(escaped_letters
); ++n
) {
763 if (value
== escaped_letters
[n
].actual
) {
765 sprintf(result
, "%c", escaped_letters
[n
].letter
);
770 sprintf(result
, "%03o", value
& 0xff);
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.
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.
785 * The dialog key must be one of the names from dialog_names[].
788 dlg_parse_bindkey(char *params
)
790 char *p
= skip_white(params
);
801 if (p
!= widget
&& *p
!= '\0') {
804 bool escaped
= FALSE
;
806 int is_function
= FALSE
;
811 while (*p
!= '\0' && curses_key
< 0) {
814 curses_key
= decode_escaped(&p
);
815 } else if (*p
== CHR_BACKSLASH
) {
817 } else if (modified
) {
819 curses_key
= ((modified
== '^')
823 curses_key
= ((modified
== '^')
825 : ((*p
& 0x1f) | 0x80));
827 } else if (*p
== '^') {
829 } else if (*p
== '~') {
831 } else if (isspace(UCH(*p
))) {
836 if (!isspace(UCH(*p
))) {
840 if (curses_key
< 0) {
844 if (sscanf(q
, "%1[Ff]%d%c", fprefix
, &keynumber
, check
) == 2) {
845 curses_key
= KEY_F(keynumber
);
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
);
861 for (xx
= 0; xx
< COUNT_DIALOG
; ++xx
) {
862 if (!dlg_strcmp(dialog_names
[xx
].name
, q
)) {
863 dialog_key
= dialog_names
[xx
].code
;
871 && make_binding(widget
, curses_key
, is_function
, dialog_key
) != 0) {
879 dump_curses_key(FILE *fp
, int curses_key
)
881 if (curses_key
> KEY_MIN
) {
884 for (n
= 0; n
< COUNT_CURSES
; ++n
) {
885 if (curses_names
[n
].code
== curses_key
) {
886 fprintf(fp
, "%s", curses_names
[n
].name
);
893 if (is_DLGK_MOUSE(curses_key
)) {
894 fprintf(fp
, "MOUSE-");
895 dump_curses_key(fp
, curses_key
- M_EVENT
);
898 if (curses_key
>= KEY_F(0)) {
899 fprintf(fp
, "F%d", curses_key
- KEY_F(0));
901 fprintf(fp
, "curses%d", curses_key
);
904 } else if (curses_key
>= 0 && curses_key
< 32) {
905 fprintf(fp
, "^%c", curses_key
+ 64);
906 } else if (curses_key
== 127) {
908 } else if (curses_key
>= 128 && curses_key
< 160) {
909 fprintf(fp
, "~%c", curses_key
- 64);
910 } else if (curses_key
== 255) {
912 } else if (curses_key
> 32 &&
914 curses_key
!= CHR_BACKSLASH
) {
915 fprintf(fp
, "%c", curses_key
);
917 fprintf(fp
, "%c%s", CHR_BACKSLASH
, encode_escaped(curses_key
));
922 dump_dialog_key(FILE *fp
, int dialog_key
)
926 for (n
= 0; n
< COUNT_DIALOG
; ++n
) {
927 if (dialog_names
[n
].code
== dialog_key
) {
928 fputs(dialog_names
[n
].name
, fp
);
934 fprintf(fp
, "dialog%d", dialog_key
);
939 dump_one_binding(FILE *fp
,
942 DLG_KEYS_BINDING
* binding
)
945 int fkey
= (actual_curses_key(binding
) > 255);
947 fprintf(fp
, "bindkey %s ", widget
);
948 dump_curses_key(fp
, actual_curses_key(binding
));
950 dump_dialog_key(fp
, binding
->dialog_key
);
951 actual
= dlg_lookup_key(win
, actual_curses_key(binding
), &fkey
);
953 if (is_DLGK_MOUSE(actual_curses_key(binding
)) && is_DLGK_MOUSE(actual
)) {
957 if (actual
!= binding
->dialog_key
) {
958 fprintf(fp
, "\t# overridden by ");
959 dump_dialog_key(fp
, actual
);
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
970 dlg_dump_window_keys(FILE *fp
, WINDOW
*win
)
975 const char *last
= "";
977 for (p
= all_bindings
; p
!= 0; p
= p
->link
) {
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)" : "");
985 for (q
= p
->binding
; q
->is_function_key
>= 0; ++q
) {
986 dump_one_binding(fp
, win
, p
->name
, q
);
994 * Dump all of the bindings which are not specific to a given widget, i.e.,
995 * the "win" member is null.
998 dlg_dump_keys(FILE *fp
)
1004 for (p
= all_bindings
; p
!= 0; p
= p
->link
) {
1010 dlg_dump_window_keys(fp
, 0);
1014 #endif /* HAVE_RC_FILE */