1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: signal.c 138 2006-09-22 22:12:03Z mikes@u.washington.edu $";
3 #endif
4 
5 /* ========================================================================
6  * Copyright 2006-2007 University of Washington
7  * Copyright 2013-2021 Eduardo Chappa
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *     http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * ========================================================================
16  */
17 
18 /*======================================================================
19      Implement busy_cue spinner
20  ====*/
21 
22 
23 #include <system.h>
24 #include <general.h>
25 
26 #include "../c-client/c-client.h"
27 
28 #include "../pith/conf.h"
29 #include "../pith/state.h"
30 #include "../pith/status.h"
31 #include "../pith/busy.h"
32 #include "../pith/debug.h"
33 #include "../pith/help.h"
34 
35 #include "../pith/charconv/utf8.h"
36 
37 #ifdef _WINDOWS
38 #include "../pico/osdep/mswin.h"
39 #endif
40 
41 #include "status.h"
42 #include "after.h"
43 #include "busy.h"
44 
45 
46 static int       dotcount;
47 static char      busy_message[MAX_BM + 1];
48 static int       busy_cue_pause;
49 static int       busy_width;
50 static int       final_message;
51 static int       final_message_pri;
52 
53 static percent_done_t percent_done_ptr;
54 
55 #define	MAX_SPINNER_WIDTH	32
56 #define	MAX_SPINNER_ELEMENTS	64
57 
58 static int spinner = 0;
59 static struct _spinner {
60 	int    width,
61 	       elements;
62 	char *bars[MAX_SPINNER_ELEMENTS];
63 	unsigned used_this_round:1;
64 } spinners[] = {
65      {4, 4, {"<|> ", "</> ", "<-> ", "<\\> "}},
66      {8, 34, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
67              "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
68              "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
69              "~~\\___/~", "~~~\\___/", "~~~\\___/", "^~~~\\___", "^~~~\\___",
70              "~^~~~\\__", "~^~~~\\__", "~~^~~~\\_", "~~^~~~\\_", "~~~^~~~\\",
71              "~~~^~~~\\", "~~~~^~~~",  "~~~~^~~~",  "~~~~~^~~",  "~~~~~^~~",
72              "~~~~~~^~",  "~~~~~~^~",  "~~~~~~~^",  "~~~~~~~^"}},
73      {9, 14 , {"| WAIT |", "| WAIT |", "-|WAIT|-", "-|WAIT|-",
74                 "--|AI|--", "--|AI|--", "---||---", "---||---",
75                 "--|AI|--", "--|AI|--", "-|WAIT|-", "-|WAIT|-",
76                 "| WAIT |", "| WAIT |"}},
77      {9, 24 , {"o   |    ", "o   |    ", " o  |    ", " o  |    ",
78                 "  o |    ", "  o |    ", "   o|    ", "   o|    ",
79                 "    \\    ", "    \\    ", "    -o   ", "    -o   ",
80                 "    / o  ", "    / o  ", "    |  o ", "    |  o ",
81                 "    \\   o", "    \\   o", "    -    ", "    -    ",
82                 "    /    ", "    /    ", "    |    ", "    |    "}},
83     {8, 38, {"~~~~~~~~",   "~~~~~~~~",   "/~~~~~~~",   "/~~~~~~~",   "_/~~~~~~",
84              "_/~~~~~~",   "__/~~~~~",   "__/~~~~~",   "___/~~~~",   "___/~~~~",
85              "\\___/~~~",  "\\___/~~~",  "~\\___/~~",  "~\\___/~~",  "~~\\___/~",
86              "~~\\___/~",  "~~~\\___/",  "~~~\\___/",  "~~~~\\___",  "~~~~\\___",
87              "\\~~~~\\__", "\\~~~~\\__", "/\\~~~~\\_", "/\\~~~~\\_", "~/\\~~~~\\",
88              "~/\\~~~~\\", "~~/\\~~~~",  "~~/\\~~~~",  "~~~/\\~~~",  "~~~/\\~~~",
89              "~~~~/\\~~",  "~~~~/\\~~", "~~~~~/\\~", "~~~~~/\\~",    "~~~~~~/\\",
90              "~~~~~~/\\", "~~~~~~~/",  "~~~~~~~/"}},
91     {6, 10, {">       ", "<>      ", "><>     ", " ><>    ", "  ><>   ",
92              "   ><>  ", "    ><> ", "     ><>", "      ><", "       >"}},
93     {6, 10, {"       <", "      <>", "     <><", "    <>< ", "   <><  ",
94              "  <><   ", " <><    ", "<><     ", "><      ", "<       "}},
95     {6, 10, {">      <", "<>    <>", "><>  <><", " ><><>< ", "  ><><  ",
96              "  <><>  ", " <><><> ", "<><  ><>", "><    ><", "<      >"}},
97     {11, 4, {"--|-(o)-|--", "--/-(o)-\\--", "----(o)----", "--\\-(o)-/--"}},
98     {6, 7, {"\\____/", "_\\__/_", "__\\/__", "__/\\__",
99 	    "_/__\\_", "/____\\", "|____|"}},
100     {4, 4, {"<|> ", "<\\> ", "<-> ", "</> "}},
101     {4, 10,{"|   ", " /  ", " _  ", "  \\ ", "  | ", "  | ", "  \\ ",
102 	    " _  ", " /  ", "|   "}},
103     {4, 8, {"_ _ ", "\\ \\ ", " | |", " / /", " _ _", " / /", " | |", "\\ \\ "}},
104     {4, 8, {"_   ", "\\   ", " |  ", "  / ", "  _ ", "  \\ ", " |  ", "/   "}},
105     {4, 8, {"_   ", "\\   ", " |  ", "  / ", "  _ ", "  / ", " |  ", "\\   "}},
106     {4, 4, {" .  ", " o  ", " O  ", " o  "}},
107     {4, 5, {"....", " ...", ". ..", ".. .", "... "}},
108     {4, 5, {"    ", ".   ", " .  ", "  . ", "   ."}},
109     {4, 4, {".oOo", "oOo.", "Oo.o", "o.oO"}},
110     {4, 11,{"____", "\\___", "|\\__", "||\\_", "|||\\", "||||", "/|||",
111 	    "_/||", "__/|", "___/", "____"}},
112     {7, 9, {".     .", " .   . ", "  . .  ",
113             "   .   ", "   +   ", "   *   ", "   X   ",
114 	    "   #   ", "       "}},
115     {4, 4, {". O ", "o o ", "O . ", "o o "}},
116     {4, 26,{"|   ", "/   ", "_   ", "\\   ", " |  ", " /  ", " _  ", " \\  ",
117             "  | ", "  / ", "  _ ", "  \\ ", "   |", "   |", "  \\ ", "  _ ", "  / ",
118 	    "  | ", " \\  ", " _  ", " /  ", " |  ", "\\   ", "_   ", "/   ", "|   "}},
119     {4, 8, {"*   ", "-*  ", "--* ", " --*", "  --", "   -", "    ", "    "}},
120     {4, 2, {"\\/\\/", "/\\/\\"}},
121     {4, 4, {"\\|/|", "|\\|/", "/|\\|", "|/|\\"}}
122 };
123 
124 
125 
126 /*
127  * various pauses in 100th's of second
128  */
129 #define	BUSY_PERIOD_PERCENT	25	/* percent done update */
130 #define	BUSY_MSG_DONE		0	/* no more updates */
131 #define	BUSY_MSG_RETRY		25	/* message line conflict */
132 #define	BUSY_DELAY_PERCENT	33	/* pause before showing % */
133 #define	BUSY_DELAY_SPINNER	100	/* second pause before spinner */
134 
135 
136 /* internal prototypes */
137 int	do_busy_cue(void *);
138 void	done_busy_cue(void *);
139 
140 
141 /*
142  * Turn on a busy cue.
143  *
144  *    msg   -- the busy message to print in status line
145  *    pc_f  -- if non-null, call this function to get the percent done,
146  *	       (an integer between 0 and 100).  If null, append dots.
147  *   delay  -- seconds to delay output of delay notification
148  *
149  *   Returns:  0 If busy cue was already set up before we got here
150  *             1 If busy cue was not already set up.
151  *
152  *   NOTE: busy_cue and cancel_busy_cue MUST be called symmetrically in the
153  *         same lexical scope.
154  */
155 int
busy_cue(char * msg,percent_done_t pc_f,int delay)156 busy_cue(char *msg, percent_done_t pc_f, int delay)
157 {
158     AFTER_S *a = NULL, **ap;
159 
160     dprint((9, "busy_cue(%s, %p, %d)\n", msg ? msg : "Busy", pc_f, delay));
161 
162     if(!(ps_global && ps_global->ttyo)){
163 	dprint((9, "busy_cue returns No (ttyo)"));
164 	return(0);
165     }
166 
167     /*
168      * If we're already busy'ing, but a cue is invoked that
169      * supplies more useful info, use it...
170      */
171     if(after_active){
172 	if(msg || pc_f)
173 	  stop_after(1);	/* uninstall old handler */
174 	else
175 	  return(0);		/* nothing to add, return */
176     }
177 
178     /* get ready to set up list of AFTER_S's */
179     ap = &a;
180 
181     dotcount = 0;
182     percent_done_ptr = pc_f;
183 
184     if(msg){
185 	strncpy(busy_message, msg, sizeof(busy_message));
186 	final_message = 1;
187     }
188     else{
189 	strncpy(busy_message, "Busy", sizeof(busy_message));
190 	final_message = 0;
191     }
192 
193     busy_message[sizeof(busy_message)-1] = '\0';
194     busy_width = utf8_width(busy_message);
195 
196     if(!delay){
197 	char progress[MAX_SCREEN_COLS+1];
198 	int space_left, slots_used;
199 
200 	final_message = 1;
201 	space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols
202 		      : 80) - busy_width - 2;  /* 2 is for [] */
203 	slots_used = MAX(0, MIN(space_left-3, 10));
204 
205 	if(percent_done_ptr && slots_used >= 4){
206 	    snprintf(progress, sizeof(progress), "%s |%*s|", busy_message, slots_used, "");
207 	    progress[sizeof(progress)-1] = '\0';
208 	}
209 	else{
210 	    dotcount++;
211 	    snprintf(progress, sizeof(progress), "%s%*s", busy_message,
212 		     spinners[spinner].width + 1, "");
213 	    progress[sizeof(progress)-1] = '\0';
214 	}
215 
216 
217 	if(status_message_remaining()){
218 	    char buf[sizeof(progress) + 30];
219 	    char  *append = " [not actually shown]";
220 
221 	    strncpy(buf, progress, sizeof(buf)-1);
222 	    buf[sizeof(buf)-1] = '\0';
223 
224 	    strncat(buf, append, sizeof(buf) - strlen(buf) - 1);
225 	    buf[sizeof(buf)-1] = '\0';
226 
227 	    add_review_message(buf, -1);
228 	}
229 	else{
230 	    q_status_message(SM_ORDER, 0, 1, progress);
231 
232 	    /*
233 	     * We use display_message so that the initial message will
234 	     * be forced out only if there is not a previous message
235 	     * currently being displayed that hasn't been displayed for
236 	     * its min display time yet.  In that case, we don't want
237 	     * to force out the initial message.
238 	     */
239 	    display_message('x');
240 	}
241 
242 	fflush(stdout);
243     }
244 
245     /*
246      * Randomly select one of the animations, even taking care
247      * to run through all of them before starting over!
248      * The user won't actually see them all because most of them
249      * will never show up before they are canceled, but it
250      * should still help.
251      */
252     spinner = -1;
253     if(F_OFF(F_USE_BORING_SPINNER,ps_global) && ps_global->active_status_interval > 0){
254 	int arrsize, eligible, pick_this_one, i, j;
255 
256 	arrsize = sizeof(spinners)/sizeof(struct _spinner);
257 
258 	/* how many of them are eligible to be used this round? */
259 	for(eligible = i = 0; i < arrsize; i++)
260 	  if(!spinners[i].used_this_round)
261 	    eligible++;
262 
263 	if(eligible == 0)				/* reset */
264 	  for(eligible = i = 0; i < arrsize; i++){
265 	      spinners[i].used_this_round = 0;
266 	      eligible++;
267 	  }
268 
269 	if(eligible > 0){	/* it will be */
270 	    pick_this_one = random() % eligible;
271 	    for(j = i = 0; i < arrsize && spinner < 0; i++)
272 	      if(!spinners[i].used_this_round){
273 		  if(j == pick_this_one)
274 		    spinner = i;
275 		  else
276 		    j++;
277 	      }
278 	}
279     }
280 
281     if(spinner < 0 || spinner > sizeof(spinners)/sizeof(struct _spinner) -1)
282       spinner = 0;
283 
284     *ap		 = new_afterstruct();
285     (*ap)->delay = (pc_f) ? BUSY_DELAY_PERCENT : BUSY_DELAY_SPINNER;
286     (*ap)->f	 = do_busy_cue;
287     (*ap)->cf	 = done_busy_cue;
288     ap		 = &(*ap)->next;
289 
290     start_after(a);		/* launch cue handler */
291 
292 #ifdef _WINDOWS
293     mswin_setcursor(MSWIN_CURSOR_BUSY);
294 #endif
295 
296     return(1);
297 }
298 
299 
300 /*
301  * If final_message was set when busy_cue was called:
302  *   and message_pri = -1 -- no final message queued
303  *                 else final message queued with min equal to message_pri
304  */
305 void
cancel_busy_cue(int message_pri)306 cancel_busy_cue(int message_pri)
307 {
308     dprint((9, "cancel_busy_cue(%d)\n", message_pri));
309 
310     final_message_pri = message_pri;
311 
312     stop_after(0);
313 }
314 
315 /*
316  * suspend_busy_cue - continue previously installed busy_cue.
317  */
318 void
suspend_busy_cue(void)319 suspend_busy_cue(void)
320 {
321     dprint((9, "suspend_busy_cue\n"));
322 
323     if(after_active)
324       busy_cue_pause = 1;
325 }
326 
327 
328 /*
329  * resume_busy_cue - continue previously installed busy_cue.
330  */
331 void
resume_busy_cue(unsigned int pause)332 resume_busy_cue(unsigned int pause)
333 {
334     dprint((9, "resume_busy_cue\n"));
335 
336     if(after_active)
337       busy_cue_pause = 0;
338 }
339 
340 
341 /*
342  * do_busy_cue - paint the busy cue and return how long caller
343  *               should pause before calling us again
344  */
345 int
do_busy_cue(void * data)346 do_busy_cue(void *data)
347 {
348     int space_left, slots_used, period;
349     char dbuf[MAX_SCREEN_COLS+1];
350 
351     /* Don't wipe out any displayed status message prematurely */
352     if(status_message_remaining() || busy_cue_pause)
353       return(BUSY_MSG_RETRY);
354 
355     space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) -
356 	busy_width - 2;  /* 2 is for [] */
357     slots_used = MAX(0, MIN(space_left-3, 10));
358 
359     if(percent_done_ptr && slots_used >= 4){
360 	int completed, pd;
361 	char *s;
362 
363 	pd = (*percent_done_ptr)();
364 	pd = MIN(MAX(0, pd), 100);
365 
366 	completed = (pd * slots_used) / 100;
367 	snprintf(dbuf, sizeof(dbuf), "%s |%s%s%*s|", busy_message,
368 	    completed > 1 ? repeat_char(completed-1, pd==100 ? ' ' : '-') : "",
369 	    (completed > 0 && pd != 100) ? ">" : "",
370 	    slots_used - completed, "");
371 	dbuf[sizeof(dbuf)-1] = '\0';
372 
373 	if(slots_used == 10){
374 	    s = dbuf + strlen(dbuf) - 8;
375 	    if(pd < 10){
376 		s++; s++;
377 		*s++ = '0' + pd;
378 	    }
379 	    else if(pd < 100){
380 		s++;
381 		*s++ = '0' + pd / 10;
382 		*s++ = '0' + pd % 10;
383 	    }
384 	    else{
385 		*s++ = '1';
386 		*s++ = '0';
387 		*s++ = '0';
388 	    }
389 
390 	    *s   = '%';
391 	}
392 
393 	period = BUSY_PERIOD_PERCENT;
394     }
395     else{
396 	char b[MAX_SPINNER_WIDTH + 2];
397 	int ind;
398 
399 	ind = (dotcount % spinners[spinner].elements);
400 
401 	spinners[spinner].used_this_round = 1;
402 	if(space_left >= spinners[spinner].width + 1){
403 	    b[0] = SPACE;
404 	    strncpy(b+1,
405 		    (ps_global->active_status_interval > 0)
406 		      ? spinners[spinner].bars[ind] : "... ", sizeof(b)-1);
407 	    b[sizeof(b)-1] = '\0';
408 	}
409 	else if(space_left >= 2 && space_left < sizeof(b)){
410 	    b[0] = '.';
411 	    b[1] = '.';
412 	    b[2] = '.';
413 	    b[space_left] = '\0';
414 	}
415 	else
416 	  b[0] = '\0';
417 
418 	snprintf(dbuf, sizeof(dbuf), "%s%s", busy_message, b);
419 	dbuf[sizeof(dbuf)-1] = '\0';
420 
421 	/* convert interval to delay in 100ths of second */
422 	period = (ps_global->active_status_interval > 0)
423 		  ? (100 / MIN(10, ps_global->active_status_interval)) : BUSY_MSG_DONE;
424     }
425 
426     status_message_write(dbuf, 1);
427     dotcount++;
428     fflush(stdout);
429 
430     return(period);
431 }
432 
433 
434 void
done_busy_cue(void * data)435 done_busy_cue(void *data)
436 {
437     int space_left, slots_used;
438 
439     if(final_message && final_message_pri >= 0){
440 	char progress[MAX_SCREEN_COLS+1];
441 
442 	/* 2 is for [] */
443 	space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - busy_width - 2;
444 	slots_used = MAX(0, MIN(space_left-3, 10));
445 
446 	if(percent_done_ptr && slots_used >= 4){
447 	    int left, right;
448 
449 	    right = (slots_used - 4)/2;
450 	    left  = slots_used - 4 - right;
451 	    snprintf(progress, sizeof(progress), "%s |%*s100%%%*s|",
452 		     busy_message, left, "", right, "");
453 	    progress[sizeof(progress)-1] = '\0';
454 	    q_status_message(SM_ORDER,
455 			     final_message_pri>=2 ? MAX(final_message_pri,3) : 0,
456 			     final_message_pri+2, progress);
457 	}
458 	else{
459 	    int padding;
460 
461 	    padding = MAX(0, MIN(space_left-5, spinners[spinner].width-4));
462 
463 	    snprintf(progress, sizeof(progress), "%s %*sDONE", busy_message,
464 		     padding, "");
465 	    progress[sizeof(progress)-1] = '\0';
466 	    q_status_message(SM_ORDER,
467 			     final_message_pri>=2 ? MAX(final_message_pri,3) : 0,
468 			     final_message_pri+2, progress);
469 	}
470     }
471 
472     mark_status_dirty();
473 }
474