3 * Progress bars for terminal programs
5 * (c) 2022 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This library is free software: you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or (at your
13 * option) any later version.
15 * This library is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
18 * License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this library. If not, see <https://www.gnu.org/licenses/>.
24 #ifndef MULTIPROGRESS_H
25 #define MULTIPROGRESS_H
27 /*----- Header files ------------------------------------------------------*/
32 /*----- Compiler-specific magic -------------------------------------------*/
34 #if (defined(__GNUC__) && (__GNUC__ > 2 || \
35 (__GNUC__ == 2 && __GNUC_MINOR__ >= 5))) || \
36 (defined(__clang__) && (__clang_major__ > 3 || \
37 (__clang_major__ == 3 && __clang_minor__ >= 3)))
38 # define MULTIPROGRESS__PRINTF_LIKE(farg, aarg) \
39 __attribute__((format(printf, farg, aarg)))
41 # define MULTIPROGRESS__PRINTF_LIKE(farg, aarg)
44 /*----- Data structures ---------------------------------------------------*/
46 struct progress_ttyinfo
{
47 /* Information about the terminal we're going to write to. This is
48 * maintained as part of the `progress_state' (see below) and
49 * published to renderers as part of the `progress_render_state'.
51 * The `fp' may be null, if no terminal could be opened, or it's just
52 * too deficient in terms of its capabilities. Capabilities are
53 * named following `termcap' conventions, even though we might well
54 * actually be using `terminfo' instead.
57 FILE *fp
; /* terminal stream, or null */
58 char *termbuf
, *capbuf
; /* buffers for termcap */
59 struct { /* terminal capabilities */
60 unsigned f
; /* various flags */
61 #define TCF_BCE 1u /* erases to background colour */
62 const char *cr
, *nw
, *up
, *ce
, *cd
; /* cursor motion and erasure */
63 const char *mr
, *md
, *me
; /* reverse video, bold */
64 const char *af
, *ab
, *op
; /* colour */
65 char pc
; /* pad character (termcap) */
67 unsigned defwd
, defht
; /* default width and height */
69 #define PROGRESS_TTYINFO_INIT \
70 { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 80, 25 }
72 struct progress_state
{
73 /* The main state for progress reporting. Here we keep track of the
74 * items which need to be displayed, and current state of the
78 struct progress_ttyinfo tty
; /* terminal state */
79 struct progress_item
*items
, *end_item
; /* list of progress items */
80 unsigned nitems
; /* number of items */
81 unsigned last_lines
; /* number written last time */
82 struct timeval tv_update
; /* last update time */
84 #define PROGRESS_STATE_INIT { PROGRESS_TTYINFO_INIT, 0, 0, 0, 0, { 0, 0 } }
86 struct progress_render_state
{
87 /* Information passed to rendering functions.
89 * The `linebuf' accumulates the text to be shown by
90 * `progress_showbar' or similar, which consists of left and right
91 * portions aligned left and right on the terminal line, with a
92 * variable-size cap in between. These strings are stored at the
93 * beginning and end of the `linebuf', so that (hopefully) new
94 * material can be added in the gap between them without us having to
95 * reallocate the buffer.
98 const struct progress_ttyinfo
*tty
; /* terminal state */
99 unsigned width
, height
; /* terminal size, in characters */
100 char *linebuf
; size_t linesz
; /* output buffer */
101 char *tempbuf
; size_t tempsz
; /* scratch buffer */
102 size_t leftsz
, rightsz
; /* left and right cursors */
103 unsigned leftwd
, rightwd
; /* left and right widths */
104 char *old_bc
, *old_up
, old_pc
; /* saved `termcap' globals */
107 struct progress_item
{
108 /* An item in the progress display.
110 * The `render' function is passed a pointer to the `progress_item'
111 * structure. Usually, it will need additional state: handle this by
112 * making the `progress_item' be the first member of a larger
113 * structure which holds the necessary information.
115 * The `render' function should limit its activities to actually
116 * writing a line of information to the terminal. In particular, it
117 * shouldn't try to calculate anything time-dependent itself.
120 struct progress_state
*parent
; /* controlling progress state */
121 struct progress_item
*next
, *prev
; /* forward and backward links */
122 void (*render
)(struct progress_item */
*item*/
, /* render function */
123 struct progress_render_state */
*rs*/
);
125 #define PROGRESS_ITEM_INIT { 0, 0, 0, 0 }
127 /*----- Functions provided ------------------------------------------------*/
129 extern int progress_init(struct progress_state */
*progress*/
);
130 /* Initialize PROGRESS.
132 * It is safe to call this function on uninitialized data. This
133 * involves opening a stream on the terminal, and determining the
134 * terminal's capabilities. Returns zero on success, or -1 on
135 * failure. The structure is usable in either case (though if no
136 * terminal could be opened, then no progress output will be
140 extern void progress_free(struct progress_state */
*progress*/
);
141 /* Free any resources held by PROGRESS.
143 * It is safe to call this function on a structure that was
144 * initialized to `PROGRESS_STATE_INIT', or by calling
145 * `progress_init', whether that function succeeded or not. It's
146 * also harmless to call it repeatedly on the same structure.
149 extern int progress_additem(struct progress_state */
*progress*/
,
150 struct progress_item */
*item*/
);
151 /* If ITEM is already associated with a progress state, then do
152 * nothing and return -1. Otherwise, add ITEM to the end of the list
153 * of active items maintained by PROGRESS, and return 0. The
154 * progress display is not updated.
157 extern int progress_removeitem(struct progress_state */
*progress*/
,
158 struct progress_item */
*item*/
);
159 /* If ITEM is not associated with a progress state, then do nothing
160 * and return -1. Otherwise, remove ITEM from the list of active
161 * items maintained by PROGRESS, and return 0. The progress display
165 extern int progress_clear(struct progress_state */
*progress*/
);
166 /* Clear any progress display currently shown on the terminal. Call
167 * this before doing your own output to the terminal, and call
168 * `progress_update' afterwards.
171 extern int progress_update(struct progress_state */
*progress*/
);
172 /* Update the progress display. This will call the `render'
173 * functions for all active progress items to redraw them.
176 extern int progress_vputleft(struct progress_render_state */
*render*/
,
177 const char */
*fmt*/
, va_list /*ap*/);
178 extern int progress_vputright(struct progress_render_state */
*render*/
,
179 const char */
*fmt*/
, va_list /*ap*/);
180 MULTIPROGRESS__PRINTF_LIKE(2, 3)
181 extern int progress_putleft(struct progress_render_state */
*render*/
,
182 const char */
*fmt*/
, ...);
183 MULTIPROGRESS__PRINTF_LIKE(2, 3)
184 extern int progress_putright(struct progress_render_state */
*render*/
,
185 const char */
*fmt*/
, ...);
186 /* Format the `printf'-style string FMT with the supplied arguments
187 * and add it to the left or right side of the current line being
188 * built up in RENDER. Later strings are added closer to the centre
189 * than earlier strings. If there isn't enough space left to show
190 * the new string on a terminal line, or if there isn't enough memory
191 * for the necessary buffers, then do nothing and return -1. If
192 * everything worked OK, then return 0.
195 extern void progress_put_sequence(const struct progress_ttyinfo */
*tty*/
,
196 const char */
*p*/
, unsigned /*nlines*/);
197 /* Send a sequence P -- one of the capability strings from TTY -- to
198 * the terminal TTY, padding it as necessary based on the fact that
199 * NLINES of the display are affected. (See the `tputs' function for
203 extern void progress_set_fgcolour(const struct progress_ttyinfo */
*tty*/
,
205 extern void progress_set_bgcolour(const struct progress_ttyinfo */
*tty*/
,
207 /* Set COLOUR as the foreground (`set_fgcolour') or background
208 * (`set_bgcolour') colour for subsequent output to TTY.
211 extern int progress_showbar(struct progress_render_state */
*render*/
,
213 /* Show a progress bar. The text of the progress bar will be as
214 * established by the `progress_putleft' and `progress_putright'
215 * functions called on RENDER so far, and the bar will be written to
216 * the terminal associated with RENDER. The length of the bar will
217 * be a FRAC fraction of the width of the terminal, so FRAC should be
218 * a real number between 0.0 and 1.0 inclusive.
221 extern int progress_shownotice(struct progress_render_state */
*render*/
,
222 int /*bg*/, int /*fg*/);
223 /* Show a notice, i.e., a temporary message which doesn't actually
224 * have any progress associated with it. The text of the notice will
225 * be as established by the `progress_putleft' and
226 * `progress_putright' functions called on RENDER so far, and the
227 * notice will be written to the terminal associated with RENDER.
228 * The notice's background and foreground colours will be BG and FG
232 /*----- That's all, folks -------------------------------------------------*/