A big mess of changes all at once.
[dvdrip] / multiprogress.c
diff --git a/multiprogress.c b/multiprogress.c
new file mode 100644 (file)
index 0000000..efad38d
--- /dev/null
@@ -0,0 +1,587 @@
+#define _XOPEN_SOURCE
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <unistd.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#if defined(USE_TERMINFO)
+#  include <curses.h>
+#  include <term.h>
+#elif defined(USE_TERMCAP)
+#  include <termcap.h>
+#endif
+
+#include "multiprogress.h"
+
+static FILE *dup_stream(int fd)
+{
+  FILE *fp;
+  int newfd;
+
+  newfd = dup(fd); if (newfd < 0) return (0);
+  fp = fdopen(newfd, "r+"); if (!fp) return (0);
+  return (fp);
+}
+
+int progress_init(struct progress_state *progress)
+{
+#ifdef USE_TERMCAP
+  char *term, *capcur;
+#endif
+#ifdef USE_TERMINFO
+  int err;
+#endif
+  struct progress_ttyinfo *tty;
+  const char *t;
+  int n;
+
+  tty = &progress->tty;
+  tty->fp = 0;
+  tty->termbuf = tty->capbuf = 0;
+  tty->cap.f = 0;
+  tty->cap.cr = tty->cap.up = tty->cap.ce = tty->cap.cd =
+    tty->cap.mr = tty->cap.md = tty->cap.me =
+    tty->cap.af = tty->cap.ab = tty->cap.op = 0;
+
+  progress->items = progress->end_item = 0;
+  progress->nitems = 0; progress->last_lines = 0;
+  progress->tv_update.tv_sec = 0; progress->tv_update.tv_usec = 0;
+
+  if (isatty(1)) tty->fp = dup_stream(1);
+  else if (isatty(2)) tty->fp = dup_stream(2);
+  else tty->fp = fopen("/dev/tty", "r+");
+  if (!tty->fp) return (-1);
+
+#define SETDIM(dim, var, getcap, dflt) do {                            \
+  t = getenv(var); if (t) { n = atoi(t); if (n) { tty->dim = n; break; } } \
+  n = getcap; if (n > 0) { tty->dim = n; break; }                      \
+  tty->dim = dflt;                                                     \
+} while (0)
+
+#if defined(USE_TERMINFO)
+
+  if (setupterm(0, fileno(tty->fp), &err) != OK || err < 1) return (-1);
+
+  tty->cap.cr = tigetstr("cr");
+  tty->cap.up = tigetstr("cuu1");
+  tty->cap.ce = tigetstr("el");
+  tty->cap.cd = tigetstr("ed");
+
+  if (tigetnum("xmc") < 1) {
+    tty->cap.mr = tigetstr("rev");
+    tty->cap.md = tigetstr("bold");
+    tty->cap.me = tigetstr("sgr0");
+
+    tty->cap.af = tigetstr("setaf");
+    tty->cap.ab = tigetstr("setab");
+    tty->cap.op = tigetstr("op");
+  }
+
+  if (tigetflag("bce") > 0) tty->cap.f |= TCF_BCE;
+
+  SETDIM(defwd, "COLUMNS", tigetnum("co"), 80);
+  SETDIM(defht, "LINES", tigetnum("li"), 25);
+
+#elif defined(USE_TERMCAP)
+
+  term = getenv("TERM"); if (!term) return (-1);
+  if (tgetent(tty->termbuf, term) < 1) return (-1);
+
+  tty->termbuf = malloc(4096); if (!tty->termbuf) return (-1);
+  tty->capbuf = malloc(4096); if (!tty->capbuf) return (-1);
+
+  capcur = tty->capbuf;
+  tty->cap.cr = tgetstr("cr", &capcur);
+  tty->cap.up = tgetstr("up", &capcur);
+  tty->cap.ce = tgetstr("ce", &capcur);
+  tty->cap.cd = tgetstr("cd", &capcur);
+
+  if (tgetnum("sg") < 1) {
+    tty->cap.mr = tgetstr("mr", &capcur);
+    tty->cap.md = tgetstr("md", &capcur);
+    tty->cap.me = tgetstr("me", &capcur);
+
+    tty->cap.af = tgetstr("AF", &capcur);
+    tty->cap.ab = tgetstr("AB", &capcur);
+    tty->cap.op = tgetstr("op", &capcur);
+  }
+
+  if (tgetflag("ut") > 0) tty->cap.f |= TCF_BCE;
+
+  t = tgetstr("pc", &capcur); PC = t ? *t : 0;
+
+  SETDIM(defwd, "COLUMNS", tgetnum("co"), 80);
+  SETDIM(defht, "LINES", tgetnum("li"), 25);
+
+#else
+
+  SETDIM(defwd, "COLUMNS", -1, 80);
+  SETDIM(defht, "LINES", -1, 25);
+
+#endif
+
+#undef SETDIM
+
+  if (!tty->cap.cr || !tty->cap.up || !tty->cap.ce || !tty->cap.cd)
+    { fclose(tty->fp); tty->fp = 0; return (-1); }
+  if (!tty->cap.af || !tty->cap.ab || !tty->cap.op) tty->cap.op = 0;
+  if (!tty->cap.me) tty->cap.mr = tty->cap.md = 0;
+  return (0);
+}
+
+void progress_free(struct progress_state *progress)
+{
+  struct progress_ttyinfo *tty = &progress->tty;
+
+  if (tty->fp) { fclose(tty->fp); tty->fp = 0; }
+  free(tty->termbuf); free(tty->capbuf); tty->termbuf = tty->capbuf = 0;
+}
+
+#if defined(USE_TERMINFO)
+static const struct progress_ttyinfo *curtty = 0;
+static int putty(int ch) { return (putc(ch, curtty->fp)); }
+static void put_sequence(const struct progress_ttyinfo *tty,
+                        const char *p, unsigned nlines)
+  { if (p) { curtty = tty; tputs(p, nlines, putty); } }
+static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
+  { put_sequence(tty, tgoto(tty->cap.af, -1, colour), 1); }
+static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
+  { put_sequence(tty, tgoto(tty->cap.ab, -1, colour), 1); }
+#elif defined(USE_TERMCAP)
+static const struct progress_ttyinfo *curtty = 0;
+static int putty(int ch) { return (putc(ch, curtty->fp)); }
+static void put_sequence(const struct progress_ttyinfo *tty,
+                        const char *p, unsigned nlines)
+  { if (p) { curtty = tty; tputs(p, nlines, putty); } }
+static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
+  { put_sequence(tty, tgoto(tty->cap.af, -1, colour), 1); }
+static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
+  { put_sequence(tty, tgoto(tty->cap.ab, -1, colour), 1); }
+#else
+static void put_sequence(const struct progress_ttyinfo *tty,
+                        const char *p, unsigned nlines) { ; }
+static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
+  { ; }
+static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
+  { ; }
+#endif
+
+#define CLRF_ALL 1u
+static int clear_progress(struct progress_state *progress,
+                         struct progress_render_state *render, unsigned f)
+{
+  const struct progress_ttyinfo *tty = &progress->tty;
+  unsigned ndel, nleave;
+  unsigned i;
+
+  if (!tty->fp) return (-1);
+
+  put_sequence(tty, tty->cap.cr, 1);
+  if (progress->last_lines) {
+    if (f&CLRF_ALL)
+      { ndel = progress->last_lines; nleave = 0; }
+    else {
+      if (progress->nitems >= progress->last_lines) ndel = 0;
+      else ndel = progress->last_lines - progress->nitems;
+      nleave = progress->last_lines - ndel;
+    }
+    if (!ndel)
+      for (i = 0; i < nleave - 1; i++) put_sequence(tty, tty->cap.up, 1);
+    else {
+      for (i = 0; i < ndel - 1; i++) put_sequence(tty, tty->cap.up, 1);
+      put_sequence(tty, tty->cap.cd, ndel);
+      for (i = 0; i < nleave; i++) put_sequence(tty, tty->cap.up, 1);
+    }
+  }
+  progress->last_lines = 0;
+  if (ferror(tty->fp)) return (-1);
+  return (0);
+}
+
+static int grow_linebuf(struct progress_render_state *render, size_t want)
+{
+  char *newbuf; size_t newsz;
+
+  if (want <= render->linesz) return (0);
+  if (!render->linesz) newsz = 4*render->width + 1;
+  else newsz = render->linesz;
+  while (newsz < want) newsz *= 2;
+  newbuf = malloc(newsz + 1); if (!newbuf) return (-1);
+  newbuf[newsz] = 0;
+  if (render->leftsz)
+    memcpy(newbuf, render->linebuf, render->leftsz);
+  if (render->rightsz)
+    memcpy(newbuf + newsz - render->rightsz,
+          render->linebuf + render->linesz - render->rightsz,
+          render->rightsz);
+  free(render->linebuf); render->linebuf = newbuf; render->linesz = newsz;
+  return (0);
+}
+
+static int grow_tempbuf(struct progress_render_state *render, size_t want)
+{
+  char *newbuf; size_t newsz;
+
+  if (want <= render->tempsz) return (0);
+  if (!render->tempsz) newsz = 4*render->width + 1;
+  else newsz = render->tempsz;
+  while (newsz < want) newsz *= 2;
+  newbuf = malloc(newsz + 1); if (!newbuf) return (-1);
+  newbuf[newsz] = 0;
+  if (render->tempsz) memcpy(newbuf, render->tempbuf, render->tempsz);
+  free(render->tempbuf); render->tempbuf = newbuf; render->tempsz = newsz;
+  return (0);
+}
+
+static int setup_render_state(struct progress_state *progress,
+                             struct progress_render_state *render)
+{
+  const struct progress_ttyinfo *tty = &progress->tty;
+  struct winsize wsz;
+  int rc = 0;
+
+  render->tty = tty;
+  render->linebuf = 0; render->linesz = 0;
+  render->tempbuf = 0; render->tempsz = 0;
+
+#ifdef USE_TERMCAP
+  render->old_bc = BC; BC = 0;
+  render->old_up = UP; UP = 0;
+#endif
+
+  if (!ioctl(fileno(tty->fp), TIOCGWINSZ, &wsz))
+    { render->width = wsz.ws_col; render->height = wsz.ws_row; }
+  else
+    { render->width = tty->defwd; render->height = tty->defht; rc = -1; }
+
+  if (render->width && !tty->cap.op && !tty->cap.mr) render->width--;
+
+  return (rc);
+}
+
+static void free_render_state(struct progress_render_state *render)
+{
+  fflush(render->tty->fp);
+  free(render->linebuf); render->linebuf = 0; render->linesz = 0;
+  free(render->tempbuf); render->tempbuf = 0; render->tempsz = 0;
+#ifdef USE_TERMCAP
+  UP = render->old_up;
+  BC = render->old_bc;
+#endif
+}
+
+#define CONV_MORE ((size_t)-2)
+#define CONV_BAD ((size_t)-1)
+
+struct measure {
+  mbstate_t ps;
+  const char *p; size_t i, sz;
+  unsigned wd;
+};
+
+static void init_measure(struct measure *m, const char *p, size_t sz)
+{
+  m->p = p; m->sz = sz; m->i = 0; m->wd = 0;
+  memset(&m->ps, 0, sizeof(m->ps));
+}
+
+static int advance_measure(struct measure *m)
+{
+  wchar_t wch;
+  unsigned chwd;
+  size_t n;
+
+  n = mbrtowc(&wch, m->p + m->i, m->sz - m->i, &m->ps);
+  if (!n) { chwd = 0; n = m->sz - m->i; }
+  else if (n == CONV_MORE) { chwd = 2; n = m->sz - m->i; }
+  else if (n == CONV_BAD) { chwd = 2; n = 1; }
+  else chwd = wcwidth(wch);
+
+  m->i += n; m->wd += chwd;
+  return (m->i < m->sz);
+}
+
+static unsigned string_width(const char *p, size_t sz)
+{
+  struct measure m;
+
+  init_measure(&m, p, sz);
+  while (advance_measure(&m));
+  return (m.wd);
+}
+
+static size_t split_string(const char *p, size_t sz,
+                          unsigned *wd_out, unsigned maxwd)
+{
+  struct measure m;
+  size_t lasti; unsigned lastwd;
+  int more;
+
+  init_measure(&m, p, sz);
+  for (;;) {
+    lasti = m.i; lastwd = m.wd;
+    more = advance_measure(&m);
+    if (m.wd > maxwd) { *wd_out = lastwd; return (lasti); }
+    else if (!more) { *wd_out = m.wd; return (sz); }
+  }
+}
+
+enum { LEFT, RIGHT };
+static int putstr(struct progress_render_state *render, unsigned side,
+                 const char *p, size_t n)
+{
+  unsigned newwd = string_width(p, n);
+  size_t want;
+
+  if (newwd >= render->width - render->leftwd - render->rightwd) return (-1);
+  want = render->leftsz + render->rightsz + n;
+  if (want > render->linesz && grow_linebuf(render, want)) return (-1);
+  switch (side) {
+    case LEFT:
+      memcpy(render->linebuf + render->leftsz, p, n);
+      render->leftsz += n; render->leftwd += newwd;
+      break;
+    case RIGHT:
+      memcpy(render->linebuf + render->linesz - render->rightsz - n, p, n);
+      render->rightsz += n; render->rightwd += newwd;
+      break;
+    default:
+      return (-1);
+  }
+  return (0);
+}
+
+static int vputf(struct progress_render_state *render, unsigned side,
+                const char *fmt, va_list ap)
+{
+  va_list bp;
+  int rc;
+
+  if (!render->tempsz && grow_tempbuf(render, 2*strlen(fmt))) return (-1);
+  for (;;) {
+    va_copy(bp, ap);
+    rc = vsnprintf(render->tempbuf, render->tempsz, fmt, bp);
+    va_end(bp);
+    if (rc < 0) return (-1);
+    if (rc <= render->tempsz) break;
+    if (grow_tempbuf(render, 2*(rc + 1))) return (-1);
+  }
+  if (putstr(render, side, render->tempbuf, rc)) return (-1);
+  return (0);
+}
+
+int progress_vputleft(struct progress_render_state *render,
+                     const char *fmt, va_list ap)
+  { return (vputf(render, LEFT, fmt, ap)); }
+
+int progress_vputright(struct progress_render_state *render,
+                      const char *fmt, va_list ap)
+  { return (vputf(render, RIGHT, fmt, ap)); }
+
+int progress_putleft(struct progress_render_state *render,
+                    const char *fmt, ...)
+{
+  va_list ap;
+  int rc;
+
+  va_start(ap, fmt); rc = vputf(render, LEFT, fmt, ap); va_end(ap);
+  return (rc);
+}
+
+int progress_putright(struct progress_render_state *render,
+                     const char *fmt, ...)
+{
+  va_list ap;
+  int rc;
+
+  va_start(ap, fmt); rc = vputf(render, RIGHT, fmt, ap); va_end(ap);
+  return (rc);
+}
+
+enum {
+  LEFT_COLOUR,
+  LEFT_MONO,
+  LEFT_SIMPLE,
+  RIGHT_ANY,
+  STOP
+};
+
+struct bar_state {
+  const struct progress_render_state *render;
+  unsigned pos, nextpos, state;
+};
+
+static void advance_bar_state(struct bar_state *bar)
+{
+  const struct progress_render_state *render = bar->render;
+  const struct progress_ttyinfo *tty = render->tty;
+  size_t here = bar->nextpos;
+
+  while (bar->nextpos == here) {
+    switch (bar->state) {
+      case LEFT_COLOUR: set_bgcolour(tty, 3); goto right;
+      case LEFT_MONO: put_sequence(tty, tty->cap.me, 1); goto right;
+      case LEFT_SIMPLE: putc('|', tty->fp); goto right;
+      right: bar->state = RIGHT_ANY; bar->nextpos = render->width; break;
+      case RIGHT_ANY: bar->state = STOP; bar->nextpos = UINT_MAX; break;
+    }
+  }
+}
+
+static void put_str(FILE *fp, const char *p, size_t sz)
+  { while (sz--) putc(*p++, fp); }
+static void put_spc(FILE *fp, unsigned n)
+  { while (n--) putc(' ', fp); }
+
+static void put_barstr(struct bar_state *bar, const char *p, size_t sz)
+{
+  unsigned wd;
+  size_t n;
+
+  for (;;) {
+    n = split_string(p, sz, &wd, bar->nextpos - bar->pos);
+    if (n == sz && wd < bar->nextpos - bar->pos) break;
+    put_str(bar->render->tty->fp, p, n); bar->pos += wd;
+    advance_bar_state(bar);
+    p += n; sz -= n;
+  }
+  put_str(bar->render->tty->fp, p, sz); bar->pos += wd;
+}
+
+static void put_barspc(struct bar_state *bar, unsigned n)
+{
+  unsigned step;
+
+  for (;;) {
+    step = bar->nextpos - bar->pos;
+    if (n < step) break;
+    put_spc(bar->render->tty->fp, step); bar->pos += step;
+    advance_bar_state(bar);
+    n -= step;
+  }
+  put_spc(bar->render->tty->fp, n); bar->pos += n;
+}
+
+int progress_showbar(struct progress_render_state *render, double frac)
+{
+  const struct progress_ttyinfo *tty = render->tty;
+  struct bar_state bar;
+
+  if (!tty->fp) return (-1);
+
+  bar.render = render; bar.pos = 0; bar.nextpos = frac*render->width + 0.5;
+
+  if (tty->cap.op) {
+    set_fgcolour(tty, 0); bar.state = LEFT_COLOUR;
+    if (bar.nextpos) set_bgcolour(tty, 2);
+    else advance_bar_state(&bar);
+  } else if (tty->cap.mr) {
+    if (bar.nextpos)
+      { bar.state = LEFT_MONO; put_sequence(tty, tty->cap.mr, 1); }
+    else
+      { bar.state = RIGHT; bar.nextpos = render->width; }
+  } else
+    bar.state = LEFT_SIMPLE;
+
+  put_barstr(&bar, render->linebuf, render->leftsz);
+  put_barspc(&bar, render->width - render->leftwd - render->rightwd);
+  put_barstr(&bar,
+            render->linebuf + render->linesz - render->rightsz,
+            render->rightsz);
+
+  put_sequence(tty, tty->cap.me, 1);
+  put_sequence(tty, tty->cap.op, 1);
+
+  return (0);
+}
+
+int progress_shownotice(struct progress_render_state *render, int bg, int fg)
+{
+  const struct progress_ttyinfo *tty = render->tty;
+
+  if (!tty->fp) return (-1);
+
+  if (tty->cap.op) { set_fgcolour(tty, fg); set_bgcolour(tty, bg); }
+  else if (tty->cap.mr) put_sequence(tty, tty->cap.mr, 1);
+  if (tty->cap.md) put_sequence(tty, tty->cap.md, 1);
+
+  put_str(tty->fp, render->linebuf, render->leftsz);
+  if (!render->rightsz && (tty->cap.f&TCF_BCE) && tty->cap.ce)
+    put_sequence(tty, tty->cap.ce, 1);
+  else {
+    put_spc(tty->fp, render->width - render->leftwd - render->rightwd);
+    put_str(tty->fp,
+           render->linebuf + render->linesz - render->rightsz,
+           render->rightsz);
+  }
+
+  put_sequence(tty, tty->cap.me, 1);
+  put_sequence(tty, tty->cap.op, 1);
+
+  return (0);
+}
+
+int progress_additem(struct progress_state *progress,
+                    struct progress_item *item)
+{
+  if (item->parent) return (-1);
+  item->prev = progress->end_item; item->next = 0;
+  if (progress->end_item) progress->end_item->next = item;
+  else progress->items = item;
+  progress->end_item = item; item->parent = progress;
+  progress->nitems++;
+
+  return (0);
+}
+
+int progress_clear(struct progress_state *progress)
+{
+  struct progress_render_state render;
+
+  if (!progress->tty.fp) return (-1);
+  if (setup_render_state(progress, &render)) return (-1);
+  clear_progress(progress, &render, CLRF_ALL);
+  free_render_state(&render);
+  return (0);
+}
+
+int progress_update(struct progress_state *progress)
+{
+  struct progress_render_state render;
+  struct progress_item *item;
+  unsigned f = 0;
+#define f_any 1u
+
+  if (!progress->tty.fp) return (-1);
+  if (setup_render_state(progress, &render)) return (-1);
+  clear_progress(progress, &render, 0);
+
+  for (item = progress->items; item; item = item->next) {
+    if (f&f_any) fputs("\r\n", progress->tty.fp);
+    render.leftsz = render.rightsz = 0;
+    render.leftwd = render.rightwd = 0;
+    item->render(item, &render); progress->last_lines++; f |= f_any;
+    if (progress->last_lines > render.height) break;
+  }
+  free_render_state(&render);
+  return (0);
+}
+
+int progress_removeitem(struct progress_state *progress,
+                       struct progress_item *item)
+{
+  if (!item->parent) return (-1);
+  if (item->next) item->next->prev = item->prev;
+  else (progress->end_item) = item->prev;
+  if (item->prev) item->prev->next = item->next;
+  else (progress->items) = item->next;
+  progress->nitems--; item->parent = 0;
+
+  return (0);
+}