]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/gui_improvements'
authorAndre Noll <maan@systemlinux.org>
Fri, 7 Jun 2013 19:34:51 +0000 (21:34 +0200)
committerAndre Noll <maan@systemlinux.org>
Fri, 7 Jun 2013 19:38:17 +0000 (21:38 +0200)
Was cooking for more than one month.

bf1831 string: Speed up xvasprintf().
bcc083 string: Add discard feature for for_each_line().
e3868d string: Simplify return value of for_each_line().
d1f0f0 string: Simplify for_each_line().
23b121 string: Clean up for_each_line() and related functions.
6256ed string: Replace the for_each_line_modes enum by a bitmap.
14e689 gui: Don't sleep before executing the status command.
e90c6c gui: Speed up add_spaces().
20e2c6 gui: Rename do_exit().
ef0508 gui: Remove superfluous cmd_died.
d7562b gui: Check stdin for readability.
a44fa6 gui: Discard overlong input lines.

Conflicts:
gui.c

1  2 
NEWS
configure.ac
gui.c
string.c
string.h

diff --combined NEWS
index 56f84753314b71f7f41b193eb4e7a6df6ce8d82f,f323df1dda766dadfdc7fe559e6a39fa42824d58..a93fb2e3315839de57daef32cf5166cd3852579c
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -2,11 -2,6 +2,12 @@@
  0.?.? (to be announced) "spectral gravity"
  ------------------------------------------
  
 +      - UTF8 support for para_gui and the mp3 audio format handler.
 +      - Scheduler improvements and fixes.
 +      - The obsolete gettimeofday() function has been replaced
 +        by clock_gettime() on systems which support it.
++      - Speed and usability improvements for para_gui.
 +
  -----------------------------------------
  0.4.12 (2012-12-20) "volatile relativity"
  -----------------------------------------
diff --combined configure.ac
index 135a0bd9affc6c74c404d8a2c1ce38fe4bd80479,35c9fd58d6073bdb68c2ee5723225973837480b1..12959c817984ccfffde88f50d7214c9a0ce8e4ce
@@@ -153,7 -153,7 +153,7 @@@ client_errlist_objs="client net string 
  client_ldflags=""
  
  gui_cmdline_objs="add_cmdline(gui)"
- gui_errlist_objs="exec signal string stat ringbuffer fd gui gui_theme"
+ gui_errlist_objs="exec signal string stat ringbuffer fd gui gui_theme time"
  gui_objs="$gui_cmdline_objs $gui_errlist_objs"
  play_errlist_objs="play fd sched ggo buffer_tree time string net
        afh_recv afh_common
@@@ -247,18 -247,6 +247,18 @@@ if test x$ac_cv_have_working_snprintf$a
  AC_MSG_ERROR([fatal: buggy snprintf() detected])
  fi])
  AX_FUNC_SNPRINTF()
 +################################################################## clock_gettime
 +clock_gettime_lib=
 +AC_CHECK_LIB([c], [clock_gettime], [clock_gettime_lib=c], [
 +      AC_CHECK_LIB([rt], [clock_gettime], [clock_gettime_lib=rt], [], [])
 +])
 +if test -n "$clock_gettime_lib"; then
 +      AC_DEFINE(HAVE_CLOCK_GETTIME, 1, [
 +              define to 1 if clock_gettime() is supported])
 +fi
 +if test "$clock_gettime_lib" = "rt"; then
 +      AC_SUBST(clock_gettime_ldflags, -lrt)
 +fi
  ########################################################################### osl
  have_osl=yes
  OLD_CPPFLAGS="$CPPFLAGS"
  AC_CHECK_HEADER(curses.h, [], [
        have_curses="no"
  ])
 -AC_CHECK_LIB([curses], [initscr], [], [
 -      have_curses="no"
 -])
 +gui_ldflags="$curses_libs"
 +AC_CHECK_LIB([ncursesw], [initscr],
 +      [gui_ldflags="$curses_libs -lncursesw"], [
 +              AC_CHECK_LIB([curses], [initscr],
 +                      [gui_ldflags="$curses_libs -lcurses"],
 +                      [have_curses="no"]
 +              )
 +      ]
 +)
  if test "$have_curses" = "yes"; then
        AC_SUBST(curses_cppflags)
 -      AC_DEFINE(HAVE_NCURSES, 1, [define to 1 to turn on curses support])
        extras="$extras gui"
        executables="$executables gui"
  else
