dvd-sector-copy.c: Reinterpret the `step' as being beyond the high bound.
[dvdrip] / multiprogress.c
CommitLineData
dc53ebfa
MW
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
22static 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
32int 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.up || !tty->cap.ce || !tty->cap.cd)
132 { fclose(tty->fp); tty->fp = 0; return (-1); }
133 if (!tty->cap.af || !tty->cap.ab || !tty->cap.op) tty->cap.op = 0;
134 if (!tty->cap.me) tty->cap.mr = tty->cap.md = 0;
135 return (0);
136}
137
138void progress_free(struct progress_state *progress)
139{
140 struct progress_ttyinfo *tty = &progress->tty;
141
142 if (tty->fp) { fclose(tty->fp); tty->fp = 0; }
143 free(tty->termbuf); free(tty->capbuf); tty->termbuf = tty->capbuf = 0;
144}
145
146#if defined(USE_TERMINFO)
147static const struct progress_ttyinfo *curtty = 0;
148static int putty(int ch) { return (putc(ch, curtty->fp)); }
149static void put_sequence(const struct progress_ttyinfo *tty,
150 const char *p, unsigned nlines)
151 { if (p) { curtty = tty; tputs(p, nlines, putty); } }
152static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
153 { put_sequence(tty, tgoto(tty->cap.af, -1, colour), 1); }
154static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
155 { put_sequence(tty, tgoto(tty->cap.ab, -1, colour), 1); }
156#elif defined(USE_TERMCAP)
157static const struct progress_ttyinfo *curtty = 0;
158static int putty(int ch) { return (putc(ch, curtty->fp)); }
159static void put_sequence(const struct progress_ttyinfo *tty,
160 const char *p, unsigned nlines)
161 { if (p) { curtty = tty; tputs(p, nlines, putty); } }
162static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
163 { put_sequence(tty, tgoto(tty->cap.af, -1, colour), 1); }
164static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
165 { put_sequence(tty, tgoto(tty->cap.ab, -1, colour), 1); }
166#else
167static void put_sequence(const struct progress_ttyinfo *tty,
168 const char *p, unsigned nlines) { ; }
169static void set_fgcolour(const struct progress_ttyinfo *tty, int colour)
170 { ; }
171static void set_bgcolour(const struct progress_ttyinfo *tty, int colour)
172 { ; }
173#endif
174
175#define CLRF_ALL 1u
176static int clear_progress(struct progress_state *progress,
177 struct progress_render_state *render, unsigned f)
178{
179 const struct progress_ttyinfo *tty = &progress->tty;
180 unsigned ndel, nleave;
181 unsigned i;
182
183 if (!tty->fp) return (-1);
184
185 put_sequence(tty, tty->cap.cr, 1);
186 if (progress->last_lines) {
187 if (f&CLRF_ALL)
188 { ndel = progress->last_lines; nleave = 0; }
189 else {
190 if (progress->nitems >= progress->last_lines) ndel = 0;
191 else ndel = progress->last_lines - progress->nitems;
192 nleave = progress->last_lines - ndel;
193 }
194 if (!ndel)
195 for (i = 0; i < nleave - 1; i++) put_sequence(tty, tty->cap.up, 1);
196 else {
197 for (i = 0; i < ndel - 1; i++) put_sequence(tty, tty->cap.up, 1);
198 put_sequence(tty, tty->cap.cd, ndel);
199 for (i = 0; i < nleave; i++) put_sequence(tty, tty->cap.up, 1);
200 }
201 }
202 progress->last_lines = 0;
203 if (ferror(tty->fp)) return (-1);
204 return (0);
205}
206
207static int grow_linebuf(struct progress_render_state *render, size_t want)
208{
209 char *newbuf; size_t newsz;
210
211 if (want <= render->linesz) return (0);
212 if (!render->linesz) newsz = 4*render->width + 1;
213 else newsz = render->linesz;
214 while (newsz < want) newsz *= 2;
215 newbuf = malloc(newsz + 1); if (!newbuf) return (-1);
216 newbuf[newsz] = 0;
217 if (render->leftsz)
218 memcpy(newbuf, render->linebuf, render->leftsz);
219 if (render->rightsz)
220 memcpy(newbuf + newsz - render->rightsz,
221 render->linebuf + render->linesz - render->rightsz,
222 render->rightsz);
223 free(render->linebuf); render->linebuf = newbuf; render->linesz = newsz;
224 return (0);
225}
226
227static int grow_tempbuf(struct progress_render_state *render, size_t want)
228{
229 char *newbuf; size_t newsz;
230
231 if (want <= render->tempsz) return (0);
232 if (!render->tempsz) newsz = 4*render->width + 1;
233 else newsz = render->tempsz;
234 while (newsz < want) newsz *= 2;
235 newbuf = malloc(newsz + 1); if (!newbuf) return (-1);
236 newbuf[newsz] = 0;
237 if (render->tempsz) memcpy(newbuf, render->tempbuf, render->tempsz);
238 free(render->tempbuf); render->tempbuf = newbuf; render->tempsz = newsz;
239 return (0);
240}
241
242static int setup_render_state(struct progress_state *progress,
243 struct progress_render_state *render)
244{
245 const struct progress_ttyinfo *tty = &progress->tty;
246 struct winsize wsz;
247 int rc = 0;
248
249 render->tty = tty;
250 render->linebuf = 0; render->linesz = 0;
251 render->tempbuf = 0; render->tempsz = 0;
252
253#ifdef USE_TERMCAP
254 render->old_bc = BC; BC = 0;
255 render->old_up = UP; UP = 0;
256#endif
257
258 if (!ioctl(fileno(tty->fp), TIOCGWINSZ, &wsz))
259 { render->width = wsz.ws_col; render->height = wsz.ws_row; }
260 else
261 { render->width = tty->defwd; render->height = tty->defht; rc = -1; }
262
263 if (render->width && !tty->cap.op && !tty->cap.mr) render->width--;
264
265 return (rc);
266}
267
268static void free_render_state(struct progress_render_state *render)
269{
270 fflush(render->tty->fp);
271 free(render->linebuf); render->linebuf = 0; render->linesz = 0;
272 free(render->tempbuf); render->tempbuf = 0; render->tempsz = 0;
273#ifdef USE_TERMCAP
274 UP = render->old_up;
275 BC = render->old_bc;
276#endif
277}
278
279#define CONV_MORE ((size_t)-2)
280#define CONV_BAD ((size_t)-1)
281
282struct measure {
283 mbstate_t ps;
284 const char *p; size_t i, sz;
285 unsigned wd;
286};
287
288static void init_measure(struct measure *m, const char *p, size_t sz)
289{
290 m->p = p; m->sz = sz; m->i = 0; m->wd = 0;
291 memset(&m->ps, 0, sizeof(m->ps));
292}
293
294static int advance_measure(struct measure *m)
295{
296 wchar_t wch;
297 unsigned chwd;
298 size_t n;
299
300 n = mbrtowc(&wch, m->p + m->i, m->sz - m->i, &m->ps);
301 if (!n) { chwd = 0; n = m->sz - m->i; }
302 else if (n == CONV_MORE) { chwd = 2; n = m->sz - m->i; }
303 else if (n == CONV_BAD) { chwd = 2; n = 1; }
304 else chwd = wcwidth(wch);
305
306 m->i += n; m->wd += chwd;
307 return (m->i < m->sz);
308}
309
310static unsigned string_width(const char *p, size_t sz)
311{
312 struct measure m;
313
314 init_measure(&m, p, sz);
315 while (advance_measure(&m));
316 return (m.wd);
317}
318
319static size_t split_string(const char *p, size_t sz,
320 unsigned *wd_out, unsigned maxwd)
321{
322 struct measure m;
323 size_t lasti; unsigned lastwd;
324 int more;
325
326 init_measure(&m, p, sz);
327 for (;;) {
328 lasti = m.i; lastwd = m.wd;
329 more = advance_measure(&m);
330 if (m.wd > maxwd) { *wd_out = lastwd; return (lasti); }
331 else if (!more) { *wd_out = m.wd; return (sz); }
332 }
333}
334
335enum { LEFT, RIGHT };
336static int putstr(struct progress_render_state *render, unsigned side,
337 const char *p, size_t n)
338{
339 unsigned newwd = string_width(p, n);
340 size_t want;
341
342 if (newwd >= render->width - render->leftwd - render->rightwd) return (-1);
343 want = render->leftsz + render->rightsz + n;
344 if (want > render->linesz && grow_linebuf(render, want)) return (-1);
345 switch (side) {
346 case LEFT:
347 memcpy(render->linebuf + render->leftsz, p, n);
348 render->leftsz += n; render->leftwd += newwd;
349 break;
350 case RIGHT:
351 memcpy(render->linebuf + render->linesz - render->rightsz - n, p, n);
352 render->rightsz += n; render->rightwd += newwd;
353 break;
354 default:
355 return (-1);
356 }
357 return (0);
358}
359
360static int vputf(struct progress_render_state *render, unsigned side,
361 const char *fmt, va_list ap)
362{
363 va_list bp;
364 int rc;
365
366 if (!render->tempsz && grow_tempbuf(render, 2*strlen(fmt))) return (-1);
367 for (;;) {
368 va_copy(bp, ap);
369 rc = vsnprintf(render->tempbuf, render->tempsz, fmt, bp);
370 va_end(bp);
371 if (rc < 0) return (-1);
372 if (rc <= render->tempsz) break;
373 if (grow_tempbuf(render, 2*(rc + 1))) return (-1);
374 }
375 if (putstr(render, side, render->tempbuf, rc)) return (-1);
376 return (0);
377}
378
379int progress_vputleft(struct progress_render_state *render,
380 const char *fmt, va_list ap)
381 { return (vputf(render, LEFT, fmt, ap)); }
382
383int progress_vputright(struct progress_render_state *render,
384 const char *fmt, va_list ap)
385 { return (vputf(render, RIGHT, fmt, ap)); }
386
387int progress_putleft(struct progress_render_state *render,
388 const char *fmt, ...)
389{
390 va_list ap;
391 int rc;
392
393 va_start(ap, fmt); rc = vputf(render, LEFT, fmt, ap); va_end(ap);
394 return (rc);
395}
396
397int progress_putright(struct progress_render_state *render,
398 const char *fmt, ...)
399{
400 va_list ap;
401 int rc;
402
403 va_start(ap, fmt); rc = vputf(render, RIGHT, fmt, ap); va_end(ap);
404 return (rc);
405}
406
407enum {
408 LEFT_COLOUR,
409 LEFT_MONO,
410 LEFT_SIMPLE,
411 RIGHT_ANY,
412 STOP
413};
414
415struct bar_state {
416 const struct progress_render_state *render;
417 unsigned pos, nextpos, state;
418};
419
420static void advance_bar_state(struct bar_state *bar)
421{
422 const struct progress_render_state *render = bar->render;
423 const struct progress_ttyinfo *tty = render->tty;
424 size_t here = bar->nextpos;
425
426 while (bar->nextpos == here) {
427 switch (bar->state) {
428 case LEFT_COLOUR: set_bgcolour(tty, 3); goto right;
429 case LEFT_MONO: put_sequence(tty, tty->cap.me, 1); goto right;
430 case LEFT_SIMPLE: putc('|', tty->fp); goto right;
431 right: bar->state = RIGHT_ANY; bar->nextpos = render->width; break;
432 case RIGHT_ANY: bar->state = STOP; bar->nextpos = UINT_MAX; break;
433 }
434 }
435}
436
437static void put_str(FILE *fp, const char *p, size_t sz)
438 { while (sz--) putc(*p++, fp); }
439static void put_spc(FILE *fp, unsigned n)
440 { while (n--) putc(' ', fp); }
441
442static void put_barstr(struct bar_state *bar, const char *p, size_t sz)
443{
444 unsigned wd;
445 size_t n;
446
447 for (;;) {
448 n = split_string(p, sz, &wd, bar->nextpos - bar->pos);
449 if (n == sz && wd < bar->nextpos - bar->pos) break;
450 put_str(bar->render->tty->fp, p, n); bar->pos += wd;
451 advance_bar_state(bar);
452 p += n; sz -= n;
453 }
454 put_str(bar->render->tty->fp, p, sz); bar->pos += wd;
455}
456
457static void put_barspc(struct bar_state *bar, unsigned n)
458{
459 unsigned step;
460
461 for (;;) {
462 step = bar->nextpos - bar->pos;
463 if (n < step) break;
464 put_spc(bar->render->tty->fp, step); bar->pos += step;
465 advance_bar_state(bar);
466 n -= step;
467 }
468 put_spc(bar->render->tty->fp, n); bar->pos += n;
469}
470
471int progress_showbar(struct progress_render_state *render, double frac)
472{
473 const struct progress_ttyinfo *tty = render->tty;
474 struct bar_state bar;
475
476 if (!tty->fp) return (-1);
477
478 bar.render = render; bar.pos = 0; bar.nextpos = frac*render->width + 0.5;
479
480 if (tty->cap.op) {
481 set_fgcolour(tty, 0); bar.state = LEFT_COLOUR;
482 if (bar.nextpos) set_bgcolour(tty, 2);
483 else advance_bar_state(&bar);
484 } else if (tty->cap.mr) {
485 if (bar.nextpos)
486 { bar.state = LEFT_MONO; put_sequence(tty, tty->cap.mr, 1); }
487 else
488 { bar.state = RIGHT; bar.nextpos = render->width; }
489 } else
490 bar.state = LEFT_SIMPLE;
491
492 put_barstr(&bar, render->linebuf, render->leftsz);
493 put_barspc(&bar, render->width - render->leftwd - render->rightwd);
494 put_barstr(&bar,
495 render->linebuf + render->linesz - render->rightsz,
496 render->rightsz);
497
498 put_sequence(tty, tty->cap.me, 1);
499 put_sequence(tty, tty->cap.op, 1);
500
501 return (0);
502}
503
504int progress_shownotice(struct progress_render_state *render, int bg, int fg)
505{
506 const struct progress_ttyinfo *tty = render->tty;
507
508 if (!tty->fp) return (-1);
509
510 if (tty->cap.op) { set_fgcolour(tty, fg); set_bgcolour(tty, bg); }
511 else if (tty->cap.mr) put_sequence(tty, tty->cap.mr, 1);
512 if (tty->cap.md) put_sequence(tty, tty->cap.md, 1);
513
514 put_str(tty->fp, render->linebuf, render->leftsz);
515 if (!render->rightsz && (tty->cap.f&TCF_BCE) && tty->cap.ce)
516 put_sequence(tty, tty->cap.ce, 1);
517 else {
518 put_spc(tty->fp, render->width - render->leftwd - render->rightwd);
519 put_str(tty->fp,
520 render->linebuf + render->linesz - render->rightsz,
521 render->rightsz);
522 }
523
524 put_sequence(tty, tty->cap.me, 1);
525 put_sequence(tty, tty->cap.op, 1);
526
527 return (0);
528}
529
530int progress_additem(struct progress_state *progress,
531 struct progress_item *item)
532{
533 if (item->parent) return (-1);
534 item->prev = progress->end_item; item->next = 0;
535 if (progress->end_item) progress->end_item->next = item;
536 else progress->items = item;
537 progress->end_item = item; item->parent = progress;
538 progress->nitems++;
539
540 return (0);
541}
542
543int progress_clear(struct progress_state *progress)
544{
545 struct progress_render_state render;
546
547 if (!progress->tty.fp) return (-1);
548 if (setup_render_state(progress, &render)) return (-1);
549 clear_progress(progress, &render, CLRF_ALL);
550 free_render_state(&render);
551 return (0);
552}
553
554int progress_update(struct progress_state *progress)
555{
556 struct progress_render_state render;
557 struct progress_item *item;
558 unsigned f = 0;
559#define f_any 1u
560
561 if (!progress->tty.fp) return (-1);
562 if (setup_render_state(progress, &render)) return (-1);
563 clear_progress(progress, &render, 0);
564
565 for (item = progress->items; item; item = item->next) {
566 if (f&f_any) fputs("\r\n", progress->tty.fp);
567 render.leftsz = render.rightsz = 0;
568 render.leftwd = render.rightwd = 0;
569 item->render(item, &render); progress->last_lines++; f |= f_any;
570 if (progress->last_lines > render.height) break;
571 }
572 free_render_state(&render);
573 return (0);
574}
575
576int progress_removeitem(struct progress_state *progress,
577 struct progress_item *item)
578{
579 if (!item->parent) return (-1);
580 if (item->next) item->next->prev = item->prev;
581 else (progress->end_item) = item->prev;
582 if (item->prev) item->prev->next = item->next;
583 else (progress->items) = item->next;
584 progress->nitems--; item->parent = 0;
585
586 return (0);
587}