mutt stable branch with some hacks
at jcs 1532 lines 36 kB view raw
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}