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