]> mj.ucw.cz Git - checkmail.git/blob - cm.c
3dd7f57074321495a8b1dd92da1e806b9a401d21
[checkmail.git] / cm.c
1 /*
2  *      Incoming Mail Checker
3  *
4  *      (c) 2005 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <ctype.h>
11 #include <getopt.h>
12 #include <fcntl.h>
13 #include <glob.h>
14 #include <fnmatch.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #include <pwd.h>
18 #include <time.h>
19 #include <curses.h>
20
21 #include "util.h"
22 #include "clists.h"
23
24 static int check_interval = 30;
25 static int force_refresh;
26 static int minimum_priority;
27 static time_t last_scan_time;
28 static char *run_cmd = "mutt -f %s";
29
30 struct options {
31   int priority;
32   int hide;
33   int hide_if_empty;
34   int highlight;
35   int beep;
36   int snippets;
37 };
38
39 struct option_node {
40   cnode n;
41   struct options o;
42   char pattern[1];
43 };
44
45 struct pattern_node {
46   cnode n;
47   char *name;
48   char pattern[1];
49 };
50
51 static clist options, patterns;
52 static struct options global_options;
53
54 struct mbox {
55   cnode n;
56   struct options o;
57   char *name;
58   char *path;
59   int index;
60   int scanning;
61   int seen;
62   time_t last_time;
63   int last_size, last_pos;
64   int total, new;
65   int last_total, last_new;
66   int last_beep_new;
67   int force_refresh;
68   char snippet[256];
69 };
70
71 static clist mboxes;
72 static struct mbox **mbox_array;
73 static int num_mboxes, mbox_array_size;
74
75 static void redraw_line(int i);
76 static void rethink_display(void);
77
78 static void
79 add_pattern(char *patt)
80 {
81   struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
82   strcpy(n->pattern, patt);
83   if (patt = strchr(n->pattern, '='))
84     {
85       *patt++ = 0;
86       n->name = patt;
87     }
88   else
89     n->name = NULL;
90   clist_add_tail(&patterns, &n->n);
91 }
92
93 static void
94 add_inbox(void)
95 {
96   struct passwd *p = getpwuid(getuid());
97   if (!p)
98     die("You don't exist, go away!");
99   char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7];
100   sprintf(buf, "/var/mail/%s=INBOX", p->pw_name);
101   add_pattern(buf);
102 }
103
104 static void
105 init_options(struct options *o)
106 {
107   o->priority = -1;
108   o->hide = -1;
109   o->hide_if_empty = -1;
110   o->beep = -1;
111   o->highlight = -1;
112   o->snippets = -1;
113 }
114
115 static void
116 setup_options(struct mbox *b)
117 {
118   b->o = global_options;
119   CLIST_FOR_EACH(struct option_node *, n, options)
120     if (!fnmatch(n->pattern, b->name, 0))
121       {
122         debug("\tApplied options %s\n", n->pattern);
123 #define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
124         MERGE(priority);
125         MERGE(hide);
126         MERGE(hide_if_empty);
127         MERGE(highlight);
128         MERGE(beep);
129         MERGE(snippets);
130       }
131 }
132
133 static char *
134 mbox_name(char *path)
135 {
136   char *c = strrchr(path, '/');
137   return c ? (c+1) : path;
138 }
139
140 static struct mbox *
141 add_mbox(clist *l, char *path, char *name)
142 {
143   struct mbox *b = xmalloc(sizeof(*b));
144   bzero(b, sizeof(*b));
145   b->path = xstrdup(path);
146   b->name = xstrdup(name);
147
148   if (name)
149     {
150       cnode *prev = l->head.prev;
151       while (prev != &l->head && strcmp(((struct mbox *)prev)->name, name) > 0)
152         prev = prev->prev;
153       clist_insert_after(&b->n, prev);
154     }
155   else
156     clist_add_tail(l, &b->n);
157
158   return b;
159 }
160
161 static void
162 del_mbox(struct mbox *b)
163 {
164   clist_remove(&b->n);
165   free(b->path);
166   free(b->name);
167   free(b);
168 }
169
170 static struct mbox *
171 find_mbox(clist *l, char *path)
172 {
173   CLIST_FOR_EACH(struct mbox *, b, *l)
174     if (!strcmp(b->path, path))
175       return b;
176   return NULL;
177 }
178
179 static inline int
180 mbox_active_p(struct mbox *b)
181 {
182   if (b->o.priority < minimum_priority)
183     return 0;
184   if (b->o.hide)
185     return 0;
186   return 1;
187 }
188
189 static inline int
190 mbox_visible_p(struct mbox *b)
191 {
192   if (!mbox_active_p(b))
193     return 0;
194   if (b->scanning < 0)
195     return 1;
196   if (b->o.hide_if_empty && !b->total)
197     return 0;
198   return 1;
199 }
200
201 static void
202 add_snippet(char **ppos, char *term, char *add)
203 {
204   char *pos = *ppos;
205   while (*add && pos < term)
206     *pos++ = *add++;
207   *ppos = pos;
208 }
209
210 static void
211 prepare_snippet(struct mbox *b, char *sender, char *subject)
212 {
213   while (*sender == ' ' || *sender == '\t')
214     sender++;
215   while (*subject == ' ' || *subject == '\t')
216     subject++;
217
218   char *pos = b->snippet;
219   char *term = b->snippet + sizeof(b->snippet) - 1;
220   if (sender[0])
221     {
222       add_snippet(&pos, term, sender);
223       add_snippet(&pos, term, ": ");
224     }
225   if (subject[0])
226     add_snippet(&pos, term, subject);
227   else
228     add_snippet(&pos, term, "No subject");
229 }
230
231 static int mb_fd, mb_pos;
232 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
233
234 static void
235 mb_reset(int pos)
236 {
237   mb_cc = mb_end = mb_buf;
238   mb_pos = pos;
239 }
240
241 static int
242 mb_seek(uns pos)
243 {
244   lseek(mb_fd, pos, SEEK_SET);
245   mb_reset(pos);
246 }
247
248 static int
249 mb_tell(void)
250 {
251   return mb_pos - (mb_end - mb_cc);
252 }
253
254 static int
255 mb_ll_get(void)
256 {
257   int len = read(mb_fd, mb_buf, sizeof(mb_buf));
258   mb_cc = mb_buf;
259   if (len <= 0)
260     {
261       mb_end = mb_buf;
262       return -1;
263     }
264   else
265     {
266       mb_end = mb_buf + len;
267       mb_pos += len;
268       return *mb_cc++;
269     }
270 }
271
272 static inline int
273 mb_get(void)
274 {
275   return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
276 }
277
278 static int
279 mb_check(const char *p, int len)
280 {
281   while (len--)
282     {
283       if (mb_get() != *p++)
284         return 0;
285     }
286   return 1;
287 }
288
289 static void
290 scan_mbox(struct mbox *b, struct stat *st)
291 {
292   char buf[1024], sender[1024], subject[1024];
293   int c;
294   const char from[] = "\nFrom ";
295
296   if (!st->st_size)
297     {
298       b->total = b->new = 0;
299       b->last_pos = 0;
300       return;
301     }
302
303   /* FIXME: Locking! */
304
305   mb_fd = open(b->path, O_RDONLY);
306   if (mb_fd < 0)
307     {
308       debug("[open failed: %m] ");
309       b->total = b->new = -1;
310       return;
311     }
312   mb_reset(0);
313
314   int incremental = 0;
315   if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh)
316     {
317       mb_seek(b->last_pos);
318       if (mb_check(from, 6))
319         {
320           debug("[incremental] ");
321           incremental = 1;
322         }
323       else
324         {
325           debug("[incremental failed] ");
326           mb_seek(0);
327         }
328     }
329   if (!incremental)
330     {
331       if (!mb_check(from+1, 5))
332         {
333           debug("[inconsistent] ");
334           b->total = b->new = -1;
335           goto done;
336         }
337       b->total = b->new = 0;
338       b->last_total = b->last_new = 0;
339     }
340   else
341     {
342       b->total = b->last_total;
343       b->new = b->last_new;
344     }
345
346   for(;;)
347     {
348       b->last_pos = mb_tell() - 5;
349       if (b->last_pos)
350         b->last_pos--;          // last_pos should be the previous \n character
351       b->last_total = b->total;
352       b->last_new = b->new;
353       while ((c = mb_get()) >= 0 && c != '\n')
354         ;
355
356       int new = 1;
357       sender[0] = 0;
358       subject[0] = 0;
359       for (;;)
360         {
361           uns i = 0;
362           for (;;)
363             {
364               c = mb_get();
365               if (c < 0)
366                 {
367                   debug("[truncated] ");
368                   goto done;
369                 }
370               if (c == '\n')
371                 break;
372               if (c == '\r')
373                 continue;
374               if (i < sizeof(buf) - 1)
375                 buf[i++] = c;
376             }
377           buf[i] = 0;
378           if (!buf[0])
379             break;
380           if (!strncasecmp(buf, "Status:", 7))
381             new = 0;
382           else if (!strncasecmp(buf, "From:", 5))
383             strcpy(sender, buf+5);
384           else if (!strncasecmp(buf, "Subject:", 8))
385             strcpy(subject, buf+8);
386         }
387
388       b->total++;
389       if (new)
390         {
391           b->new++;
392           prepare_snippet(b, sender, subject);
393         }
394
395       int ct = 1;
396       while (from[ct])
397         {
398           c = mb_get();
399           if (c < 0)
400             goto done;
401           if (c != from[ct++])
402             ct = (c == '\n');
403         }
404     }
405
406  done:
407   close(mb_fd);
408 }
409
410 static void
411 scan(void)
412 {
413   debug("Searching for mailboxes...\n");
414   last_scan_time = time(NULL);
415   CLIST_FOR_EACH(struct pattern_node *, p, patterns)
416     {
417       debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
418       glob_t g;
419       int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
420       if (err && err != GLOB_NOMATCH)
421         die("Failed to glob %s: %m", p->pattern);
422       for (uns i=0; i<g.gl_pathc; i++)
423         {
424           char *name = g.gl_pathv[i];
425           struct mbox *b = find_mbox(&mboxes, name);
426           if (!b)
427             {
428               b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
429               debug("Discovered mailbox %s (%s)\n", b->name, b->path);
430               setup_options(b);
431               b->scanning = -1;
432             }
433           b->seen = 1;
434         }
435       globfree(&g);
436     }
437
438   struct mbox *tmp;
439   CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
440     {
441       if (b->seen)
442         b->seen = 0;
443       else
444         {
445           debug("Lost mailbox %s\n", b->name);
446           del_mbox(b);
447         }
448     }
449
450   rethink_display();
451
452   debug("Scanning mailboxes...\n");
453   CLIST_FOR_EACH(struct mbox *, b, mboxes)
454     {
455       struct stat st;
456       debug("%s: ", b->name);
457       if (!mbox_active_p(b))
458         {
459           debug("inactive\n");
460           continue;
461         }
462       if (force_refresh)
463         b->force_refresh = 1;
464       if (stat(b->path, &st) < 0)
465         {
466           b->total = b->new = -1;
467           debug("%m\n");
468         }
469       else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
470         {
471           b->scanning = 1;
472           redraw_line(b->index);
473           refresh();
474
475           scan_mbox(b, &st);
476           b->last_time = st.st_mtime;
477           b->last_size = st.st_size;
478           debug("%d %d (stopped at %d of %d)\n", b->total, b->new, b->last_pos, b->last_size);
479
480           b->scanning = 0;
481           redraw_line(b->index);
482           refresh();
483         }
484       else
485         debug("not changed\n");
486       b->force_refresh = 0;
487     }
488   force_refresh = 0;
489
490   debug("Scan finished\n");
491   last_scan_time = time(NULL);
492   rethink_display();
493 }
494
495 static int cursor_at, cursor_max;
496
497 enum {
498   M_IDLE,
499   M_SCAN,
500   M_NEW,
501   M_BAD,
502   M_MAX
503 };
504 static int attrs[2][2][M_MAX];          // active, hilite, status
505
506 static void
507 redraw_line(int i)
508 {
509   move(i, 0);
510   if (i < cursor_max)
511     {
512       struct mbox *b = mbox_array[i];
513       int cc = (cursor_at == i);
514       int hi = b->o.highlight;
515
516       attrset(attrs[cc][hi][M_IDLE]);
517       if (cc)
518         printw("> ");
519       else
520         printw("  ");
521       if (b->new)
522         attrset(attrs[cc][hi][M_NEW]);
523       printw("%-20s ", b->name);
524       if (b->scanning < 0)
525         ;
526       else if (b->scanning)
527         {
528           attrset(attrs[cc][hi][M_SCAN]);
529           printw("[SCANNING]");
530         }
531       else if (b->total < 0)
532         {
533           attrset(attrs[cc][hi][M_BAD]);
534           printw("BROKEN");
535         }
536       else
537         {
538           attrset(attrs[cc][hi][M_IDLE]);
539           printw("%6d ", b->total);
540           if (b->new)
541             {
542               attrset(attrs[cc][hi][M_NEW]);
543               printw("%6d  ", b->new);
544               attrset(attrs[cc][hi][M_IDLE]);
545               int age = (last_scan_time - b->last_time);
546               if (age < 0)
547                 age = 0;
548               if (age < 3600)
549                 printw("%2d min  ", age/60);
550               else if (age < 86400)
551                 printw("%2d hrs  ", age/3600);
552               else
553                 printw("        ");
554               if (b->o.snippets && b->snippet[0])
555                 {
556                   int xx, yy;
557                   getyx(stdscr, yy, xx);
558                   int remains = COLS-1-xx;
559                   if (remains > 2)
560                     printw("%-.*s", remains, b->snippet);
561                 }
562             }
563         }
564     }
565   attrset(attrs[0][0][M_IDLE]);
566   clrtoeol();
567 }
568
569 static void
570 redraw_all(void)
571 {
572   cursor_max = num_mboxes;
573   if (cursor_max > LINES-1)
574     cursor_max = LINES-1;
575   if (cursor_at >= cursor_max)
576     cursor_at = cursor_max - 1;
577   if (cursor_at < 0)
578     cursor_at = 0;
579
580   for (int i=0; i<cursor_max; i++)
581     redraw_line(i);
582   move(cursor_max, 0);
583   if (!cursor_max)
584     {
585       printw("(no mailboxes found)");
586       clrtoeol();
587       move(1, 0);
588     }
589   clrtobot();
590 }
591
592 static void
593 rethink_display(void)
594 {
595   int i = 0;
596   int changed = 0;
597   int beeeep = 0;
598   CLIST_FOR_EACH(struct mbox *, b, mboxes)
599     if (mbox_visible_p(b))
600       {
601         b->index = i;
602         if (i >= num_mboxes || mbox_array[i] != b)
603           {
604             changed = 1;
605             if (i >= mbox_array_size)
606               {
607                 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
608                 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
609               }
610             mbox_array[i] = b;
611           }
612         if (b->o.beep && b->new > b->last_beep_new)
613           beeeep = 1;
614         b->last_beep_new = b->new;
615         i++;
616       }
617   if (i != num_mboxes)
618     changed = 1;
619   num_mboxes = i;
620
621   if (changed)
622     {
623       redraw_all();
624       refresh();
625     }
626   if (beeeep)
627     beep();
628 }
629
630 static void
631 term_init(void)
632 {
633   initscr();
634   cbreak();
635   noecho();
636   nonl();
637   intrflush(stdscr, FALSE);
638   keypad(stdscr, TRUE);
639   curs_set(0);
640
641   static const int attrs_mono[2][M_MAX] = {
642     [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_BAD] = A_DIM },
643     [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_BAD] = A_DIM },
644   };
645   for (int i=0; i<2; i++)
646     for (int j=0; j<M_MAX; j++)
647       {
648         attrs[0][i][j] = attrs_mono[i][j];
649         attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
650       }
651
652   if (has_colors())
653     {
654       start_color();
655       if (COLOR_PAIRS >= 5)
656         {
657           init_pair(1, COLOR_YELLOW, COLOR_BLACK);
658           init_pair(2, COLOR_RED, COLOR_BLACK);
659           init_pair(3, COLOR_WHITE, COLOR_BLUE);
660           init_pair(4, COLOR_YELLOW, COLOR_BLUE);
661           init_pair(5, COLOR_RED, COLOR_BLUE);
662           static const int attrs_color[2][2][M_MAX] = {
663             [0][0] = { [M_IDLE] = 0, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1), [M_BAD] = COLOR_PAIR(2) },
664             [0][1] = { [M_IDLE] = A_BOLD, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1) | A_BOLD, [M_BAD] = COLOR_PAIR(2) | A_BOLD },
665             [1][0] = { [M_IDLE] = COLOR_PAIR(3), [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4), [M_BAD] = COLOR_PAIR(5) },
666             [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD, [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4) | A_BOLD, [M_BAD] = COLOR_PAIR(5) | A_BOLD },
667           };
668           memcpy(attrs, attrs_color, sizeof(attrs));
669         }
670     }
671 }
672
673 static void
674 term_cleanup(void)
675 {
676   endwin();
677 }
678
679 static void
680 scan_and_redraw(void)
681 {
682   move(LINES-1, 0);
683   printw("Busy...");
684   refresh();
685   scan();
686   move(LINES-1, 0);
687   clrtoeol();
688   refresh();
689 }
690
691 static void
692 move_cursor(int i)
693 {
694   if (i >= 0 && i < cursor_max && i != cursor_at)
695     {
696       int old = cursor_at;
697       cursor_at = i;
698       redraw_line(old);
699       redraw_line(i);
700     }
701 }
702
703 static void
704 next_active(int since, int step)
705 {
706   if (!cursor_max)
707     return;
708   since = (since+cursor_max) % cursor_max;
709   step = (step+cursor_max) % cursor_max;
710   int besti = -1;
711   int bestp = -1;
712   int i = since;
713   do
714     {
715       struct mbox *b = mbox_array[i];
716       if (b->new && b->o.priority > bestp)
717         {
718           besti = i;
719           bestp = b->o.priority;
720         }
721       i = (i+step) % cursor_max;
722     }
723   while (i != since);
724   if (besti >= 0)
725     move_cursor(besti);
726 }
727
728 #define STR2(c) #c
729 #define STR(c) STR2(c)
730
731 static void NONRET
732 usage(void)
733 {
734   fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
735 \n\
736 Options:\n\
737 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
738 -d\t\t\tLog debug messages to stderr\n\
739 -i\t\t\tInclude user's INBOX\n\
740 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
741 -o <pattern>=<opts>\tSet mailbox options\n\
742 -o <opts>\t\tSet default options for all mailboxes\n\
743 -p <pri>\t\tSet minimum priority to show\n\
744 \n\
745 Mailbox options (set with `-o', use upper case to negate):\n\
746 0-9\t\t\tSet mailbox priority (0=default)\n\
747 b\t\t\tBeep when a message arrives\n\
748 e\t\t\tHide from display if empty\n\
749 h\t\t\tHide from display\n\
750 s\t\t\tShow message snippets\n\
751 t\t\t\tHighlight the entry\n\
752 \n\
753 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
754 It can be freely distributed and used according to the GNU GPL v2.\n\
755 ");
756   exit(1);
757 }
758
759 static void
760 parse_options(char *c)
761 {
762   struct options *o;
763   char *sep;
764   if (sep = strchr(c, '='))
765     {
766       struct option_node *n = xmalloc(sizeof(*n) + sep-c);
767       memcpy(n->pattern, c, sep-c);
768       n->pattern[sep-c] = 0;
769       clist_add_tail(&options, &n->n);
770       o = &n->o;
771       init_options(o);
772       c = sep+1;
773     }
774   else
775     o = &global_options;
776
777   int x;
778   while (x = *c++)
779     if (x >= '0' && x <= '9')
780       o->priority = x - '0';
781     else
782       {
783         int value = !!islower(x);
784         switch (tolower(x))
785           {
786           case 'b':
787             o->beep = value;
788             break;
789           case 'e':
790             o->hide_if_empty = value;
791             break;
792           case 'h':
793             o->hide = value;
794             break;
795           case 's':
796             o->snippets = value;
797             break;
798           case 't':
799             o->highlight = value;
800             break;
801           default:
802             fprintf(stderr, "Invalid mailbox option `%c'\n", x);
803             usage();
804           }
805       }
806 }
807
808 int
809 main(int argc, char **argv)
810 {
811   clist_init(&mboxes);
812   clist_init(&options);
813   clist_init(&patterns);
814
815   int c;
816   while ((c = getopt(argc, argv, "c:dim:o:p:")) >= 0)
817     switch (c)
818       {
819       case 'c':
820         check_interval = atol(optarg);
821         if (check_interval <= 0)
822           usage();
823         break;
824       case 'd':
825         debug_mode++;
826         break;
827       case 'i':
828         add_inbox();
829         break;
830       case 'm':
831         run_cmd = optarg;
832         break;
833       case 'o':
834         parse_options(optarg);
835         break;
836       case 'p':
837         minimum_priority = atol(optarg);
838         break;
839       default:
840         usage();
841       }
842   while (optind < argc)
843     add_pattern(argv[optind++]);
844
845   term_init();
846   scan_and_redraw();
847   next_active(0, 1);
848
849   int should_exit = 0;
850   while (!should_exit)
851     {
852       time_t now = time(NULL);
853       int remains = last_scan_time + check_interval - now;
854       if (remains <= 0 || force_refresh)
855         scan_and_redraw();
856       else
857         {
858           remains *= 10;
859           halfdelay((remains > 255) ? 255 : remains);
860           int ch = getch();
861           switch (ch)
862             {
863             case 'q':
864               should_exit = 1;
865               break;
866             case 'j':
867             case KEY_DOWN:
868               move_cursor(cursor_at+1);
869               break;
870             case 'k':
871             case KEY_UP:
872               move_cursor(cursor_at-1);
873               break;
874             case '^':
875             case KEY_HOME:
876             case KEY_PPAGE:
877               move_cursor(0);
878               break;
879             case '$':
880             case KEY_END:
881             case KEY_NPAGE:
882               move_cursor(cursor_max-1);
883               break;
884             case '\t':
885               next_active(cursor_at+1, 1);
886               break;
887             case '`':
888               next_active(cursor_at-1, -1);
889               break;
890             case '\r':
891             case '\n':
892               if (cursor_at < cursor_max)
893                 {
894                   struct mbox *b = mbox_array[cursor_at];
895                   char cmd[strlen(run_cmd) + strlen(b->path) + 16];
896                   sprintf(cmd, run_cmd, b->path);
897                   term_cleanup();
898                   system(cmd);
899                   term_init();
900                   redraw_all();
901                   refresh();
902                   b->force_refresh = 1;
903                   scan_and_redraw();
904                 }
905               break;
906             case 'r' & 0x1f:
907               force_refresh = 1;
908               break;
909             default:
910               if (ch >= '0' && ch <= '9')
911                 {
912                   minimum_priority = ch - '0';
913                   scan_and_redraw();
914                 }
915               else
916                 debug("Pressed unknown key %d\n", ch);
917             }
918           refresh();
919         }
920     }
921
922   term_cleanup();
923   return 0;
924 }