@@@ -1260,7 -1243,6 +1260,7 @@@ AC_DEFINE_UNQUOTED(INIT_AUDIOC_ERRLISTS
        objlist_to_errlist($audioc_errlist_objs), errors used by para_audioc)
  
  AC_SUBST(gui_objs, add_dot_o($gui_objs))
 +AC_SUBST(gui_ldflags, $gui_ldflags)
  AC_DEFINE_UNQUOTED(INIT_GUI_ERRLISTS,
        objlist_to_errlist($gui_errlist_objs), errors used by para_gui)
  
@@@ -1269,6 -1251,8 +1269,6 @@@ AC_SUBST(play_ldflags, $play_ldflags
  AC_DEFINE_UNQUOTED(INIT_PLAY_ERRLISTS,
        objlist_to_errlist($play_errlist_objs), errors used by para_play)
  
 -AC_MSG_NOTICE(play objs: $play_objs)
 -
  enum="$(for i in $filters; do printf "${i}_FILTER, " | tr '[a-z]' '[A-Z]'; done)"
  AC_DEFINE_UNQUOTED(FILTER_ENUM, $enum NUM_SUPPORTED_FILTERS,
        enum of supported filters)
diff --combined gui.c
index 631e7cc54113a6e03229f144c4a6a04feba7950d,85d77c9e581d060c5a97c543361aaf95a499ee21..096beb93bfd82f91a68c454779edaa6c59e8a565
--- 1/gui.c
--- 2/gui.c
+++ b/gui.c
@@@ -10,7 -10,7 +10,8 @@@
  #include <signal.h>
  #include <sys/types.h>
  #include <curses.h>
 +#include <locale.h>
+ #include <sys/time.h>
  
  #include "gui.cmdline.h"
  #include "para.h"
@@@ -51,7 -51,7 +52,7 @@@ static struct ringbuffer *bot_win_rb
  
  static unsigned scroll_position;
  
- static int cmd_died, curses_active;
+ static int curses_active;
  static pid_t cmd_pid;
  
  static int command_fds[2];
@@@ -288,14 -288,20 +289,20 @@@ static char *configfile_exists(void
        return file_exists(tmp)? tmp: NULL;
  }
  
- /*
-  * print num spaces to curses window
-  */
+ /* Print given number of spaces to curses window. */
  static void add_spaces(WINDOW* win, unsigned int num)
  {
-       while (num > 0) {
-               num--;
-               waddstr(win, " ");
+       char space[] = "                                ";
+       unsigned sz = sizeof(space);
+       while (num >= sz)  {
+               waddstr(win, space);
+               num -= sz;
+       }
+       if (num > 0) {
+               assert(num < sz);
+               space[num] = '\0';
+               waddstr(win, space);
        }
  }
  
  static int align_str(WINDOW* win, char *str, unsigned int len,
                unsigned int align)
  {
 -      int i, num; /* of spaces */
 +      int ret, i, num; /* of spaces */
 +      size_t width;
  
        if (!win || !str)
 -              return -1;
 -      num = len - strlen(str);
 +              return 0;
 +      ret = strwidth(str, &width);
 +      if (ret < 0) {
 +              PARA_ERROR_LOG("%s\n", para_strerror(-ret));
 +              width = 0;
 +              str[0] = '\0';
 +      }
 +      num = len - width;
        if (num < 0) {
                str[len] = '\0';
                num = 0;
@@@ -399,14 -398,11 +406,14 @@@ static int first_visible_rbe(unsigned *
        return RINGBUFFER_SIZE - 1;
  }
  
 +/*
 +returns number of first visible rbe, *lines is the number of lines drawn.
 + */
  static int draw_top_rbe(unsigned *lines)
  {
 -      unsigned len;
 -      int offset, fvr = first_visible_rbe(lines);
 +      int ret, fvr = first_visible_rbe(lines);
        struct rb_entry *rbe;
 +      size_t bytes_to_skip, cells_to_skip, width;
  
        if (fvr < 0)
                return -1;
        rbe = ringbuffer_get(bot_win_rb, fvr);
        if (!rbe)
                return -1;
 -      len = strlen(rbe->msg);
        if (*lines > bot.lines) {
 -              /* first rbe is only partially visible */
 -              offset = (*lines - bot.lines) * bot.cols;
 -              assert(offset <= len);
 -      } else
 -              offset = 0;
 +              /* rbe is partially visible multi-line */
 +              cells_to_skip = (*lines - bot.lines) * bot.cols;
 +              ret = skip_cells(rbe->msg, cells_to_skip, &bytes_to_skip);
 +              if (ret < 0)
 +                      return ret;
 +              ret = strwidth(rbe->msg + bytes_to_skip, &width);
 +              if (ret < 0)
 +                      return ret;
 +      } else {
 +              bytes_to_skip = 0;
 +              width = rbe->len;
 +      }
        wattron(bot.win, COLOR_PAIR(rbe->color));
 -      waddstr(bot.win, rbe->msg + offset);
 -      *lines = NUM_LINES(len - offset);
 +      waddstr(bot.win, rbe->msg + bytes_to_skip);
 +      *lines = NUM_LINES(width);
        return fvr;
  }
  
@@@ -461,15 -451,10 +468,15 @@@ out
  
  static void rb_add_entry(int color, char *msg)
  {
 -      struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry));
 +      struct rb_entry *old, *new;
        int x, y;
 +      size_t len;
 +
 +      if (strwidth(msg, &len) < 0)
 +              return;
 +      new = para_malloc(sizeof(struct rb_entry));
        new->color = color;
 -      new->len = strlen(msg);
 +      new->len = len;
        new->msg = msg;
        old = ringbuffer_add(bot_win_rb, new);
  //    fprintf(stderr, "added: %s\n", new->msg);
@@@ -555,7 -540,8 +562,8 @@@ static void setup_signal_handling(void
        para_sigaction(SIGHUP, SIG_IGN);
  }
  
- __noreturn static void do_exit(int ret)
+ /* kill every process in the process group and exit */
+ __noreturn static void kill_pg_and_die(int ret)
  {
        para_sigaction(SIGTERM, SIG_IGN);
        kill(0, SIGTERM);
@@@ -574,7 -560,7 +582,7 @@@ static void shutdown_curses(void
  __noreturn static void finish(int ret)
  {
        shutdown_curses();
-       do_exit(ret);
+       kill_pg_and_die(ret);
  }
  
  /*
@@@ -589,7 -575,7 +597,7 @@@ __noreturn __printf_2_3 static void msg
        va_start(argp, fmt);
        vfprintf(outfd, fmt, argp);
        va_end(argp);
-       do_exit(ret);
+       kill_pg_and_die(ret);
  }
  
  static void print_welcome(void)
@@@ -856,10 -842,8 +864,8 @@@ reap_next_child
        ret = para_reap_child(&pid);
        if (ret <= 0)
                return;
-       if (pid == cmd_pid) {
+       if (pid == cmd_pid)
                cmd_pid = 0;
-               cmd_died = 1;
-       }
        goto reap_next_child;
  }
  
@@@ -947,33 -931,36 +953,36 @@@ static void handle_signal(int sig
        }
  }
  
- static int open_stat_pipe(void)
+ static void status_pre_select(fd_set *rfds, int *max_fileno, struct timeval *tv)
  {
-       static int init = 1;
+       static struct timeval next_exec, atm, diff;
        int ret, fds[3] = {0, 1, 0};
        pid_t pid;
  
-       if (init)
-               init = 0;
-       else
-               /*
-                * Sleep a bit to avoid a busy loop. As the call to sleep() may
-                * be interrupted by SIGCHLD, we simply wait until the call
-                * succeeds.
-                */
-               while (sleep(2))
-                       ; /* nothing */
+       if (stat_pipe >= 0)
+               goto success;
+       /* Avoid busy loop */
+       gettimeofday(&atm, NULL);
+       if (tv_diff(&next_exec, &atm, &diff) > 0) {
+               if (tv_diff(&diff, tv, NULL) < 0)
+                       *tv = diff;
+               return;
+       }
+       next_exec.tv_sec = atm.tv_sec + 2;
        ret = para_exec_cmdline_pid(&pid, conf.stat_cmd_arg, fds);
        if (ret < 0)
-               return ret;
+               return;
        ret = mark_fd_nonblocking(fds[1]);
-       if (ret >= 0)
-               return fds[1];
-       close(fds[1]);
-       return ret;
+       if (ret < 0) {
+               close(fds[1]);
+               return;
+       }
+       stat_pipe = fds[1];
+ success:
+       para_fd_set(stat_pipe, rfds, max_fileno);
  }
  
- #define COMMAND_BUF_SIZE 4096
+ #define COMMAND_BUF_SIZE 32768
  
  /*
   * This is the core select loop. Besides the (internal) signal
@@@ -995,6 -982,7 +1004,7 @@@ static int do_select(int mode
        char command_buf[2][COMMAND_BUF_SIZE] = {"", ""};
        int cbo[2] = {0, 0}; /* command buf offsets */
        struct timeval tv;
+       unsigned flags[2] = {0, 0}; /* for for_each_line() */
  
  repeat:
        tv.tv_sec = conf.timeout_arg  / 1000;
  //    ret = refresh_status();
        FD_ZERO(&rfds);
        max_fileno = 0;
-       if (stat_pipe < 0)
-               stat_pipe = open_stat_pipe();
-       if (stat_pipe >= 0)
-               para_fd_set(stat_pipe, &rfds, &max_fileno);
+       status_pre_select(&rfds, &max_fileno, &tv);
        /* signal pipe */
        para_fd_set(signal_pipe, &rfds, &max_fileno);
        /* command pipe only for COMMAND_MODE */
                if (command_fds[1] >= 0)
                        para_fd_set(command_fds[1], &rfds, &max_fileno);
        }
+       if (mode == GETCH_MODE || mode == COMMAND_MODE)
+               para_fd_set(STDIN_FILENO, &rfds, &max_fileno);
        ret = para_select(max_fileno + 1, &rfds, NULL, &tv);
        if (ret <= 0)
                goto check_return; /* skip fd checks */
                                COMMAND_BUF_SIZE - 1 - cbo[i], &rfds, &sz);
                        cbo[i] += sz;
                        sz = cbo[i];
-                       cbo[i] = for_each_line(command_buf[i], cbo[i],
+                       cbo[i] = for_each_line(flags[i], command_buf[i], cbo[i],
                                add_output_line, &i);
-                       if (sz != cbo[i])
+                       if (sz != cbo[i]) { /* at least one line found */
                                wrefresh(bot.win);
+                               flags[i] = 0;
+                       }
                        if (ret < 0) {
                                PARA_NOTICE_LOG("closing command fd %d: %s",
                                        i, para_strerror(-ret));
                                close(command_fds[i]);
                                command_fds[i] = -1;
+                               flags[i] = 0;
                                if (command_fds[!i] < 0) /* both fds closed */
                                        return 0;
                        }
+                       if (cbo[i] == COMMAND_BUF_SIZE - 1) {
+                               PARA_NOTICE_LOG("discarding overlong line");
+                               cbo[i] = 0;
+                               flags[i] = FELF_DISCARD_FIRST;
+                       }
                }
        }
        ret = read_stat_pipe(&rfds);
