]> mj.ucw.cz Git - checkmail.git/blob - cm.c
50f70717ebfe8f5320bec6b095122d3c474b5042
[checkmail.git] / cm.c
1 /*
2  *      Incoming Mail Checker
3  *
4  *      (c) 2005 Martin Mares <mj@ucw.cz>
5  */
6
7 #define VERSION "0.2"
8 #define YEAR "2005"
9
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <ctype.h>
14 #include <getopt.h>
15 #include <fcntl.h>
16 #include <glob.h>
17 #include <fnmatch.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <pwd.h>
21 #include <time.h>
22 #include <curses.h>
23
24 #include "util.h"
25 #include "clists.h"
26
27 static int check_interval = 30;
28 static int force_refresh;
29 static int minimum_priority;
30 static time_t last_scan_time;
31 static char *run_cmd = "mutt -f %s";
32
33 struct options {
34   int priority;
35   int hide;
36   int hide_if_empty;
37   int highlight;
38   int beep;
39   int snippets;
40 };
41
42 struct option_node {
43   cnode n;
44   struct options o;
45   char pattern[1];
46 };
47
48 struct pattern_node {
49   cnode n;
50   char *name;
51   char pattern[1];
52 };
53
54 static clist options, patterns;
55 static struct options global_options;
56
57 struct mbox {
58   cnode n;
59   struct options o;
60   char *name;
61   char *path;
62   int index;
63   int scanning;
64   int seen;
65   time_t last_time;
66   int last_size, last_pos;
67   int total, new;
68   int last_total, last_new;
69   int last_beep_new;
70   int force_refresh;
71   char snippet[256];
72 };
73
74 static clist mboxes;
75 static struct mbox **mbox_array;
76 static int num_mboxes, mbox_array_size;
77
78 static void redraw_line(int i);
79 static void rethink_display(void);
80
81 static void
82 add_pattern(char *patt)
83 {
84   struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
85   strcpy(n->pattern, patt);
86   if (patt = strchr(n->pattern, '='))
87     {
88       *patt++ = 0;
89       n->name = patt;
90     }
91   else
92     n->name = NULL;
93   clist_add_tail(&patterns, &n->n);
94 }
95
96 static void
97 add_inbox(void)
98 {
99   struct passwd *p = getpwuid(getuid());
100   if (!p)
101     die("You don't exist, go away!");
102   char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7];
103   sprintf(buf, "/var/mail/%s=INBOX", p->pw_name);
104   add_pattern(buf);
105 }
106
107 static void
108 init_options(struct options *o)
109 {
110   o->priority = -1;
111   o->hide = -1;
112   o->hide_if_empty = -1;
113   o->beep = -1;
114   o->highlight = -1;
115   o->snippets = -1;
116 }
117
118 static void
119 setup_options(struct mbox *b)
120 {
121   b->o = global_options;
122   CLIST_FOR_EACH(struct option_node *, n, options)
123     if (!fnmatch(n->pattern, b->name, 0))
124       {
125         debug("\tApplied options %s\n", n->pattern);
126 #define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
127         MERGE(priority);
128         MERGE(hide);
129         MERGE(hide_if_empty);
130         MERGE(highlight);
131         MERGE(beep);
132         MERGE(snippets);
133       }
134 }
135
136 static char *
137 mbox_name(char *path)
138 {
139   char *c = strrchr(path, '/');
140   return c ? (c+1) : path;
141 }
142
143 static struct mbox *
144 add_mbox(clist *l, char *path, char *name)
145 {
146   struct mbox *b = xmalloc(sizeof(*b));
147   bzero(b, sizeof(*b));
148   b->path = xstrdup(path);
149   b->name = xstrdup(name);
150
151   if (name)
152     {
153       cnode *prev = l->head.prev;
154       while (prev != &l->head && strcmp(((struct mbox *)prev)->name, name) > 0)
155         prev = prev->prev;
156       clist_insert_after(&b->n, prev);
157     }
158   else
159     clist_add_tail(l, &b->n);
160
161   return b;
162 }
163
164 static void
165 del_mbox(struct mbox *b)
166 {
167   clist_remove(&b->n);
168   free(b->path);
169   free(b->name);
170   free(b);
171 }
172
173 static struct mbox *
174 find_mbox(clist *l, char *path)
175 {
176   CLIST_FOR_EACH(struct mbox *, b, *l)
177     if (!strcmp(b->path, path))
178       return b;
179   return NULL;
180 }
181
182 static inline int
183 mbox_active_p(struct mbox *b)
184 {
185   if (b->o.priority < minimum_priority)
186     return 0;
187   if (b->o.hide)
188     return 0;
189   return 1;
190 }
191
192 static inline int
193 mbox_visible_p(struct mbox *b)
194 {
195   if (!mbox_active_p(b))
196     return 0;
197   if (b->scanning < 0)
198     return 1;
199   if (b->o.hide_if_empty && !b->total)
200     return 0;
201   return 1;
202 }
203
204 static void
205 add_snippet(char **ppos, char *term, char *add)
206 {
207   char *pos = *ppos;
208   while (*add && pos < term)
209     *pos++ = *add++;
210   *ppos = pos;
211 }
212
213 static void
214 prepare_snippet(struct mbox *b, char *sender, char *subject)
215 {
216   while (*sender == ' ' || *sender == '\t')
217     sender++;
218   while (*subject == ' ' || *subject == '\t')
219     subject++;
220
221   char *pos = b->snippet;
222   char *term = b->snippet + sizeof(b->snippet) - 1;
223   if (sender[0])
224     {
225       add_snippet(&pos, term, sender);
226       add_snippet(&pos, term, ": ");
227     }
228   if (subject[0])
229     add_snippet(&pos, term, subject);
230   else
231     add_snippet(&pos, term, "No subject");
232 }
233
234 static int mb_fd, mb_pos;
235 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
236
237 static void
238 mb_reset(int pos)
239 {
240   mb_cc = mb_end = mb_buf;
241   mb_pos = pos;
242 }
243
244 static int
245 mb_seek(uns pos)
246 {
247   lseek(mb_fd, pos, SEEK_SET);
248   mb_reset(pos);
249 }
250
251 static int
252 mb_tell(void)
253 {
254   return mb_pos - (mb_end - mb_cc);
255 }
256
257 static int
258 mb_ll_get(void)
259 {
260   int len = read(mb_fd, mb_buf, sizeof(mb_buf));
261   mb_cc = mb_buf;
262   if (len <= 0)
263     {
264       mb_end = mb_buf;
265       return -1;
266     }
267   else
268     {
269       mb_end = mb_buf + len;
270       mb_pos += len;
271       return *mb_cc++;
272     }
273 }
274
275 static inline int
276 mb_get(void)
277 {
278   return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
279 }
280
281 static int
282 mb_check(const char *p, int len)
283 {
284   while (len--)
285     {
286       if (mb_get() != *p++)
287         return 0;
288     }
289   return 1;
290 }
291
292 static void
293 scan_mbox(struct mbox *b, struct stat *st)
294 {
295   char buf[1024], sender[1024], subject[1024];
296   int c;
297   const char from[] = "\nFrom ";
298
299   if (!st->st_size)
300     {
301       b->total = b->new = 0;
302       b->last_pos = 0;
303       return;
304     }
305
306   /* FIXME: Locking! */
307
308   mb_fd = open(b->path, O_RDONLY);
309   if (mb_fd < 0)
310     {
311       debug("[open failed: %m] ");
312       b->total = b->new = -1;
313       return;
314     }
315   mb_reset(0);
316
317   int incremental = 0;
318   if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh)
319     {
320       mb_seek(b->last_pos);
321       if (mb_check(from, 6))
322         {
323           debug("[incremental] ");
324           incremental = 1;
325         }
326       else
327         {
328           debug("[incremental failed] ");
329           mb_seek(0);
330         }
331     }
332   if (!incremental)
333     {
334       if (!mb_check(from+1, 5))
335         {
336           debug("[inconsistent] ");
337           b->total = b->new = -1;
338           goto done;
339         }
340       b->total = b->new = 0;
341       b->last_total = b->last_new = 0;
342     }
343   else
344     {
345       b->total = b->last_total;
346       b->new = b->last_new;
347     }
348
349   for(;;)
350     {
351       b->last_pos = mb_tell() - 5;
352       if (b->last_pos)
353         b->last_pos--;          // last_pos should be the previous \n character
354       b->last_total = b->total;
355       b->last_new = b->new;
356       while ((c = mb_get()) >= 0 && c != '\n')
357         ;
358
359       int new = 1;
360       sender[0] = 0;
361       subject[0] = 0;
362       for (;;)
363         {
364           uns i = 0;
365           for (;;)
366             {
367               c = mb_get();
368               if (c < 0)
369                 {
370                   debug("[truncated] ");
371                   goto done;
372                 }
373               if (c == '\n')
374                 break;
375               if (c == '\r')
376                 continue;
377               if (i < sizeof(buf) - 1)
378                 buf[i++] = c;
379             }
380           buf[i] = 0;
381           if (!buf[0])
382             break;
383           if (!strncasecmp(buf, "Status:", 7))
384             new = 0;
385           else if (!strncasecmp(buf, "From:", 5))
386             strcpy(sender, buf+5);
387           else if (!strncasecmp(buf, "Subject:", 8))
388             strcpy(subject, buf+8);
389         }
390
391       b->total++;
392       if (new)
393         {
394           b->new++;
395           prepare_snippet(b, sender, subject);
396         }
397
398       int ct = 1;
399       while (from[ct])
400         {
401           c = mb_get();
402           if (c < 0)
403             goto done;
404           if (c != from[ct++])
405             ct = (c == '\n');
406         }
407     }
408
409  done:
410   close(mb_fd);
411 }
412
413 static void
414 scan(void)
415 {
416   debug("Searching for mailboxes...\n");
417   last_scan_time = time(NULL);
418   CLIST_FOR_EACH(struct pattern_node *, p, patterns)
419     {
420       debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
421       glob_t g;
422       int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
423       if (err && err != GLOB_NOMATCH)
424         die("Failed to glob %s: %m", p->pattern);
425       for (uns i=0; i<g.gl_pathc; i++)
426         {
427           char *name = g.gl_pathv[i];
428           struct mbox *b = find_mbox(&mboxes, name);
429           if (!b)
430             {
431               b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
432               debug("Discovered mailbox %s (%s)\n", b->name, b->path);
433               setup_options(b);
434               b->scanning = -1;
435             }
436           b->seen = 1;
437         }
438       globfree(&g);
439     }
440
441   struct mbox *tmp;
442   CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
443     {
444       if (b->seen)
445         b->seen = 0;
446       else
447         {
448           debug("Lost mailbox %s\n", b->name);
449           del_mbox(b);
450         }
451     }
452
453   rethink_display();
454
455   debug("Scanning mailboxes...\n");
456   CLIST_FOR_EACH(struct mbox *, b, mboxes)
457     {
458       struct stat st;
459       debug("%s: ", b->name);
460       if (!mbox_active_p(b))
461         {
462           debug("inactive\n");
463           continue;
464         }
465       if (force_refresh)
466         b->force_refresh = 1;
467       if (stat(b->path, &st) < 0)
468         {
469           b->total = b->new = -1;
470           debug("%m\n");
471         }
472       else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
473         {
474           b->scanning = 1;
475           redraw_line(b->index);
476           refresh();
477
478           scan_mbox(b, &st);
479           b->last_time = st.st_mtime;
480           b->last_size = st.st_size;
481           debug("%d %d (stopped at %d of %d)\n", b->total, b->new, b->last_pos, b->last_size);
482
483           b->scanning = 0;
484           redraw_line(b->index);
485           refresh();
486         }
487       else
488         debug("not changed\n");
489       b->force_refresh = 0;
490     }
491   force_refresh = 0;
492
493   debug("Scan finished\n");
494   last_scan_time = time(NULL);
495   rethink_display();
496 }
497
498 static int cursor_at, cursor_max;
499
500 enum {
501   M_IDLE,
502   M_SCAN,
503   M_NEW,
504   M_BAD,
505   M_MAX
506 };
507 static int attrs[2][2][M_MAX];          // active, hilite, status
508
509 static void
510 redraw_line(int i)
511 {
512   move(i, 0);
513   if (i < cursor_max)
514     {
515       struct mbox *b = mbox_array[i];
516       int cc = (cursor_at == i);
517       int hi = b->o.highlight;
518
519       attrset(attrs[cc][hi][M_IDLE]);
520       if (cc)
521         printw("> ");
522       else
523         printw("  ");
524       if (b->new)
525         attrset(attrs[cc][hi][M_NEW]);
526       printw("%-20s ", b->name);
527       if (b->scanning < 0)
528         ;
529       else if (b->scanning)
530         {
531           attrset(attrs[cc][hi][M_SCAN]);
532           printw("[SCANNING]");
533         }
534       else if (b->total < 0)
535         {
536           attrset(attrs[cc][hi][M_BAD]);
537           printw("BROKEN");
538         }
539       else
540         {
541           attrset(attrs[cc][hi][M_IDLE]);
542           printw("%6d ", b->total);
543           if (b->new)
544             {
545               attrset(attrs[cc][hi][M_NEW]);
546               printw("%6d  ", b->new);
547               attrset(attrs[cc][hi][M_IDLE]);
548               int age = (last_scan_time - b->last_time);
549               if (age < 0)
550                 age = 0;
551               if (age < 3600)
552                 printw("%2d min  ", age/60);
553               else if (age < 86400)
554                 printw("%2d hrs  ", age/3600);
555               else
556                 printw("        ");
557               if (b->o.snippets && b->snippet[0])
558                 {
559                   int xx, yy;
560                   getyx(stdscr, yy, xx);
561                   int remains = COLS-1-xx;
562                   if (remains > 2)
563                     printw("%-.*s", remains, b->snippet);
564                 }
565             }
566         }
567     }
568   attrset(attrs[0][0][M_IDLE]);
569   clrtoeol();
570 }
571
572 static void
573 redraw_all(void)
574 {
575   cursor_max = num_mboxes;
576   if (cursor_max > LINES-1)
577     cursor_max = LINES-1;
578   if (cursor_at >= cursor_max)
579     cursor_at = cursor_max - 1;
580   if (cursor_at < 0)
581     cursor_at = 0;
582
583   for (int i=0; i<cursor_max; i++)
584     redraw_line(i);
585   move(cursor_max, 0);
586   if (!cursor_max)
587     {
588       printw("(no mailboxes found)");
589       clrtoeol();
590       move(1, 0);
591     }
592   clrtobot();
593 }
594
595 static void
596 rethink_display(void)
597 {
598   int i = 0;
599   int changed = 0;
600   int beeeep = 0;
601   CLIST_FOR_EACH(struct mbox *, b, mboxes)
602     if (mbox_visible_p(b))
603       {
604         b->index = i;
605         if (i >= num_mboxes || mbox_array[i] != b)
606           {
607             changed = 1;
608             if (i >= mbox_array_size)
609               {
610                 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
611                 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
612               }
613             mbox_array[i] = b;
614           }
615         if (b->o.beep && b->new > b->last_beep_new)
616           beeeep = 1;
617         b->last_beep_new = b->new;
618         i++;
619       }
620   if (i != num_mboxes)
621     changed = 1;
622   num_mboxes = i;
623
624   if (changed)
625     {
626       redraw_all();
627       refresh();
628     }
629   if (beeeep)
630     beep();
631 }
632
633 static void
634 term_init(void)
635 {
636   initscr();
637   cbreak();
638   noecho();
639   nonl();
640   intrflush(stdscr, FALSE);
641   keypad(stdscr, TRUE);
642   curs_set(0);
643
644   static const int attrs_mono[2][M_MAX] = {
645     [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_BAD] = A_DIM },
646     [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_BAD] = A_DIM },
647   };
648   for (int i=0; i<2; i++)
649     for (int j=0; j<M_MAX; j++)
650       {
651         attrs[0][i][j] = attrs_mono[i][j];
652         attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
653       }
654
655   if (has_colors())
656     {
657       start_color();
658       if (COLOR_PAIRS >= 5)
659         {
660           init_pair(1, COLOR_YELLOW, COLOR_BLACK);
661           init_pair(2, COLOR_RED, COLOR_BLACK);
662           init_pair(3, COLOR_WHITE, COLOR_BLUE);
663           init_pair(4, COLOR_YELLOW, COLOR_BLUE);
664           init_pair(5, COLOR_RED, COLOR_BLUE);
665           static const int attrs_color[2][2][M_MAX] = {
666             [0][0] = { [M_IDLE] = 0, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1), [M_BAD] = COLOR_PAIR(2) },
667             [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 },
668             [1][0] = { [M_IDLE] = COLOR_PAIR(3), [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4), [M_BAD] = COLOR_PAIR(5) },
669             [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 },
670           };
671           memcpy(attrs, attrs_color, sizeof(attrs));
672         }
673     }
674 }
675
676 static void
677 term_cleanup(void)
678 {
679   endwin();
680 }
681
682 static void
683 scan_and_redraw(void)
684 {
685   move(LINES-1, 0);
686   printw("Busy...");
687   refresh();
688   scan();
689   move(LINES-1, 0);
690   clrtoeol();
691   refresh();
692 }
693
694 static void
695 move_cursor(int i)
696 {
697   if (i >= 0 && i < cursor_max && i != cursor_at)
698     {
699       int old = cursor_at;
700       cursor_at = i;
701       redraw_line(old);
702       redraw_line(i);
703     }
704 }
705
706 static void
707 next_active(int since, int step)
708 {
709   if (!cursor_max)
710     return;
711   since = (since+cursor_max) % cursor_max;
712   step = (step+cursor_max) % cursor_max;
713   int besti = -1;
714   int bestp = -1;
715   int i = since;
716   do
717     {
718       struct mbox *b = mbox_array[i];
719       if (b->new && b->o.priority > bestp)
720         {
721           besti = i;
722           bestp = b->o.priority;
723         }
724       i = (i+step) % cursor_max;
725     }
726   while (i != since);
727   if (besti >= 0)
728     move_cursor(besti);
729 }
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 " VERSION ", (c) " 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 }