11 #include <sys/ioctl.h>
13 #if defined(USE_TERMINFO)
16 #elif defined(USE_TERMCAP)
20 #include "multiprogress.h"
22 static FILE *dup_stream(int fd
)
27 newfd
= dup(fd
); if (newfd
< 0) return (0);
28 fp
= fdopen(newfd
, "r+"); if (!fp
) return (0);
32 int progress_init(struct progress_state
*progress
)
40 struct progress_ttyinfo
*tty
;
46 tty
->termbuf
= tty
->capbuf
= 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;
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;
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);
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; } \
67 #if defined(USE_TERMINFO)
69 if (setupterm(0, fileno(tty
->fp
), &err
) != OK
|| err
< 1) return (-1);
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");
77 if (tigetnum("xmc") < 1) {
78 tty
->cap
.mr
= tigetstr("rev");
79 tty
->cap
.md
= tigetstr("bold");
80 tty
->cap
.me
= tigetstr("sgr0");
82 tty
->cap
.af
= tigetstr("setaf");
83 tty
->cap
.ab
= tigetstr("setab");
84 tty
->cap
.op
= tigetstr("op");
87 if (tigetflag("bce") > 0) tty
->cap
.f
|= TCF_BCE
;
89 SETDIM(defwd
, "COLUMNS", tigetnum("co"), 80);
90 SETDIM(defht
, "LINES", tigetnum("li"), 25);
92 #elif defined(USE_TERMCAP)
94 tty
->termbuf
= malloc(4096); if (!tty
->termbuf
) return (-1);
95 tty
->capbuf
= malloc(4096); if (!tty
->capbuf
) return (-1);
97 term
= getenv("TERM"); if (!term
) return (-1);
98 if (tgetent(tty
->termbuf
, term
) < 1) return (-1);
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
);
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
);
112 tty
->cap
.af
= tgetstr("AF", &capcur
);
113 tty
->cap
.ab
= tgetstr("AB", &capcur
);
114 tty
->cap
.op
= tgetstr("op", &capcur
);
117 if (tgetflag("ut") > 0) tty
->cap
.f
|= TCF_BCE
;
119 t
= tgetstr("pc", &capcur
); tty
->cap
.pc
= t ?
*t
: 0;
121 SETDIM(defwd
, "COLUMNS", tgetnum("co"), 80);
122 SETDIM(defht
, "LINES", tgetnum("li"), 25);
126 SETDIM(defwd
, "COLUMNS", -1, 80);
127 SETDIM(defht
, "LINES", -1, 25);
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;
142 void progress_free(struct progress_state
*progress
)
144 struct progress_ttyinfo
*tty
= &progress
->tty
;
146 if (tty
->fp
) { fclose(tty
->fp
); tty
->fp
= 0; }
147 free(tty
->termbuf
); free(tty
->capbuf
); tty
->termbuf
= tty
->capbuf
= 0;
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); }
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
)
175 static void set_bgcolour(const struct progress_ttyinfo
*tty
, int colour
)
180 static int clear_progress(struct progress_state
*progress
,
181 struct progress_render_state
*render
, unsigned f
)
183 const struct progress_ttyinfo
*tty
= &progress
->tty
;
184 unsigned ndel
, nleave
;
187 if (!tty
->fp
) return (-1);
189 put_sequence(tty
, tty
->cap
.cr
, 1);
190 if (progress
->last_lines
) {
192 { ndel
= progress
->last_lines
; nleave
= 0; }
194 if (progress
->nitems
>= progress
->last_lines
) ndel
= 0;
195 else ndel
= progress
->last_lines
- progress
->nitems
;
196 nleave
= progress
->last_lines
- ndel
;
199 for (i
= 0; i
< nleave
- 1; i
++) put_sequence(tty
, tty
->cap
.up
, 1);
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);
206 progress
->last_lines
= 0;
207 if (ferror(tty
->fp
)) return (-1);
211 static int grow_linebuf(struct progress_render_state
*render
, size_t want
)
213 char *newbuf
; size_t newsz
;
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);
222 memcpy(newbuf
, render
->linebuf
, render
->leftsz
);
224 memcpy(newbuf
+ newsz
- render
->rightsz
,
225 render
->linebuf
+ render
->linesz
- render
->rightsz
,
227 free(render
->linebuf
); render
->linebuf
= newbuf
; render
->linesz
= newsz
;
231 static int grow_tempbuf(struct progress_render_state
*render
, size_t want
)
233 char *newbuf
; size_t newsz
;
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);
241 if (render
->tempsz
) memcpy(newbuf
, render
->tempbuf
, render
->tempsz
);
242 free(render
->tempbuf
); render
->tempbuf
= newbuf
; render
->tempsz
= newsz
;
246 static int setup_render_state(struct progress_state
*progress
,
247 struct progress_render_state
*render
)
249 const struct progress_ttyinfo
*tty
= &progress
->tty
;
254 render
->linebuf
= 0; render
->linesz
= 0;
255 render
->tempbuf
= 0; render
->tempsz
= 0;
258 render
->old_bc
= BC
; BC
= 0;
259 render
->old_up
= UP
; UP
= 0;
260 render
->old_pc
= PC
; PC
= tty
->cap
.pc
;
263 if (!ioctl(fileno(tty
->fp
), TIOCGWINSZ
, &wsz
))
264 { render
->width
= wsz
.ws_col
; render
->height
= wsz
.ws_row
; }
266 { render
->width
= tty
->defwd
; render
->height
= tty
->defht
; rc
= -1; }
268 if (render
->width
&& !tty
->cap
.op
&& !tty
->cap
.mr
) render
->width
--;
273 static void free_render_state(struct progress_render_state
*render
)
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;
285 #define CONV_MORE ((size_t)-2)
286 #define CONV_BAD ((size_t)-1)
290 const char *p
; size_t i
, sz
;
294 static void init_measure(struct measure
*m
, const char *p
, size_t sz
)
296 m
->p
= p
; m
->sz
= sz
; m
->i
= 0; m
->wd
= 0;
297 memset(&m
->ps
, 0, sizeof(m
->ps
));
300 static int advance_measure(struct measure
*m
)
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
);
312 m
->i
+= n
; m
->wd
+= chwd
;
313 return (m
->i
< m
->sz
);
316 static unsigned string_width(const char *p
, size_t sz
)
320 init_measure(&m
, p
, sz
);
321 while (advance_measure(&m
));
325 static size_t split_string(const char *p
, size_t sz
,
326 unsigned *wd_out
, unsigned maxwd
)
329 size_t lasti
; unsigned lastwd
;
332 init_measure(&m
, p
, sz
);
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
); }
341 enum { LEFT
, RIGHT
};
342 static int putstr(struct progress_render_state
*render
, unsigned side
,
343 const char *p
, size_t n
)
345 unsigned newwd
= string_width(p
, n
);
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);
353 memcpy(render
->linebuf
+ render
->leftsz
, p
, n
);
354 render
->leftsz
+= n
; render
->leftwd
+= newwd
;
357 memcpy(render
->linebuf
+ render
->linesz
- render
->rightsz
- n
, p
, n
);
358 render
->rightsz
+= n
; render
->rightwd
+= newwd
;
366 static int vputf(struct progress_render_state
*render
, unsigned side
,
367 const char *fmt
, va_list ap
)
372 if (!render
->tempsz
&& grow_tempbuf(render
, 2*strlen(fmt
))) return (-1);
375 rc
= vsnprintf(render
->tempbuf
, render
->tempsz
, fmt
, bp
);
377 if (rc
< 0) return (-1);
378 if (rc
<= render
->tempsz
) break;
379 if (grow_tempbuf(render
, 2*(rc
+ 1))) return (-1);
381 if (putstr(render
, side
, render
->tempbuf
, rc
)) return (-1);
385 int progress_vputleft(struct progress_render_state
*render
,
386 const char *fmt
, va_list ap
)
387 { return (vputf(render
, LEFT
, fmt
, ap
)); }
389 int progress_vputright(struct progress_render_state
*render
,
390 const char *fmt
, va_list ap
)
391 { return (vputf(render
, RIGHT
, fmt
, ap
)); }
393 int progress_putleft(struct progress_render_state
*render
,
394 const char *fmt
, ...)
399 va_start(ap
, fmt
); rc
= vputf(render
, LEFT
, fmt
, ap
); va_end(ap
);
403 int progress_putright(struct progress_render_state
*render
,
404 const char *fmt
, ...)
409 va_start(ap
, fmt
); rc
= vputf(render
, RIGHT
, fmt
, ap
); va_end(ap
);
422 const struct progress_render_state
*render
;
423 unsigned pos
, nextpos
, state
;
426 static void advance_bar_state(struct bar_state
*bar
)
428 const struct progress_render_state
*render
= bar
->render
;
429 const struct progress_ttyinfo
*tty
= render
->tty
;
430 size_t here
= bar
->nextpos
;
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;
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
); }
448 static void put_barstr(struct bar_state
*bar
, const char *p
, size_t sz
)
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
);
460 put_str(bar
->render
->tty
->fp
, p
, sz
); bar
->pos
+= wd
;
463 static void put_barspc(struct bar_state
*bar
, unsigned n
)
468 step
= bar
->nextpos
- bar
->pos
;
470 put_spc(bar
->render
->tty
->fp
, step
); bar
->pos
+= step
;
471 advance_bar_state(bar
);
474 put_spc(bar
->render
->tty
->fp
, n
); bar
->pos
+= n
;
477 int progress_showbar(struct progress_render_state
*render
, double frac
)
479 const struct progress_ttyinfo
*tty
= render
->tty
;
480 struct bar_state bar
;
482 if (!tty
->fp
) return (-1);
484 bar
.render
= render
; bar
.pos
= 0; bar
.nextpos
= frac
*render
->width
+ 0.5;
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
) {
492 { bar
.state
= LEFT_MONO
; put_sequence(tty
, tty
->cap
.mr
, 1); }
494 { bar
.state
= RIGHT
; bar
.nextpos
= render
->width
; }
496 bar
.state
= LEFT_SIMPLE
;
498 put_barstr(&bar
, render
->linebuf
, render
->leftsz
);
499 put_barspc(&bar
, render
->width
- render
->leftwd
- render
->rightwd
);
501 render
->linebuf
+ render
->linesz
- render
->rightsz
,
504 put_sequence(tty
, tty
->cap
.me
, 1);
505 put_sequence(tty
, tty
->cap
.op
, 1);
510 int progress_shownotice(struct progress_render_state
*render
, int bg
, int fg
)
512 const struct progress_ttyinfo
*tty
= render
->tty
;
514 if (!tty
->fp
) return (-1);
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);
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);
524 put_spc(tty
->fp
, render
->width
- render
->leftwd
- render
->rightwd
);
526 render
->linebuf
+ render
->linesz
- render
->rightsz
,
530 put_sequence(tty
, tty
->cap
.me
, 1);
531 put_sequence(tty
, tty
->cap
.op
, 1);
536 int progress_additem(struct progress_state
*progress
,
537 struct progress_item
*item
)
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
;
549 int progress_clear(struct progress_state
*progress
)
551 struct progress_render_state render
;
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
);
560 int progress_update(struct progress_state
*progress
)
562 struct progress_render_state render
;
563 const struct progress_ttyinfo
*tty
= &progress
->tty
;
564 struct progress_item
*item
;
568 if (!progress
->tty
.fp
) return (-1);
569 if (setup_render_state(progress
, &render
)) return (-1);
570 clear_progress(progress
, &render
, 0);
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;
579 if (f
&f_any
) put_sequence(tty
, tty
->cap
.cr
, 1);
580 free_render_state(&render
);
584 int progress_removeitem(struct progress_state
*progress
,
585 struct progress_item
*item
)
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;