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
30#include <termios.h>
31#include <sys/types.h>
32#include <fcntl.h>
33#include <stdlib.h>
34#include <unistd.h>
35#include <string.h>
36#include <errno.h>
37#include <ctype.h>
38#ifdef HAVE_SYS_TIME_H
39# include <sys/time.h>
40#endif
41#include <time.h>
42
43#ifdef HAVE_LANGINFO_YESEXPR
44#include <langinfo.h>
45#endif
46
47/* not possible to unget more than one char under some curses libs, and it
48 * is impossible to unget function keys in SLang, so roll our own input
49 * buffering routines.
50 */
51
52/* These are used for macros and exec/push commands.
53 * They can be temporarily ignored by setting OPTIGNOREMACROEVENTS
54 */
55static size_t MacroBufferCount = 0;
56static size_t MacroBufferLen = 0;
57static event_t *MacroEvents;
58
59/* These are used in all other "normal" situations, and are not
60 * ignored when setting OPTIGNOREMACROEVENTS
61 */
62static size_t UngetCount = 0;
63static size_t UngetLen = 0;
64static event_t *UngetKeyEvents;
65
66mutt_window_t *MuttHelpWindow = NULL;
67mutt_window_t *MuttIndexWindow = NULL;
68mutt_window_t *MuttStatusWindow = NULL;
69mutt_window_t *MuttMessageWindow = NULL;
70#ifdef USE_SIDEBAR
71mutt_window_t *MuttSidebarWindow = NULL;
72#endif
73
74void mutt_refresh (void)
75{
76 /* don't refresh when we are waiting for a child. */
77 if (option (OPTKEEPQUIET))
78 return;
79
80 /* don't refresh in the middle of macros unless necessary */
81 if (UngetCount && !option (OPTFORCEREFRESH))
82 return;
83
84 /* else */
85 refresh ();
86}
87
88/* Make sure that the next refresh does a full refresh. This could be
89 optimized by not doing it at all if DISPLAY is set as this might
90 indicate that a GUI based pinentry was used. Having an option to
91 customize this is of course the Mutt way. */
92void mutt_need_hard_redraw (void)
93{
94 keypad (stdscr, TRUE);
95 clearok (stdscr, TRUE);
96 set_option (OPTNEEDREDRAW);
97}
98
99event_t mutt_getch (void)
100{
101 int ch;
102 event_t err = {-1, OP_NULL }, ret;
103 event_t timeout = {-2, OP_NULL};
104
105 if (UngetCount)
106 return (UngetKeyEvents[--UngetCount]);
107
108 if (!option(OPTIGNOREMACROEVENTS) && MacroBufferCount)
109 return (MacroEvents[--MacroBufferCount]);
110
111 SigInt = 0;
112
113 mutt_allow_interrupt (1);
114#ifdef KEY_RESIZE
115 /* ncurses 4.2 sends this when the screen is resized */
116 ch = KEY_RESIZE;
117 while (ch == KEY_RESIZE)
118#endif /* KEY_RESIZE */
119 ch = getch ();
120 mutt_allow_interrupt (0);
121
122 if (SigInt)
123 {
124 mutt_query_exit ();
125 return err;
126 }
127
128 /* either timeout, a sigwinch (if timeout is set), or the terminal
129 * has been lost */
130 if (ch == ERR)
131 {
132 if (!isatty (0))
133 {
134 endwin ();
135 exit (1);
136 }
137 return timeout;
138 }
139
140 if ((ch & 0x80) && option (OPTMETAKEY))
141 {
142 /* send ALT-x as ESC-x */
143 ch &= ~0x80;
144 mutt_unget_event (ch, 0);
145 ret.ch = '\033';
146 ret.op = 0;
147 return ret;
148 }
149
150 ret.ch = ch;
151 ret.op = 0;
152 return (ch == ctrl ('G') ? err : ret);
153}
154
155int _mutt_get_field (const char *field, char *buf, size_t buflen, int complete, int multiple, char ***files, int *numfiles)
156{
157 int ret;
158 int x;
159
160 ENTER_STATE *es = mutt_new_enter_state();
161
162 do
163 {
164 mutt_window_clearline (MuttMessageWindow, 0);
165 SETCOLOR (MT_COLOR_PROMPT);
166 addstr ((char *)field); /* cast to get around bad prototypes */
167 NORMAL_COLOR;
168 mutt_refresh ();
169 mutt_window_getyx (MuttMessageWindow, NULL, &x);
170 ret = _mutt_enter_string (buf, buflen, x, complete, multiple, files, numfiles, es);
171 }
172 while (ret == 1);
173 mutt_window_clearline (MuttMessageWindow, 0);
174 mutt_free_enter_state (&es);
175
176 return (ret);
177}
178
179int mutt_get_field_unbuffered (char *msg, char *buf, size_t buflen, int flags)
180{
181 int rc;
182
183 set_option (OPTIGNOREMACROEVENTS);
184 rc = mutt_get_field (msg, buf, buflen, flags);
185 unset_option (OPTIGNOREMACROEVENTS);
186
187 return (rc);
188}
189
190void mutt_clear_error (void)
191{
192 Errorbuf[0] = 0;
193 if (!option(OPTNOCURSES))
194 mutt_window_clearline (MuttMessageWindow, 0);
195}
196
197void mutt_edit_file (const char *editor, const char *data)
198{
199 char cmd[LONG_STRING];
200
201 mutt_endwin (NULL);
202 mutt_expand_file_fmt (cmd, sizeof (cmd), editor, data);
203 if (mutt_system (cmd))
204 {
205 mutt_error (_("Error running \"%s\"!"), cmd);
206 mutt_sleep (2);
207 }
208#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
209 /* the terminal may have been resized while the editor owned it */
210 mutt_resize_screen ();
211#endif
212 keypad (stdscr, TRUE);
213 clearok (stdscr, TRUE);
214}
215
216int mutt_yesorno (const char *msg, int def)
217{
218 event_t ch;
219 char *yes = _("yes");
220 char *no = _("no");
221 char *answer_string;
222 size_t answer_string_len;
223 size_t msglen;
224
225#ifdef HAVE_LANGINFO_YESEXPR
226 char *expr;
227 regex_t reyes;
228 regex_t reno;
229 int reyes_ok;
230 int reno_ok;
231 char answer[2];
232
233 answer[1] = 0;
234
235 reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
236 !REGCOMP (&reyes, expr, REG_NOSUB);
237 reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
238 !REGCOMP (&reno, expr, REG_NOSUB);
239#endif
240
241 mutt_window_clearline (MuttMessageWindow, 0);
242
243 /*
244 * In order to prevent the default answer to the question to wrapped
245 * around the screen in the even the question is wider than the screen,
246 * ensure there is enough room for the answer and truncate the question
247 * to fit.
248 */
249 safe_asprintf (&answer_string, " ([%s]/%s): ", def == MUTT_YES ? yes : no, def == MUTT_YES ? no : yes);
250 answer_string_len = mutt_strwidth (answer_string);
251 /* maxlen here is sort of arbitrary, so pick a reasonable upper bound */
252 msglen = mutt_wstr_trunc (msg, 4*MuttMessageWindow->cols, MuttMessageWindow->cols - answer_string_len, NULL);
253 SETCOLOR (MT_COLOR_PROMPT);
254 addnstr (msg, msglen);
255 addstr (answer_string);
256 NORMAL_COLOR;
257 FREE (&answer_string);
258
259 FOREVER
260 {
261 mutt_refresh ();
262 ch = mutt_getch ();
263 if (CI_is_return (ch.ch))
264 break;
265 if (ch.ch < 0)
266 {
267 def = -1;
268 break;
269 }
270
271#ifdef HAVE_LANGINFO_YESEXPR
272 answer[0] = ch.ch;
273 if (reyes_ok ?
274 (regexec (& reyes, answer, 0, 0, 0) == 0) :
275#else
276 if (
277#endif
278 (tolower (ch.ch) == 'y'))
279 {
280 def = MUTT_YES;
281 break;
282 }
283 else if (
284#ifdef HAVE_LANGINFO_YESEXPR
285 reno_ok ?
286 (regexec (& reno, answer, 0, 0, 0) == 0) :
287#endif
288 (tolower (ch.ch) == 'n'))
289 {
290 def = MUTT_NO;
291 break;
292 }
293 else
294 {
295 BEEP();
296 }
297 }
298
299#ifdef HAVE_LANGINFO_YESEXPR
300 if (reyes_ok)
301 regfree (& reyes);
302 if (reno_ok)
303 regfree (& reno);
304#endif
305
306 if (def != -1)
307 {
308 addstr ((char *) (def == MUTT_YES ? yes : no));
309 mutt_refresh ();
310 }
311 else
312 {
313 /* when the users cancels with ^G, clear the message stored with
314 * mutt_message() so it isn't displayed when the screen is refreshed. */
315 mutt_clear_error();
316 }
317 return (def);
318}
319
320/* this function is called when the user presses the abort key */
321void mutt_query_exit (void)
322{
323 mutt_flushinp ();
324 curs_set (1);
325 if (Timeout)
326 timeout (-1); /* restore blocking operation */
327 if (mutt_yesorno (_("Exit Mutt?"), MUTT_YES) == MUTT_YES)
328 {
329 endwin ();
330 exit (1);
331 }
332 mutt_clear_error();
333 mutt_curs_set (-1);
334 SigInt = 0;
335}
336
337static void curses_message (int error, const char *fmt, va_list ap)
338{
339 char scratch[LONG_STRING];
340
341 vsnprintf (scratch, sizeof (scratch), fmt, ap);
342
343 dprint (1, (debugfile, "%s\n", scratch));
344 mutt_format_string (Errorbuf, sizeof (Errorbuf),
345 0, MuttMessageWindow->cols, FMT_LEFT, 0, scratch, sizeof (scratch), 0);
346
347 if (!option (OPTKEEPQUIET))
348 {
349 if (error)
350 BEEP ();
351 SETCOLOR (error ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
352 mutt_window_mvaddstr (MuttMessageWindow, 0, 0, Errorbuf);
353 NORMAL_COLOR;
354 mutt_window_clrtoeol (MuttMessageWindow);
355 mutt_refresh ();
356 }
357
358 if (error)
359 set_option (OPTMSGERR);
360 else
361 unset_option (OPTMSGERR);
362}
363
364void mutt_curses_error (const char *fmt, ...)
365{
366 va_list ap;
367
368 va_start (ap, fmt);
369 curses_message (1, fmt, ap);
370 va_end (ap);
371}
372
373void mutt_curses_message (const char *fmt, ...)
374{
375 va_list ap;
376
377 va_start (ap, fmt);
378 curses_message (0, fmt, ap);
379 va_end (ap);
380}
381
382void mutt_progress_init (progress_t* progress, const char *msg,
383 unsigned short flags, unsigned short inc,
384 long size)
385{
386 struct timeval tv = { 0, 0 };
387
388 if (!progress)
389 return;
390 if (option(OPTNOCURSES))
391 return;
392
393 memset (progress, 0, sizeof (progress_t));
394 progress->inc = inc;
395 progress->flags = flags;
396 progress->msg = msg;
397 progress->size = size;
398 if (progress->size) {
399 if (progress->flags & MUTT_PROGRESS_SIZE)
400 mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
401 progress->size);
402 else
403 snprintf (progress->sizestr, sizeof (progress->sizestr), "%ld",
404 progress->size);
405 }
406 if (!inc)
407 {
408 if (size)
409 mutt_message ("%s (%s)", msg, progress->sizestr);
410 else
411 mutt_message (msg);
412 return;
413 }
414 if (gettimeofday (&tv, NULL) < 0)
415 dprint (1, (debugfile, "gettimeofday failed: %d\n", errno));
416 /* if timestamp is 0 no time-based suppression is done */
417 if (TimeInc)
418 progress->timestamp = ((unsigned int) tv.tv_sec * 1000)
419 + (unsigned int) (tv.tv_usec / 1000);
420 mutt_progress_update (progress, 0, 0);
421}
422
423void mutt_progress_update (progress_t* progress, long pos, int percent)
424{
425 char posstr[SHORT_STRING];
426 short update = 0;
427 struct timeval tv = { 0, 0 };
428 unsigned int now = 0;
429
430 if (option(OPTNOCURSES))
431 return;
432
433 if (!progress->inc)
434 goto out;
435
436 /* refresh if size > inc */
437 if (progress->flags & MUTT_PROGRESS_SIZE &&
438 (pos >= progress->pos + (progress->inc << 10)))
439 update = 1;
440 else if (pos >= progress->pos + progress->inc)
441 update = 1;
442
443 /* skip refresh if not enough time has passed */
444 if (update && progress->timestamp && !gettimeofday (&tv, NULL)) {
445 now = ((unsigned int) tv.tv_sec * 1000)
446 + (unsigned int) (tv.tv_usec / 1000);
447 if (now && now - progress->timestamp < TimeInc)
448 update = 0;
449 }
450
451 /* always show the first update */
452 if (!pos)
453 update = 1;
454
455 if (update)
456 {
457 if (progress->flags & MUTT_PROGRESS_SIZE)
458 {
459 pos = pos / (progress->inc << 10) * (progress->inc << 10);
460 mutt_pretty_size (posstr, sizeof (posstr), pos);
461 }
462 else
463 snprintf (posstr, sizeof (posstr), "%ld", pos);
464
465 dprint (5, (debugfile, "updating progress: %s\n", posstr));
466
467 progress->pos = pos;
468 if (now)
469 progress->timestamp = now;
470
471 if (progress->size > 0)
472 {
473 mutt_message ("%s %s/%s (%d%%)", progress->msg, posstr, progress->sizestr,
474 percent > 0 ? percent :
475 (int) (100.0 * (double) progress->pos / progress->size));
476 }
477 else
478 {
479 if (percent > 0)
480 mutt_message ("%s %s (%d%%)", progress->msg, posstr, percent);
481 else
482 mutt_message ("%s %s", progress->msg, posstr);
483 }
484 }
485
486out:
487 if (pos >= progress->size)
488 mutt_clear_error ();
489}
490
491void mutt_init_windows ()
492{
493 MuttHelpWindow = safe_calloc (sizeof (mutt_window_t), 1);
494 MuttIndexWindow = safe_calloc (sizeof (mutt_window_t), 1);
495 MuttStatusWindow = safe_calloc (sizeof (mutt_window_t), 1);
496 MuttMessageWindow = safe_calloc (sizeof (mutt_window_t), 1);
497#ifdef USE_SIDEBAR
498 MuttSidebarWindow = safe_calloc (sizeof (mutt_window_t), 1);
499#endif
500}
501
502void mutt_free_windows ()
503{
504 FREE (&MuttHelpWindow);
505 FREE (&MuttIndexWindow);
506 FREE (&MuttStatusWindow);
507 FREE (&MuttMessageWindow);
508#ifdef USE_SIDEBAR
509 FREE (&MuttSidebarWindow);
510#endif
511}
512
513void mutt_reflow_windows (void)
514{
515 if (option (OPTNOCURSES))
516 return;
517
518 dprint (2, (debugfile, "In mutt_reflow_windows\n"));
519
520 MuttStatusWindow->rows = 1;
521 MuttStatusWindow->cols = COLS;
522 MuttStatusWindow->row_offset = option (OPTSTATUSONTOP) ? 0 : LINES - 2;
523 MuttStatusWindow->col_offset = 0;
524
525 memcpy (MuttHelpWindow, MuttStatusWindow, sizeof (mutt_window_t));
526 if (! option (OPTHELP))
527 MuttHelpWindow->rows = 0;
528 else
529 MuttHelpWindow->row_offset = option (OPTSTATUSONTOP) ? LINES - 2 : 0;
530
531 memcpy (MuttMessageWindow, MuttStatusWindow, sizeof (mutt_window_t));
532 MuttMessageWindow->row_offset = LINES - 1;
533
534 memcpy (MuttIndexWindow, MuttStatusWindow, sizeof (mutt_window_t));
535 MuttIndexWindow->rows = MAX(LINES - MuttStatusWindow->rows -
536 MuttHelpWindow->rows - MuttMessageWindow->rows, 0);
537 MuttIndexWindow->row_offset = option (OPTSTATUSONTOP) ? MuttStatusWindow->rows :
538 MuttHelpWindow->rows;
539
540#ifdef USE_SIDEBAR
541 if (option (OPTSIDEBAR))
542 {
543 memcpy (MuttSidebarWindow, MuttIndexWindow, sizeof (mutt_window_t));
544 MuttSidebarWindow->cols = SidebarWidth;
545
546 MuttIndexWindow->cols -= SidebarWidth;
547 MuttIndexWindow->col_offset += SidebarWidth;
548 }
549#endif
550}
551
552int mutt_window_move (mutt_window_t *win, int row, int col)
553{
554 return move (win->row_offset + row, win->col_offset + col);
555}
556
557int mutt_window_mvaddch (mutt_window_t *win, int row, int col, const chtype ch)
558{
559 return mvaddch (win->row_offset + row, win->col_offset + col, ch);
560}
561
562int mutt_window_mvaddstr (mutt_window_t *win, int row, int col, const char *str)
563{
564 return mvaddstr (win->row_offset + row, win->col_offset + col, str);
565}
566
567#ifdef USE_SLANG_CURSES
568static int vw_printw (SLcurses_Window_Type *win, const char *fmt, va_list ap)
569{
570 char buf[LONG_STRING];
571
572 (void) SLvsnprintf (buf, sizeof (buf), (char *)fmt, ap);
573 SLcurses_waddnstr (win, buf, -1);
574 return 0;
575}
576#endif
577
578int mutt_window_mvprintw (mutt_window_t *win, int row, int col, const char *fmt, ...)
579{
580 va_list ap;
581 int rv;
582
583 if ((rv = mutt_window_move (win, row, col) != ERR))
584 {
585 va_start (ap, fmt);
586 rv = vw_printw (stdscr, fmt, ap);
587 va_end (ap);
588 }
589
590 return rv;
591}
592
593/* Assumes the cursor has already been positioned within the
594 * window.
595 */
596void mutt_window_clrtoeol (mutt_window_t *win)
597{
598 int row, col, curcol;
599
600 if (win->col_offset + win->cols == COLS)
601 clrtoeol ();
602 else
603 {
604 getyx (stdscr, row, col);
605 curcol = col;
606 while (curcol < win->col_offset + win->cols)
607 {
608 addch (' ');
609 curcol++;
610 }
611 move (row, col);
612 }
613}
614
615void mutt_window_clearline (mutt_window_t *win, int row)
616{
617 mutt_window_move (win, row, 0);
618 mutt_window_clrtoeol (win);
619}
620
621/* Assumes the current position is inside the window.
622 * Otherwise it will happily return negative or values outside
623 * the window boundaries
624 */
625void mutt_window_getyx (mutt_window_t *win, int *y, int *x)
626{
627 int row, col;
628
629 getyx (stdscr, row, col);
630 if (y)
631 *y = row - win->row_offset;
632 if (x)
633 *x = col - win->col_offset;
634}
635
636
637void mutt_show_error (void)
638{
639 if (option (OPTKEEPQUIET))
640 return;
641
642 SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
643 mutt_window_mvaddstr (MuttMessageWindow, 0, 0, Errorbuf);
644 NORMAL_COLOR;
645 mutt_window_clrtoeol(MuttMessageWindow);
646}
647
648void mutt_endwin (const char *msg)
649{
650 int e = errno;
651
652 if (!option (OPTNOCURSES))
653 {
654 /* at least in some situations (screen + xterm under SuSE11/12) endwin()
655 * doesn't properly flush the screen without an explicit call.
656 */
657 mutt_refresh();
658 endwin ();
659 }
660
661 if (msg && *msg)
662 {
663 puts (msg);
664 fflush (stdout);
665 }
666
667 errno = e;
668}
669
670void mutt_perror (const char *s)
671{
672 char *p = strerror (errno);
673
674 dprint (1, (debugfile, "%s: %s (errno = %d)\n", s,
675 p ? p : "unknown error", errno));
676 mutt_error ("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
677}
678
679int mutt_any_key_to_continue (const char *s)
680{
681 struct termios t;
682 struct termios old;
683 int f, ch;
684
685 f = open ("/dev/tty", O_RDONLY);
686 tcgetattr (f, &t);
687 memcpy ((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */
688 t.c_lflag &= ~(ICANON | ECHO);
689 t.c_cc[VMIN] = 1;
690 t.c_cc[VTIME] = 0;
691 tcsetattr (f, TCSADRAIN, &t);
692 fflush (stdout);
693 if (s)
694 fputs (s, stdout);
695 else
696 fputs (_("Press any key to continue..."), stdout);
697 fflush (stdout);
698 ch = fgetc (stdin);
699 fflush (stdin);
700 tcsetattr (f, TCSADRAIN, &old);
701 close (f);
702 fputs ("\r\n", stdout);
703 mutt_clear_error ();
704 return (ch);
705}
706
707int mutt_do_pager (const char *banner,
708 const char *tempfile,
709 int do_color,
710 pager_t *info)
711{
712 int rc;
713
714 if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
715 rc = mutt_pager (banner, tempfile, do_color, info);
716 else
717 {
718 char cmd[STRING];
719
720 mutt_endwin (NULL);
721 mutt_expand_file_fmt (cmd, sizeof(cmd), Pager, tempfile);
722 if (mutt_system (cmd) == -1)
723 {
724 mutt_error (_("Error running \"%s\"!"), cmd);
725 rc = -1;
726 }
727 else
728 rc = 0;
729 mutt_unlink (tempfile);
730 }
731
732 return rc;
733}
734
735int _mutt_enter_fname (const char *prompt, char *buf, size_t blen, int *redraw, int buffy, int multiple, char ***files, int *numfiles)
736{
737 event_t ch;
738
739 SETCOLOR (MT_COLOR_PROMPT);
740 mutt_window_mvaddstr (MuttMessageWindow, 0, 0, (char *) prompt);
741 addstr (_(" ('?' for list): "));
742 NORMAL_COLOR;
743 if (buf[0])
744 addstr (buf);
745 mutt_window_clrtoeol (MuttMessageWindow);
746 mutt_refresh ();
747
748 ch = mutt_getch();
749 if (ch.ch < 0)
750 {
751 mutt_window_clearline (MuttMessageWindow, 0);
752 return (-1);
753 }
754 else if (ch.ch == '?')
755 {
756 mutt_refresh ();
757 buf[0] = 0;
758 _mutt_select_file (buf, blen, MUTT_SEL_FOLDER | (multiple ? MUTT_SEL_MULTI : 0),
759 files, numfiles);
760 *redraw = REDRAW_FULL;
761 }
762 else
763 {
764 char *pc = safe_malloc (mutt_strlen (prompt) + 3);
765
766 sprintf (pc, "%s: ", prompt); /* __SPRINTF_CHECKED__ */
767 mutt_unget_event (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
768 if (_mutt_get_field (pc, buf, blen, (buffy ? MUTT_EFILE : MUTT_FILE) | MUTT_CLEAR, multiple, files, numfiles)
769 != 0)
770 buf[0] = 0;
771 MAYBE_REDRAW (*redraw);
772 FREE (&pc);
773 }
774
775 return 0;
776}
777
778void mutt_unget_event (int ch, int op)
779{
780 event_t tmp;
781
782 tmp.ch = ch;
783 tmp.op = op;
784
785 if (UngetCount >= UngetLen)
786 safe_realloc (&UngetKeyEvents, (UngetLen += 16) * sizeof(event_t));
787
788 UngetKeyEvents[UngetCount++] = tmp;
789}
790
791void mutt_unget_string (char *s)
792{
793 char *p = s + mutt_strlen (s) - 1;
794
795 while (p >= s)
796 {
797 mutt_unget_event ((unsigned char)*p--, 0);
798 }
799}
800
801/*
802 * Adds the ch/op to the macro buffer.
803 * This should be used for macros, push, and exec commands only.
804 */
805void mutt_push_macro_event (int ch, int op)
806{
807 event_t tmp;
808
809 tmp.ch = ch;
810 tmp.op = op;
811
812 if (MacroBufferCount >= MacroBufferLen)
813 safe_realloc (&MacroEvents, (MacroBufferLen += 128) * sizeof(event_t));
814
815 MacroEvents[MacroBufferCount++] = tmp;
816}
817
818void mutt_flush_macro_to_endcond (void)
819{
820 UngetCount = 0;
821 while (MacroBufferCount > 0)
822 {
823 if (MacroEvents[--MacroBufferCount].op == OP_END_COND)
824 return;
825 }
826}
827
828void mutt_flushinp (void)
829{
830 UngetCount = 0;
831 MacroBufferCount = 0;
832 flushinp ();
833}
834
835#if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
836/* The argument can take 3 values:
837 * -1: restore the value of the last call
838 * 0: make the cursor invisible
839 * 1: make the cursor visible
840 */
841void mutt_curs_set (int cursor)
842{
843 static int SavedCursor = 1;
844
845 if (cursor < 0)
846 cursor = SavedCursor;
847 else
848 SavedCursor = cursor;
849
850 if (curs_set (cursor) == ERR) {
851 if (cursor == 1) /* cnorm */
852 curs_set (2); /* cvvis */
853 }
854}
855#endif
856
857int mutt_multi_choice (char *prompt, char *letters)
858{
859 event_t ch;
860 int choice;
861 char *p;
862
863 SETCOLOR (MT_COLOR_PROMPT);
864 mutt_window_mvaddstr (MuttMessageWindow, 0, 0, prompt);
865 NORMAL_COLOR;
866 mutt_window_clrtoeol (MuttMessageWindow);
867 FOREVER
868 {
869 mutt_refresh ();
870 ch = mutt_getch ();
871 /* (ch.ch == 0) is technically possible. Treat the same as < 0 (abort) */
872 if (ch.ch <= 0 || CI_is_return (ch.ch))
873 {
874 choice = -1;
875 break;
876 }
877 else
878 {
879 p = strchr (letters, ch.ch);
880 if (p)
881 {
882 choice = p - letters + 1;
883 break;
884 }
885 else if (ch.ch <= '9' && ch.ch > '0')
886 {
887 choice = ch.ch - '0';
888 if (choice <= mutt_strlen (letters))
889 break;
890 }
891 }
892 BEEP ();
893 }
894 mutt_window_clearline (MuttMessageWindow, 0);
895 mutt_refresh ();
896 return choice;
897}
898
899/*
900 * addwch would be provided by an up-to-date curses library
901 */
902
903int mutt_addwch (wchar_t wc)
904{
905 char buf[MB_LEN_MAX*2];
906 mbstate_t mbstate;
907 size_t n1, n2;
908
909 memset (&mbstate, 0, sizeof (mbstate));
910 if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t)(-1) ||
911 (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t)(-1))
912 return -1; /* ERR */
913 else
914 return addstr (buf);
915}
916
917
918/*
919 * This formats a string, a bit like
920 * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
921 * except that the widths refer to the number of character cells
922 * when printed.
923 */
924
925void mutt_format_string (char *dest, size_t destlen,
926 int min_width, int max_width,
927 int justify, char m_pad_char,
928 const char *s, size_t n,
929 int arboreal)
930{
931 char *p;
932 wchar_t wc;
933 int w;
934 size_t k, k2;
935 char scratch[MB_LEN_MAX];
936 mbstate_t mbstate1, mbstate2;
937
938 memset(&mbstate1, 0, sizeof (mbstate1));
939 memset(&mbstate2, 0, sizeof (mbstate2));
940 --destlen;
941 p = dest;
942 for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k)
943 {
944 if (k == (size_t)(-1) || k == (size_t)(-2))
945 {
946 if (k == (size_t)(-1) && errno == EILSEQ)
947 memset (&mbstate1, 0, sizeof (mbstate1));
948
949 k = (k == (size_t)(-1)) ? 1 : n;
950 wc = replacement_char ();
951 }
952 if (arboreal && wc < MUTT_TREE_MAX)
953 w = 1; /* hack */
954 else
955 {
956#ifdef HAVE_ISWBLANK
957 if (iswblank (wc))
958 wc = ' ';
959 else
960#endif
961 if (!IsWPrint (wc))
962 wc = '?';
963 w = wcwidth (wc);
964 }
965 if (w >= 0)
966 {
967 if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
968 break;
969 min_width -= w;
970 max_width -= w;
971 strncpy (p, scratch, k2);
972 p += k2;
973 destlen -= k2;
974 }
975 }
976 w = (int)destlen < min_width ? destlen : min_width;
977 if (w <= 0)
978 *p = '\0';
979 else if (justify == FMT_RIGHT) /* right justify */
980 {
981 p[w] = '\0';
982 while (--p >= dest)
983 p[w] = *p;
984 while (--w >= 0)
985 dest[w] = m_pad_char;
986 }
987 else if (justify == FMT_CENTER) /* center */
988 {
989 char *savedp = p;
990 int half = (w+1) / 2; /* half of cushion space */
991
992 p[w] = '\0';
993
994 /* move str to center of buffer */
995 while (--p >= dest)
996 p[half] = *p;
997
998 /* fill rhs */
999 p = savedp + half;
1000 while (--w >= half)
1001 *p++ = m_pad_char;
1002
1003 /* fill lhs */
1004 while (half--)
1005 dest[half] = m_pad_char;
1006 }
1007 else /* left justify */
1008 {
1009 while (--w >= 0)
1010 *p++ = m_pad_char;
1011 *p = '\0';
1012 }
1013}
1014
1015/*
1016 * This formats a string rather like
1017 * snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
1018 * snprintf (dest, destlen, fmt, s);
1019 * except that the numbers in the conversion specification refer to
1020 * the number of character cells when printed.
1021 */
1022
1023static void mutt_format_s_x (char *dest,
1024 size_t destlen,
1025 const char *prefix,
1026 const char *s,
1027 int arboreal)
1028{
1029 int justify = FMT_RIGHT;
1030 char *p;
1031 int min_width;
1032 int max_width = INT_MAX;
1033
1034 if (*prefix == '-')
1035 ++prefix, justify = FMT_LEFT;
1036 else if (*prefix == '=')
1037 ++prefix, justify = FMT_CENTER;
1038 min_width = strtol (prefix, &p, 10);
1039 if (*p == '.')
1040 {
1041 prefix = p + 1;
1042 max_width = strtol (prefix, &p, 10);
1043 if (p <= prefix)
1044 max_width = INT_MAX;
1045 }
1046
1047 mutt_format_string (dest, destlen, min_width, max_width,
1048 justify, ' ', s, mutt_strlen (s), arboreal);
1049}
1050
1051void mutt_format_s (char *dest,
1052 size_t destlen,
1053 const char *prefix,
1054 const char *s)
1055{
1056 mutt_format_s_x (dest, destlen, prefix, s, 0);
1057}
1058
1059void mutt_format_s_tree (char *dest,
1060 size_t destlen,
1061 const char *prefix,
1062 const char *s)
1063{
1064 mutt_format_s_x (dest, destlen, prefix, s, 1);
1065}
1066
1067/*
1068 * mutt_paddstr (n, s) is almost equivalent to
1069 * mutt_format_string (bigbuf, big, n, n, FMT_LEFT, ' ', s, big, 0), addstr (bigbuf)
1070 */
1071
1072void mutt_paddstr (int n, const char *s)
1073{
1074 wchar_t wc;
1075 int w;
1076 size_t k;
1077 size_t len = mutt_strlen (s);
1078 mbstate_t mbstate;
1079
1080 memset (&mbstate, 0, sizeof (mbstate));
1081 for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k)
1082 {
1083 if (k == (size_t)(-1) || k == (size_t)(-2))
1084 {
1085 if (k == (size_t) (-1))
1086 memset (&mbstate, 0, sizeof (mbstate));
1087 k = (k == (size_t)(-1)) ? 1 : len;
1088 wc = replacement_char ();
1089 }
1090 if (!IsWPrint (wc))
1091 wc = '?';
1092 w = wcwidth (wc);
1093 if (w >= 0)
1094 {
1095 if (w > n)
1096 break;
1097 addnstr ((char *)s, k);
1098 n -= w;
1099 }
1100 }
1101 while (n-- > 0)
1102 addch (' ');
1103}
1104
1105/* See how many bytes to copy from string so its at most maxlen bytes
1106 * long and maxwid columns wide */
1107size_t mutt_wstr_trunc (const char *src, size_t maxlen, size_t maxwid, size_t *width)
1108{
1109 wchar_t wc;
1110 size_t n, w = 0, l = 0, cl;
1111 int cw;
1112 mbstate_t mbstate;
1113
1114 if (!src)
1115 goto out;
1116
1117 n = mutt_strlen (src);
1118
1119 memset (&mbstate, 0, sizeof (mbstate));
1120 for (w = 0; n && (cl = mbrtowc (&wc, src, n, &mbstate)); src += cl, n -= cl)
1121 {
1122 if (cl == (size_t)(-1) || cl == (size_t)(-2))
1123 {
1124 if (cl == (size_t)(-1))
1125 memset (&mbstate, 0, sizeof (mbstate));
1126 cl = (cl == (size_t)(-1)) ? 1 : n;
1127 wc = replacement_char ();
1128 }
1129 cw = wcwidth (wc);
1130 /* hack because MUTT_TREE symbols aren't turned into characters
1131 * until rendered by print_enriched_string (#3364) */
1132 if (cw < 0 && cl == 1 && src[0] && src[0] < MUTT_TREE_MAX)
1133 cw = 1;
1134 else if (cw < 0)
1135 cw = 0; /* unprintable wchar */
1136 if (cl + l > maxlen || cw + w > maxwid)
1137 break;
1138 l += cl;
1139 w += cw;
1140 }
1141out:
1142 if (width)
1143 *width = w;
1144 return l;
1145}
1146
1147/*
1148 * returns the number of bytes the first (multibyte) character
1149 * of input consumes:
1150 * < 0 ... conversion error
1151 * = 0 ... end of input
1152 * > 0 ... length (bytes)
1153 */
1154int mutt_charlen (const char *s, int *width)
1155{
1156 wchar_t wc;
1157 mbstate_t mbstate;
1158 size_t k, n;
1159
1160 if (!s || !*s)
1161 return 0;
1162
1163 n = mutt_strlen (s);
1164 memset (&mbstate, 0, sizeof (mbstate));
1165 k = mbrtowc (&wc, s, n, &mbstate);
1166 if (width)
1167 *width = wcwidth (wc);
1168 return (k == (size_t)(-1) || k == (size_t)(-2)) ? -1 : k;
1169}
1170
1171/*
1172 * mutt_strwidth is like mutt_strlen except that it returns the width
1173 * referring to the number of character cells.
1174 */
1175
1176int mutt_strwidth (const char *s)
1177{
1178 wchar_t wc;
1179 int w;
1180 size_t k, n;
1181 mbstate_t mbstate;
1182
1183 if (!s) return 0;
1184
1185 n = mutt_strlen (s);
1186
1187 memset (&mbstate, 0, sizeof (mbstate));
1188 for (w=0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k)
1189 {
1190 if (k == (size_t)(-1) || k == (size_t)(-2))
1191 {
1192 if (k == (size_t)(-1))
1193 memset (&mbstate, 0, sizeof (mbstate));
1194 k = (k == (size_t)(-1)) ? 1 : n;
1195 wc = replacement_char ();
1196 }
1197 if (!IsWPrint (wc))
1198 wc = '?';
1199 w += wcwidth (wc);
1200 }
1201 return w;
1202}