1*8462SApril.Chin@Sun.COM#!/usr/bin/ksh93
2*8462SApril.Chin@Sun.COM
3*8462SApril.Chin@Sun.COM#
4*8462SApril.Chin@Sun.COM# CDDL HEADER START
5*8462SApril.Chin@Sun.COM#
6*8462SApril.Chin@Sun.COM# The contents of this file are subject to the terms of the
7*8462SApril.Chin@Sun.COM# Common Development and Distribution License (the "License").
8*8462SApril.Chin@Sun.COM# You may not use this file except in compliance with the License.
9*8462SApril.Chin@Sun.COM#
10*8462SApril.Chin@Sun.COM# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11*8462SApril.Chin@Sun.COM# or http://www.opensolaris.org/os/licensing.
12*8462SApril.Chin@Sun.COM# See the License for the specific language governing permissions
13*8462SApril.Chin@Sun.COM# and limitations under the License.
14*8462SApril.Chin@Sun.COM#
15*8462SApril.Chin@Sun.COM# When distributing Covered Code, include this CDDL HEADER in each
16*8462SApril.Chin@Sun.COM# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17*8462SApril.Chin@Sun.COM# If applicable, add the following below this CDDL HEADER, with the
18*8462SApril.Chin@Sun.COM# fields enclosed by brackets "[]" replaced with your own identifying
19*8462SApril.Chin@Sun.COM# information: Portions Copyright [yyyy] [name of copyright owner]
20*8462SApril.Chin@Sun.COM#
21*8462SApril.Chin@Sun.COM# CDDL HEADER END
22*8462SApril.Chin@Sun.COM#
23*8462SApril.Chin@Sun.COM
24*8462SApril.Chin@Sun.COM#
25*8462SApril.Chin@Sun.COM# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
26*8462SApril.Chin@Sun.COM# Use is subject to license terms.
27*8462SApril.Chin@Sun.COM#
28*8462SApril.Chin@Sun.COM
29*8462SApril.Chin@Sun.COM#
30*8462SApril.Chin@Sun.COM# termclock - a simple analog clock for terminals
31*8462SApril.Chin@Sun.COM#
32*8462SApril.Chin@Sun.COM
33*8462SApril.Chin@Sun.COM# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
34*8462SApril.Chin@Sun.COMexport PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin
35*8462SApril.Chin@Sun.COM
36*8462SApril.Chin@Sun.COM# Make sure all math stuff runs in the "C" locale to avoid problems
37*8462SApril.Chin@Sun.COM# with alternative # radix point representations (e.g. ',' instead of
38*8462SApril.Chin@Sun.COM# '.' in de_DE.*-locales). This needs to be set _before_ any
39*8462SApril.Chin@Sun.COM# floating-point constants are defined in this script).
40*8462SApril.Chin@Sun.COMif [[ "${LC_ALL}" != "" ]] ; then
41*8462SApril.Chin@Sun.COM    export \
42*8462SApril.Chin@Sun.COM        LC_MONETARY="${LC_ALL}" \
43*8462SApril.Chin@Sun.COM        LC_MESSAGES="${LC_ALL}" \
44*8462SApril.Chin@Sun.COM        LC_COLLATE="${LC_ALL}" \
45*8462SApril.Chin@Sun.COM        LC_CTYPE="${LC_ALL}"
46*8462SApril.Chin@Sun.COM        unset LC_ALL
47*8462SApril.Chin@Sun.COMfi
48*8462SApril.Chin@Sun.COMexport LC_NUMERIC=C
49*8462SApril.Chin@Sun.COM
50*8462SApril.Chin@Sun.COMfunction fatal_error
51*8462SApril.Chin@Sun.COM{
52*8462SApril.Chin@Sun.COM	print -u2 "${progname}: $*"
53*8462SApril.Chin@Sun.COM	exit 1
54*8462SApril.Chin@Sun.COM}
55*8462SApril.Chin@Sun.COM
56*8462SApril.Chin@Sun.COM# cache tput values (to avoid |fork()|'ing a "tput" child every second)
57*8462SApril.Chin@Sun.COMfunction tput_cup
58*8462SApril.Chin@Sun.COM{
59*8462SApril.Chin@Sun.COM	# static variable as cache for "tput_cup"
60*8462SApril.Chin@Sun.COM	typeset -S -A tput_cup_cache
61*8462SApril.Chin@Sun.COM
62*8462SApril.Chin@Sun.COM	integer y="$1" x="$2"
63*8462SApril.Chin@Sun.COM	nameref c="tput_cup_cache[\"${y}_${x}\"]"
64*8462SApril.Chin@Sun.COM
65*8462SApril.Chin@Sun.COM	if [[ "$c" == "" ]] ; then
66*8462SApril.Chin@Sun.COM		# fast path for known terminal types
67*8462SApril.Chin@Sun.COM		if [[ ${TERM} == ~(Elr)(vt100|vt220|xterm|xterm-color|dtterm) ]] ; then
68*8462SApril.Chin@Sun.COM			c="${ printf "\E[%d;%dH" y+1 x+1 ; }"
69*8462SApril.Chin@Sun.COM		else
70*8462SApril.Chin@Sun.COM			c="${ tput cup $y $x ; }"
71*8462SApril.Chin@Sun.COM		fi
72*8462SApril.Chin@Sun.COM	fi
73*8462SApril.Chin@Sun.COM
74*8462SApril.Chin@Sun.COM	print -r -n -- "$c"
75*8462SApril.Chin@Sun.COM	return 0
76*8462SApril.Chin@Sun.COM}
77*8462SApril.Chin@Sun.COM
78*8462SApril.Chin@Sun.COM# Get terminal size and put values into a compound variable with the integer
79*8462SApril.Chin@Sun.COM# members "columns" and "lines"
80*8462SApril.Chin@Sun.COMfunction get_term_size
81*8462SApril.Chin@Sun.COM{
82*8462SApril.Chin@Sun.COM	nameref rect=$1
83*8462SApril.Chin@Sun.COM
84*8462SApril.Chin@Sun.COM	rect.columns=${ tput cols ; } || return 1
85*8462SApril.Chin@Sun.COM	rect.lines=${ tput lines ; }  || return 1
86*8462SApril.Chin@Sun.COM
87*8462SApril.Chin@Sun.COM	return 0
88*8462SApril.Chin@Sun.COM}
89*8462SApril.Chin@Sun.COM
90*8462SApril.Chin@Sun.COMfunction draw_clock
91*8462SApril.Chin@Sun.COM{
92*8462SApril.Chin@Sun.COM	float angle a
93*8462SApril.Chin@Sun.COM	float x y
94*8462SApril.Chin@Sun.COM	integer i=1
95*8462SApril.Chin@Sun.COM
96*8462SApril.Chin@Sun.COM	for(( angle=0.0 ; angle < 360. ; angle+=6 )) ; do
97*8462SApril.Chin@Sun.COM		((
98*8462SApril.Chin@Sun.COM			a=angle/360.*(2*M_PI) ,
99*8462SApril.Chin@Sun.COM
100*8462SApril.Chin@Sun.COM			x=clock.len_x*cos(a) ,
101*8462SApril.Chin@Sun.COM			y=clock.len_y*sin(a)
102*8462SApril.Chin@Sun.COM		))
103*8462SApril.Chin@Sun.COM
104*8462SApril.Chin@Sun.COM		tput_cup $(( y+clock.middle_y )) $(( x+clock.middle_x ))
105*8462SApril.Chin@Sun.COM
106*8462SApril.Chin@Sun.COM		# add "mark" every 30 degrees
107*8462SApril.Chin@Sun.COM		if (( int(angle)%30 == 0 )) ; then
108*8462SApril.Chin@Sun.COM			print -r -n "$(((++i)%12+1))"
109*8462SApril.Chin@Sun.COM		else
110*8462SApril.Chin@Sun.COM			print -r -n "x"
111*8462SApril.Chin@Sun.COM		fi
112*8462SApril.Chin@Sun.COM	done
113*8462SApril.Chin@Sun.COM	return 0
114*8462SApril.Chin@Sun.COM}
115*8462SApril.Chin@Sun.COM
116*8462SApril.Chin@Sun.COMfunction draw_hand
117*8462SApril.Chin@Sun.COM{
118*8462SApril.Chin@Sun.COM	float   angle="$1" a
119*8462SApril.Chin@Sun.COM	typeset ch="$2"
120*8462SApril.Chin@Sun.COM	float   length="$3"
121*8462SApril.Chin@Sun.COM	float   x y
122*8462SApril.Chin@Sun.COM
123*8462SApril.Chin@Sun.COM	(( a=angle/360.*(2*M_PI) ))
124*8462SApril.Chin@Sun.COM
125*8462SApril.Chin@Sun.COM	for (( s=0.0 ; s < 10. ; s+=0.5 )) ; do
126*8462SApril.Chin@Sun.COM		((
127*8462SApril.Chin@Sun.COM			x=(clock.len_x*(s/10.)*(length/100.))*cos(a) ,
128*8462SApril.Chin@Sun.COM			y=(clock.len_y*(s/10.)*(length/100.))*sin(a)
129*8462SApril.Chin@Sun.COM		))
130*8462SApril.Chin@Sun.COM
131*8462SApril.Chin@Sun.COM		tput_cup $(( y+clock.middle_y )) $(( x+clock.middle_x ))
132*8462SApril.Chin@Sun.COM		print -r -n -- "${ch}"
133*8462SApril.Chin@Sun.COM	done
134*8462SApril.Chin@Sun.COM	return 0
135*8462SApril.Chin@Sun.COM}
136*8462SApril.Chin@Sun.COM
137*8462SApril.Chin@Sun.COMfunction draw_clock_hand
138*8462SApril.Chin@Sun.COM{
139*8462SApril.Chin@Sun.COM	nameref hand=$1
140*8462SApril.Chin@Sun.COM	draw_hand $(( 360.*(hand.val/hand.scale)-90. )) "${hand.ch}" ${hand.length}
141*8462SApril.Chin@Sun.COM	return 0
142*8462SApril.Chin@Sun.COM}
143*8462SApril.Chin@Sun.COM
144*8462SApril.Chin@Sun.COMfunction clear_clock_hand
145*8462SApril.Chin@Sun.COM{
146*8462SApril.Chin@Sun.COM	nameref hand=$1
147*8462SApril.Chin@Sun.COM	draw_hand $(( 360.*(hand.val/hand.scale)-90. )) " " ${hand.length}
148*8462SApril.Chin@Sun.COM	return 0
149*8462SApril.Chin@Sun.COM}
150*8462SApril.Chin@Sun.COM
151*8462SApril.Chin@Sun.COMfunction main_loop
152*8462SApril.Chin@Sun.COM{
153*8462SApril.Chin@Sun.COM	typeset c
154*8462SApril.Chin@Sun.COM
155*8462SApril.Chin@Sun.COM	# note: we can't use subshells when writing to the double-buffer file because this
156*8462SApril.Chin@Sun.COM	# will render the tput value cache useless
157*8462SApril.Chin@Sun.COM	while true ; do
158*8462SApril.Chin@Sun.COM		if ${init_screen} ; then
159*8462SApril.Chin@Sun.COM			init_screen="false"
160*8462SApril.Chin@Sun.COM
161*8462SApril.Chin@Sun.COM			get_term_size termsize || fatal_error $"Couldn't get terminal size."
162*8462SApril.Chin@Sun.COM
163*8462SApril.Chin@Sun.COM			((
164*8462SApril.Chin@Sun.COM				clock.middle_x=termsize.columns/2.-.5 ,
165*8462SApril.Chin@Sun.COM				clock.middle_y=termsize.lines/2.-.5 ,
166*8462SApril.Chin@Sun.COM				clock.len_x=termsize.columns/2-2 ,
167*8462SApril.Chin@Sun.COM				clock.len_y=termsize.lines/2-2 ,
168*8462SApril.Chin@Sun.COM			))
169*8462SApril.Chin@Sun.COM
170*8462SApril.Chin@Sun.COM			{
171*8462SApril.Chin@Sun.COM				clear
172*8462SApril.Chin@Sun.COM				draw_clock
173*8462SApril.Chin@Sun.COM			} >&6
174*8462SApril.Chin@Sun.COM		fi
175*8462SApril.Chin@Sun.COM
176*8462SApril.Chin@Sun.COM		{
177*8462SApril.Chin@Sun.COM			(( ${ date +"hours.val=%H , minutes.val=%M , seconds.val=%S" ; } ))
178*8462SApril.Chin@Sun.COM
179*8462SApril.Chin@Sun.COM			# small trick to get a smooth "analog" flair
180*8462SApril.Chin@Sun.COM			((
181*8462SApril.Chin@Sun.COM				hours.val+=minutes.val/60. ,
182*8462SApril.Chin@Sun.COM				minutes.val+=seconds.val/60.
183*8462SApril.Chin@Sun.COM			))
184*8462SApril.Chin@Sun.COM
185*8462SApril.Chin@Sun.COM			draw_clock_hand seconds
186*8462SApril.Chin@Sun.COM			draw_clock_hand minutes
187*8462SApril.Chin@Sun.COM			draw_clock_hand hours
188*8462SApril.Chin@Sun.COM
189*8462SApril.Chin@Sun.COM			# move cursor to home position
190*8462SApril.Chin@Sun.COM			tput_cup 0 0
191*8462SApril.Chin@Sun.COM		} >&6
192*8462SApril.Chin@Sun.COM
193*8462SApril.Chin@Sun.COM		6<#((0))
194*8462SApril.Chin@Sun.COM		cat <&6
195*8462SApril.Chin@Sun.COM
196*8462SApril.Chin@Sun.COM		redirect 6<&- ;  rm -f "${scratchfile}" ; redirect 6<>"${scratchfile}"
197*8462SApril.Chin@Sun.COM
198*8462SApril.Chin@Sun.COM		c="" ; read -r -t ${update_interval} -N 1 c
199*8462SApril.Chin@Sun.COM		if [[ "$c" != "" ]] ; then
200*8462SApril.Chin@Sun.COM			case "$c" in
201*8462SApril.Chin@Sun.COM				~(Fi)q | $'\E') return 0 ;;
202*8462SApril.Chin@Sun.COM			esac
203*8462SApril.Chin@Sun.COM		fi
204*8462SApril.Chin@Sun.COM
205*8462SApril.Chin@Sun.COM		{
206*8462SApril.Chin@Sun.COM			clear_clock_hand hours
207*8462SApril.Chin@Sun.COM			clear_clock_hand minutes
208*8462SApril.Chin@Sun.COM			clear_clock_hand seconds
209*8462SApril.Chin@Sun.COM		} >&6
210*8462SApril.Chin@Sun.COM	done
211*8462SApril.Chin@Sun.COM}
212*8462SApril.Chin@Sun.COM
213*8462SApril.Chin@Sun.COMfunction usage
214*8462SApril.Chin@Sun.COM{
215*8462SApril.Chin@Sun.COM	OPTIND=0
216*8462SApril.Chin@Sun.COM	getopts -a "${progname}" "${termclock_usage}" OPT '-?'
217*8462SApril.Chin@Sun.COM	exit 2
218*8462SApril.Chin@Sun.COM}
219*8462SApril.Chin@Sun.COM
220*8462SApril.Chin@Sun.COM# program start
221*8462SApril.Chin@Sun.COMbuiltin basename
222*8462SApril.Chin@Sun.COMbuiltin cat
223*8462SApril.Chin@Sun.COMbuiltin date
224*8462SApril.Chin@Sun.COMbuiltin rm
225*8462SApril.Chin@Sun.COM
226*8462SApril.Chin@Sun.COMtypeset progname="${ basename "${0}" ; }"
227*8462SApril.Chin@Sun.COM
228*8462SApril.Chin@Sun.COMfloat -r M_PI=3.14159265358979323846
229*8462SApril.Chin@Sun.COM
230*8462SApril.Chin@Sun.COM# terminal size rect
231*8462SApril.Chin@Sun.COMtypeset -C termsize=(
232*8462SApril.Chin@Sun.COM	integer columns=-1
233*8462SApril.Chin@Sun.COM	integer lines=-1
234*8462SApril.Chin@Sun.COM)
235*8462SApril.Chin@Sun.COM
236*8462SApril.Chin@Sun.COMtypeset init_screen="true"
237*8462SApril.Chin@Sun.COM
238*8462SApril.Chin@Sun.COMtypeset -C clock=(
239*8462SApril.Chin@Sun.COM	float   middle_x
240*8462SApril.Chin@Sun.COM	float   middle_y
241*8462SApril.Chin@Sun.COM	integer len_x
242*8462SApril.Chin@Sun.COM	integer len_y
243*8462SApril.Chin@Sun.COM)
244*8462SApril.Chin@Sun.COM
245*8462SApril.Chin@Sun.COM
246*8462SApril.Chin@Sun.COM# set clock properties
247*8462SApril.Chin@Sun.COMtypeset -C seconds=(
248*8462SApril.Chin@Sun.COM	float val
249*8462SApril.Chin@Sun.COM	typeset ch
250*8462SApril.Chin@Sun.COM	float   scale
251*8462SApril.Chin@Sun.COM	integer length )
252*8462SApril.Chin@Sun.COMtypeset -C minutes=(
253*8462SApril.Chin@Sun.COM	float val
254*8462SApril.Chin@Sun.COM	typeset ch
255*8462SApril.Chin@Sun.COM	float   scale
256*8462SApril.Chin@Sun.COM	integer length )
257*8462SApril.Chin@Sun.COMtypeset -C hours=(
258*8462SApril.Chin@Sun.COM	float val
259*8462SApril.Chin@Sun.COM	typeset ch
260*8462SApril.Chin@Sun.COM	float   scale
261*8462SApril.Chin@Sun.COM	integer length )
262*8462SApril.Chin@Sun.COM
263*8462SApril.Chin@Sun.COMseconds.length=90 seconds.scale=60 seconds.ch=$"s"
264*8462SApril.Chin@Sun.COMminutes.length=75 minutes.scale=60 minutes.ch=$"m"
265*8462SApril.Chin@Sun.COMhours.length=50   hours.scale=12   hours.ch=$"h"
266*8462SApril.Chin@Sun.COM
267*8462SApril.Chin@Sun.COMfloat update_interval=0.9
268*8462SApril.Chin@Sun.COM
269*8462SApril.Chin@Sun.COMtypeset -r termclock_usage=$'+
270*8462SApril.Chin@Sun.COM[-?\n@(#)\$Id: termclock (Roland Mainz) 2008-11-04 \$\n]
271*8462SApril.Chin@Sun.COM[-author?Roland Mainz <roland.mainz@nrubsig.org>]
272*8462SApril.Chin@Sun.COM[-author?David Korn <dgk@research.att.com>]
273*8462SApril.Chin@Sun.COM[+NAME?termclock - analog clock for terminals]
274*8462SApril.Chin@Sun.COM[+DESCRIPTION?\btermclock\b is an analog clock for terminals.
275*8462SApril.Chin@Sun.COM        The termclock program displays the time in analog or digital
276*8462SApril.Chin@Sun.COM        form. The time is continuously updated at a frequency which
277*8462SApril.Chin@Sun.COM        may be specified by the user.]
278*8462SApril.Chin@Sun.COM[u:update?Update interval (defaults to 0.9 seconds).]:[interval]
279*8462SApril.Chin@Sun.COM[+SEE ALSO?\bksh93\b(1), \bxclock\b(1)]
280*8462SApril.Chin@Sun.COM'
281*8462SApril.Chin@Sun.COM
282*8462SApril.Chin@Sun.COMwhile getopts -a "${progname}" "${termclock_usage}" OPT ; do
283*8462SApril.Chin@Sun.COM#	printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
284*8462SApril.Chin@Sun.COM	case ${OPT} in
285*8462SApril.Chin@Sun.COM		u)    update_interval=${OPTARG} ;;
286*8462SApril.Chin@Sun.COM		*)    usage ;;
287*8462SApril.Chin@Sun.COM	esac
288*8462SApril.Chin@Sun.COMdone
289*8462SApril.Chin@Sun.COMshift $((OPTIND-1))
290*8462SApril.Chin@Sun.COM
291*8462SApril.Chin@Sun.COM# prechecks
292*8462SApril.Chin@Sun.COMwhich tput >/dev/null   || fatal_error $"tput not found."
293*8462SApril.Chin@Sun.COMwhich mktemp >/dev/null || fatal_error $"mktemp not found."
294*8462SApril.Chin@Sun.COM(( update_interval >= 0. && update_interval <= 7200. )) || fatal_error $"invalid update_interval value."
295*8462SApril.Chin@Sun.COM
296*8462SApril.Chin@Sun.COM# create temporary file for double-buffering and register an EXIT trap
297*8462SApril.Chin@Sun.COM# to remove this file when the shell interpreter exits
298*8462SApril.Chin@Sun.COMscratchfile="${ mktemp "/tmp/termclock.ppid${PPID}_pid$$.XXXXXX" ; }"
299*8462SApril.Chin@Sun.COM[[ "${scratchfile}" != "" ]] || fatal_error $"Could not create temporary file name."
300*8462SApril.Chin@Sun.COMtrap 'rm -f "${scratchfile}"' EXIT
301*8462SApril.Chin@Sun.COMrm -f "${scratchfile}" ; redirect 6<>"${scratchfile}" || fatal_error $"Could not create temporary file."
302*8462SApril.Chin@Sun.COM
303*8462SApril.Chin@Sun.COM# register trap to handle window size changes
304*8462SApril.Chin@Sun.COMtrap 'init_screen="true"' WINCH
305*8462SApril.Chin@Sun.COM
306*8462SApril.Chin@Sun.COMmain_loop
307*8462SApril.Chin@Sun.COM
308*8462SApril.Chin@Sun.COM# exiting - put cursor below clock
309*8462SApril.Chin@Sun.COMtput_cup $((termsize.lines-2)) 0
310*8462SApril.Chin@Sun.COM
311*8462SApril.Chin@Sun.COMexit 0
312*8462SApril.Chin@Sun.COM# EOF.
313