Annotation of embedaddon/iftop/ui.c, revision 1.1.1.2
1.1 misho 1: /*
2: * ui.c:
3: *
4: */
5:
1.1.1.2 ! misho 6: #include "config.h"
! 7:
1.1 misho 8: #include <sys/types.h>
9:
10: #include <ctype.h>
1.1.1.2 ! misho 11: #include <ncurses.h>
1.1 misho 12: #include <errno.h>
13: #include <string.h>
14: #include <math.h>
15: #include <pthread.h>
16: #include <signal.h>
17: #include <stdlib.h>
18: #include <unistd.h>
19: #include <netdb.h>
20:
21: #include <sys/wait.h>
22:
23: #include "addr_hash.h"
24: #include "serv_hash.h"
25: #include "iftop.h"
26: #include "resolver.h"
27: #include "sorted_list.h"
28: #include "options.h"
29: #include "screenfilter.h"
30:
1.1.1.2 ! misho 31: #include "ui_common.h"
1.1 misho 32:
33: #define HELP_TIME 2
34:
35: #define HELP_MESSAGE \
36: "Host display: General:\n"\
37: " n - toggle DNS host resolution P - pause display\n"\
38: " s - toggle show source host h - toggle this help display\n"\
39: " d - toggle show destination host b - toggle bar graph display\n"\
40: " t - cycle line display mode B - cycle bar graph average\n"\
1.1.1.2 ! misho 41: " T - toggle cumulative line totals\n"\
1.1 misho 42: "Port display: j/k - scroll display\n"\
43: " N - toggle service resolution f - edit filter code\n"\
44: " S - toggle show source port l - set screen filter\n"\
45: " D - toggle show destination port L - lin/log scales\n"\
46: " p - toggle port display ! - shell command\n"\
47: " q - quit\n"\
48: "Sorting:\n"\
49: " 1/2/3 - sort by 1st/2nd/3rd column\n"\
50: " < - sort by source name\n"\
51: " > - sort by dest name\n"\
52: " o - freeze current order\n"\
53: "\n"\
1.1.1.2 ! misho 54: "iftop, version " PACKAGE_VERSION
1.1 misho 55:
56:
57: extern hash_type* history;
58: extern int history_pos;
59: extern int history_len;
60:
61: extern options_t options ;
62:
63: void ui_finish();
64:
65: #define HELP_MSG_SIZE 80
66: int showhelphint = 0;
67: int persistenthelp = 0;
68: time_t helptimer = 0;
69: char helpmsg[HELP_MSG_SIZE];
70: int dontshowdisplay = 0;
71:
72: /* Barchart scales. */
73: static struct {
74: int max, interval;
75: } scale[] = {
76: { 64000, 10 }, /* 64 kbit/s */
77: { 128000, 10 },
78: { 256000, 10 },
79: { 1000000, 10 }, /* 1 Mbit/s */
80: { 10000000, 10 },
81: { 100000000, 100 },
82: { 1000000000, 100 } /* 1 Gbit/s */
83: };
84: static int rateidx = 0, wantbiggerrate;
85:
1.1.1.2 ! misho 86: static int rateidx_init = 0;
! 87:
1.1 misho 88: static int get_bar_interval(float bandwidth) {
89: int i = 10;
90: if(bandwidth > 100000000) {
91: i = 100;
92: }
93: return i;
94: }
95:
96: static float get_max_bandwidth() {
97: float max;
98: if(options.max_bandwidth > 0) {
99: max = options.max_bandwidth;
100: }
101: else {
102: max = scale[rateidx].max;
103: }
104: return max;
105: }
106:
107: /* rate in bits */
108: static int get_bar_length(const int rate) {
109: float l;
110: if (rate <= 0)
111: return 0;
1.1.1.2 ! misho 112: if (rate > scale[rateidx].max) {
! 113: wantbiggerrate = 1;
! 114: if(! rateidx_init) {
! 115: while(rate > scale[rateidx_init++].max) {
! 116: }
! 117: rateidx = rateidx_init;
! 118: }
! 119: }
1.1 misho 120: if(options.log_scale) {
121: l = log(rate) / log(get_max_bandwidth());
122: }
123: else {
124: l = rate / get_max_bandwidth();
125: }
126: return (l * COLS);
127: }
128:
129: static void draw_bar_scale(int* y) {
130: float i;
131: float max,interval;
132: max = get_max_bandwidth();
133: interval = get_bar_interval(max);
134: if(options.showbars) {
135: float stop;
136: /* Draw bar graph scale on top of the window. */
137: move(*y, 0);
138: clrtoeol();
139: mvhline(*y + 1, 0, 0, COLS);
140: /* i in bytes */
141:
142: if(options.log_scale) {
143: i = 1.25;
144: stop = max / 8;
145: }
146: else {
147: i = max / (5 * 8);
148: stop = max / 8;
149: }
150:
151: /* for (i = 1.25; i * 8 <= max; i *= interval) { */
152: while(i <= stop) {
153: char s[40], *p;
154: int x;
155: /* This 1024 vs 1000 stuff is just plain evil */
1.1.1.2 ! misho 156: readable_size(i, s, sizeof s, options.log_scale ? 1000 : 1024, options.bandwidth_in_bytes);
1.1 misho 157: p = s + strspn(s, " ");
158: x = get_bar_length(i * 8);
159: mvaddch(*y + 1, x, ACS_BTEE);
160: if (x + strlen(p) >= COLS)
161: x = COLS - strlen(p);
162: mvaddstr(*y, x, p);
163:
164: if(options.log_scale) {
165: i *= interval;
166: }
167: else {
168: i += max / (5 * 8);
169: }
170: }
171: mvaddch(*y + 1, 0, ACS_LLCORNER);
172: *y += 2;
173: }
174: else {
175: mvhline(*y, 0, 0, COLS);
176: *y += 1;
177: }
178: }
179:
180: void draw_line_total(float sent, float recv, int y, int x, option_linedisplay_t linedisplay, int bytes) {
181: char buf[10];
1.1.1.2 ! misho 182: float n = 0;
1.1 misho 183: switch(linedisplay) {
184: case OPTION_LINEDISPLAY_TWO_LINE:
185: draw_line_total(sent, recv, y, x, OPTION_LINEDISPLAY_ONE_LINE_SENT, bytes);
186: draw_line_total(sent, recv, y+1, x, OPTION_LINEDISPLAY_ONE_LINE_RECV, bytes);
187: break;
188: case OPTION_LINEDISPLAY_ONE_LINE_SENT:
189: n = sent;
190: break;
191: case OPTION_LINEDISPLAY_ONE_LINE_RECV:
192: n = recv;
193: break;
194: case OPTION_LINEDISPLAY_ONE_LINE_BOTH:
195: n = recv + sent;
196: break;
197: }
198: if(linedisplay != OPTION_LINEDISPLAY_TWO_LINE) {
199: readable_size(n, buf, 10, 1024, bytes);
200: mvaddstr(y, x, buf);
201: }
202: }
203:
204: void draw_bar(float n, int y) {
205: int L;
206: mvchgat(y, 0, -1, A_NORMAL, 0, NULL);
207: L = get_bar_length(8 * n);
208: if (L > 0)
209: mvchgat(y, 0, L + 1, A_REVERSE, 0, NULL);
210: }
211:
212: void draw_line_totals(int y, host_pair_line* line, option_linedisplay_t linedisplay) {
213: int j;
214: int x = (COLS - 8 * HISTORY_DIVISIONS);
215:
216: for(j = 0; j < HISTORY_DIVISIONS; j++) {
217: draw_line_total(line->sent[j], line->recv[j], y, x, linedisplay, options.bandwidth_in_bytes);
218: x += 8;
219: }
220:
221: if(options.showbars) {
222: switch(linedisplay) {
223: case OPTION_LINEDISPLAY_TWO_LINE:
224: draw_bar(line->sent[options.bar_interval],y);
225: draw_bar(line->recv[options.bar_interval],y+1);
226: break;
227: case OPTION_LINEDISPLAY_ONE_LINE_SENT:
228: draw_bar(line->sent[options.bar_interval],y);
229: break;
230: case OPTION_LINEDISPLAY_ONE_LINE_RECV:
231: draw_bar(line->recv[options.bar_interval],y);
232: break;
233: case OPTION_LINEDISPLAY_ONE_LINE_BOTH:
234: draw_bar(line->recv[options.bar_interval] + line->sent[options.bar_interval],y);
235: break;
236: }
237: }
238: }
239:
240: void draw_totals(host_pair_line* totals) {
241: /* Draw rule */
242: int y = LINES - 4;
243: int j;
244: char buf[10];
245: int x = (COLS - 8 * HISTORY_DIVISIONS);
246: y++;
247: draw_line_totals(y, totals, OPTION_LINEDISPLAY_TWO_LINE);
248: y += 2;
249: for(j = 0; j < HISTORY_DIVISIONS; j++) {
250: readable_size((totals->sent[j] + totals->recv[j]) , buf, 10, 1024, options.bandwidth_in_bytes);
251: mvaddstr(y, x, buf);
252: x += 8;
253: }
254: }
255:
256: extern history_type history_totals;
257:
258:
259: void ui_print() {
260: sorted_list_node* nn = NULL;
261: char host1[HOSTNAME_LENGTH], host2[HOSTNAME_LENGTH];
262: static char *line;
263: static int lcols;
264: int y = 0;
265:
266: if (dontshowdisplay)
267: return;
268:
269: if (!line || lcols != COLS) {
270: xfree(line);
271: line = calloc(COLS + 1, 1);
272: }
273:
274: /*
275: * erase() is faster than clear(). Dunno why we switched to
276: * clear() -pdw 24/10/02
277: */
278: erase();
279:
280: draw_bar_scale(&y);
281:
282: if(options.showhelp) {
283: mvaddstr(y,0,HELP_MESSAGE);
284: }
285: else {
286: int i = 0;
287:
288: while(i < options.screen_offset && ((nn = sorted_list_next_item(&screen_list, nn)) != NULL)) {
289: i++;
290: }
291:
292: /* Screen layout: we have 2 * HISTORY_DIVISIONS 6-character wide history
293: * items, and so can use COLS - 12 * HISTORY_DIVISIONS to print the two
294: * host names. */
295:
296: if(i == 0 || nn != NULL) {
297: while((y < LINES - 5) && ((nn = sorted_list_next_item(&screen_list, nn)) != NULL)) {
298: int x = 0, L;
299:
300:
301: host_pair_line* screen_line = (host_pair_line*)nn->data;
302:
303: if(y < LINES - 5) {
304: L = (COLS - 8 * HISTORY_DIVISIONS - 4) / 2;
305: if(options.show_totals) {
306: L -= 4;
307: }
308: if(L > HOSTNAME_LENGTH) {
309: L = HOSTNAME_LENGTH;
310: }
311:
1.1.1.2 ! misho 312: sprint_host(host1, screen_line->ap.af,
! 313: &(screen_line->ap.src6),
! 314: screen_line->ap.src_port,
! 315: screen_line->ap.protocol, L, options.aggregate_src);
! 316: sprint_host(host2, screen_line->ap.af,
! 317: &(screen_line->ap.dst6),
! 318: screen_line->ap.dst_port,
! 319: screen_line->ap.protocol, L, options.aggregate_dest);
! 320:
1.1 misho 321: if(!screen_filter_match(host1) && !screen_filter_match(host2)) {
322: continue;
323: }
324:
325: mvaddstr(y, x, host1);
326: x += L;
327:
328: switch(options.linedisplay) {
329: case OPTION_LINEDISPLAY_TWO_LINE:
330: mvaddstr(y, x, " => ");
331: mvaddstr(y+1, x, " <= ");
332: break;
333: case OPTION_LINEDISPLAY_ONE_LINE_BOTH:
334: mvaddstr(y, x, "<=> ");
335: break;
336: case OPTION_LINEDISPLAY_ONE_LINE_SENT:
337: mvaddstr(y, x, " => ");
338: break;
339: case OPTION_LINEDISPLAY_ONE_LINE_RECV:
340: mvaddstr(y, x, " <= ");
341: break;
342: }
343:
344: x += 4;
345:
346:
347: mvaddstr(y, x, host2);
348:
349: if(options.show_totals) {
350: draw_line_total(screen_line->total_sent, screen_line->total_recv, y, COLS - 8 * (HISTORY_DIVISIONS + 1), options.linedisplay, 1);
351: }
352:
353: draw_line_totals(y, screen_line, options.linedisplay);
354:
355: }
356: if(options.linedisplay == OPTION_LINEDISPLAY_TWO_LINE) {
357: y += 2;
358: }
359: else {
360: y += 1;
361: }
362: }
363: }
364: }
365:
366:
367: y = LINES - 3;
368:
369: mvhline(y-1, 0, 0, COLS);
370:
371: mvaddstr(y, 0, "TX: ");
372: mvaddstr(y+1, 0, "RX: ");
373: mvaddstr(y+2, 0, "TOTAL: ");
374:
375: /* Cummulative totals */
1.1.1.2 ! misho 376: mvaddstr(y, 16, "cum: ");
1.1 misho 377:
378: readable_size(history_totals.total_sent, line, 10, 1024, 1);
379: mvaddstr(y, 22, line);
380:
381: readable_size(history_totals.total_recv, line, 10, 1024, 1);
382: mvaddstr(y+1, 22, line);
383:
384: readable_size(history_totals.total_recv + history_totals.total_sent, line, 10, 1024, 1);
385: mvaddstr(y+2, 22, line);
386:
387: /* peak traffic */
388: mvaddstr(y, 32, "peak: ");
389:
390: readable_size(peaksent / RESOLUTION, line, 10, 1024, options.bandwidth_in_bytes);
391: mvaddstr(y, 39, line);
392:
393: readable_size(peakrecv / RESOLUTION, line, 10, 1024, options.bandwidth_in_bytes);
394: mvaddstr(y+1, 39, line);
395:
396: readable_size(peaktotal / RESOLUTION, line, 10, 1024, options.bandwidth_in_bytes);
397: mvaddstr(y+2, 39, line);
398:
399: mvaddstr(y, COLS - 8 * HISTORY_DIVISIONS - 8, "rates:");
400:
401: draw_totals(&totals);
402:
403:
404: if(showhelphint) {
405: mvaddstr(0, 0, " ");
406: mvaddstr(0, 1, helpmsg);
407: mvaddstr(0, 1 + strlen(helpmsg), " ");
408: mvchgat(0, 0, strlen(helpmsg) + 2, A_REVERSE, 0, NULL);
409: }
410: move(LINES - 1, COLS - 1);
411:
412: refresh();
413:
414: /* Bar chart auto scale */
415: if (wantbiggerrate && options.max_bandwidth == 0) {
1.1.1.2 ! misho 416: ++rateidx;
! 417: wantbiggerrate = 0;
1.1 misho 418: }
419: }
420:
421: void ui_tick(int print) {
422: if(print) {
423: ui_print();
424: }
425: else if(showhelphint && (time(NULL) - helptimer > HELP_TIME) && !persistenthelp) {
426: showhelphint = 0;
427: ui_print();
428: }
429: }
430:
431: void ui_curses_init() {
432: (void) initscr(); /* initialize the curses library */
433: keypad(stdscr, TRUE); /* enable keyboard mapping */
434: (void) nonl(); /* tell curses not to do NL->CR/NL on output */
435: (void) cbreak(); /* take input chars one at a time, no wait for \n */
436: (void) noecho(); /* don't echo input */
1.1.1.2 ! misho 437: (void) curs_set(0); /* hide blinking cursor in ui */
1.1 misho 438: halfdelay(2);
439: }
440:
441: void showhelp(const char * s) {
442: strncpy(helpmsg, s, HELP_MSG_SIZE);
443: showhelphint = 1;
444: helptimer = time(NULL);
445: persistenthelp = 0;
446: tick(1);
447: }
448:
449: void ui_init() {
450: char msg[20];
451: ui_curses_init();
452:
453: erase();
454:
455: screen_list_init();
456: screen_hash = addr_hash_create();
457:
458: service_hash = serv_hash_create();
459: serv_hash_initialise(service_hash);
460:
461: snprintf(msg,20,"Listening on %s",options.interface);
462: showhelp(msg);
463:
464:
465: }
466:
467:
468: void showportstatus() {
469: if(options.showports == OPTION_PORTS_ON) {
470: showhelp("Port display ON");
471: }
472: else if(options.showports == OPTION_PORTS_OFF) {
473: showhelp("Port display OFF");
474: }
475: else if(options.showports == OPTION_PORTS_DEST) {
476: showhelp("Port display DEST");
477: }
478: else if(options.showports == OPTION_PORTS_SRC) {
479: showhelp("Port display SOURCE");
480: }
481: }
482:
483:
484: void ui_loop() {
485: /* in edline.c */
486: char *edline(int linenum, const char *prompt, const char *initial);
487: /* in iftop.c */
488: char *set_filter_code(const char *filter);
489:
490: extern sig_atomic_t foad;
491:
492: while(foad == 0) {
493: int i;
494: i = getch();
495: switch (i) {
496: case 'q':
497: foad = 1;
498: break;
499:
500: case 'n':
501: if(options.dnsresolution) {
502: options.dnsresolution = 0;
503: showhelp("DNS resolution off");
504: }
505: else {
506: options.dnsresolution = 1;
507: showhelp("DNS resolution on");
508: }
509: tick(1);
510: break;
511:
512: case 'N':
513: if(options.portresolution) {
514: options.portresolution = 0;
515: showhelp("Port resolution off");
516: }
517: else {
518: options.portresolution = 1;
519: showhelp("Port resolution on");
520: }
521: tick(1);
522: break;
523:
524: case 'h':
525: case '?':
526: options.showhelp = !options.showhelp;
527: tick(1);
528: break;
529:
530: case 'b':
531: if(options.showbars) {
532: options.showbars = 0;
533: showhelp("Bars off");
534: }
535: else {
536: options.showbars = 1;
537: showhelp("Bars on");
538: }
539: tick(1);
540: break;
541:
542: case 'B':
543: options.bar_interval = (options.bar_interval + 1) % 3;
544: if(options.bar_interval == 0) {
545: showhelp("Bars show 2s average");
546: }
547: else if(options.bar_interval == 1) {
548: showhelp("Bars show 10s average");
549: }
550: else {
551: showhelp("Bars show 40s average");
552: }
553: ui_print();
554: break;
555: case 's':
556: if(options.aggregate_src) {
557: options.aggregate_src = 0;
558: showhelp("Show source host");
559: }
560: else {
561: options.aggregate_src = 1;
562: showhelp("Hide source host");
563: }
564: break;
565: case 'd':
566: if(options.aggregate_dest) {
567: options.aggregate_dest = 0;
568: showhelp("Show dest host");
569: }
570: else {
571: options.aggregate_dest = 1;
572: showhelp("Hide dest host");
573: }
574: break;
575: case 'S':
576: /* Show source ports */
577: if(options.showports == OPTION_PORTS_OFF) {
578: options.showports = OPTION_PORTS_SRC;
579: }
580: else if(options.showports == OPTION_PORTS_DEST) {
581: options.showports = OPTION_PORTS_ON;
582: }
583: else if(options.showports == OPTION_PORTS_ON) {
584: options.showports = OPTION_PORTS_DEST;
585: }
586: else {
587: options.showports = OPTION_PORTS_OFF;
588: }
589: showportstatus();
590: break;
591: case 'D':
592: /* Show dest ports */
593: if(options.showports == OPTION_PORTS_OFF) {
594: options.showports = OPTION_PORTS_DEST;
595: }
596: else if(options.showports == OPTION_PORTS_SRC) {
597: options.showports = OPTION_PORTS_ON;
598: }
599: else if(options.showports == OPTION_PORTS_ON) {
600: options.showports = OPTION_PORTS_SRC;
601: }
602: else {
603: options.showports = OPTION_PORTS_OFF;
604: }
605: showportstatus();
606: break;
607: case 'p':
608: options.showports =
609: (options.showports == OPTION_PORTS_OFF)
610: ? OPTION_PORTS_ON
611: : OPTION_PORTS_OFF;
612: showportstatus();
613: // Don't tick here, otherwise we get a bogus display
614: break;
615: case 'P':
616: if(options.paused) {
617: options.paused = 0;
618: showhelp("Display unpaused");
619: }
620: else {
621: options.paused = 1;
622: showhelp("Display paused");
623: persistenthelp = 1;
624: }
625: break;
626: case 'o':
627: if(options.freezeorder) {
628: options.freezeorder = 0;
629: showhelp("Order unfrozen");
630: }
631: else {
632: options.freezeorder = 1;
633: showhelp("Order frozen");
634: persistenthelp = 1;
635: }
636: break;
637: case '1':
638: options.sort = OPTION_SORT_DIV1;
639: showhelp("Sort by col 1");
640: break;
641: case '2':
642: options.sort = OPTION_SORT_DIV2;
643: showhelp("Sort by col 2");
644: break;
645: case '3':
646: options.sort = OPTION_SORT_DIV3;
647: showhelp("Sort by col 3");
648: break;
649: case '<':
650: options.sort = OPTION_SORT_SRC;
651: showhelp("Sort by source");
652: break;
653: case '>':
654: options.sort = OPTION_SORT_DEST;
655: showhelp("Sort by dest");
656: break;
657: case 'j':
658: options.screen_offset++;
659: ui_print();
660: break;
661: case 'k':
662: if(options.screen_offset > 0) {
663: options.screen_offset--;
664: ui_print();
665: }
666: break;
667: case 't':
668: options.linedisplay = (options.linedisplay + 1) % 4;
669: switch(options.linedisplay) {
670: case OPTION_LINEDISPLAY_TWO_LINE:
671: showhelp("Two lines per host");
672: break;
673: case OPTION_LINEDISPLAY_ONE_LINE_SENT:
674: showhelp("Sent traffic only");
675: break;
676: case OPTION_LINEDISPLAY_ONE_LINE_RECV:
677: showhelp("Received traffic only");
678: break;
679: case OPTION_LINEDISPLAY_ONE_LINE_BOTH:
680: showhelp("One line per host");
681: break;
682: }
683: ui_print();
684: break;
685: case 'f': {
686: char *s;
687: dontshowdisplay = 1;
688: if ((s = edline(0, "Net filter", options.filtercode))) {
689: char *m;
690: if (s[strspn(s, " \t")] == 0) {
691: /* Empty filter; set to NULL. */
692: xfree(s);
693: s = NULL;
694: }
695: if (!(m = set_filter_code(s))) {
696: xfree(options.filtercode);
697: options.filtercode = s;
698: /* -lpcap will write junk to stderr; we do our best to
699: * erase it.... */
700: move(COLS - 1, LINES - 1);
701: wrefresh(curscr);
702: showhelp("Installed new filter");
703: } else {
704: showhelp(m);
705: xfree(s);
706: }
707: }
708: dontshowdisplay = 0;
709: ui_print();
710: break;
711: }
712: case 'l': {
713: #ifdef HAVE_REGCOMP
714: char *s;
715: dontshowdisplay = 1;
716: if ((s = edline(0, "Screen filter", options.screenfilter))) {
717: if(!screen_filter_set(s)) {
718: showhelp("Invalid regexp");
719: }
720: }
721: dontshowdisplay = 0;
722: ui_print();
723: #else
724: showhelp("Sorry, screen filters not supported on this platform")
725: #endif
726: break;
727: }
728: case '!': {
1.1.1.2 ! misho 729: #ifdef ALLOW_SUBSHELL
1.1 misho 730: char *s;
731: dontshowdisplay = 1;
732: if ((s = edline(0, "Command", "")) && s[strspn(s, " \t")]) {
733: int i, dowait = 0;
734: erase();
735: refresh();
736: endwin();
737: errno = 0;
738: i = system(s);
739: if (i == -1 || (i == 127 && errno != 0)) {
740: fprintf(stderr, "system: %s: %s\n", s, strerror(errno));
741: dowait = 1;
742: } else if (i != 0) {
743: if (WIFEXITED(i))
744: fprintf(stderr, "%s: exited with code %d\n", s, WEXITSTATUS(i));
745: else if (WIFSIGNALED(i))
746: fprintf(stderr, "%s: killed by signal %d\n", s, WTERMSIG(i));
747: dowait = 1;
748: }
749: ui_curses_init();
750: if (dowait) {
751: fprintf(stderr, "Press any key....");
752: while (getch() == ERR);
753: }
754: erase();
755: xfree(s);
756: }
757: dontshowdisplay = 0;
758: #else
759: showhelp("Sorry, subshells have been disabled.");
760: #endif
761: break;
762: }
763: case 'T':
764: options.show_totals = !options.show_totals;
765: if(options.show_totals) {
1.1.1.2 ! misho 766: showhelp("Show cumulative totals");
1.1 misho 767: }
768: else {
1.1.1.2 ! misho 769: showhelp("Hide cumulative totals");
1.1 misho 770: }
771: ui_print();
772: break;
773: case 'L':
774: options.log_scale = !options.log_scale;
775: showhelp(options.log_scale ? "Logarithmic scale" : "Linear scale");
776: ui_print();
777: break;
778: case KEY_CLEAR:
779: case 12: /* ^L */
780: wrefresh(curscr);
781: break;
782: case ERR:
783: break;
784: default:
785: showhelp("Press H or ? for help");
786: break;
787: }
788: tick(0);
789: }
790: }
791:
792: void ui_finish() {
793: endwin();
794: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>