Update to latest madtty.
[apps/madmutt.git] / lib-ui / curs_lib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 2004 g10 Code GmbH
5  *
6  * Parts were written/modified by:
7  * Nico Golde <nico@ngolde.de>
8  *
9  * This file is part of mutt-ng, see http://www.muttng.org/.
10  * It's licensed under the GNU General Public License,
11  * please see the file GPL in the top level source directory.
12  */
13
14 #include <lib-ui/lib-ui.h>
15
16 #include <langinfo.h>
17 #include <termios.h>
18
19 #include <lib-lua/lib-lua.h>
20 #include <lib-sys/unix.h>
21 #include <lib-sys/mutt_signal.h>
22
23 #include "menu.h"
24 #include "browser.h"
25
26 #include "mutt.h"
27 #include "pager.h"
28 #include "charset.h"
29 #include "madtty.h"
30
31 /* not possible to unget more than one char under some curses libs, and it
32  * is impossible to unget function keys in SLang, so roll our own input
33  * buffering routines.
34  */
35 ssize_t UngetCount = 0;
36 static ssize_t UngetBufLen = 0;
37 static event_t *KeyEvent;
38
39 event_t mutt_getch (void)
40 {
41   int ch;
42   event_t err = { -1, OP_NULL }, ret;
43
44   if (!option (OPTUNBUFFEREDINPUT) && UngetCount)
45     return KeyEvent[--UngetCount];
46
47   SigInt = 0;
48
49   mutt_allow_interrupt (1);
50   ch = getch();
51   mutt_allow_interrupt (0);
52
53   if (SigInt)
54     mutt_query_exit ();
55
56   if (ch == ERR)
57     return err;
58
59   ret.ch = ch;
60   ret.op = 0;
61   return ch == ctrl ('G') ? err : ret;
62 }
63
64 int mutt_get_field_unbuffered (char *msg, char *buf, ssize_t buflen, int flags)
65 {
66   int rc;
67
68   set_option (OPTUNBUFFEREDINPUT);
69   rc = mutt_get_field (msg, buf, buflen, flags);
70   unset_option (OPTUNBUFFEREDINPUT);
71
72   return rc;
73 }
74
75 void mutt_clear_error (void)
76 {
77   Errorbuf[0] = 0;
78   if (!option (OPTNOCURSES))
79     CLEARLINE(stdscr, LINES - 1);
80 }
81
82 static struct timeval const slice = { 0, 1000 * 1000 / 100 };
83 static struct timeval timeval_add(struct timeval a, struct timeval b)
84 {
85     int usec = a.tv_usec + b.tv_usec;
86     a.tv_sec += b.tv_sec;
87     while (usec > 1000 * 1000) {
88         a.tv_sec += 1;
89         usec -= 1000 * 1000;
90     }
91     a.tv_usec = usec;
92     return a;
93 }
94
95 static int is_expired(struct timeval now, struct timeval expiry)
96 {
97     return now.tv_sec > expiry.tv_sec
98         || (now.tv_sec == expiry.tv_sec && now.tv_usec > expiry.tv_usec);
99 }
100
101 void mutt_edit_file(const char *data)
102 {
103     char cmd[STRING];
104     const char *args[] = { "/bin/sh", "-c", cmd, NULL };
105     int dirty = 0, ch, res, mh, mw, pty, pid;
106     struct timeval next;
107     madtty_t *rt;
108
109     m_quotefile_fmt(cmd, sizeof(cmd), mod_core.editor, data);
110     getmaxyx(main_w, mh, mw);
111     SigChild = 0;
112
113     rt = madtty_create(mh - 2, mw);
114     pid = madtty_forkpty(rt, args[0], args, &pty);
115     if (pid < 0) {
116         madtty_destroy(rt);
117         mutt_error(_("unable to start editor"));
118         return;
119     }
120
121     SETCOLOR(main_w, MT_COLOR_SIDEBAR);
122     mvwhline(main_w, 0, 0, ACS_HLINE, mw);
123
124     nodelay(stdscr, true);
125     gettimeofday(&next, NULL);
126     while (!SigChild) {
127         struct timeval tv = { 0, 1000 * 1000 / 100 };
128         fd_set rfds;
129
130         FD_ZERO(&rfds);
131         FD_SET(0, &rfds);
132         FD_SET(pty, &rfds);
133
134         if (select(pty + 1, &rfds, NULL, NULL, &tv) < 0)
135             break;
136
137         if (FD_ISSET(pty, &rfds)) {
138             madtty_process(rt);
139             dirty = 1;
140         }
141
142         while ((ch = getch()) != ERR) {
143             madtty_keypress(rt, ch); /* pass the keypress for handling */
144             dirty = 1;
145         }
146
147         gettimeofday(&tv, NULL);
148         if (dirty && is_expired(tv, next)) {
149             madtty_draw(rt, main_w, 1, 0);
150             wrefresh(main_w);
151             dirty = 0;
152             next = timeval_add(tv, slice);
153         }
154     }
155     while (waitpid(pid, &res, 0) < 0 && errno == EINTR);
156     nodelay(stdscr, false);
157     close(pty);
158     madtty_destroy(rt);
159 }
160
161 int mutt_yesorno (const char *msg, int def)
162 {
163   event_t ch;
164   const char *yes = _("yes");
165   const char *no = _("no");
166   char *answer_string;
167   ssize_t answer_string_len;
168   char *expr;
169   regex_t reyes;
170   regex_t reno;
171   int reyes_ok;
172   int reno_ok;
173   char answer[2];
174
175   answer[1] = 0;
176
177   reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
178     !regcomp (&reyes, expr, REG_NOSUB | REG_EXTENDED);
179   reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
180     !regcomp (&reno, expr, REG_NOSUB | REG_EXTENDED);
181
182   CLEARLINE(stdscr, LINES - 1);
183
184   /*
185    * In order to prevent the default answer to the question to wrapped
186    * around the screen in the even the question is wider than the screen,
187    * ensure there is enough room for the answer and truncate the question
188    * to fit.
189    */
190   answer_string = p_new(char, getmaxx(stdscr) + 1);
191   snprintf (answer_string, getmaxx(stdscr) + 1, " ([%s]/%s): ", def == M_YES ? yes : no,
192             def == M_YES ? no : yes);
193   answer_string_len = m_strlen(answer_string);
194   wprintw (stdscr, "%.*s%s", getmaxx(stdscr) - answer_string_len, msg, answer_string);
195   p_delete(&answer_string);
196
197   for (;;) {
198     mutt_refresh ();
199     ch = mutt_getch ();
200     if (CI_is_return (ch.ch))
201       break;
202     if (ch.ch == -1) {
203       def = -1;
204       break;
205     }
206
207     answer[0] = ch.ch;
208     if (reyes_ok ? (regexec (&reyes, answer, 0, 0, 0) == 0) : tolower (ch.ch) == *yes)
209     {
210       def = M_YES;
211       break;
212     }
213     else if (
214               reno_ok ? (regexec (&reno, answer, 0, 0, 0) == 0) :
215               (tolower (ch.ch) == *no)) {
216       def = M_NO;
217       break;
218     } else {
219       BEEP ();
220     }
221   }
222
223   if (reyes_ok)
224     regfree (&reyes);
225   if (reno_ok)
226     regfree (&reno);
227
228   if (def != -1) {
229     waddstr (stdscr, (char *) (def == M_YES ? yes : no));
230     mutt_refresh ();
231   }
232   CLEARLINE(stdscr, LINES - 1);
233   return def;
234 }
235
236 /* this function is called when the user presses the abort key */
237 void mutt_query_exit (void)
238 {
239   mutt_flushinp ();
240   curs_set (1);
241   if (Timeout)
242     wtimeout (stdscr, -1);               /* restore blocking operation */
243   if (mutt_yesorno (_("Exit Madmutt?"), M_YES) == M_YES) {
244     mutt_endwin (NULL);
245     mutt_exit(1);
246   }
247   mutt_clear_error ();
248   mutt_curs_set (-1);
249   SigInt = 0;
250 }
251
252 void mutt_curses_error (const char *fmt, ...)
253 {
254   char TmpErrorbuf[STRING];
255   va_list ap;
256
257   va_start (ap, fmt);
258   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
259   va_end (ap);
260
261   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
262                       0, getmaxy(stdscr) - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
263   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
264
265   if (!option (OPTKEEPQUIET)) {
266     BEEP ();
267     SETCOLOR(stdscr, MT_COLOR_ERROR);
268     mvwaddstr (stdscr, LINES - 1, 0, Errorbuf);
269     wclrtoeol (stdscr);
270     SETCOLOR(stdscr, MT_COLOR_NORMAL);
271     mutt_refresh ();
272   }
273
274   set_option (OPTMSGERR);
275 }
276
277 void mutt_progress_bar (progress_t* progress, long pos) {
278   char posstr[STRING];
279
280   if (!pos) {
281     if (!NetInc)
282       mutt_message (progress->msg);
283     else {
284       if (progress->size)
285         mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
286                           progress->size);
287       progress->pos = 0;
288     }
289   }
290
291   if (!NetInc)
292     return;
293
294   if (pos >= progress->pos + (NetInc << 10)) {
295     progress->pos = pos;
296     pos = pos / (NetInc << 10) * (NetInc << 10);
297     mutt_pretty_size (posstr, sizeof (posstr), pos);
298     if (progress->size)
299       mutt_message ("%s %s/%s", progress->msg, posstr, progress->sizestr);
300     else
301       mutt_message ("%s %s", progress->msg, posstr);
302   }
303 }
304
305 void mutt_curses_message (const char *fmt, ...)
306 {
307   char TmpErrorbuf[STRING];
308   va_list ap;
309
310   va_start (ap, fmt);
311   vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
312   va_end (ap);
313
314   mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
315                       0, getmaxx(stdscr) - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
316   snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */
317
318   if (!option (OPTKEEPQUIET)) {
319     SETCOLOR(stdscr, MT_COLOR_MESSAGE);
320     mvwaddstr (stdscr, LINES - 1, 0, Errorbuf);
321     wclrtoeol (stdscr);
322     SETCOLOR(stdscr, MT_COLOR_NORMAL);
323     mutt_refresh ();
324   }
325
326   unset_option (OPTMSGERR);
327 }
328
329 void mutt_show_error (void)
330 {
331   if (option (OPTKEEPQUIET))
332     return;
333
334   SETCOLOR(stdscr, option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
335   CLEARLINE(stdscr, LINES - 1);
336   waddstr (stdscr, Errorbuf);
337   SETCOLOR(stdscr, MT_COLOR_NORMAL);
338 }
339
340 void curses_initialize(void)
341 {
342     initscr();
343     if (start_color() == ERR || !has_colors() || COLORS < 8)
344         mutt_exit(-1);
345     madtty_init_colors();
346     madtty_init_vt100_graphics();
347     ci_start_color();
348     noecho();
349     raw();
350     keypad(stdscr, true);
351     typeahead(-1);
352     meta(stdscr, true);
353     curs_set(0);
354     ESCDELAY = 50;
355 }
356
357 int mutt_any_key_to_continue (const char *s)
358 {
359   struct termios t;
360   struct termios old;
361   int f, ch;
362
363   f = open ("/dev/tty", O_RDONLY);
364   tcgetattr (f, &t);
365   memcpy ((void *) &old, (void *) &t, sizeof (struct termios)); /* save original state */
366   t.c_lflag &= ~(ICANON | ECHO);
367   t.c_cc[VMIN] = 1;
368   t.c_cc[VTIME] = 0;
369   tcsetattr (f, TCSADRAIN, &t);
370   fflush (stdout);
371   if (s)
372     fputs (s, stdout);
373   else
374     fputs (_("Press any key to continue..."), stdout);
375   fflush (stdout);
376   ch = fgetc (stdin);
377   fflush (stdin);
378   tcsetattr (f, TCSADRAIN, &old);
379   close (f);
380   fputs ("\r\n", stdout);
381   mutt_clear_error ();
382   return ch;
383 }
384
385 int _mutt_enter_fname (const char *prompt, char *buf, ssize_t blen,
386                        int *redraw, int buffy, int multiple, char ***files,
387                        int *numfiles)
388 {
389   event_t ch;
390
391   mvwaddstr(stdscr, LINES - 1, 0, (char *) prompt);
392   waddstr(stdscr, _(" ('?' for list): "));
393   if (buf[0])
394     waddstr (stdscr, buf);
395   wclrtoeol (stdscr);
396   mutt_refresh ();
397
398   ch = mutt_getch ();
399   if (ch.ch == -1) {
400     CLEARLINE(stdscr, LINES - 1);
401     return -1;
402   }
403   else if (ch.ch == '?') {
404     mutt_refresh ();
405     buf[0] = 0;
406     mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0),
407                       files, numfiles);
408     *redraw = REDRAW_FULL;
409   }
410   else {
411     char *pc = p_new(char, m_strlen(prompt) + 3);
412
413     sprintf(pc, "%s: ", prompt);
414     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
415     if (_mutt_get_field
416         (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files,
417          numfiles)
418         != 0)
419       buf[0] = 0;
420     MAYBE_REDRAW (*redraw);
421     p_delete(&pc);
422   }
423
424   return 0;
425 }
426
427 void mutt_ungetch (int ch, int op)
428 {
429   event_t tmp;
430
431   tmp.ch = ch;
432   tmp.op = op;
433
434   if (UngetCount >= UngetBufLen)
435     p_realloc(&KeyEvent, UngetBufLen += 128);
436
437   KeyEvent[UngetCount++] = tmp;
438 }
439
440 void mutt_flushinp (void)
441 {
442   UngetCount = 0;
443   flushinp ();
444 }
445
446 /* The argument can take 3 values:
447  * -1: restore the value of the last call
448  *  0: make the cursor invisible
449  *  1: make the cursor visible
450  */
451 void mutt_curs_set (int cursor)
452 {
453   static int SavedCursor = 1;
454
455   if (cursor < 0)
456     cursor = SavedCursor;
457   else
458     SavedCursor = cursor;
459
460   if (curs_set (cursor) == ERR) {
461     if (cursor == 1)            /* cnorm */
462       curs_set (2);             /* cvvis */
463   }
464 }
465
466 int mutt_multi_choice (const char *prompt, const char *letters)
467 {
468   event_t ch;
469   int choice;
470   char *p;
471
472   mvwaddstr (stdscr, LINES - 1, 0, prompt);
473   wclrtoeol (stdscr);
474   for (;;) {
475     mutt_refresh ();
476     ch = mutt_getch ();
477     if (ch.ch == -1 || CI_is_return (ch.ch)) {
478       choice = -1;
479       break;
480     }
481     else {
482       p = strchr (letters, ch.ch);
483       if (p) {
484         choice = p - letters + 1;
485         break;
486       }
487       else if (ch.ch <= '9' && ch.ch > '0') {
488         choice = ch.ch - '0';
489         if (choice <= m_strlen(letters))
490           break;
491       }
492     }
493     BEEP ();
494   }
495   CLEARLINE(stdscr, LINES - 1);
496   mutt_refresh ();
497   return choice;
498 }
499
500 ssize_t mutt_pretty_size(char *s, ssize_t len, ssize_t n)
501 {
502     if (n == 0)
503         return m_strcpy(s, len, "0K");
504
505     if (n < 10189)           /* 0.1K - 9.9K */
506         return snprintf(s, len, "%3.1fK", (n < 103) ? 0.1 : n / 1024.0);
507
508     if (n < 1023949)         /* 10K - 999K */
509         /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
510         return snprintf(s, len, "%ldK", (n + 51) / 1024L);
511
512     if (n < 10433332)        /* 1.0M - 9.9M */
513         return snprintf(s, len, "%3.1fM", n / 1048576.0);
514
515     /* (10433332 + 52428) / 1048576 = 10 */
516     return snprintf (s, len, "%ldM", (n + 52428) / 1048576L);
517 }
518
519 /*
520  * This formats a string, a bit like
521  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
522  * except that the widths refer to the number of character cells
523  * when printed.
524  */
525
526 void mutt_format_string (char *dest, ssize_t destlen,
527                          int min_width, int max_width,
528                          int right_justify, char m_pad_char,
529                          const char *s, ssize_t n, int arboreal)
530 {
531   char *p;
532   wchar_t wc;
533   int w;
534   ssize_t k, k2;
535   char scratch[MB_LEN_MAX];
536   mbstate_t mbstate1, mbstate2;
537
538   p_clear(&mbstate1, 1);
539   p_clear(&mbstate2, 1);
540   --destlen;
541   p = dest;
542   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k) {
543     if (k == -1 || k == -2) {
544       k = (k == -1) ? 1 : n;
545       wc = CharsetReplacement;
546     }
547     if (arboreal && wc < M_TREE_MAX)
548       w = 1;                    /* hack */
549     else {
550       if (!iswprint(wc))
551         wc = '?';
552       w = wcwidth (wc);
553     }
554     if (w >= 0) {
555       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
556         break;
557       min_width -= w;
558       max_width -= w;
559       m_strncpy(p, destlen, scratch, k2);
560       p += k2;
561       destlen -= k2;
562     }
563   }
564   w = destlen < min_width ? destlen : min_width;
565   if (w <= 0)
566     *p = '\0';
567   else if (right_justify) {
568     p[w] = '\0';
569     while (--p >= dest)
570       p[w] = *p;
571     while (--w >= 0)
572       dest[w] = m_pad_char;
573   }
574   else {
575     while (--w >= 0)
576       *p++ = m_pad_char;
577     *p = '\0';
578   }
579 }
580
581 /*
582  * This formats a string rather like
583  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
584  *   snprintf (dest, destlen, fmt, s);
585  * except that the numbers in the conversion specification refer to
586  * the number of character cells when printed.
587  */
588
589 static void mutt_format_s_x (char *dest, ssize_t destlen,
590                              const char *prefix, const char *s, int arboreal)
591 {
592   int right_justify = 1;
593   char *p;
594   int min_width;
595   int max_width = INT_MAX;
596
597   if (*prefix == '-')
598     ++prefix, right_justify = 0;
599   min_width = strtol (prefix, &p, 10);
600   if (*p == '.') {
601     prefix = p + 1;
602     max_width = strtol (prefix, &p, 10);
603     if (p <= prefix)
604       max_width = INT_MAX;
605   }
606
607   mutt_format_string (dest, destlen, min_width, max_width,
608                       right_justify, ' ', s, m_strlen(s), arboreal);
609 }
610
611 void mutt_format_s (char *dest, ssize_t destlen,
612                     const char *prefix, const char *s)
613 {
614   mutt_format_s_x (dest, destlen, prefix, s, 0);
615 }
616
617 void mutt_format_s_tree (char *dest, ssize_t destlen,
618                          const char *prefix, const char *s)
619 {
620   mutt_format_s_x (dest, destlen, prefix, s, 1);
621 }
622
623 /*
624  * mutt_paddstr (n, s) is almost equivalent to
625  * mutt_format_string (bigbuf, big, n, n, 0, ' ', s, big, 0), waddstr (main_w, bigbuf)
626  */
627
628 void mutt_paddstr(WINDOW *win, int n, const char *s)
629 {
630   wchar_t wc;
631   int w;
632   ssize_t k;
633   ssize_t len = m_strlen(s);
634   mbstate_t mbstate;
635
636   p_clear(&mbstate, 1);
637   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k) {
638     if (k == -1 || k == -2) {
639       k = (k == -1) ? 1 : len;
640       wc = CharsetReplacement;
641     }
642     if (!iswprint(wc))
643       wc = '?';
644     w = wcwidth (wc);
645     if (w >= 0) {
646       if (w > n)
647         break;
648       waddnstr(win, (char *) s, k);
649       n -= w;
650     }
651   }
652   while (n-- > 0)
653     waddch(win, ' ');
654 }