@@@ dvdrip-upload: change settings while i'm stealing someone else's internet
[dvdrip] / multiprogress.h
1 /* -*-c-*-
2 *
3 * Progress bars for terminal programs
4 *
5 * (c) 2022 Mark Wooding
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
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.
14 *
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.
19 *
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/>.
22 */
23
24 #ifndef MULTIPROGRESS_H
25 #define MULTIPROGRESS_H
26
27 /*----- Header files ------------------------------------------------------*/
28
29 #include <stdio.h>
30 #include <sys/time.h>
31
32 /*----- Compiler-specific magic -------------------------------------------*/
33
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)))
40 #else
41 # define MULTIPROGRESS__PRINTF_LIKE(farg, aarg)
42 #endif
43
44 /*----- Data structures ---------------------------------------------------*/
45
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'.
50 *
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.
55 */
56
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) */
66 } cap;
67 unsigned defwd, defht; /* default width and height */
68 };
69 #define PROGRESS_TTYINFO_INIT \
70 { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 80, 25 }
71
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
75 * display.
76 */
77
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 */
83 };
84 #define PROGRESS_STATE_INIT { PROGRESS_TTYINFO_INIT, 0, 0, 0, 0, { 0, 0 } }
85
86 struct progress_render_state {
87 /* Information passed to rendering functions.
88 *
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.
96 */
97
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 */
105 };
106
107 struct progress_item {
108 /* An item in the progress display.
109 *
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.
114 *
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.
118 */
119
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*/);
124 };
125 #define PROGRESS_ITEM_INIT { 0, 0, 0, 0 }
126
127 /*----- Functions provided ------------------------------------------------*/
128
129 extern int progress_init(struct progress_state */*progress*/);
130 /* Initialize PROGRESS.
131 *
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
137 * produced).
138 */
139
140 extern void progress_free(struct progress_state */*progress*/);
141 /* Free any resources held by PROGRESS.
142 *
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.
147 */
148
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.
155 */
156
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
162 * is not updated.
163 */
164
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.
169 */
170
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.
174 */
175
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.
193 */
194
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
200 * the details.)
201 */
202
203 extern void progress_set_fgcolour(const struct progress_ttyinfo */*tty*/,
204 int /*colour*/);
205 extern void progress_set_bgcolour(const struct progress_ttyinfo */*tty*/,
206 int /*colour*/);
207 /* Set COLOUR as the foreground (`set_fgcolour') or background
208 * (`set_bgcolour') colour for subsequent output to TTY.
209 */
210
211 extern int progress_showbar(struct progress_render_state */*render*/,
212 double /*frac*/);
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.
219 */
220
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
229 * respectively.
230 */
231
232 /*----- That's all, folks -------------------------------------------------*/
233
234 #endif