X-Git-Url: http://ncurses.scripts.mit.edu/?p=ncurses.git;a=blobdiff_plain;f=test%2Fdemo_terminfo.c;h=c1fffc7048f057277dd0295c8f5fa63dfffc1f8e;hp=8b64869f1c2aae4e35e3540a5a7ae36abc475f3e;hb=e2e9c09c48b19b24979cafb2d4864f538b5ddd1c;hpb=471bc007361fd4bc8d2fae060c7d5b09828ed541 diff --git a/test/demo_terminfo.c b/test/demo_terminfo.c index 8b64869f..c1fffc70 100644 --- a/test/demo_terminfo.c +++ b/test/demo_terminfo.c @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright (c) 2009-2010,2012 Free Software Foundation, Inc. * + * Copyright (c) 2009-2015,2016 Free Software Foundation, Inc. * * * * Permission is hereby granted, free of charge, to any person obtaining a * * copy of this software and associated documentation files (the * @@ -29,48 +29,75 @@ /* * Author: Thomas E. Dickey * - * $Id: demo_terminfo.c,v 1.13 2012/07/07 23:37:13 tom Exp $ + * $Id: demo_terminfo.c,v 1.44 2016/09/10 21:22:49 tom Exp $ * * A simple demo of the terminfo interface. */ #define USE_TINFO #include +#include -#ifdef NCURSES_VERSION -#if !(defined(HAVE_TERM_ENTRY_H) && HAVE_TERM_ENTRY_H) -#undef NCURSES_XNAMES -#define NCURSES_XNAMES 0 -#endif #if NCURSES_XNAMES +#if HAVE_TERM_ENTRY_H #include +#else +#undef NCURSES_XNAMES +#define NCURSES_XNAMES 0 #endif #endif +static void failed(const char *) GCC_NORETURN; + +static void +failed(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + ExitProgram(EXIT_FAILURE); +} + #if HAVE_TIGETSTR + #if defined(HAVE_CURSES_DATA_BOOLNAMES) || defined(DECL_CURSES_DATA_BOOLNAMES) +#define USE_CODE_LISTS 1 +#else +#define USE_CODE_LISTS 0 +#endif +static bool a_opt = FALSE; static bool b_opt = FALSE; static bool f_opt = FALSE; static bool n_opt = FALSE; static bool q_opt = FALSE; static bool s_opt = FALSE; static bool x_opt = FALSE; +static bool y_opt = FALSE; static char *d_opt; static char *e_opt; static char **db_list; static int db_item; +static char *my_blob; +static char **my_boolcodes; +static char **my_numcodes; +static char **my_numvalues; +static char **my_strcodes; +static char **my_strvalues; + static long total_values; +static long total_b_values; +static long total_n_values; +static long total_s_values; #define FCOLS 8 -#define FNAME(type) "%s %-*s = ", #type, FCOLS +#define FNAME(type) "%s %-*s = ", #type, f_opt ? 24 : FCOLS static char * make_dbitem(char *p, char *q) { - char *result = malloc(strlen(e_opt) + 2 + (size_t) (p - q)); - sprintf(result, "%s=%.*s", e_opt, (int) (p - q), q); + size_t need = strlen(e_opt) + 2 + (size_t) (p - q); + char *result = malloc(need); + _nc_SPRINTF(result, _nc_SLIMIT(need) "%s=%.*s", e_opt, (int) (p - q), q); return result; } @@ -125,6 +152,7 @@ next_dbitem(void) return result; } +#ifdef NO_LEAKS static void free_dblist(void) { @@ -136,27 +164,19 @@ free_dblist(void) db_list = 0; } } +#endif + static void -dumpit(NCURSES_CONST char *cap) +dumpit(NCURSES_CONST char *cap, const char *show) { - /* - * One of the limitations of the termcap interface is that the library - * cannot determine the size of the buffer passed via tgetstr(), nor the - * amount of space remaining. This demo simply reuses the whole buffer - * for each call; a normal termcap application would try to use the buffer - * to hold all of the strings extracted from the terminal entry. - */ const char *str; int num; if ((str = tigetstr(cap)) != 0 && (str != (char *) -1)) { total_values++; + total_s_values++; if (!q_opt) { - /* - * Note that the strings returned are mostly terminfo format, since - * ncurses does not convert except for a handful of special cases. - */ - printf(FNAME(str), cap); + printf(FNAME(str), show ? show : cap); while (*str != 0) { int ch = UChar(*str++); switch (ch) { @@ -207,14 +227,16 @@ dumpit(NCURSES_CONST char *cap) } } else if ((num = tigetnum(cap)) >= 0) { total_values++; + total_n_values++; if (!q_opt) { - printf(FNAME(num), cap); + printf(FNAME(num), show ? show : cap); printf(" %d\n", num); } } else if ((num = tigetflag(cap)) >= 0) { total_values++; + total_b_values++; if (!q_opt) { - printf(FNAME(flg), cap); + printf(FNAME(flg), show ? show : cap); printf("%s\n", num ? "true" : "false"); } } @@ -223,6 +245,80 @@ dumpit(NCURSES_CONST char *cap) fflush(stdout); } +#define isCapName(c) (isalnum(UChar(c)) || ((c) == '_')) +#define LegalItem(c,n) (n) + +static void +brute_force(const char *name) +{ +#define MAX_FORCE 5 /* omit "colors", since CPU-time is a problem */ + static const char legal[] = "\ +0123456789\ +ABCDEFGHIJKLMNOPQRSTUVWXYZ\ +abcdefghijklmnopqrstuvwxyz_"; + int length; + int j, k; + bool carry; + bool changed; + char cap[MAX_FORCE + 1]; + int item[MAX_FORCE + 1]; + + if (db_list) { + putenv(next_dbitem()); + } + if (!q_opt) + printf("Terminal type \"%s\"\n", name); + setupterm((NCURSES_CONST char *) name, 1, (int *) 0); + if (!q_opt) { + if (strcmp(name, ttytype)) + printf("... actual \"%s\"\n", ttytype); + } + + for (length = 1; length <= MAX_FORCE; ++length) { + /* set all digits to zeros */ + for (j = 0; j < length; ++j) { + item[j] = LegalItem(j, 0); + } + + do { + changed = FALSE; + /* copy digits to cap-name */ + for (j = 0; j < length; ++j) { + cap[j] = legal[item[j]]; + } + cap[length] = '\0'; + dumpit(cap, NULL); + + k = length - 1; + do { + carry = FALSE; + for (; k >= 0; --k) { + item[k] += 1; + if (legal[item[k]]) { + changed = TRUE; + break; + } + if (k > 0 && + legal[item[k - 1] + 1]) { + for (j = k; j < length; ++j) { + item[j] = LegalItem(j, 0); + } + carry = TRUE; + changed = TRUE; + } + } + } while (carry); + } while (changed); + } + del_curterm(cur_term); +} + +#if USE_CODE_LISTS +#define fullname(type,n) f_opt ? type##fnames[n] : cap +#else +#define fullname(type,n) cap +#endif + static void demo_terminfo(char *name) { @@ -232,39 +328,40 @@ demo_terminfo(char *name) if (db_list) { putenv(next_dbitem()); } - printf("Terminal type \"%s\"\n", name); + if (!q_opt) + printf("Terminal type \"%s\"\n", name); setupterm(name, 1, (int *) 0); if (b_opt) { for (n = 0;; ++n) { - cap = f_opt ? boolfnames[n] : boolnames[n]; + cap = my_boolcodes[n]; if (cap == 0) break; - dumpit(cap); + dumpit(cap, fullname(bool, n)); } } if (n_opt) { for (n = 0;; ++n) { - cap = f_opt ? numfnames[n] : numnames[n]; + cap = my_numcodes[n]; if (cap == 0) break; - dumpit(cap); + dumpit(cap, fullname(num, n)); } } if (s_opt) { for (n = 0;; ++n) { - cap = f_opt ? strfnames[n] : strnames[n]; + cap = my_strcodes[n]; if (cap == 0) break; - dumpit(cap); + dumpit(cap, fullname(str, n)); } } #ifdef NCURSES_VERSION - if (x_opt) { + if (x_opt && (my_blob == 0)) { int mod; - if (f_opt) { + if (y_opt) { #if NCURSES_XNAMES TERMTYPE *term = &(cur_term->type); if (term != 0 @@ -272,18 +369,18 @@ demo_terminfo(char *name) || (NUM_NUMBERS(term) != NUMCOUNT) || (NUM_STRINGS(term) != STRCOUNT))) { for (n = BOOLCOUNT; n < NUM_BOOLEANS(term); ++n) { - dumpit(ExtBoolname(term, (int) n, boolnames)); + dumpit(ExtBoolname(term, (int) n, boolnames), NULL); } for (n = NUMCOUNT; n < NUM_NUMBERS(term); ++n) { - dumpit(ExtNumname(term, (int) n, numnames)); + dumpit(ExtNumname(term, (int) n, numnames), NULL); } for (n = STRCOUNT; n < NUM_STRINGS(term); ++n) { - dumpit(ExtStrname(term, (int) n, strnames)); + dumpit(ExtStrname(term, (int) n, strnames), NULL); } } #endif } else { - char temp[10]; + char temp[80]; static const char *xterm_keys[] = { "kDC", "kDN", "kEND", "kHOM", "kIC", @@ -291,18 +388,370 @@ demo_terminfo(char *name) }; for (n = 0; n < SIZEOF(xterm_keys); ++n) { for (mod = 0; mod < 8; ++mod) { - if (mod == 0) - strcpy(temp, xterm_keys[n]); - else - sprintf(temp, "%s%d", xterm_keys[n], mod); - dumpit(temp); + if (mod == 0) { + /* these happen to be standard - avoid duplicates */ + if (!strcmp(xterm_keys[n], "kDC") || + !strcmp(xterm_keys[n], "kEND") || + !strcmp(xterm_keys[n], "kHOM") || + !strcmp(xterm_keys[n], "kLFT") || + !strcmp(xterm_keys[n], "kRIT")) { + continue; + } + _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp)) + "%.*s", 8, xterm_keys[n]); + } else { + _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp)) + "%.*s%d", 8, xterm_keys[n], mod); + } + dumpit(temp, NULL); } } } } #endif + del_curterm(cur_term); +} + +typedef enum { + pDefault = 0 + ,pComment + ,pDescription + ,pEscaped + ,pNewline + ,pName + ,pNumber + ,pString +} STATE; + +static void +parse_description(const char *input_name) +{ + static char empty[1]; + + FILE *fp; + struct stat sb; + size_t count_bools = 0; + size_t count_nums = 0; + size_t count_strs = 0; + size_t len; + size_t j, k, jl; + STATE state; + + if (stat(input_name, &sb) != 0 + || (sb.st_mode & S_IFMT) != S_IFREG) { + failed("input is not a file"); + } + + if (sb.st_size == 0) { + failed("input is empty"); + } + + /* + * None of the arrays could be larger than the input-file, and since it + * is small, just allocate the maximum for simplicity. + */ + if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0 || + (my_boolcodes = typeCalloc(char *, sb.st_size)) == 0 || + (my_numcodes = typeCalloc(char *, sb.st_size)) == 0 || + (my_numvalues = typeCalloc(char *, sb.st_size)) == 0 || + (my_strcodes = typeCalloc(char *, sb.st_size)) == 0 || + (my_strvalues = typeCalloc(char *, sb.st_size)) == 0) { + failed("cannot allocate memory for input-file"); + } + + if ((fp = fopen(input_name, "r")) == 0) + failed("cannot open input-file"); + len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp); + my_blob[sb.st_size] = '\0'; + fclose(fp); + /* + * First, get rid of comments and escaped newlines, as well as repeated + * colons to construct a canonical entry. + */ + state = pNewline; + for (j = k = 0; j < len; ++j) { + int ch = my_blob[j]; + if (ch == '\t') { + ch = ' '; + } + switch (state) { + case pNewline: + if (ch == ' ') { + continue; + } + if (ch == '#') { + state = pComment; + continue; + } + state = pDefault; + /* FALLTHRU */ + case pDefault: + switch (ch) { + case '|': + state = pDescription; + continue; + case '\\': + state = pEscaped; + continue; + case '\n': + state = pNewline; + continue; + case ' ': + break; + case ',': + my_blob[k++] = (char) ch; + break; + default: + if (isalpha(UChar(ch))) + state = pName; + else + fprintf(stderr, "OOPS @%d:%.20s\n", __LINE__, my_blob + j); + my_blob[k++] = (char) ch; + break; + } + break; + case pComment: + if (ch == '\n') + state = pNewline; + break; + case pDescription: + switch (ch) { + case ',': + state = pDefault; + break; + case '\n': + state = pNewline; + break; + } + break; + case pEscaped: + if (ch != '\n') { + my_blob[k++] = (char) ch; + state = pDefault; + } else { + state = pNewline; + } + break; + case pName: + switch (ch) { + case '\n': + state = pNewline; + continue; + case ' ': + case ',': + state = pDefault; + break; + case '#': + state = pNumber; + break; + case '=': + state = pString; + break; + case '|': + state = pDescription; + continue; + } + my_blob[k++] = (char) ch; + break; + case pNumber: + switch (ch) { + case '\n': + state = pNewline; + continue; + case ',': + state = pDefault; + break; + case ' ': + state = pDefault; + continue; + } + my_blob[k++] = (char) ch; + break; + case pString: + switch (ch) { + case '\n': + state = pNewline; + break; + case ',': + state = pDefault; + my_blob[k++] = (char) ch; + break; + default: + my_blob[k++] = (char) ch; + break; + } + break; + default: + /* not used */ + break; + } + } + my_blob[k] = '\0'; + + /* + * Then, parse what's left, making indexes of the names and values. + */ + state = pDefault; + for (j = 0; my_blob[j] != '\0'; ++j) { + switch (state) { + case pDefault: + switch (my_blob[j]) { + case '\\': + state = pEscaped; + break; + case ',': + my_blob[j] = '\0'; + if (my_blob[j + 1] != '\0' && my_blob[j + 1] != ',') + state = pName; + break; + case ' ': + break; + default: + break; + } + case pEscaped: + break; + case pName: + state = pDefault; + if (isalpha(UChar(my_blob[j]))) { + for (jl = 1; isalnum(UChar(my_blob[j + jl])); ++jl) { + ; + } + } else { + jl = 0; + } + if (jl != 0) { + switch (my_blob[j + jl]) { + case '#': + my_numvalues[count_nums] = &my_blob[j + jl + 1]; + my_numcodes[count_nums++] = &my_blob[j]; + my_blob[j + jl] = '\0'; + state = pNumber; + j += jl; + break; + case '=': + my_strvalues[count_strs] = &my_blob[j + jl + 1]; + my_strcodes[count_strs++] = &my_blob[j]; + my_blob[j + jl] = '\0'; + state = pString; + j += jl; + break; + default: + if (my_blob[j + jl] == '@') { + /* + * We cannot get the type for a cancelled item + * directly, but can infer it assuming the input + * came from infocmp, which puts the data in a + * known order. + */ + if (count_strs) { + my_strvalues[count_strs] = empty; + my_strcodes[count_strs++] = &my_blob[j]; + } else if (count_nums) { + my_numvalues[count_nums] = empty; + my_numcodes[count_nums++] = &my_blob[j]; + } else { + my_boolcodes[count_bools++] = &my_blob[j]; + } + my_blob[j + jl] = '\0'; + j += jl + 1; + } else { + my_boolcodes[count_bools++] = &my_blob[j]; + my_blob[j + jl] = '\0'; + j += jl; + } + state = (isCapName(my_blob[j + 1]) + ? pName + : pDefault); + break; + } + } + break; + case pNumber: + if (!isdigit(UChar(my_blob[j]))) { + --j; + state = pDefault; + } + break; + case pString: + switch (my_blob[j]) { + case '\\': + if (my_blob[j + 1] != '\0') { + ++j; + } else { + --j; + state = pDefault; + } + break; + case ',': + --j; + state = pDefault; + break; + } + break; + case pNewline: + case pComment: + case pDescription: + default: + break; + } + } + my_boolcodes[count_bools] = 0; + my_numcodes[count_nums] = 0; + my_numvalues[count_nums] = 0; + my_strcodes[count_strs] = 0; + my_strvalues[count_strs] = 0; + +#if 0 + printf("# bools:%d\n", (int) count_bools); + for (j = 0; my_boolcodes[j]; ++j) + printf("\t%s,\n", my_boolcodes[j]); + + printf("# numbers:%d\n", (int) count_nums); + for (j = 0; my_numcodes[j]; ++j) + printf("\t%s#%s,\n", my_numcodes[j], my_numvalues[j]); + + printf("# strings:%d\n", (int) count_strs); + for (j = 0; my_strcodes[j]; ++j) + printf("\t%s=%s,\n", my_strcodes[j], my_strvalues[j]); +#endif +} + +#if USE_CODE_LISTS +static char ** +copy_code_list(NCURSES_CONST char *const *list) +{ + int pass; + size_t count; + size_t length = 1; + char **result = 0; + char *blob = 0; + char *unused = 0; + + for (pass = 0; pass < 2; ++pass) { + for (count = 0; list[count] != 0; ++count) { + size_t chunk = strlen(list[count]) + 1; + if (pass == 0) { + length += chunk; + } else { + result[count] = unused; + _nc_STRCPY(unused, list[count], length); + unused += chunk; + } + } + if (pass == 0) { + blob = malloc(length); + result = typeCalloc(char *, count + 1); + unused = blob; + if (blob == 0 || result == 0) + failed("copy_code_list failed"); + } + } + + return result; } +#endif static void usage(void) @@ -315,16 +764,19 @@ usage(void) "capabilities for the given terminal, using short names.", "", "Options:", + " -a try all names, print capabilities found", " -b print boolean-capabilities", " -d LIST colon-separated list of databases to use", " -e NAME environment variable to set with -d option", " -f print full names", + " -i NAME terminal description to use as names for \"-a\" option", " -n print numeric-capabilities", " -q quiet (prints only counts)", " -r COUNT repeat for given count", " -s print string-capabilities", #ifdef NCURSES_VERSION " -x print extended capabilities", + " -y direct-lookup names of extended capabilities", #endif }; unsigned n; @@ -341,9 +793,13 @@ main(int argc, char *argv[]) int repeat; char *name; int r_opt = 1; + char *input_name = 0; - while ((n = getopt(argc, argv, "bd:e:fnqr:sx")) != -1) { + while ((n = getopt(argc, argv, "abd:e:fi:nqr:sxy")) != -1) { switch (n) { + case 'a': + a_opt = TRUE; + break; case 'b': b_opt = TRUE; break; @@ -356,6 +812,9 @@ main(int argc, char *argv[]) case 'f': f_opt = TRUE; break; + case 'i': + input_name = optarg; + break; case 'n': n_opt = TRUE; break; @@ -372,7 +831,10 @@ main(int argc, char *argv[]) #ifdef NCURSES_VERSION case 'x': x_opt = TRUE; - use_extended_names(TRUE); + break; + case 'y': + y_opt = TRUE; + x_opt = TRUE; break; #endif default: @@ -381,7 +843,11 @@ main(int argc, char *argv[]) } } - if (!(b_opt || n_opt || s_opt || x_opt)) { +#if HAVE_USE_EXTENDED_NAMES + use_extended_names(x_opt); +#endif + + if (!(b_opt || n_opt || s_opt)) { b_opt = TRUE; n_opt = TRUE; s_opt = TRUE; @@ -389,39 +855,75 @@ main(int argc, char *argv[]) make_dblist(); - for (repeat = 0; repeat < r_opt; ++repeat) { - if (optind < argc) { - for (n = optind; n < argc; ++n) { - demo_terminfo(argv[n]); + if (a_opt) { + for (repeat = 0; repeat < r_opt; ++repeat) { + if (optind < argc) { + for (n = optind; n < argc; ++n) { + brute_force(argv[n]); + } + } else if ((name = getenv("TERM")) != 0) { + brute_force(name); + } else { + static char dumb[] = "dumb"; + brute_force(dumb); + } + } + } else { + if (input_name != 0) { + parse_description(input_name); + } +#if USE_CODE_LISTS + else { + my_boolcodes = copy_code_list(boolnames); + my_numcodes = copy_code_list(numnames); + my_strcodes = copy_code_list(strnames); + } +#else + else { + failed("no capability-lists available (use -i option)"); + } +#endif /* USE_CODE_LISTS */ + for (repeat = 0; repeat < r_opt; ++repeat) { + if (optind < argc) { + for (n = optind; n < argc; ++n) { + demo_terminfo(argv[n]); + } + } else if ((name = getenv("TERM")) != 0) { + demo_terminfo(name); + } else { + static char dumb[] = "dumb"; + demo_terminfo(dumb); } - } else if ((name = getenv("TERM")) != 0) { - demo_terminfo(name); - } else { - static char dumb[] = "dumb"; - demo_terminfo(dumb); } } - printf("%ld values\n", total_values); +#define PLURAL(n) n, (n != 1) ? "s" : "" + printf("%ld value%s (%ld boolean%s, %ld number%s, %ld string%s)\n", + PLURAL(total_values), + PLURAL(total_b_values), + PLURAL(total_n_values), + PLURAL(total_s_values)); +#ifdef NO_LEAKS free_dblist(); + if (my_blob != 0) { + free(my_blob); + free(my_boolcodes); + free(my_numcodes); + free(my_numvalues); + free(my_strcodes); + free(my_strvalues); + } +#endif ExitProgram(EXIT_SUCCESS); } -#else -int -main(int argc GCC_UNUSED, char *argv[]GCC_UNUSED) -{ - printf("This program requires the terminfo arrays\n"); - ExitProgram(EXIT_FAILURE); -} -#endif #else /* !HAVE_TIGETSTR */ int main(int argc GCC_UNUSED, char *argv[]GCC_UNUSED) { - printf("This program requires the terminfo functions such as tigetstr\n"); + failed("This program requires the terminfo functions such as tigetstr"); ExitProgram(EXIT_FAILURE); } #endif /* HAVE_TIGETSTR */