mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2002,2010,2012-2013 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 2004 g10 Code GmbH
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#if HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "mutt.h"
25#include "mutt_menu.h"
26#include "mutt_curses.h"
27#include "pager.h"
28#include "mbyte.h"
29#ifdef USE_INOTIFY
30#include "monitor.h"
31#endif
32
33#include <termios.h>
34#include <sys/types.h>
35#include <fcntl.h>
36#include <stdlib.h>
37#include <unistd.h>
38#include <string.h>
39#include <errno.h>
40#include <ctype.h>
41#ifdef HAVE_SYS_TIME_H
42# include <sys/time.h>
43#endif
44#include <time.h>
45
46#ifdef HAVE_LANGINFO_YESEXPR
47#include <langinfo.h>
48#endif
49
50/* Error message ring */
51struct error_history
52{
53 char **msg;
54 short last;
55} ErrorHistory = {0, 0};
56
57static short OldErrorHistSize = 0;
58
59
60/* not possible to unget more than one char under some curses libs, and it
61 * is impossible to unget function keys in SLang, so roll our own input
62 * buffering routines.
63 */
64
65/* These are used for macros and exec/push commands.
66 * They can be temporarily ignored by setting OPTIGNOREMACROEVENTS
67 */
68static size_t MacroBufferCount = 0;
69static size_t MacroBufferLen = 0;
70static event_t *MacroEvents;
71
72/* These are used in all other "normal" situations, and are not
73 * ignored when setting OPTIGNOREMACROEVENTS
74 */
75static size_t UngetCount = 0;
76static size_t UngetLen = 0;
77static event_t *UngetKeyEvents;
78
79int MuttGetchTimeout = -1;
80
81mutt_window_t *MuttHelpWindow = NULL;
82mutt_window_t *MuttIndexWindow = NULL;
83mutt_window_t *MuttStatusWindow = NULL;
84mutt_window_t *MuttMessageWindow = NULL;
85#ifdef USE_SIDEBAR
86mutt_window_t *MuttSidebarWindow = NULL;
87#endif
88
89static void reflow_message_window_rows (int mw_rows);
90
91
92void mutt_refresh (void)
93{
94 /* don't refresh when we are waiting for a child. */
95 if (option (OPTKEEPQUIET))
96 return;
97
98 /* don't refresh in the middle of macros unless necessary */
99 if (MacroBufferCount && !option (OPTFORCEREFRESH) &&
100 !option (OPTIGNOREMACROEVENTS))
101 return;
102
103 /* else */
104 refresh ();
105}
106
107/* Make sure that the next refresh does a full refresh. This could be
108 optimized by not doing it at all if DISPLAY is set as this might
109 indicate that a GUI based pinentry was used. Having an option to
110 customize this is of course the Mutt way. */
111void mutt_need_hard_redraw (void)
112{
113 keypad (stdscr, TRUE);
114 clearok (stdscr, TRUE);
115 mutt_set_current_menu_redraw_full ();
116}
117
118/* delay is just like for timeout() or poll():
119 * the number of milliseconds mutt_getch() should block for input.
120 * delay == 0 means mutt_getch() is non-blocking.
121 * delay < 0 means mutt_getch is blocking.
122 */
123void mutt_getch_timeout (int delay)
124{
125 MuttGetchTimeout = delay;
126 timeout (delay);
127}
128
129#ifdef USE_INOTIFY
130static int mutt_monitor_getch (void)
131{
132 int ch;
133
134 /* ncurses has its own internal buffer, so before we perform a poll,
135 * we need to make sure there isn't a character waiting */
136 timeout (0);
137 ch = getch ();
138 timeout (MuttGetchTimeout);
139 if (ch == ERR)
140 {
141 if (mutt_monitor_poll () != 0)
142 ch = ERR;
143 else
144 ch = getch ();
145 }
146 return ch;
147}
148#endif /* USE_INOTIFY */
149
150event_t mutt_getch (void)
151{
152 int ch;
153 event_t err = {-1, OP_NULL }, ret;
154 event_t timeout = {-2, OP_NULL};
155
156 if (UngetCount)
157 return (UngetKeyEvents[--UngetCount]);
158
159 if (!option(OPTIGNOREMACROEVENTS) && MacroBufferCount)
160 return (MacroEvents[--MacroBufferCount]);
161
162 SigInt = 0;
163
164 mutt_allow_interrupt (1);
165#ifdef KEY_RESIZE
166 /* ncurses 4.2 sends this when the screen is resized */
167 ch = KEY_RESIZE;
168 while (ch == KEY_RESIZE)
169#endif /* KEY_RESIZE */
170#ifdef USE_INOTIFY
171 ch = mutt_monitor_getch ();
172#else
173 ch = getch ();
174#endif /* USE_INOTIFY */
175 mutt_allow_interrupt (0);
176
177 if (SigInt)
178 {
179 mutt_query_exit ();
180 return err;
181 }
182
183 /* either timeout, a sigwinch (if timeout is set), or the terminal
184 * has been lost */
185 if (ch == ERR)
186 {
187 if (!isatty (0))
188 {
189 endwin ();
190 exit (1);
191 }
192 return timeout;
193 }
194
195 if ((ch & 0x80) && option (OPTMETAKEY))
196 {
197 /* send ALT-x as ESC-x */
198 ch &= ~0x80;
199 mutt_unget_event (ch, 0);
200 ret.ch = '\033';
201 ret.op = 0;
202 return ret;
203 }
204
205 ret.ch = ch;
206 ret.op = 0;
207 return (ch == ctrl ('G') ? err : ret);
208}
209
210int _mutt_get_field (const char *field, char *buf, size_t buflen, int complete, int multiple, char ***files, int *numfiles)
211{
212 BUFFER *buffer;
213 int rc;
214
215 buffer = mutt_buffer_pool_get ();
216
217 mutt_buffer_addstr (buffer, buf);
218 rc = _mutt_buffer_get_field (field, buffer, complete, multiple, files, numfiles);
219 strfcpy (buf, mutt_b2s (buffer), buflen);
220
221 mutt_buffer_pool_release (&buffer);
222 return rc;
223}
224
225int _mutt_buffer_get_field (const char *field, BUFFER *buffer, int complete, int multiple, char ***files, int *numfiles)
226{
227 int ret;
228 int x;
229
230 ENTER_STATE *es = mutt_new_enter_state();
231
232 do
233 {
234#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
235 if (SigWinch)
236 {
237 SigWinch = 0;
238 mutt_resize_screen ();
239 clearok(stdscr, TRUE);
240 mutt_current_menu_redraw ();
241 }
242#endif
243 mutt_window_clearline (MuttMessageWindow, 0);
244 SETCOLOR (MT_COLOR_PROMPT);
245 addstr ((char *)field); /* cast to get around bad prototypes */
246 NORMAL_COLOR;
247 mutt_refresh ();
248 mutt_window_getyx (MuttMessageWindow, NULL, &x);
249 ret = _mutt_enter_string (buffer->data, buffer->dsize, x, complete, multiple, files, numfiles, es);
250 }
251 while (ret == 1);
252
253 if (ret != 0)
254 mutt_buffer_clear (buffer);
255 else
256 mutt_buffer_fix_dptr (buffer);
257
258 mutt_window_clearline (MuttMessageWindow, 0);
259 mutt_free_enter_state (&es);
260
261 return (ret);
262}
263
264int mutt_get_field_unbuffered (char *msg, char *buf, size_t buflen, int flags)
265{
266 int rc, reset_ignoremacro = 0;
267
268 if (!option (OPTIGNOREMACROEVENTS))
269 {
270 set_option (OPTIGNOREMACROEVENTS);
271 reset_ignoremacro = 1;
272 }
273 rc = mutt_get_field (msg, buf, buflen, flags);
274 if (reset_ignoremacro)
275 unset_option (OPTIGNOREMACROEVENTS);
276
277 return (rc);
278}
279
280void mutt_clear_error (void)
281{
282 Errorbuf[0] = 0;
283 if (!option(OPTNOCURSES))
284 mutt_window_clearline (MuttMessageWindow, 0);
285}
286
287void mutt_edit_file (const char *editor, const char *data)
288{
289 BUFFER *cmd;
290
291 cmd = mutt_buffer_pool_get ();
292
293 mutt_endwin (NULL);
294 mutt_expand_file_fmt (cmd, editor, data);
295 if (mutt_system (mutt_b2s (cmd)))
296 {
297 mutt_error (_("Error running \"%s\"!"), mutt_b2s (cmd));
298 mutt_sleep (2);
299 }
300#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
301 /* the terminal may have been resized while the editor owned it */
302 mutt_resize_screen ();
303#endif
304 keypad (stdscr, TRUE);
305 clearok (stdscr, TRUE);
306
307 mutt_buffer_pool_release (&cmd);
308}
309
310int mutt_yesorno (const char *msg, int def)
311{
312 event_t ch;
313 char *yes = _("yes");
314 char *no = _("no");
315 char *answer_string;
316 int answer_string_wid, msg_wid;
317 size_t trunc_msg_len;
318 int redraw = 1, prompt_lines = 1;
319
320#ifdef HAVE_LANGINFO_YESEXPR
321 char *expr;
322 regex_t reyes;
323 regex_t reno;
324 int reyes_ok;
325 int reno_ok;
326 char answer[2];
327
328 answer[1] = 0;
329
330 reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
331 !REGCOMP (&reyes, expr, REG_NOSUB);
332 reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
333 !REGCOMP (&reno, expr, REG_NOSUB);
334#endif
335
336 /*
337 * In order to prevent the default answer to the question to wrapped
338 * around the screen in the even the question is wider than the screen,
339 * ensure there is enough room for the answer and truncate the question
340 * to fit.
341 */
342 safe_asprintf (&answer_string, " ([%s]/%s): ", def == MUTT_YES ? yes : no, def == MUTT_YES ? no : yes);
343 answer_string_wid = mutt_strwidth (answer_string);
344 msg_wid = mutt_strwidth (msg);
345
346 FOREVER
347 {
348 if (redraw || SigWinch)
349 {
350 redraw = 0;
351#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
352 if (SigWinch)
353 {
354 SigWinch = 0;
355 mutt_resize_screen ();
356 clearok (stdscr, TRUE);
357 mutt_current_menu_redraw ();
358 }
359#endif
360 if (MuttMessageWindow->cols)
361 {
362 prompt_lines = (msg_wid + answer_string_wid + MuttMessageWindow->cols - 1) /
363 MuttMessageWindow->cols;
364 prompt_lines = MAX (1, MIN (3, prompt_lines));
365 }
366 if (prompt_lines != MuttMessageWindow->rows)
367 {
368 reflow_message_window_rows (prompt_lines);
369 mutt_current_menu_redraw ();
370 }
371
372 /* maxlen here is sort of arbitrary, so pick a reasonable upper bound */
373 trunc_msg_len = mutt_wstr_trunc (msg, 4 * prompt_lines * MuttMessageWindow->cols,
374 prompt_lines * MuttMessageWindow->cols - answer_string_wid,
375 NULL);
376
377 mutt_window_move (MuttMessageWindow, 0, 0);
378 SETCOLOR (MT_COLOR_PROMPT);
379 addnstr (msg, trunc_msg_len);
380 addstr (answer_string);
381 NORMAL_COLOR;
382 mutt_window_clrtoeol (MuttMessageWindow);
383 }
384
385 mutt_refresh ();
386 /* SigWinch is not processed unless timeout is set */
387 mutt_getch_timeout (30 * 1000);
388 ch = mutt_getch ();
389 mutt_getch_timeout (-1);
390 if (ch.ch == -2)
391 continue;
392 if (CI_is_return (ch.ch))
393 break;
394 if (ch.ch < 0)
395 {
396 def = -1;
397 break;
398 }
399
400#ifdef HAVE_LANGINFO_YESEXPR
401 answer[0] = ch.ch;
402 if (reyes_ok ?
403 (regexec (& reyes, answer, 0, 0, 0) == 0) :
404#else
405 if (
406#endif
407 (tolower (ch.ch) == 'y'))
408 {
409 def = MUTT_YES;
410 break;
411 }
412 else if (
413#ifdef HAVE_LANGINFO_YESEXPR
414 reno_ok ?
415 (regexec (& reno, answer, 0, 0, 0) == 0) :
416#endif
417 (tolower (ch.ch) == 'n'))
418 {
419 def = MUTT_NO;
420 break;
421 }
422 else
423 {
424 BEEP();
425 }
426 }
427
428 FREE (&answer_string);
429
430#ifdef HAVE_LANGINFO_YESEXPR
431 if (reyes_ok)
432 regfree (& reyes);
433 if (reno_ok)
434 regfree (& reno);
435#endif
436
437 if (MuttMessageWindow->rows != 1)
438 {
439 reflow_message_window_rows (1);
440 mutt_current_menu_redraw ();
441 }
442 else
443 mutt_window_clearline (MuttMessageWindow, 0);
444
445 if (def != -1)
446 {
447 addstr ((char *) (def == MUTT_YES ? yes : no));
448 mutt_refresh ();
449 }
450 else
451 {
452 /* when the users cancels with ^G, clear the message stored with
453 * mutt_message() so it isn't displayed when the screen is refreshed. */
454 mutt_clear_error();
455 }
456 return (def);
457}
458
459/* this function is called when the user presses the abort key */
460void mutt_query_exit (void)
461{
462 mutt_flushinp ();
463 curs_set (1);
464 if (Timeout)
465 mutt_getch_timeout (-1); /* restore blocking operation */
466 if (mutt_yesorno (_("Exit Mutt?"), MUTT_YES) == MUTT_YES)
467 {
468 endwin ();
469 exit (1);
470 }
471 mutt_clear_error();
472 mutt_curs_set (-1);
473 SigInt = 0;
474}
475
476void mutt_error_history_init (void)
477{
478 short i;
479
480 if (OldErrorHistSize && ErrorHistory.msg)
481 {
482 for (i = 0; i < OldErrorHistSize; i++)
483 FREE (&ErrorHistory.msg[i]);
484 FREE (&ErrorHistory.msg);
485 }
486
487 if (ErrorHistSize)
488 ErrorHistory.msg = safe_calloc (ErrorHistSize, sizeof (char *));
489 ErrorHistory.last = 0;
490
491 OldErrorHistSize = ErrorHistSize;
492}
493
494static void error_history_add (const char *s)
495{
496 static int in_process = 0;
497
498 if (!ErrorHistSize || in_process || !s || !*s)
499 return;
500 in_process = 1;
501
502 mutt_str_replace (&ErrorHistory.msg[ErrorHistory.last], s);
503 if (++ErrorHistory.last >= ErrorHistSize)
504 ErrorHistory.last = 0;
505
506 in_process = 0;
507}
508
509static void error_history_dump (FILE *f)
510{
511 short cur;
512
513 cur = ErrorHistory.last;
514 do
515 {
516 if (ErrorHistory.msg[cur])
517 {
518 fputs (ErrorHistory.msg[cur], f);
519 fputc ('\n', f);
520 }
521 if (++cur >= ErrorHistSize)
522 cur = 0;
523 } while (cur != ErrorHistory.last);
524}
525
526void mutt_error_history_display ()
527{
528 static int in_process = 0;
529 BUFFER *t = NULL;
530 FILE *f;
531
532 if (!ErrorHistSize)
533 {
534 mutt_error _("Error History is disabled.");
535 return;
536 }
537
538 if (in_process)
539 {
540 mutt_error _("Error History is currently being shown.");
541 return;
542 }
543
544 t = mutt_buffer_pool_get ();
545 mutt_buffer_mktemp (t);
546 if ((f = safe_fopen (mutt_b2s (t), "w")) == NULL)
547 {
548 mutt_perror (mutt_b2s (t));
549 goto cleanup;
550 }
551 error_history_dump (f);
552 safe_fclose (&f);
553
554 in_process = 1;
555 mutt_do_pager (_("Error History"), mutt_b2s (t), 0, NULL);
556 in_process = 0;
557
558cleanup:
559 mutt_buffer_pool_release (&t);
560}
561
562static void curses_message (int error, const char *fmt, va_list ap)
563{
564 char scratch[LONG_STRING];
565
566 vsnprintf (scratch, sizeof (scratch), fmt, ap);
567 error_history_add (scratch);
568
569 dprint (1, (debugfile, "%s\n", scratch));
570 mutt_format_string (Errorbuf, sizeof (Errorbuf),
571 0, MuttMessageWindow->cols, FMT_LEFT, 0, scratch, sizeof (scratch), 0);
572
573 if (!option (OPTKEEPQUIET))
574 {
575 if (error)
576 BEEP ();
577 SETCOLOR (error ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
578 mutt_window_mvaddstr (MuttMessageWindow, 0, 0, Errorbuf);
579 NORMAL_COLOR;
580 mutt_window_clrtoeol (MuttMessageWindow);
581 mutt_refresh ();
582 }
583
584 if (error)
585 set_option (OPTMSGERR);
586 else
587 unset_option (OPTMSGERR);
588}
589
590void mutt_curses_error (const char *fmt, ...)
591{
592 va_list ap;
593
594 va_start (ap, fmt);
595 curses_message (1, fmt, ap);
596 va_end (ap);
597}
598
599void mutt_curses_message (const char *fmt, ...)
600{
601 va_list ap;
602
603 va_start (ap, fmt);
604 curses_message (0, fmt, ap);
605 va_end (ap);
606}
607
608void mutt_progress_init (progress_t* progress, const char *msg,
609 unsigned short flags, unsigned short inc,
610 long size)
611{
612 struct timeval tv = { 0, 0 };
613
614 if (!progress)
615 return;
616 if (option(OPTNOCURSES))
617 return;
618
619 memset (progress, 0, sizeof (progress_t));
620 progress->inc = inc;
621 progress->flags = flags;
622 progress->msg = msg;
623 progress->size = size;
624 if (progress->size)
625 {
626 if (progress->flags & MUTT_PROGRESS_SIZE)
627 mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
628 progress->size);
629 else
630 snprintf (progress->sizestr, sizeof (progress->sizestr), "%ld",
631 progress->size);
632 }
633 if (!inc)
634 {
635 if (size)
636 mutt_message ("%s (%s)", msg, progress->sizestr);
637 else
638 mutt_message (msg);
639 return;
640 }
641 if (gettimeofday (&tv, NULL) < 0)
642 dprint (1, (debugfile, "gettimeofday failed: %d\n", errno));
643 /* if timestamp is 0 no time-based suppression is done */
644 if (TimeInc)
645 progress->timestamp = ((unsigned int) tv.tv_sec * 1000)
646 + (unsigned int) (tv.tv_usec / 1000);
647 mutt_progress_update (progress, 0, 0);
648}
649
650void mutt_progress_update (progress_t* progress, long pos, int percent)
651{
652 char posstr[SHORT_STRING];
653 short update = 0;
654 struct timeval tv = { 0, 0 };
655 unsigned int now = 0;
656
657 if (option(OPTNOCURSES))
658 return;
659
660 if (!progress->inc)
661 goto out;
662
663 /* refresh if size > inc */
664 if (progress->flags & MUTT_PROGRESS_SIZE &&
665 (pos >= progress->pos + (progress->inc << 10)))
666 update = 1;
667 else if (pos >= progress->pos + progress->inc)
668 update = 1;
669
670 /* skip refresh if not enough time has passed */
671 if (update && progress->timestamp && !gettimeofday (&tv, NULL))
672 {
673 now = ((unsigned int) tv.tv_sec * 1000)
674 + (unsigned int) (tv.tv_usec / 1000);
675 if (now && now - progress->timestamp < TimeInc)
676 update = 0;
677 }
678
679 /* always show the first update */
680 if (!pos)
681 update = 1;
682
683 if (update)
684 {
685 if (progress->flags & MUTT_PROGRESS_SIZE)
686 {
687 pos = pos / (progress->inc << 10) * (progress->inc << 10);
688 mutt_pretty_size (posstr, sizeof (posstr), pos);
689 }
690 else
691 snprintf (posstr, sizeof (posstr), "%ld", pos);
692
693 dprint (5, (debugfile, "updating progress: %s\n", posstr));
694
695 progress->pos = pos;
696 if (now)
697 progress->timestamp = now;
698
699 if (progress->size > 0)
700 {
701 mutt_message ("%s %s/%s (%d%%)", progress->msg, posstr, progress->sizestr,
702 percent > 0 ? percent :
703 (int) (100.0 * (double) progress->pos / progress->size));
704 }
705 else
706 {
707 if (percent > 0)
708 mutt_message ("%s %s (%d%%)", progress->msg, posstr, percent);
709 else
710 mutt_message ("%s %s", progress->msg, posstr);
711 }
712 }
713
714out:
715 if (pos >= progress->size)
716 mutt_clear_error ();
717}
718
719void mutt_init_windows ()
720{
721 MuttHelpWindow = safe_calloc (sizeof (mutt_window_t), 1);
722 MuttIndexWindow = safe_calloc (sizeof (mutt_window_t), 1);
723 MuttStatusWindow = safe_calloc (sizeof (mutt_window_t), 1);
724 MuttMessageWindow = safe_calloc (sizeof (mutt_window_t), 1);
725#ifdef USE_SIDEBAR
726 MuttSidebarWindow = safe_calloc (sizeof (mutt_window_t), 1);
727#endif
728}
729
730void mutt_free_windows ()
731{
732 FREE (&MuttHelpWindow);
733 FREE (&MuttIndexWindow);
734 FREE (&MuttStatusWindow);
735 FREE (&MuttMessageWindow);
736#ifdef USE_SIDEBAR
737 FREE (&MuttSidebarWindow);
738#endif
739}
740
741void mutt_reflow_windows (void)
742{
743 if (option (OPTNOCURSES))
744 return;
745
746 dprint (2, (debugfile, "In mutt_reflow_windows\n"));
747
748 MuttStatusWindow->rows = 1;
749 MuttStatusWindow->cols = COLS;
750 MuttStatusWindow->row_offset = option (OPTSTATUSONTOP) ? 0 : LINES - 2;
751 MuttStatusWindow->col_offset = 0;
752
753 memcpy (MuttHelpWindow, MuttStatusWindow, sizeof (mutt_window_t));
754 if (! option (OPTHELP))
755 MuttHelpWindow->rows = 0;
756 else
757 MuttHelpWindow->row_offset = option (OPTSTATUSONTOP) ? LINES - 2 : 0;
758
759 memcpy (MuttMessageWindow, MuttStatusWindow, sizeof (mutt_window_t));
760 MuttMessageWindow->row_offset = LINES - 1;
761
762 memcpy (MuttIndexWindow, MuttStatusWindow, sizeof (mutt_window_t));
763 MuttIndexWindow->rows = MAX(LINES - MuttStatusWindow->rows -
764 MuttHelpWindow->rows - MuttMessageWindow->rows, 0);
765 MuttIndexWindow->row_offset = option (OPTSTATUSONTOP) ? MuttStatusWindow->rows :
766 MuttHelpWindow->rows;
767
768#ifdef USE_SIDEBAR
769 if (option (OPTSIDEBAR))
770 {
771 memcpy (MuttSidebarWindow, MuttIndexWindow, sizeof (mutt_window_t));
772 MuttSidebarWindow->cols = SidebarWidth;
773
774 MuttIndexWindow->cols -= SidebarWidth;
775 MuttIndexWindow->col_offset += SidebarWidth;
776 }
777#endif
778
779 mutt_set_current_menu_redraw_full ();
780 /* the pager menu needs this flag set to recalc lineInfo */
781 mutt_set_current_menu_redraw (REDRAW_FLOW);
782}
783
784static void reflow_message_window_rows (int mw_rows)
785{
786 MuttMessageWindow->rows = mw_rows;
787 MuttMessageWindow->row_offset = LINES - mw_rows;
788
789 MuttStatusWindow->row_offset = option (OPTSTATUSONTOP) ? 0 : LINES - mw_rows - 1;
790
791 if (option (OPTHELP))
792 MuttHelpWindow->row_offset = option (OPTSTATUSONTOP) ? LINES - mw_rows - 1 : 0;
793
794 MuttIndexWindow->rows = MAX(LINES - MuttStatusWindow->rows -
795 MuttHelpWindow->rows - MuttMessageWindow->rows, 0);
796
797#ifdef USE_SIDEBAR
798 if (option (OPTSIDEBAR))
799 MuttSidebarWindow->rows = MuttIndexWindow->rows;
800#endif
801
802 /* We don't also set REDRAW_FLOW because this function only
803 * changes rows and is a temporary adjustment. */
804 mutt_set_current_menu_redraw_full ();
805}
806
807int mutt_window_move (mutt_window_t *win, int row, int col)
808{
809 return move (win->row_offset + row, win->col_offset + col);
810}
811
812int mutt_window_mvaddch (mutt_window_t *win, int row, int col, const chtype ch)
813{
814 return mvaddch (win->row_offset + row, win->col_offset + col, ch);
815}
816
817int mutt_window_mvaddstr (mutt_window_t *win, int row, int col, const char *str)
818{
819 return mvaddstr (win->row_offset + row, win->col_offset + col, str);
820}
821
822#ifdef USE_SLANG_CURSES
823static int vw_printw (SLcurses_Window_Type *win, const char *fmt, va_list ap)
824{
825 char buf[LONG_STRING];
826
827 (void) SLvsnprintf (buf, sizeof (buf), (char *)fmt, ap);
828 SLcurses_waddnstr (win, buf, -1);
829 return 0;
830}
831#endif
832
833int mutt_window_mvprintw (mutt_window_t *win, int row, int col, const char *fmt, ...)
834{
835 va_list ap;
836 int rv;
837
838 if ((rv = mutt_window_move (win, row, col)) != ERR)
839 {
840 va_start (ap, fmt);
841 rv = vw_printw (stdscr, fmt, ap);
842 va_end (ap);
843 }
844
845 return rv;
846}
847
848/* Assumes the cursor has already been positioned within the
849 * window.
850 */
851void mutt_window_clrtoeol (mutt_window_t *win)
852{
853 int row, col, curcol;
854
855 if (win->col_offset + win->cols == COLS)
856 clrtoeol ();
857 else
858 {
859 getyx (stdscr, row, col);
860 curcol = col;
861 while (curcol < win->col_offset + win->cols)
862 {
863 addch (' ');
864 curcol++;
865 }
866 move (row, col);
867 }
868}
869
870void mutt_window_clearline (mutt_window_t *win, int row)
871{
872 mutt_window_move (win, row, 0);
873 mutt_window_clrtoeol (win);
874}
875
876/* Assumes the current position is inside the window.
877 * Otherwise it will happily return negative or values outside
878 * the window boundaries
879 */
880void mutt_window_getyx (mutt_window_t *win, int *y, int *x)
881{
882 int row, col;
883
884 getyx (stdscr, row, col);
885 if (y)
886 *y = row - win->row_offset;
887 if (x)
888 *x = col - win->col_offset;
889}
890
891
892void mutt_show_error (void)
893{
894 if (option (OPTKEEPQUIET))
895 return;
896
897 SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
898 mutt_window_mvaddstr (MuttMessageWindow, 0, 0, Errorbuf);
899 NORMAL_COLOR;
900 mutt_window_clrtoeol(MuttMessageWindow);
901}
902
903void mutt_endwin (const char *msg)
904{
905 int e = errno;
906
907 if (!option (OPTNOCURSES))
908 {
909 /* at least in some situations (screen + xterm under SuSE11/12) endwin()
910 * doesn't properly flush the screen without an explicit call.
911 */
912 mutt_refresh();
913 endwin ();
914 }
915
916 if (msg && *msg)
917 {
918 puts (msg);
919 fflush (stdout);
920 }
921
922 errno = e;
923}
924
925void mutt_perror (const char *s)
926{
927 char *p = strerror (errno);
928
929 dprint (1, (debugfile, "%s: %s (errno = %d)\n", s,
930 p ? p : "unknown error", errno));
931 mutt_error ("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
932}
933
934int mutt_any_key_to_continue (const char *s)
935{
936 struct termios t;
937 struct termios old;
938 int f, ch;
939
940 f = open ("/dev/tty", O_RDONLY);
941 tcgetattr (f, &t);
942 memcpy ((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */
943 t.c_lflag &= ~(ICANON | ECHO);
944 t.c_cc[VMIN] = 1;
945 t.c_cc[VTIME] = 0;
946 tcsetattr (f, TCSADRAIN, &t);
947 fflush (stdout);
948 if (s)
949 fputs (s, stdout);
950 else
951 fputs (_("Press any key to continue..."), stdout);
952 fflush (stdout);
953 ch = fgetc (stdin);
954 fflush (stdin);
955 tcsetattr (f, TCSADRAIN, &old);
956 close (f);
957 fputs ("\r\n", stdout);
958 mutt_clear_error ();
959 return (ch);
960}
961
962int mutt_do_pager (const char *banner,
963 const char *tempfile,
964 int do_color,
965 pager_t *info)
966{
967 int rc;
968
969 if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
970 rc = mutt_pager (banner, tempfile, do_color, info);
971 else
972 {
973 BUFFER *cmd = NULL;
974
975 cmd = mutt_buffer_pool_get ();
976 mutt_endwin (NULL);
977 mutt_expand_file_fmt (cmd, Pager, tempfile);
978 if (mutt_system (mutt_b2s (cmd)) == -1)
979 {
980 mutt_error (_("Error running \"%s\"!"), mutt_b2s (cmd));
981 rc = -1;
982 }
983 else
984 rc = 0;
985 mutt_unlink (tempfile);
986 mutt_buffer_pool_release (&cmd);
987 }
988
989 return rc;
990}
991
992int _mutt_enter_fname (const char *prompt, char *buf, size_t blen, int buffy,
993 int multiple, char ***files, int *numfiles)
994{
995 BUFFER *fname;
996 int rc;
997
998 fname = mutt_buffer_pool_get ();
999
1000 mutt_buffer_addstr (fname, buf);
1001 rc = _mutt_buffer_enter_fname (prompt, fname, buffy, multiple, files, numfiles);
1002 strfcpy (buf, mutt_b2s (fname), blen);
1003
1004 mutt_buffer_pool_release (&fname);
1005 return rc;
1006}
1007
1008int _mutt_buffer_enter_fname (const char *prompt, BUFFER *fname, int buffy,
1009 int multiple, char ***files, int *numfiles)
1010{
1011 event_t ch;
1012
1013 SETCOLOR (MT_COLOR_PROMPT);
1014 mutt_window_mvaddstr (MuttMessageWindow, 0, 0, (char *) prompt);
1015 addstr (_(" ('?' for list): "));
1016 NORMAL_COLOR;
1017 if (mutt_buffer_len (fname))
1018 addstr (mutt_b2s (fname));
1019 mutt_window_clrtoeol (MuttMessageWindow);
1020 mutt_refresh ();
1021
1022 do
1023 {
1024 ch = mutt_getch();
1025 } while (ch.ch == -2);
1026 if (ch.ch < 0)
1027 {
1028 mutt_window_clearline (MuttMessageWindow, 0);
1029 return (-1);
1030 }
1031 else if (ch.ch == '?')
1032 {
1033 mutt_refresh ();
1034 mutt_buffer_clear (fname);
1035 _mutt_buffer_select_file (fname,
1036 MUTT_SEL_FOLDER | (multiple ? MUTT_SEL_MULTI : 0),
1037 files, numfiles);
1038 }
1039 else
1040 {
1041 char *pc = safe_malloc (mutt_strlen (prompt) + 3);
1042
1043 sprintf (pc, "%s: ", prompt); /* __SPRINTF_CHECKED__ */
1044 mutt_unget_event (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
1045
1046 mutt_buffer_increase_size (fname, LONG_STRING);
1047 if (_mutt_buffer_get_field (pc, fname,
1048 (buffy ? MUTT_EFILE : MUTT_FILE) | MUTT_CLEAR,
1049 multiple, files, numfiles) != 0)
1050 mutt_buffer_clear (fname);
1051 FREE (&pc);
1052 }
1053
1054 return 0;
1055}
1056
1057void mutt_unget_event (int ch, int op)
1058{
1059 event_t tmp;
1060
1061 tmp.ch = ch;
1062 tmp.op = op;
1063
1064 if (UngetCount >= UngetLen)
1065 safe_realloc (&UngetKeyEvents, (UngetLen += 16) * sizeof(event_t));
1066
1067 UngetKeyEvents[UngetCount++] = tmp;
1068}
1069
1070void mutt_unget_string (char *s)
1071{
1072 char *p = s + mutt_strlen (s) - 1;
1073
1074 while (p >= s)
1075 {
1076 mutt_unget_event ((unsigned char)*p--, 0);
1077 }
1078}
1079
1080/*
1081 * Adds the ch/op to the macro buffer.
1082 * This should be used for macros, push, and exec commands only.
1083 */
1084void mutt_push_macro_event (int ch, int op)
1085{
1086 event_t tmp;
1087
1088 tmp.ch = ch;
1089 tmp.op = op;
1090
1091 if (MacroBufferCount >= MacroBufferLen)
1092 safe_realloc (&MacroEvents, (MacroBufferLen += 128) * sizeof(event_t));
1093
1094 MacroEvents[MacroBufferCount++] = tmp;
1095}
1096
1097void mutt_flush_macro_to_endcond (void)
1098{
1099 UngetCount = 0;
1100 while (MacroBufferCount > 0)
1101 {
1102 if (MacroEvents[--MacroBufferCount].op == OP_END_COND)
1103 return;
1104 }
1105}
1106
1107/* Normally, OP_END_COND should only be in the MacroEvent buffer.
1108 * km_error_key() (ab)uses OP_END_COND as a barrier in the unget
1109 * buffer, and calls this function to flush. */
1110void mutt_flush_unget_to_endcond (void)
1111{
1112 while (UngetCount > 0)
1113 {
1114 if (UngetKeyEvents[--UngetCount].op == OP_END_COND)
1115 return;
1116 }
1117}
1118
1119void mutt_flushinp (void)
1120{
1121 UngetCount = 0;
1122 MacroBufferCount = 0;
1123 flushinp ();
1124}
1125
1126#if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
1127/* The argument can take 3 values:
1128 * -1: restore the value of the last call
1129 * 0: make the cursor invisible
1130 * 1: make the cursor visible
1131 */
1132void mutt_curs_set (int cursor)
1133{
1134 static int SavedCursor = 1;
1135
1136 if (cursor < 0)
1137 cursor = SavedCursor;
1138 else
1139 SavedCursor = cursor;
1140
1141 if (curs_set (cursor) == ERR)
1142 {
1143 if (cursor == 1) /* cnorm */
1144 curs_set (2); /* cvvis */
1145 }
1146}
1147#endif
1148
1149int mutt_multi_choice (char *prompt, char *letters)
1150{
1151 event_t ch;
1152 int choice;
1153 int redraw = 1, prompt_lines = 1;
1154 char *p;
1155
1156 FOREVER
1157 {
1158 if (redraw || SigWinch)
1159 {
1160 redraw = 0;
1161#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
1162 if (SigWinch)
1163 {
1164 SigWinch = 0;
1165 mutt_resize_screen ();
1166 clearok (stdscr, TRUE);
1167 mutt_current_menu_redraw ();
1168 }
1169#endif
1170 if (MuttMessageWindow->cols)
1171 {
1172 prompt_lines = (mutt_strwidth (prompt) + MuttMessageWindow->cols - 1) /
1173 MuttMessageWindow->cols;
1174 prompt_lines = MAX (1, MIN (3, prompt_lines));
1175 }
1176 if (prompt_lines != MuttMessageWindow->rows)
1177 {
1178 reflow_message_window_rows (prompt_lines);
1179 mutt_current_menu_redraw ();
1180 }
1181
1182 SETCOLOR (MT_COLOR_PROMPT);
1183 mutt_window_mvaddstr (MuttMessageWindow, 0, 0, prompt);
1184 NORMAL_COLOR;
1185 mutt_window_clrtoeol (MuttMessageWindow);
1186 }
1187
1188 mutt_refresh ();
1189 /* SigWinch is not processed unless timeout is set */
1190 mutt_getch_timeout (30 * 1000);
1191 ch = mutt_getch ();
1192 mutt_getch_timeout (-1);
1193 if (ch.ch == -2)
1194 continue;
1195 /* (ch.ch == 0) is technically possible. Treat the same as < 0 (abort) */
1196 if (ch.ch <= 0 || CI_is_return (ch.ch))
1197 {
1198 choice = -1;
1199 break;
1200 }
1201 else
1202 {
1203 p = strchr (letters, ch.ch);
1204 if (p)
1205 {
1206 choice = p - letters + 1;
1207 break;
1208 }
1209 else if (ch.ch <= '9' && ch.ch > '0')
1210 {
1211 choice = ch.ch - '0';
1212 if (choice <= mutt_strlen (letters))
1213 break;
1214 }
1215 }
1216 BEEP ();
1217 }
1218 if (MuttMessageWindow->rows != 1)
1219 {
1220 reflow_message_window_rows (1);
1221 mutt_current_menu_redraw ();
1222 }
1223 else
1224 mutt_window_clearline (MuttMessageWindow, 0);
1225 mutt_refresh ();
1226 return choice;
1227}
1228
1229/*
1230 * addwch would be provided by an up-to-date curses library
1231 */
1232
1233int mutt_addwch (wchar_t wc)
1234{
1235 char buf[MB_LEN_MAX*2];
1236 mbstate_t mbstate;
1237 size_t n1, n2;
1238
1239 memset (&mbstate, 0, sizeof (mbstate));
1240 if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t)(-1) ||
1241 (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t)(-1))
1242 return -1; /* ERR */
1243 else
1244 return addstr (buf);
1245}
1246
1247
1248/*
1249 * This formats a string, a bit like
1250 * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
1251 * except that the widths refer to the number of character cells
1252 * when printed.
1253 */
1254
1255void mutt_format_string (char *dest, size_t destlen,
1256 int min_width, int max_width,
1257 int justify, char m_pad_char,
1258 const char *s, size_t n,
1259 int arboreal)
1260{
1261 char *p;
1262 wchar_t wc;
1263 int w;
1264 size_t k, k2;
1265 char scratch[MB_LEN_MAX];
1266 mbstate_t mbstate1, mbstate2;
1267
1268 memset(&mbstate1, 0, sizeof (mbstate1));
1269 memset(&mbstate2, 0, sizeof (mbstate2));
1270 --destlen;
1271 p = dest;
1272 for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k)
1273 {
1274 if (k == (size_t)(-1) || k == (size_t)(-2))
1275 {
1276 if (k == (size_t)(-1) && errno == EILSEQ)
1277 memset (&mbstate1, 0, sizeof (mbstate1));
1278
1279 k = (k == (size_t)(-1)) ? 1 : n;
1280 wc = replacement_char ();
1281 }
1282 if (arboreal && wc < MUTT_TREE_MAX)
1283 w = 1; /* hack */
1284 else
1285 {
1286#ifdef HAVE_ISWBLANK
1287 if (iswblank (wc))
1288 wc = ' ';
1289 else
1290#endif
1291 if (!IsWPrint (wc))
1292 wc = '?';
1293 w = wcwidth (wc);
1294 }
1295 if (w >= 0)
1296 {
1297 if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
1298 break;
1299 min_width -= w;
1300 max_width -= w;
1301 strncpy (p, scratch, k2);
1302 p += k2;
1303 destlen -= k2;
1304 }
1305 }
1306 w = (int)destlen < min_width ? destlen : min_width;
1307 if (w <= 0)
1308 *p = '\0';
1309 else if (justify == FMT_RIGHT) /* right justify */
1310 {
1311 p[w] = '\0';
1312 while (--p >= dest)
1313 p[w] = *p;
1314 while (--w >= 0)
1315 dest[w] = m_pad_char;
1316 }
1317 else if (justify == FMT_CENTER) /* center */
1318 {
1319 char *savedp = p;
1320 int half = (w+1) / 2; /* half of cushion space */
1321
1322 p[w] = '\0';
1323
1324 /* move str to center of buffer */
1325 while (--p >= dest)
1326 p[half] = *p;
1327
1328 /* fill rhs */
1329 p = savedp + half;
1330 while (--w >= half)
1331 *p++ = m_pad_char;
1332
1333 /* fill lhs */
1334 while (half--)
1335 dest[half] = m_pad_char;
1336 }
1337 else /* left justify */
1338 {
1339 while (--w >= 0)
1340 *p++ = m_pad_char;
1341 *p = '\0';
1342 }
1343}
1344
1345/*
1346 * This formats a string rather like
1347 * snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
1348 * snprintf (dest, destlen, fmt, s);
1349 * except that the numbers in the conversion specification refer to
1350 * the number of character cells when printed.
1351 */
1352
1353static void mutt_format_s_x (char *dest,
1354 size_t destlen,
1355 const char *prefix,
1356 const char *s,
1357 int arboreal)
1358{
1359 int justify = FMT_RIGHT;
1360 char *p;
1361 int min_width;
1362 int max_width = INT_MAX;
1363
1364 if (*prefix == '-')
1365 ++prefix, justify = FMT_LEFT;
1366 else if (*prefix == '=')
1367 ++prefix, justify = FMT_CENTER;
1368 min_width = strtol (prefix, &p, 10);
1369 if (*p == '.')
1370 {
1371 prefix = p + 1;
1372 max_width = strtol (prefix, &p, 10);
1373 if (p <= prefix)
1374 max_width = INT_MAX;
1375 }
1376
1377 mutt_format_string (dest, destlen, min_width, max_width,
1378 justify, ' ', s, mutt_strlen (s), arboreal);
1379}
1380
1381void mutt_format_s (char *dest,
1382 size_t destlen,
1383 const char *prefix,
1384 const char *s)
1385{
1386 mutt_format_s_x (dest, destlen, prefix, s, 0);
1387}
1388
1389void mutt_format_s_tree (char *dest,
1390 size_t destlen,
1391 const char *prefix,
1392 const char *s)
1393{
1394 mutt_format_s_x (dest, destlen, prefix, s, 1);
1395}
1396
1397/*
1398 * mutt_paddstr (n, s) is almost equivalent to
1399 * mutt_format_string (bigbuf, big, n, n, FMT_LEFT, ' ', s, big, 0), addstr (bigbuf)
1400 */
1401
1402void mutt_paddstr (int n, const char *s)
1403{
1404 wchar_t wc;
1405 int w;
1406 size_t k;
1407 size_t len = mutt_strlen (s);
1408 mbstate_t mbstate;
1409
1410 memset (&mbstate, 0, sizeof (mbstate));
1411 for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k)
1412 {
1413 if (k == (size_t)(-1) || k == (size_t)(-2))
1414 {
1415 if (k == (size_t) (-1))
1416 memset (&mbstate, 0, sizeof (mbstate));
1417 k = (k == (size_t)(-1)) ? 1 : len;
1418 wc = replacement_char ();
1419 }
1420 if (!IsWPrint (wc))
1421 wc = '?';
1422 w = wcwidth (wc);
1423 if (w >= 0)
1424 {
1425 if (w > n)
1426 break;
1427 addnstr ((char *)s, k);
1428 n -= w;
1429 }
1430 }
1431 while (n-- > 0)
1432 addch (' ');
1433}
1434
1435/* See how many bytes to copy from string so its at most maxlen bytes
1436 * long and maxwid columns wide */
1437size_t mutt_wstr_trunc (const char *src, size_t maxlen, size_t maxwid, size_t *width)
1438{
1439 wchar_t wc;
1440 size_t n, w = 0, l = 0, cl;
1441 int cw;
1442 mbstate_t mbstate;
1443
1444 if (!src)
1445 goto out;
1446
1447 n = mutt_strlen (src);
1448
1449 memset (&mbstate, 0, sizeof (mbstate));
1450 for (w = 0; n && (cl = mbrtowc (&wc, src, n, &mbstate)); src += cl, n -= cl)
1451 {
1452 if (cl == (size_t)(-1) || cl == (size_t)(-2))
1453 {
1454 if (cl == (size_t)(-1))
1455 memset (&mbstate, 0, sizeof (mbstate));
1456 cl = (cl == (size_t)(-1)) ? 1 : n;
1457 wc = replacement_char ();
1458 }
1459 cw = wcwidth (wc);
1460 /* hack because MUTT_TREE symbols aren't turned into characters
1461 * until rendered by print_enriched_string (#3364) */
1462 if (cw < 0 && cl == 1 && src[0] && src[0] < MUTT_TREE_MAX)
1463 cw = 1;
1464 else if (cw < 0)
1465 cw = 0; /* unprintable wchar */
1466 if (cl + l > maxlen || cw + w > maxwid)
1467 break;
1468 l += cl;
1469 w += cw;
1470 }
1471out:
1472 if (width)
1473 *width = w;
1474 return l;
1475}
1476
1477/*
1478 * returns the number of bytes the first (multibyte) character
1479 * of input consumes:
1480 * < 0 ... conversion error
1481 * = 0 ... end of input
1482 * > 0 ... length (bytes)
1483 */
1484int mutt_charlen (const char *s, int *width)
1485{
1486 wchar_t wc;
1487 mbstate_t mbstate;
1488 size_t k, n;
1489
1490 if (!s || !*s)
1491 return 0;
1492
1493 n = mutt_strlen (s);
1494 memset (&mbstate, 0, sizeof (mbstate));
1495 k = mbrtowc (&wc, s, n, &mbstate);
1496 if (width)
1497 *width = wcwidth (wc);
1498 return (k == (size_t)(-1) || k == (size_t)(-2)) ? -1 : k;
1499}
1500
1501/*
1502 * mutt_strwidth is like mutt_strlen except that it returns the width
1503 * referring to the number of character cells.
1504 */
1505
1506int mutt_strwidth (const char *s)
1507{
1508 wchar_t wc;
1509 int w;
1510 size_t k, n;
1511 mbstate_t mbstate;
1512
1513 if (!s) return 0;
1514
1515 n = mutt_strlen (s);
1516
1517 memset (&mbstate, 0, sizeof (mbstate));
1518 for (w=0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k)
1519 {
1520 if (k == (size_t)(-1) || k == (size_t)(-2))
1521 {
1522 if (k == (size_t)(-1))
1523 memset (&mbstate, 0, sizeof (mbstate));
1524 k = (k == (size_t)(-1)) ? 1 : n;
1525 wc = replacement_char ();
1526 }
1527 if (!IsWPrint (wc))
1528 wc = '?';
1529 w += wcwidth (wc);
1530 }
1531 return w;
1532}