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