]> gitweb.fperrin.net Git - iftop.git/blob - ui.c
gitignore
[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     /* lost packets */
404     mvaddstr(y+2, 32, "lost: ");
405     readable_size(history_totals.lost_packets, line, 10, 1000, OPTION_BW_PKTS);
406     mvaddstr(y+2, 39, line);
407
408     mvaddstr(y, COLS - 8 * HISTORY_DIVISIONS - 8, "rates:");
409
410     draw_totals(&totals);
411
412
413     if(showhelphint) {
414       mvaddstr(0, 0, " ");
415       mvaddstr(0, 1, helpmsg);
416       mvaddstr(0, 1 + strlen(helpmsg), " ");
417       mvchgat(0, 0, strlen(helpmsg) + 2, A_REVERSE, 0, NULL);
418     }
419     move(LINES - 1, COLS - 1);
420     
421     refresh();
422
423     /* Bar chart auto scale */
424     if (wantbiggerrate && options.max_bandwidth == 0) {
425       ++rateidx;
426       wantbiggerrate = 0;
427     }
428 }
429
430 void ui_tick(int print) {
431   if(print) {
432     ui_print();
433   }
434   else if(showhelphint && (time(NULL) - helptimer > HELP_TIME) && !persistenthelp) {
435     showhelphint = 0;
436     ui_print();
437   }
438 }
439
440 void ui_curses_init() {
441     (void) initscr();      /* initialize the curses library */
442     keypad(stdscr, TRUE);  /* enable keyboard mapping */
443     (void) nonl();         /* tell curses not to do NL->CR/NL on output */
444     (void) cbreak();       /* take input chars one at a time, no wait for \n */
445     (void) noecho();       /* don't echo input */
446     (void) curs_set(0);    /* hide blinking cursor in ui */
447     halfdelay(2);
448 }
449
450 void showhelp(const char * s) {
451   strncpy(helpmsg, s, HELP_MSG_SIZE);
452   showhelphint = 1;
453   helptimer = time(NULL);
454   persistenthelp = 0;
455   tick(1);
456 }
457
458 void ui_init() {
459     char msg[20];
460     ui_curses_init();
461     
462     erase();
463
464     screen_list_init();
465     screen_hash = addr_hash_create();
466
467     service_hash = serv_hash_create();
468     serv_hash_initialise(service_hash);
469
470     snprintf(msg,20,"Listening on %s",options.interface);
471     showhelp(msg);
472
473
474 }
475
476
477 void showportstatus() {
478   if(options.showports == OPTION_PORTS_ON) {
479     showhelp("Port display ON");
480   }
481   else if(options.showports == OPTION_PORTS_OFF) {
482     showhelp("Port display OFF");
483   }
484   else if(options.showports == OPTION_PORTS_DEST) {
485     showhelp("Port display DEST");
486   }
487   else if(options.showports == OPTION_PORTS_SRC) {
488     showhelp("Port display SOURCE");
489   }
490 }
491
492
493 void ui_loop() {
494     /* in edline.c */
495     char *edline(int linenum, const char *prompt, const char *initial);
496     /* in iftop.c */
497     char *set_filter_code(const char *filter);
498
499     extern sig_atomic_t foad;
500
501     while(foad == 0) {
502         int i;
503         i = getch();
504         switch (i) {
505             case 'q':
506                 foad = 1;
507                 break;
508
509             case 'n':
510                 if(options.dnsresolution) {
511                     options.dnsresolution = 0;
512                     showhelp("DNS resolution off");
513                 }
514                 else {
515                     options.dnsresolution = 1;
516                     showhelp("DNS resolution on");
517                 }
518                 tick(1);
519                 break;
520
521             case 'N':
522                 if(options.portresolution) {
523                     options.portresolution = 0;
524                     showhelp("Port resolution off");
525                 }
526                 else {
527                     options.portresolution = 1;
528                     showhelp("Port resolution on");
529                 }
530                 tick(1);
531                 break;
532
533             case 'h':
534             case '?':
535                 options.showhelp = !options.showhelp;
536                 tick(1);
537                 break;
538
539             case 'b':
540                 if(options.showbars) {
541                     options.showbars = 0;
542                     showhelp("Bars off");
543                 }
544                 else {
545                     options.showbars = 1;
546                     showhelp("Bars on");
547                 }
548                 tick(1);
549                 break;
550
551             case 'B':
552                 options.bar_interval = (options.bar_interval + 1) % 3;
553                 if(options.bar_interval == 0) {
554                     showhelp("Bars show 2s average");
555                 }
556                 else if(options.bar_interval == 1) { 
557                     showhelp("Bars show 10s average");
558                 }
559                 else {
560                     showhelp("Bars show 40s average");
561                 }
562                 ui_print();
563                 break;
564             case 's':
565                 if(options.aggregate_src) {
566                     options.aggregate_src = 0;
567                     showhelp("Show source host");
568                 }
569                 else {
570                     options.aggregate_src = 1;
571                     showhelp("Hide source host");
572                 }
573                 break;
574             case 'd':
575                 if(options.aggregate_dest) {
576                     options.aggregate_dest = 0;
577                     showhelp("Show dest host");
578                 }
579                 else {
580                     options.aggregate_dest = 1;
581                     showhelp("Hide dest host");
582                 }
583                 break;
584             case 'S':
585                 /* Show source ports */
586                 if(options.showports == OPTION_PORTS_OFF) {
587                   options.showports = OPTION_PORTS_SRC;
588                 }
589                 else if(options.showports == OPTION_PORTS_DEST) {
590                   options.showports = OPTION_PORTS_ON;
591                 }
592                 else if(options.showports == OPTION_PORTS_ON) {
593                   options.showports = OPTION_PORTS_DEST;
594                 }
595                 else {
596                   options.showports = OPTION_PORTS_OFF;
597                 }
598                 showportstatus();
599                 break;
600             case 'D':
601                 /* Show dest ports */
602                 if(options.showports == OPTION_PORTS_OFF) {
603                   options.showports = OPTION_PORTS_DEST;
604                 }
605                 else if(options.showports == OPTION_PORTS_SRC) {
606                   options.showports = OPTION_PORTS_ON;
607                 }
608                 else if(options.showports == OPTION_PORTS_ON) {
609                   options.showports = OPTION_PORTS_SRC;
610                 }
611                 else {
612                   options.showports = OPTION_PORTS_OFF;
613                 }
614                 showportstatus();
615                 break;
616             case 'p':
617                 options.showports = 
618                   (options.showports == OPTION_PORTS_OFF)
619                   ? OPTION_PORTS_ON
620                   : OPTION_PORTS_OFF;
621                 showportstatus();
622                 // Don't tick here, otherwise we get a bogus display
623                 break;
624             case 'P':
625                 if(options.paused) {
626                     options.paused = 0;
627                     showhelp("Display unpaused");
628                 }
629                 else {
630                     options.paused = 1;
631                     showhelp("Display paused");
632                     persistenthelp = 1;
633                 }
634                 break;
635             case 'o':
636                 if(options.freezeorder) {
637                     options.freezeorder = 0;
638                     showhelp("Order unfrozen");
639                 }
640                 else {
641                     options.freezeorder = 1;
642                     showhelp("Order frozen");
643                     persistenthelp = 1;
644                 }
645                 break;
646             case '1':
647                 options.sort = OPTION_SORT_DIV1;
648                 showhelp("Sort by col 1");
649                 break;
650             case '2':
651                 options.sort = OPTION_SORT_DIV2;
652                 showhelp("Sort by col 2");
653                 break;
654             case '3':
655                 options.sort = OPTION_SORT_DIV3;
656                 showhelp("Sort by col 3");
657                 break;
658             case '<':
659                 options.sort = OPTION_SORT_SRC;
660                 showhelp("Sort by source");
661                 break;
662             case '>':
663                 options.sort = OPTION_SORT_DEST;
664                 showhelp("Sort by dest");
665                 break;
666             case 'j':
667                 options.screen_offset++;
668                 ui_print();
669                 break;
670             case 'k':
671                 if(options.screen_offset > 0) {
672                   options.screen_offset--;
673                   ui_print();
674                 }
675                 break;
676             case 't':
677                 options.linedisplay = (options.linedisplay + 1) % 4;
678                 switch(options.linedisplay) {
679                   case OPTION_LINEDISPLAY_TWO_LINE:
680                     showhelp("Two lines per host");
681                     break;
682                   case OPTION_LINEDISPLAY_ONE_LINE_SENT:
683                     showhelp("Sent traffic only");
684                     break;
685                   case OPTION_LINEDISPLAY_ONE_LINE_RECV:
686                     showhelp("Received traffic only");
687                     break;
688                   case OPTION_LINEDISPLAY_ONE_LINE_BOTH:
689                     showhelp("One line per host");
690                     break;
691                 }
692                 ui_print();
693                 break;
694             case 'f': {
695                 char *s;
696                 dontshowdisplay = 1;
697                 if ((s = edline(0, "Net filter", options.filtercode))) {
698                     char *m;
699                     if (s[strspn(s, " \t")] == 0) {
700                         /* Empty filter; set to NULL. */
701                         xfree(s);
702                         s = NULL;
703                     }
704                     if (!(m = set_filter_code(s))) {
705                         xfree(options.filtercode);
706                         options.filtercode = s;
707                         /* -lpcap will write junk to stderr; we do our best to
708                          * erase it.... */
709                         move(COLS - 1, LINES - 1);
710                         wrefresh(curscr);
711                         showhelp("Installed new filter");
712                     } else {
713                         showhelp(m);
714                         xfree(s);
715                     }
716                 }
717                 dontshowdisplay = 0;
718                 ui_print();
719                 break;
720             }
721             case 'l': {
722 #ifdef HAVE_REGCOMP
723                 char *s;
724                 dontshowdisplay = 1;
725                 if ((s = edline(0, "Screen filter", options.screenfilter))) {
726                     if(!screen_filter_set(s)) {
727                         showhelp("Invalid regexp");
728                     }
729                 }
730                 dontshowdisplay = 0;
731                 ui_print();
732 #else
733                 showhelp("Sorry, screen filters not supported on this platform")
734 #endif
735                 break;
736             }
737             case '!': {
738 #ifdef ALLOW_SUBSHELL
739                 char *s;
740                 dontshowdisplay = 1;
741                 if ((s = edline(0, "Command", "")) && s[strspn(s, " \t")]) {
742                     int i, dowait = 0;
743                     erase();
744                     refresh();
745                     endwin();
746                     errno = 0;
747                     i = system(s);
748                     if (i == -1 || (i == 127 && errno != 0)) {
749                         fprintf(stderr, "system: %s: %s\n", s, strerror(errno));
750                         dowait = 1;
751                     } else if (i != 0) {
752                         if (WIFEXITED(i))
753                             fprintf(stderr, "%s: exited with code %d\n", s, WEXITSTATUS(i));
754                         else if (WIFSIGNALED(i))
755                             fprintf(stderr, "%s: killed by signal %d\n", s, WTERMSIG(i));
756                         dowait = 1;
757                     }
758                     ui_curses_init();
759                     if (dowait) {
760                         fprintf(stderr, "Press any key....");
761                         while (getch() == ERR);
762                     }
763                     erase();
764                     xfree(s);
765                 }
766                 dontshowdisplay = 0;
767 #else
768                 showhelp("Sorry, subshells have been disabled.");
769 #endif
770                 break;
771             }
772             case 'T':
773                 options.show_totals = !options.show_totals;
774                 if(options.show_totals) {
775                     showhelp("Show cumulative totals");
776                 }
777                 else {
778                     showhelp("Hide cumulative totals");
779                 }
780                 ui_print();
781                 break;
782             case 'L':
783                 options.log_scale = !options.log_scale;
784                 showhelp(options.log_scale ? "Logarithmic scale" : "Linear scale");
785                 ui_print();
786                 break;
787             case KEY_CLEAR:
788             case 12:    /* ^L */
789                 wrefresh(curscr);
790                 break;
791             case ERR:
792                 break;
793             default:
794                 showhelp("Press H or ? for help");
795                 break;
796         }
797         tick(0);
798     }
799 }
800
801 void ui_finish() {
802     endwin();
803 }