From - Mon Sep 16 06:39:29 2002 Return-Path: Received: from tanana.ukpeagvik.net ([209.124.156.162]) by daimi.au.dk (8.11.6/8.11.6) with ESMTP id g8G3RTo14576 for ; Mon, 16 Sep 2002 05:27:31 +0200 Received: (from floyd@localhost) by tanana.ukpeagvik.net (8.11.4/8.11.4) id g8G3RHq19499; Sun, 15 Sep 2002 19:27:17 -0800 Sender: floyd@barrow.com To: Kasper Dupont Subject: Re: moving the cursor References: <874rd4i1cd.fld@barrow.com> <3D78811E.F67B2861@daimi.au.dk> Organization: __________ From: Floyd Davidson Date: 15 Sep 2002 19:27:17 -0800 In-Reply-To: Kasper Dupont's message of "Fri, 06 Sep 2002 12:19:10 +0200" Message-ID: <877khmobqi.fld@barrow.com> Lines: 1141 User-Agent: gnus 5.8.3/XEmacs 21.1.9/Linux MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Virus-Scanned: by amavisd-milter (http://amavis.org/) X-UIDL: 2bT"!l_##!8hD"!HO2!! X-Mozilla-Status: 8011 X-Mozilla-Status2: 00000000 Kasper Dupont wrote: >Floyd Davidson wrote: >> >> Below is a demo program which demonstrates part of how this is >> done. It lacks a number of refinements that really should be >> included in a real program, but adding them would make it a bit >> too large to post. (If you ask, I'll be happy to email a much >> larger demo that provides an extensive example.) For example, >> this demo does not verify that any of the TERMINFO capabilities >> are actually defined in the database before attempting to use >> them. If a capability is not defined, the results are >> necessarily strange! It also does not demonstrate exactly the >> terminal attributes you are looking for... > >If you have a larger example I would be happy to place it in my FAQ: >http://www.daimi.au.dk/~kasperd/comp.os.linux.development.faq.html > >meanwhile I'll take a look on the small example and place it my FAQ. >(Seems it is also about time I place some anchors for each question.) Here is the "larger example". Larger in that it is far *too* large! It shows how to manipulate just about all of the useful terminal attributes and is not meant so much as a tutorial as it is a something I can go grab a chunk of code out of when I need some part of this functionality. It's big enough that if all of this were used in a program it would be just as well (or maybe much better) to just use ncurses functions. It is fairly interesting to run this in an xterm with TERM defined as "vt100", "xterm", and as "nxterm". The "xterm" terminfo entry does not have color and some other attributes defined. Using vt100 doesn't get color, but at least enables inverse video and underlined video. Using nxterm (or some of the other xterm definitions) allows everything to work. Feel free to use this as you wish. If you'd like to hack it up to meet _your_ style, have at it! Have a good day, Floyd /* * A demonstration program to illustrate using the TERMINFO * database without invoking the entire curses package for * window management. * * The following specific functionalities are demonstrated: * * A) terminal initialization * 1) accessing the TERMINFO database * a) determining terminal characteristics * b) determining terminal capabilities * 2) closing a terminal * a) normal program termination * b) abnormal program termination * B) sending TERMINFO defined escape sequences * 1) string output function definition * 2) sending non-parameterized sequences * 3) sending parameterized sequences * C) using TERMINFO specified capabilities * 1) terminal colors * 2) terminal attributes * 3) cursor addressing * 4) save and restore cursor position * 5) cursor visibility * 6) clear screen * 7 clear line * D) non-TERMINFO useful functions * 1) getch() for single char input * 2) kbhit() test for keyboard input availability * * This module must be linked with the ncurses library to form an * executable binary: * * cc foo.c -lncurses -o foo * */ #include #include #include #include #include #include #include #include #include #include int getch(void); int kbhit(void); extern char acs_map[]; /* * VT100 line graphics symbols * * (These are functional with the Linux console and with xterm) */ #define ACS_ULCORNER (acs_map['l']) /* upper left corner */ #define ACS_LLCORNER (acs_map['m']) /* lower left corner */ #define ACS_URCORNER (acs_map['k']) /* upper right corner */ #define ACS_LRCORNER (acs_map['j']) /* lower right corner */ #define ACS_LTEE (acs_map['t']) /* tee pointing right */ #define ACS_RTEE (acs_map['u']) /* tee pointing left */ #define ACS_BTEE (acs_map['v']) /* tee pointing up */ #define ACS_TTEE (acs_map['w']) /* tee pointing down */ #define ACS_HLINE (acs_map['q']) /* horizontal line */ #define ACS_VLINE (acs_map['x']) /* vertical line */ #define ACS_PLUS (acs_map['n']) /* large plus or crossover */ #define ACS_DIAMOND (acs_map['`']) /* diamond */ #define ACS_CKBOARD (acs_map['a']) /* checker board (stipple) */ #define ACS_DEGREE (acs_map['f']) /* degree symbol */ #define ACS_PLMINUS (acs_map['g']) /* plus/minus */ #define ACS_BULLET (acs_map['~']) /* bullet */ /* produces a dotted square on an Xterm, scan line 9 on console */ #define ACS_S9 (acs_map['s']) /* scan line 9 */ #if 0 /* dotted square on an Xterm, small solid block on console */ #define ACS_S1 (acs_map['o']) /* scan line 1 */ #endif /* End of VT-100 symbols */ /* * Teletype 5410v1 symbols * * (OK on the Linux console, and xterm depending on the font.) * */ #define ACS_LARROW (acs_map[',']) /* arrow pointing left */ #define ACS_RARROW (acs_map['+']) /* arrow pointing right */ #define ACS_DARROW (acs_map['.']) /* arrow pointing down */ #define ACS_UARROW (acs_map['-']) /* arrow pointing up */ #define ACS_BLOCK (acs_map['0']) /* solid square block */ #if 0 /* Xterm produces a "nl", console produces dotted block */ #define ACS_BOARD (acs_map['h']) /* board of squares */ /* Xterm procudes a "vt", console produces a small solid block */ #define ACS_LANTERN (acs_map['i']) /* lantern symbol */ #endif /* End of Teletype 5410v1 symbols */ /* * SysV symbols * * (OK on the Linux console, and xterm depending on the font.) * */ #define ACS_LEQUAL (acs_map['y']) /* less/equal */ #define ACS_GEQUAL (acs_map['z']) /* greater/equal */ #define ACS_PI (acs_map['{']) /* Pi */ #define ACS_STERLING (acs_map['}']) /* UK pound sign */ /* small solid block on linux console */ #define ACS_NEQUAL (acs_map['|']) /* not equal */ #if 0 /* produce a dotted square on an Xterm */ #define ACS_S3 (acs_map['p']) /* scan line 3 */ #define ACS_S7 (acs_map['r']) /* scan line 7 */ #endif /* End of SysV symbols */ /* * Set NC_ATTR to 1 for terminfo defined color attributes * * Set NC_ATTR to 0 for direct use of ANSI X3.64 control sequences * * (Using ANSI X3.64 control sequences may cause strange results * on terminals which do not recognize a given sequence.) */ #define NC_ATTR 1 /* screen attribute routines */ void cursor_move(int, int); /* cursor positioning */ void clrscrn(void); /* clear the screen */ void ceol(void); /* clear to end of line */ void ceos(void); /* clear to end of screen */ void inverse(int); /* inverse video on/off */ void sc(void); /* save current cursor position */ void rc(void); /* restore saved cursor position */ void invisible(void); /* make cursor invisible */ void visible(void); /* make cursor visible */ void foreground(int, int); /* set foreground color */ void background(int, int); /* set background color */ void resetcolor(void); /* set default colors */ void smacs(void); /* enable alternate char set */ void rmacs(void); /* disable alternate char set */ /* screen attributes, use as first arg to foreground() and background */ #define RESET 0 #define BOLD 1 #define BRIGHT 1 #define DIM 2 #define STANDOUT 3 #define UNDERLINE 4 #define BLINK 5 #define INVISIBLE 6 #define INVERSE 7 #define PROTECT 8 #define ALTCHARSET 9 #define NOCHANGE 10 char *attrs[] = { "reset", "bold", "dim", "standout", "underline", "blink", "invisible", "inverse", "no change" }; /* screen colors */ #define BLACK 0 #define RED 1 #define GREEN 2 #define YELLOW 3 #define BLUE 4 #define PURPLE 5 #define CYAN 6 #define WHITE 7 char *colors[] = { "black", "red", "green", "yellow", "blue", "purple", "cyan", "white" }; /* output routines for internal use */ void tisend(char *); /* terminal setup and exit routines */ void terminal_init(void); void terminal_close(void); int rows, cols, color_flag; char *altcharset, *normcharset; /* * fun functions to do things to the screen */ void print_title(void); /* print a screen title */ void print_data(void); /* print data to screen */ void boxscreen(void); /* * drawing primatives */ void box(char *, int, int); void drawbox(int, int, int, int); void vline(char, int, int, int); void hline(char, int, int, int); /* * signal handler function */ void handler(int); void catcher(int); char *progname; char title[] = "Screen Attributes Demonstration Program"; char marks[] = " *** "; int alarmflag; int exitflag; int inflag; int main(int argc, char *argv[]) { int count = argc; int ch = 0; int i; progname = argv[0]; /* set up terminfo, and flag if no terminal type is known */ terminal_init(); #ifndef NSIG #define NSIG SIGUSR2 #endif exitflag = 0; /* reset the terminal on abnormal program termination */ for(i = 1; i < NSIG; ++i) { signal(i, handler); } alarmflag = 0; signal(SIGALRM, catcher); for(count = 0; count < 100; ++count) { if (exitflag) { break; } if (count % 2) { cursor_move(rows - 2, 15); alarmflag = 0; alarm(1); /* fetch characters for 1 second */ inflag = 0; while (1) { if (alarmflag) { break; } ch = -1; if (kbhit()) { ch = getch(); } if (ch == -1) { continue; } inflag = 1; fprintf(stderr,"0x%2.2x ", ch); if (ch == 3) { break; } } if (inflag) { fprintf(stderr," "); } if (ch == 3) { break; } continue; } if (count % 5) { print_data(); } else { invisible(); clrscrn(); print_data(); cursor_move(3, (cols/2) - (strlen(title)/2) - (strlen(marks))); print_title(); cursor_move(rows - 3, 15); printf("Type ^C to exit... Or any other key to see the keystroke sequence."); fflush(stdout); boxscreen(); print_data(); } fflush(stdout); } terminal_close(); #if NC_ATTR if (0 == color_flag) { char *term = getenv("TERM"); printf("\nNOTE: The TERMINFO database does _NOT_"); printf(" define color for %s.\n\n", term); } #endif return EXIT_SUCCESS; } /* * clear the terminal screen and home the cursor */ void clrscrn(void) { tputs(clear_screen, rows, putchar); } /* * clear to end of line */ void ceol(void) { tputs(clr_eol, 1, putchar); } /* * clear to end of screen */ void ceos(void) { tputs(clr_eos, 1, putchar); } /* * enable alternate charset */ void smacs(void) { tputs(enter_alt_charset_mode, 1, putchar); } /* * disable alternate charset */ void rmacs(void) { tputs(exit_alt_charset_mode, 1, putchar); } /* * cursor positioning, * * home is 1,1 */ void cursor_move(int row, int col) { /* reject out of hand anything negative */ if (--row < 0 || --col < 0) { return; } /* fold out of range values back into the screen */ row = row % rows; col = col % cols; /* move the cursor */ tputs(tparm(cursor_address, row, col), 1, putchar); } /* * restore the cursor location */ void rc(void) { tputs(restore_cursor, 1, putchar); } /* * save the cursor location */ void sc(void) { tputs(save_cursor, 1, putchar); } /* * turn STANDOUT mode on or off * * (bright and inverse modes both on) */ void inverse(int state) { if (state) { tisend(enter_standout_mode); } else { tisend(exit_standout_mode); } } /* * make cursor invisible */ void invisible(void) { #if NC_ATTR tputs(cursor_invisible, 1, putchar); #else printf("%c[?25l", 0x1b); #endif } /* * make cursor visible */ void visible(void) { #if NC_ATTR tisend(cursor_normal); #else printf("%c[?25h", 0x1b); #endif } /* * set the foreground color and attributes * * Note: that setting the attribute with anything * other than NOCHANGE will clear the previously set * attribute, *and* will also reset the background * color to the default value. Therefore, this function * and the background() function must always be used as * a pair if an attribute is set and other than the * default background color is desired. */ void foreground(int attr, int color) { int sgr[9]; if (attr < 0 || attr > NOCHANGE) { return; } if (color < 0 || color > 7) { return; } memset(sgr, 0, sizeof sgr); switch (attr) { case ALTCHARSET: sgr[8] = 1; break; case PROTECT: sgr[7] = 1; break; case INVISIBLE: sgr[6] = 1; break; case BOLD: sgr[5] = 1; break; case DIM: sgr[4] = 1; break; case BLINK: sgr[3] = 1; break; case INVERSE: sgr[2] = 1; break; case UNDERLINE: sgr[1] = 1; break; case STANDOUT: sgr[0] = 1; break; case RESET: case NOCHANGE: break; default: attr = RESET; break; } #if NC_ATTR if (attr != NOCHANGE) { tisend(tparm(set_attributes, sgr[0], sgr[1], sgr[2], sgr[3], sgr[4], sgr[5], sgr[6], sgr[7], sgr[8])); } tisend(tparm(set_a_foreground, color)); #else if (attr == STANDOUT) { attr = INVERSE; printf("%c[%dm", 0x1b, BOLD); } if (attr != NOCHANGE) { printf("%c[%dm", 0x1b, attr); } if (color_flag) { printf("%c[%dm", 0x1b, color+30); } #endif } /* * set the background color and attributes * * * Note: that setting the attribute with anything * other than NOCHANGE will clear the previously set * attribute, *and* will also reset the foreground * color to the default value. Therefore, this function * and the foreground() function must always be used as * a pair if an attribute is set and other than the * default foreground color is desired. */ void background(int attr, int color) { int sgr[9]; if (!color_flag) { return; } if (attr < 0 || attr > NOCHANGE) { return; } if (color < 0 || color > 7) { return; } memset(sgr, 0, sizeof sgr); switch (attr) { case ALTCHARSET: sgr[8] = 1; break; case PROTECT: sgr[7] = 1; break; case INVISIBLE: sgr[6] = 1; break; case BOLD: sgr[5] = 1; break; case DIM: sgr[4] = 1; break; case BLINK: sgr[3] = 1; break; case INVERSE: sgr[2] = 1; break; case UNDERLINE: sgr[1] = 1; break; case STANDOUT: sgr[0] = 1; break; case RESET: case NOCHANGE: break; default: attr = RESET; break; } #if NC_ATTR if (attr != NOCHANGE) { tisend(tparm(set_attributes, sgr[0], sgr[1], sgr[2], sgr[3], sgr[4], sgr[5], sgr[6], sgr[7], sgr[8])); } tisend(tparm(set_a_background, color)); #else if (attr == STANDOUT) { attr = INVERSE; printf("%c[%dm", 0x1b, BOLD); } if (attr != NOCHANGE) { printf("%c[%dm", 0x1b, attr); } if (color_flag) { printf("%c[%dm", 0x1b, color+40); } #endif } /* * reset color to default */ void resetcolor(void) { #if NC_ATTR /* if terminfo has sgr0 defined correctly, this isn't needed, * but it may be required if the alternate character set is * not switched out by sgr0. */ tisend(tparm(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0)); tisend(exit_attribute_mode); #else printf("%c[0;10m%c", 0x1b, 0x0f); #endif } /* * set up the terminal */ void terminal_init(void) { char *term = getenv("TERM"); char *s; int err_ret; if ( !term || ! *term) { fprintf(stderr,"%s: The TERM environment variable is not set.\n", progname); exit(4); } /* bsd would use err_ret = setterm(term); */ setupterm(term, 1, &err_ret); switch (err_ret) { case -1: fprintf(stderr,"%s: Can't access terminfo database - ", progname); fprintf(stderr,"check TERMINFO environment variable\n"); exit(3); break; case 0: fprintf(stderr, "%s: Can't find entry for terminal %s\n", progname, term); exit(2); break; case 1: break; default: fprintf(stderr, "%s: Unknown tgetent return code %d\n", progname, err_ret); exit(40); } s = getenv("COLUMNS"); if ( !s || ! *s) { cols = tigetnum("cols"); } else { cols = atoi(s); if (!cols) { cols = tigetnum("cols"); } } s = getenv("LINES"); if ( !s || ! *s) { rows = tigetnum("lines"); } else { rows = atoi(s); if (!rows) { rows = tigetnum("lines"); } } /* screen size is too small - arbitrary choice of size is used */ if (cols < 30 || rows < 12) { fprintf(stderr, "Screen size [%dx%d] is too small.\n", cols, rows); exit(5); } if (0 > (color_flag = tigetnum("colors"))) { color_flag = 0; } /* terminal init strings */ tisend(init_1string); tisend(init_2string); tisend(init_3string); tisend(keypad_xmit); tisend(enter_ca_mode); altcharset = tigetstr("smacs"); normcharset = tigetstr("rmacs"); /* find strings to enable/disable alternate character sets */ if (altcharset == NULL || altcharset == (char *) -1 || normcharset == NULL || normcharset == (char *) -1) { altcharset = ""; normcharset = ""; } else { char *acsc = tigetstr("acsc"); if (acsc != NULL && acsc != (char *)-1) { while (acsc && acsc[0] && acsc[1]) { acs_map[(int)acsc[0]] = acsc[1]; acsc += 2; } } } } void catcher(int sig) { sig = 0; alarmflag = 1; signal(SIGALRM, catcher); return; } /* * cleanup and exit */ void handler(int sig) { sig = 0; exitflag = 1; } /* * close the terminal */ void terminal_close(void) { resetcolor(); inverse(0); cursor_move(rows-5, 1); visible(); ceos(); tisend(keypad_local); #if 0 /* * If enabled, this will restore the screen as it existed * prior to this program being run. */ tisend(exit_ca_mode); #endif #if 0 /* zap the screen */ clrscrn(); #endif return; } /* * send a terminfo paramter string */ void tisend(char *parm) { if ( (char *) -1 != parm ) { tputs(parm, 1, putchar); } } void boxscreen(void) { unsigned int bsize; char border[10]; border[0] = '<'; border[1] = '>'; border[2] = 0; border[3] = 0; bsize = strlen(border); foreground(BRIGHT, GREEN); background(NOCHANGE, CYAN); box(border, cols, rows); foreground(BRIGHT, RED); background(NOCHANGE, WHITE); drawbox(2, 4, rows - 4, cols - 8); background(BRIGHT, WHITE); vline(' ', 2, 3 , rows-2); vline(' ', 2, cols - 2, rows-2); resetcolor(); } /* * Using line graphics, draw a box. * * The point of origin is x,y and the size is w,h */ void drawbox(int y, int x, int h, int w) { if (!ACS_HLINE || !ACS_VLINE) { ACS_HLINE = '-'; ACS_VLINE = '|'; ACS_ULCORNER = '+'; ACS_LLCORNER = '+'; ACS_URCORNER = '+'; ACS_LRCORNER = '+'; } else { smacs(); } hline(ACS_HLINE, y , x+1 , w); hline(ACS_HLINE, y+h+1, x+1 , w); vline(ACS_VLINE, y+1 , x , h); vline(ACS_VLINE, y+1 , x+w+1, h); cursor_move(y , x ); printf("%c", ACS_ULCORNER); cursor_move(y , x+w+1); printf("%c", ACS_URCORNER); cursor_move(y+h+1, x+w+1); printf("%c", ACS_LRCORNER); cursor_move(y+h+1, x ); printf("%c", ACS_LLCORNER); rmacs(); } void hline(char line, int startrow, int startcol, int length) { cursor_move(startrow, startcol); while (length) { printf("%c", line); if (length > 0) { ++startcol; --length; cursor_move(startrow, startcol); } if (length < 0) { --startcol; ++length; cursor_move(startrow, startcol); } } } void vline(char line, int startrow, int startcol, int length) { cursor_move(startrow, startcol); while (length) { printf("%c", line); if (length > 0) { ++startrow; --length; cursor_move(startrow, startcol); } if (length < 0) { --startrow; ++length; cursor_move(startrow, startcol); } } } void box(char *border, int horiz, int vert) { int i, bsize; bsize = strlen(border); /* top line */ for ( i = 1; i <= horiz; i += bsize) { cursor_move(1, i); printf("%s", border); } /* both sides */ for ( i = 1; i < vert-0; ++i) { cursor_move(i, 1); printf("%s", border); cursor_move(i, horiz - bsize + 1); printf("%s", border); } /* bottom line */ for ( i = 1; i <= horiz; i += bsize) { cursor_move(vert - 0, i); printf("%s", border); } } /* * print the screen title */ void print_title(void) { foreground(RESET, RED); background(NOCHANGE, BLACK); printf(marks); foreground(UNDERLINE, WHITE); background(NOCHANGE, BLACK); printf(title); /* * note that clearing the UNDERLINE attribute by use of * RESET, also clears everything else... */ foreground(RESET, RED); background(NOCHANGE, BLACK); printf(marks); resetcolor(); } /* * print strings to the screen with changing * colors and different attributes. */ void print_data(void) { int i, offset, maxcol, currow; static int color = RED; char buffer[128]; resetcolor(); for(i = 0; i < 7; ++i) { currow = (2 * i) + 6; if (currow > (rows - 3)) { break; } offset = (cols / 2) + (3 * i) - 35; if (offset < 5) { offset = 5; } maxcol = (cols & ~1) - offset - 5; if (maxcol < 30) { return; } cursor_move(currow, offset); if (color_flag) { sprintf(buffer, "%-10s printed on row %2.2d, column %2.2d", colors[color], currow, offset); buffer[maxcol] = 0; foreground(BRIGHT, color); background(NOCHANGE, BLACK); } else { sprintf(buffer, "%-10s printed on row %2.2d, column %2.2d", attrs[color], currow, offset); buffer[maxcol] = 0; foreground(color, 1); } printf("%s", buffer); resetcolor(); if (++color > WHITE) { color = RED; } } if (++color > WHITE) { color = RED; } } /* * getch.c -- getch(), a blocking single character input from stdin * kbhit(), a keyboard lookahead monitor */ /* * getch() -- a blocking single character input from stdin * * Returns a character, or -1 if an input error occurs. * */ #define CMIN 1 #ifdef CTIME #undef CTIME #endif #define CTIME 1 /* * get a single character from stdin. * * First stdout is flushed, stdin is then switched to * raw mode and input is waited for. When a single * character is received from stdin the tty is restored * to its original state and the character is returned. * If the read from stdin fails, a -1 is returned. * * A conditional allows compiling with or without echo. */ int getch(void) { char ch; int error; static struct termios Otty, Ntty; fflush(stdout); tcgetattr(0, &Otty); Ntty = Otty; Ntty.c_iflag = 0; /* input mode */ Ntty.c_oflag = 0; /* output mode */ /* disable echoing the char as it is typed */ Ntty.c_lflag = 0; /* line settings (no echo) */ Ntty.c_cc[VMIN] = CMIN; /* minimum characters to wait for */ Ntty.c_cc[VTIME] = CTIME;/* minimum time to wait */ /* * return a char from the current input buffer, or block if * no input is waiting. */ #define FLAG TCSANOW if ( 0 == (error = tcsetattr( 0, FLAG, &Ntty))) { error = read( 0, &ch, 1 ); /* get char from stdin */ error += tcsetattr(0, FLAG, &Otty); /* restore old settings */ } return ( error == 1 ? (int) ch : -1 ); } /* * kbhit() -- a keyboard lookahead monitor * * returns the number of characters available to read. * */ int kbhit(void) { int cnt = 0; int error; static struct termios Otty, Ntty; tcgetattr( 0, &Otty); Ntty = Otty; Ntty.c_iflag = 0; /* input mode */ Ntty.c_oflag = 0; /* output mode */ Ntty.c_lflag &= ~ICANON; /* raw mode */ Ntty.c_cc[VMIN] = CMIN; /* minimum characters to wait for */ Ntty.c_cc[VTIME] = CTIME; /* minimum time to wait */ if (0 == (error = tcsetattr(0, TCSANOW, &Ntty))) { struct timeval tv; error += ioctl(0, FIONREAD, &cnt); error += tcsetattr(0, TCSANOW, &Otty); tv.tv_sec = 0; tv.tv_usec = 10000; select(1, NULL, NULL, NULL, &tv); } return ( error == 0 ? cnt : -1 ); } -- Floyd L. Davidson Ukpeagvik (Barrow, Alaska) "Place where people hunt snowy owls"