@@@ -1082,10 -1077,8 +1099,8 @@@ check_return
                        return ret;
                break;
        case EXTERNAL_MODE:
-               if (cmd_died) {
-                       cmd_died = 0;
+               if (cmd_pid == 0)
                        return 0;
-               }
        }
        goto repeat;
  }
@@@ -1159,7 -1152,6 +1174,6 @@@ static void external_cmd(char *cmd
        shutdown_curses();
        if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0)
                return;
-       cmd_died = 0;
        do_select(EXTERNAL_MODE);
        init_curses();
  }
@@@ -1184,7 -1176,7 +1198,7 @@@ static void com_scroll_top(void
                struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
                if (!rbe)
                        break;
 -              lines += NUM_LINES(strlen(rbe->msg));
 +              lines += NUM_LINES(rbe->len);
        }
        i++;
        if (lines > 0 && scroll_position != i) {
@@@ -1215,7 -1207,7 +1229,7 @@@ static void com_page_down(void
                struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
                if (!rbe)
                        break;
 -              lines += NUM_LINES(strlen(rbe->msg));
 +              lines += NUM_LINES(rbe->len);
        }
        if (lines) {
                scroll_position = i;
@@@ -1536,7 -1528,6 +1550,7 @@@ int main(int argc, char *argv[]
        top.lines = theme.top_lines_default;
        setup_signal_handling();
        bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
 +      setlocale(LC_CTYPE, "");
        initscr(); /* needed only once, always successful */
        init_curses();
        print_welcome();
diff --combined string.c
index dfcfa2cdf6eb776c63afdaec981aed55147a31b1,4d8b8b747201950ab4eb23caf85d8599dee94a26..38e25b09c5f3ff2b289d6e943a0f7bde3bfc23ad
+++ b/string.c
@@@ -6,18 -6,12 +6,18 @@@
  
  /** \file string.c Memory allocation and string handling functions. */
  
 -#include <sys/time.h> /* gettimeofday */
 +#define _GNU_SOURCE
 +
  #include <pwd.h>
  #include <sys/utsname.h> /* uname() */
 +
  #include <string.h>
  #include <regex.h>
  
 +#include <langinfo.h>
 +#include <wchar.h>
 +#include <wctype.h>
 +
  #include "para.h"
  #include "string.h"
  #include "error.h"
@@@ -142,15 -136,18 +142,18 @@@ __must_check __malloc char *para_strdup
  __printf_2_0 unsigned xvasprintf(char **result, const char *fmt, va_list ap)
  {
        int ret;
-       size_t size;
+       size_t size = 150;
        va_list aq;
  
+       *result = para_malloc(size + 1);
        va_copy(aq, ap);
-       ret = vsnprintf(NULL, 0, fmt, aq);
+       ret = vsnprintf(*result, size, fmt, aq);
        va_end(aq);
        assert(ret >= 0);
+       if (ret < size) /* OK */
+               return ret;
        size = ret + 1;
-       *result = para_malloc(size);
+       *result = para_realloc(*result, size);
        va_copy(aq, ap);
        ret = vsnprintf(*result, size, fmt, aq);
        va_end(aq);
@@@ -356,19 -353,35 +359,35 @@@ __malloc char *para_hostname(void
  }
  
  /**
-  * Used to distinguish between read-only and read-write mode.
+  * Call a custom function for each complete line.
+  *
+  * \param flags Any combination of flags defined in \ref for_each_line_flags.
+  * \param buf The buffer containing data separated by newlines.
+  * \param size The number of bytes in \a buf.
+  * \param line_handler The custom function.
+  * \param private_data Pointer passed to \a line_handler.
+  *
+  * For each complete line in \p buf, \p line_handler is called. The first
+  * argument to \p line_handler is (a copy of) the current line, and \p
+  * private_data is passed as the second argument.  If the \p FELF_READ_ONLY
+  * flag is unset, a pointer into \a buf is passed to the line handler,
+  * otherwise a pointer to a copy of the current line is passed instead. This
+  * copy is freed immediately after the line handler returns.
   *
-  * \sa for_each_line(), for_each_line_ro().
+  * The function returns if \p line_handler returns a negative value or no more
+  * lines are in the buffer.  The rest of the buffer (last chunk containing an
+  * incomplete line) is moved to the beginning of the buffer if FELF_READ_ONLY is
+  * unset.
+  *
+  * \return On success this function returns the number of bytes not handled to
+  * \p line_handler. The only possible error is a negative return value from the
+  * line handler. In this case processing stops and the return value of the line
+  * handler is returned to indicate failure.
+  *
+  * \sa \ref for_each_line_flags.
   */
- enum for_each_line_modes{
-       /** Activate read-only mode. */
-       LINE_MODE_RO,
-       /** Activate read-write mode. */
-       LINE_MODE_RW
- };
- static int for_each_complete_line(enum for_each_line_modes mode, char *buf,
-               size_t size, line_handler_t *line_handler, void *private_data)
+ int for_each_line(unsigned flags, char *buf, size_t size,
+               line_handler_t *line_handler, void *private_data)
  {
        char *start = buf, *end;
        int ret, i, num_lines = 0;
                } else
                        end = next_cr;
                num_lines++;
-               if (!line_handler) {
-                       start = ++end;
-                       continue;
-               }
-               if (mode == LINE_MODE_RO) {
-                       size_t s = end - start;
-                       char *b = para_malloc(s + 1);
-                       memcpy(b, start, s);
-                       b[s] = '\0';
- //                    PARA_NOTICE_LOG("b: %s, start: %s\n", b, start);
-                       ret = line_handler(b, private_data);
-                       free(b);
-               } else {
-                       *end = '\0';
-                       ret = line_handler(start, private_data);
+               if (!(flags & FELF_DISCARD_FIRST) || start != buf) {
+                       if (flags & FELF_READ_ONLY) {
+                               size_t s = end - start;
+                               char *b = para_malloc(s + 1);
+                               memcpy(b, start, s);
+                               b[s] = '\0';
+                               ret = line_handler(b, private_data);
+                               free(b);
+                       } else {
+                               *end = '\0';
+                               ret = line_handler(start, private_data);
+                       }
+                       if (ret < 0)
+                               return ret;
                }
-               if (ret < 0)
-                       return ret;
                start = ++end;
        }
-       if (!line_handler || mode == LINE_MODE_RO)
-               return num_lines;
        i = buf + size - start;
-       if (i && i != size)
+       if (i && i != size && !(flags & FELF_READ_ONLY))
                memmove(buf, start, i);
        return i;
  }
  
- /**
-  * Call a custom function for each complete line.
-  *
-  * \param buf The buffer containing data separated by newlines.
-  * \param size The number of bytes in \a buf.
-  * \param line_handler The custom function.
-  * \param private_data Pointer passed to \a line_handler.
-  *
-  * If \p line_handler is \p NULL, the function returns the number of complete
-  * lines in \p buf.  Otherwise, \p line_handler is called for each complete
-  * line in \p buf.  The first argument to \p line_handler is the current line,
-  * and \p private_data is passed as the second argument.  The function returns
-  * if \p line_handler returns a negative value or no more lines are in the
-  * buffer.  The rest of the buffer (last chunk containing an incomplete line)
-  * is moved to the beginning of the buffer.
-  *
-  * \return If \p line_handler is not \p NULL, this function returns the number
-  * of bytes not handled to \p line_handler on success, or the negative return
-  * value of the \p line_handler on errors.
-  *
-  * \sa for_each_line_ro().
-  */
- int for_each_line(char *buf, size_t size, line_handler_t *line_handler,
-               void *private_data)
- {
-       return for_each_complete_line(LINE_MODE_RW, buf, size, line_handler,
-               private_data);
- }
- /**
-  * Call a custom function for each complete line.
-  *
-  * \param buf Same meaning as in \p for_each_line().
-  * \param size Same meaning as in \p for_each_line().
-  * \param line_handler Same meaning as in \p for_each_line().
-  * \param private_data Same meaning as in \p for_each_line().
-  *
-  * This function behaves like \p for_each_line(), but \a buf is left unchanged.
-  *
-  * \return On success, the function returns the number of complete lines in \p
-  * buf, otherwise the (negative) return value of \p line_handler is returned.
-  *
-  * \sa for_each_line().
-  */
- int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler,
-               void *private_data)
- {
-       return for_each_complete_line(LINE_MODE_RO, buf, size, line_handler,
-               private_data);
- }
  /** Return the hex characters of the lower 4 bits. */
  #define hex(a) (hexchar[(a) & 15])
  
@@@ -988,145 -945,3 +951,145 @@@ char *key_value_copy(const char *src, s
                return NULL;
        return safe_strdup(src + keylen + 1, len - keylen - 1);
  }
 +
 +static bool utf8_mode(void)
 +{
 +      static bool initialized, have_utf8;
 +
 +      if (!initialized) {
 +              char *info = nl_langinfo(CODESET);
 +              have_utf8 = (info && strcmp(info, "UTF-8") == 0);
 +              initialized = true;
 +              PARA_INFO_LOG("%susing UTF-8 character encoding\n",
 +                      have_utf8? "" : "not ");
 +      }
 +      return have_utf8;
 +}
 +
 +/*
 + * glibc's wcswidth returns -1 if the string contains a tab character, which
 + * makes the function next to useless. The two functions below are taken from
 + * mutt.
 + */
 +
 +#define IsWPrint(wc) (iswprint(wc) || wc >= 0xa0)
 +
 +static int mutt_wcwidth(wchar_t wc, size_t pos)
 +{
 +      int n;
 +
 +      if (wc == 0x09) /* tab */
 +              return (pos | 7) + 1 - pos;
 +      n = wcwidth(wc);
 +      if (IsWPrint(wc) && n > 0)
 +              return n;
 +      if (!(wc & ~0x7f))
 +              return 2;
 +      if (!(wc & ~0xffff))
 +              return 6;
 +      return 10;
 +}
 +
 +static size_t mutt_wcswidth(const wchar_t *s, size_t n)
 +{
 +      size_t w = 0;
 +
 +      while (n--)
 +              w += mutt_wcwidth(*s++, w);
 +      return w;
 +}
 +
 +/**
 + * Skip a given number of cells at the beginning of a string.
 + *
 + * \param s The input string.
 + * \param cells_to_skip Desired number of cells that should be skipped.
 + * \param bytes_to_skip Result.
 + *
 + * This function computes how many input bytes must be skipped to advance a
 + * string by the given width. If the current character encoding is not UTF-8,
 + * this is simply the given number of cells, i.e. \a cells_to_skip. Otherwise,
 + * \a s is treated as a multibyte string and on successful return, \a s +
 + * bytes_to_skip points to the start of a multibyte string such that the total
 + * width of the multibyte characters that are skipped by advancing \a s that
 + * many bytes equals at least \a cells_to_skip.
 + *
 + * \return Standard.
 + */
 +int skip_cells(const char *s, size_t cells_to_skip, size_t *bytes_to_skip)
 +{
 +      wchar_t wc;
 +      mbstate_t ps;
 +      size_t n, bytes_parsed, cells_skipped;
 +
 +      *bytes_to_skip = 0;
 +      if (cells_to_skip == 0)
 +              return 0;
 +      if (!utf8_mode()) {
 +              *bytes_to_skip = cells_to_skip;
 +              return 0;
 +      }
 +      bytes_parsed = cells_skipped = 0;
 +      memset(&ps, 0, sizeof(ps));
 +      n = strlen(s);
 +      while (cells_to_skip > cells_skipped) {
 +              size_t mbret;
 +
 +              mbret = mbrtowc(&wc, s + bytes_parsed, n - bytes_parsed, &ps);
 +              assert(mbret != 0);
 +              if (mbret == (size_t)-1 || mbret == (size_t)-2)
 +                      return -ERRNO_TO_PARA_ERROR(EILSEQ);
 +              bytes_parsed += mbret;
 +              cells_skipped += mutt_wcwidth(wc, cells_skipped);
 +      }
 +      *bytes_to_skip = bytes_parsed;
 +      return 1;
 +}
 +
 +/**
 + * Compute the width of an UTF-8 string.
 + *
 + * \param s The string.
 + * \param result The width of \a s is returned here.
 + *
 + * If not in UTF8-mode. this function is just a wrapper for strlen(3).
 + * Otherwise \a s is treated as an UTF-8 string and its display width is
 + * computed. Note that this function may fail if the underlying call to
 + * mbsrtowcs(3) fails, so the caller must check the return value.
 + *
 + * \sa nl_langinfo(3), wcswidth(3).
 + *
 + * \return Standard.
 + */
 +__must_check int strwidth(const char *s, size_t *result)
 +{
 +      const char *src = s;
 +      mbstate_t state;
 +      static wchar_t *dest;
 +      size_t num_wchars;
 +
 +      /*
 +       * Never call any log function here. This may result in an endless loop
 +       * as para_gui's para_log() calls this function.
 +       */
 +
 +      if (!utf8_mode()) {
 +              *result = strlen(s);
 +              return 0;
 +      }
 +      memset(&state, 0, sizeof(state));
 +      *result = 0;
 +      num_wchars = mbsrtowcs(NULL, &src, 0, &state);
 +      if (num_wchars == (size_t)-1)
 +              return -ERRNO_TO_PARA_ERROR(errno);
 +      if (num_wchars == 0)
 +              return 0;
 +      dest = para_malloc(num_wchars * sizeof(*dest));
 +      src = s;
 +      memset(&state, 0, sizeof(state));
 +      num_wchars = mbsrtowcs(dest, &src, num_wchars, &state);
 +      assert(num_wchars > 0 && num_wchars != (size_t)-1);
 +      *result = mutt_wcswidth(dest, num_wchars);
 +      free(dest);
 +      return 1;
 +}
diff --combined string.h
index d45c9199822d993ab2594877a6cd45914a902f15,230f654b67a8e553e37e2f7635fbfe057e63cfca..935c7d456c2c5b6910b356f2ddd52fd62848cf67
+++ b/string.h
@@@ -34,6 -34,23 +34,23 @@@ struct para_buffer 
        void *private_data;
  };
  
+ /**
+  * Controls the behavior of for_each_line().
+  *
+  * \sa for_each_line().
+  */
+ enum for_each_line_flags {
+       /** Activate read-only mode. */
+       FELF_READ_ONLY = 1 << 0,
+       /** Don't call line handler for the first input line. */
+       FELF_DISCARD_FIRST = 1 << 1,
+ };
+ /** Used for \ref for_each_line(). */
+ typedef int line_handler_t(char *, void *);
+ int for_each_line(unsigned flags, char *buf, size_t size,
+               line_handler_t *line_handler, void *private_data);
  /**
    * Write the contents of a status item to a para_buffer.
    *
@@@ -72,12 -89,6 +89,6 @@@ __must_check __malloc char *para_lognam
  __must_check __malloc char *para_homedir(void);
  __malloc char *para_hostname(void);
  __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...);
- /** Used for for_each_line() and for_each_line_ro(). */
- typedef int line_handler_t(char *, void *);
- int for_each_line(char *buf, size_t size, line_handler_t *line_handler,
-       void *private_data);
- int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler,
-       void *private_data);
  int para_atoi64(const char *str, int64_t *result);
  int para_atoi32(const char *str, int32_t *value);
  int get_loglevel_by_name(const char *txt);
@@@ -91,5 -102,3 +102,5 @@@ void freep(void *arg)
  int compute_word_num(const char *buf, const char *delim, int offset);
  char *safe_strdup(const char *src, size_t len);
  char *key_value_copy(const char *src, size_t len, const char *key);
 +int skip_cells(const char *s, size_t cells_to_skip, size_t *result);
 +__must_check int strwidth(const char *s, size_t *result);