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 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 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 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 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 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 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 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 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 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 * 613 skip_white(char *s) 614 { 615 while (*s != '\0' && isspace(UCH(*s))) 616 ++s; 617 return s; 618 } 619 620 static char * 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 * 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 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 * 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 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 * 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 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 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 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 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 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 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