blob: 65c2d13d7489494ed940f2d6f222e1fdbc3932e1 [file] [log] [blame]
Steve Kondikae271bc2015-11-15 02:50:53 +01001/****************************************************************************
2 * Copyright (c) 2005-2014,2015 Free Software Foundation, Inc. *
3 * *
4 * Permission is hereby granted, free of charge, to any person obtaining a *
5 * copy of this software and associated documentation files (the *
6 * "Software"), to deal in the Software without restriction, including *
7 * without limitation the rights to use, copy, modify, merge, publish, *
8 * distribute, distribute with modifications, sublicense, and/or sell *
9 * copies of the Software, and to permit persons to whom the Software is *
10 * furnished to do so, subject to the following conditions: *
11 * *
12 * The above copyright notice and this permission notice shall be included *
13 * in all copies or substantial portions of the Software. *
14 * *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
22 * *
23 * Except as contained in this notice, the name(s) of the above copyright *
24 * holders shall not be used in advertising or otherwise to promote the *
25 * sale, use or other dealings in this Software without prior written *
26 * authorization. *
27 ****************************************************************************/
28
29/*
30 * Author: Thomas E. Dickey
31 *
32 * $Id: demo_termcap.c,v 1.48 2015/08/08 20:25:39 tom Exp $
33 *
34 * A simple demo of the termcap interface.
35 */
36#define USE_TINFO
37#include <test.priv.h>
38#include <sys/stat.h>
39
40#if NCURSES_XNAMES
41#if HAVE_TERM_ENTRY_H
42#include <term_entry.h>
43#else
44#undef NCURSES_XNAMES
45#define NCURSES_XNAMES 0
46#endif
47#endif
48
49#ifdef NCURSES_VERSION
50#include <termcap.h>
51#endif
52
53static void
54failed(const char *msg)
55{
56 fprintf(stderr, "%s\n", msg);
57 ExitProgram(EXIT_FAILURE);
58}
59
60#if HAVE_TGETENT
61
62#if defined(HAVE_CURSES_DATA_BOOLNAMES) || defined(DECL_CURSES_DATA_BOOLNAMES)
63#define USE_CODE_LISTS 1
64#else
65#define USE_CODE_LISTS 0
66#endif
67
68#define FCOLS 8
69#define FNAME(type) "%s %-*s = ", #type, FCOLS
70
71static bool b_opt = FALSE;
72static bool n_opt = FALSE;
73static bool s_opt = FALSE;
74static bool q_opt = FALSE;
75static bool x_opt = FALSE;
76static bool y_opt = FALSE;
77
78static char *d_opt;
79static char *e_opt;
80static char **db_list;
81static int db_item;
82
83static char *my_blob;
84static char **my_boolcodes;
85static char **my_numcodes;
86static char **my_numvalues;
87static char **my_strcodes;
88static char **my_strvalues;
89
90static long total_values;
91static long total_b_values;
92static long total_n_values;
93static long total_s_values;
94
95#define isCapName(c) (isgraph(c) && strchr("^=:\\", c) == 0)
96#define EachCapName(n) n = 33; n < 127; ++n
97
98static char *
99make_dbitem(char *p, char *q)
100{
101 char *result = malloc(strlen(e_opt) + 2 + (size_t) (p - q));
102 sprintf(result, "%s=%.*s", e_opt, (int) (p - q), q);
103 return result;
104}
105
106static void
107make_dblist(void)
108{
109 if (d_opt && e_opt) {
110 int pass;
111
112 for (pass = 0; pass < 2; ++pass) {
113 char *p, *q;
114 size_t count = 0;
115
116 for (p = q = d_opt; *p != '\0'; ++p) {
117 if (*p == ':') {
118 if (p != q + 1) {
119 if (pass) {
120 db_list[count] = make_dbitem(p, q);
121 }
122 count++;
123 }
124 q = p + 1;
125 }
126 }
127 if (p != q + 1) {
128 if (pass) {
129 db_list[count] = make_dbitem(p, q);
130 }
131 count++;
132 }
133 if (!pass) {
134 db_list = typeCalloc(char *, count + 1);
135 }
136 }
137 }
138}
139
140static char *
141next_dbitem(void)
142{
143 char *result = 0;
144
145 if (db_list) {
146 if ((result = db_list[db_item]) == 0) {
147 db_item = 0;
148 result = db_list[0];
149 } else {
150 db_item++;
151 }
152 }
153 printf("** %s\n", result);
154 return result;
155}
156
157static void
158free_dblist(void)
159{
160 if (db_list) {
161 int n;
162 for (n = 0; db_list[n]; ++n)
163 free(db_list[n]);
164 free(db_list);
165 db_list = 0;
166 }
167}
168
169static void
170show_string(const char *name, const char *value)
171{
172 printf(FNAME(str), name);
173 if (value == ((char *) -1)) {
174 printf("CANCELLED");
175 } else if (value == ((char *) 0)) {
176 printf("ABSENT");
177 } else {
178 while (*value != 0) {
179 int ch = UChar(*value++);
180 switch (ch) {
181 case '\177':
182 fputs("^?", stdout);
183 break;
184 case '\033':
185 fputs("\\E", stdout);
186 break;
187 case '\b':
188 fputs("\\b", stdout);
189 break;
190 case '\f':
191 fputs("\\f", stdout);
192 break;
193 case '\n':
194 fputs("\\n", stdout);
195 break;
196 case '\r':
197 fputs("\\r", stdout);
198 break;
199 case ' ':
200 fputs("\\s", stdout);
201 break;
202 case '\t':
203 fputs("\\t", stdout);
204 break;
205 case '^':
206 fputs("\\^", stdout);
207 break;
208 case ':':
209 fputs("\\072", stdout);
210 break;
211 case '\\':
212 fputs("\\\\", stdout);
213 break;
214 default:
215 if (isgraph(ch))
216 fputc(ch, stdout);
217 else if (ch < 32)
218 printf("^%c", ch + '@');
219 else
220 printf("\\%03o", ch);
221 break;
222 }
223 }
224 }
225 printf("\n");
226}
227
228static void
229show_number(const char *name, int value)
230{
231 printf(FNAME(num), name);
232 printf(" %d\n", value);
233}
234
235static void
236dumpit(NCURSES_CONST char *cap)
237{
238 /*
239 * One of the limitations of the termcap interface is that the library
240 * cannot determine the size of the buffer passed via tgetstr(), nor the
241 * amount of space remaining. This demo simply reuses the whole buffer
242 * for each call; a normal termcap application would try to use the buffer
243 * to hold all of the strings extracted from the terminal entry.
244 */
245 char area[1024], *ap = area;
246 char *str;
247 int num;
248
249 if ((str = tgetstr(cap, &ap)) != 0) {
250 total_values++;
251 total_s_values++;
252 if (!q_opt) {
253 /*
254 * Note that the strings returned are mostly terminfo format, since
255 * ncurses does not convert except for a handful of special cases.
256 */
257 show_string(cap, str);
258 }
259 } else if ((num = tgetnum(cap)) >= 0) {
260 total_values++;
261 total_n_values++;
262 if (!q_opt) {
263 show_number(cap, num);
264 }
265 } else if (tgetflag(cap) > 0) {
266 total_values++;
267 total_b_values++;
268 if (!q_opt) {
269 printf(FNAME(flg), cap);
270 printf("%s\n", "true");
271 }
272 }
273
274 if (!q_opt)
275 fflush(stdout);
276}
277
278static void
279brute_force(const char *name)
280{
281 char buffer[1024];
282
283 if (db_list) {
284 putenv(next_dbitem());
285 }
286 if (!q_opt)
287 printf("Terminal type \"%s\"\n", name);
288 if (tgetent(buffer, name) >= 0) {
289 char cap[3];
290 int c1, c2;
291
292 cap[2] = 0;
293 for (EachCapName(c1)) {
294 cap[0] = (char) c1;
295 if (isCapName(c1)) {
296 for (EachCapName(c2)) {
297 cap[1] = (char) c2;
298 if (isCapName(c2)) {
299 dumpit(cap);
300 }
301 }
302 }
303 }
304 }
305}
306
307#if NCURSES_XNAMES
308static void
309dump_xname(NCURSES_CONST char *cap)
310{
311 if (strlen(cap) == 2)
312 dumpit(cap);
313}
314#endif
315
316static void
317demo_termcap(NCURSES_CONST char *name)
318{
319 unsigned n;
320 NCURSES_CONST char *cap;
321 char buffer[1024];
322
323 if (db_list) {
324 putenv(next_dbitem());
325 }
326 if (!q_opt)
327 printf("Terminal type \"%s\"\n", name);
328 if (tgetent(buffer, name) >= 0) {
329
330 if (b_opt) {
331 for (n = 0;; ++n) {
332 cap = my_boolcodes[n];
333 if (cap == 0)
334 break;
335 dumpit(cap);
336 }
337 }
338
339 if (n_opt) {
340 for (n = 0;; ++n) {
341 cap = my_numcodes[n];
342 if (cap == 0)
343 break;
344 dumpit(cap);
345 }
346 }
347
348 if (s_opt) {
349 for (n = 0;; ++n) {
350 cap = my_strcodes[n];
351 if (cap == 0)
352 break;
353 dumpit(cap);
354 }
355 }
356#ifdef NCURSES_VERSION
357 if (x_opt && (my_blob == 0) && y_opt) {
358#if NCURSES_XNAMES
359 TERMTYPE *term = &(cur_term->type);
360 if (term != 0
361 && ((NUM_BOOLEANS(term) != BOOLCOUNT)
362 || (NUM_NUMBERS(term) != NUMCOUNT)
363 || (NUM_STRINGS(term) != STRCOUNT))) {
364 for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) {
365 dump_xname(ExtBoolname(term, (int) n, boolnames));
366 }
367 for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) {
368 dump_xname(ExtNumname(term, (int) n, numnames));
369 }
370 for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) {
371 dump_xname(ExtStrname(term, (int) n, strnames));
372 }
373 }
374#endif
375 }
376#endif
377 }
378}
379
380typedef enum {
381 pDefault = 0
382 ,pComment
383 ,pDescription
384 ,pEscaped
385 ,pNewline
386 ,pName
387 ,pNumber
388 ,pString
389} STATE;
390
391static void
392parse_description(const char *input_name)
393{
394 static char empty[1];
395
396 FILE *fp;
397 struct stat sb;
398 size_t count_bools = 0;
399 size_t count_nums = 0;
400 size_t count_strs = 0;
401 size_t len;
402 size_t j, k;
403 STATE state;
404
405 if (stat(input_name, &sb) != 0
406 || (sb.st_mode & S_IFMT) != S_IFREG) {
407 failed("input is not a file");
408 }
409
410 if (sb.st_size == 0) {
411 failed("input is empty");
412 }
413
414 /*
415 * None of the arrays could be larger than the input-file, and since it
416 * is small, just allocate the maximum for simplicity.
417 */
418 if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0 ||
419 (my_boolcodes = typeCalloc(char *, sb.st_size)) == 0 ||
420 (my_numcodes = typeCalloc(char *, sb.st_size)) == 0 ||
421 (my_numvalues = typeCalloc(char *, sb.st_size)) == 0 ||
422 (my_strcodes = typeCalloc(char *, sb.st_size)) == 0 ||
423 (my_strvalues = typeCalloc(char *, sb.st_size)) == 0) {
424 failed("cannot allocate memory for input-file");
425 }
426
427 if ((fp = fopen(input_name, "r")) == 0)
428 failed("cannot open input-file");
429 len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
430 my_blob[sb.st_size] = '\0';
431 fclose(fp);
432
433 /*
434 * First, get rid of comments and escaped newlines, as well as repeated
435 * colons to construct a canonical entry.
436 *
437 * FIXME: actually this should make an additional pass just to strip
438 * comment-lines and escaped newlines. But it is workable for infocmp
439 * output.
440 */
441 state = pNewline;
442 for (j = k = 0; j < len; ++j) {
443 int ch = my_blob[j];
444 if (ch == '\t') {
445 ch = ' ';
446 }
447 switch (state) {
448 case pNewline:
449 if (ch == ' ') {
450 continue;
451 }
452 if (ch == '#') {
453 state = pComment;
454 continue;
455 }
456 state = pDefault;
457 /* FALLTHRU */
458 case pDefault:
459 switch (ch) {
460 case '|':
461 state = pDescription;
462 continue;
463 case '\\':
464 state = pEscaped;
465 continue;
466 case '\n':
467 state = pNewline;
468 continue;
469 case ' ':
470 case ':':
471 break;
472 default:
473 state = pName;
474 break;
475 }
476 my_blob[k++] = (char) ch;
477 break;
478 case pComment:
479 if (ch == '\n')
480 state = pNewline;
481 break;
482 case pDescription:
483 switch (ch) {
484 case ':':
485 state = pDefault;
486 break;
487 case '\n':
488 state = pNewline;
489 break;
490 }
491 break;
492 case pEscaped:
493 if (ch != '\n') {
494 my_blob[k++] = (char) ch;
495 state = pDefault;
496 } else {
497 state = pNewline;
498 }
499 break;
500 case pName:
501 switch (ch) {
502 case '\n':
503 state = pNewline;
504 continue;
505 case ' ':
506 case ':':
507 state = pDefault;
508 break;
509 case '#':
510 state = pNumber;
511 break;
512 case '|':
513 state = pDescription;
514 continue;
515 }
516 my_blob[k++] = (char) ch;
517 break;
518 case pNumber:
519 switch (ch) {
520 case '\n':
521 state = pNewline;
522 continue;
523 case ':':
524 state = pDefault;
525 break;
526 case ' ':
527 state = pDefault;
528 continue;
529 }
530 my_blob[k++] = (char) ch;
531 break;
532 case pString:
533 switch (ch) {
534 case '\\':
535 if (my_blob[j + 1] == '\0') {
536 state = pDefault;
537 continue;
538 }
539 break;
540 case '\n':
541 state = pNewline;
542 continue;
543 case ':':
544 state = pDefault;
545 break;
546 }
547 my_blob[k++] = (char) ch;
548 break;
549 default:
550 /* not used */
551 break;
552 }
553 }
554 my_blob[k] = '\0';
555
556 /*
557 * Then, parse what's left, making indexes of the names and values.
558 */
559 state = pDefault;
560 for (j = 0; my_blob[j] != '\0'; ++j) {
561 switch (state) {
562 case pDefault:
563 switch (my_blob[j]) {
564 case '\\':
565 state = pEscaped;
566 break;
567 case ':':
568 my_blob[j] = '\0';
569 if (my_blob[j + 1] != '\0' && my_blob[j + 1] != ':')
570 state = pName;
571 break;
572 case ' ':
573 break;
574 default:
575 break;
576 }
577 case pEscaped:
578 break;
579 case pName:
580 state = pDefault;
581 /*
582 * Commented-out capabilities might be accessible (they are in
583 * ncurses).
584 */
585 if (my_blob[j] == '.' && my_blob[j + 1] == '.') {
586 j += 2;
587 }
588 if (my_blob[j + 1] != '\0') {
589 switch (my_blob[j + 2]) {
590 case '#':
591 my_numvalues[count_nums] = &my_blob[j + 3];
592 my_numcodes[count_nums++] = &my_blob[j];
593 my_blob[j + 2] = '\0';
594 state = pNumber;
595 j += 2;
596 break;
597 case '=':
598 my_strvalues[count_strs] = &my_blob[j + 3];
599 my_strcodes[count_strs++] = &my_blob[j];
600 my_blob[j + 2] = '\0';
601 state = pString;
602 j += 2;
603 break;
604 default:
605 if (my_blob[j + 2] == '@') {
606 /*
607 * We cannot get the type for a cancelled item
608 * directly, but can infer it assuming the input
609 * came from infocmp, which puts the data in a
610 * known order.
611 */
612 if (count_strs) {
613 my_strvalues[count_strs] = empty;
614 my_strcodes[count_strs++] = &my_blob[j];
615 } else if (count_nums) {
616 my_numvalues[count_nums] = empty;
617 my_numcodes[count_nums++] = &my_blob[j];
618 } else {
619 my_boolcodes[count_bools++] = &my_blob[j];
620 }
621 } else {
622 my_boolcodes[count_bools++] = &my_blob[j];
623 }
624 j++;
625 break;
626 }
627 }
628 break;
629 case pNumber:
630 if (!isdigit(UChar(my_blob[j]))) {
631 --j;
632 state = pDefault;
633 }
634 break;
635 case pString:
636 switch (my_blob[j]) {
637 case '\\':
638 if (my_blob[j + 1] == '\0') {
639 state = pDefault;
640 continue;
641 } else {
642 ++j;
643 }
644 break;
645 case '\n':
646 state = pNewline;
647 continue;
648 case ':':
649 --j;
650 state = pDefault;
651 break;
652 }
653 break;
654 case pNewline:
655 case pComment:
656 case pDescription:
657 default:
658 break;
659 }
660 }
661 my_boolcodes[count_bools] = 0;
662 my_numcodes[count_nums] = 0;
663 my_numvalues[count_nums] = 0;
664 my_strcodes[count_strs] = 0;
665 my_strvalues[count_strs] = 0;
666
667#if 0
668 printf("bools:%d\n", (int) count_bools);
669 for (j = 0; my_boolcodes[j]; ++j)
670 printf("%5d:%s\n", (int) j, my_boolcodes[j]);
671
672 printf("numbers:%d\n", (int) count_nums);
673 for (j = 0; my_numcodes[j]; ++j)
674 printf("%5d:%s(%s)\n", (int) j, my_numcodes[j], my_numvalues[j]);
675
676 printf("strings:%d\n", (int) count_strs);
677 for (j = 0; my_strcodes[j]; ++j)
678 printf("%5d:%s(%s)\n", (int) j, my_strcodes[j], my_strvalues[j]);
679#endif
680}
681
682#if USE_CODE_LISTS
683static char **
684copy_code_list(NCURSES_CONST char *const *list)
685{
686 int pass;
687 size_t count;
688 size_t length = 1;
689 char **result = 0;
690 char *blob = 0;
691 char *unused = 0;
692
693 for (pass = 0; pass < 2; ++pass) {
694 for (count = 0; list[count] != 0; ++count) {
695 size_t chunk = strlen(list[count]) + 1;
696 if (pass == 0) {
697 length += chunk;
698 } else {
699 result[count] = unused;
700 strcpy(unused, list[count]);
701 unused += chunk;
702 }
703 }
704 if (pass == 0) {
705 blob = malloc(length);
706 result = typeCalloc(char *, count + 1);
707 unused = blob;
708 if (blob == 0 || result == 0)
709 failed("copy_code_list failed");
710 }
711 }
712
713 return result;
714}
715#endif
716
717static void
718usage(void)
719{
720 static const char *msg[] =
721 {
722 "Usage: demo_termcap [options] [terminal]",
723 "",
724 "If no options are given, print all (boolean, numeric, string)",
725 "capabilities for the given terminal, using short names.",
726 "",
727 "Options:",
728 " -a try all names, print capabilities found",
729 " -b print boolean-capabilities",
730 " -d LIST colon-separated list of databases to use",
731 " -e NAME environment variable to set with -d option",
732 " -i NAME terminal description to use as names for \"-a\" option, etc.",
733 " -n print numeric-capabilities",
734 " -q quiet (prints only counts)",
735 " -r COUNT repeat for given count",
736 " -s print string-capabilities",
737 " -v print termcap-variables",
738#ifdef NCURSES_VERSION
739 " -x print extended capabilities",
740#endif
741 };
742 unsigned n;
743 for (n = 0; n < SIZEOF(msg); ++n) {
744 fprintf(stderr, "%s\n", msg[n]);
745 }
746 ExitProgram(EXIT_FAILURE);
747}
748
749int
750main(int argc, char *argv[])
751{
752 int n;
753 char *name;
754 bool a_opt = FALSE;
755 bool v_opt = FALSE;
756 char *input_name = 0;
757
758 int repeat;
759 int r_opt = 1;
760
761 while ((n = getopt(argc, argv, "abd:e:i:nqr:svxy")) != -1) {
762 switch (n) {
763 case 'a':
764 a_opt = TRUE;
765 break;
766 case 'b':
767 b_opt = TRUE;
768 break;
769 case 'd':
770 d_opt = optarg;
771 break;
772 case 'e':
773 e_opt = optarg;
774 break;
775 case 'i':
776 input_name = optarg;
777 break;
778 case 'n':
779 n_opt = TRUE;
780 break;
781 case 'q':
782 q_opt = TRUE;
783 break;
784 case 'r':
785 if ((r_opt = atoi(optarg)) <= 0)
786 usage();
787 break;
788 case 's':
789 s_opt = TRUE;
790 break;
791 case 'v':
792 v_opt = TRUE;
793 break;
794#if NCURSES_XNAMES
795 case 'x':
796 x_opt = TRUE;
797 break;
798 case 'y':
799 y_opt = TRUE;
800 x_opt = TRUE;
801 break;
802#endif
803 default:
804 usage();
805 break;
806 }
807 }
808
809#if HAVE_USE_EXTENDED_NAMES
810 use_extended_names(x_opt);
811#endif
812
813 if (!(b_opt || n_opt || s_opt)) {
814 b_opt = TRUE;
815 n_opt = TRUE;
816 s_opt = TRUE;
817 }
818
819 make_dblist();
820
821 if (a_opt) {
822 for (repeat = 0; repeat < r_opt; ++repeat) {
823 if (optind < argc) {
824 for (n = optind; n < argc; ++n) {
825 brute_force(argv[n]);
826 }
827 } else if ((name = getenv("TERM")) != 0) {
828 brute_force(name);
829 } else {
830 static char dumb[] = "dumb";
831 brute_force(dumb);
832 }
833 }
834 } else {
835 if (input_name != 0) {
836 parse_description(input_name);
837 }
838#if USE_CODE_LISTS
839 else {
840 my_boolcodes = copy_code_list(boolcodes);
841 my_numcodes = copy_code_list(numcodes);
842 my_strcodes = copy_code_list(strcodes);
843 }
844#else
845 else {
846 failed("no capability-lists available (use -i option)");
847 }
848#endif /* USE_CODE_LISTS */
849 for (repeat = 0; repeat < r_opt; ++repeat) {
850 if (optind < argc) {
851 for (n = optind; n < argc; ++n) {
852 demo_termcap(argv[n]);
853 }
854 } else if ((name = getenv("TERM")) != 0) {
855 demo_termcap(name);
856 } else {
857 static char dumb[] = "dumb";
858 demo_termcap(dumb);
859 }
860 }
861 }
862
863 printf("%ld values (%ld booleans, %ld numbers, %ld strings)\n",
864 total_values, total_b_values, total_n_values, total_s_values);
865
866#if defined(NCURSES_VERSION) || defined(HAVE_CURSES_DATA_OSPEED)
867 if (v_opt) {
868 show_number("PC", PC);
869 show_string("UP", UP);
870 show_string("BC", BC);
871 show_number("ospeed", ospeed);
872 }
873#endif
874
875 free_dblist();
876
877 ExitProgram(EXIT_SUCCESS);
878}
879
880#else
881int
882main(int argc GCC_UNUSED,
883 char *argv[]GCC_UNUSED)
884{
885 failed("This program requires termcap");
886}
887#endif