chiark / gitweb /
multiprogress.c: Provide the obvious default for `cr'.
[dvdrip] / multiprogress.c
1 #define _XOPEN_SOURCE
2
3 #include <limits.h>
4 #include <stdarg.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <wchar.h>
8
9 #include <unistd.h>
10 #include <termios.h>
11 #include <sys/ioctl.h>
12
13 #if defined(USE_TERMINFO)
14 #  include <curses.h>
15 #  include <term.h>
16 #elif defined(USE_TERMCAP)
17 #  include <termcap.h>
18 #endif
19
20 #include "multiprogress.h"
21
22 static FILE *dup_stream(int fd)
23 {
24   FILE *fp;
25   int newfd;
26
27   newfd = dup(fd); if (newfd < 0) return (0);
28   fp = fdopen(newfd, "r+"); if (!fp) return (0);
29   return (fp);
30 }
31
32 int progress_init(struct progress_state *progress)
33 {
34 #ifdef USE_TERMCAP
35   char *term, *capcur;
36 #endif
37 #ifdef USE_TERMINFO
38   int err;
39 #endif
40   struct progress_ttyinfo *tty;
41   const char *t;
42   int n;
43
44   tty = &progress->tty;
45   tty->fp = 0;
46   tty->termbuf = tty->capbuf = 0;
47   tty->cap.f = 0;
48   tty->cap.cr = tty->cap.up = tty->cap.ce = tty->cap.cd =
49     tty->cap.mr = tty->cap.md = tty->cap.me =
50     tty->cap.af = tty->cap.ab = tty->cap.op = 0;
51
52   progress->items = progress->end_item = 0;
53   progress->nitems = 0; progress->last_lines = 0;
54   progress->tv_update.tv_sec = 0; progress->tv_update.tv_usec = 0;
55
56   if (isatty(1)) tty->fp = dup_stream(1);
57   else if (isatty(2)) tty->fp = dup_stream(2);
58   else tty->fp = fopen("/dev/tty", "r+");
59   if (!tty->fp) return (-1);
60
61 #define SETDIM(dim, var, getcap, dflt) do {                             \
62   t = getenv(var); if (t) { n = atoi(t); if (n) { tty->dim = n; break; } } \
63   n = getcap; if (n > 0) { tty->dim = n; break; }                       \
64   tty->dim = dflt;                                                      \
65 } while (0)
66
67 #if defined(USE_TERMINFO)
68
69   if (setupterm(0, fileno(tty->fp), &err) != OK || err < 1) return (-1);
70
71   tty->cap.cr = tigetstr("cr");
72   tty->cap.up = tigetstr("cuu1");
73   tty->cap.ce = tigetstr("el");
74   tty->cap.cd = tigetstr("ed");
75
76   if (tigetnum("xmc") < 1) {
77     tty->cap.mr = tigetstr("rev");
78     tty->cap.md = tigetstr("bold");
79     tty->cap.me = tigetstr("sgr0");
80
81     tty->cap.af = tigetstr("setaf");
82     tty->cap.ab = tigetstr("setab");
83     tty->cap.op = tigetstr("op");
84   }
85
86   if (tigetflag("bce") > 0) tty->cap.f |= TCF_BCE;
87
88   SETDIM(defwd, "COLUMNS", tigetnum("co"), 80);
89   SETDIM(defht, "LINES", tigetnum("li"), 25);
90
91 #elif defined(USE_TERMCAP)
92
93   term = getenv("TERM"); if (!term) return (-1);
94   if (tgetent(tty->termbuf, term) < 1) return (-1);
95
96   tty->termbuf = malloc(4096); if (!tty->termbuf) return (-1);
97   tty->capbuf = malloc(4096); if (!tty->capbuf) return (-1);
98
99   capcur = tty->capbuf;
100   tty->cap.cr = tgetstr("cr", &capcur);
101   tty->cap.up = tgetstr("up", &capcur);
102   tty->cap.ce = tgetstr("ce", &capcur);
103   tty->cap.cd = tgetstr("cd", &capcur);
104
105   if (tgetnum("sg") < 1) {
106     tty->cap.mr = tgetstr("mr", &capcur);
107     tty->cap.md = tgetstr("md", &capcur);
108     tty->cap.me = tgetstr("me", &capcur);
109
110     tty->cap.af = tgetstr("AF", &capcur);
111     tty->cap.ab = tgetstr("AB", &capcur);
112     tty->cap.op = tgetstr("op", &capcur);
113   }
114
115   if (tgetflag("ut") > 0) tty->cap.f |= TCF_BCE;
116
117   t = tgetstr("pc", &capcur); PC = t ? *t : 0;
118
119   SETDIM(defwd, "COLUMNS", tgetnum("co"), 80);
120   SETDIM(defht, "LINES", tgetnum("li"), 25);
121
122 #else
123
124   SETDIM(defwd, "COLUMNS", -1, 80);
125   SETDIM(defht, "LINES", -1, 25);
126
127 #endif
128
129 #undef SETDIM
130
131   if (!tty->cap.cr) tty->cap.cr = "\r";
132   if (!tty->cap.up || !tty->cap.ce || !tty->cap.cd)
133     { fclose(tty->fp); tty->fp = 0; return (-1); }
134   if (!tty->cap.af || !tty->cap.ab || !tty->cap.op) tty->cap.op = 0;
135   if (!tty->cap.me) tty->cap.mr = tty->cap.md = 0;
136   return (0);
137 }
138
139 void progress_free(struct progress_state *progress)
140 {
141   struct progress_ttyinfo *tty = &progress->tty;
142
143   if (tty->fp) { fclose(tty->fp); tty->fp = 0; }
144   free(tty->termbuf); free(tty->capbuf); tty->termbuf = tty->capbuf = 0;
145 }
146
147 #if defined(USE_TERMINFO)
148 static const struct progress_ttyinfo *curtty = 0;
149 static int putty(int ch) { return (putc(ch, curtty->fp)); }
150 static void put_sequence(const struct progress_ttyinfo *tty,
151                          const char *p, unsigned nlines)
152   { if (p) { curtty = tty; tputs(p, nlines, putty); } }
153 static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
154   { put_sequence(tty, tgoto(tty->cap.af, -1, colour), 1); }
155 static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
156   { put_sequence(tty, tgoto(tty->cap.ab, -1, colour), 1); }
157 #elif defined(USE_TERMCAP)
158 static const struct progress_ttyinfo *curtty = 0;
159 static int putty(int ch) { return (putc(ch, curtty->fp)); }
160 static void put_sequence(const struct progress_ttyinfo *tty,
161                          const char *p, unsigned nlines)
162   { if (p) { curtty = tty; tputs(p, nlines, putty); } }
163 static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
164   { put_sequence(tty, tgoto(tty->cap.af, -1, colour), 1); }
165 static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
166   { put_sequence(tty, tgoto(tty->cap.ab, -1, colour), 1); }
167 #else
168 static void put_sequence(const struct progress_ttyinfo *tty,
169                          const char *p, unsigned nlines) { ; }
170 static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
171   { ; }
172 static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
173   { ; }
174 #endif
175
176 #define CLRF_ALL 1u
177 static int clear_progress(struct progress_state *progress,
178                           struct progress_render_state *render, unsigned f)
179 {
180   const struct progress_ttyinfo *tty = &progress->tty;
181   unsigned ndel, nleave;
182   unsigned i;
183
184   if (!tty->fp) return (-1);
185
186   put_sequence(tty, tty->cap.cr, 1);
187   if (progress->last_lines) {
188     if (f&CLRF_ALL)
189       { ndel = progress->last_lines; nleave = 0; }
190     else {
191       if (progress->nitems >= progress->last_lines) ndel = 0;
192       else ndel = progress->last_lines - progress->nitems;
193       nleave = progress->last_lines - ndel;
194     }
195     if (!ndel)
196       for (i = 0; i < nleave - 1; i++) put_sequence(tty, tty->cap.up, 1);
197     else {
198       for (i = 0; i < ndel - 1; i++) put_sequence(tty, tty->cap.up, 1);
199       put_sequence(tty, tty->cap.cd, ndel);
200       for (i = 0; i < nleave; i++) put_sequence(tty, tty->cap.up, 1);
201     }
202   }
203   progress->last_lines = 0;
204   if (ferror(tty->fp)) return (-1);
205   return (0);
206 }
207
208 static int grow_linebuf(struct progress_render_state *render, size_t want)
209 {
210   char *newbuf; size_t newsz;
211
212   if (want <= render->linesz) return (0);
213   if (!render->linesz) newsz = 4*render->width + 1;
214   else newsz = render->linesz;
215   while (newsz < want) newsz *= 2;
216   newbuf = malloc(newsz + 1); if (!newbuf) return (-1);
217   newbuf[newsz] = 0;
218   if (render->leftsz)
219     memcpy(newbuf, render->linebuf, render->leftsz);
220   if (render->rightsz)
221     memcpy(newbuf + newsz - render->rightsz,
222            render->linebuf + render->linesz - render->rightsz,
223            render->rightsz);
224   free(render->linebuf); render->linebuf = newbuf; render->linesz = newsz;
225   return (0);
226 }
227
228 static int grow_tempbuf(struct progress_render_state *render, size_t want)
229 {
230   char *newbuf; size_t newsz;
231
232   if (want <= render->tempsz) return (0);
233   if (!render->tempsz) newsz = 4*render->width + 1;
234   else newsz = render->tempsz;
235   while (newsz < want) newsz *= 2;
236   newbuf = malloc(newsz + 1); if (!newbuf) return (-1);
237   newbuf[newsz] = 0;
238   if (render->tempsz) memcpy(newbuf, render->tempbuf, render->tempsz);
239   free(render->tempbuf); render->tempbuf = newbuf; render->tempsz = newsz;
240   return (0);
241 }
242
243 static int setup_render_state(struct progress_state *progress,
244                               struct progress_render_state *render)
245 {
246   const struct progress_ttyinfo *tty = &progress->tty;
247   struct winsize wsz;
248   int rc = 0;
249
250   render->tty = tty;
251   render->linebuf = 0; render->linesz = 0;
252   render->tempbuf = 0; render->tempsz = 0;
253
254 #ifdef USE_TERMCAP
255   render->old_bc = BC; BC = 0;
256   render->old_up = UP; UP = 0;
257 #endif
258
259   if (!ioctl(fileno(tty->fp), TIOCGWINSZ, &wsz))
260     { render->width = wsz.ws_col; render->height = wsz.ws_row; }
261   else
262     { render->width = tty->defwd; render->height = tty->defht; rc = -1; }
263
264   if (render->width && !tty->cap.op && !tty->cap.mr) render->width--;
265
266   return (rc);
267 }
268
269 static void free_render_state(struct progress_render_state *render)
270 {
271   fflush(render->tty->fp);
272   free(render->linebuf); render->linebuf = 0; render->linesz = 0;
273   free(render->tempbuf); render->tempbuf = 0; render->tempsz = 0;
274 #ifdef USE_TERMCAP
275   UP = render->old_up;
276   BC = render->old_bc;
277 #endif
278 }
279
280 #define CONV_MORE ((size_t)-2)
281 #define CONV_BAD ((size_t)-1)
282
283 struct measure {
284   mbstate_t ps;
285   const char *p; size_t i, sz;
286   unsigned wd;
287 };
288
289 static void init_measure(struct measure *m, const char *p, size_t sz)
290 {
291   m->p = p; m->sz = sz; m->i = 0; m->wd = 0;
292   memset(&m->ps, 0, sizeof(m->ps));
293 }
294
295 static int advance_measure(struct measure *m)
296 {
297   wchar_t wch;
298   unsigned chwd;
299   size_t n;
300
301   n = mbrtowc(&wch, m->p + m->i, m->sz - m->i, &m->ps);
302   if (!n) { chwd = 0; n = m->sz - m->i; }
303   else if (n == CONV_MORE) { chwd = 2; n = m->sz - m->i; }
304   else if (n == CONV_BAD) { chwd = 2; n = 1; }
305   else chwd = wcwidth(wch);
306
307   m->i += n; m->wd += chwd;
308   return (m->i < m->sz);
309 }
310
311 static unsigned string_width(const char *p, size_t sz)
312 {
313   struct measure m;
314
315   init_measure(&m, p, sz);
316   while (advance_measure(&m));
317   return (m.wd);
318 }
319
320 static size_t split_string(const char *p, size_t sz,
321                            unsigned *wd_out, unsigned maxwd)
322 {
323   struct measure m;
324   size_t lasti; unsigned lastwd;
325   int more;
326
327   init_measure(&m, p, sz);
328   for (;;) {
329     lasti = m.i; lastwd = m.wd;
330     more = advance_measure(&m);
331     if (m.wd > maxwd) { *wd_out = lastwd; return (lasti); }
332     else if (!more) { *wd_out = m.wd; return (sz); }
333   }
334 }
335
336 enum { LEFT, RIGHT };
337 static int putstr(struct progress_render_state *render, unsigned side,
338                   const char *p, size_t n)
339 {
340   unsigned newwd = string_width(p, n);
341   size_t want;
342
343   if (newwd >= render->width - render->leftwd - render->rightwd) return (-1);
344   want = render->leftsz + render->rightsz + n;
345   if (want > render->linesz && grow_linebuf(render, want)) return (-1);
346   switch (side) {
347     case LEFT:
348       memcpy(render->linebuf + render->leftsz, p, n);
349       render->leftsz += n; render->leftwd += newwd;
350       break;
351     case RIGHT:
352       memcpy(render->linebuf + render->linesz - render->rightsz - n, p, n);
353       render->rightsz += n; render->rightwd += newwd;
354       break;
355     default:
356       return (-1);
357   }
358   return (0);
359 }
360
361 static int vputf(struct progress_render_state *render, unsigned side,
362                  const char *fmt, va_list ap)
363 {
364   va_list bp;
365   int rc;
366
367   if (!render->tempsz && grow_tempbuf(render, 2*strlen(fmt))) return (-1);
368   for (;;) {
369     va_copy(bp, ap);
370     rc = vsnprintf(render->tempbuf, render->tempsz, fmt, bp);
371     va_end(bp);
372     if (rc < 0) return (-1);
373     if (rc <= render->tempsz) break;
374     if (grow_tempbuf(render, 2*(rc + 1))) return (-1);
375   }
376   if (putstr(render, side, render->tempbuf, rc)) return (-1);
377   return (0);
378 }
379
380 int progress_vputleft(struct progress_render_state *render,
381                       const char *fmt, va_list ap)
382   { return (vputf(render, LEFT, fmt, ap)); }
383
384 int progress_vputright(struct progress_render_state *render,
385                        const char *fmt, va_list ap)
386   { return (vputf(render, RIGHT, fmt, ap)); }
387
388 int progress_putleft(struct progress_render_state *render,
389                      const char *fmt, ...)
390 {
391   va_list ap;
392   int rc;
393
394   va_start(ap, fmt); rc = vputf(render, LEFT, fmt, ap); va_end(ap);
395   return (rc);
396 }
397
398 int progress_putright(struct progress_render_state *render,
399                       const char *fmt, ...)
400 {
401   va_list ap;
402   int rc;
403
404   va_start(ap, fmt); rc = vputf(render, RIGHT, fmt, ap); va_end(ap);
405   return (rc);
406 }
407
408 enum {
409   LEFT_COLOUR,
410   LEFT_MONO,
411   LEFT_SIMPLE,
412   RIGHT_ANY,
413   STOP
414 };
415
416 struct bar_state {
417   const struct progress_render_state *render;
418   unsigned pos, nextpos, state;
419 };
420
421 static void advance_bar_state(struct bar_state *bar)
422 {
423   const struct progress_render_state *render = bar->render;
424   const struct progress_ttyinfo *tty = render->tty;
425   size_t here = bar->nextpos;
426
427   while (bar->nextpos == here) {
428     switch (bar->state) {
429       case LEFT_COLOUR: set_bgcolour(tty, 3); goto right;
430       case LEFT_MONO: put_sequence(tty, tty->cap.me, 1); goto right;
431       case LEFT_SIMPLE: putc('|', tty->fp); goto right;
432       right: bar->state = RIGHT_ANY; bar->nextpos = render->width; break;
433       case RIGHT_ANY: bar->state = STOP; bar->nextpos = UINT_MAX; break;
434     }
435   }
436 }
437
438 static void put_str(FILE *fp, const char *p, size_t sz)
439   { while (sz--) putc(*p++, fp); }
440 static void put_spc(FILE *fp, unsigned n)
441   { while (n--) putc(' ', fp); }
442
443 static void put_barstr(struct bar_state *bar, const char *p, size_t sz)
444 {
445   unsigned wd;
446   size_t n;
447
448   for (;;) {
449     n = split_string(p, sz, &wd, bar->nextpos - bar->pos);
450     if (n == sz && wd < bar->nextpos - bar->pos) break;
451     put_str(bar->render->tty->fp, p, n); bar->pos += wd;
452     advance_bar_state(bar);
453     p += n; sz -= n;
454   }
455   put_str(bar->render->tty->fp, p, sz); bar->pos += wd;
456 }
457
458 static void put_barspc(struct bar_state *bar, unsigned n)
459 {
460   unsigned step;
461
462   for (;;) {
463     step = bar->nextpos - bar->pos;
464     if (n < step) break;
465     put_spc(bar->render->tty->fp, step); bar->pos += step;
466     advance_bar_state(bar);
467     n -= step;
468   }
469   put_spc(bar->render->tty->fp, n); bar->pos += n;
470 }
471
472 int progress_showbar(struct progress_render_state *render, double frac)
473 {
474   const struct progress_ttyinfo *tty = render->tty;
475   struct bar_state bar;
476
477   if (!tty->fp) return (-1);
478
479   bar.render = render; bar.pos = 0; bar.nextpos = frac*render->width + 0.5;
480
481   if (tty->cap.op) {
482     set_fgcolour(tty, 0); bar.state = LEFT_COLOUR;
483     if (bar.nextpos) set_bgcolour(tty, 2);
484     else advance_bar_state(&bar);
485   } else if (tty->cap.mr) {
486     if (bar.nextpos)
487       { bar.state = LEFT_MONO; put_sequence(tty, tty->cap.mr, 1); }
488     else
489       { bar.state = RIGHT; bar.nextpos = render->width; }
490   } else
491     bar.state = LEFT_SIMPLE;
492
493   put_barstr(&bar, render->linebuf, render->leftsz);
494   put_barspc(&bar, render->width - render->leftwd - render->rightwd);
495   put_barstr(&bar,
496              render->linebuf + render->linesz - render->rightsz,
497              render->rightsz);
498
499   put_sequence(tty, tty->cap.me, 1);
500   put_sequence(tty, tty->cap.op, 1);
501
502   return (0);
503 }
504
505 int progress_shownotice(struct progress_render_state *render, int bg, int fg)
506 {
507   const struct progress_ttyinfo *tty = render->tty;
508
509   if (!tty->fp) return (-1);
510
511   if (tty->cap.op) { set_fgcolour(tty, fg); set_bgcolour(tty, bg); }
512   else if (tty->cap.mr) put_sequence(tty, tty->cap.mr, 1);
513   if (tty->cap.md) put_sequence(tty, tty->cap.md, 1);
514
515   put_str(tty->fp, render->linebuf, render->leftsz);
516   if (!render->rightsz && (tty->cap.f&TCF_BCE) && tty->cap.ce)
517     put_sequence(tty, tty->cap.ce, 1);
518   else {
519     put_spc(tty->fp, render->width - render->leftwd - render->rightwd);
520     put_str(tty->fp,
521             render->linebuf + render->linesz - render->rightsz,
522             render->rightsz);
523   }
524
525   put_sequence(tty, tty->cap.me, 1);
526   put_sequence(tty, tty->cap.op, 1);
527
528   return (0);
529 }
530
531 int progress_additem(struct progress_state *progress,
532                      struct progress_item *item)
533 {
534   if (item->parent) return (-1);
535   item->prev = progress->end_item; item->next = 0;
536   if (progress->end_item) progress->end_item->next = item;
537   else progress->items = item;
538   progress->end_item = item; item->parent = progress;
539   progress->nitems++;
540
541   return (0);
542 }
543
544 int progress_clear(struct progress_state *progress)
545 {
546   struct progress_render_state render;
547
548   if (!progress->tty.fp) return (-1);
549   if (setup_render_state(progress, &render)) return (-1);
550   clear_progress(progress, &render, CLRF_ALL);
551   free_render_state(&render);
552   return (0);
553 }
554
555 int progress_update(struct progress_state *progress)
556 {
557   struct progress_render_state render;
558   struct progress_item *item;
559   unsigned f = 0;
560 #define f_any 1u
561
562   if (!progress->tty.fp) return (-1);
563   if (setup_render_state(progress, &render)) return (-1);
564   clear_progress(progress, &render, 0);
565
566   for (item = progress->items; item; item = item->next) {
567     if (f&f_any) fputs("\r\n", progress->tty.fp);
568     render.leftsz = render.rightsz = 0;
569     render.leftwd = render.rightwd = 0;
570     item->render(item, &render); progress->last_lines++; f |= f_any;
571     if (progress->last_lines > render.height) break;
572   }
573   free_render_state(&render);
574   return (0);
575 }
576
577 int progress_removeitem(struct progress_state *progress,
578                         struct progress_item *item)
579 {
580   if (!item->parent) return (-1);
581   if (item->next) item->next->prev = item->prev;
582   else (progress->end_item) = item->prev;
583   if (item->prev) item->prev->next = item->next;
584   else (progress->items) = item->next;
585   progress->nitems--; item->parent = 0;
586
587   return (0);
588 }