1/**************************************************************************** 2 * Copyright (c) 1998-2007,2008 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 * view.c -- a silly little viewer program 30 * 31 * written by Eric S. Raymond <esr@snark.thyrsus.com> December 1994 32 * to test the scrolling code in ncurses. 33 * 34 * modified by Thomas Dickey <dickey@clark.net> July 1995 to demonstrate 35 * the use of 'resizeterm()', and May 2000 to illustrate wide-character 36 * handling. 37 * 38 * Takes a filename argument. It's a simple file-viewer with various 39 * scroll-up and scroll-down commands. 40 * 41 * n -- scroll one line forward 42 * p -- scroll one line back 43 * 44 * Either command accepts a numeric prefix interpreted as a repeat count. 45 * Thus, typing `5n' should scroll forward 5 lines in the file. 46 * 47 * The way you can tell this is working OK is that, in the trace file, 48 * there should be one scroll operation plus a small number of line 49 * updates, as opposed to a whole-page update. This means the physical 50 * scroll operation worked, and the refresh() code only had to do a 51 * partial repaint. 52 * 53 * $Id: view.c,v 1.69 2008/09/06 22:10:50 tom Exp $ 54 */ 55 56#include <test.priv.h> 57 58#include <time.h> 59 60#undef CTRL /* conflict on AIX 5.2 with <sys/ioctl.h> */ 61 62#if HAVE_TERMIOS_H 63# include <termios.h> 64#else 65# include <sgtty.h> 66#endif 67 68#if !defined(sun) || !HAVE_TERMIOS_H 69# if HAVE_SYS_IOCTL_H 70# include <sys/ioctl.h> 71# endif 72#endif 73 74#define my_pair 1 75 76/* This is needed to compile 'struct winsize' */ 77#if NEED_PTEM_H 78#include <sys/stream.h> 79#include <sys/ptem.h> 80#endif 81 82#if USE_WIDEC_SUPPORT 83#if HAVE_MBTOWC && HAVE_MBLEN 84#define reset_mbytes(state) mblen(NULL, 0), mbtowc(NULL, NULL, 0) 85#define count_mbytes(buffer,length,state) mblen(buffer,length) 86#define check_mbytes(wch,buffer,length,state) \ 87 (int) mbtowc(&wch, buffer, length) 88#define state_unused 89#elif HAVE_MBRTOWC && HAVE_MBRLEN 90#define reset_mbytes(state) init_mb(state) 91#define count_mbytes(buffer,length,state) mbrlen(buffer,length,&state) 92#define check_mbytes(wch,buffer,length,state) \ 93 (int) mbrtowc(&wch, buffer, length, &state) 94#else 95make an error 96#endif 97#endif /* USE_WIDEC_SUPPORT */ 98 99static RETSIGTYPE finish(int sig) GCC_NORETURN; 100static void show_all(const char *tag); 101 102#if defined(SIGWINCH) && defined(TIOCGWINSZ) && HAVE_RESIZE_TERM 103#define CAN_RESIZE 1 104#else 105#define CAN_RESIZE 0 106#endif 107 108#if CAN_RESIZE 109static RETSIGTYPE adjust(int sig); 110static int interrupted; 111#endif 112 113static bool waiting = FALSE; 114static int shift = 0; 115static bool try_color = FALSE; 116 117static char *fname; 118static NCURSES_CH_T **vec_lines; 119static NCURSES_CH_T **lptr; 120static int num_lines; 121 122static void 123usage(void) 124{ 125 static const char *msg[] = 126 { 127 "Usage: view [options] file" 128 ,"" 129 ,"Options:" 130 ," -c use color if terminal supports it" 131 ," -i ignore INT, QUIT, TERM signals" 132 ," -n NUM specify maximum number of lines (default 1000)" 133#if defined(KEY_RESIZE) 134 ," -r use old-style sigwinch handler rather than KEY_RESIZE" 135#endif 136#ifdef TRACE 137 ," -t trace screen updates" 138 ," -T NUM specify trace mask" 139#endif 140 }; 141 size_t n; 142 for (n = 0; n < SIZEOF(msg); n++) 143 fprintf(stderr, "%s\n", msg[n]); 144 ExitProgram(EXIT_FAILURE); 145} 146 147static int 148ch_len(NCURSES_CH_T * src) 149{ 150 int result = 0; 151#if USE_WIDEC_SUPPORT 152#endif 153 154#if USE_WIDEC_SUPPORT 155 while (getcchar(src++, NULL, NULL, NULL, NULL) > 0) 156 result++; 157#else 158 while (*src++) 159 result++; 160#endif 161 return result; 162} 163 164/* 165 * Allocate a string into an array of chtype's. If UTF-8 mode is 166 * active, translate the string accordingly. 167 */ 168static NCURSES_CH_T * 169ch_dup(char *src) 170{ 171 unsigned len = strlen(src); 172 NCURSES_CH_T *dst = typeMalloc(NCURSES_CH_T, len + 1); 173 unsigned j, k; 174#if USE_WIDEC_SUPPORT 175 wchar_t wstr[CCHARW_MAX + 1]; 176 wchar_t wch; 177 int l = 0; 178 size_t rc; 179 int width; 180#ifndef state_unused 181 mbstate_t state; 182#endif 183#endif /* USE_WIDEC_SUPPORT */ 184 185#if USE_WIDEC_SUPPORT 186 reset_mbytes(state); 187#endif 188 for (j = k = 0; j < len; j++) { 189#if USE_WIDEC_SUPPORT 190 rc = check_mbytes(wch, src + j, len - j, state); 191 if (rc == (size_t) -1 || rc == (size_t) -2) 192 break; 193 j += rc - 1; 194 if ((width = wcwidth(wch)) < 0) 195 break; 196 if ((width > 0 && l > 0) || l == CCHARW_MAX) { 197 wstr[l] = L'\0'; 198 l = 0; 199 if (setcchar(dst + k, wstr, 0, 0, NULL) != OK) 200 break; 201 ++k; 202 } 203 if (width == 0 && l == 0) 204 wstr[l++] = L' '; 205 wstr[l++] = wch; 206#else 207 dst[k++] = src[j]; 208#endif 209 } 210#if USE_WIDEC_SUPPORT 211 if (l > 0) { 212 wstr[l] = L'\0'; 213 if (setcchar(dst + k, wstr, 0, 0, NULL) == OK) 214 ++k; 215 } 216 wstr[0] = L'\0'; 217 setcchar(dst + k, wstr, 0, 0, NULL); 218#else 219 dst[k] = 0; 220#endif 221 return dst; 222} 223 224int 225main(int argc, char *argv[]) 226{ 227 int MAXLINES = 1000; 228 FILE *fp; 229 char buf[BUFSIZ]; 230 int i; 231 int my_delay = 0; 232 NCURSES_CH_T **olptr; 233 int value = 0; 234 bool done = FALSE; 235 bool got_number = FALSE; 236#if CAN_RESIZE 237 bool nonposix_resize = FALSE; 238#endif 239 const char *my_label = "Input"; 240 241 setlocale(LC_ALL, ""); 242 243#ifndef NCURSES_VERSION 244 /* 245 * We know ncurses will catch SIGINT if we don't establish our own handler. 246 * Other versions of curses may/may not catch it. 247 */ 248 (void) signal(SIGINT, finish); /* arrange interrupts to terminate */ 249#endif 250 251 while ((i = getopt(argc, argv, "cin:rtT:")) != -1) { 252 switch (i) { 253 case 'c': 254 try_color = TRUE; 255 break; 256 case 'i': 257 CATCHALL(SIG_IGN); 258 break; 259 case 'n': 260 if ((MAXLINES = atoi(optarg)) < 1 || 261 (MAXLINES + 2) <= 1) 262 usage(); 263 break; 264#if CAN_RESIZE 265 case 'r': 266 nonposix_resize = TRUE; 267 break; 268#endif 269#ifdef TRACE 270 case 'T': 271 trace((unsigned) atoi(optarg)); 272 break; 273 case 't': 274 trace(TRACE_CALLS); 275 break; 276#endif 277 default: 278 usage(); 279 } 280 } 281 if (optind + 1 != argc) 282 usage(); 283 284 if ((vec_lines = typeMalloc(NCURSES_CH_T *, MAXLINES + 2)) == 0) 285 usage(); 286 287 fname = argv[optind]; 288 if ((fp = fopen(fname, "r")) == 0) { 289 perror(fname); 290 ExitProgram(EXIT_FAILURE); 291 } 292#if CAN_RESIZE 293 if (nonposix_resize) 294 (void) signal(SIGWINCH, adjust); /* arrange interrupts to resize */ 295#endif 296 297 /* slurp the file */ 298 for (lptr = &vec_lines[0]; (lptr - vec_lines) < MAXLINES; lptr++) { 299 char temp[BUFSIZ], *s, *d; 300 int col; 301 302 if (fgets(buf, sizeof(buf), fp) == 0) 303 break; 304 305 /* convert tabs so that shift will work properly */ 306 for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) { 307 if (*d == '\n') { 308 *d = '\0'; 309 break; 310 } else if (*d == '\t') { 311 col = (col | 7) + 1; 312 while ((d - temp) != col) 313 *d++ = ' '; 314 } else 315#if USE_WIDEC_SUPPORT 316 col++, d++; 317#else 318 if (isprint(UChar(*d))) { 319 col++; 320 d++; 321 } else { 322 sprintf(d, "\\%03o", UChar(*s)); 323 d += strlen(d); 324 col = (d - temp); 325 } 326#endif 327 } 328 *lptr = ch_dup(temp); 329 } 330 (void) fclose(fp); 331 num_lines = lptr - vec_lines; 332 333 (void) initscr(); /* initialize the curses library */ 334 keypad(stdscr, TRUE); /* enable keyboard mapping */ 335 (void) nonl(); /* tell curses not to do NL->CR/NL on output */ 336 (void) cbreak(); /* take input chars one at a time, no wait for \n */ 337 (void) noecho(); /* don't echo input */ 338 nodelay(stdscr, TRUE); 339 idlok(stdscr, TRUE); /* allow use of insert/delete line */ 340 341 if (try_color) { 342 if (has_colors()) { 343 start_color(); 344 init_pair(my_pair, COLOR_WHITE, COLOR_BLUE); 345 bkgd(COLOR_PAIR(my_pair)); 346 } else { 347 try_color = FALSE; 348 } 349 } 350 351 lptr = vec_lines; 352 while (!done) { 353 int n, c; 354 355 if (!got_number) 356 show_all(my_label); 357 358 n = 0; 359 for (;;) { 360#if CAN_RESIZE 361 if (interrupted) { 362 adjust(0); 363 my_label = "interrupt"; 364 } 365#endif 366 waiting = TRUE; 367 c = getch(); 368 waiting = FALSE; 369 if ((c < 127) && isdigit(c)) { 370 if (!got_number) { 371 mvprintw(0, 0, "Count: "); 372 clrtoeol(); 373 } 374 addch(UChar(c)); 375 value = 10 * value + (c - '0'); 376 got_number = TRUE; 377 } else 378 break; 379 } 380 if (got_number && value) { 381 n = value; 382 } else { 383 n = 1; 384 } 385 386 if (c != ERR) 387 my_label = keyname(c); 388 switch (c) { 389 case KEY_DOWN: 390 case 'n': 391 olptr = lptr; 392 for (i = 0; i < n; i++) 393 if ((lptr - vec_lines) < (num_lines - LINES + 1)) 394 lptr++; 395 else 396 break; 397 scrl(lptr - olptr); 398 break; 399 400 case KEY_UP: 401 case 'p': 402 olptr = lptr; 403 for (i = 0; i < n; i++) 404 if (lptr > vec_lines) 405 lptr--; 406 else 407 break; 408 scrl(lptr - olptr); 409 break; 410 411 case 'h': 412 case KEY_HOME: 413 lptr = vec_lines; 414 break; 415 416 case 'e': 417 case KEY_END: 418 if (num_lines > LINES) 419 lptr = vec_lines + num_lines - LINES + 1; 420 else 421 lptr = vec_lines; 422 break; 423 424 case 'r': 425 case KEY_RIGHT: 426 shift += n; 427 break; 428 429 case 'l': 430 case KEY_LEFT: 431 shift -= n; 432 if (shift < 0) { 433 shift = 0; 434 beep(); 435 } 436 break; 437 438 case 'q': 439 done = TRUE; 440 break; 441 442#ifdef KEY_RESIZE 443 case KEY_RESIZE: /* ignore this; ncurses will repaint */ 444 break; 445#endif 446 case 's': 447 if (got_number) { 448 halfdelay(my_delay = n); 449 } else { 450 nodelay(stdscr, FALSE); 451 my_delay = -1; 452 } 453 break; 454 case ' ': 455 nodelay(stdscr, TRUE); 456 my_delay = 0; 457 break; 458 case ERR: 459 if (!my_delay) 460 napms(50); 461 break; 462 default: 463 beep(); 464 break; 465 } 466 if (c >= KEY_MIN || (c > 0 && !isdigit(c))) { 467 got_number = FALSE; 468 value = 0; 469 } 470 } 471 472 finish(0); /* we're done */ 473} 474 475static RETSIGTYPE 476finish(int sig) 477{ 478 endwin(); 479#if NO_LEAKS 480 if (vec_lines != 0) { 481 int n; 482 for (n = 0; n < num_lines; ++n) { 483 free(vec_lines[n]); 484 } 485 free(vec_lines); 486 } 487#endif 488 ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS); 489} 490 491#if CAN_RESIZE 492/* 493 * This uses functions that are "unsafe", but it seems to work on SunOS and 494 * Linux. Usually: the "unsafe" refers to the functions that POSIX lists 495 * which may be called from a signal handler. Those do not include buffered 496 * I/O, which is used for instance in wrefresh(). To be really portable, you 497 * should use the KEY_RESIZE return (which relies on ncurses' sigwinch 498 * handler). 499 * 500 * The 'wrefresh(curscr)' is needed to force the refresh to start from the top 501 * of the screen -- some xterms mangle the bitmap while resizing. 502 */ 503static RETSIGTYPE 504adjust(int sig) 505{ 506 if (waiting || sig == 0) { 507 struct winsize size; 508 509 if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) { 510 resize_term(size.ws_row, size.ws_col); 511 wrefresh(curscr); /* Linux needs this */ 512 show_all(sig ? "SIGWINCH" : "interrupt"); 513 } 514 interrupted = FALSE; 515 } else { 516 interrupted = TRUE; 517 } 518 (void) signal(SIGWINCH, adjust); /* some systems need this */ 519} 520#endif /* CAN_RESIZE */ 521 522static void 523show_all(const char *tag) 524{ 525 int i; 526 char temp[BUFSIZ]; 527 NCURSES_CH_T *s; 528 time_t this_time; 529 530#if CAN_RESIZE 531 sprintf(temp, "%.20s (%3dx%3d) col %d ", tag, LINES, COLS, shift); 532 i = strlen(temp); 533 if ((i + 7) < (int) sizeof(temp)) 534 sprintf(temp + i, "view %.*s", (int) (sizeof(temp) - 7 - i), fname); 535#else 536 (void) tag; 537 sprintf(temp, "view %.*s", (int) sizeof(temp) - 7, fname); 538#endif 539 move(0, 0); 540 printw("%.*s", COLS, temp); 541 clrtoeol(); 542 this_time = time((time_t *) 0); 543 strcpy(temp, ctime(&this_time)); 544 if ((i = strlen(temp)) != 0) { 545 temp[--i] = 0; 546 if (move(0, COLS - i - 2) != ERR) 547 printw(" %s", temp); 548 } 549 550 scrollok(stdscr, FALSE); /* prevent screen from moving */ 551 for (i = 1; i < LINES; i++) { 552 move(i, 0); 553 printw("%3ld:", (long) (lptr + i - vec_lines)); 554 clrtoeol(); 555 if ((s = lptr[i - 1]) != 0) { 556 int len = ch_len(s); 557 if (len > shift) { 558#if USE_WIDEC_SUPPORT 559 add_wchstr(s + shift); 560#else 561 addchstr(s + shift); 562#endif 563 } 564#if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT) 565 if (try_color) 566 wchgat(stdscr, -1, A_NORMAL, my_pair, NULL); 567#endif 568 } 569 } 570 setscrreg(1, LINES - 1); 571 scrollok(stdscr, TRUE); 572 refresh(); 573} 574