1 /* $OpenBSD: display.c,v 1.16 2003/06/19 22:40:45 millert Exp $ */ 2 3 /* 4 * Top users/processes display for Unix 5 * Version 3 6 * 7 * Copyright (c) 1984, 1989, William LeFebvre, Rice University 8 * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /* 32 * This file contains the routines that display information on the screen. 33 * Each section of the screen has two routines: one for initially writing 34 * all constant and dynamic text, and one for only updating the text that 35 * changes. The prefix "i_" is used on all the "initial" routines and the 36 * prefix "u_" is used for all the "updating" routines. 37 * 38 * ASSUMPTIONS: 39 * None of the "i_" routines use any of the termcap capabilities. 40 * In this way, those routines can be safely used on terminals that 41 * have minimal (or nonexistant) terminal capabilities. 42 * 43 * The routines are called in this order: *_loadave, i_timeofday, 44 * *_procstates, *_cpustates, *_memory, *_message, *_header, 45 * *_process, u_endscreen. 46 */ 47 48 #include <sys/types.h> 49 #include <stdio.h> 50 #include <ctype.h> 51 #include <stdlib.h> 52 #include <string.h> 53 #include <term.h> 54 #include <time.h> 55 #include <unistd.h> 56 #include <stdarg.h> 57 58 #include "screen.h" /* interface to screen package */ 59 #include "layout.h" /* defines for screen position layout */ 60 #include "display.h" 61 #include "top.h" 62 #include "top.local.h" 63 #include "boolean.h" 64 #include "machine.h" /* we should eliminate this!!! */ 65 #include "utils.h" 66 67 #ifdef DEBUG 68 FILE *debug; 69 #endif 70 71 static pid_t lmpid = 0; 72 static int last_hi = 0; /* used in u_process and u_endscreen */ 73 static int lastline = 0; 74 static int display_width = MAX_COLS; 75 76 static char *cpustates_tag(void); 77 static int string_count(char **); 78 static void summary_format(char *, size_t, int *, char **); 79 static void line_update(char *, char *, int, int); 80 81 #define lineindex(l) ((l)*display_width) 82 83 /* things initialized by display_init and used thruout */ 84 85 /* buffer of proc information lines for display updating */ 86 char *screenbuf = NULL; 87 88 static char **procstate_names; 89 static char **cpustate_names; 90 static char **memory_names; 91 92 static int num_procstates; 93 static int num_cpustates; 94 95 static int *lprocstates; 96 static int *lcpustates; 97 98 static int *cpustate_columns; 99 static int cpustate_total_length; 100 101 static enum { 102 OFF, ON, ERASE 103 } header_status = ON; 104 105 int 106 display_resize(void) 107 { 108 int display_lines; 109 110 /* first, deallocate any previous buffer that may have been there */ 111 if (screenbuf != NULL) 112 free(screenbuf); 113 114 /* calculate the current dimensions */ 115 /* if operating in "dumb" mode, we only need one line */ 116 display_lines = smart_terminal ? screen_length - Header_lines : 1; 117 118 /* 119 * we don't want more than MAX_COLS columns, since the 120 * machine-dependent modules make static allocations based on 121 * MAX_COLS and we don't want to run off the end of their buffers 122 */ 123 display_width = screen_width; 124 if (display_width >= MAX_COLS) 125 display_width = MAX_COLS - 1; 126 127 /* now, allocate space for the screen buffer */ 128 screenbuf = (char *) malloc(display_lines * display_width); 129 if (screenbuf == (char *) NULL) 130 return (-1); 131 132 /* return number of lines available */ 133 /* for dumb terminals, pretend like we can show any amount */ 134 return (smart_terminal ? display_lines : Largest); 135 } 136 137 int 138 display_init(struct statics * statics) 139 { 140 int display_lines, *ip, i; 141 char **pp; 142 143 /* call resize to do the dirty work */ 144 display_lines = display_resize(); 145 146 /* only do the rest if we need to */ 147 if (display_lines > -1) { 148 /* save pointers and allocate space for names */ 149 procstate_names = statics->procstate_names; 150 num_procstates = string_count(procstate_names); 151 lprocstates = (int *) malloc(num_procstates * sizeof(int)); 152 153 cpustate_names = statics->cpustate_names; 154 num_cpustates = string_count(cpustate_names); 155 lcpustates = (int *) malloc(num_cpustates * sizeof(int)); 156 cpustate_columns = (int *) malloc(num_cpustates * sizeof(int)); 157 158 memory_names = statics->memory_names; 159 160 /* calculate starting columns where needed */ 161 cpustate_total_length = 0; 162 pp = cpustate_names; 163 ip = cpustate_columns; 164 while (*pp != NULL) { 165 if ((i = strlen(*pp++)) > 0) { 166 *ip++ = cpustate_total_length; 167 cpustate_total_length += i + 8; 168 } 169 } 170 } 171 /* return number of lines available */ 172 return (display_lines); 173 } 174 175 void 176 i_loadave(pid_t mpid, double *avenrun) 177 { 178 int i; 179 180 /* i_loadave also clears the screen, since it is first */ 181 clear(); 182 183 /* mpid == -1 implies this system doesn't have an _mpid */ 184 if (mpid != -1) 185 printf("last pid: %5ld; ", (long) mpid); 186 187 printf("load averages"); 188 189 for (i = 0; i < 3; i++) 190 printf("%c %5.2f", i == 0 ? ':' : ',', avenrun[i]); 191 192 lmpid = mpid; 193 } 194 195 void 196 u_loadave(pid_t mpid, double *avenrun) 197 { 198 int i; 199 200 if (mpid != -1) { 201 /* change screen only when value has really changed */ 202 if (mpid != lmpid) { 203 Move_to(x_lastpid, y_lastpid); 204 printf("%5ld", (long) mpid); 205 lmpid = mpid; 206 } 207 /* i remembers x coordinate to move to */ 208 i = x_loadave; 209 } else 210 i = x_loadave_nompid; 211 212 /* move into position for load averages */ 213 Move_to(i, y_loadave); 214 215 /* display new load averages */ 216 /* we should optimize this and only display changes */ 217 for (i = 0; i < 3; i++) 218 printf("%s%5.2f", i == 0 ? "" : ", ", avenrun[i]); 219 } 220 221 /* 222 * Display the current time. 223 * "ctime" always returns a string that looks like this: 224 * 225 * Sun Sep 16 01:03:52 1973 226 * 012345678901234567890123 227 * 1 2 228 * 229 * We want indices 11 thru 18 (length 8). 230 */ 231 232 void 233 i_timeofday(time_t * tod) 234 { 235 236 if (smart_terminal) { 237 Move_to(screen_width - 8, 0); 238 } else { 239 if (fputs(" ", stdout) == EOF) 240 exit(1); 241 } 242 #ifdef DEBUG 243 { 244 char *foo; 245 foo = ctime(tod); 246 if (fputs(foo, stdout) == EOF) 247 exit(1); 248 } 249 #endif 250 printf("%-8.8s\n", &(ctime(tod)[11])); 251 lastline = 1; 252 } 253 254 static int ltotal = 0; 255 static char procstates_buffer[128]; 256 257 /* 258 * *_procstates(total, brkdn, names) - print the process summary line 259 * 260 * Assumptions: cursor is at the beginning of the line on entry 261 * lastline is valid 262 */ 263 void 264 i_procstates(int total, int *brkdn) 265 { 266 int i; 267 268 /* write current number of processes and remember the value */ 269 printf("%d processes:", total); 270 ltotal = total; 271 272 /* put out enough spaces to get to column 15 */ 273 i = digits(total); 274 while (i++ < 4) { 275 if (putchar(' ') == EOF) 276 exit(1); 277 } 278 279 /* format and print the process state summary */ 280 summary_format(procstates_buffer, sizeof(procstates_buffer), brkdn, 281 procstate_names); 282 if (fputs(procstates_buffer, stdout) == EOF) 283 exit(1); 284 285 /* save the numbers for next time */ 286 memcpy(lprocstates, brkdn, num_procstates * sizeof(int)); 287 } 288 289 void 290 u_procstates(int total, int *brkdn) 291 { 292 static char new[128]; 293 int i; 294 295 /* update number of processes only if it has changed */ 296 if (ltotal != total) { 297 /* move and overwrite */ 298 #if (x_procstate == 0) 299 Move_to(x_procstate, y_procstate); 300 #else 301 /* cursor is already there...no motion needed */ 302 /* assert(lastline == 1); */ 303 #endif 304 printf("%d", total); 305 306 /* if number of digits differs, rewrite the label */ 307 if (digits(total) != digits(ltotal)) { 308 if (fputs(" processes:", stdout) == EOF) 309 exit(1); 310 /* put out enough spaces to get to column 15 */ 311 i = digits(total); 312 while (i++ < 4) { 313 if (putchar(' ') == EOF) 314 exit(1); 315 } 316 /* cursor may end up right where we want it!!! */ 317 } 318 /* save new total */ 319 ltotal = total; 320 } 321 /* see if any of the state numbers has changed */ 322 if (memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0) { 323 /* format and update the line */ 324 summary_format(new, sizeof(new), brkdn, procstate_names); 325 line_update(procstates_buffer, new, x_brkdn, y_brkdn); 326 memcpy(lprocstates, brkdn, num_procstates * sizeof(int)); 327 } 328 } 329 330 /* 331 * *_cpustates(states, names) - print the cpu state percentages 332 * 333 * Assumptions: cursor is on the PREVIOUS line 334 */ 335 336 static int cpustates_column; 337 338 /* cpustates_tag() calculates the correct tag to use to label the line */ 339 340 static char * 341 cpustates_tag(void) 342 { 343 static char *short_tag = "CPU: "; 344 static char *long_tag = "CPU states: "; 345 char *use; 346 347 /* 348 * if length + strlen(long_tag) >= screen_width, then we have to use 349 * the shorter tag (we subtract 2 to account for ": ") 350 */ 351 if (cpustate_total_length + (int) strlen(long_tag) - 2 >= screen_width) 352 use = short_tag; 353 else 354 use = long_tag; 355 356 /* set cpustates_column accordingly then return result */ 357 cpustates_column = strlen(use); 358 return (use); 359 } 360 361 void 362 i_cpustates(int *states) 363 { 364 int i = 0, value; 365 char **names = cpustate_names, *thisname; 366 367 /* print tag and bump lastline */ 368 printf("\n%s", cpustates_tag()); 369 lastline++; 370 371 /* now walk thru the names and print the line */ 372 while ((thisname = *names++) != NULL) { 373 if (*thisname != '\0') { 374 /* retrieve the value and remember it */ 375 value = *states++; 376 377 /* if percentage is >= 1000, print it as 100% */ 378 printf((value >= 1000 ? "%s%4.0f%% %s" : "%s%4.1f%% %s"), 379 i++ == 0 ? "" : ", ", 380 ((float) value) / 10., 381 thisname); 382 } 383 } 384 385 /* copy over values into "last" array */ 386 memcpy(lcpustates, states, num_cpustates * sizeof(int)); 387 } 388 389 void 390 u_cpustates(int *states) 391 { 392 char **names = cpustate_names, *thisname; 393 int value, *lp, *colp; 394 395 Move_to(cpustates_column, y_cpustates); 396 lastline = y_cpustates; 397 lp = lcpustates; 398 colp = cpustate_columns; 399 400 /* we could be much more optimal about this */ 401 while ((thisname = *names++) != NULL) { 402 if (*thisname != '\0') { 403 /* did the value change since last time? */ 404 if (*lp != *states) { 405 /* yes, move and change */ 406 Move_to(cpustates_column + *colp, y_cpustates); 407 lastline = y_cpustates; 408 409 /* retrieve value and remember it */ 410 value = *states; 411 412 /* if percentage is >= 1000, print it as 100% */ 413 printf((value >= 1000 ? "%4.0f" : "%4.1f"), 414 ((double) value) / 10.); 415 416 /* remember it for next time */ 417 *lp = *states; 418 } 419 } 420 /* increment and move on */ 421 lp++; 422 states++; 423 colp++; 424 } 425 } 426 427 void 428 z_cpustates(void) 429 { 430 char **names = cpustate_names, *thisname; 431 int i = 0, *lp; 432 433 /* show tag and bump lastline */ 434 printf("\n%s", cpustates_tag()); 435 lastline++; 436 437 while ((thisname = *names++) != NULL) { 438 if (*thisname != '\0') 439 printf("%s %% %s", i++ == 0 ? "" : ", ", thisname); 440 } 441 442 /* fill the "last" array with all -1s, to insure correct updating */ 443 lp = lcpustates; 444 i = num_cpustates; 445 while (--i >= 0) 446 *lp++ = -1; 447 } 448 449 static char memory_buffer[MAX_COLS]; 450 451 /* 452 * *_memory(stats) - print "Memory: " followed by the memory summary string 453 * 454 * Assumptions: cursor is on "lastline" 455 * for i_memory ONLY: cursor is on the previous line 456 */ 457 void 458 i_memory(int *stats) 459 { 460 if (fputs("\nMemory: ", stdout) == EOF) 461 exit(1); 462 lastline++; 463 464 /* format and print the memory summary */ 465 summary_format(memory_buffer, sizeof(memory_buffer), stats, 466 memory_names); 467 if (fputs(memory_buffer, stdout) == EOF) 468 exit(1); 469 } 470 471 void 472 u_memory(int *stats) 473 { 474 static char new[MAX_COLS]; 475 476 /* format the new line */ 477 summary_format(new, sizeof(new), stats, memory_names); 478 line_update(memory_buffer, new, x_mem, y_mem); 479 } 480 481 /* 482 * *_message() - print the next pending message line, or erase the one 483 * that is there. 484 * 485 * Note that u_message is (currently) the same as i_message. 486 * 487 * Assumptions: lastline is consistent 488 */ 489 490 /* 491 * i_message is funny because it gets its message asynchronously (with 492 * respect to screen updates). 493 */ 494 495 static char next_msg[MAX_COLS + 5]; 496 static int msglen = 0; 497 /* 498 * Invariant: msglen is always the length of the message currently displayed 499 * on the screen (even when next_msg doesn't contain that message). 500 */ 501 502 void 503 i_message(void) 504 { 505 while (lastline < y_message) { 506 if (fputc('\n', stdout) == EOF) 507 exit(1); 508 lastline++; 509 } 510 if (next_msg[0] != '\0') { 511 standout(next_msg); 512 msglen = strlen(next_msg); 513 next_msg[0] = '\0'; 514 } else if (msglen > 0) { 515 (void) clear_eol(msglen); 516 msglen = 0; 517 } 518 } 519 520 void 521 u_message(void) 522 { 523 i_message(); 524 } 525 526 static int header_length; 527 528 /* 529 * *_header(text) - print the header for the process area 530 * 531 * Assumptions: cursor is on the previous line and lastline is consistent 532 */ 533 534 void 535 i_header(char *text) 536 { 537 header_length = strlen(text); 538 if (header_status == ON) { 539 if (putchar('\n') == EOF) 540 exit(1); 541 if (fputs(text, stdout) == EOF) 542 exit(1); 543 lastline++; 544 } else if (header_status == ERASE) { 545 header_status = OFF; 546 } 547 } 548 549 /* ARGSUSED */ 550 void 551 u_header(char *text) 552 { 553 if (header_status == ERASE) { 554 if (putchar('\n') == EOF) 555 exit(1); 556 lastline++; 557 clear_eol(header_length); 558 header_status = OFF; 559 } 560 } 561 562 /* 563 * *_process(line, thisline) - print one process line 564 * 565 * Assumptions: lastline is consistent 566 */ 567 568 void 569 i_process(int line, char *thisline) 570 { 571 char *base; 572 size_t len; 573 574 /* make sure we are on the correct line */ 575 while (lastline < y_procs + line) { 576 if (putchar('\n') == EOF) 577 exit(1); 578 lastline++; 579 } 580 581 /* truncate the line to conform to our current screen width */ 582 thisline[display_width] = '\0'; 583 584 /* write the line out */ 585 if (fputs(thisline, stdout) == EOF) 586 exit(1); 587 588 /* copy it in to our buffer */ 589 base = smart_terminal ? screenbuf + lineindex(line) : screenbuf; 590 len = strlcpy(base, thisline, display_width); 591 if (len < (size_t)display_width) { 592 /* zero fill the rest of it */ 593 memset(base + len, 0, display_width - len); 594 } 595 } 596 597 void 598 u_process(int linenum, char *linebuf) 599 { 600 int screen_line = linenum + Header_lines; 601 char *bufferline; 602 size_t len; 603 604 /* remember a pointer to the current line in the screen buffer */ 605 bufferline = &screenbuf[lineindex(linenum)]; 606 607 /* truncate the line to conform to our current screen width */ 608 linebuf[display_width] = '\0'; 609 610 /* is line higher than we went on the last display? */ 611 if (linenum >= last_hi) { 612 /* yes, just ignore screenbuf and write it out directly */ 613 /* get positioned on the correct line */ 614 if (screen_line - lastline == 1) { 615 if (putchar('\n') == EOF) 616 exit(1); 617 lastline++; 618 } else { 619 Move_to(0, screen_line); 620 lastline = screen_line; 621 } 622 623 /* now write the line */ 624 if (fputs(linebuf, stdout) == EOF) 625 exit(1); 626 627 /* copy it in to the buffer */ 628 len = strlcpy(bufferline, linebuf, display_width); 629 if (len < (size_t)display_width) { 630 /* zero fill the rest of it */ 631 memset(bufferline + len, 0, display_width - len); 632 } 633 } else { 634 line_update(bufferline, linebuf, 0, linenum + Header_lines); 635 } 636 } 637 638 void 639 u_endscreen(int hi) 640 { 641 int screen_line = hi + Header_lines, i; 642 643 if (smart_terminal) { 644 if (hi < last_hi) { 645 /* need to blank the remainder of the screen */ 646 /* 647 * but only if there is any screen left below this 648 * line 649 */ 650 if (lastline + 1 < screen_length) { 651 /* 652 * efficiently move to the end of currently 653 * displayed info 654 */ 655 if (screen_line - lastline < 5) { 656 while (lastline < screen_line) { 657 if (putchar('\n') == EOF) 658 exit(1); 659 lastline++; 660 } 661 } else { 662 Move_to(0, screen_line); 663 lastline = screen_line; 664 } 665 666 if (clear_to_end) { 667 /* we can do this the easy way */ 668 putcap(clear_to_end); 669 } else { 670 /* use clear_eol on each line */ 671 i = hi; 672 while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi) { 673 if (putchar('\n') == EOF) 674 exit(1); 675 } 676 } 677 } 678 } 679 last_hi = hi; 680 681 /* move the cursor to a pleasant place */ 682 Move_to(x_idlecursor, y_idlecursor); 683 lastline = y_idlecursor; 684 } else { 685 /* 686 * separate this display from the next with some vertical 687 * room 688 */ 689 if (fputs("\n\n", stdout) == EOF) 690 exit(1); 691 } 692 } 693 694 void 695 display_header(int t) 696 { 697 if (t) { 698 header_status = ON; 699 } else if (header_status == ON) { 700 header_status = ERASE; 701 } 702 } 703 704 void 705 new_message(int type, const char *msgfmt,...) 706 { 707 va_list ap; 708 int i; 709 710 va_start(ap, msgfmt); 711 /* first, format the message */ 712 vsnprintf(next_msg, sizeof(next_msg), msgfmt, ap); 713 va_end(ap); 714 715 if (msglen > 0) { 716 /* message there already -- can we clear it? */ 717 if (!overstrike) { 718 /* yes -- write it and clear to end */ 719 i = strlen(next_msg); 720 if ((type & MT_delayed) == 0) { 721 if (type & MT_standout) 722 standout(next_msg); 723 else { 724 if (fputs(next_msg, stdout) == EOF) 725 exit(1); 726 } 727 (void) clear_eol(msglen - i); 728 msglen = i; 729 next_msg[0] = '\0'; 730 } 731 } 732 } else { 733 if ((type & MT_delayed) == 0) { 734 if (type & MT_standout) 735 standout(next_msg); 736 else { 737 if (fputs(next_msg, stdout) == EOF) 738 exit(1); 739 } 740 msglen = strlen(next_msg); 741 next_msg[0] = '\0'; 742 } 743 } 744 } 745 746 void 747 clear_message(void) 748 { 749 if (clear_eol(msglen) == 1) { 750 if (putchar('\r') == EOF) 751 exit(1); 752 } 753 } 754 755 int 756 readline(char *buffer, int size, int numeric) 757 { 758 char *ptr = buffer, ch, cnt = 0, maxcnt = 0; 759 760 /* allow room for null terminator */ 761 size -= 1; 762 763 /* read loop */ 764 while ((fflush(stdout), read(0, ptr, 1) > 0)) { 765 /* newline means we are done */ 766 if ((ch = *ptr) == '\n') 767 break; 768 769 /* handle special editing characters */ 770 if (ch == ch_kill) { 771 /* kill line -- account for overstriking */ 772 if (overstrike) 773 msglen += maxcnt; 774 775 /* return null string */ 776 *buffer = '\0'; 777 if (putchar('\r') == EOF) 778 exit(1); 779 return (-1); 780 } else if (ch == ch_erase) { 781 /* erase previous character */ 782 if (cnt <= 0) { 783 /* none to erase! */ 784 if (putchar('\7') == EOF) 785 exit(1); 786 } else { 787 if (fputs("\b \b", stdout) == EOF) 788 exit(1); 789 ptr--; 790 cnt--; 791 } 792 } 793 /* check for character validity and buffer overflow */ 794 else if (cnt == size || (numeric && !isdigit(ch)) || 795 !isprint(ch)) { 796 /* not legal */ 797 if (putchar('\7') == EOF) 798 exit(1); 799 } else { 800 /* echo it and store it in the buffer */ 801 if (putchar(ch) == EOF) 802 exit(1); 803 ptr++; 804 cnt++; 805 if (cnt > maxcnt) 806 maxcnt = cnt; 807 } 808 } 809 810 /* all done -- null terminate the string */ 811 *ptr = '\0'; 812 813 /* account for the extra characters in the message area */ 814 /* (if terminal overstrikes, remember the furthest they went) */ 815 msglen += overstrike ? maxcnt : cnt; 816 817 /* return either inputted number or string length */ 818 if (putchar('\r') == EOF) 819 exit(1); 820 return (cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt); 821 } 822 823 /* internal support routines */ 824 static int 825 string_count(char **pp) 826 { 827 int cnt; 828 829 cnt = 0; 830 while (*pp++ != NULL) 831 cnt++; 832 return (cnt); 833 } 834 835 #define COPYLEFT(to, from) \ 836 do { \ 837 len = strlcpy((to), (from), left); \ 838 if (len >= left) \ 839 return; \ 840 p += len; \ 841 left -= len; \ 842 } while (0) 843 844 static void 845 summary_format(char *buf, size_t left, int *numbers, char **names) 846 { 847 char *p, *thisname; 848 size_t len; 849 int num; 850 851 /* format each number followed by its string */ 852 p = buf; 853 while ((thisname = *names++) != NULL) { 854 /* get the number to format */ 855 num = *numbers++; 856 857 if (num >= 0) { 858 /* is this number in kilobytes? */ 859 if (thisname[0] == 'K') { 860 /* yes: format it as a memory value */ 861 COPYLEFT(p, format_k(num)); 862 863 /* 864 * skip over the K, since it was included by 865 * format_k 866 */ 867 COPYLEFT(p, thisname + 1); 868 } else if (num > 0) { 869 len = snprintf(p, left, "%d%s", num, thisname); 870 if (len == (size_t)-1 || len >= left) 871 return; 872 p += len; 873 left -= len; 874 } 875 } else { 876 /* 877 * Ignore negative numbers, but display corresponding 878 * string. 879 */ 880 COPYLEFT(p, thisname); 881 } 882 } 883 884 /* if the last two characters in the string are ", ", delete them */ 885 p -= 2; 886 if (p >= buf && p[0] == ',' && p[1] == ' ') 887 *p = '\0'; 888 } 889 890 static void 891 line_update(char *old, char *new, int start, int line) 892 { 893 int ch, diff, newcol = start + 1, lastcol = start; 894 char cursor_on_line = No, *current; 895 896 /* compare the two strings and only rewrite what has changed */ 897 current = old; 898 #ifdef DEBUG 899 fprintf(debug, "line_update, starting at %d\n", start); 900 fputs(old, debug); 901 fputc('\n', debug); 902 fputs(new, debug); 903 fputs("\n-\n", debug); 904 #endif 905 906 /* start things off on the right foot */ 907 /* this is to make sure the invariants get set up right */ 908 if ((ch = *new++) != *old) { 909 if (line - lastline == 1 && start == 0) { 910 if (putchar('\n') == EOF) 911 exit(1); 912 } else 913 Move_to(start, line); 914 915 cursor_on_line = Yes; 916 if (putchar(ch) == EOF) 917 exit(1); 918 *old = ch; 919 lastcol = 1; 920 } 921 old++; 922 923 /* 924 * main loop -- check each character. If the old and new aren't the 925 * same, then update the display. When the distance from the 926 * current cursor position to the new change is small enough, 927 * the characters that belong there are written to move the 928 * cursor over. 929 * 930 * Invariants: 931 * lastcol is the column where the cursor currently is sitting 932 * (always one beyond the end of the last mismatch). 933 */ 934 do { 935 if ((ch = *new++) != *old) { 936 /* new character is different from old */ 937 /* make sure the cursor is on top of this character */ 938 diff = newcol - lastcol; 939 if (diff > 0) { 940 /* 941 * some motion is required--figure out which 942 * is shorter 943 */ 944 if (diff < 6 && cursor_on_line) { 945 /* 946 * overwrite old stuff--get it out of 947 * the old buffer 948 */ 949 printf("%.*s", diff, ¤t[lastcol - start]); 950 } else { 951 /* use cursor addressing */ 952 Move_to(newcol, line); 953 cursor_on_line = Yes; 954 } 955 /* remember where the cursor is */ 956 lastcol = newcol + 1; 957 } else { 958 /* already there, update position */ 959 lastcol++; 960 } 961 962 /* write what we need to */ 963 if (ch == '\0') { 964 /* 965 * at the end--terminate with a 966 * clear-to-end-of-line 967 */ 968 (void) clear_eol(strlen(old)); 969 } else { 970 /* write the new character */ 971 if (putchar(ch) == EOF) 972 exit(1); 973 } 974 /* put the new character in the screen buffer */ 975 *old = ch; 976 } 977 /* update working column and screen buffer pointer */ 978 newcol++; 979 old++; 980 } while (ch != '\0'); 981 982 /* zero out the rest of the line buffer -- MUST BE DONE! */ 983 diff = display_width - newcol; 984 if (diff > 0) 985 memset(old, 0, diff); 986 987 /* remember where the current line is */ 988 if (cursor_on_line) 989 lastline = line; 990 } 991 992 /* 993 * printable(str) - make the string pointed to by "str" into one that is 994 * printable (i.e.: all ascii), by converting all non-printable 995 * characters into '?'. Replacements are done in place and a pointer 996 * to the original buffer is returned. 997 */ 998 char * 999 printable(char *str) 1000 { 1001 char *ptr, ch; 1002 1003 ptr = str; 1004 while ((ch = *ptr) != '\0') { 1005 if (!isprint(ch)) 1006 *ptr = '?'; 1007 ptr++; 1008 } 1009 return (str); 1010 } 1011