X-Git-Url: https://git.distorted.org.uk/~mdw/dvdrip/blobdiff_plain/822ac8a86682d421a76abad84a3235cdd17b58ea..dc53ebfaa3fb887f962b574c6bafa45b160fc765:/multiprogress.c?ds=sidebyside diff --git a/multiprogress.c b/multiprogress.c new file mode 100644 index 0000000..efad38d --- /dev/null +++ b/multiprogress.c @@ -0,0 +1,587 @@ +#define _XOPEN_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#if defined(USE_TERMINFO) +# include +# include +#elif defined(USE_TERMCAP) +# include +#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); +}