xref: /freebsd/contrib/dialog/dlg_keys.c (revision 595e514d0df2bac5b813d35f83e32875dbf16a83)
1 /*
2  *  $Id: dlg_keys.c,v 1.26 2009/02/22 16:19:51 tom Exp $
3  *
4  * dlg_keys.c -- runtime binding support for dialog
5  *
6  * Copyright 2006-2007,2009 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 <dialog.h>
25 #include <dlg_keys.h>
26 
27 #define LIST_BINDINGS struct _list_bindings
28 
29 LIST_BINDINGS {
30     LIST_BINDINGS *link;
31     WINDOW *win;		/* window on which widget gets input */
32     const char *name;		/* widget name */
33     bool buttons;		/* true only for dlg_register_buttons() */
34     DLG_KEYS_BINDING *binding;	/* list of bindings */
35 };
36 
37 static LIST_BINDINGS *all_bindings;
38 static const DLG_KEYS_BINDING end_keys_binding = END_KEYS_BINDING;
39 
40 /*
41  * For a given named widget's window, associate a binding table.
42  */
43 void
44 dlg_register_window(WINDOW *win, const char *name, DLG_KEYS_BINDING * binding)
45 {
46     LIST_BINDINGS *p, *q;
47 
48     for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) {
49 	if (p->win == win && !strcmp(p->name, name)) {
50 	    p->binding = binding;
51 	    return;
52 	}
53     }
54     /* add built-in bindings at the end of the list (see compare_bindings). */
55     if ((p = dlg_calloc(LIST_BINDINGS, 1)) != 0) {
56 	p->win = win;
57 	p->name = name;
58 	p->binding = binding;
59 	if (q != 0)
60 	    q->link = p;
61 	else
62 	    all_bindings = p;
63     }
64 }
65 
66 /*
67  * Unlike dlg_lookup_key(), this looks for either widget-builtin or rc-file
68  * definitions, depending on whether 'win' is null.
69  */
70 static int
71 key_is_bound(WINDOW *win, const char *name, int curses_key, int function_key)
72 {
73     LIST_BINDINGS *p;
74 
75     for (p = all_bindings; p != 0; p = p->link) {
76 	if (p->win == win && !dlg_strcmp(p->name, name)) {
77 	    int n;
78 	    for (n = 0; p->binding[n].is_function_key >= 0; ++n) {
79 		if (p->binding[n].curses_key == curses_key
80 		    && p->binding[n].is_function_key == function_key) {
81 		    return TRUE;
82 		}
83 	    }
84 	}
85     }
86     return FALSE;
87 }
88 
89 /*
90  * Call this function after dlg_register_window(), for the list of button
91  * labels associated with the widget.
92  *
93  * Ensure that dlg_lookup_key() will not accidentally translate a key that
94  * we would like to use for a button abbreviation to some other key, e.g.,
95  * h/j/k/l for navigation into a cursor key.  Do this by binding the key
96  * to itself.
97  *
98  * See dlg_char_to_button().
99  */
100 void
101 dlg_register_buttons(WINDOW *win, const char *name, const char **buttons)
102 {
103     int n;
104     LIST_BINDINGS *p;
105     DLG_KEYS_BINDING *q;
106 
107     if (buttons == 0)
108 	return;
109 
110     for (n = 0; buttons[n] != 0; ++n) {
111 	int curses_key = dlg_button_to_char(buttons[n]);
112 
113 	/* ignore multibyte characters */
114 	if (curses_key >= KEY_MIN)
115 	    continue;
116 
117 	/* if it is not bound in the widget, skip it (no conflicts) */
118 	if (!key_is_bound(win, name, curses_key, FALSE))
119 	    continue;
120 
121 #ifdef HAVE_RC_FILE
122 	/* if it is bound in the rc-file, skip it */
123 	if (key_is_bound(0, name, curses_key, FALSE))
124 	    continue;
125 #endif
126 
127 	if ((p = dlg_calloc(LIST_BINDINGS, 1)) != 0) {
128 	    if ((q = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0) {
129 		q[0].is_function_key = 0;
130 		q[0].curses_key = curses_key;
131 		q[0].dialog_key = curses_key;
132 		q[1] = end_keys_binding;
133 
134 		p->win = win;
135 		p->name = name;
136 		p->buttons = TRUE;
137 		p->binding = q;
138 
139 		/* put these at the beginning, to override the widget's table */
140 		p->link = all_bindings;
141 		all_bindings = p;
142 	    } else {
143 		free(p);
144 	    }
145 	}
146     }
147 }
148 
149 /*
150  * Remove the bindings for a given window.
151  */
152 void
153 dlg_unregister_window(WINDOW *win)
154 {
155     LIST_BINDINGS *p, *q;
156 
157     for (p = all_bindings, q = 0; p != 0; p = p->link) {
158 	if (p->win == win) {
159 	    if (q != 0) {
160 		q->link = p->link;
161 	    } else {
162 		all_bindings = p->link;
163 	    }
164 	    /* the user-defined and buttons-bindings all are length=1 */
165 	    if (p->binding[1].is_function_key < 0)
166 		free(p->binding);
167 	    free(p);
168 	    dlg_unregister_window(win);
169 	    break;
170 	}
171 	q = p;
172     }
173 }
174 
175 /*
176  * Call this after wgetch(), using the same window pointer and passing
177  * the curses-key.
178  *
179  * If there is no binding associated with the widget, it simply returns
180  * the given curses-key.
181  *
182  * Parameters:
183  *	win is the window on which the wgetch() was done.
184  *	curses_key is the value returned by wgetch().
185  *	fkey in/out (on input, it is true if curses_key is a function key,
186  *		and on output, it is true if the result is a function key).
187  */
188 int
189 dlg_lookup_key(WINDOW *win, int curses_key, int *fkey)
190 {
191     LIST_BINDINGS *p;
192     int n;
193 
194     /*
195      * Ignore mouse clicks, since they are already encoded properly.
196      */
197 #ifdef KEY_MOUSE
198     if (*fkey != 0 && curses_key == KEY_MOUSE) {
199 	;
200     } else
201 #endif
202 	/*
203 	 * Ignore resize events, since they are already encoded properly.
204 	 */
205 #ifdef KEY_RESIZE
206     if (*fkey != 0 && curses_key == KEY_RESIZE) {
207 	;
208     } else
209 #endif
210     if (*fkey == 0 || curses_key < KEY_MAX) {
211 	for (p = all_bindings; p != 0; p = p->link) {
212 	    if (p->win == win || p->win == 0) {
213 		int function_key = (*fkey != 0);
214 		for (n = 0; p->binding[n].is_function_key >= 0; ++n) {
215 		    if (p->buttons
216 			&& !function_key
217 			&& p->binding[n].curses_key == (int) dlg_toupper(curses_key)) {
218 			*fkey = 0;
219 			return p->binding[n].dialog_key;
220 		    }
221 		    if (p->binding[n].curses_key == curses_key
222 			&& p->binding[n].is_function_key == function_key) {
223 			*fkey = p->binding[n].dialog_key;
224 			return *fkey;
225 		    }
226 		}
227 	    }
228 	}
229     }
230     return curses_key;
231 }
232 
233 /*
234  * Test a dialog internal keycode to see if it corresponds to one of the push
235  * buttons on the widget such as "OK".
236  *
237  * This is only useful if there are user-defined key bindings, since there are
238  * no built-in bindings that map directly to DLGK_OK, etc.
239  *
240  * See also dlg_ok_buttoncode().
241  */
242 int
243 dlg_result_key(int dialog_key, int fkey GCC_UNUSED, int *resultp)
244 {
245     int done = FALSE;
246 
247 #ifdef HAVE_RC_FILE
248     if (fkey) {
249 	switch ((DLG_KEYS_ENUM) dialog_key) {
250 	case DLGK_OK:
251 	    *resultp = DLG_EXIT_OK;
252 	    done = TRUE;
253 	    break;
254 	case DLGK_CANCEL:
255 	    if (!dialog_vars.nocancel) {
256 		*resultp = DLG_EXIT_CANCEL;
257 		done = TRUE;
258 	    }
259 	    break;
260 	case DLGK_EXTRA:
261 	    if (dialog_vars.extra_button) {
262 		*resultp = DLG_EXIT_EXTRA;
263 		done = TRUE;
264 	    }
265 	    break;
266 	case DLGK_HELP:
267 	    if (dialog_vars.help_button) {
268 		*resultp = DLG_EXIT_HELP;
269 		done = TRUE;
270 	    }
271 	    break;
272 	case DLGK_ESC:
273 	    *resultp = DLG_EXIT_ESC;
274 	    done = TRUE;
275 	    break;
276 	default:
277 	    break;
278 	}
279     } else
280 #endif
281     if (dialog_key == ESC) {
282 	*resultp = DLG_EXIT_ESC;
283 	done = TRUE;
284     } else if (dialog_key == ERR) {
285 	*resultp = DLG_EXIT_ERROR;
286 	done = TRUE;
287     }
288 
289     return done;
290 }
291 
292 #ifdef HAVE_RC_FILE
293 typedef struct {
294     const char *name;
295     int code;
296 } CODENAME;
297 
298 #define CURSES_NAME(upper) { #upper, KEY_ ## upper }
299 #define COUNT_CURSES  sizeof(curses_names)/sizeof(curses_names[0])
300 static const CODENAME curses_names[] =
301 {
302     CURSES_NAME(DOWN),
303     CURSES_NAME(UP),
304     CURSES_NAME(LEFT),
305     CURSES_NAME(RIGHT),
306     CURSES_NAME(HOME),
307     CURSES_NAME(BACKSPACE),
308     CURSES_NAME(F0),
309     CURSES_NAME(DL),
310     CURSES_NAME(IL),
311     CURSES_NAME(DC),
312     CURSES_NAME(IC),
313     CURSES_NAME(EIC),
314     CURSES_NAME(CLEAR),
315     CURSES_NAME(EOS),
316     CURSES_NAME(EOL),
317     CURSES_NAME(SF),
318     CURSES_NAME(SR),
319     CURSES_NAME(NPAGE),
320     CURSES_NAME(PPAGE),
321     CURSES_NAME(STAB),
322     CURSES_NAME(CTAB),
323     CURSES_NAME(CATAB),
324     CURSES_NAME(ENTER),
325     CURSES_NAME(PRINT),
326     CURSES_NAME(LL),
327     CURSES_NAME(A1),
328     CURSES_NAME(A3),
329     CURSES_NAME(B2),
330     CURSES_NAME(C1),
331     CURSES_NAME(C3),
332     CURSES_NAME(BTAB),
333     CURSES_NAME(BEG),
334     CURSES_NAME(CANCEL),
335     CURSES_NAME(CLOSE),
336     CURSES_NAME(COMMAND),
337     CURSES_NAME(COPY),
338     CURSES_NAME(CREATE),
339     CURSES_NAME(END),
340     CURSES_NAME(EXIT),
341     CURSES_NAME(FIND),
342     CURSES_NAME(HELP),
343     CURSES_NAME(MARK),
344     CURSES_NAME(MESSAGE),
345     CURSES_NAME(MOVE),
346     CURSES_NAME(NEXT),
347     CURSES_NAME(OPEN),
348     CURSES_NAME(OPTIONS),
349     CURSES_NAME(PREVIOUS),
350     CURSES_NAME(REDO),
351     CURSES_NAME(REFERENCE),
352     CURSES_NAME(REFRESH),
353     CURSES_NAME(REPLACE),
354     CURSES_NAME(RESTART),
355     CURSES_NAME(RESUME),
356     CURSES_NAME(SAVE),
357     CURSES_NAME(SBEG),
358     CURSES_NAME(SCANCEL),
359     CURSES_NAME(SCOMMAND),
360     CURSES_NAME(SCOPY),
361     CURSES_NAME(SCREATE),
362     CURSES_NAME(SDC),
363     CURSES_NAME(SDL),
364     CURSES_NAME(SELECT),
365     CURSES_NAME(SEND),
366     CURSES_NAME(SEOL),
367     CURSES_NAME(SEXIT),
368     CURSES_NAME(SFIND),
369     CURSES_NAME(SHELP),
370     CURSES_NAME(SHOME),
371     CURSES_NAME(SIC),
372     CURSES_NAME(SLEFT),
373     CURSES_NAME(SMESSAGE),
374     CURSES_NAME(SMOVE),
375     CURSES_NAME(SNEXT),
376     CURSES_NAME(SOPTIONS),
377     CURSES_NAME(SPREVIOUS),
378     CURSES_NAME(SPRINT),
379     CURSES_NAME(SREDO),
380     CURSES_NAME(SREPLACE),
381     CURSES_NAME(SRIGHT),
382     CURSES_NAME(SRSUME),
383     CURSES_NAME(SSAVE),
384     CURSES_NAME(SSUSPEND),
385     CURSES_NAME(SUNDO),
386     CURSES_NAME(SUSPEND),
387     CURSES_NAME(UNDO),
388 };
389 
390 #define DIALOG_NAME(upper) { #upper, DLGK_ ## upper }
391 #define COUNT_DIALOG  sizeof(dialog_names)/sizeof(dialog_names[0])
392 static const CODENAME dialog_names[] =
393 {
394     DIALOG_NAME(OK),
395     DIALOG_NAME(CANCEL),
396     DIALOG_NAME(EXTRA),
397     DIALOG_NAME(HELP),
398     DIALOG_NAME(ESC),
399     DIALOG_NAME(PAGE_FIRST),
400     DIALOG_NAME(PAGE_LAST),
401     DIALOG_NAME(PAGE_NEXT),
402     DIALOG_NAME(PAGE_PREV),
403     DIALOG_NAME(ITEM_FIRST),
404     DIALOG_NAME(ITEM_LAST),
405     DIALOG_NAME(ITEM_NEXT),
406     DIALOG_NAME(ITEM_PREV),
407     DIALOG_NAME(FIELD_FIRST),
408     DIALOG_NAME(FIELD_LAST),
409     DIALOG_NAME(FIELD_NEXT),
410     DIALOG_NAME(FIELD_PREV),
411     DIALOG_NAME(GRID_UP),
412     DIALOG_NAME(GRID_DOWN),
413     DIALOG_NAME(GRID_LEFT),
414     DIALOG_NAME(GRID_RIGHT),
415     DIALOG_NAME(DELETE_LEFT),
416     DIALOG_NAME(DELETE_RIGHT),
417     DIALOG_NAME(DELETE_ALL),
418     DIALOG_NAME(ENTER),
419     DIALOG_NAME(BEGIN),
420     DIALOG_NAME(FINAL),
421     DIALOG_NAME(SELECT)
422 };
423 
424 static char *
425 skip_white(char *s)
426 {
427     while (*s != '\0' && isspace(UCH(*s)))
428 	++s;
429     return s;
430 }
431 
432 static char *
433 skip_black(char *s)
434 {
435     while (*s != '\0' && !isspace(UCH(*s)))
436 	++s;
437     return s;
438 }
439 
440 /*
441  * Find a user-defined binding, given the curses key code.
442  */
443 static DLG_KEYS_BINDING *
444 find_binding(char *widget, int curses_key)
445 {
446     LIST_BINDINGS *p;
447     DLG_KEYS_BINDING *result = 0;
448 
449     for (p = all_bindings; p != 0; p = p->link) {
450 	if (p->win == 0
451 	    && !dlg_strcmp(p->name, widget)
452 	    && p->binding->curses_key == curses_key) {
453 	    result = p->binding;
454 	    break;
455 	}
456     }
457     return result;
458 }
459 
460 /*
461  * Built-in bindings have a nonzero "win" member, and the associated binding
462  * table can have more than one entry.  We keep those last, since lookups will
463  * find the user-defined bindings first and use those.
464  *
465  * Sort "*" (all-widgets) entries past named widgets, since those are less
466  * specific.
467  */
468 static int
469 compare_bindings(LIST_BINDINGS * a, LIST_BINDINGS * b)
470 {
471     int result = 0;
472     if (a->win == b->win) {
473 	if (!strcmp(a->name, b->name)) {
474 	    result = a->binding[0].curses_key - b->binding[0].curses_key;
475 	} else if (!strcmp(b->name, "*")) {
476 	    result = -1;
477 	} else if (!strcmp(a->name, "*")) {
478 	    result = 1;
479 	} else {
480 	    result = dlg_strcmp(a->name, b->name);
481 	}
482     } else if (b->win) {
483 	result = -1;
484     } else {
485 	result = 1;
486     }
487     return result;
488 }
489 
490 /*
491  * Find a user-defined binding, given the curses key code.  If it does not
492  * exist, create a new one, inserting it into the linked list, keeping it
493  * sorted to simplify lookups for user-defined bindings that can override
494  * the built-in bindings.
495  */
496 static DLG_KEYS_BINDING *
497 make_binding(char *widget, int curses_key, int is_function, int dialog_key)
498 {
499     LIST_BINDINGS *entry = 0;
500     DLG_KEYS_BINDING *data = 0;
501     char *name;
502     LIST_BINDINGS *p, *q;
503     DLG_KEYS_BINDING *result = find_binding(widget, curses_key);
504 
505     if (result == 0
506 	&& (entry = dlg_calloc(LIST_BINDINGS, 1)) != 0
507 	&& (data = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0
508 	&& (name = dlg_strclone(widget)) != 0) {
509 
510 	entry->name = name;
511 	entry->binding = data;
512 
513 	data[0].is_function_key = is_function;
514 	data[0].curses_key = curses_key;
515 	data[0].dialog_key = dialog_key;
516 
517 	data[1] = end_keys_binding;
518 
519 	for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) {
520 	    if (compare_bindings(entry, p) < 0) {
521 		break;
522 	    }
523 	}
524 	if (q != 0) {
525 	    q->link = entry;
526 	} else {
527 	    all_bindings = entry;
528 	}
529 	if (p != 0) {
530 	    entry->link = p;
531 	}
532 	result = data;
533     } else if (entry != 0) {
534 	free(entry);
535 	if (data)
536 	    free(data);
537     }
538 
539     return result;
540 }
541 
542 /*
543  * Parse the parameters of the "bindkeys" configuration-file entry.  This
544  * expects widget name which may be "*", followed by curses key definition and
545  * then dialog key definition.
546  *
547  * The curses key "should" be one of the names (ignoring case) from
548  * curses_names[], but may also be a single control character (prefix "^" or
549  * "~" depending on whether it is C0 or C1), or an escaped single character.
550  * Binding a printable character with dialog is possible but not useful.
551  *
552  * The dialog key must be one of the names from dialog_names[].
553  */
554 int
555 dlg_parse_bindkey(char *params)
556 {
557     char *p = skip_white(params);
558     char *q;
559     bool escaped = FALSE;
560     int modified = 0;
561     int result = FALSE;
562     unsigned xx;
563     char *widget;
564     int is_function = FALSE;
565     int curses_key;
566     int dialog_key;
567 
568     curses_key = -1;
569     dialog_key = -1;
570     widget = p;
571 
572     p = skip_black(p);
573     if (p != widget && *p != '\0') {
574 	*p++ = '\0';
575 	q = p;
576 	while (*p != '\0' && curses_key < 0) {
577 	    if (escaped) {
578 		escaped = FALSE;
579 		curses_key = *p;
580 	    } else if (*p == '\\') {
581 		escaped = TRUE;
582 	    } else if (modified) {
583 		if (*p == '?') {
584 		    curses_key = ((modified == '^')
585 				  ? 127
586 				  : 255);
587 		} else {
588 		    curses_key = ((modified == '^')
589 				  ? (*p & 0x1f)
590 				  : ((*p & 0x1f) | 0x80));
591 		}
592 	    } else if (*p == '^') {
593 		modified = *p;
594 	    } else if (*p == '~') {
595 		modified = *p;
596 	    } else if (isspace(UCH(*p))) {
597 		break;
598 	    }
599 	    ++p;
600 	}
601 	if (!isspace(UCH(*p))) {
602 	    ;
603 	} else {
604 	    *p++ = '\0';
605 	    if (curses_key < 0) {
606 		char fprefix[2];
607 		char check[2];
608 		int keynumber;
609 		if (sscanf(q, "%[Ff]%d%c", fprefix, &keynumber, check) == 2) {
610 		    curses_key = KEY_F(keynumber);
611 		    is_function = TRUE;
612 		} else {
613 		    for (xx = 0; xx < COUNT_CURSES; ++xx) {
614 			if (!dlg_strcmp(curses_names[xx].name, q)) {
615 			    curses_key = curses_names[xx].code;
616 			    is_function = TRUE;
617 			    break;
618 			}
619 		    }
620 		}
621 	    }
622 	}
623 	q = skip_white(p);
624 	p = skip_black(q);
625 	if (p != q) {
626 	    for (xx = 0; xx < COUNT_DIALOG; ++xx) {
627 		if (!dlg_strcmp(dialog_names[xx].name, q)) {
628 		    dialog_key = dialog_names[xx].code;
629 		    break;
630 		}
631 	    }
632 	}
633 	if (*widget != '\0'
634 	    && curses_key >= 0
635 	    && dialog_key >= 0
636 	    && make_binding(widget, curses_key, is_function, dialog_key) != 0) {
637 	    result = TRUE;
638 	}
639     }
640     return result;
641 }
642 
643 static void
644 dump_curses_key(FILE *fp, int curses_key)
645 {
646     if (curses_key > KEY_MIN) {
647 	unsigned n;
648 	bool found = FALSE;
649 	for (n = 0; n < COUNT_CURSES; ++n) {
650 	    if (curses_names[n].code == curses_key) {
651 		fprintf(fp, "%s", curses_names[n].name);
652 		found = TRUE;
653 		break;
654 	    }
655 	}
656 	if (!found) {
657 	    if (curses_key >= KEY_F(0)) {
658 		fprintf(fp, "F%d", curses_key - KEY_F(0));
659 	    } else {
660 		fprintf(fp, "curses%d", curses_key);
661 	    }
662 	}
663     } else if (curses_key >= 0 && curses_key < 32) {
664 	fprintf(fp, "^%c", curses_key + 64);
665     } else if (curses_key == 127) {
666 	fprintf(fp, "^?");
667     } else if (curses_key >= 128 && curses_key < 160) {
668 	fprintf(fp, "~%c", curses_key - 64);
669     } else if (curses_key == 255) {
670 	fprintf(fp, "~?");
671     } else {
672 	fprintf(fp, "\\%c", curses_key);
673     }
674 }
675 
676 static void
677 dump_dialog_key(FILE *fp, int dialog_key)
678 {
679     unsigned n;
680     bool found = FALSE;
681     for (n = 0; n < COUNT_DIALOG; ++n) {
682 	if (dialog_names[n].code == dialog_key) {
683 	    fputs(dialog_names[n].name, fp);
684 	    found = TRUE;
685 	    break;
686 	}
687     }
688     if (!found) {
689 	fprintf(fp, "dialog%d", dialog_key);
690     }
691 }
692 
693 static void
694 dump_one_binding(FILE *fp, const char *widget, DLG_KEYS_BINDING * binding)
695 {
696     fprintf(fp, "bindkey %s ", widget);
697     dump_curses_key(fp, binding->curses_key);
698     fputc(' ', fp);
699     dump_dialog_key(fp, binding->dialog_key);
700     fputc('\n', fp);
701 }
702 
703 void
704 dlg_dump_keys(FILE *fp)
705 {
706     LIST_BINDINGS *p;
707     const char *last = "";
708     unsigned n;
709     unsigned count = 0;
710 
711     for (p = all_bindings; p != 0; p = p->link) {
712 	if (p->win == 0) {
713 	    ++count;
714 	}
715     }
716     if (count != 0) {
717 	for (p = all_bindings, n = 0; p != 0; p = p->link) {
718 	    if (p->win == 0) {
719 		if (dlg_strcmp(last, p->name)) {
720 		    fprintf(fp, "\n# key bindings for %s widgets\n",
721 			    !strcmp(p->name, "*") ? "all" : p->name);
722 		    last = p->name;
723 		}
724 		dump_one_binding(fp, p->name, p->binding);
725 	    }
726 	}
727     }
728 }
729 #endif /* HAVE_RC_FILE */
730