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