1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: signal.c 138 2006-09-22 22:12:03Z mikes@u.washington.edu $";
5 /* ========================================================================
6 * Copyright 2006-2007 University of Washington
7 * Copyright 2013-2020 Eduardo Chappa
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
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * ========================================================================
18 /*======================================================================
19 Implement busy_cue spinner
26 #include "../c-client/c-client.h"
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"
35 #include "../pith/charconv/utf8.h"
38 #include "../pico/osdep/mswin.h"
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
;
53 static percent_done_t percent_done_ptr
;
55 #define MAX_SPINNER_WIDTH 32
56 #define MAX_SPINNER_ELEMENTS 64
58 static int spinner
= 0;
59 static struct _spinner
{
62 char *bars
[MAX_SPINNER_ELEMENTS
];
63 unsigned used_this_round
:1;
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 ",
115 {4, 4, {". O ", "o o ", "O . ", "o o "}},
116 {4, 26,{"| ", "/ ", "_ ", "\\ ", " | ", " / ", " _ ", " \\ ",
117 " | ", " / ", " _ ", " \\ ", " |", " |", " \\ ", " _ ", " / ",
118 " | ", " \\ ", " _ ", " / ", " | ", "\\ ", "_ ", "/ ", "| "}},
119 {4, 8, {"* ", "-* ", "--* ", " --*", " --", " -", " ", " "}},
120 {4, 2, {"\\/\\/", "/\\/\\"}},
121 {4, 4, {"\\|/|", "|\\|/", "/|\\|", "|/|\\"}}
127 * various pauses in 100th's of second
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 */
136 /* internal prototypes */
137 int do_busy_cue(void *);
138 void done_busy_cue(void *);
142 * Turn on a busy cue.
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
149 * Returns: 0 If busy cue was already set up before we got here
150 * 1 If busy cue was not already set up.
152 * NOTE: busy_cue and cancel_busy_cue MUST be called symmetrically in the
153 * same lexical scope.
156 busy_cue(char *msg
, percent_done_t pc_f
, int delay
)
158 AFTER_S
*a
= NULL
, **ap
;
160 dprint((9, "busy_cue(%s, %p, %d)\n", msg
? msg
: "Busy", pc_f
, delay
));
162 if(!(ps_global
&& ps_global
->ttyo
)){
163 dprint((9, "busy_cue returns No (ttyo)"));
168 * If we're already busy'ing, but a cue is invoked that
169 * supplies more useful info, use it...
173 stop_after(1); /* uninstall old handler */
175 return(0); /* nothing to add, return */
178 /* get ready to set up list of AFTER_S's */
182 percent_done_ptr
= pc_f
;
185 strncpy(busy_message
, msg
, sizeof(busy_message
));
189 strncpy(busy_message
, "Busy", sizeof(busy_message
));
193 busy_message
[sizeof(busy_message
)-1] = '\0';
194 busy_width
= utf8_width(busy_message
);
197 char progress
[MAX_SCREEN_COLS
+1];
198 int space_left
, slots_used
;
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));
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';
211 snprintf(progress
, sizeof(progress
), "%s%*s", busy_message
,
212 spinners
[spinner
].width
+ 1, "");
213 progress
[sizeof(progress
)-1] = '\0';
217 if(status_message_remaining()){
218 char buf
[sizeof(progress
) + 30];
219 char *append
= " [not actually shown]";
221 strncpy(buf
, progress
, sizeof(buf
)-1);
222 buf
[sizeof(buf
)-1] = '\0';
224 strncat(buf
, append
, sizeof(buf
) - strlen(buf
) - 1);
225 buf
[sizeof(buf
)-1] = '\0';
227 add_review_message(buf
, -1);
230 q_status_message(SM_ORDER
, 0, 1, progress
);
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.
239 display_message('x');
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
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
;
256 arrsize
= sizeof(spinners
)/sizeof(struct _spinner
);
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
)
263 if(eligible
== 0) /* reset */
264 for(eligible
= i
= 0; i
< arrsize
; i
++){
265 spinners
[i
].used_this_round
= 0;
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
)
281 if(spinner
< 0 || spinner
> sizeof(spinners
)/sizeof(struct _spinner
) -1)
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
;
290 start_after(a
); /* launch cue handler */
293 mswin_setcursor(MSWIN_CURSOR_BUSY
);
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
306 cancel_busy_cue(int message_pri
)
308 dprint((9, "cancel_busy_cue(%d)\n", message_pri
));
310 final_message_pri
= message_pri
;
316 * suspend_busy_cue - continue previously installed busy_cue.
319 suspend_busy_cue(void)
321 dprint((9, "suspend_busy_cue\n"));
329 * resume_busy_cue - continue previously installed busy_cue.
332 resume_busy_cue(unsigned int pause
)
334 dprint((9, "resume_busy_cue\n"));
342 * do_busy_cue - paint the busy cue and return how long caller
343 * should pause before calling us again
346 do_busy_cue(void *data
)
348 int space_left
, slots_used
, period
;
349 char dbuf
[MAX_SCREEN_COLS
+1];
351 /* Don't wipe out any displayed status message prematurely */
352 if(status_message_remaining() || busy_cue_pause
)
353 return(BUSY_MSG_RETRY
);
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));
359 if(percent_done_ptr
&& slots_used
>= 4){
363 pd
= (*percent_done_ptr
)();
364 pd
= MIN(MAX(0, pd
), 100);
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';
373 if(slots_used
== 10){
374 s
= dbuf
+ strlen(dbuf
) - 8;
381 *s
++ = '0' + pd
/ 10;
382 *s
++ = '0' + pd
% 10;
393 period
= BUSY_PERIOD_PERCENT
;
396 char b
[MAX_SPINNER_WIDTH
+ 2];
399 ind
= (dotcount
% spinners
[spinner
].elements
);
401 spinners
[spinner
].used_this_round
= 1;
402 if(space_left
>= spinners
[spinner
].width
+ 1){
405 (ps_global
->active_status_interval
> 0)
406 ? spinners
[spinner
].bars
[ind
] : "... ", sizeof(b
)-1);
407 b
[sizeof(b
)-1] = '\0';
409 else if(space_left
>= 2 && space_left
< sizeof(b
)){
413 b
[space_left
] = '\0';
418 snprintf(dbuf
, sizeof(dbuf
), "%s%s", busy_message
, b
);
419 dbuf
[sizeof(dbuf
)-1] = '\0';
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
;
426 status_message_write(dbuf
, 1);
435 done_busy_cue(void *data
)
437 int space_left
, slots_used
;
439 if(final_message
&& final_message_pri
>= 0){
440 char progress
[MAX_SCREEN_COLS
+1];
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));
446 if(percent_done_ptr
&& slots_used
>= 4){
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
);
461 padding
= MAX(0, MIN(space_left
-5, spinners
[spinner
].width
-4));
463 snprintf(progress
, sizeof(progress
), "%s %*sDONE", busy_message
,
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
);