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