]> gitweb.fperrin.net Git - iftop.git/blob - ui.c
Do traffic accounting in pps
[iftop.git] / ui.c
1 /*
2  * ui.c:
3  *
4  */
5
6 #include "config.h"
7
8 #include <sys/types.h>
9
10 #include <ctype.h>
11 #include <ncurses.h>
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
31 #include "ui_common.h"
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"\
41 "                                        T - toggle cumulative line totals\n"\
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"\
54 "iftop, version " PACKAGE_VERSION
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
86 static int rateidx_init = 0;
87
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;
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     }
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 */
156             readable_size(i, s, sizeof s, options.log_scale ? 1000 : 1024, options.bandwidth_unit);
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, option_bw_unit_t unit) {
181     char buf[10];
182     float n = 0;
183     switch(linedisplay) {
184         case OPTION_LINEDISPLAY_TWO_LINE:
185           draw_line_total(sent, recv, y, x, OPTION_LINEDISPLAY_ONE_LINE_SENT, unit);
186           draw_line_total(sent, recv, y+1, x, OPTION_LINEDISPLAY_ONE_LINE_RECV, unit);
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, unit);
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_unit);
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_unit);
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     option_bw_unit_t cumunit;
266
267     if (dontshowdisplay)
268         return;
269
270     if (!line || lcols != COLS) {
271         xfree(line);
272         line = calloc(COLS + 1, 1);
273     }
274
275     /* 
276      * erase() is faster than clear().  Dunno why we switched to 
277      * clear() -pdw 24/10/02
278      */
279     erase();
280
281     draw_bar_scale(&y);
282
283     if(options.showhelp) {
284       mvaddstr(y,0,HELP_MESSAGE);
285     }
286     else {
287       int i = 0;
288
289       while(i < options.screen_offset && ((nn = sorted_list_next_item(&screen_list, nn)) != NULL)) {
290         i++;
291       }
292
293       /* Screen layout: we have 2 * HISTORY_DIVISIONS 6-character wide history
294        * items, and so can use COLS - 12 * HISTORY_DIVISIONS to print the two
295        * host names. */
296
297       if(i == 0 || nn != NULL) {
298         while((y < LINES - 5) && ((nn = sorted_list_next_item(&screen_list, nn)) != NULL)) {
299             int x = 0, L;
300
301
302             host_pair_line* screen_line = (host_pair_line*)nn->data;
303
304             if(y < LINES - 5) {
305                 L = (COLS - 8 * HISTORY_DIVISIONS - 4) / 2;
306                 if(options.show_totals) {
307                     L -= 4;    
308                 }
309                 if(L > HOSTNAME_LENGTH) {
310                     L = HOSTNAME_LENGTH;
311                 }
312
313                 sprint_host(host1, screen_line->ap.af,
314                             &(screen_line->ap.src6),
315                             screen_line->ap.src_port,
316                             screen_line->ap.protocol, L, options.aggregate_src);
317                 sprint_host(host2, screen_line->ap.af,
318                             &(screen_line->ap.dst6),
319                             screen_line->ap.dst_port,
320                             screen_line->ap.protocol, L, options.aggregate_dest);
321
322                 if(!screen_filter_match(host1) && !screen_filter_match(host2)) {
323                   continue;
324                 }
325
326                 mvaddstr(y, x, host1);
327                 x += L;
328
329                 switch(options.linedisplay) {
330                   case OPTION_LINEDISPLAY_TWO_LINE:
331                     mvaddstr(y, x, " => ");
332                     mvaddstr(y+1, x, " <= ");
333                     break;
334                   case OPTION_LINEDISPLAY_ONE_LINE_BOTH:
335                     mvaddstr(y, x, "<=> ");
336                     break;
337                   case OPTION_LINEDISPLAY_ONE_LINE_SENT:
338                     mvaddstr(y, x, " => ");
339                     break;
340                   case OPTION_LINEDISPLAY_ONE_LINE_RECV:
341                     mvaddstr(y, x, " <= ");
342                     break;
343                 }
344
345                 x += 4;
346
347
348                 mvaddstr(y, x, host2);
349                 
350                 if(options.show_totals) {
351                     draw_line_total(screen_line->total_sent, screen_line->total_recv, y, COLS - 8 * (HISTORY_DIVISIONS + 1), options.linedisplay, 1);
352                 }
353
354                 draw_line_totals(y, screen_line, options.linedisplay);
355
356             }
357             if(options.linedisplay == OPTION_LINEDISPLAY_TWO_LINE) {
358               y += 2;
359             }
360             else {
361               y += 1;
362             }
363         }
364       }
365     }
366
367
368     y = LINES - 3;
369     
370     mvhline(y-1, 0, 0, COLS);
371
372     mvaddstr(y, 0, "TX: ");
373     mvaddstr(y+1, 0, "RX: ");
374     mvaddstr(y+2, 0, "TOTAL: ");
375
376     /* Cummulative totals */
377     mvaddstr(y, 16, "cum: ");
378
379     /* Previous versions of iftop always displayed totals in bytes, even when
380        use-bytes = false. Stay compatible when the default unit hasn't been
381        changed. */
382     cumunit = options.bandwidth_unit;
383     if (cumunit == OPTION_BW_BITS)
384       cumunit = OPTION_BW_BYTES;
385     readable_size(history_totals.total_sent, line, 10, 1024, cumunit);
386     mvaddstr(y, 22, line);
387
388     readable_size(history_totals.total_recv, line, 10, 1024, cumunit);
389     mvaddstr(y+1, 22, line);
390
391     readable_size(history_totals.total_recv + history_totals.total_sent, line, 10, 1024, cumunit);
392     mvaddstr(y+2, 22, line);
393
394     /* peak traffic */
395     mvaddstr(y, 32, "peak: ");
396
397     readable_size(peaksent / RESOLUTION, line, 10, 1024, options.bandwidth_unit);
398     mvaddstr(y, 39, line);
399
400     readable_size(peakrecv / RESOLUTION, line, 10, 1024, options.bandwidth_unit);
401     mvaddstr(y+1, 39, line);
402
403     readable_size(peaktotal / RESOLUTION, line, 10, 1024, options.bandwidth_unit);
404     mvaddstr(y+2, 39, line);
405
406     mvaddstr(y, COLS - 8 * HISTORY_DIVISIONS - 8, "rates:");
407
408     draw_totals(&totals);
409
410
411     if(showhelphint) {
412       mvaddstr(0, 0, " ");
413       mvaddstr(0, 1, helpmsg);
414       mvaddstr(0, 1 + strlen(helpmsg), " ");
415       mvchgat(0, 0, strlen(helpmsg) + 2, A_REVERSE, 0, NULL);
416     }
417     move(LINES - 1, COLS - 1);
418     
419     refresh();
420
421     /* Bar chart auto scale */
422     if (wantbiggerrate && options.max_bandwidth == 0) {
423       ++rateidx;
424       wantbiggerrate = 0;
425     }
426 }
427
428 void ui_tick(int print) {
429   if(print) {
430     ui_print();
431   }
432   else if(showhelphint && (time(NULL) - helptimer > HELP_TIME) && !persistenthelp) {
433     showhelphint = 0;
434     ui_print();
435   }
436 }
437
438 void ui_curses_init() {
439     (void) initscr();      /* initialize the curses library */
440     keypad(stdscr, TRUE);  /* enable keyboard mapping */
441     (void) nonl();         /* tell curses not to do NL->CR/NL on output */
442     (void) cbreak();       /* take input chars one at a time, no wait for \n */
443     (void) noecho();       /* don't echo input */
444     (void) curs_set(0);    /* hide blinking cursor in ui */
445     halfdelay(2);
446 }
447
448 void showhelp(const char * s) {
449   strncpy(helpmsg, s, HELP_MSG_SIZE);
450   showhelphint = 1;
451   helptimer = time(NULL);
452   persistenthelp = 0;
453   tick(1);
454 }
455
456 void ui_init() {
457     char msg[20];
458     ui_curses_init();
459     
460     erase();
461
462     screen_list_init();
463     screen_hash = addr_hash_create();
464
465     service_hash = serv_hash_create();
466     serv_hash_initialise(service_hash);
467
468     snprintf(msg,20,"Listening on %s",options.interface);
469     showhelp(msg);
470
471
472 }
473
474
475 void showportstatus() {
476   if(options.showports == OPTION_PORTS_ON) {
477     showhelp("Port display ON");
478   }
479   else if(options.showports == OPTION_PORTS_OFF) {
480     showhelp("Port display OFF");
481   }
482   else if(options.showports == OPTION_PORTS_DEST) {
483     showhelp("Port display DEST");
484   }
485   else if(options.showports == OPTION_PORTS_SRC) {
486     showhelp("Port display SOURCE");
487   }
488 }
489
490
491 void ui_loop() {
492     /* in edline.c */
493     char *edline(int linenum, const char *prompt, const char *initial);
494     /* in iftop.c */
495     char *set_filter_code(const char *filter);
496
497     extern sig_atomic_t foad;
498
499     while(foad == 0) {
500         int i;
501         i = getch();
502         switch (i) {
503             case 'q':
504                 foad = 1;
505                 break;
506
507             case 'n':
508                 if(options.dnsresolution) {
509                     options.dnsresolution = 0;
510                     showhelp("DNS resolution off");
511                 }
512                 else {
513                     options.dnsresolution = 1;
514                     showhelp("DNS resolution on");
515                 }
516                 tick(1);
517                 break;
518
519             case 'N':
520                 if(options.portresolution) {
521                     options.portresolution = 0;
522                     showhelp("Port resolution off");
523                 }
524                 else {
525                     options.portresolution = 1;
526                     showhelp("Port resolution on");
527                 }
528                 tick(1);
529                 break;
530
531             case 'h':
532             case '?':
533                 options.showhelp = !options.showhelp;
534                 tick(1);
535                 break;
536
537             case 'b':
538                 if(options.showbars) {
539                     options.showbars = 0;
540                     showhelp("Bars off");
541                 }
542                 else {
543                     options.showbars = 1;
544                     showhelp("Bars on");
545                 }
546                 tick(1);
547                 break;
548
549             case 'B':
550                 options.bar_interval = (options.bar_interval + 1) % 3;
551                 if(options.bar_interval == 0) {
552                     showhelp("Bars show 2s average");
553                 }
554                 else if(options.bar_interval == 1) { 
555                     showhelp("Bars show 10s average");
556                 }
557                 else {
558                     showhelp("Bars show 40s average");
559                 }
560                 ui_print();
561                 break;
562             case 's':
563                 if(options.aggregate_src) {
564                     options.aggregate_src = 0;
565                     showhelp("Show source host");
566                 }
567                 else {
568                     options.aggregate_src = 1;
569                     showhelp("Hide source host");
570                 }
571                 break;
572             case 'd':
573                 if(options.aggregate_dest) {
574                     options.aggregate_dest = 0;
575                     showhelp("Show dest host");
576                 }
577                 else {
578                     options.aggregate_dest = 1;
579                     showhelp("Hide dest host");
580                 }
581                 break;
582             case 'S':
583                 /* Show source ports */
584                 if(options.showports == OPTION_PORTS_OFF) {
585                   options.showports = OPTION_PORTS_SRC;
586                 }
587                 else if(options.showports == OPTION_PORTS_DEST) {
588                   options.showports = OPTION_PORTS_ON;
589                 }
590                 else if(options.showports == OPTION_PORTS_ON) {
591                   options.showports = OPTION_PORTS_DEST;
592                 }
593                 else {
594                   options.showports = OPTION_PORTS_OFF;
595                 }
596                 showportstatus();
597                 break;
598             case 'D':
599                 /* Show dest ports */
600                 if(options.showports == OPTION_PORTS_OFF) {
601                   options.showports = OPTION_PORTS_DEST;
602                 }
603                 else if(options.showports == OPTION_PORTS_SRC) {
604                   options.showports = OPTION_PORTS_ON;
605                 }
606                 else if(options.showports == OPTION_PORTS_ON) {
607                   options.showports = OPTION_PORTS_SRC;
608                 }
609                 else {
610                   options.showports = OPTION_PORTS_OFF;
611                 }
612                 showportstatus();
613                 break;
614             case 'p':
615                 options.showports = 
616                   (options.showports == OPTION_PORTS_OFF)
617                   ? OPTION_PORTS_ON
618                   : OPTION_PORTS_OFF;
619                 showportstatus();
620                 // Don't tick here, otherwise we get a bogus display
621                 break;
622             case 'P':
623                 if(options.paused) {
624                     options.paused = 0;
625                     showhelp("Display unpaused");
626                 }
627                 else {
628                     options.paused = 1;
629                     showhelp("Display paused");
630                     persistenthelp = 1;
631                 }
632                 break;
633             case 'o':
634                 if(options.freezeorder) {
635                     options.freezeorder = 0;
636                     showhelp("Order unfrozen");
637                 }
638                 else {
639                     options.freezeorder = 1;
640                     showhelp("Order frozen");
641                     persistenthelp = 1;
642                 }
643                 break;
644             case '1':
645                 options.sort = OPTION_SORT_DIV1;
646                 showhelp("Sort by col 1");
647                 break;
648             case '2':
649                 options.sort = OPTION_SORT_DIV2;
650                 showhelp("Sort by col 2");
651                 break;
652             case '3':
653                 options.sort = OPTION_SORT_DIV3;
654                 showhelp("Sort by col 3");
655                 break;
656             case '<':
657                 options.sort = OPTION_SORT_SRC;
658                 showhelp("Sort by source");
659                 break;
660             case '>':
661                 options.sort = OPTION_SORT_DEST;
662                 showhelp("Sort by dest");
663                 break;
664             case 'j':
665                 options.screen_offset++;
666                 ui_print();
667                 break;
668             case 'k':
669                 if(options.screen_offset > 0) {
670                   options.screen_offset--;
671                   ui_print();
672                 }
673                 break;
674             case 't':
675                 options.linedisplay = (options.linedisplay + 1) % 4;
676                 switch(options.linedisplay) {
677                   case OPTION_LINEDISPLAY_TWO_LINE:
678                     showhelp("Two lines per host");
679                     break;
680                   case OPTION_LINEDISPLAY_ONE_LINE_SENT:
681                     showhelp("Sent traffic only");
682                     break;
683                   case OPTION_LINEDISPLAY_ONE_LINE_RECV:
684                     showhelp("Received traffic only");
685                     break;
686                   case OPTION_LINEDISPLAY_ONE_LINE_BOTH:
687                     showhelp("One line per host");
688                     break;
689                 }
690                 ui_print();
691                 break;
692             case 'f': {
693                 char *s;
694                 dontshowdisplay = 1;
695                 if ((s = edline(0, "Net filter", options.filtercode))) {
696                     char *m;
697                     if (s[strspn(s, " \t")] == 0) {
698                         /* Empty filter; set to NULL. */
699                         xfree(s);
700                         s = NULL;
701                     }
702                     if (!(m = set_filter_code(s))) {
703                         xfree(options.filtercode);
704                         options.filtercode = s;
705                         /* -lpcap will write junk to stderr; we do our best to
706                          * erase it.... */
707                         move(COLS - 1, LINES - 1);
708                         wrefresh(curscr);
709                         showhelp("Installed new filter");
710                     } else {
711                         showhelp(m);
712                         xfree(s);
713                     }
714                 }
715                 dontshowdisplay = 0;
716                 ui_print();
717                 break;
718             }
719             case 'l': {
720 #ifdef HAVE_REGCOMP
721                 char *s;
722                 dontshowdisplay = 1;
723                 if ((s = edline(0, "Screen filter", options.screenfilter))) {
724                     if(!screen_filter_set(s)) {
725                         showhelp("Invalid regexp");
726                     }
727                 }
728                 dontshowdisplay = 0;
729                 ui_print();
730 #else
731                 showhelp("Sorry, screen filters not supported on this platform")
732 #endif
733                 break;
734             }
735             case '!': {
736 #ifdef ALLOW_SUBSHELL
737                 char *s;
738                 dontshowdisplay = 1;
739                 if ((s = edline(0, "Command", "")) && s[strspn(s, " \t")]) {
740                     int i, dowait = 0;
741                     erase();
742                     refresh();
743                     endwin();
744                     errno = 0;
745                     i = system(s);
746                     if (i == -1 || (i == 127 && errno != 0)) {
747                         fprintf(stderr, "system: %s: %s\n", s, strerror(errno));
748                         dowait = 1;
749                     } else if (i != 0) {
750                         if (WIFEXITED(i))
751                             fprintf(stderr, "%s: exited with code %d\n", s, WEXITSTATUS(i));
752                         else if (WIFSIGNALED(i))
753                             fprintf(stderr, "%s: killed by signal %d\n", s, WTERMSIG(i));
754                         dowait = 1;
755                     }
756                     ui_curses_init();
757                     if (dowait) {
758                         fprintf(stderr, "Press any key....");
759                         while (getch() == ERR);
760                     }
761                     erase();
762                     xfree(s);
763                 }
764                 dontshowdisplay = 0;
765 #else
766                 showhelp("Sorry, subshells have been disabled.");
767 #endif
768                 break;
769             }
770             case 'T':
771                 options.show_totals = !options.show_totals;
772                 if(options.show_totals) {
773                     showhelp("Show cumulative totals");
774                 }
775                 else {
776                     showhelp("Hide cumulative totals");
777                 }
778                 ui_print();
779                 break;
780             case 'L':
781                 options.log_scale = !options.log_scale;
782                 showhelp(options.log_scale ? "Logarithmic scale" : "Linear scale");
783                 ui_print();
784                 break;
785             case KEY_CLEAR:
786             case 12:    /* ^L */
787                 wrefresh(curscr);
788                 break;
789             case ERR:
790                 break;
791             default:
792                 showhelp("Press H or ? for help");
793                 break;
794         }
795         tick(0);
796     }
797 }
798
799 void ui_finish() {
800     endwin();
801 }