more example scripts from Teckids e.V.
[shellsnippets/shellsnippets.git] / mksh / uhr
1 #!/bin/mksh
2 # $MirOS: contrib/hosted/tg/uhr,v 1.21 2017/03/31 02:49:41 tg Exp $
3 #-
4 # Copyright © 2012, 2013, 2015, 2017
5 #       mirabilos <m@mirbsd.org>
6 #
7 # Provided that these terms and disclaimer and all copyright notices
8 # are retained or reproduced in an accompanying document, permission
9 # is granted to deal in this work without restriction, including un‐
10 # limited rights to use, publicly perform, distribute, sell, modify,
11 # merge, give away, or sublicence.
12 #
13 # This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
14 # the utmost extent permitted by applicable law, neither express nor
15 # implied; without malicious intent or gross negligence. In no event
16 # may a licensor, author or contributor be held liable for indirect,
17 # direct, other damage, loss, or other issues arising in any way out
18 # of dealing in the work, even if advised of the possibility of such
19 # damage or existence of a defect, except proven that it results out
20 # of said person’s immediate fault when using the work as intended.
21 #-
22 # Analoguhr mit Digitalanzeige. Grundlegende Annahme: schnelles Ter‐
23 # minal, d.h. keine Voroptimierung der Darstellung durch das Skript;
24 # Font im Seitenverhältnis 1:2 (z.B. 9x18 aus XFree86® fixed-misc).
25
26 if [[ $KSH_VERSION != @(\@\(#\)MIRBSD KSH R)@(4[1-9]|[5-9][0-9]|[1-9][0-9]+([0-9]))\ +([0-9])/+([0-9])/+([0-9])?(\ *) ]]; then
27         print -u2 Uhr requires mksh R41 or newer.
28         exit 1
29 fi
30 set -U
31 print '\r\e[K\rLoading…'
32
33 function graceful {
34         print -n '\033[;H\033[J'
35         exit 0
36 }
37 trap graceful INT TERM HUP
38
39 # Shell library for easy display of a progress bar, modified for Uhr
40 #
41 # Usage:
42 # – before:   init_progress_bar $n
43 # – $n times: draw_progress_bar
44 # – after:    done_progress_bar
45 #
46 # init_progress_bar trashes the EXIT trap, done_progress_bar clears it.
47
48 # global variables used by this library
49 _cnt_progress_bar=0
50 _cur_progress_bar=0
51 isin_progress_bar=0
52 nlin_progress_bar=0
53
54 if [[ $KSH_VERSION = @(\@\(#\)MIRBSD KSH R)@(5[5-9]|[6-9][0-9]|[1-9][0-9][0-9])\ * ]]; then
55         alias global='typeset -g'
56 else
57         alias global=global
58 fi
59
60 # args: $1 = number of draw_progress_bar calls to make up 100%
61 function init_progress_bar {
62         global -i _cnt_progress_bar=$1 _cur_progress_bar=0
63         global -i nlin_progress_bar=$LINES isin_progress_bar=1
64
65         trap 'done_progress_bar 1' EXIT
66         # set up scrolling region, draw initial empty bar
67         sigwinch_uhr
68         got_sigwinch=0
69 }
70
71 unalias global
72
73 function sigwinch_uhr {
74         got_sigwinch=1
75         (( isin_progress_bar )) || return 0
76
77         # get new terminal size
78         nlin_progress_bar=$LINES
79
80         # save position; clear scrolling region; restore position; newline;
81         # up one line (to ensure we are not in the last line); save position;
82         # clear rest of screen; set new scrolling region; restore position
83         print -nu2 "\\e7\\e[0;0r\\e8\\n\\e[A\\e7\\e[J\\e[1;$((# nlin_progress_bar - 1))r\\e8"
84
85         # redraw progress bar
86         draw_progress_bar_internal
87 }
88
89 function done_progress_bar {
90         (( isin_progress_bar )) || return 0
91         isin_progress_bar=0
92         # save position; clear scrolling region; restore position;
93         # save position; clear rest of screen; restore position
94         print -nu2 "\\e7\\e[0;0r\\e8\\e7\\e[J\\e8"
95         trap - EXIT
96         [[ -n $1 ]] || (( _cur_progress_bar == _cnt_progress_bar )) || \
97             print -ru2 W: expected $_cnt_progress_bar draw_progress_bar calls, \
98             got only $_cur_progress_bar
99 }
100
101 function draw_progress_bar {
102         # increment current progress
103         if (( ++_cur_progress_bar > _cnt_progress_bar )); then
104                 print -ru2 "W: too many draw_progress_bar calls"
105                 _cur_progress_bar=$_cnt_progress_bar
106         fi
107         # remaining drawing code
108         draw_progress_bar_internal
109 }
110
111 function draw_progress_bar_internal {
112         local bar num w=$COLUMNS
113
114         ((# num = (_cur_progress_bar * w * 8) / _cnt_progress_bar ))
115         while ((# num >= 8 )); do
116                 bar+=█
117                 ((# num -= 8 ))
118         done
119         case $num {
120         (7) bar+=▉ ;;
121         (6) bar+=▊ ;;
122         (5) bar+=▋ ;;
123         (4) bar+=▌ ;;
124         (3) bar+=▍ ;;
125         (2) bar+=▎ ;;
126         (1) bar+=▏ ;;
127         }
128         # fill complete line, right-align completion percentage display
129         local -R$w spc="$((# _cur_progress_bar * 100 / _cnt_progress_bar))%"
130         # elide percentage when it stops fitting
131         ((# (_cur_progress_bar * w / _cnt_progress_bar) > (w - 4) )) && spc=
132         # save position; go to last line; set colours;
133         # output a line full of spaces (and completion percentage);
134         # jump to first column; output bar (line præfix); restore position
135         print -nu2 -- "\\e7\\e[$nlin_progress_bar;1H\\e[0;1;33;44m$spc\\r$bar\\e8"
136 }
137
138 trap sigwinch_uhr WINCH
139
140 # stupid GNU idiots breaking everything by default… grml…
141 bcopt=
142 bc --help >/dev/null 2>&1 && bcopt=-q
143
144 integer F_NO=0x00 M_NO=0x1F
145 integer F_BG=0x01 M_BG=0x1E
146 integer F_CC=0x02 M_CC=0x1D
147 integer F_HP=0x04 M_HP=0x1B
148 integer F_MP=0x08 M_MP=0x17
149 integer F_SP=0x10 M_SP=0x0F
150 integer B_BG=0x01 B_BLK=0x02 B_NB=0x0C B_DOT=0x10
151
152 #       -       BLK     BG      NB      DOT     NB|DOT
153 set -A m2c \
154         ' '     '▀'   '*'     '▀'   '·'    '░'   \
155         '▄'   '█'   '█'   '█'   '▆'   '█'   \
156         '*'     '█'   '#'     '◘'   '⁂'   '◙'   \
157         '▄'   '█'   '▆'   '█'   '▒'   '▓'   \
158         '.'     '▛'   '☿'   '▛'   ':'     '▒'   \
159         '▄'   '█'   '◙'   '█'   '▆'   '▓'
160
161 set -A m2m
162 integer m2m
163
164 integer i=-1 j
165 while (( ++i <= 0x1F )); do
166         (( m2m[i] = !i ? 0 : (i & B_BLK) ? 1 :
167             (i & B_NB) ? ((i & B_DOT) ? 5 : 3) : (i & B_DOT) ? 4 : 2 ))
168 done
169
170 function refresh {
171         local -i10 i j k l=-2 q=0
172         local t
173
174         unset changed[$(((r / 2) * n + r))]
175         for k in "${!changed[@]}"; do
176                 (( i = m2m[fb[phys_v[k]]] ))
177                 (( j = m2m[fb[phys_v[k] + n]] ))
178                 (( phys_l[k] == l )) || t+=${phys_p[k]}
179                 (( l = k ))
180                 t+=${m2c[j * 6 + i]}
181                 (( ++q & 7 )) && continue
182                 print -nr -- "$t"
183                 t=
184         done
185         set -A changed
186         print -n "$t\e[$((r / 2 + 1));$((r + 1))H\e[7mⓄ\e[0m\e[1;9H"
187 }
188
189 # put arrayname x y
190 function put {
191         local _x=$(($2)) _y=$(($3)) _i
192         nameref _px=$1
193
194         (( _i = (r - _y) * n + _x + r ))
195         _px+=($_i)
196 }
197
198 # retrace arrayname maskname colourname
199 function retrace {
200         nameref _px=$1 _m=$2 _c=$3
201         local _i _k _z _s
202
203         for _i in "${_px[@]}"; do
204                 (( fb[_i] = (fb[_i] & _m) | _c ))
205                 # map to physical coordinates
206                 if [[ -z ${phys_z[_i]} ]]; then
207                         (( phys_z[_i] = _z = (_i / n) / 2 ))
208                         (( phys_s[_i] = _s = _i % n ))
209                         (( phys_i[_i] = _z * n + _s ))
210                 fi
211                 _k=${phys_i[_i]}
212                 if [[ -z ${phys_v[_k]} ]]; then
213                         _z=${phys_z[_i]}
214                         _s=${phys_s[_i]}
215                         (( phys_v[_k] = _z * n * 2 + _s ))
216                         (( phys_l[_k] = (_s && _z) ? _k - 1 : -3 ))
217                         phys_p[_k]=$'\e['$((_z + 1))\;$((_s + 1))H
218                 fi
219                 changed[_k]= #set
220         done
221 }
222
223 function draw_hour_marker {
224         draw_progress_bar
225         f=$1 e=$2 S=$3
226         (( i = mkx[f] ))
227         (( j = mky[f] & ~1 ))
228         Y='0 1 2'
229         if (( L > 26 )); then
230                 d='###########'
231                 S="${d::e+2} ${S::e}  ${S: e:e}  ${S:2*e} ${d::e+2}"
232                 (( e += 2 ))
233                 Y+=' 3 4'
234                 (( j += 2 ))
235         fi
236         (( i -= e / 2 ))
237         k=0
238         for y in $Y; do
239                 (( y = j - y * 2 + 1 + (r & 1) ))
240                 (( dy = y + 1 ))
241                 (( x = i - 1 ))
242                 while (( ++x < (i + e) )); do
243                         [[ ${S: k++:1} = ' ' ]] && continue
244                         put lb x y
245                         put lb x dy
246                 done
247         done
248 }
249
250 function draw_hour_markers {
251         set -A lb
252         draw_hour_marker  0 7 '# # # # #  # ## # # #'
253         draw_hour_marker  1 1 '###'
254         draw_hour_marker  2 3 '# ## ## #'
255         draw_hour_marker  3 5 '# # ## # ## # #'
256         draw_hour_marker  4 5 '# # ## # ##  # '
257         draw_hour_marker  5 3 '# ## # # '
258         draw_hour_marker  6 5 '# # ## # # #  #'
259         draw_hour_marker  7 7 '# # # ## # # # #  # #'
260         draw_hour_marker  8 9 '# # # # ## # # # # #  # # #'
261         draw_hour_marker  9 5 '# # ##  # # # #'
262         draw_hour_marker 10 3 '# # # # #'
263         draw_hour_marker 11 5 '# # # #  ## # #'
264         retrace lb M_BG F_BG
265 }
266
267 # draw outer circle with Bresenham
268 function draw_outer_circle {
269         draw_progress_bar
270         set -A lc
271         integer x=r y=-1 f=r dx dy
272         while (( y < x )); do
273                 (( dy = y++ * 2 + 1 ))
274                 if (( y )); then
275                         (( f -= dy ))
276                         if (( f < 0 )); then
277                                 (( dx = 1 - x-- * 2 ))
278                                 (( f -= dx ))
279                         fi
280                 fi
281                 put lc x y
282                 put lc -x y
283                 put lc -x -y
284                 put lc x -y
285                 put lc y x
286                 put lc -y x
287                 put lc -y -x
288                 put lc y -x
289         done
290         retrace lc M_CC F_CC
291 }
292
293 function main_loop {
294         typeset -Z6 tosleep
295
296         set -A do -- -1 -1 -1
297         dodate_get
298         (( got_sigwinch )) && return
299         dodate_draw
300         while (( !got_sigwinch )); do
301                 (( tosleep = 1000000 - ${EPOCHREALTIME#*.} ))
302                 if (( tosleep > 999999 )); then
303                         sleep 0.2
304                         (( tosleep = 1000000 - ${EPOCHREALTIME#*.} ))
305                 fi
306                 if (( tosleep > 999999 )); then
307                         # huh… maybe no gettimeofday(2) here
308                         while :; do
309                                 d=$(date +'%H %M %S,%d %b %Y')
310                                 set -A dt $d
311                                 (( dt[2] == do[2] )) || break
312                                 sleep 0.1
313                         done
314                 else
315                         sleep 0.$tosleep
316                 fi
317                 dodate_get
318                 retrace lms$((do[2])) M_SP F_NO
319                 (( do[1] == dt[1] )) || retrace lms$((do[1])) M_MP F_NO
320                 (( do[0] == dt[0] )) || retrace lh$((do[0])) M_HP F_NO
321                 (( got_sigwinch )) || dodate_draw
322         done
323 }
324
325 function dodate_get {
326         d=$(date +'%H %M %S,%d %b %Y')
327         S=${d#*,}
328         d=${d%,*}
329         print -n "\e[1;$((n - ${%S} + 1))H$S\e[1;1H${d// /:}"
330         set -A dt $d
331         (( dt[0] = (dt[0] % 12) * 5 + (dt[1] / 12) ))
332 }
333
334 function dodate_draw {
335         (( do[0] == dt[0] )) || retrace lh$((dt[0])) M_HP F_HP
336         (( do[1] == dt[1] )) || retrace lms$((dt[1])) M_MP F_MP
337         retrace lms$((dt[2])) M_SP F_SP
338         refresh
339         set -A do -- "${dt[@]}"
340 }
341
342 while :; do
343         (( L = LINES >= (COLUMNS / 2) ? (COLUMNS / 2) : LINES ))
344         init_progress_bar $((60 + 60 + (L > 21 ? (12 + 1 + 12) : 0) + 1 ))
345         S='Pregenerating arrays, please wait...'
346         if (( (r = (COLUMNS - ${%S}) / 2 - 2) > 0 )); then
347                 d=
348                 (( n = ${%S} + 2 ))
349                 while (( n-- )); do
350                         d+=─
351                 done
352                 S="\\e[$((LINES / 2 - 1));${r}H┌$d┐\\e[$((LINES / 2));${r}H│ $S │\\e[$((LINES / 2 + 1));${r}H└$d┘"
353         fi
354         print "\\e7\\e[0m$S\\e8"
355
356         (( r = LINES * 2 ))
357         (( r = (r > COLUMNS ? COLUMNS : r) / 2 - 1))
358         (( n = 2 * r + 1 ))
359         set -A fb
360         integer fb
361         set -A changed
362         set -A phys_z
363         set -A phys_s
364         set -A phys_i
365         set -A phys_v
366         set -A phys_p
367         # doch eine (minimale) Voroptimierung der Bildschirmausgabe
368         set -A phys_l
369
370         # precalculate all lines’ endpoints with bc and paths with Bresenham
371         integer x y dx sx dy sy e f
372         bc -l $bcopt |&
373         print -p scale=8
374         print -p r=$r
375         print -p o=r
376         print -p 'define p(t) {
377                 auto d
378                 d = 90 - t
379                 if (d < 0) d = 360 + d
380                 return (d * 3.1415926535897932 / 180)
381         }'
382         # minutes and seconds – full length, 60 items
383         i=-1
384         while (( ++i < 60 )); do
385                 draw_progress_bar
386                 eval set -A lms$i
387                 print -p "r * c(p($i * 6))"
388                 read -p S; [[ $S = ?(-).* ]] && S=0
389                 x=${S%%.*}
390                 print -p "r * s(p($i * 6))"
391                 read -p S; [[ $S = ?(-).* ]] && S=0
392                 y=${S%%.*}
393                 (( dx = x < 0 ? -x : x ))
394                 (( sx = x < 0 ? 1 : -1 ))
395                 (( dy = y < 0 ? y : -y ))
396                 (( sy = y < 0 ? 1 : -1 ))
397                 (( e = dx + dy ))
398                 while :; do
399                         put lms$i x y
400                         (( !x && !y )) && break
401                         (( f = 2 * e ))
402                         if (( f > dy )); then
403                                 (( e += dy ))
404                                 (( x += sx ))
405                         fi
406                         if (( f < dx )); then
407                                 (( e += dx ))
408                                 (( y += sy ))
409                         fi
410                 done
411         done
412         # hours – 2/3 length, 60 items (5 per hour)
413         print -p 'r = o * 2 / 3'
414         i=-1
415         while (( ++i < 60 )); do
416                 draw_progress_bar
417                 eval set -A lh$i
418                 print -p "r * c(p($i * 6))"
419                 read -p S; [[ $S = ?(-).* ]] && S=0
420                 x=${S%%.*}
421                 print -p "r * s(p($i * 6))"
422                 read -p S; [[ $S = ?(-).* ]] && S=0
423                 y=${S%%.*}
424                 (( dx = x < 0 ? -x : x ))
425                 (( sx = x < 0 ? 1 : -1 ))
426                 (( dy = y < 0 ? y : -y ))
427                 (( sy = y < 0 ? 1 : -1 ))
428                 (( e = dx + dy ))
429                 while :; do
430                         put lh$i x y
431                         (( !x && !y )) && break
432                         (( f = 2 * e ))
433                         if (( f > dy )); then
434                                 (( e += dy ))
435                                 (( x += sx ))
436                         fi
437                         if (( f < dx )); then
438                                 (( e += dx ))
439                                 (( y += sy ))
440                         fi
441                 done
442         done
443         # hour markers – 80% length, 12 items
444         if (( L > 21 )); then
445                 print -p 'r = o * 8 / 10'
446                 i=-1
447                 set -A mkx
448                 set -A mky
449                 while (( ++i < 12 )); do
450                         draw_progress_bar
451                         print -p "r * c(p($i * 30))"
452                         read -p S; [[ $S = ?(-).* ]] && S=0
453                         mkx[i]=${S%%.*}
454                         print -p "r * s(p($i * 30))"
455                         read -p S; [[ $S = ?(-).* ]] && S=0
456                         mky[i]=${S%%.*}
457                 done
458                 draw_progress_bar
459                 # fine-tuning of roman numeral position via screen size
460                 (( ++mkx[7] ))
461                 (( ++mkx[8] ))
462                 case $L {
463                 (22|23) (( ++mkx[6] )) ;|
464                 (23)
465                         (( mky[1] += 2 ))
466                         (( mky[2] += 2 ))
467                         (( mky[10] += 2 ))
468                         (( mky[11] += 2 ))
469                         ;;
470                 (24|25|29|30|31|34)
471                         (( mky[4] += 2 ))
472                         (( mky[8] += 2 ))
473                         ;|
474                 (27|28|29)
475                         (( ++mkx[10] ))
476                         (( mky[8] += 2 ))
477                         (( mky[9] += 2 ))
478                         (( mky[10] += 2 ))
479                         ;|
480                 (27|29|31)
481                         (( mky[0] -= 2 ))
482                         ;|
483                 (27)
484                         (( --mkx[4] ))
485                         (( --mkx[5] ))
486                         (( ++mkx[6] ))
487                         (( mkx[7] += 2 ))
488                         (( ++mkx[8] ))
489                         (( ++mkx[10] ))
490                         ;;
491                 (29)
492                         (( mky[5] += 2 ))
493                         (( mky[7] += 2 ))
494                         ;;
495                 (30)
496                         (( mky[11] -= 2 ))
497                         ;;
498                 }
499                 (( mky[0] += 2 * (L & 1) ))
500         fi
501         exec 3>&p; exec 3>&-
502
503         draw_outer_circle
504         (( L > 21 )) && draw_hour_markers
505         done_progress_bar
506         print -n -- '\e[H\e[J'
507         refresh
508
509         main_loop
510 done